11{-# LANGUAGE NamedFieldPuns #-}
2+ {-# LANGUAGE QuasiQuotes #-}
23{-# LANGUAGE RecordWildCards #-}
4+ {-# LANGUAGE ExistentialQuantification #-}
35{-|
46Module : PostgREST.Query.PreQuery
57Description : Builds queries that run prior to the main query
@@ -20,6 +22,7 @@ import qualified Hasql.Decoders as HD
2022import qualified Hasql.DynamicStatements.Snippet as SQL hiding (sql )
2123import qualified Hasql.Encoders as HE
2224import qualified Hasql.Statement as SQL
25+ import NeatInterpolation (trimming )
2326import qualified PostgreSQL.Binary.Encoding as PGBinary
2427import Unsafe.Coerce (unsafeCoerce )
2528
@@ -39,12 +42,21 @@ import Protolude
3942-- sets transaction variables
4043txVarQuery :: DbActionPlan -> AppConfig -> AuthResult -> ApiRequest -> SQL. Statement () ()
4144txVarQuery dbActPlan AppConfig {.. } AuthResult {.. } ApiRequest {.. } =
42- lmap (const settings) $ SQL. Statement txSettingsSql txSettingsParams HD. noResult configDbPreparedStatements
45+ if configDbConfig then
46+ lmap (const roleSettingsParams) $ SQL. Statement (txSettingsSqlWithRoleSettings configDbHasParameterPrivilege) txRoleSettingsParams HD. noResult configDbPreparedStatements
47+ else
48+ lmap (const settings) $ SQL. Statement txSettingsSqlWithoutRoleSettings txSettingsParams HD. noResult configDbPreparedStatements
4349 where
44- settings = unzip $
45- -- To ensure `GRANT SET ON PARAMETER <superuser_setting> TO authenticator` works, the role settings must be set before the impersonated role.
46- -- Otherwise the GRANT SET would have to be applied to the impersonated role. See https://github.com/PostgREST/postgrest/issues/3045
47- searchPathSetting : roleSettings ++
50+ settings =
51+ preRoleSettings ++ postRoleSettings
52+
53+ roleSettingsParams =
54+ (preRoleSettings, authRole, postRoleSettings)
55+
56+ preRoleSettings =
57+ [ searchPathSetting ]
58+
59+ postRoleSettings =
4860 [ roleSetting
4961 , claimsSetting
5062 , methodSetting
@@ -65,7 +77,6 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
6577 claims = authClaims & KM. insert " role" (JSON. String $ decodeUtf8 authRole) -- insert "role" to claims as well
6678
6779 roleSetting = (" role" , authRole)
68- roleSettings = HM. toList $ fromMaybe mempty $ HM. lookup authRole configRoleSettings
6980 appSettings = join bimap toUtf8 <$> configAppSettings
7081 timezoneSetting = maybe mempty (\ (PreferTimezone tz) -> [(" timezone" , tz)]) $ preferTimezone iPreferences
7182 funcSettings = case dbActPlan of
@@ -75,15 +86,71 @@ txVarQuery dbActPlan AppConfig{..} AuthResult{..} ApiRequest{..} =
7586 let schemas = escapeIdentList (iSchema : configDbExtraSearchPath) in
7687 (" search_path" , schemas)
7788
78- txSettingsSql :: ByteString
79- txSettingsSql =
80- " select set_config(setting, value, true) " <>
81- " from unnest($1::text[], $2::text[]) with ordinality as _(setting, value, ordinality) " <>
82- " order by ordinality"
83- txSettingsParams :: HE. Params ([ByteString ], [ByteString ])
89+ txSettingsSqlWithoutRoleSettings :: ByteString
90+ txSettingsSqlWithoutRoleSettings =
91+ encodeUtf8 [trimming |
92+ select set_config(setting, value, true)
93+ from unnest($$1::text[], $$2::text[]) with ordinality as _(setting, value, ordinality)
94+ order by ordinality
95+ |]
96+
97+ txSettingsSqlWithRoleSettings :: Bool -> ByteString
98+ txSettingsSqlWithRoleSettings hasParameterPrivilege =
99+ encodeUtf8 [trimming |
100+ with pre_settings as (
101+ select 1::int as sort_order, ordinality, setting, value
102+ from unnest($$1::text[], $$2::text[]) with ordinality as _(setting, value, ordinality)
103+ ),
104+ role_setting as (
105+ select unnest(r.rolconfig) as setting
106+ from pg_catalog.pg_roles r
107+ where r.rolname = $$3
108+ ),
109+ kv_settings as (
110+ select setting_parts[1] as key, array_to_string(setting_parts[2:], '=') as value
111+ from role_setting
112+ cross join lateral string_to_array(setting, '=') as parsed(setting_parts)
113+ ),
114+ role_settings as (
115+ select 2::int as sort_order, row_number() over (order by kv.key) as ordinality, kv.key as setting, kv.value
116+ from kv_settings kv
117+ join pg_catalog.pg_settings ps on ps.name = kv.key and ${parameterPrivilegeCheck}
118+ where kv.key <> 'default_transaction_isolation'
119+ ),
120+ post_settings as (
121+ select 3::int as sort_order, ordinality, setting, value
122+ from unnest($$4::text[], $$5::text[]) with ordinality as _(setting, value, ordinality)
123+ ),
124+ settings as (
125+ select * from pre_settings
126+ union all select * from role_settings
127+ union all select * from post_settings
128+ )
129+ select set_config(setting, value, true)
130+ from settings
131+ order by sort_order, ordinality
132+ |]
133+ where
134+ parameterPrivilegeCheck =
135+ if hasParameterPrivilege then
136+ [trimming |(ps.context = 'user' or has_parameter_privilege(quote_ident(current_user)::regrole::oid, ps.name, 'set'))|] :: Text
137+ else
138+ [trimming |(ps.context = 'user')|] :: Text
139+
140+ txSettingsParams :: HE. Params [(ByteString , ByteString )]
84141txSettingsParams =
85- (fst >$< txSettingsParameter) <>
86- (snd >$< txSettingsParameter)
142+ unzip >$< ((fst >$< txSettingsParameter) <> (snd >$< txSettingsParameter))
143+
144+ txRoleSettingsParams :: HE. Params ([(ByteString , ByteString )], ByteString , [(ByteString , ByteString )])
145+ txRoleSettingsParams =
146+ ((\ (preSettings, _, _) -> preSettings) >$< txSettingsParams) <>
147+ ((\ (_, role, _) -> role) >$< txRoleParameter) <>
148+ ((\ (_, _, postSettings) -> postSettings) >$< txSettingsParams)
149+
150+ txRoleParameter :: HE. Params ByteString
151+ txRoleParameter =
152+ HE. param $ HE. nonNullable HE. unknown
153+
87154txSettingsParameter :: HE. Params [ByteString ]
88155txSettingsParameter =
89156 HE. param $ HE. nonNullable txSettingsArray
0 commit comments