break:remove the db-use-legacy-gucs config
BREAKING CHANGE All PostgreSQL versions will use JSON GUCs for headers, cookies and JWT claims.
This commit is contained in:
@@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
|
- Removed [raw-media-types config](https://postgrest.org/en/v11.1/references/configuration.html#raw-media-types) - @steve-chavez
|
||||||
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
|
- Removed `application/octet-stream`, `text/plain`, `text/xml` [builtin support for scalar results](https://postgrest.org/en/v11.1/references/api/resource_representation.html#scalar-function-response-format) - @steve-chavez
|
||||||
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
|
- Removed default `application/openapi+json` media type for [db-root-spec](https://postgrest.org/en/v11.1/references/configuration.html#db-root-spec) - @steve-chavez
|
||||||
|
- Removed [db-use-legacy-gucs](https://postgrest.org/en/v11.2/references/configuration.html#db-use-legacy-gucs) - @laurenceisla
|
||||||
|
|
||||||
## [11.2.2] - 2023-10-25
|
## [11.2.2] - 2023-10-25
|
||||||
|
|
||||||
|
|||||||
@@ -192,7 +192,6 @@ test-suite spec
|
|||||||
Feature.ConcurrentSpec
|
Feature.ConcurrentSpec
|
||||||
Feature.CorsSpec
|
Feature.CorsSpec
|
||||||
Feature.ExtraSearchPathSpec
|
Feature.ExtraSearchPathSpec
|
||||||
Feature.LegacyGucsSpec
|
|
||||||
Feature.NoSuperuserSpec
|
Feature.NoSuperuserSpec
|
||||||
Feature.ObservabilitySpec
|
Feature.ObservabilitySpec
|
||||||
Feature.OpenApi.DisabledOpenApiSpec
|
Feature.OpenApi.DisabledOpenApiSpec
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ handleRequest AuthResult{..} conf appState authenticated prepared pgVer apiReq@A
|
|||||||
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf
|
roleIsoLvl = HM.findWithDefault SQL.ReadCommitted authRole $ configRoleIsoLvl conf
|
||||||
runQuery isoLvl mode query =
|
runQuery isoLvl mode query =
|
||||||
runDbHandler appState isoLvl mode authenticated prepared $ do
|
runDbHandler appState isoLvl mode authenticated prepared $ do
|
||||||
Query.setPgLocals conf authClaims authRole (HM.toList roleSettings) apiReq pgVer
|
Query.setPgLocals conf authClaims authRole (HM.toList roleSettings) apiReq
|
||||||
Query.runPreReq conf
|
Query.runPreReq conf
|
||||||
query
|
query
|
||||||
|
|
||||||
|
|||||||
@@ -191,10 +191,6 @@ exampleConfigFile =
|
|||||||
|## https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
|## https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING
|
||||||
|db-uri = "postgresql://"
|
|db-uri = "postgresql://"
|
||||||
|
|
|
|
||||||
|## Determine if GUC request settings for headers, cookies and jwt claims use the legacy names (string with dashes, invalid starting from PostgreSQL v14) with text values instead of the new names (string without dashes, valid on all PostgreSQL versions) with json values.
|
|
||||||
|## For PostgreSQL v14 and up, this setting will be ignored.
|
|
||||||
|db-use-legacy-gucs = true
|
|
||||||
|
|
|
||||||
|# jwt-aud = "your_audience_claim"
|
|# jwt-aud = "your_audience_claim"
|
||||||
|
|
|
|
||||||
|## Jspath to the role claim key
|
|## Jspath to the role claim key
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ data AppConfig = AppConfig
|
|||||||
, configDbTxAllowOverride :: Bool
|
, configDbTxAllowOverride :: Bool
|
||||||
, configDbTxRollbackAll :: Bool
|
, configDbTxRollbackAll :: Bool
|
||||||
, configDbUri :: Text
|
, configDbUri :: Text
|
||||||
, configDbUseLegacyGucs :: Bool
|
|
||||||
, configFilePath :: Maybe FilePath
|
, configFilePath :: Maybe FilePath
|
||||||
, configJWKS :: Maybe JWKSet
|
, configJWKS :: Maybe JWKSet
|
||||||
, configJwtAudience :: Maybe StringOrURI
|
, configJwtAudience :: Maybe StringOrURI
|
||||||
@@ -157,7 +156,6 @@ toText conf =
|
|||||||
,("db-pre-config", q . maybe mempty dumpQi . configDbPreConfig)
|
,("db-pre-config", q . maybe mempty dumpQi . configDbPreConfig)
|
||||||
,("db-tx-end", q . showTxEnd)
|
,("db-tx-end", q . showTxEnd)
|
||||||
,("db-uri", q . configDbUri)
|
,("db-uri", q . configDbUri)
|
||||||
,("db-use-legacy-gucs", T.toLower . show . configDbUseLegacyGucs)
|
|
||||||
,("jwt-aud", T.decodeUtf8 . LBS.toStrict . JSON.encode . maybe "" toJSON . configJwtAudience)
|
,("jwt-aud", T.decodeUtf8 . LBS.toStrict . JSON.encode . maybe "" toJSON . configJwtAudience)
|
||||||
,("jwt-role-claim-key", q . T.intercalate mempty . fmap dumpJSPath . configJwtRoleClaimKey)
|
,("jwt-role-claim-key", q . T.intercalate mempty . fmap dumpJSPath . configJwtRoleClaimKey)
|
||||||
,("jwt-secret", q . T.decodeUtf8 . showJwtSecret)
|
,("jwt-secret", q . T.decodeUtf8 . showJwtSecret)
|
||||||
@@ -257,7 +255,6 @@ parser optPath env dbSettings roleSettings roleIsolationLvl =
|
|||||||
<*> parseTxEnd "db-tx-end" snd
|
<*> parseTxEnd "db-tx-end" snd
|
||||||
<*> parseTxEnd "db-tx-end" fst
|
<*> parseTxEnd "db-tx-end" fst
|
||||||
<*> (fromMaybe "postgresql://" <$> optString "db-uri")
|
<*> (fromMaybe "postgresql://" <$> optString "db-uri")
|
||||||
<*> (fromMaybe True <$> optBool "db-use-legacy-gucs")
|
|
||||||
<*> pure optPath
|
<*> pure optPath
|
||||||
<*> pure Nothing
|
<*> pure Nothing
|
||||||
<*> parseJwtAudience "jwt-aud"
|
<*> parseJwtAudience "jwt-aud"
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ dbSettingsNames =
|
|||||||
,"db_root_spec"
|
,"db_root_spec"
|
||||||
,"db_schemas"
|
,"db_schemas"
|
||||||
,"db_tx_end"
|
,"db_tx_end"
|
||||||
,"db_use_legacy_gucs"
|
|
||||||
,"jwt_aud"
|
,"jwt_aud"
|
||||||
,"jwt_role_claim_key"
|
,"jwt_role_claim_key"
|
||||||
,"jwt_secret"
|
,"jwt_secret"
|
||||||
|
|||||||
+12
-31
@@ -14,13 +14,11 @@ module PostgREST.Query
|
|||||||
) where
|
) where
|
||||||
|
|
||||||
import qualified Data.Aeson as JSON
|
import qualified Data.Aeson as JSON
|
||||||
import qualified Data.Aeson.Key as K
|
|
||||||
import qualified Data.Aeson.KeyMap as KM
|
import qualified Data.Aeson.KeyMap as KM
|
||||||
import qualified Data.ByteString as BS
|
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.HashMap.Strict as HM
|
import qualified Data.HashMap.Strict as HM
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.Text.Encoding as T
|
|
||||||
import qualified Hasql.Decoders as HD
|
import qualified Hasql.Decoders as HD
|
||||||
import qualified Hasql.DynamicStatements.Snippet as SQL (Snippet)
|
import qualified Hasql.DynamicStatements.Snippet as SQL (Snippet)
|
||||||
import qualified Hasql.DynamicStatements.Statement as SQL
|
import qualified Hasql.DynamicStatements.Statement as SQL
|
||||||
@@ -33,8 +31,6 @@ import qualified PostgREST.Query.Statements as Statements
|
|||||||
import qualified PostgREST.RangeQuery as RangeQuery
|
import qualified PostgREST.RangeQuery as RangeQuery
|
||||||
import qualified PostgREST.SchemaCache as SchemaCache
|
import qualified PostgREST.SchemaCache as SchemaCache
|
||||||
|
|
||||||
import Data.Scientific (FPFormat (..), formatScientific, isInteger)
|
|
||||||
|
|
||||||
import PostgREST.ApiRequest (ApiRequest (..))
|
import PostgREST.ApiRequest (ApiRequest (..))
|
||||||
import PostgREST.ApiRequest.Preferences (PreferCount (..),
|
import PostgREST.ApiRequest.Preferences (PreferCount (..),
|
||||||
PreferTransaction (..),
|
PreferTransaction (..),
|
||||||
@@ -42,8 +38,7 @@ import PostgREST.ApiRequest.Preferences (PreferCount (..),
|
|||||||
shouldCount)
|
shouldCount)
|
||||||
import PostgREST.Config (AppConfig (..),
|
import PostgREST.Config (AppConfig (..),
|
||||||
OpenAPIMode (..))
|
OpenAPIMode (..))
|
||||||
import PostgREST.Config.PgVersion (PgVersion (..),
|
import PostgREST.Config.PgVersion (PgVersion (..))
|
||||||
pgVersion140)
|
|
||||||
import PostgREST.Error (Error)
|
import PostgREST.Error (Error)
|
||||||
import PostgREST.MediaType (MediaType (..))
|
import PostgREST.MediaType (MediaType (..))
|
||||||
import PostgREST.Plan (CallReadPlan (..),
|
import PostgREST.Plan (CallReadPlan (..),
|
||||||
@@ -238,37 +233,23 @@ optionalRollback AppConfig{..} ApiRequest{iPreferences=Preferences{..}} = do
|
|||||||
|
|
||||||
-- | Runs local (transaction scoped) GUCs for every request.
|
-- | Runs local (transaction scoped) GUCs for every request.
|
||||||
setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> BS.ByteString -> [(ByteString, ByteString)] ->
|
setPgLocals :: AppConfig -> KM.KeyMap JSON.Value -> BS.ByteString -> [(ByteString, ByteString)] ->
|
||||||
ApiRequest -> PgVersion -> DbHandler ()
|
ApiRequest -> DbHandler ()
|
||||||
setPgLocals AppConfig{..} claims role roleSettings req actualPgVersion = lift $
|
setPgLocals AppConfig{..} claims role roleSettings req = lift $
|
||||||
SQL.statement mempty $ SQL.dynamicallyParameterized
|
SQL.statement mempty $ SQL.dynamicallyParameterized
|
||||||
("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ roleSettingsSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql))
|
("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ roleSettingsSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql))
|
||||||
HD.noResult configDbPreparedStatements
|
HD.noResult configDbPreparedStatements
|
||||||
where
|
where
|
||||||
methodSql = setConfigLocal mempty ("request.method", iMethod req)
|
methodSql = setConfigLocal ("request.method", iMethod req)
|
||||||
pathSql = setConfigLocal mempty ("request.path", iPath req)
|
pathSql = setConfigLocal ("request.path", iPath req)
|
||||||
headersSql = if usesLegacyGucs
|
headersSql = setConfigLocalJson "request.headers" (iHeaders req)
|
||||||
then setConfigLocal "request.header." <$> iHeaders req
|
cookiesSql = setConfigLocalJson "request.cookies" (iCookies req)
|
||||||
else setConfigLocalJson "request.headers" (iHeaders req)
|
claimsSql = [setConfigLocal ("request.jwt.claims", LBS.toStrict $ JSON.encode claims)]
|
||||||
cookiesSql = if usesLegacyGucs
|
roleSql = [setConfigLocal ("role", role)]
|
||||||
then setConfigLocal "request.cookie." <$> iCookies req
|
roleSettingsSql = setConfigLocal <$> roleSettings
|
||||||
else setConfigLocalJson "request.cookies" (iCookies req)
|
appSettingsSql = setConfigLocal <$> (join bimap toUtf8 <$> configAppSettings)
|
||||||
claimsSql = if usesLegacyGucs
|
|
||||||
then setConfigLocal "request.jwt.claim." <$> [(toUtf8 $ K.toText c, toUtf8 $ unquoted v) | (c,v) <- KM.toList claims]
|
|
||||||
else [setConfigLocal mempty ("request.jwt.claims", LBS.toStrict $ JSON.encode claims)]
|
|
||||||
roleSql = [setConfigLocal mempty ("role", role)]
|
|
||||||
roleSettingsSql = setConfigLocal mempty <$> roleSettings
|
|
||||||
appSettingsSql = setConfigLocal mempty <$> (join bimap toUtf8 <$> configAppSettings)
|
|
||||||
searchPathSql =
|
searchPathSql =
|
||||||
let schemas = escapeIdentList (iSchema req : configDbExtraSearchPath) in
|
let schemas = escapeIdentList (iSchema req : configDbExtraSearchPath) in
|
||||||
setConfigLocal mempty ("search_path", schemas)
|
setConfigLocal ("search_path", schemas)
|
||||||
usesLegacyGucs = configDbUseLegacyGucs && actualPgVersion < pgVersion140
|
|
||||||
|
|
||||||
unquoted :: JSON.Value -> Text
|
|
||||||
unquoted (JSON.String t) = t
|
|
||||||
unquoted (JSON.Number n) =
|
|
||||||
toS $ formatScientific Fixed (if isInteger n then Just 0 else Nothing) n
|
|
||||||
unquoted (JSON.Bool b) = show b
|
|
||||||
unquoted v = T.decodeUtf8 . LBS.toStrict $ JSON.encode v
|
|
||||||
|
|
||||||
-- | Runs the pre-request function.
|
-- | Runs the pre-request function.
|
||||||
runPreReq :: AppConfig -> DbHandler ()
|
runPreReq :: AppConfig -> DbHandler ()
|
||||||
|
|||||||
@@ -484,14 +484,14 @@ explainF fmt opts snip =
|
|||||||
fmtPlanFmt PlanJSON = "FORMAT JSON"
|
fmtPlanFmt PlanJSON = "FORMAT JSON"
|
||||||
|
|
||||||
-- | Do a pg set_config(setting, value, true) call. This is equivalent to a SET LOCAL.
|
-- | Do a pg set_config(setting, value, true) call. This is equivalent to a SET LOCAL.
|
||||||
setConfigLocal :: ByteString -> (ByteString, ByteString) -> SQL.Snippet
|
setConfigLocal :: (ByteString, ByteString) -> SQL.Snippet
|
||||||
setConfigLocal prefix (k, v) =
|
setConfigLocal (k, v) =
|
||||||
"set_config(" <> unknownEncoder (prefix <> k) <> ", " <> unknownEncoder v <> ", true)"
|
"set_config(" <> unknownEncoder k <> ", " <> unknownEncoder v <> ", true)"
|
||||||
|
|
||||||
-- | Starting from PostgreSQL v14, some characters are not allowed for config names (mostly affecting headers with "-").
|
-- | Starting from PostgreSQL v14, some characters are not allowed for config names (mostly affecting headers with "-").
|
||||||
-- | A JSON format string is used to avoid this problem. See https://github.com/PostgREST/postgrest/issues/1857
|
-- | A JSON format string is used to avoid this problem. See https://github.com/PostgREST/postgrest/issues/1857
|
||||||
setConfigLocalJson :: ByteString -> [(ByteString, ByteString)] -> [SQL.Snippet]
|
setConfigLocalJson :: ByteString -> [(ByteString, ByteString)] -> [SQL.Snippet]
|
||||||
setConfigLocalJson prefix keyVals = [setConfigLocal mempty (prefix, gucJsonVal keyVals)]
|
setConfigLocalJson prefix keyVals = [setConfigLocal (prefix, gucJsonVal keyVals)]
|
||||||
where
|
where
|
||||||
gucJsonVal :: [(ByteString, ByteString)] -> ByteString
|
gucJsonVal :: [(ByteString, ByteString)] -> ByteString
|
||||||
gucJsonVal = LBS.toStrict . JSON.encode . HM.fromList . arrayByteStringToText
|
gucJsonVal = LBS.toStrict . JSON.encode . HM.fromList . arrayByteStringToText
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = ""
|
db-pre-config = ""
|
||||||
db-tx-end = "commit"
|
db-tx-end = "commit"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = true
|
|
||||||
jwt-aud = ""
|
jwt-aud = ""
|
||||||
jwt-role-claim-key = ".\"aliased\""
|
jwt-role-claim-key = ".\"aliased\""
|
||||||
jwt-secret = ""
|
jwt-secret = ""
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = ""
|
db-pre-config = ""
|
||||||
db-tx-end = "commit"
|
db-tx-end = "commit"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = true
|
|
||||||
jwt-aud = ""
|
jwt-aud = ""
|
||||||
jwt-role-claim-key = ".\"role\""
|
jwt-role-claim-key = ".\"role\""
|
||||||
jwt-secret = ""
|
jwt-secret = ""
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = ""
|
db-pre-config = ""
|
||||||
db-tx-end = "commit"
|
db-tx-end = "commit"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = true
|
|
||||||
jwt-aud = ""
|
jwt-aud = ""
|
||||||
jwt-role-claim-key = ".\"role\""
|
jwt-role-claim-key = ".\"role\""
|
||||||
jwt-secret = ""
|
jwt-secret = ""
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = false
|
|||||||
db-pre-config = ""
|
db-pre-config = ""
|
||||||
db-tx-end = "commit"
|
db-tx-end = "commit"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = true
|
|
||||||
jwt-aud = ""
|
jwt-aud = ""
|
||||||
jwt-role-claim-key = ".\"role\""
|
jwt-role-claim-key = ".\"role\""
|
||||||
jwt-secret = ""
|
jwt-secret = ""
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = "postgrest.pre_config"
|
db-pre-config = "postgrest.pre_config"
|
||||||
db-tx-end = "rollback-allow-override"
|
db-tx-end = "rollback-allow-override"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = false
|
|
||||||
jwt-aud = "https://otherexample.org"
|
jwt-aud = "https://otherexample.org"
|
||||||
jwt-role-claim-key = ".\"other\".\"pre_config_role\""
|
jwt-role-claim-key = ".\"other\".\"pre_config_role\""
|
||||||
jwt-secret = "ODERREALLYREALLYREALLYREALLYVERYSAFE"
|
jwt-secret = "ODERREALLYREALLYREALLYREALLYVERYSAFE"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = "postgrest.preconf"
|
db-pre-config = "postgrest.preconf"
|
||||||
db-tx-end = "commit-allow-override"
|
db-tx-end = "commit-allow-override"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = false
|
|
||||||
jwt-aud = "https://example.org"
|
jwt-aud = "https://example.org"
|
||||||
jwt-role-claim-key = ".\"a\".\"role\""
|
jwt-role-claim-key = ".\"a\".\"role\""
|
||||||
jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE"
|
jwt-secret = "OVERRIDE=REALLY=REALLY=REALLY=REALLY=VERY=SAFE"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = false
|
|||||||
db-pre-config = "postgrest.pre_config"
|
db-pre-config = "postgrest.pre_config"
|
||||||
db-tx-end = "rollback-allow-override"
|
db-tx-end = "rollback-allow-override"
|
||||||
db-uri = "tmp_db"
|
db-uri = "tmp_db"
|
||||||
db-use-legacy-gucs = false
|
|
||||||
jwt-aud = "https://postgrest.org"
|
jwt-aud = "https://postgrest.org"
|
||||||
jwt-role-claim-key = ".\"user\"[0].\"real-role\""
|
jwt-role-claim-key = ".\"user\"[0].\"real-role\""
|
||||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = true
|
|||||||
db-pre-config = ""
|
db-pre-config = ""
|
||||||
db-tx-end = "commit"
|
db-tx-end = "commit"
|
||||||
db-uri = "postgresql://"
|
db-uri = "postgresql://"
|
||||||
db-use-legacy-gucs = true
|
|
||||||
jwt-aud = ""
|
jwt-aud = ""
|
||||||
jwt-role-claim-key = ".\"role\""
|
jwt-role-claim-key = ".\"role\""
|
||||||
jwt-secret = ""
|
jwt-secret = ""
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ db-config = false
|
|||||||
db-pre-config = "postgrest.pre_config"
|
db-pre-config = "postgrest.pre_config"
|
||||||
db-tx-end = "rollback-allow-override"
|
db-tx-end = "rollback-allow-override"
|
||||||
db-uri = "tmp_db"
|
db-uri = "tmp_db"
|
||||||
db-use-legacy-gucs = false
|
|
||||||
jwt-aud = "https://postgrest.org"
|
jwt-aud = "https://postgrest.org"
|
||||||
jwt-role-claim-key = ".user[0].\"real-role\""
|
jwt-role-claim-key = ".user[0].\"real-role\""
|
||||||
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
jwt-secret = "c2VjdXJpdHl0aHJvdWdob2JzY3VyaXR5"
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
module Feature.LegacyGucsSpec where
|
|
||||||
|
|
||||||
import Network.Wai (Application)
|
|
||||||
|
|
||||||
import Network.HTTP.Types
|
|
||||||
import Test.Hspec hiding (pendingWith)
|
|
||||||
import Test.Hspec.Wai
|
|
||||||
import Test.Hspec.Wai.JSON
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
|
||||||
import SpecHelper
|
|
||||||
|
|
||||||
spec :: SpecWith ((), Application)
|
|
||||||
spec =
|
|
||||||
describe "remote procedure call with legacy gucs disabled" $ do
|
|
||||||
it "custom header is set" $
|
|
||||||
request methodPost "/rpc/get_guc_value" [("Custom-Header", "test")]
|
|
||||||
[json| { "prefix": "request.headers", "name": "custom-header" } |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"test"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = [ matchContentTypeJson ]
|
|
||||||
}
|
|
||||||
|
|
||||||
it "standard header is set" $
|
|
||||||
request methodPost "/rpc/get_guc_value" [("Origin", "http://example.com")]
|
|
||||||
[json| { "prefix": "request.headers", "name": "origin" } |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"http://example.com"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = [ matchContentTypeJson ]
|
|
||||||
}
|
|
||||||
|
|
||||||
it "current role is available as GUC claim" $
|
|
||||||
request methodPost "/rpc/get_guc_value" []
|
|
||||||
[json| { "prefix": "request.jwt.claims", "name": "role" } |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"postgrest_test_anonymous"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = [ matchContentTypeJson ]
|
|
||||||
}
|
|
||||||
|
|
||||||
it "single cookie ends up as claims" $
|
|
||||||
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue")]
|
|
||||||
[json| {"prefix": "request.cookies", "name":"acookie"} |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"cookievalue"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = []
|
|
||||||
}
|
|
||||||
|
|
||||||
it "multiple cookies ends up as claims" $
|
|
||||||
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue;secondcookie=anothervalue")]
|
|
||||||
[json| {"prefix": "request.cookies", "name":"secondcookie"} |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"anothervalue"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = []
|
|
||||||
}
|
|
||||||
|
|
||||||
it "gets the Authorization value" $
|
|
||||||
request methodPost "/rpc/get_guc_value" [authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"]
|
|
||||||
[json| {"prefix": "request.headers", "name":"authorization"} |]
|
|
||||||
`shouldRespondWith`
|
|
||||||
[json|"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"|]
|
|
||||||
{ matchStatus = 200
|
|
||||||
, matchHeaders = []
|
|
||||||
}
|
|
||||||
@@ -13,8 +13,7 @@ import Text.Heredoc
|
|||||||
|
|
||||||
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
|
||||||
pgVersion109, pgVersion110,
|
pgVersion109, pgVersion110,
|
||||||
pgVersion112, pgVersion114,
|
pgVersion112, pgVersion114)
|
||||||
pgVersion140)
|
|
||||||
|
|
||||||
import Protolude hiding (get)
|
import Protolude hiding (get)
|
||||||
import SpecHelper
|
import SpecHelper
|
||||||
@@ -974,12 +973,7 @@ spec actualPgVersion =
|
|||||||
it "custom header is set" $
|
it "custom header is set" $
|
||||||
request methodPost "/rpc/get_guc_value"
|
request methodPost "/rpc/get_guc_value"
|
||||||
[("Custom-Header", "test")]
|
[("Custom-Header", "test")]
|
||||||
(
|
[json| { "prefix": "request.headers", "name": "custom-header" } |]
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| { "prefix": "request.headers", "name": "custom-header" } |]
|
|
||||||
else
|
|
||||||
[json| { "name": "request.header.custom-header" } |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"test"|]
|
[json|"test"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
@@ -988,12 +982,7 @@ spec actualPgVersion =
|
|||||||
it "standard header is set" $
|
it "standard header is set" $
|
||||||
request methodPost "/rpc/get_guc_value"
|
request methodPost "/rpc/get_guc_value"
|
||||||
[("Origin", "http://example.com")]
|
[("Origin", "http://example.com")]
|
||||||
(
|
[json| { "prefix": "request.headers", "name": "origin" } |]
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| { "prefix": "request.headers", "name": "origin" } |]
|
|
||||||
else
|
|
||||||
[json| { "name": "request.header.origin" } |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"http://example.com"|]
|
[json|"http://example.com"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
@@ -1001,12 +990,7 @@ spec actualPgVersion =
|
|||||||
}
|
}
|
||||||
it "current role is available as GUC claim" $
|
it "current role is available as GUC claim" $
|
||||||
request methodPost "/rpc/get_guc_value" []
|
request methodPost "/rpc/get_guc_value" []
|
||||||
(
|
[json| { "prefix": "request.jwt.claims", "name": "role" } |]
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| { "prefix": "request.jwt.claims", "name": "role" } |]
|
|
||||||
else
|
|
||||||
[json| { "name": "request.jwt.claim.role" } |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"postgrest_test_anonymous"|]
|
[json|"postgrest_test_anonymous"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
@@ -1014,25 +998,15 @@ spec actualPgVersion =
|
|||||||
}
|
}
|
||||||
it "single cookie ends up as claims" $
|
it "single cookie ends up as claims" $
|
||||||
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue")]
|
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue")]
|
||||||
(
|
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| {"prefix": "request.cookies", "name":"acookie"} |]
|
[json| {"prefix": "request.cookies", "name":"acookie"} |]
|
||||||
else
|
|
||||||
[json| {"name":"request.cookie.acookie"} |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"cookievalue"|]
|
[json|"cookievalue"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
, matchHeaders = []
|
, matchHeaders = []
|
||||||
}
|
}
|
||||||
it "multiple cookies ends up as claims" $
|
it "multiple cookies end up as claims" $
|
||||||
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue;secondcookie=anothervalue")]
|
request methodPost "/rpc/get_guc_value" [("Cookie","acookie=cookievalue;secondcookie=anothervalue")]
|
||||||
(
|
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| {"prefix": "request.cookies", "name":"secondcookie"} |]
|
[json| {"prefix": "request.cookies", "name":"secondcookie"} |]
|
||||||
else
|
|
||||||
[json| {"name":"request.cookie.secondcookie"} |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"anothervalue"|]
|
[json|"anothervalue"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
@@ -1048,12 +1022,7 @@ spec actualPgVersion =
|
|||||||
}
|
}
|
||||||
it "gets the Authorization value" $
|
it "gets the Authorization value" $
|
||||||
request methodPost "/rpc/get_guc_value" [authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"]
|
request methodPost "/rpc/get_guc_value" [authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"]
|
||||||
(
|
|
||||||
if actualPgVersion >= pgVersion140 then
|
|
||||||
[json| {"prefix": "request.headers", "name":"authorization"} |]
|
[json| {"prefix": "request.headers", "name":"authorization"} |]
|
||||||
else
|
|
||||||
[json| {"name":"request.header.authorization"} |]
|
|
||||||
)
|
|
||||||
`shouldRespondWith`
|
`shouldRespondWith`
|
||||||
[json|"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"|]
|
[json|"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIn0.Xod-F15qsGL0WhdOCr2j3DdKuTw9QJERVgoFD3vGaWA"|]
|
||||||
{ matchStatus = 200
|
{ matchStatus = 200
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ import qualified Feature.Auth.NoJwtSpec
|
|||||||
import qualified Feature.ConcurrentSpec
|
import qualified Feature.ConcurrentSpec
|
||||||
import qualified Feature.CorsSpec
|
import qualified Feature.CorsSpec
|
||||||
import qualified Feature.ExtraSearchPathSpec
|
import qualified Feature.ExtraSearchPathSpec
|
||||||
import qualified Feature.LegacyGucsSpec
|
|
||||||
import qualified Feature.NoSuperuserSpec
|
import qualified Feature.NoSuperuserSpec
|
||||||
import qualified Feature.ObservabilitySpec
|
import qualified Feature.ObservabilitySpec
|
||||||
import qualified Feature.OpenApi.DisabledOpenApiSpec
|
import qualified Feature.OpenApi.DisabledOpenApiSpec
|
||||||
@@ -106,7 +105,6 @@ main = do
|
|||||||
responseHeadersApp = app testCfgResponseHeaders
|
responseHeadersApp = app testCfgResponseHeaders
|
||||||
disallowRollbackApp = app testCfgDisallowRollback
|
disallowRollbackApp = app testCfgDisallowRollback
|
||||||
forceRollbackApp = app testCfgForceRollback
|
forceRollbackApp = app testCfgForceRollback
|
||||||
testCfgLegacyGucsApp = app testCfgLegacyGucs
|
|
||||||
planEnabledApp = app testPlanEnabledCfg
|
planEnabledApp = app testPlanEnabledCfg
|
||||||
pgSafeUpdateApp = app testPgSafeUpdateEnabledCfg
|
pgSafeUpdateApp = app testPgSafeUpdateEnabledCfg
|
||||||
obsApp = app testObservabilityCfg
|
obsApp = app testObservabilityCfg
|
||||||
@@ -229,10 +227,6 @@ main = do
|
|||||||
parallel $ before multipleSchemaApp $
|
parallel $ before multipleSchemaApp $
|
||||||
describe "Feature.Query.MultipleSchemaSpec" Feature.Query.MultipleSchemaSpec.spec
|
describe "Feature.Query.MultipleSchemaSpec" Feature.Query.MultipleSchemaSpec.spec
|
||||||
|
|
||||||
-- this test runs with db-uses-legacy-gucs = false
|
|
||||||
parallel $ before testCfgLegacyGucsApp $
|
|
||||||
describe "Feature.LegacyGucsSpec" Feature.LegacyGucsSpec.spec
|
|
||||||
|
|
||||||
-- this test runs with db-plan-enabled = true
|
-- this test runs with db-plan-enabled = true
|
||||||
parallel $ before planEnabledApp $
|
parallel $ before planEnabledApp $
|
||||||
describe "Feature.Query.PlanSpec.spec" $ Feature.Query.PlanSpec.spec actualPgVersion
|
describe "Feature.Query.PlanSpec.spec" $ Feature.Query.PlanSpec.spec actualPgVersion
|
||||||
|
|||||||
@@ -116,7 +116,6 @@ baseCfg = let secret = Just $ encodeUtf8 "reallyreallyreallyreallyverysafe" in
|
|||||||
, configDbConfig = False
|
, configDbConfig = False
|
||||||
, configDbPreConfig = Nothing
|
, configDbPreConfig = Nothing
|
||||||
, configDbUri = "postgresql://"
|
, configDbUri = "postgresql://"
|
||||||
, configDbUseLegacyGucs = True
|
|
||||||
, configFilePath = Nothing
|
, configFilePath = Nothing
|
||||||
, configJWKS = parseSecret <$> secret
|
, configJWKS = parseSecret <$> secret
|
||||||
, configJwtAudience = Nothing
|
, configJwtAudience = Nothing
|
||||||
@@ -226,9 +225,6 @@ testCfgResponseHeaders = baseCfg { configDbPreRequest = Just $ QualifiedIdentifi
|
|||||||
testMultipleSchemaCfg :: AppConfig
|
testMultipleSchemaCfg :: AppConfig
|
||||||
testMultipleSchemaCfg = baseCfg { configDbSchemas = fromList ["v1", "v2", "SPECIAL \"@/\\#~_-"] }
|
testMultipleSchemaCfg = baseCfg { configDbSchemas = fromList ["v1", "v2", "SPECIAL \"@/\\#~_-"] }
|
||||||
|
|
||||||
testCfgLegacyGucs :: AppConfig
|
|
||||||
testCfgLegacyGucs = baseCfg { configDbUseLegacyGucs = False }
|
|
||||||
|
|
||||||
testPgSafeUpdateEnabledCfg :: AppConfig
|
testPgSafeUpdateEnabledCfg :: AppConfig
|
||||||
testPgSafeUpdateEnabledCfg = baseCfg { configDbPreRequest = Just $ QualifiedIdentifier "test" "load_safeupdate" }
|
testPgSafeUpdateEnabledCfg = baseCfg { configDbPreRequest = Just $ QualifiedIdentifier "test" "load_safeupdate" }
|
||||||
|
|
||||||
|
|||||||
Vendored
+14
-45
@@ -91,10 +91,7 @@ CREATE FUNCTION set_authors_only_owner() RETURNS trigger
|
|||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
begin
|
begin
|
||||||
NEW.owner = case when current_setting('server_version_num')::int >= 140000
|
NEW.owner = current_setting('request.jwt.claims')::json->>'id';
|
||||||
then current_setting('request.jwt.claims')::json->>'id'
|
|
||||||
else current_setting('request.jwt.claim.id')
|
|
||||||
end;
|
|
||||||
RETURN NEW;
|
RETURN NEW;
|
||||||
end
|
end
|
||||||
$$;
|
$$;
|
||||||
@@ -377,10 +374,7 @@ CREATE OR REPLACE FUNCTION switch_role() RETURNS void
|
|||||||
declare
|
declare
|
||||||
user_id text;
|
user_id text;
|
||||||
Begin
|
Begin
|
||||||
user_id = case when current_setting('server_version_num')::int >= 140000
|
user_id = (current_setting('request.jwt.claims')::json->>'id')::text;
|
||||||
then (current_setting('request.jwt.claims')::json->>'id')::text
|
|
||||||
else current_setting('request.jwt.claim.id')::text
|
|
||||||
end;
|
|
||||||
if user_id = '1'::text then
|
if user_id = '1'::text then
|
||||||
execute 'set local role postgrest_test_author';
|
execute 'set local role postgrest_test_author';
|
||||||
elseif user_id = '2'::text then
|
elseif user_id = '2'::text then
|
||||||
@@ -408,34 +402,15 @@ CREATE FUNCTION reveal_big_jwt() RETURNS TABLE (
|
|||||||
iss text, sub text, exp bigint,
|
iss text, sub text, exp bigint,
|
||||||
nbf bigint, iat bigint, jti text, "http://postgrest.com/foo" boolean
|
nbf bigint, iat bigint, jti text, "http://postgrest.com/foo" boolean
|
||||||
)
|
)
|
||||||
LANGUAGE plpgsql SECURITY DEFINER
|
AS $$
|
||||||
STABLE
|
SELECT current_setting('request.jwt.claims')::json->>'iss' as iss,
|
||||||
AS $$
|
current_setting('request.jwt.claims')::json->>'sub' as sub,
|
||||||
BEGIN
|
(current_setting('request.jwt.claims')::json->>'exp')::bigint as exp,
|
||||||
-- JWT claims are set in JSON format since v14
|
(current_setting('request.jwt.claims')::json->>'nbf')::bigint as nbf,
|
||||||
IF (current_setting('server_version_num')::INT >= 140000) THEN
|
(current_setting('request.jwt.claims')::json->>'iat')::bigint as iat,
|
||||||
RETURN QUERY
|
current_setting('request.jwt.claims')::json->>'jti' as jti,
|
||||||
SELECT current_setting('request.jwt.claims')::json->>'iss' as iss,
|
(current_setting('request.jwt.claims')::json->>'http://postgrest.com/foo')::boolean as "http://postgrest.com/foo";
|
||||||
current_setting('request.jwt.claims')::json->>'sub' as sub,
|
$$ LANGUAGE sql SECURITY DEFINER STABLE;
|
||||||
(current_setting('request.jwt.claims')::json->>'exp')::bigint as exp,
|
|
||||||
(current_setting('request.jwt.claims')::json->>'nbf')::bigint as nbf,
|
|
||||||
(current_setting('request.jwt.claims')::json->>'iat')::bigint as iat,
|
|
||||||
current_setting('request.jwt.claims')::json->>'jti' as jti,
|
|
||||||
(current_setting('request.jwt.claims')::json->>'http://postgrest.com/foo')::boolean
|
|
||||||
as "http://postgrest.com/foo";
|
|
||||||
ELSE
|
|
||||||
RETURN QUERY
|
|
||||||
SELECT current_setting('request.jwt.claim.iss') as iss,
|
|
||||||
current_setting('request.jwt.claim.sub') as sub,
|
|
||||||
current_setting('request.jwt.claim.exp')::bigint as exp,
|
|
||||||
current_setting('request.jwt.claim.nbf')::bigint as nbf,
|
|
||||||
current_setting('request.jwt.claim.iat')::bigint as iat,
|
|
||||||
current_setting('request.jwt.claim.jti') as jti,
|
|
||||||
current_setting('request.jwt.claim.http://postgrest.com/foo')::boolean
|
|
||||||
as "http://postgrest.com/foo";
|
|
||||||
END IF;
|
|
||||||
END;
|
|
||||||
$$;
|
|
||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION assert() RETURNS void
|
CREATE FUNCTION assert() RETURNS void
|
||||||
@@ -1187,7 +1162,7 @@ create function test.get_guc_value(name text) returns text as $$
|
|||||||
select nullif(current_setting(name), '')::text;
|
select nullif(current_setting(name), '')::text;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
-- Get the GUC values for Postgres v14.0 and up
|
-- Get the JSON type GUC values
|
||||||
create function test.get_guc_value(prefix text, name text) returns text as $$
|
create function test.get_guc_value(prefix text, name text) returns text as $$
|
||||||
select nullif(current_setting(prefix)::json->>name, '')::text;
|
select nullif(current_setting(prefix)::json->>name, '')::text;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
@@ -2093,15 +2068,9 @@ where fst_shift_activity_id is not null
|
|||||||
-- for a pre-request function
|
-- for a pre-request function
|
||||||
create or replace function custom_headers() returns void as $$
|
create or replace function custom_headers() returns void as $$
|
||||||
declare
|
declare
|
||||||
user_agent text := case when current_setting('server_version_num')::int >= 140000
|
user_agent text := current_setting('request.headers', true)::json->>'user-agent';
|
||||||
then current_setting('request.headers', true)::json->>'user-agent'
|
|
||||||
else current_setting('request.header.user-agent', true)
|
|
||||||
end;
|
|
||||||
req_path text := current_setting('request.path', true);
|
req_path text := current_setting('request.path', true);
|
||||||
req_accept text := case when current_setting('server_version_num')::int >= 140000
|
req_accept text := current_setting('request.headers', true)::json->>'accept';
|
||||||
then current_setting('request.headers', true)::json->>'accept'
|
|
||||||
else current_setting('request.header.accept', true)
|
|
||||||
end;
|
|
||||||
req_method text := current_setting('request.method', true);
|
req_method text := current_setting('request.method', true);
|
||||||
begin
|
begin
|
||||||
if user_agent similar to 'MSIE (6.0|7.0)' then
|
if user_agent similar to 'MSIE (6.0|7.0)' then
|
||||||
|
|||||||
Reference in New Issue
Block a user