mirror of
https://github.com/zhigang1992/graphql-engine.git
synced 2026-05-24 08:54:11 +08:00
committed by
Shahidh K Muhammed
parent
605f8633f3
commit
92c4cff79e
@@ -29,10 +29,9 @@ import Hasura.GraphQL.Resolve.Mutation
|
||||
import Hasura.GraphQL.Resolve.Select
|
||||
import Hasura.GraphQL.Validate.Field
|
||||
import Hasura.GraphQL.Validate.Types
|
||||
import Hasura.RQL.DML.Internal ( dmlTxErrorHandler
|
||||
, convPartialSQLExp
|
||||
, sessVarFromCurrentSetting
|
||||
)
|
||||
import Hasura.RQL.DML.Internal (convPartialSQLExp,
|
||||
dmlTxErrorHandler,
|
||||
sessVarFromCurrentSetting)
|
||||
import Hasura.RQL.DML.Mutation
|
||||
import Hasura.RQL.GBoolExp (toSQLBoolExp)
|
||||
import Hasura.RQL.Types
|
||||
@@ -115,8 +114,9 @@ traverseInsObj rim (gName, annVal) defVal@(AnnInsObj cols objRels arrRels) =
|
||||
-- if relational insert input is 'null' then ignore
|
||||
-- return default value
|
||||
fmap (fromMaybe defVal) $ forM objM $ \obj -> do
|
||||
let relName = RelName $ G.unName gName
|
||||
let relNameM = RelName <$> mkNonEmptyText (G.unName gName)
|
||||
onConflictM = OMap.lookup "on_conflict" obj
|
||||
relName <- onNothing relNameM $ throw500 "found empty GName String"
|
||||
dataVal <- onNothing (OMap.lookup "data" obj) $
|
||||
throw500 "\"data\" object not found"
|
||||
relInfo <- onNothing (Map.lookup relName rim) $
|
||||
@@ -280,7 +280,7 @@ validateInsert insCols objRels addCols = do
|
||||
forM_ objRels $ \relInfo -> do
|
||||
let lCols = map fst $ riMapping relInfo
|
||||
relName = riName relInfo
|
||||
relNameTxt = getRelTxt relName
|
||||
relNameTxt = relNameToTxt relName
|
||||
lColConflicts = lCols `intersect` (addCols <> insCols)
|
||||
withPathK relNameTxt $ unless (null lColConflicts) $ throwVE $
|
||||
"cannot insert object relation ship " <> relName
|
||||
@@ -311,7 +311,7 @@ insertObjRel strfyNum role objRelIns =
|
||||
RelIns singleObjIns relInfo = objRelIns
|
||||
multiObjIns = singleToMulti singleObjIns
|
||||
relName = riName relInfo
|
||||
relNameTxt = getRelTxt relName
|
||||
relNameTxt = relNameToTxt relName
|
||||
mapCols = riMapping relInfo
|
||||
tn = riRTable relInfo
|
||||
allCols = _aiTableCols singleObjIns
|
||||
@@ -352,7 +352,7 @@ insertArrRel strfyNum role resCols arrRelIns =
|
||||
RelIns multiObjIns relInfo = arrRelIns
|
||||
colMapping = riMapping relInfo
|
||||
tn = riRTable relInfo
|
||||
relNameTxt = getRelTxt $ riName relInfo
|
||||
relNameTxt = relNameToTxt $ riName relInfo
|
||||
mutFlds = [("affected_rows", RR.MCount)]
|
||||
|
||||
-- | insert an object with object and array relationships
|
||||
|
||||
@@ -63,8 +63,7 @@ isValidCol :: PGCol -> Bool
|
||||
isValidCol = isValidName . G.Name . getPGColTxt
|
||||
|
||||
isValidRel :: ToTxt a => RelName -> QualifiedObject a -> Bool
|
||||
isValidRel rn rt = isValidName (G.Name $ getRelTxt rn)
|
||||
&& isValidObjectName rt
|
||||
isValidRel rn rt = isValidName (mkRelName rn) && isValidObjectName rt
|
||||
|
||||
isValidField :: FieldInfo -> Bool
|
||||
isValidField = \case
|
||||
@@ -114,10 +113,10 @@ mkColName :: PGCol -> G.Name
|
||||
mkColName (PGCol n) = G.Name n
|
||||
|
||||
mkRelName :: RelName -> G.Name
|
||||
mkRelName (RelName r) = G.Name r
|
||||
mkRelName rn = G.Name $ relNameToTxt rn
|
||||
|
||||
mkAggRelName :: RelName -> G.Name
|
||||
mkAggRelName (RelName r) = G.Name $ r <> "_aggregate"
|
||||
mkAggRelName rn = G.Name $ relNameToTxt rn <> "_aggregate"
|
||||
|
||||
mkBoolExpName :: QualifiedTable -> G.Name
|
||||
mkBoolExpName tn =
|
||||
@@ -225,13 +224,13 @@ mkRelFld allowAgg (RelInfo rn rTy _ remTab isManual) isNullable = case rTy of
|
||||
ObjRel -> [objRelFld]
|
||||
where
|
||||
objRelFld = mkHsraObjFldInfo (Just "An object relationship")
|
||||
(G.Name $ getRelTxt rn) Map.empty objRelTy
|
||||
(mkRelName rn) Map.empty objRelTy
|
||||
objRelTy = bool (G.toGT $ G.toNT relTabTy) (G.toGT relTabTy) isObjRelNullable
|
||||
isObjRelNullable = isManual || isNullable
|
||||
relTabTy = mkTableTy remTab
|
||||
|
||||
arrRelFld =
|
||||
mkHsraObjFldInfo (Just "An array relationship") (G.Name $ getRelTxt rn)
|
||||
mkHsraObjFldInfo (Just "An array relationship") (mkRelName rn)
|
||||
(fromInpValL $ mkSelArgs remTab) arrRelTy
|
||||
arrRelTy = G.toGT $ G.toNT $ G.toLT $ G.toNT $ mkTableTy remTab
|
||||
aggArrRelFld = mkHsraObjFldInfo (Just "An aggregated array relationship")
|
||||
@@ -533,7 +532,7 @@ mkBoolExpInp tn fields =
|
||||
Left (PGColInfo colName colTy _) ->
|
||||
mk (mkColName colName) (mkCompExpTy colTy)
|
||||
Right (RelInfo relName _ _ remTab _, _, _, _, _) ->
|
||||
mk (G.Name $ getRelTxt relName) (mkBoolExpTy remTab)
|
||||
mk (mkRelName relName) (mkBoolExpTy remTab)
|
||||
|
||||
mkPGColInp :: PGColInfo -> InpValInfo
|
||||
mkPGColInp (PGColInfo colName colTy _) =
|
||||
@@ -937,13 +936,13 @@ mkInsInp tn insCols relInfoMap =
|
||||
|
||||
relInps = flip map (Map.toList relInfoMap) $
|
||||
\(relName, relInfo) ->
|
||||
let rty = riType relInfo
|
||||
remoteQT = riRTable relInfo
|
||||
in case rty of
|
||||
ObjRel -> InpValInfo Nothing (G.Name $ getRelTxt relName) Nothing $
|
||||
G.toGT $ mkObjInsInpTy remoteQT
|
||||
ArrRel -> InpValInfo Nothing (G.Name $ getRelTxt relName) Nothing $
|
||||
G.toGT $ mkArrInsInpTy remoteQT
|
||||
let remoteQT = riRTable relInfo
|
||||
tyMaker = case riType relInfo of
|
||||
ObjRel -> mkObjInsInpTy
|
||||
ArrRel -> mkArrInsInpTy
|
||||
in InpValInfo Nothing (mkRelName relName) Nothing $
|
||||
G.toGT $ tyMaker remoteQT
|
||||
|
||||
|
||||
{-
|
||||
|
||||
@@ -1319,7 +1318,7 @@ mkGCtxRole' tn insPermM selPermM updColsM
|
||||
mkFld ty = \case
|
||||
Left ci -> [((ty, mkColName $ pgiName ci), Left ci)]
|
||||
Right (ri, allowAgg, perm, lim, _) ->
|
||||
let relFld = ( (ty, G.Name $ getRelTxt $ riName ri)
|
||||
let relFld = ( (ty, mkRelName $ riName ri)
|
||||
, Right (ri, False, perm, lim)
|
||||
)
|
||||
aggRelFld = ( (ty, mkAggRelName $ riName ri)
|
||||
|
||||
@@ -42,7 +42,7 @@ triggerTmplt = case parseGingerTmplt $(FE.embedStringFile "src-rsr/trigger.sql.j
|
||||
Right tmplt -> Just tmplt
|
||||
|
||||
pgIdenTrigger:: Ops -> TriggerName -> T.Text
|
||||
pgIdenTrigger op trn = pgFmtIden (qualifyTriggerName op trn)
|
||||
pgIdenTrigger op trn = pgFmtIden . qualifyTriggerName op $ triggerNameToTxt trn
|
||||
where
|
||||
qualifyTriggerName op' trn' = "notify_hasura_" <> trn' <> "_" <> T.pack (show op')
|
||||
|
||||
@@ -61,7 +61,7 @@ getTriggerSql
|
||||
-> Maybe T.Text
|
||||
getTriggerSql op trn qt allCols strfyNum spec =
|
||||
let globalCtx = HashMap.fromList
|
||||
[ (T.pack "NAME", trn)
|
||||
[ (T.pack "NAME", triggerNameToTxt trn)
|
||||
, (T.pack "QUALIFIED_TRIGGER_NAME", pgIdenTrigger op trn)
|
||||
, (T.pack "QUALIFIED_TABLE", toSQLTxt qt)
|
||||
]
|
||||
|
||||
@@ -82,9 +82,9 @@ type InsPermDef = PermDef InsPerm
|
||||
type CreateInsPerm = CreatePerm InsPerm
|
||||
|
||||
buildViewName :: QualifiedTable -> RoleName -> PermType -> QualifiedTable
|
||||
buildViewName (QualifiedObject sn tn) (RoleName rTxt) pt =
|
||||
buildViewName (QualifiedObject sn tn) rn pt =
|
||||
QualifiedObject hdbViewsSchema $ TableName
|
||||
(rTxt <> "__" <> T.pack (show pt) <> "__" <> snTxt <> "__" <> tnTxt)
|
||||
(roleNameToTxt rn <> "__" <> T.pack (show pt) <> "__" <> snTxt <> "__" <> tnTxt)
|
||||
where
|
||||
snTxt = getSchemaTxt sn
|
||||
tnTxt = getTableTxt tn
|
||||
|
||||
@@ -34,7 +34,7 @@ addCollectionP2 (CollectionDef queryList) =
|
||||
withPathK "queries" $
|
||||
unless (null duplicateNames) $ throw400 NotSupported $
|
||||
"found duplicate query names "
|
||||
<> T.intercalate ", " (map (T.dquote . unQueryName) duplicateNames)
|
||||
<> T.intercalate ", " (map (T.dquote . unNonEmptyText . unQueryName) duplicateNames)
|
||||
where
|
||||
duplicateNames = duplicates $ map _lqName queryList
|
||||
|
||||
|
||||
@@ -133,14 +133,13 @@ checkPermOnCol pt allowedCols pgCol = do
|
||||
unless (HS.member pgCol allowedCols) $
|
||||
throw400 PermissionDenied $ permErrMsg roleName
|
||||
where
|
||||
permErrMsg (RoleName "admin") =
|
||||
"no such column exists : " <>> pgCol
|
||||
permErrMsg roleName =
|
||||
mconcat
|
||||
[ "role " <>> roleName
|
||||
, " does not have permission to "
|
||||
, permTypeToCode pt <> " column " <>> pgCol
|
||||
]
|
||||
permErrMsg roleName
|
||||
| roleName == adminRole = "no such column exists : " <>> pgCol
|
||||
| otherwise = mconcat
|
||||
[ "role " <>> roleName
|
||||
, " does not have permission to "
|
||||
, permTypeToCode pt <> " column " <>> pgCol
|
||||
]
|
||||
|
||||
binRHSBuilder
|
||||
:: PGColType -> Value -> DMLP1 S.SQLExp
|
||||
|
||||
@@ -641,7 +641,7 @@ mkAggSelect :: AnnAggSel -> S.Select
|
||||
mkAggSelect annAggSel =
|
||||
prefixNumToAliases $ arrNodeToSelect bn extr $ S.BELit True
|
||||
where
|
||||
aggSel = AnnRelG (RelName "root") [] annAggSel
|
||||
aggSel = AnnRelG rootRelName [] annAggSel
|
||||
ArrNode extr _ bn =
|
||||
aggSelToArrNode (Iden "root") (FieldName "root") aggSel
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ askTabInfoFromTrigger trn = do
|
||||
let tabInfos = M.elems $ scTables sc
|
||||
liftMaybe (err400 NotExists errMsg) $ find (isJust.M.lookup trn.tiEventTriggerInfoMap) tabInfos
|
||||
where
|
||||
errMsg = "event trigger " <> trn <<> " does not exist"
|
||||
errMsg = "event trigger " <> triggerNameToTxt trn <<> " does not exist"
|
||||
|
||||
askEventTriggerInfo
|
||||
:: (QErrM m, CacheRM m)
|
||||
@@ -114,7 +114,7 @@ askEventTriggerInfo trn = do
|
||||
let etim = tiEventTriggerInfoMap ti
|
||||
liftMaybe (err400 NotExists errMsg) $ M.lookup trn etim
|
||||
where
|
||||
errMsg = "event trigger " <> trn <<> " does not exist"
|
||||
errMsg = "event trigger " <> triggerNameToTxt trn <<> " does not exist"
|
||||
|
||||
askQTemplateInfo
|
||||
:: (P1C m)
|
||||
|
||||
@@ -288,7 +288,7 @@ instance ToJSON AnnBoolExpPartialSQL where
|
||||
, toJSON (pci, map opExpSToJSON opExps)
|
||||
)
|
||||
AVRel ri relBoolExp ->
|
||||
( getRelTxt $ riName ri
|
||||
( relNameToTxt $ riName ri
|
||||
, toJSON (ri, toJSON relBoolExp)
|
||||
)
|
||||
opExpSToJSON :: OpExpG PartialSQLExp -> Value
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
module Hasura.RQL.Types.Common
|
||||
( PGColInfo(..)
|
||||
, RelName(..)
|
||||
, relNameToTxt
|
||||
, RelType(..)
|
||||
, rootRelName
|
||||
, relTypeToTxt
|
||||
, RelInfo(..)
|
||||
|
||||
@@ -17,6 +19,12 @@ module Hasura.RQL.Types.Common
|
||||
, ColVals
|
||||
, MutateResp(..)
|
||||
, ForeignKey(..)
|
||||
|
||||
, NonEmptyText
|
||||
, mkNonEmptyText
|
||||
, unNonEmptyText
|
||||
, adminText
|
||||
, rootText
|
||||
) where
|
||||
|
||||
import Hasura.Prelude
|
||||
@@ -25,6 +33,7 @@ import Hasura.SQL.Types
|
||||
import Data.Aeson
|
||||
import Data.Aeson.Casing
|
||||
import Data.Aeson.TH
|
||||
import Data.Aeson.Types
|
||||
import qualified Data.HashMap.Strict as HM
|
||||
import qualified Data.Text as T
|
||||
import qualified Database.PG.Query as Q
|
||||
@@ -41,15 +50,49 @@ data PGColInfo
|
||||
|
||||
$(deriveJSON (aesonDrop 3 snakeCase) ''PGColInfo)
|
||||
|
||||
newtype NonEmptyText = NonEmptyText {unNonEmptyText :: T.Text}
|
||||
deriving (Show, Eq, Ord, Hashable, ToJSON, ToJSONKey, Lift, Q.ToPrepArg, DQuote)
|
||||
|
||||
mkNonEmptyText :: T.Text -> Maybe NonEmptyText
|
||||
mkNonEmptyText "" = Nothing
|
||||
mkNonEmptyText text = Just $ NonEmptyText text
|
||||
|
||||
parseNonEmptyText :: T.Text -> Parser NonEmptyText
|
||||
parseNonEmptyText text = case mkNonEmptyText text of
|
||||
Nothing -> fail "empty string not allowed"
|
||||
Just neText -> return neText
|
||||
|
||||
instance FromJSON NonEmptyText where
|
||||
parseJSON = withText "String" parseNonEmptyText
|
||||
|
||||
instance FromJSONKey NonEmptyText where
|
||||
fromJSONKey = FromJSONKeyTextParser parseNonEmptyText
|
||||
|
||||
instance Q.FromCol NonEmptyText where
|
||||
fromCol bs = mkNonEmptyText <$> Q.fromCol bs
|
||||
>>= maybe (Left "empty string not allowed") Right
|
||||
|
||||
adminText :: NonEmptyText
|
||||
adminText = NonEmptyText "admin"
|
||||
|
||||
rootText :: NonEmptyText
|
||||
rootText = NonEmptyText "root"
|
||||
|
||||
newtype RelName
|
||||
= RelName {getRelTxt :: T.Text}
|
||||
= RelName {getRelTxt :: NonEmptyText}
|
||||
deriving (Show, Eq, Hashable, FromJSON, ToJSON, Q.ToPrepArg, Q.FromCol, Lift)
|
||||
|
||||
instance IsIden RelName where
|
||||
toIden (RelName r) = Iden r
|
||||
toIden rn = Iden $ relNameToTxt rn
|
||||
|
||||
instance DQuote RelName where
|
||||
dquoteTxt (RelName r) = r
|
||||
dquoteTxt = relNameToTxt
|
||||
|
||||
rootRelName :: RelName
|
||||
rootRelName = RelName rootText
|
||||
|
||||
relNameToTxt :: RelName -> T.Text
|
||||
relNameToTxt = unNonEmptyText . getRelTxt
|
||||
|
||||
relTypeToTxt :: RelType -> T.Text
|
||||
relTypeToTxt ObjRel = "object"
|
||||
@@ -101,18 +144,18 @@ fromPGCol :: PGCol -> FieldName
|
||||
fromPGCol (PGCol c) = FieldName c
|
||||
|
||||
fromRel :: RelName -> FieldName
|
||||
fromRel (RelName r) = FieldName r
|
||||
fromRel = FieldName . relNameToTxt
|
||||
|
||||
newtype TQueryName
|
||||
= TQueryName { getTQueryName :: T.Text }
|
||||
= TQueryName { getTQueryName :: NonEmptyText }
|
||||
deriving ( Show, Eq, Hashable, FromJSONKey, ToJSONKey
|
||||
, FromJSON, ToJSON, Q.ToPrepArg, Q.FromCol, Lift)
|
||||
|
||||
instance IsIden TQueryName where
|
||||
toIden (TQueryName r) = Iden r
|
||||
toIden (TQueryName r) = Iden $ unNonEmptyText r
|
||||
|
||||
instance DQuote TQueryName where
|
||||
dquoteTxt (TQueryName r) = r
|
||||
dquoteTxt (TQueryName r) = unNonEmptyText r
|
||||
|
||||
newtype TemplateParam
|
||||
= TemplateParam { getTemplateParam :: T.Text }
|
||||
|
||||
@@ -2,7 +2,8 @@ module Hasura.RQL.Types.EventTrigger
|
||||
( CreateEventTriggerQuery(..)
|
||||
, SubscribeOpSpec(..)
|
||||
, SubscribeColumns(..)
|
||||
, TriggerName
|
||||
, TriggerName(..)
|
||||
, triggerNameToTxt
|
||||
, Ops(..)
|
||||
, EventId
|
||||
, TriggerOpsDef(..)
|
||||
@@ -27,15 +28,22 @@ import Data.Aeson.Casing
|
||||
import Data.Aeson.TH
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.DDL.Headers
|
||||
import Hasura.RQL.Types.Common (NonEmptyText (..))
|
||||
import Hasura.SQL.Types
|
||||
import Language.Haskell.TH.Syntax (Lift)
|
||||
|
||||
import qualified Data.ByteString.Lazy as LBS
|
||||
import qualified Data.Text as T
|
||||
import qualified Database.PG.Query as Q
|
||||
import qualified Text.Regex.TDFA as TDFA
|
||||
|
||||
type TriggerName = T.Text
|
||||
type EventId = T.Text
|
||||
newtype TriggerName = TriggerName { unTriggerName :: NonEmptyText }
|
||||
deriving (Show, Eq, Hashable, Lift, FromJSON, ToJSON, ToJSONKey, Q.FromCol, Q.ToPrepArg)
|
||||
|
||||
triggerNameToTxt :: TriggerName -> Text
|
||||
triggerNameToTxt = unNonEmptyText . unTriggerName
|
||||
|
||||
type EventId = T.Text
|
||||
|
||||
data Ops = INSERT | UPDATE | DELETE | MANUAL deriving (Show)
|
||||
|
||||
@@ -106,7 +114,7 @@ $(deriveToJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''WebhookConfInfo
|
||||
|
||||
data CreateEventTriggerQuery
|
||||
= CreateEventTriggerQuery
|
||||
{ cetqName :: !T.Text
|
||||
{ cetqName :: !TriggerName
|
||||
, cetqTable :: !QualifiedTable
|
||||
, cetqInsert :: !(Maybe SubscribeOpSpec)
|
||||
, cetqUpdate :: !(Maybe SubscribeOpSpec)
|
||||
@@ -134,7 +142,7 @@ instance FromJSON CreateEventTriggerQuery where
|
||||
replace <- o .:? "replace" .!= False
|
||||
let regex = "^[A-Za-z]+[A-Za-z0-9_\\-]*$" :: LBS.ByteString
|
||||
compiledRegex = TDFA.makeRegex regex :: TDFA.Regex
|
||||
isMatch = TDFA.match compiledRegex (T.unpack name)
|
||||
isMatch = TDFA.match compiledRegex . T.unpack $ triggerNameToTxt name
|
||||
if isMatch then return ()
|
||||
else fail "only alphanumeric and underscore and hyphens allowed for name"
|
||||
if any isJust [insert, update, delete] || enableManual then
|
||||
@@ -170,7 +178,7 @@ $(deriveJSON (aesonDrop 2 snakeCase){omitNothingFields=True} ''TriggerOpsDef)
|
||||
|
||||
data DeleteEventTriggerQuery
|
||||
= DeleteEventTriggerQuery
|
||||
{ detqName :: !T.Text
|
||||
{ detqName :: !TriggerName
|
||||
} deriving (Show, Eq, Lift)
|
||||
|
||||
$(deriveJSON (aesonDrop 4 snakeCase){omitNothingFields=True} ''DeleteEventTriggerQuery)
|
||||
@@ -187,16 +195,16 @@ data EventTriggerConf
|
||||
|
||||
$(deriveJSON (aesonDrop 3 snakeCase){omitNothingFields=True} ''EventTriggerConf)
|
||||
|
||||
data RedeliverEventQuery
|
||||
newtype RedeliverEventQuery
|
||||
= RedeliverEventQuery
|
||||
{ rdeqEventId :: !EventId
|
||||
{ rdeqEventId :: EventId
|
||||
} deriving (Show, Eq, Lift)
|
||||
|
||||
$(deriveJSON (aesonDrop 4 snakeCase){omitNothingFields=True} ''RedeliverEventQuery)
|
||||
|
||||
data InvokeEventTriggerQuery
|
||||
= InvokeEventTriggerQuery
|
||||
{ ietqName :: !T.Text
|
||||
{ ietqName :: !TriggerName
|
||||
, ietqPayload :: !Value
|
||||
} deriving (Show, Eq, Lift)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module Hasura.RQL.Types.Permission
|
||||
( RoleName(..)
|
||||
, roleNameToTxt
|
||||
|
||||
, SessVar
|
||||
, SessVarVal
|
||||
@@ -23,6 +24,8 @@ module Hasura.RQL.Types.Permission
|
||||
) where
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Common (NonEmptyText, adminText, mkNonEmptyText,
|
||||
unNonEmptyText)
|
||||
import Hasura.Server.Utils (adminSecretHeader,
|
||||
deprecatedAccessKeyHeader,
|
||||
userRoleHeader)
|
||||
@@ -40,15 +43,18 @@ import qualified Data.Text as T
|
||||
import qualified PostgreSQL.Binary.Decoding as PD
|
||||
|
||||
newtype RoleName
|
||||
= RoleName {getRoleTxt :: T.Text}
|
||||
= RoleName {getRoleTxt :: NonEmptyText}
|
||||
deriving ( Show, Eq, Hashable, FromJSONKey, ToJSONKey, FromJSON
|
||||
, ToJSON, Q.FromCol, Q.ToPrepArg, Lift)
|
||||
|
||||
instance DQuote RoleName where
|
||||
dquoteTxt (RoleName r) = r
|
||||
dquoteTxt = roleNameToTxt
|
||||
|
||||
roleNameToTxt :: RoleName -> Text
|
||||
roleNameToTxt = unNonEmptyText . getRoleTxt
|
||||
|
||||
adminRole :: RoleName
|
||||
adminRole = RoleName "admin"
|
||||
adminRole = RoleName adminText
|
||||
|
||||
isAdmin :: RoleName -> Bool
|
||||
isAdmin = (adminRole ==)
|
||||
@@ -63,9 +69,10 @@ newtype UserVars
|
||||
isUserVar :: T.Text -> Bool
|
||||
isUserVar = T.isPrefixOf "x-hasura-" . T.toLower
|
||||
|
||||
-- returns Nothing if x-hasura-role is an empty string
|
||||
roleFromVars :: UserVars -> Maybe RoleName
|
||||
roleFromVars =
|
||||
fmap RoleName . getVarVal userRoleHeader
|
||||
roleFromVars uv =
|
||||
getVarVal userRoleHeader uv >>= fmap RoleName . mkNonEmptyText
|
||||
|
||||
getVarVal :: SessVar -> UserVars -> Maybe SessVarVal
|
||||
getVarVal k =
|
||||
@@ -90,7 +97,7 @@ data UserInfo
|
||||
|
||||
mkUserInfo :: RoleName -> UserVars -> UserInfo
|
||||
mkUserInfo rn (UserVars v) =
|
||||
UserInfo rn $ UserVars $ Map.insert userRoleHeader (getRoleTxt rn) $
|
||||
UserInfo rn $ UserVars $ Map.insert userRoleHeader (roleNameToTxt rn) $
|
||||
foldl (flip Map.delete) v [adminSecretHeader, deprecatedAccessKeyHeader]
|
||||
|
||||
instance Hashable UserInfo
|
||||
@@ -102,7 +109,7 @@ instance Hashable UserInfo
|
||||
userInfoToList :: UserInfo -> [(Text, Text)]
|
||||
userInfoToList userInfo =
|
||||
let vars = Map.toList $ unUserVars . userVars $ userInfo
|
||||
rn = getRoleTxt . userRole $ userInfo
|
||||
rn = roleNameToTxt . userRole $ userInfo
|
||||
in (userRoleHeader, rn) : vars
|
||||
|
||||
adminUserInfo :: UserInfo
|
||||
@@ -162,7 +169,7 @@ instance Show PermId where
|
||||
show $ mconcat
|
||||
[ getTableTxt tn
|
||||
, "."
|
||||
, getRoleTxt rn
|
||||
, roleNameToTxt rn
|
||||
, "."
|
||||
, T.pack $ show pType
|
||||
]
|
||||
|
||||
@@ -2,6 +2,7 @@ module Hasura.RQL.Types.QueryCollection where
|
||||
|
||||
import Hasura.GraphQL.Validate.Types (stripTypenames)
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Common (NonEmptyText)
|
||||
import Hasura.SQL.Types
|
||||
|
||||
import Data.Aeson
|
||||
@@ -15,13 +16,13 @@ import qualified Database.PG.Query as Q
|
||||
import qualified Language.GraphQL.Draft.Syntax as G
|
||||
|
||||
newtype CollectionName
|
||||
= CollectionName {unCollectionName :: T.Text}
|
||||
= CollectionName {unCollectionName :: NonEmptyText}
|
||||
deriving ( Show, Eq, Ord, Hashable, ToJSON, ToJSONKey, Lift
|
||||
, FromJSON, Q.FromCol, Q.ToPrepArg, DQuote
|
||||
)
|
||||
|
||||
newtype QueryName
|
||||
= QueryName {unQueryName :: T.Text}
|
||||
= QueryName {unQueryName :: NonEmptyText}
|
||||
deriving (Show, Eq, Ord, Hashable, Lift, ToJSON, ToJSONKey, FromJSON, DQuote)
|
||||
|
||||
newtype GQLQuery
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
module Hasura.RQL.Types.RemoteSchema where
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Common (NonEmptyText)
|
||||
import Language.Haskell.TH.Syntax (Lift)
|
||||
import System.Environment (lookupEnv)
|
||||
|
||||
@@ -19,7 +20,7 @@ type UrlFromEnv = Text
|
||||
|
||||
newtype RemoteSchemaName
|
||||
= RemoteSchemaName
|
||||
{ unRemoteSchemaName :: Text}
|
||||
{ unRemoteSchemaName :: NonEmptyText }
|
||||
deriving ( Show, Eq, Lift, Hashable, J.ToJSON, J.ToJSONKey
|
||||
, J.FromJSON, Q.ToPrepArg, Q.FromCol, DQuote
|
||||
)
|
||||
|
||||
@@ -36,18 +36,18 @@ reportSchemaObj :: SchemaObjId -> T.Text
|
||||
reportSchemaObj (SOTable tn) = "table " <> qualObjectToText tn
|
||||
reportSchemaObj (SOFunction fn) = "function " <> qualObjectToText fn
|
||||
reportSchemaObj (SOQTemplate qtn) =
|
||||
"query-template " <> getTQueryName qtn
|
||||
"query-template " <> unNonEmptyText (getTQueryName qtn)
|
||||
reportSchemaObj (SOTableObj tn (TOCol cn)) =
|
||||
"column " <> qualObjectToText tn <> "." <> getPGColTxt cn
|
||||
reportSchemaObj (SOTableObj tn (TORel cn)) =
|
||||
"relationship " <> qualObjectToText tn <> "." <> getRelTxt cn
|
||||
"relationship " <> qualObjectToText tn <> "." <> relNameToTxt cn
|
||||
reportSchemaObj (SOTableObj tn (TOCons cn)) =
|
||||
"constraint " <> qualObjectToText tn <> "." <> getConstraintTxt cn
|
||||
reportSchemaObj (SOTableObj tn (TOPerm rn pt)) =
|
||||
"permission " <> qualObjectToText tn <> "." <> getRoleTxt rn
|
||||
"permission " <> qualObjectToText tn <> "." <> roleNameToTxt rn
|
||||
<> "." <> permTypeToCode pt
|
||||
reportSchemaObj (SOTableObj tn (TOTrigger trn )) =
|
||||
"event-trigger " <> qualObjectToText tn <> "." <> trn
|
||||
"event-trigger " <> qualObjectToText tn <> "." <> triggerNameToTxt trn
|
||||
|
||||
instance Show SchemaObjId where
|
||||
show soi = T.unpack $ reportSchemaObj soi
|
||||
|
||||
@@ -585,20 +585,25 @@ httpApp corsCfg serverCtx enableConsole consoleAssetsDir enableTelemetry = do
|
||||
mkSpockAction encodeQErr id serverCtx $ mkPostHandler $
|
||||
mkAPIRespHandler gqlExplainHandler
|
||||
|
||||
mkTmpltName tmpltText =
|
||||
onNothing (mkNonEmptyText tmpltText) $ throw400 NotSupported "template name is empty string"
|
||||
|
||||
enableGraphQL = isGraphQLEnabled serverCtx
|
||||
enableMetadata = isMetadataEnabled serverCtx
|
||||
enablePGDump = isPGDumpEnabled serverCtx
|
||||
enableConfig = isConfigEnabled serverCtx
|
||||
|
||||
tmpltGetOrDeleteH tmpltName = do
|
||||
tmpltGetOrDeleteH tmpltText = do
|
||||
tmpltArgs <- tmpltArgsFromQueryParams
|
||||
mkSpockAction encodeQErr id serverCtx $ mkGetHandler $
|
||||
mkSpockAction encodeQErr id serverCtx $ mkGetHandler $ do
|
||||
tmpltName <- mkTmpltName tmpltText
|
||||
JSONResp <$> mkQTemplateAction tmpltName tmpltArgs
|
||||
|
||||
tmpltPutOrPostH tmpltName = do
|
||||
tmpltPutOrPostH tmpltText = do
|
||||
tmpltArgs <- tmpltArgsFromQueryParams
|
||||
mkSpockAction encodeQErr id serverCtx $ mkPostHandler $
|
||||
mkAPIRespHandler $ \bodyTmpltArgs ->
|
||||
mkAPIRespHandler $ \bodyTmpltArgs -> do
|
||||
tmpltName <- mkTmpltName tmpltText
|
||||
mkQTemplateAction tmpltName $ M.union bodyTmpltArgs tmpltArgs
|
||||
|
||||
tmpltArgsFromQueryParams = do
|
||||
|
||||
@@ -264,7 +264,7 @@ processAuthZHeader jwtCtx headers authzHeader = do
|
||||
getCurrentRole defaultRole =
|
||||
let userRoleHeaderB = CS.cs userRoleHeader
|
||||
mUserRole = snd <$> find (\h -> fst h == CI.mk userRoleHeaderB) headers
|
||||
in maybe defaultRole (RoleName . bsToTxt) mUserRole
|
||||
in maybe defaultRole RoleName $ mUserRole >>= mkNonEmptyText . bsToTxt
|
||||
|
||||
decodeJSON val = case A.fromJSON val of
|
||||
A.Error e -> throw400 JWTInvalidClaims ("x-hasura-* claims: " <> T.pack e)
|
||||
|
||||
@@ -21,8 +21,9 @@ import qualified Hasura.Logging as L
|
||||
import qualified Text.PrettyPrint.ANSI.Leijen as PP
|
||||
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types (RoleName (..),
|
||||
SchemaCache (..))
|
||||
import Hasura.RQL.Types ( RoleName (..)
|
||||
, SchemaCache (..)
|
||||
, mkNonEmptyText )
|
||||
import Hasura.Server.Auth
|
||||
import Hasura.Server.Cors
|
||||
import Hasura.Server.Logging
|
||||
@@ -167,7 +168,9 @@ instance FromEnv AdminSecret where
|
||||
fromEnv = Right . AdminSecret . T.pack
|
||||
|
||||
instance FromEnv RoleName where
|
||||
fromEnv = Right . RoleName . T.pack
|
||||
fromEnv string = case mkNonEmptyText (T.pack string) of
|
||||
Nothing -> Left "empty string not allowed"
|
||||
Just neText -> Right $ RoleName neText
|
||||
|
||||
instance FromEnv Bool where
|
||||
fromEnv = parseStrAsBool
|
||||
@@ -817,11 +820,13 @@ jwtSecretHelp = "The JSON containing type and the JWK used for verifying. e.g: "
|
||||
<> "`{\"type\": \"RS256\", \"key\": \"<your-PEM-RSA-public-key>\", \"claims_namespace\": \"<optional-custom-claims-key-name>\"}`"
|
||||
|
||||
parseUnAuthRole :: Parser (Maybe RoleName)
|
||||
parseUnAuthRole = optional $
|
||||
RoleName <$> strOption ( long "unauthorized-role" <>
|
||||
metavar "<ROLE>" <>
|
||||
help (snd unAuthRoleEnv)
|
||||
)
|
||||
parseUnAuthRole = fmap mkRoleName $ optional $
|
||||
strOption ( long "unauthorized-role" <>
|
||||
metavar "<ROLE>" <>
|
||||
help (snd unAuthRoleEnv)
|
||||
)
|
||||
where
|
||||
mkRoleName mText = mText >>= (fmap RoleName . mkNonEmptyText)
|
||||
|
||||
parseCorsConfig :: Parser (Maybe CorsConfig)
|
||||
parseCorsConfig = mapCC <$> disableCors <*> corsDomain
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
description: Create event trigger with role ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.name
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: create_event_trigger
|
||||
args:
|
||||
name: ""
|
||||
table: users
|
||||
webhook: https://httpbin.org/post
|
||||
insert:
|
||||
columns: "*"
|
||||
payload:
|
||||
- username
|
||||
update:
|
||||
columns:
|
||||
- username
|
||||
- real_name
|
||||
payload: "*"
|
||||
delete:
|
||||
columns: "*"
|
||||
headers:
|
||||
- name: X-Hasura-From-Val
|
||||
value: myvalue
|
||||
- name: X-Hasura-From-Env
|
||||
value_from_env: EVENT_WEBHOOK_HEADER
|
||||
replace: false
|
||||
@@ -0,0 +1,21 @@
|
||||
description: Create permission with role ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.role
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: create_insert_permission
|
||||
args:
|
||||
table: article
|
||||
role: ""
|
||||
permission:
|
||||
check:
|
||||
author_id: X-HASURA-USER-ID
|
||||
"$or":
|
||||
- category: editorial
|
||||
is_reviewed: false
|
||||
- category:
|
||||
"$neq": editorial
|
||||
@@ -0,0 +1,15 @@
|
||||
description: Create object relationship with name ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.name
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: create_object_relationship
|
||||
args:
|
||||
table: article
|
||||
name: ""
|
||||
using:
|
||||
foreign_key_constraint_on: author_id
|
||||
@@ -0,0 +1,17 @@
|
||||
description: Create query collection with name ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.name
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: create_query_collection
|
||||
args:
|
||||
name: ""
|
||||
comment: an optional comment
|
||||
definition:
|
||||
queries:
|
||||
- name: query_1
|
||||
query: query { test {id name}}
|
||||
@@ -0,0 +1,17 @@
|
||||
description: Create query collection with name ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.name
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: create_query_collection
|
||||
args:
|
||||
name: ""
|
||||
comment: an optional comment
|
||||
definition:
|
||||
queries:
|
||||
- name: query_1
|
||||
query: query { test {id name}}
|
||||
@@ -0,0 +1,19 @@
|
||||
description: Create remote schema with name ""
|
||||
url: /v1/query
|
||||
status: 400
|
||||
response:
|
||||
path: $.name
|
||||
error: >-
|
||||
empty string not allowed
|
||||
code: parse-failed
|
||||
query:
|
||||
type: add_remote_schema
|
||||
args:
|
||||
name: ""
|
||||
definition:
|
||||
url: https://remote-server.com/graphql
|
||||
headers:
|
||||
- name: X-Server-Request-From
|
||||
value: Hasura
|
||||
forward_client_headers: false
|
||||
comment: some optional comment
|
||||
@@ -582,3 +582,28 @@ class TestCreatePermission(DefaultTestQueries):
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/v1/permissions"
|
||||
|
||||
|
||||
class TestNonEmptyText:
|
||||
|
||||
def test_create_event_trigger(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_event_trigger.yaml')
|
||||
|
||||
def test_create_insert_permission(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_insert_permission.yaml')
|
||||
|
||||
def test_create_query_collection(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_query_collection.yaml')
|
||||
|
||||
def test_create_query_collection_queryname(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_query_collection_queryname.yaml')
|
||||
|
||||
def test_create_object_relationship(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_object_relationship.yaml')
|
||||
|
||||
def test_create_remote_schema(self, hge_ctx):
|
||||
check_query_f(hge_ctx, self.dir() + '/create_remote_schema.yaml')
|
||||
|
||||
@classmethod
|
||||
def dir(cls):
|
||||
return "queries/v1/non_empty_text"
|
||||
|
||||
Reference in New Issue
Block a user