set all session data in a single paramater, 'hasura.user' (closes #825)

This commit is contained in:
Vamshi Surabhi 2018-10-24 16:09:47 +05:30 committed by GitHub
parent cd030068c2
commit ab9692da4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 40 additions and 26 deletions

View File

@ -0,0 +1,22 @@
Auditing actions on tables in Postgres
======================================
Typically audit logging is added to some of the tables to comply with various certifications.
You may want to capture the user information (role and the session variables) for every change in Postgres that is done through graphql-engine.
For every mutation, hasura roughly executes the following transaction:
.. code-block:: sql
BEGIN;
SET local "hasura.user" = '{"x-hasura-role": "role", ... various session variables}'
SQL related to the mutation
COMMIT;
This information can therefore be captured in any trigger on the underlying tables by using the ``current_setting`` function as follows:
.. code-block:: sql
current_setting('hasura.user');
We've set up some utility functions that'll let you quickly get started with auditing in this `repo <https://github.com/hasura/audit-trigger>`__.

View File

@ -54,3 +54,4 @@ Articles:
Sample apps <sample-apps/index> Sample apps <sample-apps/index>
Integration/migration tutorials <integrations/index> Integration/migration tutorials <integrations/index>
Integrating with monitoring frameworks <monitoring/index> Integrating with monitoring frameworks <monitoring/index>
Auditing tables <auditing-tables>

View File

@ -210,16 +210,16 @@ valueParser :: (MonadError QErr m) => PGColType -> Value -> m S.SQLExp
valueParser columnType = \case valueParser columnType = \case
-- When it is a special variable -- When it is a special variable
val@(String t) val@(String t)
| isXHasuraTxt t -> asCurrentSetting t | isXHasuraTxt t -> return $ fromCurSess t
| isReqUserId t -> asCurrentSetting userIdHeader | isReqUserId t -> return $ fromCurSess userIdHeader
| otherwise -> txtRHSBuilder columnType val | otherwise -> txtRHSBuilder columnType val
-- Typical value as Aeson's value -- Typical value as Aeson's value
val -> txtRHSBuilder columnType val val -> txtRHSBuilder columnType val
where where
asCurrentSetting hdr = return $ S.SEUnsafe $ curSess = S.SEUnsafe "current_setting('hasura.user')::json"
"current_setting('hasura." <> dropAndSnakeCase hdr fromCurSess hdr =
<> "')::" <> T.pack (show columnType) S.SEOpApp (S.SQLOp "->>") [curSess, S.SELit $ T.toLower hdr]
`S.SETyAnn` (S.AnnType $ T.pack $ show columnType)
-- Convert where clause into SQL BoolExp -- Convert where clause into SQL BoolExp
convFilterExp :: (MonadError QErr m) convFilterExp :: (MonadError QErr m)

View File

@ -65,7 +65,8 @@ $(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
) )
adminUserInfo :: UserInfo adminUserInfo :: UserInfo
adminUserInfo = UserInfo adminRole Map.empty adminUserInfo =
UserInfo adminRole $ Map.singleton "x-hasura-role" "admin"
data PermType data PermType
= PTInsert = PTInsert

View File

@ -172,7 +172,7 @@ getUserInfo logger manager rawHeaders = \case
userInfoFromHeaders = userInfoFromHeaders =
case M.lookup userRoleHeader headers of case M.lookup userRoleHeader headers of
Just v -> UserInfo (RoleName v) headers Just v -> UserInfo (RoleName v) headers
Nothing -> UserInfo adminRole M.empty Nothing -> adminUserInfo
userInfoWhenAccessKey key reqKey = do userInfoWhenAccessKey key reqKey = do
when (reqKey /= getAccessKey key) $ throw401 $ "invalid " <> accessKeyHeader when (reqKey /= getAccessKey key) $ throw401 $ "invalid " <> accessKeyHeader

View File

@ -10,10 +10,11 @@ import Data.Aeson.Casing
import Data.Aeson.TH import Data.Aeson.TH
import Language.Haskell.TH.Syntax (Lift) import Language.Haskell.TH.Syntax (Lift)
import qualified Data.Aeson.Text as AT
import qualified Data.ByteString.Builder as BB import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
import qualified Data.Text.Lazy as LT
import qualified Data.Vector as V import qualified Data.Vector as V
import Hasura.Prelude import Hasura.Prelude
@ -25,7 +26,6 @@ import Hasura.RQL.DDL.Schema.Table
import Hasura.RQL.DML.QueryTemplate import Hasura.RQL.DML.QueryTemplate
import Hasura.RQL.DML.Returning (encodeJSONVector) import Hasura.RQL.DML.Returning (encodeJSONVector)
import Hasura.RQL.Types import Hasura.RQL.Types
import Hasura.Server.Utils
import Hasura.SQL.Types import Hasura.SQL.Types
import qualified Database.PG.Query as Q import qualified Database.PG.Query as Q
@ -220,9 +220,9 @@ buildTxAny userInfo sc rq = case rq of
setHeadersTx :: UserInfo -> Q.TxE QErr () setHeadersTx :: UserInfo -> Q.TxE QErr ()
setHeadersTx userInfo = setHeadersTx userInfo =
forM_ hdrs $ \h -> Q.unitQE defaultTxErrorHandler (mkQ h) () False Q.unitQE defaultTxErrorHandler setSess () False
where where
hdrs = Map.toList $ Map.delete accessKeyHeader toStrictText = LT.toStrict . AT.encodeToLazyText
$ userHeaders userInfo setSess = Q.fromText $
mkQ (h, v) = Q.fromText $ "SET LOCAL \"hasura.user\" = " <>
"SET LOCAL hasura." <> dropAndSnakeCase h <> " = " <> pgFmtLit v pgFmtLit (toStrictText $ userHeaders userInfo)

View File

@ -21,16 +21,6 @@ import qualified Text.Ginger as TG
import Hasura.Prelude import Hasura.Prelude
dropAndSnakeCase :: T.Text -> T.Text
dropAndSnakeCase = T.drop 9 . toSnakeCase . T.toLower
toSnakeCase :: T.Text -> T.Text
toSnakeCase = T.pack . map change . T.unpack
where
change '-' = '_'
change c = c
isXHasuraTxt :: T.Text -> Bool isXHasuraTxt :: T.Text -> Bool
isXHasuraTxt = T.isInfixOf "x-hasura-" . T.toLower isXHasuraTxt = T.isInfixOf "x-hasura-" . T.toLower