feat: Make db-anon-role optional

Without db-anon-role, PostgREST will block any anonymous access without hitting the database.

Resolves #1689, Ref #1823
This commit is contained in:
Wolfgang Walther
2022-01-22 15:59:26 +01:00
parent 05b5ecd23b
commit c3ade07ad6
27 changed files with 90 additions and 50 deletions
+1
View File
@@ -14,6 +14,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #1988, Add the current user to the request log on stdout - @DavidLindbom, @wolfgangwalther - #1988, Add the current user to the request log on stdout - @DavidLindbom, @wolfgangwalther
- #1991, Add the ability to run without `db-uri` using libpq's PG environment variables to connect. @wolfgangwalther - #1991, Add the ability to run without `db-uri` using libpq's PG environment variables to connect. @wolfgangwalther
- #1769, Add the ability to run without `db-schemas`, defaulting to `db-schemas=public`. @wolfgangwalther - #1769, Add the ability to run without `db-schemas`, defaulting to `db-schemas=public`. @wolfgangwalther
- #1689, Add the ability to run without `db-anon-role` disabling anonymous access. @wolfgangwalther
### Fixed ### Fixed
+1
View File
@@ -46,6 +46,7 @@ let
} }
'' ''
# previously required settings to make this work with older branches # previously required settings to make this work with older branches
export PGRST_DB_ANON_ROLE="postgrest_test_anonymous"
export PGRST_DB_URI="postgresql://" export PGRST_DB_URI="postgresql://"
export PGRST_DB_SCHEMAS="test" export PGRST_DB_SCHEMAS="test"
-2
View File
@@ -25,7 +25,6 @@ let
"ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])" "ARG_USE_ENV([PGUSER], [postgrest_test_authenticator], [Authenticator PG role])"
"ARG_USE_ENV([PGDATABASE], [postgres], [PG database name])" "ARG_USE_ENV([PGDATABASE], [postgres], [PG database name])"
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])" "ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
"ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [Anonymous PG role])"
]; ];
positionalCompletion = "_command"; positionalCompletion = "_command";
inRootDir = true; inRootDir = true;
@@ -54,7 +53,6 @@ let
export PGUSER export PGUSER
export PGDATABASE export PGDATABASE
export PGRST_DB_SCHEMAS export PGRST_DB_SCHEMAS
export PGRST_DB_ANON_ROLE
log "Initializing database cluster..." log "Initializing database cluster..."
# We try to make the database cluster as independent as possible from the host # We try to make the database cluster as independent as possible from the host
+1
View File
@@ -185,6 +185,7 @@ test-suite spec
Feature.JsonOperatorSpec Feature.JsonOperatorSpec
Feature.LegacyGucsSpec Feature.LegacyGucsSpec
Feature.MultipleSchemaSpec Feature.MultipleSchemaSpec
Feature.NoAnonSpec
Feature.NoJwtSpec Feature.NoJwtSpec
Feature.NonexistentSchemaSpec Feature.NonexistentSchemaSpec
Feature.OpenApiSpec Feature.OpenApiSpec
+1 -1
View File
@@ -208,7 +208,7 @@ postgrestResponse conf@AppConfig{..} maybeDbStructure jsonDbS pgVer pool AuthRes
let handleReq apiReq = handleRequest $ RequestContext conf dbStructure apiReq pgVer let handleReq apiReq = handleRequest $ RequestContext conf dbStructure apiReq pgVer
runDbHandler pool (txMode apiRequest) (authRole /= configDbAnonRole) configDbPreparedStatements . runDbHandler pool (txMode apiRequest) (Just authRole /= configDbAnonRole) configDbPreparedStatements .
Middleware.optionalRollback conf apiRequest $ Middleware.optionalRollback conf apiRequest $
Middleware.runPgLocals conf authClaims authRole handleReq apiRequest jsonDbS pgVer Middleware.runPgLocals conf authClaims authRole handleReq apiRequest jsonDbS pgVer
+16 -15
View File
@@ -32,7 +32,7 @@ import qualified Network.Wai.Middleware.HttpAuth as Wai
import Control.Lens (set) import Control.Lens (set)
import Control.Monad.Except (liftEither) import Control.Monad.Except (liftEither)
import Data.Either.Combinators (mapLeft, mapRight) import Data.Either.Combinators (mapLeft)
import Data.List (lookup) import Data.List (lookup)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import System.IO.Unsafe (unsafePerformIO) import System.IO.Unsafe (unsafePerformIO)
@@ -71,16 +71,17 @@ parseToken AppConfig{..} token time = do
jwtClaimsError JWT.JWTExpired = JwtTokenInvalid "JWT expired" jwtClaimsError JWT.JWTExpired = JwtTokenInvalid "JWT expired"
jwtClaimsError e = JwtTokenInvalid $ show e jwtClaimsError e = JwtTokenInvalid $ show e
parseClaims :: AppConfig -> JSON.Value -> AuthResult parseClaims :: Monad m =>
parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) = AppConfig -> JSON.Value -> ExceptT Error m AuthResult
AuthResult parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) = do
{ authClaims = mclaims & M.insert "role" (JSON.toJSON role) -- role defaults to anon if not specified in jwt
, authRole = role role <- liftEither . maybeToRight JwtTokenRequired $
} unquoted <$> walkJSPath (Just jclaims) configJwtRoleClaimKey <|> configDbAnonRole
return AuthResult
{ authClaims = mclaims & M.insert "role" (JSON.toJSON role)
, authRole = role
}
where where
-- role defaults to anon if not specified in jwt
role = maybe configDbAnonRole unquoted (walkJSPath (Just jclaims) configJwtRoleClaimKey)
walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value walkJSPath :: Maybe JSON.Value -> JSPath -> Maybe JSON.Value
walkJSPath x [] = x walkJSPath x [] = x
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (M.lookup key o) rest walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (M.lookup key o) rest
@@ -91,7 +92,7 @@ parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) =
unquoted (JSON.String t) = t unquoted (JSON.String t) = t
unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v
-- impossible case - just added to please -Wincomplete-patterns -- impossible case - just added to please -Wincomplete-patterns
parseClaims _ _ = AuthResult { authClaims = M.empty, authRole = mempty } parseClaims _ _ = return AuthResult { authClaims = M.empty, authRole = mempty }
-- | Validate authorization header. -- | Validate authorization header.
-- Parse and store JWT claims for future use in the request. -- Parse and store JWT claims for future use in the request.
@@ -101,11 +102,11 @@ middleware appState app req respond = do
time <- getTime appState time <- getTime appState
let token = fromMaybe "" $ Wai.extractBearerAuth =<< lookup HTTP.hAuthorization (Wai.requestHeaders req) let token = fromMaybe "" $ Wai.extractBearerAuth =<< lookup HTTP.hAuthorization (Wai.requestHeaders req)
claims <- runExceptT $ parseToken conf (LBS.fromStrict token) time authResult <- runExceptT $
parseToken conf (LBS.fromStrict token) time >>=
parseClaims conf
let let req' = req { Wai.vault = Wai.vault req & Vault.insert authResultKey authResult }
authResult = mapRight (parseClaims conf) claims
req' = req { Wai.vault = Wai.vault req & Vault.insert authResultKey authResult }
app req' respond app req' respond
authResultKey :: Vault.Key (Either Error AuthResult) authResultKey :: Vault.Key (Either Error AuthResult)
+4 -7
View File
@@ -63,7 +63,7 @@ import Protolude hiding (Proxy, toList)
data AppConfig = AppConfig data AppConfig = AppConfig
{ configAppSettings :: [(Text, Text)] { configAppSettings :: [(Text, Text)]
, configDbAnonRole :: Text , configDbAnonRole :: Maybe Text
, configDbChannel :: Text , configDbChannel :: Text
, configDbChannelEnabled :: Bool , configDbChannelEnabled :: Bool
, configDbExtraSearchPath :: [Text] , configDbExtraSearchPath :: [Text]
@@ -121,7 +121,7 @@ toText conf =
where where
-- apply conf to all pgrst settings -- apply conf to all pgrst settings
pgrstSettings = (\(k, v) -> (k, v conf)) <$> pgrstSettings = (\(k, v) -> (k, v conf)) <$>
[("db-anon-role", q . configDbAnonRole) [("db-anon-role", q . fromMaybe "" . configDbAnonRole)
,("db-channel", q . configDbChannel) ,("db-channel", q . configDbChannel)
,("db-channel-enabled", T.toLower . show . configDbChannelEnabled) ,("db-channel-enabled", T.toLower . show . configDbChannelEnabled)
,("db-extra-search-path", q . T.intercalate "," . configDbExtraSearchPath) ,("db-extra-search-path", q . T.intercalate "," . configDbExtraSearchPath)
@@ -207,7 +207,7 @@ parser :: Maybe FilePath -> Environment -> [(Text, Text)] -> C.Parser C.Config A
parser optPath env dbSettings = parser optPath env dbSettings =
AppConfig AppConfig
<$> parseAppSettings "app.settings" <$> parseAppSettings "app.settings"
<*> reqString "db-anon-role" <*> optString "db-anon-role"
<*> (fromMaybe "pgrst" <$> optString "db-channel") <*> (fromMaybe "pgrst" <$> optString "db-channel")
<*> (fromMaybe True <$> optBool "db-channel-enabled") <*> (fromMaybe True <$> optBool "db-channel-enabled")
<*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path") <*> (maybe ["public"] splitOnCommas <$> optValue "db-extra-search-path")
@@ -322,9 +322,6 @@ parser optPath env dbSettings =
Just v -> pure $ Just v Just v -> pure $ Just v
Nothing -> alias Nothing -> alias
reqString :: C.Key -> C.Parser C.Config Text
reqString k = overrideFromDbOrEnvironment C.required k coerceText
optString :: C.Key -> C.Parser C.Config (Maybe Text) optString :: C.Key -> C.Parser C.Config (Maybe Text)
optString k = mfilter (/= "") <$> overrideFromDbOrEnvironment C.optional k coerceText optString k = mfilter (/= "") <$> overrideFromDbOrEnvironment C.optional k coerceText
@@ -352,7 +349,7 @@ parser optPath env dbSettings =
let dbSettingName = T.pack $ dashToUnderscore <$> toS key in let dbSettingName = T.pack $ dashToUnderscore <$> toS key in
if dbSettingName `notElem` [ if dbSettingName `notElem` [
"server_host", "server_port", "server_unix_socket", "server_unix_socket_mode", "log_level", "server_host", "server_port", "server_unix_socket", "server_unix_socket_mode", "log_level",
"db_anon_role", "db_uri", "db_channel_enabled", "db_channel", "db_pool", "db_pool_timeout", "db_config"] "db_uri", "db_channel_enabled", "db_channel", "db_pool", "db_pool_timeout", "db_config"]
then lookup dbSettingName dbSettings then lookup dbSettingName dbSettings
else Nothing else Nothing
+8
View File
@@ -274,6 +274,7 @@ data Error
| PutRangeNotAllowedError | PutRangeNotAllowedError
| JwtTokenMissing | JwtTokenMissing
| JwtTokenInvalid Text | JwtTokenInvalid Text
| JwtTokenRequired
| SingularityError Integer | SingularityError Integer
| NotFound | NotFound
| ApiRequestError ApiRequestError | ApiRequestError ApiRequestError
@@ -288,6 +289,7 @@ instance PgrstError Error where
status PutRangeNotAllowedError = HTTP.status400 status PutRangeNotAllowedError = HTTP.status400
status JwtTokenMissing = HTTP.status500 status JwtTokenMissing = HTTP.status500
status (JwtTokenInvalid _) = HTTP.unauthorized401 status (JwtTokenInvalid _) = HTTP.unauthorized401
status JwtTokenRequired = HTTP.unauthorized401
status (SingularityError _) = HTTP.status406 status (SingularityError _) = HTTP.status406
status NotFound = HTTP.status404 status NotFound = HTTP.status404
status (PgErr err) = status err status (PgErr err) = status err
@@ -295,6 +297,7 @@ instance PgrstError Error where
headers (SingularityError _) = [ContentType.toHeader CTSingularJSON] headers (SingularityError _) = [ContentType.toHeader CTSingularJSON]
headers (JwtTokenInvalid m) = [ContentType.toHeader CTApplicationJSON, invalidTokenHeader m] headers (JwtTokenInvalid m) = [ContentType.toHeader CTApplicationJSON, invalidTokenHeader m]
headers JwtTokenRequired = [ContentType.toHeader CTApplicationJSON, requiredTokenHeader]
headers (PgErr err) = headers err headers (PgErr err) = headers err
headers (ApiRequestError err) = headers err headers (ApiRequestError err) = headers err
headers _ = [ContentType.toHeader CTApplicationJSON] headers _ = [ContentType.toHeader CTApplicationJSON]
@@ -322,6 +325,8 @@ instance JSON.ToJSON Error where
"message" .= ("Server lacks JWT secret" :: Text)] "message" .= ("Server lacks JWT secret" :: Text)]
toJSON (JwtTokenInvalid message) = JSON.object [ toJSON (JwtTokenInvalid message) = JSON.object [
"message" .= (message :: Text)] "message" .= (message :: Text)]
toJSON JwtTokenRequired = JSON.object [
"message" .= ("Anonymous access is disabled" :: Text)]
toJSON NotFound = JSON.object [] toJSON NotFound = JSON.object []
toJSON (PgErr err) = JSON.toJSON err toJSON (PgErr err) = JSON.toJSON err
toJSON (ApiRequestError err) = JSON.toJSON err toJSON (ApiRequestError err) = JSON.toJSON err
@@ -330,5 +335,8 @@ invalidTokenHeader :: Text -> Header
invalidTokenHeader m = invalidTokenHeader m =
("WWW-Authenticate", "Bearer error=\"invalid_token\", " <> "error_description=" <> encodeUtf8 (show m)) ("WWW-Authenticate", "Bearer error=\"invalid_token\", " <> "error_description=" <> encodeUtf8 (show m))
requiredTokenHeader :: Header
requiredTokenHeader = ("WWW-Authenticate", "Bearer")
singularityError :: (Integral a) => a -> Error singularityError :: (Integral a) => a -> Error
singularityError = SingularityError . toInteger singularityError = SingularityError . toInteger
-2
View File
@@ -1,5 +1,3 @@
db-anon-role = "required"
db-schema = "provided_through_alias" db-schema = "provided_through_alias"
max-rows = 1000 max-rows = 1000
pre-request = "check_alias" pre-request = "check_alias"
-2
View File
@@ -1,5 +1,3 @@
db-anon-role = "required"
db-channel-enabled = "1" db-channel-enabled = "1"
db-prepared-statements = "0" db-prepared-statements = "0"
jwt-secret-is-base64 = "2" jwt-secret-is-base64 = "2"
-2
View File
@@ -1,5 +1,3 @@
db-anon-role = "required"
db-channel-enabled = "true" db-channel-enabled = "true"
db-prepared-statements = "FALSE" db-prepared-statements = "FALSE"
jwt-secret-is-base64 = "\"true\"" jwt-secret-is-base64 = "\"true\""
-1
View File
@@ -1,3 +1,2 @@
db-anon-role = "required"
# Not the default, but only works with PG* variables, which are not set # Not the default, but only works with PG* variables, which are not set
db-config = false db-config = false
+1 -1
View File
@@ -1,4 +1,4 @@
db-anon-role = "required" db-anon-role = ""
db-channel = "pgrst" db-channel = "pgrst"
db-channel-enabled = true db-channel-enabled = true
db-extra-search-path = "public" db-extra-search-path = "public"
@@ -1,4 +1,4 @@
db-anon-role = "required" db-anon-role = ""
db-channel = "pgrst" db-channel = "pgrst"
db-channel-enabled = true db-channel-enabled = true
db-extra-search-path = "public" db-extra-search-path = "public"
@@ -1,4 +1,4 @@
db-anon-role = "required" db-anon-role = ""
db-channel = "pgrst" db-channel = "pgrst"
db-channel-enabled = true db-channel-enabled = true
db-extra-search-path = "public" db-extra-search-path = "public"
+1 -1
View File
@@ -1,4 +1,4 @@
db-anon-role = "required" db-anon-role = ""
db-channel = "pgrst" db-channel = "pgrst"
db-channel-enabled = true db-channel-enabled = true
db-extra-search-path = "public" db-extra-search-path = "public"
@@ -1,4 +1,4 @@
db-anon-role = "postgrest_test_anonymous" db-anon-role = "other"
db-channel = "postgrest" db-channel = "postgrest"
db-channel-enabled = false db-channel-enabled = false
db-extra-search-path = "public,extensions,other" db-extra-search-path = "public,extensions,other"
@@ -1,4 +1,4 @@
db-anon-role = "postgrest_test_anonymous" db-anon-role = "anonymous"
db-channel = "postgrest" db-channel = "postgrest"
db-channel-enabled = false db-channel-enabled = false
db-extra-search-path = "public,extensions,private" db-extra-search-path = "public,extensions,private"
+1 -1
View File
@@ -1,4 +1,4 @@
db-anon-role = "required" db-anon-role = ""
db-channel = "pgrst" db-channel = "pgrst"
db-channel-enabled = true db-channel-enabled = true
db-extra-search-path = "public" db-extra-search-path = "public"
-1
View File
@@ -1,5 +1,4 @@
# tests how config options fall back with invalid types # tests how config options fall back with invalid types
db-anon-role = "required"
# expects string # expects string
app.settings.test = false app.settings.test = false
+2 -1
View File
@@ -7,6 +7,7 @@ ALTER ROLE db_config_authenticator SET pgrst.raw_media_types = 'application/vnd.
ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLY=REALLY=REALLY=REALLY=VERY=SAFE'; ALTER ROLE db_config_authenticator SET pgrst.jwt_secret = 'REALLY=REALLY=REALLY=REALLY=VERY=SAFE';
ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'false'; ALTER ROLE db_config_authenticator SET pgrst.jwt_secret_is_base64 = 'false';
ALTER ROLE db_config_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"'; ALTER ROLE db_config_authenticator SET pgrst.jwt_role_claim_key = '."a"."role"';
ALTER ROLE db_config_authenticator SET pgrst.db_anon_role = 'anonymous';
ALTER ROLE db_config_authenticator SET pgrst.db_tx_end = 'commit-allow-override'; ALTER ROLE db_config_authenticator SET pgrst.db_tx_end = 'commit-allow-override';
ALTER ROLE db_config_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2'; ALTER ROLE db_config_authenticator SET pgrst.db_schemas = 'test, tenant1, tenant2';
ALTER ROLE db_config_authenticator SET pgrst.db_root_spec = 'root'; ALTER ROLE db_config_authenticator SET pgrst.db_root_spec = 'root';
@@ -29,7 +30,6 @@ ALTER ROLE db_config_authenticator SET pgrst.server_port = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket_mode = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.server_unix_socket_mode = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.log_level = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.log_level = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_anon_role = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_uri = 'postgresql://ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_uri = 'postgresql://ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_channel_enabled = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_channel_enabled = 'ignored';
ALTER ROLE db_config_authenticator SET pgrst.db_channel = 'ignored'; ALTER ROLE db_config_authenticator SET pgrst.db_channel = 'ignored';
@@ -45,6 +45,7 @@ ALTER ROLE other_authenticator SET pgrst.raw_media_types = 'application/vnd.pgrs
ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE'; ALTER ROLE other_authenticator SET pgrst.jwt_secret = 'ODERREALLYREALLYREALLYREALLYVERYSAFE';
ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true'; ALTER ROLE other_authenticator SET pgrst.jwt_secret_is_base64 = 'true';
ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"'; ALTER ROLE other_authenticator SET pgrst.jwt_role_claim_key = '."other"."role"';
ALTER ROLE other_authenticator SET pgrst.db_anon_role = 'other';
ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override'; ALTER ROLE other_authenticator SET pgrst.db_tx_end = 'rollback-allow-override';
ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2'; ALTER ROLE other_authenticator SET pgrst.db_schemas = 'test, other_tenant1, other_tenant2';
ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root'; ALTER ROLE other_authenticator SET pgrst.db_root_spec = 'other_root';
+2
View File
@@ -1,6 +1,8 @@
\ir db_config.sql \ir db_config.sql
CREATE ROLE postgrest_test_anonymous; CREATE ROLE postgrest_test_anonymous;
ALTER ROLE :USER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
CREATE ROLE postgrest_test_author; CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_author TO :USER; GRANT postgrest_test_anonymous, postgrest_test_author TO :USER;
-4
View File
@@ -23,10 +23,6 @@ cli:
- name: invalid config file - name: invalid config file
expect: error expect: error
args: ['test/io-tests/configs/invalid.yaml'] args: ['test/io-tests/configs/invalid.yaml']
# failures: required config options
- name: missing db-anon-role
expect: error
env:
# failures: wrong config values # failures: wrong config values
- name: invalid server-unix-socket-mode not octal - name: invalid server-unix-socket-mode not octal
expect: error expect: error
+7 -4
View File
@@ -92,8 +92,7 @@ def defaultenv():
"PGDATABASE": os.environ["PGDATABASE"], "PGDATABASE": os.environ["PGDATABASE"],
"PGHOST": os.environ["PGHOST"], "PGHOST": os.environ["PGHOST"],
"PGUSER": os.environ["PGUSER"], "PGUSER": os.environ["PGUSER"],
"PGRST_DB_ANON_ROLE": os.environ["PGRST_DB_ANON_ROLE"], "PGRST_DB_CONFIG": "true",
"PGRST_DB_CONFIG": "false",
"PGRST_LOG_LEVEL": "info", "PGRST_LOG_LEVEL": "info",
} }
@@ -384,6 +383,7 @@ def test_read_secret_from_file(secretpath, defaultenv):
with run(stdin=secret, env=env) as postgrest: with run(stdin=secret, env=env) as postgrest:
response = postgrest.session.get("/authors_only", headers=headers) response = postgrest.session.get("/authors_only", headers=headers)
print(response.text)
assert response.status_code == 200 assert response.status_code == 200
@@ -600,6 +600,8 @@ def test_jwt_secret_external_file_reload(tmp_path, defaultenv):
**defaultenv, **defaultenv,
"PGRST_JWT_SECRET": f"@{external_secret_file}", "PGRST_JWT_SECRET": f"@{external_secret_file}",
"PGRST_DB_CHANNEL_ENABLED": "true", "PGRST_DB_CHANNEL_ENABLED": "true",
"PGRST_DB_CONFIG": "false",
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous", # required for NOTIFY
} }
with run(env=env) as postgrest: with run(env=env) as postgrest:
@@ -609,7 +611,7 @@ def test_jwt_secret_external_file_reload(tmp_path, defaultenv):
# change external file # change external file
external_secret_file.write_text(SECRET) external_secret_file.write_text(SECRET)
# SIGUSR1 doesn't reload external files # SIGUSR1 doesn't reload external files, at least when db-config=false
postgrest.process.send_signal(signal.SIGUSR1) postgrest.process.send_signal(signal.SIGUSR1)
time.sleep(0.1) time.sleep(0.1)
@@ -627,7 +629,8 @@ def test_jwt_secret_external_file_reload(tmp_path, defaultenv):
external_secret_file.write_text("invalid" * 5) external_secret_file.write_text("invalid" * 5)
# reload config and external file with NOTIFY # reload config and external file with NOTIFY
postgrest.session.post("/rpc/reload_pgrst_config") response = postgrest.session.post("/rpc/reload_pgrst_config")
assert response.status_code == 200
time.sleep(0.1) time.sleep(0.1)
response = postgrest.session.get("/authors_only", headers=headers) response = postgrest.session.get("/authors_only", headers=headers)
+30
View File
@@ -0,0 +1,30 @@
module Feature.NoAnonSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "server started without anonymous role" $ do
it "behaves normally on attempted auth" $ do
-- token body: { "role": "postgrest_test_author" }
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"
request methodGet "/authors_only"
[auth]
""
`shouldRespondWith`
200
it "responds with error when user does not attempt auth" $
get "/items"
`shouldRespondWith`
[json|{"message":"Anonymous access is disabled"}|]
{ matchStatus = 401
, matchHeaders = ["WWW-Authenticate" <:> "Bearer"]
}
+6
View File
@@ -37,6 +37,7 @@ import qualified Feature.InsertSpec
import qualified Feature.JsonOperatorSpec import qualified Feature.JsonOperatorSpec
import qualified Feature.LegacyGucsSpec import qualified Feature.LegacyGucsSpec
import qualified Feature.MultipleSchemaSpec import qualified Feature.MultipleSchemaSpec
import qualified Feature.NoAnonSpec
import qualified Feature.NoJwtSpec import qualified Feature.NoJwtSpec
import qualified Feature.NonexistentSchemaSpec import qualified Feature.NonexistentSchemaSpec
import qualified Feature.OpenApiSpec import qualified Feature.OpenApiSpec
@@ -96,6 +97,7 @@ main = do
maxRowsApp = app testMaxRowsCfg maxRowsApp = app testMaxRowsCfg
disabledOpenApi = app testDisabledOpenApiCfg disabledOpenApi = app testDisabledOpenApiCfg
proxyApp = app testProxyCfg proxyApp = app testProxyCfg
noAnonApp = app testCfgNoAnon
noJwtApp = app testCfgNoJWT noJwtApp = app testCfgNoJWT
binaryJwtApp = app testCfgBinaryJWT binaryJwtApp = app testCfgBinaryJWT
audJwtApp = app testCfgAudienceJWT audJwtApp = app testCfgAudienceJWT
@@ -170,6 +172,10 @@ main = do
parallel $ before proxyApp $ parallel $ before proxyApp $
describe "Feature.ProxySpec" Feature.ProxySpec.spec describe "Feature.ProxySpec" Feature.ProxySpec.spec
-- this test runs without an anonymous role
parallel $ before noAnonApp $
describe "Feature.NoAnonSpec" Feature.NoAnonSpec.spec
-- this test runs without a JWT secret -- this test runs without a JWT secret
parallel $ before noJwtApp $ parallel $ before noJwtApp $
describe "Feature.NoJwtSpec" Feature.NoJwtSpec.spec describe "Feature.NoJwtSpec" Feature.NoJwtSpec.spec
+4 -1
View File
@@ -76,7 +76,7 @@ baseCfg :: AppConfig
baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
AppConfig { AppConfig {
configAppSettings = [ ("app.settings.app_host", "localhost") , ("app.settings.external_api_secret", "0123456789abcdef") ] configAppSettings = [ ("app.settings.app_host", "localhost") , ("app.settings.external_api_secret", "0123456789abcdef") ]
, configDbAnonRole = "postgrest_test_anonymous" , configDbAnonRole = Just "postgrest_test_anonymous"
, configDbChannel = mempty , configDbChannel = mempty
, configDbChannelEnabled = True , configDbChannelEnabled = True
, configDbExtraSearchPath = [] , configDbExtraSearchPath = []
@@ -118,6 +118,9 @@ testCfgDisallowRollback = baseCfg { configDbTxAllowOverride = False, configDbTxR
testCfgForceRollback :: AppConfig testCfgForceRollback :: AppConfig
testCfgForceRollback = baseCfg { configDbTxAllowOverride = False, configDbTxRollbackAll = True } testCfgForceRollback = baseCfg { configDbTxAllowOverride = False, configDbTxRollbackAll = True }
testCfgNoAnon :: AppConfig
testCfgNoAnon = baseCfg { configDbAnonRole = Nothing }
testCfgNoJWT :: AppConfig testCfgNoJWT :: AppConfig
testCfgNoJWT = baseCfg { configJwtSecret = Nothing, configJWKS = Nothing } testCfgNoJWT = baseCfg { configJwtSecret = Nothing, configJWKS = Nothing }