feat: support string comparison for jwt-role-claim-key
This commit is contained in:
committed by
Steve Chavez
parent
2df167637d
commit
af6b79d4d7
@@ -12,6 +12,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther
|
- #2858, Performance improvements when calling RPCs via GET using indexes in more cases - @wolfgangwalther
|
||||||
- #3560, Log resolved host in "Listening on ..." messages - @develop7
|
- #3560, Log resolved host in "Listening on ..." messages - @develop7
|
||||||
- #3727, Log maximum pool size - @steve-chavez
|
- #3727, Log maximum pool size - @steve-chavez
|
||||||
|
- #1536, Add string comparison feature for jwt-role-claim-key - @taimoorzaeem
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
+6
-1
@@ -27,10 +27,12 @@ CSV
|
|||||||
durations
|
durations
|
||||||
DDL
|
DDL
|
||||||
DOM
|
DOM
|
||||||
|
DSL
|
||||||
DevOps
|
DevOps
|
||||||
dockerize
|
dockerize
|
||||||
enum
|
enum
|
||||||
Enums
|
Enums
|
||||||
|
Entra
|
||||||
eq
|
eq
|
||||||
ETH
|
ETH
|
||||||
Ethereum
|
Ethereum
|
||||||
@@ -68,9 +70,11 @@ isdistinct
|
|||||||
JS
|
JS
|
||||||
js
|
js
|
||||||
JSON
|
JSON
|
||||||
|
JSPath
|
||||||
JWK
|
JWK
|
||||||
JWT
|
JWT
|
||||||
jwt
|
jwt
|
||||||
|
Keycloak
|
||||||
Kubernetes
|
Kubernetes
|
||||||
localhost
|
localhost
|
||||||
login
|
login
|
||||||
@@ -94,10 +98,11 @@ npm
|
|||||||
nxl
|
nxl
|
||||||
nxr
|
nxr
|
||||||
OAuth
|
OAuth
|
||||||
|
ORM
|
||||||
Observability
|
Observability
|
||||||
|
Okta
|
||||||
OpenAPI
|
OpenAPI
|
||||||
openapi
|
openapi
|
||||||
ORM
|
|
||||||
ov
|
ov
|
||||||
parametrized
|
parametrized
|
||||||
passphrase
|
passphrase
|
||||||
|
|||||||
@@ -161,6 +161,43 @@ JWT Claims Validation
|
|||||||
|
|
||||||
PostgREST honors the :code:`exp` claim for token expiration, rejecting expired tokens.
|
PostgREST honors the :code:`exp` claim for token expiration, rejecting expired tokens.
|
||||||
|
|
||||||
|
.. _jwt_role_claim_key_extract:
|
||||||
|
|
||||||
|
JWT Role Claim Key Extraction
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A JSPath DSL that specifies the location of the :code:`role` key in the JWT claims. This can be used to consume a JWT provided by a third party service like Auth0, Okta, Microsoft Entra or Keycloak.
|
||||||
|
|
||||||
|
The DSL follows the `JSONPath <https://goessner.net/articles/JsonPath/>`_ expression grammar with extended string comparison operators. Supported operators are:
|
||||||
|
|
||||||
|
- ``==`` selects the first array element that exactly matches the right operand
|
||||||
|
- ``!=`` selects the first array element that does not match the right operand
|
||||||
|
- ``^==`` selects the first array element that starts with the right operand
|
||||||
|
- ``==^`` selects the first array element that ends with the right operand
|
||||||
|
- ``*==`` selects the first array element that contains the right operand
|
||||||
|
|
||||||
|
Usage examples:
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
# {"postgrest":{"roles": ["other", "author"]}}
|
||||||
|
# the DSL accepts characters that are alphanumerical or one of "_$@" as keys
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[1]"
|
||||||
|
|
||||||
|
# {"https://www.example.com/role": { "key": "author" }}
|
||||||
|
# non-alphanumerical characters can go inside quotes(escaped in the config value)
|
||||||
|
jwt-role-claim-key = ".\"https://www.example.com/role\".key"
|
||||||
|
|
||||||
|
# {"postgrest":{"roles": ["other", "author"]}}
|
||||||
|
# `@` represents the current element in the array
|
||||||
|
# all the these match the string "author"
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[?(@ == \"author\")]"
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[?(@ != \"other\")]"
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[?(@ ^== \"aut\")]"
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[?(@ ==^ \"hor\")]"
|
||||||
|
jwt-role-claim-key = ".postgrest.roles[?(@ *== \"utho\")]"
|
||||||
|
|
||||||
|
|
||||||
JWT Security
|
JWT Security
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|||||||
@@ -620,17 +620,7 @@ jwt-role-claim-key
|
|||||||
|
|
||||||
*For backwards compatibility, this config parameter is also available without prefix as "role-claim-key".*
|
*For backwards compatibility, this config parameter is also available without prefix as "role-claim-key".*
|
||||||
|
|
||||||
A JSPath DSL that specifies the location of the :code:`role` key in the JWT claims. This can be used to consume a JWT provided by a third party service like Auth0, Okta or Keycloak. Usage examples:
|
See :ref:`jwt_role_claim_key_extract` on how to specify key paths and usage examples.
|
||||||
|
|
||||||
.. code:: bash
|
|
||||||
|
|
||||||
# {"postgrest":{"roles": ["other", "author"]}}
|
|
||||||
# the DSL accepts characters that are alphanumerical or one of "_$@" as keys
|
|
||||||
jwt-role-claim-key = ".postgrest.roles[1]"
|
|
||||||
|
|
||||||
# {"https://www.example.com/role": { "key": "author }}
|
|
||||||
# non-alphanumerical characters can go inside quotes(escaped in the config value)
|
|
||||||
jwt-role-claim-key = ".\"https://www.example.com/role\".key"
|
|
||||||
|
|
||||||
.. _jwt-secret:
|
.. _jwt-secret:
|
||||||
|
|
||||||
|
|||||||
+15
-1
@@ -27,6 +27,7 @@ import qualified Data.ByteString as BS
|
|||||||
import qualified Data.ByteString.Lazy.Char8 as LBS
|
import qualified Data.ByteString.Lazy.Char8 as LBS
|
||||||
import qualified Data.Cache as C
|
import qualified Data.Cache as C
|
||||||
import qualified Data.Scientific as Sci
|
import qualified Data.Scientific as Sci
|
||||||
|
import qualified Data.Text as T
|
||||||
import qualified Data.Vault.Lazy as Vault
|
import qualified Data.Vault.Lazy as Vault
|
||||||
import qualified Data.Vector as V
|
import qualified Data.Vector as V
|
||||||
import qualified Jose.Jwk as JWT
|
import qualified Jose.Jwk as JWT
|
||||||
@@ -46,7 +47,8 @@ import System.TimeIt (timeItT)
|
|||||||
|
|
||||||
import PostgREST.AppState (AppState, AuthResult (..), getConfig,
|
import PostgREST.AppState (AppState, AuthResult (..), getConfig,
|
||||||
getJwtCache, getTime)
|
getJwtCache, getTime)
|
||||||
import PostgREST.Config (AppConfig (..), JSPath, JSPathExp (..))
|
import PostgREST.Config (AppConfig (..), FilterExp (..), JSPath,
|
||||||
|
JSPathExp (..))
|
||||||
import PostgREST.Error (Error (..))
|
import PostgREST.Error (Error (..))
|
||||||
|
|
||||||
import Protolude
|
import Protolude
|
||||||
@@ -121,8 +123,20 @@ parseClaims AppConfig{..} jclaims@(JSON.Object mclaims) = do
|
|||||||
walkJSPath x [] = x
|
walkJSPath x [] = x
|
||||||
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (KM.lookup (K.fromText key) o) rest
|
walkJSPath (Just (JSON.Object o)) (JSPKey key:rest) = walkJSPath (KM.lookup (K.fromText key) o) rest
|
||||||
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
|
walkJSPath (Just (JSON.Array ar)) (JSPIdx idx:rest) = walkJSPath (ar V.!? idx) rest
|
||||||
|
walkJSPath (Just (JSON.Array ar)) [JSPFilter (EqualsCond txt)] = findFirstMatch (==) txt ar
|
||||||
|
walkJSPath (Just (JSON.Array ar)) [JSPFilter (NotEqualsCond txt)] = findFirstMatch (/=) txt ar
|
||||||
|
walkJSPath (Just (JSON.Array ar)) [JSPFilter (StartsWithCond txt)] = findFirstMatch T.isPrefixOf txt ar
|
||||||
|
walkJSPath (Just (JSON.Array ar)) [JSPFilter (EndsWithCond txt)] = findFirstMatch T.isSuffixOf txt ar
|
||||||
|
walkJSPath (Just (JSON.Array ar)) [JSPFilter (ContainsCond txt)] = findFirstMatch T.isInfixOf txt ar
|
||||||
walkJSPath _ _ = Nothing
|
walkJSPath _ _ = Nothing
|
||||||
|
|
||||||
|
findFirstMatch matchWith pattern = foldr checkMatch Nothing
|
||||||
|
where
|
||||||
|
checkMatch (JSON.String txt) acc
|
||||||
|
| pattern `matchWith` txt = Just $ JSON.String txt
|
||||||
|
| otherwise = acc
|
||||||
|
checkMatch _ acc = acc
|
||||||
|
|
||||||
unquoted :: JSON.Value -> BS.ByteString
|
unquoted :: JSON.Value -> BS.ByteString
|
||||||
unquoted (JSON.String t) = encodeUtf8 t
|
unquoted (JSON.String t) = encodeUtf8 t
|
||||||
unquoted v = LBS.toStrict $ JSON.encode v
|
unquoted v = LBS.toStrict $ JSON.encode v
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ module PostgREST.Config
|
|||||||
, Environment
|
, Environment
|
||||||
, JSPath
|
, JSPath
|
||||||
, JSPathExp(..)
|
, JSPathExp(..)
|
||||||
|
, FilterExp(..)
|
||||||
, LogLevel(..)
|
, LogLevel(..)
|
||||||
, OpenAPIMode(..)
|
, OpenAPIMode(..)
|
||||||
, Proxy(..)
|
, Proxy(..)
|
||||||
@@ -54,8 +55,9 @@ import System.Posix.Types (FileMode)
|
|||||||
|
|
||||||
import PostgREST.Config.Database (RoleIsolationLvl,
|
import PostgREST.Config.Database (RoleIsolationLvl,
|
||||||
RoleSettings)
|
RoleSettings)
|
||||||
import PostgREST.Config.JSPath (JSPath, JSPathExp (..),
|
import PostgREST.Config.JSPath (FilterExp (..), JSPath,
|
||||||
dumpJSPath, pRoleClaimKey)
|
JSPathExp (..), dumpJSPath,
|
||||||
|
pRoleClaimKey)
|
||||||
import PostgREST.Config.Proxy (Proxy (..),
|
import PostgREST.Config.Proxy (Proxy (..),
|
||||||
isMalformedProxyUri, toURI)
|
isMalformedProxyUri, toURI)
|
||||||
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier, dumpQi,
|
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier, dumpQi,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
{-# OPTIONS_GHC -Wno-unused-do-bind #-}
|
||||||
module PostgREST.Config.JSPath
|
module PostgREST.Config.JSPath
|
||||||
( JSPath
|
( JSPath
|
||||||
, JSPathExp(..)
|
, JSPathExp(..)
|
||||||
|
, FilterExp(..)
|
||||||
, dumpJSPath
|
, dumpJSPath
|
||||||
, pRoleClaimKey
|
, pRoleClaimKey
|
||||||
) where
|
) where
|
||||||
@@ -14,18 +16,37 @@ import Text.Read (read)
|
|||||||
import Protolude
|
import Protolude
|
||||||
|
|
||||||
|
|
||||||
-- | full jspath, e.g. .property[0].attr.detail
|
-- | full jspath, e.g. .property[0].attr.detail[?(@ == "role1")]
|
||||||
type JSPath = [JSPathExp]
|
type JSPath = [JSPathExp]
|
||||||
|
|
||||||
-- | jspath expression, e.g. .property, .property[0] or ."property-dash"
|
-- NOTE: We only accept one JSPFilter expr (at the end of input)
|
||||||
|
-- | jspath expression
|
||||||
data JSPathExp
|
data JSPathExp
|
||||||
= JSPKey Text
|
= JSPKey Text -- .property or ."property-dash"
|
||||||
| JSPIdx Int
|
| JSPIdx Int -- [0]
|
||||||
|
| JSPFilter FilterExp -- [?(@ == "match")]
|
||||||
|
|
||||||
|
data FilterExp
|
||||||
|
= EqualsCond Text
|
||||||
|
| NotEqualsCond Text
|
||||||
|
| StartsWithCond Text
|
||||||
|
| EndsWithCond Text
|
||||||
|
| ContainsCond Text
|
||||||
|
|
||||||
dumpJSPath :: JSPathExp -> Text
|
dumpJSPath :: JSPathExp -> Text
|
||||||
-- TODO: this needs to be quoted properly for special chars
|
-- TODO: this needs to be quoted properly for special chars
|
||||||
dumpJSPath (JSPKey k) = "." <> show k
|
dumpJSPath (JSPKey k) = "." <> show k
|
||||||
dumpJSPath (JSPIdx i) = "[" <> show i <> "]"
|
dumpJSPath (JSPIdx i) = "[" <> show i <> "]"
|
||||||
|
dumpJSPath (JSPFilter cond) = "[?(@" <> expr <> ")]"
|
||||||
|
where
|
||||||
|
expr =
|
||||||
|
case cond of
|
||||||
|
EqualsCond text -> " == " <> show text
|
||||||
|
NotEqualsCond text -> " != " <> show text
|
||||||
|
StartsWithCond text -> " ^== " <> show text
|
||||||
|
EndsWithCond text -> " ==^ " <> show text
|
||||||
|
ContainsCond text -> " *== " <> show text
|
||||||
|
|
||||||
|
|
||||||
-- Used for the config value "role-claim-key"
|
-- Used for the config value "role-claim-key"
|
||||||
pRoleClaimKey :: Text -> Either Text JSPath
|
pRoleClaimKey :: Text -> Either Text JSPath
|
||||||
@@ -33,19 +54,47 @@ pRoleClaimKey selStr =
|
|||||||
mapLeft show $ P.parse pJSPath ("failed to parse role-claim-key value (" <> toS selStr <> ")") (toS selStr)
|
mapLeft show $ P.parse pJSPath ("failed to parse role-claim-key value (" <> toS selStr <> ")") (toS selStr)
|
||||||
|
|
||||||
pJSPath :: P.Parser JSPath
|
pJSPath :: P.Parser JSPath
|
||||||
pJSPath = toJSPath <$> (period *> pPath `P.sepBy` period <* P.eof)
|
pJSPath = P.many1 pJSPathExp <* P.eof
|
||||||
where
|
|
||||||
toJSPath :: [(Text, Maybe Int)] -> JSPath
|
|
||||||
toJSPath = concatMap (\(key, idx) -> JSPKey key : maybeToList (JSPIdx <$> idx))
|
|
||||||
period = P.char '.' <?> "period (.)"
|
|
||||||
pPath :: P.Parser (Text, Maybe Int)
|
|
||||||
pPath = (,) <$> pJSPKey <*> P.optionMaybe pJSPIdx
|
|
||||||
|
|
||||||
pJSPKey :: P.Parser Text
|
pJSPathExp :: P.Parser JSPathExp
|
||||||
pJSPKey = toS <$> P.many1 (P.alphaNum <|> P.oneOf "_$@") <|> pQuotedValue <?> "attribute name [a..z0..9_$@])"
|
pJSPathExp = pJSPKey <|> pJSPFilter <|> pJSPIdx
|
||||||
|
|
||||||
pJSPIdx :: P.Parser Int
|
pJSPKey :: P.Parser JSPathExp
|
||||||
pJSPIdx = P.char '[' *> (read <$> P.many1 P.digit) <* P.char ']' <?> "array index [0..n]"
|
pJSPKey = do
|
||||||
|
P.char '.'
|
||||||
|
val <- toS <$> P.many1 (P.alphaNum <|> P.oneOf "_$@") <|> pQuotedValue
|
||||||
|
return (JSPKey val) <?> "pJSPKey: JSPath attribute key"
|
||||||
|
|
||||||
|
pJSPIdx :: P.Parser JSPathExp
|
||||||
|
pJSPIdx = do
|
||||||
|
P.char '['
|
||||||
|
num <- read <$> P.many1 P.digit
|
||||||
|
P.char ']'
|
||||||
|
return (JSPIdx num) <?> "pJSPIdx: JSPath array index"
|
||||||
|
|
||||||
|
pJSPFilter :: P.Parser JSPathExp
|
||||||
|
pJSPFilter = do
|
||||||
|
P.try $ P.string "[?("
|
||||||
|
condition <- pFilterConditionParser
|
||||||
|
P.char ')'
|
||||||
|
P.char ']'
|
||||||
|
P.eof -- this should be the last jspath expression
|
||||||
|
return (JSPFilter condition) <?> "pJSPFilter: JSPath filter exp"
|
||||||
|
|
||||||
|
pFilterConditionParser :: P.Parser FilterExp
|
||||||
|
pFilterConditionParser = do
|
||||||
|
P.char '@'
|
||||||
|
P.spaces
|
||||||
|
filt <- matchOperator
|
||||||
|
P.spaces
|
||||||
|
filt <$> pQuotedValue
|
||||||
|
where
|
||||||
|
matchOperator =
|
||||||
|
P.try (P.string "==^" $> EndsWithCond)
|
||||||
|
<|> P.try (P.string "==" $> EqualsCond)
|
||||||
|
<|> P.try (P.string "!=" $> NotEqualsCond)
|
||||||
|
<|> P.try (P.string "^==" $> StartsWithCond)
|
||||||
|
<|> P.try (P.string "*==" $> ContainsCond)
|
||||||
|
|
||||||
pQuotedValue :: P.Parser Text
|
pQuotedValue :: P.Parser Text
|
||||||
pQuotedValue = toS <$> (P.char '"' *> P.many (P.noneOf "\"") <* P.char '"')
|
pQuotedValue = toS <$> (P.char '"' *> P.many (P.noneOf "\"") <* P.char '"')
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
db-aggregates-enabled = false
|
||||||
|
db-anon-role = ""
|
||||||
|
db-channel = "pgrst"
|
||||||
|
db-channel-enabled = true
|
||||||
|
db-extra-search-path = "public"
|
||||||
|
db-hoisted-tx-settings = "statement_timeout,plan_filter.statement_cost_limit,default_transaction_isolation"
|
||||||
|
db-max-rows = ""
|
||||||
|
db-plan-enabled = false
|
||||||
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
|
db-pool-max-lifetime = 1800
|
||||||
|
db-pool-max-idletime = 30
|
||||||
|
db-pool-automatic-recovery = true
|
||||||
|
db-pre-request = ""
|
||||||
|
db-prepared-statements = true
|
||||||
|
db-root-spec = ""
|
||||||
|
db-schemas = "public"
|
||||||
|
db-config = true
|
||||||
|
db-pre-config = ""
|
||||||
|
db-tx-end = "commit"
|
||||||
|
db-uri = "postgresql://"
|
||||||
|
jwt-aud = ""
|
||||||
|
jwt-role-claim-key = ".\"roles\"[?(@ == \"role1\")]"
|
||||||
|
jwt-secret = ""
|
||||||
|
jwt-secret-is-base64 = false
|
||||||
|
jwt-cache-max-lifetime = 0
|
||||||
|
log-level = "error"
|
||||||
|
openapi-mode = "follow-privileges"
|
||||||
|
openapi-security-active = false
|
||||||
|
openapi-server-proxy-uri = ""
|
||||||
|
server-cors-allowed-origins = ""
|
||||||
|
server-host = "!4"
|
||||||
|
server-port = 3000
|
||||||
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
|
server-unix-socket = ""
|
||||||
|
server-unix-socket-mode = "660"
|
||||||
|
admin-server-host = "!4"
|
||||||
|
admin-server-port = ""
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
db-aggregates-enabled = false
|
||||||
|
db-anon-role = ""
|
||||||
|
db-channel = "pgrst"
|
||||||
|
db-channel-enabled = true
|
||||||
|
db-extra-search-path = "public"
|
||||||
|
db-hoisted-tx-settings = "statement_timeout,plan_filter.statement_cost_limit,default_transaction_isolation"
|
||||||
|
db-max-rows = ""
|
||||||
|
db-plan-enabled = false
|
||||||
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
|
db-pool-max-lifetime = 1800
|
||||||
|
db-pool-max-idletime = 30
|
||||||
|
db-pool-automatic-recovery = true
|
||||||
|
db-pre-request = ""
|
||||||
|
db-prepared-statements = true
|
||||||
|
db-root-spec = ""
|
||||||
|
db-schemas = "public"
|
||||||
|
db-config = true
|
||||||
|
db-pre-config = ""
|
||||||
|
db-tx-end = "commit"
|
||||||
|
db-uri = "postgresql://"
|
||||||
|
jwt-aud = ""
|
||||||
|
jwt-role-claim-key = ".\"roles\"[?(@ != \"role1\")]"
|
||||||
|
jwt-secret = ""
|
||||||
|
jwt-secret-is-base64 = false
|
||||||
|
jwt-cache-max-lifetime = 0
|
||||||
|
log-level = "error"
|
||||||
|
openapi-mode = "follow-privileges"
|
||||||
|
openapi-security-active = false
|
||||||
|
openapi-server-proxy-uri = ""
|
||||||
|
server-cors-allowed-origins = ""
|
||||||
|
server-host = "!4"
|
||||||
|
server-port = 3000
|
||||||
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
|
server-unix-socket = ""
|
||||||
|
server-unix-socket-mode = "660"
|
||||||
|
admin-server-host = "!4"
|
||||||
|
admin-server-port = ""
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
db-aggregates-enabled = false
|
||||||
|
db-anon-role = ""
|
||||||
|
db-channel = "pgrst"
|
||||||
|
db-channel-enabled = true
|
||||||
|
db-extra-search-path = "public"
|
||||||
|
db-hoisted-tx-settings = "statement_timeout,plan_filter.statement_cost_limit,default_transaction_isolation"
|
||||||
|
db-max-rows = ""
|
||||||
|
db-plan-enabled = false
|
||||||
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
|
db-pool-max-lifetime = 1800
|
||||||
|
db-pool-max-idletime = 30
|
||||||
|
db-pool-automatic-recovery = true
|
||||||
|
db-pre-request = ""
|
||||||
|
db-prepared-statements = true
|
||||||
|
db-root-spec = ""
|
||||||
|
db-schemas = "public"
|
||||||
|
db-config = true
|
||||||
|
db-pre-config = ""
|
||||||
|
db-tx-end = "commit"
|
||||||
|
db-uri = "postgresql://"
|
||||||
|
jwt-aud = ""
|
||||||
|
jwt-role-claim-key = ".\"roles\"[?(@ ^== \"role1\")]"
|
||||||
|
jwt-secret = ""
|
||||||
|
jwt-secret-is-base64 = false
|
||||||
|
jwt-cache-max-lifetime = 0
|
||||||
|
log-level = "error"
|
||||||
|
openapi-mode = "follow-privileges"
|
||||||
|
openapi-security-active = false
|
||||||
|
openapi-server-proxy-uri = ""
|
||||||
|
server-cors-allowed-origins = ""
|
||||||
|
server-host = "!4"
|
||||||
|
server-port = 3000
|
||||||
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
|
server-unix-socket = ""
|
||||||
|
server-unix-socket-mode = "660"
|
||||||
|
admin-server-host = "!4"
|
||||||
|
admin-server-port = ""
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
db-aggregates-enabled = false
|
||||||
|
db-anon-role = ""
|
||||||
|
db-channel = "pgrst"
|
||||||
|
db-channel-enabled = true
|
||||||
|
db-extra-search-path = "public"
|
||||||
|
db-hoisted-tx-settings = "statement_timeout,plan_filter.statement_cost_limit,default_transaction_isolation"
|
||||||
|
db-max-rows = ""
|
||||||
|
db-plan-enabled = false
|
||||||
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
|
db-pool-max-lifetime = 1800
|
||||||
|
db-pool-max-idletime = 30
|
||||||
|
db-pool-automatic-recovery = true
|
||||||
|
db-pre-request = ""
|
||||||
|
db-prepared-statements = true
|
||||||
|
db-root-spec = ""
|
||||||
|
db-schemas = "public"
|
||||||
|
db-config = true
|
||||||
|
db-pre-config = ""
|
||||||
|
db-tx-end = "commit"
|
||||||
|
db-uri = "postgresql://"
|
||||||
|
jwt-aud = ""
|
||||||
|
jwt-role-claim-key = ".\"roles\"[?(@ ==^ \"role1\")]"
|
||||||
|
jwt-secret = ""
|
||||||
|
jwt-secret-is-base64 = false
|
||||||
|
jwt-cache-max-lifetime = 0
|
||||||
|
log-level = "error"
|
||||||
|
openapi-mode = "follow-privileges"
|
||||||
|
openapi-security-active = false
|
||||||
|
openapi-server-proxy-uri = ""
|
||||||
|
server-cors-allowed-origins = ""
|
||||||
|
server-host = "!4"
|
||||||
|
server-port = 3000
|
||||||
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
|
server-unix-socket = ""
|
||||||
|
server-unix-socket-mode = "660"
|
||||||
|
admin-server-host = "!4"
|
||||||
|
admin-server-port = ""
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
db-aggregates-enabled = false
|
||||||
|
db-anon-role = ""
|
||||||
|
db-channel = "pgrst"
|
||||||
|
db-channel-enabled = true
|
||||||
|
db-extra-search-path = "public"
|
||||||
|
db-hoisted-tx-settings = "statement_timeout,plan_filter.statement_cost_limit,default_transaction_isolation"
|
||||||
|
db-max-rows = ""
|
||||||
|
db-plan-enabled = false
|
||||||
|
db-pool = 10
|
||||||
|
db-pool-acquisition-timeout = 10
|
||||||
|
db-pool-max-lifetime = 1800
|
||||||
|
db-pool-max-idletime = 30
|
||||||
|
db-pool-automatic-recovery = true
|
||||||
|
db-pre-request = ""
|
||||||
|
db-prepared-statements = true
|
||||||
|
db-root-spec = ""
|
||||||
|
db-schemas = "public"
|
||||||
|
db-config = true
|
||||||
|
db-pre-config = ""
|
||||||
|
db-tx-end = "commit"
|
||||||
|
db-uri = "postgresql://"
|
||||||
|
jwt-aud = ""
|
||||||
|
jwt-role-claim-key = ".\"roles\"[?(@ *== \"role1\")]"
|
||||||
|
jwt-secret = ""
|
||||||
|
jwt-secret-is-base64 = false
|
||||||
|
jwt-cache-max-lifetime = 0
|
||||||
|
log-level = "error"
|
||||||
|
openapi-mode = "follow-privileges"
|
||||||
|
openapi-security-active = false
|
||||||
|
openapi-server-proxy-uri = ""
|
||||||
|
server-cors-allowed-origins = ""
|
||||||
|
server-host = "!4"
|
||||||
|
server-port = 3000
|
||||||
|
server-trace-header = ""
|
||||||
|
server-timing-enabled = false
|
||||||
|
server-unix-socket = ""
|
||||||
|
server-unix-socket-mode = "660"
|
||||||
|
admin-server-host = "!4"
|
||||||
|
admin-server-port = ""
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jwt-role-claim-key = ".roles[?(@ == \"role1\")]"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jwt-role-claim-key = ".roles[?(@ != \"role1\")]"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jwt-role-claim-key = ".roles[?(@ ^== \"role1\")]"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jwt-role-claim-key = ".roles[?(@ ==^ \"role1\")]"
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
jwt-role-claim-key = ".roles[?(@ *== \"role1\")]"
|
||||||
@@ -153,6 +153,48 @@ roleclaims:
|
|||||||
role: postgrest_test_author
|
role: postgrest_test_author
|
||||||
other: true
|
other: true
|
||||||
expected_status: 401
|
expected_status: 401
|
||||||
|
# https://github.com/PostgREST/postgrest/pull/3813
|
||||||
|
- key: '.realm_access.roles[?(@ == "postgrest_test_author")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- other
|
||||||
|
- postgrest_test_author
|
||||||
|
expected_status: 200
|
||||||
|
- key: '.realm_access.roles[?(@ != "other")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- other
|
||||||
|
- postgrest_test_author
|
||||||
|
expected_status: 200
|
||||||
|
- key: '.realm_access.roles[?(@ ^== "postgrest_te")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- other
|
||||||
|
- postgrest_test_author
|
||||||
|
expected_status: 200
|
||||||
|
- key: '.realm_access.roles[?(@ ==^ "st_test_author")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- other
|
||||||
|
- postgrest_test_author
|
||||||
|
expected_status: 200
|
||||||
|
- key: '.realm_access.roles[?(@ *== "_test_")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- other
|
||||||
|
- postgrest_test_author
|
||||||
|
expected_status: 200
|
||||||
|
- key: '.realm_access.roles[?(@ == "string")]'
|
||||||
|
data:
|
||||||
|
realm_access:
|
||||||
|
roles:
|
||||||
|
- obj_key: obj_value
|
||||||
|
expected_status: 401 # fails because it compares an object with a string
|
||||||
|
|
||||||
invalidroleclaimkeys:
|
invalidroleclaimkeys:
|
||||||
- 'role.other'
|
- 'role.other'
|
||||||
@@ -160,6 +202,7 @@ invalidroleclaimkeys:
|
|||||||
- '.my_role;;domain'
|
- '.my_role;;domain'
|
||||||
- '.#$$%&$%/'
|
- '.#$$%&$%/'
|
||||||
- '1234'
|
- '1234'
|
||||||
|
- '.role[?(@ =)]'
|
||||||
|
|
||||||
invalidopenapimodes:
|
invalidopenapimodes:
|
||||||
- 'follow-'
|
- 'follow-'
|
||||||
|
|||||||
Reference in New Issue
Block a user