module Hasura.GraphQL.Execute.Common
  ( MonadGQLExecutionCheck (..),
  )
where

import Data.Aeson.Ordered qualified as JO
import Hasura.Base.Error
import Hasura.GraphQL.Execute.Backend
import Hasura.GraphQL.Transport.HTTP.Protocol
import Hasura.Prelude
import Hasura.RQL.Types.GraphqlSchemaIntrospection
import Hasura.RQL.Types.SchemaCache
import Hasura.Server.Init (AllowListStatus)
import Hasura.Server.Types (RequestId)
import Hasura.Session
import Hasura.Tracing qualified as Tracing
import Network.HTTP.Types qualified as HTTP
import Network.Wai.Extended qualified as Wai

-- | Typeclass representing safety checks (if any) that need to be performed
-- before a GraphQL query should be allowed to be executed. In OSS, the safety
-- check is to check in the query is in the allow list.
--
-- the `executeIntrospection` function has different implementations in OSS and
-- Pro. In Pro, the GraphQL schema introspection can be disabled for specified
-- roles and in OSS there is no restrictions.
--
-- | TODO (from master): Limitation: This parses the query, which is not ideal if we already
-- have the query cached. The parsing happens unnecessary. But getting this to
-- either return a plan or parse was tricky and complicated.
class (Monad m) => MonadGQLExecutionCheck m where
  checkGQLExecution ::
    UserInfo ->
    ([HTTP.Header], Wai.IpAddress) ->
    -- | allow list enabled?
    AllowListStatus ->
    -- | needs allow list
    SchemaCache ->
    -- | the unparsed GraphQL query string (and related values)
    GQLReqUnparsed ->
    RequestId ->
    m (Either QErr GQLReqParsed)

  executeIntrospection ::
    UserInfo ->
    JO.Value ->
    SetGraphqlIntrospectionOptions ->
    m (Either QErr ExecutionStep)

  checkGQLBatchedReqs ::
    UserInfo ->
    RequestId ->
    [GQLReq GQLQueryText] ->
    SchemaCache ->
    m (Either QErr ())

instance (MonadGQLExecutionCheck m) => MonadGQLExecutionCheck (ReaderT r m) where
  checkGQLExecution :: UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> ReaderT r m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId =
    m (Either QErr GQLReqParsed)
-> ReaderT r m (Either QErr GQLReqParsed)
forall (m :: * -> *) a. Monad m => m a -> ReaderT r m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr GQLReqParsed)
 -> ReaderT r m (Either QErr GQLReqParsed))
-> m (Either QErr GQLReqParsed)
-> ReaderT r m (Either QErr GQLReqParsed)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId

  executeIntrospection :: UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> ReaderT r m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled =
    m (Either QErr ExecutionStep)
-> ReaderT r m (Either QErr ExecutionStep)
forall (m :: * -> *) a. Monad m => m a -> ReaderT r m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ExecutionStep)
 -> ReaderT r m (Either QErr ExecutionStep))
-> m (Either QErr ExecutionStep)
-> ReaderT r m (Either QErr ExecutionStep)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled

  checkGQLBatchedReqs :: UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> ReaderT r m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc =
    m (Either QErr ()) -> ReaderT r m (Either QErr ())
forall (m :: * -> *) a. Monad m => m a -> ReaderT r m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ()) -> ReaderT r m (Either QErr ()))
-> m (Either QErr ()) -> ReaderT r m (Either QErr ())
forall a b. (a -> b) -> a -> b
$ UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc

instance (MonadGQLExecutionCheck m) => MonadGQLExecutionCheck (ExceptT e m) where
  checkGQLExecution :: UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> ExceptT e m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId =
    m (Either QErr GQLReqParsed)
-> ExceptT e m (Either QErr GQLReqParsed)
forall (m :: * -> *) a. Monad m => m a -> ExceptT e m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr GQLReqParsed)
 -> ExceptT e m (Either QErr GQLReqParsed))
-> m (Either QErr GQLReqParsed)
-> ExceptT e m (Either QErr GQLReqParsed)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId

  executeIntrospection :: UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> ExceptT e m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled =
    m (Either QErr ExecutionStep)
-> ExceptT e m (Either QErr ExecutionStep)
forall (m :: * -> *) a. Monad m => m a -> ExceptT e m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ExecutionStep)
 -> ExceptT e m (Either QErr ExecutionStep))
-> m (Either QErr ExecutionStep)
-> ExceptT e m (Either QErr ExecutionStep)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled

  checkGQLBatchedReqs :: UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> ExceptT e m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc =
    m (Either QErr ()) -> ExceptT e m (Either QErr ())
forall (m :: * -> *) a. Monad m => m a -> ExceptT e m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ()) -> ExceptT e m (Either QErr ()))
-> m (Either QErr ()) -> ExceptT e m (Either QErr ())
forall a b. (a -> b) -> a -> b
$ UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc

instance (MonadGQLExecutionCheck m) => MonadGQLExecutionCheck (Tracing.TraceT m) where
  checkGQLExecution :: UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> TraceT m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId =
    m (Either QErr GQLReqParsed) -> TraceT m (Either QErr GQLReqParsed)
forall (m :: * -> *) a. Monad m => m a -> TraceT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr GQLReqParsed)
 -> TraceT m (Either QErr GQLReqParsed))
-> m (Either QErr GQLReqParsed)
-> TraceT m (Either QErr GQLReqParsed)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> ([Header], IpAddress)
-> AllowListStatus
-> SchemaCache
-> GQLReqUnparsed
-> RequestId
-> m (Either QErr GQLReqParsed)
checkGQLExecution UserInfo
ui ([Header], IpAddress)
det AllowListStatus
enableAL SchemaCache
sc GQLReqUnparsed
req RequestId
requestId

  executeIntrospection :: UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> TraceT m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled =
    m (Either QErr ExecutionStep)
-> TraceT m (Either QErr ExecutionStep)
forall (m :: * -> *) a. Monad m => m a -> TraceT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ExecutionStep)
 -> TraceT m (Either QErr ExecutionStep))
-> m (Either QErr ExecutionStep)
-> TraceT m (Either QErr ExecutionStep)
forall a b. (a -> b) -> a -> b
$ UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> Value
-> SetGraphqlIntrospectionOptions
-> m (Either QErr ExecutionStep)
executeIntrospection UserInfo
userInfo Value
introspectionQuery SetGraphqlIntrospectionOptions
rolesDisabled

  checkGQLBatchedReqs :: UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> TraceT m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc =
    m (Either QErr ()) -> TraceT m (Either QErr ())
forall (m :: * -> *) a. Monad m => m a -> TraceT m a
forall (t :: (* -> *) -> * -> *) (m :: * -> *) a.
(MonadTrans t, Monad m) =>
m a -> t m a
lift (m (Either QErr ()) -> TraceT m (Either QErr ()))
-> m (Either QErr ()) -> TraceT m (Either QErr ())
forall a b. (a -> b) -> a -> b
$ UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
forall (m :: * -> *).
MonadGQLExecutionCheck m =>
UserInfo
-> RequestId
-> [GQLReqUnparsed]
-> SchemaCache
-> m (Either QErr ())
checkGQLBatchedReqs UserInfo
userInfo RequestId
requestId [GQLReqUnparsed]
reqs SchemaCache
sc