feat: add pre-config function
Allows using the in-database configuration without SUPERUSER
This commit is contained in:
committed by
Steve Chavez
parent
8a3686d86b
commit
14be3fb671
@@ -136,6 +136,9 @@ exampleConfigFile =
|
||||
|## Enable in-database configuration
|
||||
|db-config = true
|
||||
|
|
||||
|## Function for in-database configuration
|
||||
|## db-pre-config = "postgrest.pre_config"
|
||||
|
|
||||
|## Extra schemas to add to the search_path of every request
|
||||
|db-extra-search-path = "public"
|
||||
|
|
||||
|
||||
@@ -80,6 +80,7 @@ data AppConfig = AppConfig
|
||||
, configDbRootSpec :: Maybe QualifiedIdentifier
|
||||
, configDbSchemas :: NonEmpty Text
|
||||
, configDbConfig :: Bool
|
||||
, configDbPreConfig :: Maybe QualifiedIdentifier
|
||||
, configDbTxAllowOverride :: Bool
|
||||
, configDbTxRollbackAll :: Bool
|
||||
, configDbUri :: Text
|
||||
@@ -144,6 +145,7 @@ toText conf =
|
||||
,("db-root-spec", q . maybe mempty dumpQi . configDbRootSpec)
|
||||
,("db-schemas", q . T.intercalate "," . toList . configDbSchemas)
|
||||
,("db-config", T.toLower . show . configDbConfig)
|
||||
,("db-pre-config", q . maybe mempty dumpQi . configDbPreConfig)
|
||||
,("db-tx-end", q . showTxEnd)
|
||||
,("db-uri", q . configDbUri)
|
||||
,("db-use-legacy-gucs", T.toLower . show . configDbUseLegacyGucs)
|
||||
@@ -240,6 +242,7 @@ parser optPath env dbSettings roleSettings =
|
||||
<*> (fromList . maybe ["public"] splitOnCommas <$> optWithAlias (optValue "db-schemas")
|
||||
(optValue "db-schema"))
|
||||
<*> (fromMaybe True <$> optBool "db-config")
|
||||
<*> (fmap toQi <$> optString "db-pre-config")
|
||||
<*> parseTxEnd "db-tx-end" snd
|
||||
<*> parseTxEnd "db-tx-end" fst
|
||||
<*> (fromMaybe "postgresql://" <$> optString "db-uri")
|
||||
|
||||
@@ -35,6 +35,7 @@ dbSettingsNames :: [Text]
|
||||
dbSettingsNames =
|
||||
(prefix <>) <$>
|
||||
["db_anon_role"
|
||||
,"db_pre_config"
|
||||
,"db_extra_search_path"
|
||||
,"db_max_rows"
|
||||
,"db_plan_enabled"
|
||||
@@ -64,21 +65,21 @@ pgVersionStatement = SQL.Statement sql HE.noParams versionRow
|
||||
sql = "SELECT current_setting('server_version_num')::integer, current_setting('server_version')"
|
||||
versionRow = HD.singleRow $ PgVersion <$> column HD.int4 <*> column HD.text
|
||||
|
||||
queryDbSettings :: Bool -> Session [(Text, Text)]
|
||||
queryDbSettings prepared =
|
||||
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
|
||||
transaction SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ dbSettingsStatement prepared
|
||||
|
||||
-- | Get db settings from the connection role. Global settings will be overridden by database specific settings.
|
||||
-- i.e. Doing:
|
||||
-- ALTER ROLE authenticator IN DATABASE postgres SET <prefix>jwt_aud = 'val';
|
||||
-- ALTER ROLE authenticator SET <prefix>jwt_aud = 'overridden';
|
||||
-- Will result in <prefix>jwt_aud = 'overridden'
|
||||
-- | Query the in-database configuration. The settings have the following priorities:
|
||||
--
|
||||
-- A setting on the database only will have no effect
|
||||
-- ALTER DATABASE postgres SET <prefix>jwt_aud = 'xx'
|
||||
dbSettingsStatement :: Bool -> SQL.Statement [Text] [(Text, Text)]
|
||||
dbSettingsStatement = SQL.Statement sql (arrayParam HE.text) decodeSettings
|
||||
-- 1. Role + with database-specific settings:
|
||||
-- ALTER ROLE authenticator IN DATABASE postgres SET <prefix>jwt_aud = 'val';
|
||||
-- 2. Role + with settings:
|
||||
-- ALTER ROLE authenticator SET <prefix>jwt_aud = 'overridden';
|
||||
-- 3. pre-config function:
|
||||
-- CREATE FUNCTION pre_config() .. PERFORM set_config(<prefix>jwt_aud, 'pre_config_aud'..)
|
||||
--
|
||||
-- The example above will result in <prefix>jwt_aud = 'val'
|
||||
-- A setting on the database only will have no effect: ALTER DATABASE postgres SET <prefix>jwt_aud = 'xx'
|
||||
queryDbSettings :: Maybe Text -> Bool -> Session [(Text, Text)]
|
||||
queryDbSettings preConfFunc prepared =
|
||||
let transaction = if prepared then SQL.transaction else SQL.unpreparedTransaction in
|
||||
transaction SQL.ReadCommitted SQL.Read $ SQL.statement dbSettingsNames $ SQL.Statement sql (arrayParam HE.text) decodeSettings prepared
|
||||
where
|
||||
sql = [qc|
|
||||
WITH
|
||||
@@ -94,14 +95,26 @@ dbSettingsStatement = SQL.Statement sql (arrayParam HE.text) decodeSettings
|
||||
substr(setting, 1, strpos(setting, '=') - 1) as k,
|
||||
substr(setting, strpos(setting, '=') + 1) as v
|
||||
FROM role_setting
|
||||
{preConfigF}
|
||||
)
|
||||
SELECT DISTINCT ON (key)
|
||||
replace(k, '{prefix}', '') AS key,
|
||||
v AS value
|
||||
FROM kv_settings
|
||||
WHERE k = ANY($1)
|
||||
ORDER BY key, database DESC;
|
||||
WHERE k = ANY($1) AND v IS NOT NULL
|
||||
ORDER BY key, database DESC NULLS LAST;
|
||||
|]
|
||||
preConfigF = case preConfFunc of
|
||||
Nothing -> mempty
|
||||
Just func -> [qc|
|
||||
UNION
|
||||
SELECT
|
||||
null as database,
|
||||
x as k,
|
||||
current_setting(x, true) as v
|
||||
FROM unnest($1) x
|
||||
JOIN {func}() _ ON TRUE
|
||||
|]::Text
|
||||
decodeSettings = HD.rowList $ (,) <$> column HD.text <*> column HD.text
|
||||
|
||||
queryRoleSettings :: Bool -> Session RoleSettings
|
||||
|
||||
+14
-10
@@ -25,13 +25,17 @@ import Hasql.Connection (acquire)
|
||||
import Network.Socket
|
||||
import Network.Socket.ByteString
|
||||
|
||||
import PostgREST.AppState (AppState)
|
||||
import PostgREST.Config (AppConfig (..), readAppConfig)
|
||||
import PostgREST.Config.Database (queryDbSettings, queryPgVersion,
|
||||
queryRoleSettings)
|
||||
import PostgREST.Config.PgVersion (PgVersion (..), minimumPgVersion)
|
||||
import PostgREST.Error (checkIsFatal)
|
||||
import PostgREST.SchemaCache (querySchemaCache)
|
||||
import PostgREST.AppState (AppState)
|
||||
import PostgREST.Config (AppConfig (..),
|
||||
readAppConfig)
|
||||
import PostgREST.Config.Database (queryDbSettings,
|
||||
queryPgVersion,
|
||||
queryRoleSettings)
|
||||
import PostgREST.Config.PgVersion (PgVersion (..),
|
||||
minimumPgVersion)
|
||||
import PostgREST.Error (checkIsFatal)
|
||||
import PostgREST.SchemaCache (querySchemaCache)
|
||||
import PostgREST.SchemaCache.Identifiers (dumpQi)
|
||||
|
||||
import qualified PostgREST.AppState as AppState
|
||||
|
||||
@@ -89,8 +93,8 @@ connectionWorker appState = do
|
||||
when configDbChannelEnabled $
|
||||
AppState.signalListener appState
|
||||
AppState.logWithZTime appState "Connection successful"
|
||||
-- this could be fail because the connection drops, but the
|
||||
-- loadSchemaCache will pick the error and retry again
|
||||
-- this could be fail because the connection drops, but the loadSchemaCache will pick the error and retry again
|
||||
-- We cannot retry after it fails immediately, because db-pre-config could have user errors. We just log the error and continue.
|
||||
when configDbConfig $ reReadConfig False appState
|
||||
scStatus <- loadSchemaCache appState
|
||||
case scStatus of
|
||||
@@ -237,7 +241,7 @@ reReadConfig startingUp appState = do
|
||||
AppConfig{..} <- AppState.getConfig appState
|
||||
dbSettings <-
|
||||
if configDbConfig then do
|
||||
qDbSettings <- AppState.usePool appState $ queryDbSettings configDbPreparedStatements
|
||||
qDbSettings <- AppState.usePool appState $ queryDbSettings (dumpQi <$> configDbPreConfig) configDbPreparedStatements
|
||||
case qDbSettings of
|
||||
Left e -> do
|
||||
AppState.logWithZTime appState
|
||||
|
||||
Reference in New Issue
Block a user