Correct openapi ignore mode to filter schemas

Also rename openapi-mode options

* follow-acl -> follow-privileges
* ignore-acl -> ignore-privileges
This commit is contained in:
steve-chavez
2021-06-27 22:48:49 -05:00
committed by Steve Chavez
parent 41d119b19f
commit f3a184af01
21 changed files with 145 additions and 36 deletions
+23 -1
View File
@@ -1,4 +1,4 @@
module Feature.IgnoreAclOpenApiSpec where
module Feature.IgnorePrivOpenApiSpec where
import Control.Lens ((^?))
@@ -32,6 +32,17 @@ spec = describe "OpenAPI Ignore ACL" $ do
liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|]
it "only includes tables that belong to another schema if the Accept-Profile header is used" $ do
r1 <- simpleBody <$> get "/"
let tableKey1 = r1 ^? key "paths" . key "/children"
liftIO $ tableKey1 `shouldBe` Nothing
r2 <- simpleBody <$> request methodGet "/" [("Accept-Profile", "v1")] ""
let tableKey2 = r2 ^? key "paths" . key "/children"
liftIO $ tableKey2 `shouldNotBe` Nothing
describe "RPC" $ do
it "includes privileged function even if user does not have permission" $ do
@@ -41,3 +52,14 @@ spec = describe "OpenAPI Ignore ACL" $ do
. nth 0
liftIO $ funcTag `shouldBe` Just [aesonQQ|"(rpc) privileged_hello"|]
it "only includes functions that belong to another schema if the Accept-Profile header is used" $ do
r1 <- simpleBody <$> get "/"
let funcKey1 = r1 ^? key "paths" . key "/rpc/get_parents_below"
liftIO $ funcKey1 `shouldBe` Nothing
r2 <- simpleBody <$> request methodGet "/" [("Accept-Profile", "v1")] ""
let funcKey2 = r2 ^? key "paths" . key "/rpc/get_parents_below"
liftIO $ funcKey2 `shouldNotBe` Nothing
+65
View File
@@ -0,0 +1,65 @@
module Feature.IgnorePrivOpenApiSpec where
import Control.Lens ((^?))
import Data.Aeson.Lens
import Data.Aeson.QQ
import Network.HTTP.Types
import Network.Wai (Application)
import Network.Wai.Test (SResponse (..))
import Test.Hspec hiding (pendingWith)
import Test.Hspec.Wai
import Protolude hiding (get)
import SpecHelper
spec :: SpecWith ((), Application)
spec = describe "OpenAPI Ignore Privileges" $ do
it "root path returns a valid openapi spec" $ do
validateOpenApiResponse [("Accept", "application/openapi+json")]
request methodHead "/" (acceptHdrs "application/openapi+json") ""
`shouldRespondWith` "" { matchStatus = 200 }
describe "table" $ do
it "includes privileged table even if user does not have permission" $ do
r <- simpleBody <$> get "/"
let tableTag = r ^? key "paths" . key "/authors_only"
. key "post" . key "tags"
. nth 0
liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|]
it "only includes tables that belong to another schema if the Accept-Profile header is used" $ do
r1 <- simpleBody <$> get "/"
let tableKey1 = r1 ^? key "paths" . key "/children"
liftIO $ tableKey1 `shouldBe` Nothing
r2 <- simpleBody <$> request methodGet "/" [("Accept-Profile", "v1")] ""
let tableKey2 = r2 ^? key "paths" . key "/children"
liftIO $ tableKey2 `shouldNotBe` Nothing
describe "RPC" $ do
it "includes privileged function even if user does not have permission" $ do
r <- simpleBody <$> get "/"
let funcTag = r ^? key "paths" . key "/rpc/privileged_hello"
. key "post" . key "tags"
. nth 0
liftIO $ funcTag `shouldBe` Just [aesonQQ|"(rpc) privileged_hello"|]
it "only includes functions that belong to another schema if the Accept-Profile header is used" $ do
r1 <- simpleBody <$> get "/"
let funcKey1 = r1 ^? key "paths" . key "/rpc/get_parents_below"
liftIO $ funcKey1 `shouldBe` Nothing
r2 <- simpleBody <$> request methodGet "/" [("Accept-Profile", "v1")] ""
let funcKey2 = r2 ^? key "paths" . key "/rpc/get_parents_below"
liftIO $ funcKey2 `shouldNotBe` Nothing
+4 -4
View File
@@ -32,7 +32,7 @@ import qualified Feature.DisabledOpenApiSpec
import qualified Feature.EmbedDisambiguationSpec
import qualified Feature.ExtraSearchPathSpec
import qualified Feature.HtmlRawOutputSpec
import qualified Feature.IgnoreAclOpenApiSpec
import qualified Feature.IgnorePrivOpenApiSpec
import qualified Feature.InsertSpec
import qualified Feature.JsonOperatorSpec
import qualified Feature.MultipleSchemaSpec
@@ -95,7 +95,6 @@ main = do
let withApp = app testCfg
maxRowsApp = app testMaxRowsCfg
disabledOpenApi = app testDisabledOpenApiCfg
ignoreAclOpenApi = app testIgnoreAclOpenApiCfg
proxyApp = app testProxyCfg
noJwtApp = app testCfgNoJWT
binaryJwtApp = app testCfgBinaryJWT
@@ -112,6 +111,7 @@ main = do
unicodeApp = appDbs testUnicodeCfg
nonexistentSchemaApp = appDbs testNonexistentSchemaCfg
multipleSchemaApp = appDbs testMultipleSchemaCfg
ignorePrivOpenApi = appDbs testIgnorePrivOpenApiCfg
let analyze :: IO ()
analyze = do
@@ -161,8 +161,8 @@ main = do
describe "Feature.DisabledOpenApiSpec" Feature.DisabledOpenApiSpec.spec
-- this test runs with openapi-mode set to ignore-acl
parallel $ before ignoreAclOpenApi $
describe "Feature.IgnoreAclOpenApiSpec" Feature.IgnoreAclOpenApiSpec.spec
parallel $ before ignorePrivOpenApi $
describe "Feature.IgnorePrivOpenApiSpec" Feature.IgnorePrivOpenApiSpec.spec
-- this test runs with a proxy
parallel $ before proxyApp $
+3 -3
View File
@@ -96,7 +96,7 @@ _baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
, configJwtSecret = secret
, configJwtSecretIsBase64 = False
, configLogLevel = LogCrit
, configOpenApiMode = OAFollowACL
, configOpenApiMode = OAFollowPriv
, configOpenApiServerProxyUri = Nothing
, configRawMediaTypes = []
, configServerHost = "localhost"
@@ -128,8 +128,8 @@ testMaxRowsCfg testDbConn = (testCfg testDbConn) { configDbMaxRows = Just 2 }
testDisabledOpenApiCfg :: Text -> AppConfig
testDisabledOpenApiCfg testDbConn = (testCfg testDbConn) { configOpenApiMode = OADisabled }
testIgnoreAclOpenApiCfg :: Text -> AppConfig
testIgnoreAclOpenApiCfg testDbConn = (testCfg testDbConn) { configOpenApiMode = OAIgnoreACL }
testIgnorePrivOpenApiCfg :: Text -> AppConfig
testIgnorePrivOpenApiCfg testDbConn = (testCfg testDbConn) { configOpenApiMode = OAIgnorePriv, configDbSchemas = fromList ["test", "v1"] }
testProxyCfg :: Text -> AppConfig
testProxyCfg testDbConn = (testCfg testDbConn) { configOpenApiServerProxyUri = Just "https://postgrest.com/openapi.json" }
+1
View File
@@ -59,3 +59,4 @@ ALTER ROLE other_authenticator SET pgrst.db_prepared_statements = 'false';
ALTER ROLE other_authenticator SET pgrst.db_pre_request = 'test.other_custom_headers';
ALTER ROLE other_authenticator SET pgrst.db_max_rows = '100';
ALTER ROLE other_authenticator SET pgrst.db_extra_search_path = 'public, extensions, other';
ALTER ROLE other_authenticator SET pgrst.openapi_mode = 'disabled';
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"aliased\""
jwt-secret = ""
jwt-secret-is-base64 = true
log-level = "error"
openapi-mode = "follow-acl"
openapi-mode = "follow-privileges"
openapi-server-proxy-uri = ""
raw-media-types = ""
server-host = "!4"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = true
log-level = "error"
openapi-mode = "follow-acl"
openapi-mode = "follow-privileges"
openapi-server-proxy-uri = ""
raw-media-types = ""
server-host = "!4"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = true
log-level = "error"
openapi-mode = "follow-acl"
openapi-mode = "follow-privileges"
openapi-server-proxy-uri = ""
raw-media-types = ""
server-host = "!4"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = false
log-level = "error"
openapi-mode = "follow-acl"
openapi-mode = "follow-privileges"
openapi-server-proxy-uri = ""
raw-media-types = ""
server-host = "!4"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"other\".\"role\""
jwt-secret = "ODERREALLYREALLYREALLYREALLYVERYSAFE"
jwt-secret-is-base64 = true
log-level = "info"
openapi-mode = "ignore-acl"
openapi-mode = "disabled"
openapi-server-proxy-uri = "https://otherexample.org/api"
raw-media-types = "application/vnd.pgrst.other-db-config"
server-host = "0.0.0.0"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"a\".\"role\""
jwt-secret = "OVERRIDEREALLYREALLYREALLYREALLYVERYSAFE"
jwt-secret-is-base64 = true
log-level = "info"
openapi-mode = "ignore-acl"
openapi-mode = "ignore-privileges"
openapi-server-proxy-uri = "https://example.org/api"
raw-media-types = "application/vnd.pgrst.db-config"
server-host = "0.0.0.0"
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"user\"[0].\"real-role\""
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
jwt-secret-is-base64 = true
log-level = "info"
openapi-mode = "ignore-acl"
openapi-mode = "ignore-privileges"
openapi-server-proxy-uri = "https://postgrest.org"
raw-media-types = "application/vnd.pgrst.config"
server-host = "0.0.0.0"
+1 -1
View File
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".\"role\""
jwt-secret = ""
jwt-secret-is-base64 = false
log-level = "error"
openapi-mode = "follow-acl"
openapi-mode = "follow-privileges"
openapi-server-proxy-uri = ""
raw-media-types = ""
server-host = "!4"
+1 -1
View File
@@ -19,7 +19,7 @@ PGRST_JWT_ROLE_CLAIM_KEY: '.user[0]."real-role"'
PGRST_JWT_SECRET: c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5
PGRST_JWT_SECRET_IS_BASE64: true
PGRST_LOG_LEVEL: info
PGRST_OPENAPI_MODE: 'ignore-acl'
PGRST_OPENAPI_MODE: 'ignore-privileges'
PGRST_OPENAPI_SERVER_PROXY_URI: 'https://postgrest.org'
PGRST_RAW_MEDIA_TYPES: application/vnd.pgrst.config
PGRST_SERVER_HOST: 0.0.0.0
+1 -1
View File
@@ -17,7 +17,7 @@ jwt-role-claim-key = ".user[0].\"real-role\""
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
jwt-secret-is-base64 = true
log-level = "info"
openapi-mode = "ignore-acl"
openapi-mode = "ignore-privileges"
openapi-server-proxy-uri = "https://postgrest.org"
raw-media-types = "application/vnd.pgrst.config"
server-host = "0.0.0.0"
+5
View File
@@ -174,3 +174,8 @@ invalidroleclaimkeys:
- '.my_role;;domain'
- '.#$$%&$%/'
- '1234'
invalidopenapimodes:
- 'follow-'
- 'ignore-'
- '.#$$%&$%/'
+15
View File
@@ -425,6 +425,21 @@ def test_invalid_role_claim_key(invalidroleclaimkey, defaultenv):
print(line)
@pytest.mark.parametrize("invalidopenapimodes", FIXTURES["invalidopenapimodes"])
def test_invalid_openapi_mode(invalidopenapimodes, defaultenv):
"Given an invalid openapi-mode, Postgrest should exit with a non-zero exit code."
env = {
**defaultenv,
"PGRST_OPENAPI_MODE": invalidopenapimodes,
}
with pytest.raises(PostgrestError):
dump = dumpconfig(CONFIGSDIR / "defaults.config", env=env)
for line in dump.split("\n"):
if line.startswith("openapi-mode"):
print(line)
def test_iat_claim(defaultenv):
"""
A claim with an 'iat' (issued at) attribute should be successful.