graphql-engine/server/src-lib/Hasura/Server/Auth.hs
Tirumarai Selvan cc8e2ccc78
Scheduled triggers (close #1914) (#3553)
server: add scheduled triggers 

Co-authored-by: Alexis King <lexi.lambda@gmail.com>
Co-authored-by: Marion Schleifer <marion@hasura.io>
Co-authored-by: Karthikeyan Chinnakonda <karthikeyan@hasura.io>
Co-authored-by: Aleksandra Sikora <ola.zxcvbnm@gmail.com>
2020-05-13 18:03:16 +05:30

201 lines
6.8 KiB
Haskell

{-# LANGUAGE RecordWildCards #-}
module Hasura.Server.Auth
( getUserInfo
, getUserInfoWithExpTime
, AuthMode (..)
, mkAuthMode
, AdminSecret (..)
-- WebHook related
, AuthHookType (..)
, AuthHookG (..)
, AuthHook
-- JWT related
, RawJWT
, JWTConfig (..)
, JWTCtx (..)
, JWKSet (..)
, processJwt
, updateJwkRef
, UserAuthentication (..)
) where
import Control.Concurrent.Extended (forkImmortal)
import Data.IORef (newIORef)
import Data.Time.Clock (UTCTime)
import Hasura.Server.Version (HasVersion)
import qualified Data.Text as T
import qualified Network.HTTP.Client as H
import qualified Network.HTTP.Types as N
import Hasura.Logging
import Hasura.Prelude
import Hasura.RQL.Types
import Hasura.Server.Auth.JWT
import Hasura.Server.Auth.WebHook
import Hasura.Server.Utils
import Hasura.Session
-- | Typeclass representing the @UserInfo@ authorization and resolving effect
class (Monad m) => UserAuthentication m where
resolveUserInfo
:: HasVersion
=> Logger Hasura
-> H.Manager
-> [N.Header]
-- ^ request headers
-> AuthMode
-> m (Either QErr (UserInfo, Maybe UTCTime))
newtype AdminSecret
= AdminSecret { getAdminSecret :: T.Text }
deriving (Show, Eq)
data AuthMode
= AMNoAuth
| AMAdminSecret !AdminSecret !(Maybe RoleName)
| AMAdminSecretAndHook !AdminSecret !AuthHook
| AMAdminSecretAndJWT !AdminSecret !JWTCtx !(Maybe RoleName)
deriving (Show, Eq)
mkAuthMode
:: ( HasVersion
, MonadIO m
, MonadError T.Text m
)
=> Maybe AdminSecret
-> Maybe AuthHook
-> Maybe JWTConfig
-> Maybe RoleName
-> H.Manager
-> Logger Hasura
-> m AuthMode
mkAuthMode mAdminSecret mWebHook mJwtSecret mUnAuthRole httpManager logger =
case (mAdminSecret, mWebHook, mJwtSecret) of
(Nothing, Nothing, Nothing) -> return AMNoAuth
(Just key, Nothing, Nothing) -> return $ AMAdminSecret key mUnAuthRole
(Just key, Just hook, Nothing) -> unAuthRoleNotReqForWebHook >>
return (AMAdminSecretAndHook key hook)
(Just key, Nothing, Just jwtConf) -> do
jwtCtx <- mkJwtCtx jwtConf httpManager logger
return $ AMAdminSecretAndJWT key jwtCtx mUnAuthRole
(Nothing, Just _, Nothing) -> throwError $
"Fatal Error : --auth-hook (HASURA_GRAPHQL_AUTH_HOOK)" <> requiresAdminScrtMsg
(Nothing, Nothing, Just _) -> throwError $
"Fatal Error : --jwt-secret (HASURA_GRAPHQL_JWT_SECRET)" <> requiresAdminScrtMsg
(Nothing, Just _, Just _) -> throwError
"Fatal Error: Both webhook and JWT mode cannot be enabled at the same time"
(Just _, Just _, Just _) -> throwError
"Fatal Error: Both webhook and JWT mode cannot be enabled at the same time"
where
requiresAdminScrtMsg =
" requires --admin-secret (HASURA_GRAPHQL_ADMIN_SECRET) or "
<> " --access-key (HASURA_GRAPHQL_ACCESS_KEY) to be set"
unAuthRoleNotReqForWebHook =
when (isJust mUnAuthRole) $ throwError $
"Fatal Error: --unauthorized-role (HASURA_GRAPHQL_UNAUTHORIZED_ROLE) is not allowed"
<> " when --auth-hook (HASURA_GRAPHQL_AUTH_HOOK) is set"
-- | Given the 'JWTConfig' (the user input of JWT configuration), create the 'JWTCtx' (the runtime JWT config used)
mkJwtCtx
:: ( HasVersion
, MonadIO m
, MonadError T.Text m
)
=> JWTConfig
-> H.Manager
-> Logger Hasura
-> m JWTCtx
mkJwtCtx JWTConfig{..} httpManager logger = do
jwkRef <- case jcKeyOrUrl of
Left jwk -> liftIO $ newIORef (JWKSet [jwk])
Right url -> getJwkFromUrl url
let claimsFmt = fromMaybe JCFJson jcClaimsFormat
return $ JWTCtx jwkRef jcClaimNs jcAudience claimsFmt jcIssuer
where
-- if we can't find any expiry time for the JWK (either in @Expires@ header or @Cache-Control@
-- header), do not start a background thread for refreshing the JWK
getJwkFromUrl url = do
ref <- liftIO $ newIORef $ JWKSet []
maybeExpiry <- withJwkError $ updateJwkRef logger httpManager url ref
case maybeExpiry of
Nothing -> return ref
Just time -> do
void $ liftIO $ forkImmortal "jwkRefreshCtrl" logger $
jwkRefreshCtrl logger httpManager url ref (convertDuration time)
return ref
withJwkError act = do
res <- runExceptT act
case res of
Right r -> return r
Left err -> case err of
-- when fetching JWK initially, except expiry parsing error, all errors are critical
JFEHttpException _ msg -> throwError msg
JFEHttpError _ _ _ e -> throwError e
JFEJwkParseError _ e -> throwError e
JFEExpiryParseError _ _ -> return Nothing
getUserInfo
:: (HasVersion, MonadIO m, MonadError QErr m)
=> Logger Hasura
-> H.Manager
-> [N.Header]
-> AuthMode
-> m UserInfo
getUserInfo l m r a = fst <$> getUserInfoWithExpTime l m r a
getUserInfoWithExpTime
:: (HasVersion, MonadIO m, MonadError QErr m)
=> Logger Hasura
-> H.Manager
-> [N.Header]
-> AuthMode
-> m (UserInfo, Maybe UTCTime)
getUserInfoWithExpTime logger manager rawHeaders = \case
AMNoAuth -> (, Nothing) <$> userInfoFromHeaders UAuthNotSet
AMAdminSecret adminScrt unAuthRole ->
case adminSecretM of
Just givenAdminScrt ->
withNoExpTime $ userInfoWhenAdminSecret adminScrt givenAdminScrt
Nothing ->
withNoExpTime $ userInfoWhenNoAdminSecret unAuthRole
AMAdminSecretAndHook accKey hook ->
whenAdminSecretAbsent accKey $
userInfoFromAuthHook logger manager hook rawHeaders
AMAdminSecretAndJWT accKey jwtSecret unAuthRole ->
whenAdminSecretAbsent accKey $ processJwt jwtSecret rawHeaders unAuthRole
where
-- when admin secret is absent, run the action to retrieve UserInfo, otherwise
-- adminsecret override
whenAdminSecretAbsent ak action =
maybe action (withNoExpTime . userInfoWhenAdminSecret ak) adminSecretM
adminSecretM= foldl1 (<|>) $
map (`getSessionVariableValue` sessionVariables) [adminSecretHeader, deprecatedAccessKeyHeader]
sessionVariables = mkSessionVariables rawHeaders
userInfoWhenAdminSecret key reqKey = do
when (reqKey /= getAdminSecret key) $ throw401 $
"invalid " <> adminSecretHeader <> "/" <> deprecatedAccessKeyHeader
userInfoFromHeaders UAdminSecretSent
userInfoWhenNoAdminSecret = \case
Nothing -> throw401 $ adminSecretHeader <> "/"
<> deprecatedAccessKeyHeader <> " required, but not found"
Just roleName -> mkUserInfo UAdminSecretNotSent sessionVariables $ Just roleName
withNoExpTime a = (, Nothing) <$> a
userInfoFromHeaders uas =
mkUserInfo uas sessionVariables $ Just adminRoleName