mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-21 15:38:40 +03:00
clean up user variables parsing logic and fix explain api (#869)
This commit is contained in:
parent
fb842fde6f
commit
8b0082eac1
6
console/package-lock.json
generated
6
console/package-lock.json
generated
@ -6448,9 +6448,9 @@
|
||||
}
|
||||
},
|
||||
"hasura-console-graphiql": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/hasura-console-graphiql/-/hasura-console-graphiql-0.0.6.tgz",
|
||||
"integrity": "sha512-9gdwhtrsE3tkwfoWEy/3qOhvB3k16G8w7fF/ReLXNl+f2b3HhBZM5tXYYI9vN/v85vufx611E6uZO3AeU0IaOQ==",
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/hasura-console-graphiql/-/hasura-console-graphiql-0.0.8.tgz",
|
||||
"integrity": "sha512-0V5xqb/8BBAufLaQr517pcROee0RnhW1yRWVhogKCts8oKDTyZknsbiC95PC3e+kvALVfwZ3HyMuR18eW/Dnyg==",
|
||||
"requires": {
|
||||
"codemirror": "^5.26.0",
|
||||
"codemirror-graphql": "^0.7.1",
|
||||
|
@ -59,7 +59,7 @@
|
||||
"deep-equal": "^1.0.1",
|
||||
"graphiql": "^0.11.11",
|
||||
"graphql": "^0.13.2",
|
||||
"hasura-console-graphiql": "0.0.6",
|
||||
"hasura-console-graphiql": "0.0.8",
|
||||
"history": "^3.0.0",
|
||||
"hoist-non-react-statics": "^1.0.3",
|
||||
"invariant": "^2.2.0",
|
||||
|
@ -227,15 +227,21 @@ const graphQLFetcherFinal = (graphQLParams, url, headers) => {
|
||||
};
|
||||
|
||||
/* Analyse Fetcher */
|
||||
const analyzeFetcher = (url, headers) => {
|
||||
const analyzeFetcher = (url, headers, analyzeApiChange) => {
|
||||
return query => {
|
||||
const editedQuery = {
|
||||
query,
|
||||
};
|
||||
const user = {};
|
||||
let user = {};
|
||||
const reqHeaders = getHeadersAsJSON(headers);
|
||||
user.role = 'admin';
|
||||
user.headers = reqHeaders;
|
||||
if (!analyzeApiChange) {
|
||||
user.role = 'admin';
|
||||
user.headers = reqHeaders;
|
||||
} else {
|
||||
user = {
|
||||
'x-hasura-role': 'admin',
|
||||
};
|
||||
}
|
||||
editedQuery.user = user;
|
||||
return fetch(`${url}/explain`, {
|
||||
method: 'post',
|
||||
|
@ -758,7 +758,6 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
background: rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
outline: none;
|
||||
overflow: -webkit-paged-y;
|
||||
}
|
||||
.myOverlayClass {
|
||||
position: fixed;
|
||||
@ -808,11 +807,11 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
display: -webkit-box;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
height: 100%;
|
||||
height: calc(100% - 46px);
|
||||
}
|
||||
.topLevelNodesWrapper {
|
||||
border-right: 1px solid #ccc;
|
||||
height: calc(100% - 42px);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.textCenter {
|
||||
@ -827,7 +826,9 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
font-size: 16px;
|
||||
}
|
||||
.analysisWrapper {
|
||||
height: calc(100% - 30px);
|
||||
height: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
.topLevelNodesWrapper ul {
|
||||
-webkit-padding-start: 0px;
|
||||
@ -861,30 +862,53 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
color: #e2701a;
|
||||
}
|
||||
.plansWrapper {
|
||||
height: 50%;
|
||||
position: relative;
|
||||
padding: 0px 20px;
|
||||
}
|
||||
.plansTitle {
|
||||
padding: 10px 20px;
|
||||
padding-bottom: 0;
|
||||
padding: 10px 0px;
|
||||
color: #767e93;
|
||||
font-weight: 600;
|
||||
}
|
||||
.overflowAuto {
|
||||
overflow: auto;
|
||||
|
||||
.copyGenerated {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 60px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.copyGenerated img,
|
||||
.copyExecution img {
|
||||
width: 20px;
|
||||
opacity: 0.6;
|
||||
}
|
||||
.copyGenerated img:hover,
|
||||
.copyExecution img:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.copyGenerated:focus,
|
||||
.copyExecution:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.codeBlock {
|
||||
padding: 10px 20px;
|
||||
width: 90%;
|
||||
/* position: relative;
|
||||
padding: 10px 20px; */
|
||||
background-color: rgb(253, 249, 237);
|
||||
margin: 20px;
|
||||
/* margin: 20px;
|
||||
width: 100%; */
|
||||
width: auto;
|
||||
border-radius: 5px;
|
||||
max-height: 150px;
|
||||
max-height: calc(100% - 60px);
|
||||
overflow: auto;
|
||||
margin-top: 10px;
|
||||
margin-top: 0px;
|
||||
min-height: calc(100% - 60px);
|
||||
}
|
||||
|
||||
.codeBlock pre {
|
||||
display: block;
|
||||
padding: 0px;
|
||||
padding: 10px 20px;
|
||||
margin: 0px;
|
||||
font-size: 13px;
|
||||
line-height: unset;
|
||||
@ -895,6 +919,7 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
overflow: unset;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.codeBlock code {
|
||||
@ -905,6 +930,46 @@ div.CodeMirror-lint-tooltip > * + * {
|
||||
.graphiql-container .analyse-button-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.copyTooltip {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.copyTooltip .tooltiptext {
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
border-radius: 6px;
|
||||
padding: 4px 0px;
|
||||
font-size: 14px;
|
||||
position: absolute;
|
||||
z-index: 1000000000;
|
||||
right: -21px;
|
||||
bottom: 30px;
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.3s;
|
||||
transition: opacity 0.3s;
|
||||
display: none;
|
||||
width: 57px;
|
||||
}
|
||||
|
||||
.copyTooltip .tooltiptext::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 22px;
|
||||
margin-left: -5px;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
border-color: #555 transparent transparent transparent;
|
||||
}
|
||||
|
||||
.copyTooltip:hover .tooltiptext {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
display: block;
|
||||
}
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
|
@ -22,6 +22,7 @@ class GraphiQLWrapper extends Component {
|
||||
onBoardingEnabled: false,
|
||||
queries: null,
|
||||
supportAnalyze: false,
|
||||
analyzeApiChange: false,
|
||||
};
|
||||
const queryFile = this.props.queryParams
|
||||
? this.props.queryParams.query_file
|
||||
@ -35,13 +36,17 @@ class GraphiQLWrapper extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.data.serverVersion) {
|
||||
this.checkSemVer(this.props.data.serverVersion);
|
||||
this.checkSemVer(this.props.data.serverVersion).then(() =>
|
||||
this.checkNewAnalyzeVersion(this.props.data.serverVersion)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.data.serverVersion !== this.props.data.serverVersion) {
|
||||
this.checkSemVer(nextProps.data.serverVersion);
|
||||
this.checkSemVer(nextProps.data.serverVersion).then(() =>
|
||||
this.checkNewAnalyzeVersion(nextProps.data.serverVersion)
|
||||
);
|
||||
}
|
||||
}
|
||||
shouldComponentUpdate(nextProps) {
|
||||
@ -60,6 +65,21 @@ class GraphiQLWrapper extends Component {
|
||||
this.updateAnalyzeState(false);
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
checkNewAnalyzeVersion(version) {
|
||||
try {
|
||||
const analyzeApiChange = semverCheck('analyzeApiChange', version);
|
||||
if (analyzeApiChange) {
|
||||
this.updateAnalyzeApiState(true);
|
||||
} else {
|
||||
this.updateAnalyzeApiState(false);
|
||||
}
|
||||
} catch (e) {
|
||||
this.updateAnalyzeApiState(false);
|
||||
console.error(e);
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
updateAnalyzeState(supportAnalyze) {
|
||||
this.setState({
|
||||
@ -67,10 +87,16 @@ class GraphiQLWrapper extends Component {
|
||||
supportAnalyze: supportAnalyze,
|
||||
});
|
||||
}
|
||||
updateAnalyzeApiState(analyzeApiChange) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
analyzeApiChange: analyzeApiChange,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const styles = require('../Common/Common.scss');
|
||||
const { supportAnalyze } = this.state;
|
||||
const { supportAnalyze, analyzeApiChange } = this.state;
|
||||
const graphQLFetcher = graphQLParams => {
|
||||
if (this.state.headerFocus) {
|
||||
return null;
|
||||
@ -84,7 +110,8 @@ class GraphiQLWrapper extends Component {
|
||||
|
||||
const analyzeFetcherInstance = analyzeFetcher(
|
||||
this.props.data.url,
|
||||
this.props.data.headers
|
||||
this.props.data.headers,
|
||||
analyzeApiChange
|
||||
);
|
||||
|
||||
// let content = "fetching schema";
|
||||
|
@ -6,6 +6,7 @@ const componentsSemver = {
|
||||
eventRedeliver: '1.0.0-alpha17',
|
||||
sqlAnalyze: '1.0.0-alpha25',
|
||||
aggregationPerm: '1.0.0-alpha26',
|
||||
analyzeApiChange: '1.0.0-alpha26',
|
||||
insertPrefix: '1.0.0-alpha26',
|
||||
};
|
||||
|
||||
|
@ -35,7 +35,7 @@ import qualified Hasura.Server.Query as RQ
|
||||
data GQLExplain
|
||||
= GQLExplain
|
||||
{ _gqeQuery :: !GH.GraphQLRequest
|
||||
, _gqeUser :: !UserInfo
|
||||
, _gqeUser :: !(Maybe (Map.HashMap Text Text))
|
||||
} deriving (Show, Eq)
|
||||
|
||||
$(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
|
||||
@ -103,7 +103,7 @@ explainField userInfo gCtx fld =
|
||||
validateHdrs hdrs = do
|
||||
let receivedHdrs = userVars userInfo
|
||||
forM_ hdrs $ \hdr ->
|
||||
unless (Map.member hdr receivedHdrs) $
|
||||
unless (isJust $ getVarVal hdr receivedHdrs) $
|
||||
throw400 NotFound $ hdr <<> " header is expected but not found"
|
||||
|
||||
explainGQLQuery
|
||||
@ -113,7 +113,7 @@ explainGQLQuery
|
||||
-> GCtxMap
|
||||
-> GQLExplain
|
||||
-> m BL.ByteString
|
||||
explainGQLQuery pool iso gCtxMap (GQLExplain query userInfo)= do
|
||||
explainGQLQuery pool iso gCtxMap (GQLExplain query userVarsRaw)= do
|
||||
(opTy, selSet) <- runReaderT (GV.validateGQ query) gCtx
|
||||
unless (opTy == G.OperationTypeQuery) $
|
||||
throw400 InvalidParams "only queries can be explained"
|
||||
@ -121,6 +121,9 @@ explainGQLQuery pool iso gCtxMap (GQLExplain query userInfo)= do
|
||||
plans <- liftIO (runExceptT $ runTx tx) >>= liftEither
|
||||
return $ J.encode plans
|
||||
where
|
||||
usrVars = mkUserVars $ maybe [] Map.toList userVarsRaw
|
||||
userInfo = mkUserInfo (fromMaybe adminRole $ roleFromVars usrVars) usrVars
|
||||
gCtx = getGCtx (userRole userInfo) gCtxMap
|
||||
runTx tx =
|
||||
Q.runTx pool (iso, Nothing) $ RQ.setHeadersTx userInfo >> tx
|
||||
Q.runTx pool (iso, Nothing) $
|
||||
RQ.setHeadersTx (userVars userInfo) >> tx
|
||||
|
@ -62,9 +62,9 @@ buildTx userInfo gCtx fld = do
|
||||
"lookup failed: opctx: " <> showName f
|
||||
|
||||
validateHdrs hdrs = do
|
||||
let receivedHdrs = userVars userInfo
|
||||
let receivedVars = userVars userInfo
|
||||
forM_ hdrs $ \hdr ->
|
||||
unless (Map.member hdr receivedHdrs) $
|
||||
unless (isJust $ getVarVal hdr receivedVars) $
|
||||
throw400 NotFound $ hdr <<> " header is expected but not found"
|
||||
|
||||
-- {-# SCC resolveFld #-}
|
||||
|
@ -37,4 +37,4 @@ runGQ pool isoL userInfo gCtxMap req = do
|
||||
gCtx = getGCtx (userRole userInfo) gCtxMap
|
||||
runTx tx =
|
||||
Q.runTx pool (isoL, Nothing) $
|
||||
RQ.setHeadersTx userInfo >> tx
|
||||
RQ.setHeadersTx (userVars userInfo) >> tx
|
||||
|
@ -169,7 +169,7 @@ onStart serverEnv wsConn (StartMsg opId q) = catchAndIgnore $ do
|
||||
|
||||
(opTy, fields) <- either (withComplete . preExecErr) return $
|
||||
runReaderT (validateGQ q) gCtx
|
||||
let qTx = RQ.setHeadersTx userInfo >>
|
||||
let qTx = RQ.setHeadersTx (userVars userInfo) >>
|
||||
resolveSelSet userInfo gCtx opTy fields
|
||||
|
||||
case opTy of
|
||||
|
@ -27,7 +27,6 @@ import Hasura.GraphQL.Utils
|
||||
import Hasura.GraphQL.Validate.Context
|
||||
import Hasura.GraphQL.Validate.Types
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.Server.Utils (duplicates)
|
||||
import Hasura.SQL.Value
|
||||
|
||||
newtype P a = P { unP :: Maybe (Either AnnGValue a)}
|
||||
|
@ -53,7 +53,6 @@ import Hasura.RQL.DDL.Permission.Internal
|
||||
import Hasura.RQL.DDL.Permission.Triggers
|
||||
import Hasura.RQL.DML.Internal (onlyPositiveInt)
|
||||
import Hasura.RQL.Types
|
||||
import Hasura.Server.Utils (isXHasuraTxt)
|
||||
import Hasura.SQL.Types
|
||||
|
||||
import qualified Database.PG.Query as Q
|
||||
@ -133,7 +132,7 @@ buildInsPermInfo tabInfo (PermDef rn (InsPerm chk upsrt set) _) = withPathK "per
|
||||
vn = buildViewName tn rn PTInsert
|
||||
|
||||
fetchHdr (String t) = bool Nothing (Just $ T.toLower t)
|
||||
$ isXHasuraTxt t
|
||||
$ isUserVar t
|
||||
fetchHdr _ = Nothing
|
||||
|
||||
buildInsInfra :: QualifiedTable -> InsPermInfo -> Q.TxE QErr ()
|
||||
|
@ -196,7 +196,7 @@ getDependentHeaders boolExp = case boolExp of
|
||||
|
||||
parseOnlyString val = case val of
|
||||
(String t)
|
||||
| isXHasuraTxt t -> [T.toLower t]
|
||||
| isUserVar t -> [T.toLower t]
|
||||
| isReqUserId t -> [userIdHeader]
|
||||
| otherwise -> []
|
||||
_ -> []
|
||||
@ -209,7 +209,7 @@ valueParser :: (MonadError QErr m) => PGColType -> Value -> m S.SQLExp
|
||||
valueParser columnType = \case
|
||||
-- When it is a special variable
|
||||
val@(String t)
|
||||
| isXHasuraTxt t -> return $ fromCurSess t
|
||||
| isUserVar t -> return $ fromCurSess t
|
||||
| isReqUserId t -> return $ fromCurSess userIdHeader
|
||||
| otherwise -> txtRHSBuilder columnType val
|
||||
-- Typical value as Aeson's value
|
||||
|
@ -243,7 +243,7 @@ toJSONableExp colTy expn
|
||||
-- validate headers
|
||||
validateHeaders :: (P1C m) => [T.Text] -> m ()
|
||||
validateHeaders depHeaders = do
|
||||
headers <- M.keys . userVars <$> askUserInfo
|
||||
headers <- getVarNames . userVars <$> askUserInfo
|
||||
forM_ depHeaders $ \hdr ->
|
||||
unless (hdr `elem` map T.toLower headers) $
|
||||
throw400 NotFound $ hdr <<> " header is expected but not found"
|
||||
|
@ -3,13 +3,22 @@
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
|
||||
module Hasura.RQL.Types.Permission
|
||||
( RoleName(..)
|
||||
, UserId(..)
|
||||
|
||||
, UserVars
|
||||
, UserInfo(..)
|
||||
, mkUserVars
|
||||
, isUserVar
|
||||
, getVarNames
|
||||
, getVarVal
|
||||
, roleFromVars
|
||||
|
||||
, UserInfo
|
||||
, userRole
|
||||
, userVars
|
||||
, mkUserInfo
|
||||
, adminUserInfo
|
||||
, adminRole
|
||||
, isAdmin
|
||||
@ -24,9 +33,6 @@ import Hasura.SQL.Types
|
||||
import qualified Database.PG.Query as Q
|
||||
|
||||
import Data.Aeson
|
||||
import qualified Data.Aeson as J
|
||||
import qualified Data.Aeson.Casing as J
|
||||
import qualified Data.Aeson.TH as J
|
||||
import Data.Hashable
|
||||
import Data.Word
|
||||
import Instances.TH.Lift ()
|
||||
@ -53,7 +59,34 @@ isAdmin = (adminRole ==)
|
||||
newtype UserId = UserId { getUserId :: Word64 }
|
||||
deriving (Show, Eq, FromJSON, ToJSON)
|
||||
|
||||
type UserVars = (Map.HashMap T.Text T.Text)
|
||||
newtype UserVars
|
||||
= UserVars { unUserVars :: Map.HashMap T.Text T.Text }
|
||||
deriving (Show, Eq, FromJSON, ToJSON, Hashable)
|
||||
|
||||
isUserVar :: T.Text -> Bool
|
||||
isUserVar = T.isPrefixOf "x-hasura-" . T.toLower
|
||||
|
||||
roleFromVars :: UserVars -> Maybe RoleName
|
||||
roleFromVars =
|
||||
fmap RoleName . getVarVal userRoleVar
|
||||
|
||||
getVarVal :: Text -> UserVars -> Maybe Text
|
||||
getVarVal k =
|
||||
Map.lookup k . unUserVars
|
||||
|
||||
getVarNames :: UserVars -> [T.Text]
|
||||
getVarNames =
|
||||
Map.keys . unUserVars
|
||||
|
||||
mkUserVars :: [(T.Text, T.Text)] -> UserVars
|
||||
mkUserVars l =
|
||||
UserVars $ Map.fromList
|
||||
[ (T.toLower k, v)
|
||||
| (k, v) <- l, isUserVar k
|
||||
]
|
||||
|
||||
userRoleVar :: Text
|
||||
userRoleVar = "x-hasura-role"
|
||||
|
||||
data UserInfo
|
||||
= UserInfo
|
||||
@ -61,15 +94,19 @@ data UserInfo
|
||||
, userVars :: !UserVars
|
||||
} deriving (Show, Eq, Generic)
|
||||
|
||||
mkUserInfo :: RoleName -> UserVars -> UserInfo
|
||||
mkUserInfo rn (UserVars v) =
|
||||
UserInfo rn $ UserVars $ Map.insert userRoleVar (getRoleTxt rn) v
|
||||
|
||||
instance Hashable UserInfo
|
||||
|
||||
$(J.deriveJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
|
||||
''UserInfo
|
||||
)
|
||||
-- $(J.deriveToJSON (J.aesonDrop 4 J.camelCase){J.omitNothingFields=True}
|
||||
-- ''UserInfo
|
||||
-- )
|
||||
|
||||
adminUserInfo :: UserInfo
|
||||
adminUserInfo =
|
||||
UserInfo adminRole $ Map.singleton "x-hasura-role" "admin"
|
||||
mkUserInfo adminRole $ mkUserVars []
|
||||
|
||||
data PermType
|
||||
= PTInsert
|
||||
|
@ -104,11 +104,6 @@ parseBody = do
|
||||
Just jVal -> decodeValue jVal
|
||||
Nothing -> throw400 InvalidJSON "invalid json"
|
||||
|
||||
filterHeaders :: [(T.Text, T.Text)] -> [(T.Text, T.Text)]
|
||||
filterHeaders hdrs = flip filter hdrs $ \(h, _) ->
|
||||
isXHasuraTxt h && (T.toLower h /= userRoleHeader)
|
||||
&& (T.toLower h /= accessKeyHeader)
|
||||
|
||||
onlyAdmin :: Handler ()
|
||||
onlyAdmin = do
|
||||
uRole <- asks (userRole . hcUser)
|
||||
|
@ -29,7 +29,6 @@ import Data.CaseInsensitive (CI (..), original)
|
||||
import Data.IORef (newIORef)
|
||||
|
||||
import qualified Data.ByteString.Lazy as BL
|
||||
import qualified Data.HashMap.Strict as M
|
||||
import qualified Data.String.Conversions as CS
|
||||
import qualified Data.Text as T
|
||||
import qualified Network.HTTP.Client as H
|
||||
@ -148,14 +147,14 @@ mkUserInfoFromResp logger url statusCode respBody
|
||||
throw500 "Invalid response from authorization hook"
|
||||
where
|
||||
getUserInfoFromHdrs rawHeaders = do
|
||||
let headers = M.fromList [(T.toLower k, v) | (k, v) <- M.toList rawHeaders]
|
||||
case M.lookup userRoleHeader headers of
|
||||
let usrVars = mkUserVars rawHeaders
|
||||
case roleFromVars usrVars of
|
||||
Nothing -> do
|
||||
logError
|
||||
throw500 "missing x-hasura-role key in webhook response"
|
||||
Just v -> do
|
||||
Just rn -> do
|
||||
logWebHookResp L.LevelInfo Nothing
|
||||
return $ UserInfo (RoleName v) headers
|
||||
return $ mkUserInfo rn usrVars
|
||||
|
||||
logError =
|
||||
logWebHookResp L.LevelError $ Just respBody
|
||||
@ -212,7 +211,7 @@ getUserInfo logger manager rawHeaders = \case
|
||||
AMNoAuth -> return userInfoFromHeaders
|
||||
|
||||
AMAccessKey accKey unAuthRole ->
|
||||
case getHeader accessKeyHeader of
|
||||
case getVarVal accessKeyHeader usrVars of
|
||||
Just givenAccKey -> userInfoWhenAccessKey accKey givenAccKey
|
||||
Nothing -> userInfoWhenNoAccessKey unAuthRole
|
||||
|
||||
@ -226,20 +225,18 @@ getUserInfo logger manager rawHeaders = \case
|
||||
-- when access key is absent, run the action to retrieve UserInfo, otherwise
|
||||
-- accesskey override
|
||||
whenAccessKeyAbsent ak action =
|
||||
maybe action (userInfoWhenAccessKey ak) $ getHeader accessKeyHeader
|
||||
maybe action (userInfoWhenAccessKey ak) $ getVarVal accessKeyHeader usrVars
|
||||
|
||||
headers =
|
||||
M.fromList $ filter (T.isPrefixOf "x-hasura-" . fst) $
|
||||
flip map rawHeaders $
|
||||
\(hdrName, hdrVal) ->
|
||||
(T.toLower $ bsToTxt $ original hdrName, bsToTxt hdrVal)
|
||||
|
||||
getHeader h = M.lookup h headers
|
||||
usrVars =
|
||||
mkUserVars
|
||||
[ (T.toLower $ bsToTxt $ original hdrName, bsToTxt hdrVal)
|
||||
| (hdrName, hdrVal) <- rawHeaders
|
||||
]
|
||||
|
||||
userInfoFromHeaders =
|
||||
case M.lookup userRoleHeader headers of
|
||||
Just v -> UserInfo (RoleName v) headers
|
||||
Nothing -> adminUserInfo
|
||||
case roleFromVars usrVars of
|
||||
Just rn -> mkUserInfo rn usrVars
|
||||
Nothing -> mkUserInfo adminRole usrVars
|
||||
|
||||
userInfoWhenAccessKey key reqKey = do
|
||||
when (reqKey /= getAccessKey key) $ throw401 $ "invalid " <> accessKeyHeader
|
||||
@ -247,5 +244,4 @@ getUserInfo logger manager rawHeaders = \case
|
||||
|
||||
userInfoWhenNoAccessKey = \case
|
||||
Nothing -> throw401 $ accessKeyHeader <> " required, but not found"
|
||||
Just role -> return $ UserInfo role $
|
||||
M.insertWith const userRoleHeader (getRoleTxt role) headers
|
||||
Just role -> return $ mkUserInfo role usrVars
|
||||
|
@ -177,8 +177,7 @@ processJwt jwtCtx headers mUnAuthRole =
|
||||
|
||||
withoutAuthZHeader = do
|
||||
unAuthRole <- maybe missingAuthzHeader return mUnAuthRole
|
||||
return $ UserInfo unAuthRole
|
||||
$ Map.singleton userRoleHeader $ getRoleTxt unAuthRole
|
||||
return $ mkUserInfo unAuthRole $ mkUserVars []
|
||||
missingAuthzHeader =
|
||||
throw400 InvalidHeaders "Missing Authorization header in JWT authentication mode"
|
||||
|
||||
@ -220,10 +219,9 @@ processAuthZHeader jwtCtx headers authzHeader = do
|
||||
metadata <- decodeJSON $ A.Object finalClaims
|
||||
|
||||
-- delete the x-hasura-access-key from this map, and insert x-hasura-role
|
||||
let hasuraMd = Map.insert userRoleHeader (getRoleTxt role) $
|
||||
Map.delete accessKeyHeader metadata
|
||||
let hasuraMd = Map.delete accessKeyHeader metadata
|
||||
|
||||
return $ UserInfo role hasuraMd
|
||||
return $ mkUserInfo role $ mkUserVars $ Map.toList hasuraMd
|
||||
|
||||
where
|
||||
parseAuthzHeader = do
|
||||
|
@ -105,7 +105,7 @@ runQuery
|
||||
runQuery pool isoL userInfo sc query = do
|
||||
tx <- liftEither $ buildTxAny userInfo sc query
|
||||
res <- liftIO $ runExceptT $ Q.runTx pool (isoL, Nothing) $
|
||||
setHeadersTx userInfo >> tx
|
||||
setHeadersTx (userVars userInfo) >> tx
|
||||
liftEither res
|
||||
|
||||
queryNeedsReload :: RQLQuery -> Bool
|
||||
@ -218,11 +218,11 @@ buildTxAny userInfo sc rq = case rq of
|
||||
, finalSc
|
||||
)
|
||||
|
||||
setHeadersTx :: UserInfo -> Q.TxE QErr ()
|
||||
setHeadersTx userInfo =
|
||||
setHeadersTx :: UserVars -> Q.TxE QErr ()
|
||||
setHeadersTx uVars =
|
||||
Q.unitQE defaultTxErrorHandler setSess () False
|
||||
where
|
||||
toStrictText = LT.toStrict . AT.encodeToLazyText
|
||||
setSess = Q.fromText $
|
||||
"SET LOCAL \"hasura.user\" = " <>
|
||||
pgFmtLit (toStrictText $ userVars userInfo)
|
||||
pgFmtLit (toStrictText uVars)
|
||||
|
@ -22,9 +22,6 @@ import qualified Text.Ginger as TG
|
||||
|
||||
import Hasura.Prelude
|
||||
|
||||
isXHasuraTxt :: T.Text -> Bool
|
||||
isXHasuraTxt = T.isInfixOf "x-hasura-" . T.toLower
|
||||
|
||||
jsonHeader :: (T.Text, T.Text)
|
||||
jsonHeader = ("Content-Type", "application/json; charset=utf-8")
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user