From 979018de346efb6ca8e318c879eb93785cd5f726 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Tue, 13 Apr 2021 23:29:27 -0500 Subject: [PATCH] Add a request.spec GUC for db-root-spec The request.spec GUC contains the schema cache structure in json. It's only available when the root endpoint(/) is requested and when db-root-spec is not empty. --- src/PostgREST/App.hs | 2 +- src/PostgREST/Middleware.hs | 31 ++++++++++++++++------------- src/PostgREST/Query/SqlFragment.hs | 2 +- src/PostgREST/Request/ApiRequest.hs | 10 +++++----- test/fixtures/schema.sql | 4 ++++ 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 81dd8502373..858fda15311 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -151,7 +151,7 @@ postgrestResponse conf@AppConfig{..} maybeDbStructure pool time req = do runDbHandler pool (txMode apiRequest) jwtClaims . Middleware.optionalRollback conf apiRequest $ - Middleware.runPgLocals conf jwtClaims handleReq apiRequest + Middleware.runPgLocals conf jwtClaims handleReq apiRequest dbStructure runDbHandler :: SQL.Pool -> SQL.Mode -> Auth.JWTClaims -> DbHandler a -> Handler IO a runDbHandler pool mode jwtClaims handler = do diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index 57d6bcd8b9c..d8561962055 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -40,11 +40,12 @@ import System.IO.Unsafe (unsafePerformIO) import System.Log.FastLogger (toLogStr) import PostgREST.Config (AppConfig (..), LogLevel (..)) +import PostgREST.DbStructure (DbStructure) import PostgREST.Error (Error, errorResponseFor) import PostgREST.GucHeader (addHeadersIfNotIncluded) import PostgREST.Query.SqlFragment (fromQi, intercalateSnippet, - unknownLiteral) -import PostgREST.Request.ApiRequest (ApiRequest (..)) + unknownEncoder) +import PostgREST.Request.ApiRequest (ApiRequest (..), Target (..)) import PostgREST.Request.Preferences @@ -54,33 +55,35 @@ import Protolude.Conv (toS) -- | Runs local(transaction scoped) GUCs for every request, plus the pre-request function runPgLocals :: AppConfig -> M.HashMap Text JSON.Value -> (ApiRequest -> ExceptT Error H.Transaction Wai.Response) -> - ApiRequest -> ExceptT Error H.Transaction Wai.Response -runPgLocals conf claims app req = do + ApiRequest -> DbStructure -> ExceptT Error H.Transaction Wai.Response +runPgLocals conf claims app req dbs = do lift $ H.statement mempty $ H.dynamicallyParameterized - ("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql)) + ("select " <> intercalateSnippet ", " (searchPathSql : roleSql ++ claimsSql ++ [methodSql, pathSql] ++ headersSql ++ cookiesSql ++ appSettingsSql ++ specSql)) HD.noResult (configDbPreparedStatements conf) lift $ traverse_ H.sql preReqSql app req where - methodSql = setConfigLocal mempty ("request.method", toS $ iMethod req) - pathSql = setConfigLocal mempty ("request.path", toS $ iPath req) + methodSql = setConfigLocal mempty ("request.method", iMethod req) + pathSql = setConfigLocal mempty ("request.path", iPath req) headersSql = setConfigLocal "request.header." <$> iHeaders req cookiesSql = setConfigLocal "request.cookie." <$> iCookies req claimsWithRole = let anon = JSON.String . toS $ configDbAnonRole conf in -- role claim defaults to anon if not specified in jwt M.union claims (M.singleton "role" anon) - claimsSql = setConfigLocal "request.jwt.claim." <$> [(c,unquoted v) | (c,v) <- M.toList claimsWithRole] - roleSql = maybeToList $ (\x -> setConfigLocal mempty ("role", unquoted x)) <$> M.lookup "role" claimsWithRole - appSettingsSql = setConfigLocal mempty <$> configAppSettings conf + claimsSql = setConfigLocal "request.jwt.claim." <$> [(toS c, toS $ unquoted v) | (c,v) <- M.toList claimsWithRole] + roleSql = maybeToList $ (\x -> setConfigLocal mempty ("role", toS $ unquoted x)) <$> M.lookup "role" claimsWithRole + appSettingsSql = setConfigLocal mempty <$> (join bimap toS <$> configAppSettings conf) searchPathSql = let schemas = T.intercalate ", " (iSchema req : configDbExtraSearchPath conf) in - setConfigLocal mempty ("search_path", schemas) + setConfigLocal mempty ("search_path", toS schemas) preReqSql = (\f -> "select " <> fromQi f <> "();") <$> configDbPreRequest conf - + specSql = case iTarget req of + TargetProc{tpIsRootSpec=True} -> [setConfigLocal mempty ("request.spec", toS $ JSON.encode dbs)] + _ -> mempty -- | Do a pg set_config(setting, value, true) call. This is equivalent to a SET LOCAL. - setConfigLocal :: Text -> (Text, Text) -> H.Snippet + setConfigLocal :: ByteString -> (ByteString, ByteString) -> H.Snippet setConfigLocal prefix (k, v) = - "set_config(" <> unknownLiteral (prefix <> k) <> ", " <> unknownLiteral v <> ", true)" + "set_config(" <> unknownEncoder (prefix <> k) <> ", " <> unknownEncoder v <> ", true)" -- | Log in apache format. Only requests that have a status greater than minStatus are logged. -- | There's no way to filter logs in the apache format on wai-extra: https://hackage.haskell.org/package/wai-extra-3.0.29.2/docs/Network-Wai-Middleware-RequestLogger.html#t:OutputFormat. diff --git a/src/PostgREST/Query/SqlFragment.hs b/src/PostgREST/Query/SqlFragment.hs index 26def5fa9f8..c1b0f9d5c43 100644 --- a/src/PostgREST/Query/SqlFragment.hs +++ b/src/PostgREST/Query/SqlFragment.hs @@ -32,7 +32,7 @@ module PostgREST.Query.SqlFragment , returningF , selectBody , sourceCTEName - , unknownLiteral + , unknownEncoder , intercalateSnippet ) where diff --git a/src/PostgREST/Request/ApiRequest.hs b/src/PostgREST/Request/ApiRequest.hs index 74be71eb354..1cfefa95145 100644 --- a/src/PostgREST/Request/ApiRequest.hs +++ b/src/PostgREST/Request/ApiRequest.hs @@ -42,7 +42,7 @@ import Network.HTTP.Types.URI (parseQueryReplacePlus, parseSimpleQuery) import Network.Wai (Request (..)) import Network.Wai.Parse (parseHttpAccept) -import Web.Cookie (parseCookiesText) +import Web.Cookie (parseCookies) import PostgREST.ContentType (ContentType (..)) import PostgREST.DbStructure (DbStructure (..)) @@ -152,8 +152,8 @@ data ApiRequest = ApiRequest { , iOrder :: [(Text, Text)] -- ^ &order parameters for each level , iCanonicalQS :: ByteString -- ^ Alphabetized (canonical) request query string for response URLs , iJWT :: Text -- ^ JSON Web Token - , iHeaders :: [(Text, Text)] -- ^ HTTP request headers - , iCookies :: [(Text, Text)] -- ^ Request Cookies + , iHeaders :: [(ByteString, ByteString)] -- ^ HTTP request headers + , iCookies :: [(ByteString, ByteString)] -- ^ Request Cookies , iPath :: ByteString -- ^ Raw request path , iMethod :: ByteString -- ^ Raw request method , iProfile :: Maybe Schema -- ^ The request profile for enabling use of multiple schemas. Follows the spec in hhttps://www.w3.org/TR/dx-prof-conneg/ttps://www.w3.org/TR/dx-prof-conneg/. @@ -200,8 +200,8 @@ userApiRequest confSchemas rootSpec dbStructure req reqBody . map (join (***) toS . second (fromMaybe BS.empty)) $ qString , iJWT = tokenStr - , iHeaders = [ (toS $ CI.foldedCase k, toS v) | (k,v) <- hdrs, k /= hCookie] - , iCookies = maybe [] parseCookiesText $ lookupHeader "Cookie" + , iHeaders = [ (CI.foldedCase k, v) | (k,v) <- hdrs, k /= hCookie] + , iCookies = maybe [] parseCookies $ lookupHeader "Cookie" , iPath = rawPathInfo req , iMethod = method , iProfile = profile diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index 4931a654fc2..88c66cdb910 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -1663,6 +1663,10 @@ case current_setting('request.header.accept', true) end $_$ language plpgsql; +create or replace function root_override() returns json as $$ + select current_setting('request.spec', true)::json; +$$ language sql; + create or replace function welcome() returns text as $$ select 'Welcome to PostgREST'::text; $$ language sql;