diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 9e07d8c41..0655641d6 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -43,6 +43,7 @@ data AppConfig = AppConfig { , configJwtSecret :: Maybe Text , configPool :: Int , configMaxRows :: Maybe Integer + , configReqCheck :: Maybe Text , configQuiet :: Bool } @@ -57,6 +58,7 @@ argParser = AppConfig <*> (optional . map toS <$> strOption) (long "jwt-secret" <> short 'j' <> help "secret used to encrypt and decrypt JWT tokens" <> metavar "SECRET") <*> option auto (long "pool" <> short 'o' <> help "max connections in database pool" <> metavar "COUNT" <> value 10 <> showDefault) <*> (readMay <$> strOption (long "max-rows" <> short 'm' <> help "max rows in response" <> metavar "COUNT" <> value "infinity" <> showDefault)) + <*> (optional . map toS . strOption) (long "pre-request" <> help "schema-qualified name of proc to call to validate requests" <> metavar "FUNCTION") <*> pure False defaultCorsPolicy :: CorsResourcePolicy diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 9f7e92a49..34533b23e 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -32,10 +32,13 @@ runWithClaims conf eClaims app req = JWTMissingSecret -> return $ errResponse status500 "Server lacks JWT secret" JWTClaims claims -> do -- role claim defaults to anon if not specified in jwt - H.sql . mconcat . claimsToSQL $ M.union claims (M.singleton "role" anon) + let setClaims = claimsToSQL (M.union claims (M.singleton "role" anon)) + H.sql (mconcat $ setClaims ++ customReqCheck) app req where anon = String . toS $ configAnonRole conf + customReqCheck = maybeToList $ (\f -> "select " <> toS f <> "();") + <$> configReqCheck conf unauthed message = responseLBS unauthorized401 [ ctToHeader CTApplicationJSON , ( "WWW-Authenticate" diff --git a/test/Feature/AuthSpec.hs b/test/Feature/AuthSpec.hs index 1aa586c2e..ceb2b0378 100644 --- a/test/Feature/AuthSpec.hs +++ b/test/Feature/AuthSpec.hs @@ -103,6 +103,11 @@ spec = describe "authorization" $ do ] } + it "runs a custom request validation proc" $ do + let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYmFkX3JvbGUifQ.ENAiheEOlskpfoT5byj-gKJkOhHKTvETQu1Zso3c4Ts" + request methodGet "/items" [auth] "" + `shouldRespondWith` 400 + it "should fail when jwt contains no claims" $ do let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.e30.lu-rG8aSCiw-aOlN0IxpRGz5r7Jwq7K9r3tuMPUpytI" request methodGet "/authors_only" [auth] "" diff --git a/test/SpecHelper.hs b/test/SpecHelper.hs index c4a800ceb..b07ffe225 100644 --- a/test/SpecHelper.hs +++ b/test/SpecHelper.hs @@ -51,23 +51,23 @@ testDbConn = "postgres://postgrest_test_authenticator@localhost:5432/postgrest_t testCfg :: AppConfig testCfg = - AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (Just "safe") 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (Just "safe") 10 Nothing (Just "test.block_bad_role") True testCfgNoJWT :: AppConfig testCfgNoJWT = - AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 Nothing 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 Nothing 10 Nothing Nothing True testUnicodeCfg :: AppConfig testUnicodeCfg = - AppConfig testDbConn "postgrest_test_anonymous" Nothing "تست" "localhost" 3000 (Just "safe") 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" Nothing "تست" "localhost" 3000 (Just "safe") 10 Nothing Nothing True testLtdRowsCfg :: AppConfig testLtdRowsCfg = - AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (Just "safe") 10 (Just 2) True + AppConfig testDbConn "postgrest_test_anonymous" Nothing "test" "localhost" 3000 (Just "safe") 10 (Just 2) Nothing True testProxyCfg :: AppConfig testProxyCfg = - AppConfig testDbConn "postgrest_test_anonymous" (Just "https://postgrest.com/openapi.json") "test" "localhost" 3000 (Just "safe") 10 Nothing True + AppConfig testDbConn "postgrest_test_anonymous" (Just "https://postgrest.com/openapi.json") "test" "localhost" 3000 (Just "safe") 10 Nothing Nothing True setupDb :: IO () setupDb = do diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 3a94812af..fa590df9f 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -4,7 +4,7 @@ GRANT USAGE ON SCHEMA , test , jwt , "تست" -TO postgrest_test_anonymous; +TO postgrest_test_anonymous, bad_role; -- Schema test objects SET search_path = test, "تست", pg_catalog; diff --git a/test/fixtures/roles.sql b/test/fixtures/roles.sql index a146a8fc8..aa507c87d 100644 --- a/test/fixtures/roles.sql +++ b/test/fixtures/roles.sql @@ -1,7 +1,8 @@ -DROP ROLE IF EXISTS postgrest_test_authenticator, postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author; +DROP ROLE IF EXISTS postgrest_test_authenticator, postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author, bad_role; CREATE ROLE postgrest_test_authenticator WITH login noinherit; CREATE ROLE postgrest_test_anonymous; CREATE ROLE postgrest_test_default_role; CREATE ROLE postgrest_test_author; +CREATE ROLE bad_role; -GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO postgrest_test_authenticator; +GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author, bad_role TO postgrest_test_authenticator; diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 42356ce9a..56d8f6d8e 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -237,6 +237,14 @@ SELECT jwt.sign( ) r; $$; +create function block_bad_role() returns void +language plpgsql as $$ +begin + if current_role = 'bad_role' then + raise invalid_password using message = 'role is not allowed'; + end if; +end +$$; -- -- Name: reveal_big_jwt(); Type: FUNCTION; Schema: test; Owner: -