fradrive/src/Foundation/Authorization.hs
2021-09-07 12:57:53 +02:00

1897 lines
113 KiB
Haskell

{-# LANGUAGE UndecidableInstances, InstanceSigs #-}
{-# OPTIONS_GHC -fno-warn-redundant-constraints -fprof-auto #-}
module Foundation.Authorization
( evalAccess, evalAccessFor, evalAccessWith
, evalAccessDB, evalAccessForDB, evalAccessWithDB
, hasReadAccessTo, hasWriteAccessTo
, wouldHaveReadAccessTo, wouldHaveWriteAccessTo
, wouldHaveReadAccessToIff, wouldHaveWriteAccessToIff
, AuthContext(..), getAuthContext
, isDryRun, isDryRunDB
, maybeBearerToken, requireBearerToken
, requireCurrentBearerRestrictions, maybeCurrentBearerRestrictions
, BearerAuthSite, MonadAP
, routeAuthTags
, orAR, andAR, notAR, trueAR, falseAR
, authoritiveApproot
, AuthorizationCacheKey(..)
) where
import Import.NoFoundation hiding (Last(..))
import Foundation.Type
import Foundation.Routes
import Foundation.I18n
import Foundation.DB
import Handler.Utils.ExamOffice.Exam
import Handler.Utils.ExamOffice.ExternalExam
import Handler.Utils.Memcached
import Handler.Utils.I18n
import Handler.Utils.Routes
import Utils.Course (courseIsVisible)
import Utils.Metrics (observeAuthTagEvaluation, AuthTagEvalOutcome(..))
import qualified Data.Set as Set
import qualified Data.Aeson as JSON
import qualified Data.HashSet as HashSet
import qualified Data.Map as Map
import Data.Map ((!?))
import qualified Data.Text as Text
import Data.List (findIndex)
-- import Data.Semigroup (Last(..))
import qualified Database.Esqueleto.Legacy as E
import qualified Database.Esqueleto.Utils as E
import Control.Monad.Error.Class (MonadError(..))
import Control.Monad.Memo.Class (MonadMemo(..), for4)
import Data.Aeson.Lens hiding (_Value, key)
-- import qualified Data.Conduit.Combinators as C
import qualified Data.Binary as Binary
import GHC.TypeLits (TypeError)
import qualified GHC.TypeLits as TypeError (ErrorMessage(..))
-- import Utils.VolatileClusterSettings
type BearerAuthSite site
= ( MonadCrypto (HandlerFor site)
, CryptoIDKey ~ MonadCryptoKey (HandlerFor site)
, MonadCrypto (ReaderT SqlBackend (HandlerFor site))
, CryptoIDKey ~ MonadCryptoKey (ReaderT SqlBackend (HandlerFor site))
, MonadCrypto (ExceptT AuthResult (HandlerFor site))
, CryptoIDKey ~ MonadCryptoKey (ExceptT AuthResult (HandlerFor site))
, MonadCrypto (MaybeT (HandlerFor site))
, CryptoIDKey ~ MonadCryptoKey (MaybeT (HandlerFor site))
, MonadCrypto (ExceptT AuthResult (ReaderT SqlReadBackend (HandlerFor site)))
, CryptoIDKey ~ MonadCryptoKey (ExceptT AuthResult (ReaderT SqlReadBackend (HandlerFor site)))
, MonadCrypto (ReaderT SqlReadBackend (HandlerFor site))
, CryptoIDKey ~ MonadCryptoKey (ReaderT SqlReadBackend (HandlerFor site))
, MonadCrypto (MaybeT (WriterT Any (ReaderT SqlReadBackend (HandlerFor site))))
, CryptoIDKey ~ MonadCryptoKey (MaybeT (WriterT Any (ReaderT SqlReadBackend (HandlerFor site))))
, MonadCrypto (MaybeT (ReaderT SqlReadBackend (HandlerFor site)))
, CryptoIDKey ~ MonadCryptoKey (MaybeT (ReaderT SqlReadBackend (HandlerFor site)))
, UserId ~ AuthId site, User ~ AuthEntity site
, YesodAuthPersist site
)
-- Access Control
newtype InvalidAuthTag = InvalidAuthTag Text
deriving (Eq, Ord, Show, Read, Generic, Typeable)
instance Exception InvalidAuthTag
type AuthTagsEval m = AuthDNF -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> WriterT (Set AuthTag) m AuthResult
data AccessPredicate
= APPure (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Reader MsgRenderer AuthResult)
| APHandler (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> HandlerFor UniWorX AuthResult)
| APDB (ByteString -> (forall m. MonadAP m => AuthTagsEval m) -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> ReaderT SqlReadBackend (HandlerFor UniWorX) AuthResult)
| APBind (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> HandlerFor UniWorX (Either AccessPredicate AuthResult))
| APBindDB (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> HandlerFor UniWorX (Either (ReaderT SqlReadBackend (HandlerFor UniWorX) (Either AccessPredicate AuthResult)) (Either AccessPredicate AuthResult)))
class (MonadHandler m, HandlerSite m ~ UniWorX, BearerAuthSite UniWorX, MonadUnliftIO m) => MonadAP m where
evalAccessPred :: AccessPredicate -> ByteString -> (forall m'. MonadAP m' => AuthTagsEval m') -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m AuthResult
apRunDB :: forall a. ReaderT SqlReadBackend (HandlerFor UniWorX) a -> m a
type family DisabledMonadAPInstance t err :: Constraint where
DisabledMonadAPInstance t err
= TypeError ( 'TypeError.Text "Used dangerous MonadAP instance for: " 'TypeError.:<>: 'TypeError.ShowType t
'TypeError.:$$: 'TypeError.Text "This instance is currently disabled via TypeError because: " 'TypeError.:<>: err
'TypeError.:$$: 'TypeError.Text "Please consider removing the usage triggering this error message before re-enabling or removing the instance."
)
instance ( BearerAuthSite UniWorX
-- , DisabledMonadAPInstance (HandlerFor UniWorX) ('TypeError.Text "It causes too many database connections")
) => MonadAP (HandlerFor UniWorX) where
evalAccessPred :: HasCallStack => AccessPredicate -> ByteString -> (forall m'. MonadAP m' => AuthTagsEval m') -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> HandlerFor UniWorX AuthResult
evalAccessPred aPred contCtx cont aid r w = case aPred of
(APPure p) -> runReader (p aid r w) <$> getMsgRenderer
(APHandler p) -> p aid r w
(APDB p) -> apRunDB $ p contCtx cont aid r w
(APBind p) -> evalAccessPred (APBindDB $ \aid' r' w' -> Right <$> p aid' r' w') contCtx cont aid r w
(APBindDB p) -> let contAP p' = evalAccessPred p' contCtx cont aid r w
in p aid r w >>= either apRunDB return >>= either contAP return
apRunDB :: forall a. HasCallStack => ReaderT SqlReadBackend (HandlerFor UniWorX) a -> HandlerFor UniWorX a
apRunDB = runDBRead' callStack
instance BearerAuthSite UniWorX => MonadAP (WidgetFor UniWorX) where
evalAccessPred :: HasCallStack => AccessPredicate -> ByteString -> (forall m'. MonadAP m' => AuthTagsEval m') -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> WidgetFor UniWorX AuthResult
evalAccessPred aPred contCtx cont aid r w = liftHandler $ evalAccessPred aPred contCtx cont aid r w
apRunDB :: forall a. HasCallStack => ReaderT SqlReadBackend (HandlerFor UniWorX) a -> WidgetFor UniWorX a
apRunDB = liftHandler . apRunDB
instance (MonadHandler m, HandlerSite m ~ UniWorX, BackendCompatible SqlReadBackend backend, BearerAuthSite UniWorX, MonadUnliftIO m) => MonadAP (ReaderT backend m) where
evalAccessPred aPred contCtx cont aid r w = mapReaderT liftHandler . withReaderT (projectBackend @SqlReadBackend) $ case aPred of
(APPure p) -> lift $ runReader (p aid r w) <$> getMsgRenderer
(APHandler p) -> lift $ p aid r w
(APDB p) -> p contCtx cont aid r w
(APBind p) -> evalAccessPred (APBindDB $ \aid' r' w' -> Right <$> p aid' r' w') contCtx cont aid r w
(APBindDB p) -> let contAP p' = evalAccessPred p' contCtx cont aid r w
in lift (p aid r w) >>= either id return >>= either contAP return
apRunDB = hoist liftHandler . withReaderT projectBackend
-- cacheAP :: ( Binary k
-- , Typeable v, Binary v
-- )
-- => Maybe Expiry
-- -> k
-- -> HandlerFor UniWorX v
-- -> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> v -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-- -> AccessPredicate
-- cacheAP mExp k mkV cont = APBind $ \mAuthId route isWrite -> either (return . Left) (fmap Right) . cont mAuthId route isWrite =<< memcachedBy mExp k mkV
cacheAPDB :: ( Binary k
, Typeable v, Binary v, NFData v
)
=> Maybe Expiry
-> k
-> ReaderT SqlReadBackend (HandlerFor UniWorX) v
-> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> v -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-> AccessPredicate
cacheAPDB mExp k mkV cont = APBindDB $ \mAuthId route isWrite -> do
cachedV <- memcachedByGet k
case cachedV of
Just v -> fmap Right . either (return . Left) (fmap Right) $ cont mAuthId route isWrite v
Nothing -> return . Left $ do
v <- mkV
memcachedBySet mExp k v
either (return . Left) (fmap Right . lift) $ cont mAuthId route isWrite v
-- cacheAP' :: ( Binary k
-- , Typeable v, Binary v
-- )
-- => Maybe Expiry
-- -> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Maybe (k, HandlerFor UniWorX v))
-- -> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Maybe v -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-- -> AccessPredicate
-- cacheAP' mExp mkKV cont = APBind $ \mAuthId route isWrite -> case mkKV mAuthId route isWrite of
-- Just (k, mkV) -> either (return . Left) (fmap Right) . cont mAuthId route isWrite . Just =<< memcachedBy mExp k mkV
-- Nothing -> either (return . Left) (fmap Right) $ cont mAuthId route isWrite Nothing
cacheAPDB' :: ( Binary k
, Typeable v, Binary v, NFData v
)
=> Maybe Expiry
-> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Maybe (k, ReaderT SqlReadBackend (HandlerFor UniWorX) v))
-> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Maybe v -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-> AccessPredicate
cacheAPDB' mExp mkKV cont = APBindDB $ \mAuthId route isWrite -> case mkKV mAuthId route isWrite of
Just (k, mkV) -> do
cachedV <- memcachedByGet k
case cachedV of
Just v -> fmap Right . either (return . Left) (fmap Right) . cont mAuthId route isWrite $ Just v
Nothing -> return . Left $ do
v <- mkV
memcachedBySet mExp k v
either (return . Left) (fmap Right . lift) . cont mAuthId route isWrite $ Just v
Nothing -> fmap Right . either (return . Left) (fmap Right) $ cont mAuthId route isWrite Nothing
orAR, andAR :: MsgRenderer -> AuthResult -> AuthResult -> AuthResult
orAR _ Authorized _ = Authorized
orAR _ _ Authorized = Authorized
orAR _ AuthenticationRequired _ = AuthenticationRequired
orAR _ _ AuthenticationRequired = AuthenticationRequired
orAR mr (Unauthorized x) (Unauthorized y) = Unauthorized . render mr $ MsgUnauthorizedOr x y
-- and
andAR mr (Unauthorized x) (Unauthorized y) = Unauthorized . render mr $ MsgUnauthorizedAnd x y
andAR _ reason@(Unauthorized _) _ = reason
andAR _ _ reason@(Unauthorized _) = reason
andAR _ Authorized other = other
andAR _ AuthenticationRequired _ = AuthenticationRequired
_orARI18n, _andARI18n :: MsgRenderer -> I18nAuthResult -> I18nAuthResult -> I18nAuthResult
_orARI18n _ AuthorizedI18n _ = AuthorizedI18n
_orARI18n _ _ AuthorizedI18n = AuthorizedI18n
_orARI18n _ AuthenticationRequiredI18n _ = AuthenticationRequiredI18n
_orARI18n _ _ AuthenticationRequiredI18n = AuthenticationRequiredI18n
_orARI18n mr (UnauthorizedI18n x) (UnauthorizedI18n y) = fmap (Unauthorized . render mr) . MsgUnauthorizedOr <$> x <*> y
_orARI18n mr _ _ = UnauthorizedI18n . opoint $ render mr MsgUnauthorizedI18nMismatch
-- and
_andARI18n mr (UnauthorizedI18n x) (UnauthorizedI18n y) = fmap (Unauthorized . render mr) . MsgUnauthorizedAnd <$> x <*> y
_andARI18n _ reason@(UnauthorizedI18n _) _ = reason
_andARI18n _ _ reason@(UnauthorizedI18n _) = reason
_andARI18n _ AuthorizedI18n other = other
_andARI18n _ AuthenticationRequiredI18n _ = AuthenticationRequiredI18n
_andARI18n mr _ _ = UnauthorizedI18n . opoint $ render mr MsgUnauthorizedI18nMismatch
notAR :: RenderMessage UniWorX msg => MsgRenderer -> msg -> AuthResult -> AuthResult
notAR _ _ (Unauthorized _) = Authorized
notAR _ _ AuthenticationRequired = AuthenticationRequired
notAR mr msg Authorized = Unauthorized . render mr . MsgUnauthorizedNot $ render mr msg
trueAR, falseAR :: MsgRendererS UniWorX -> AuthResult
trueAR = const Authorized
falseAR = Unauthorized . ($ MsgUnauthorized) . render
trueAP, _falseAP :: AccessPredicate
trueAP = APPure . const . const . const $ trueAR <$> ask
_falseAP = APPure . const . const . const $ falseAR <$> ask -- included for completeness
data AuthContext = AuthContext
{ authCtxAuth :: Maybe (AuthId UniWorX)
, authCtxBearer :: Maybe (BearerToken UniWorX)
, authActiveTags :: AuthTagActive
} deriving (Generic, Typeable)
deriving stock instance Eq (AuthId UniWorX) => Eq AuthContext
deriving stock instance Ord (AuthId UniWorX) => Ord AuthContext
deriving stock instance (Read (AuthId UniWorX), Eq (AuthId UniWorX), Hashable (AuthId UniWorX)) => Read AuthContext
deriving stock instance (Show (AuthId UniWorX), Eq (AuthId UniWorX), Hashable (AuthId UniWorX)) => Show AuthContext
deriving anyclass instance Hashable (AuthId UniWorX) => Hashable AuthContext
deriving anyclass instance (Binary (AuthId UniWorX), Eq (AuthId UniWorX), Hashable (AuthId UniWorX)) => Binary AuthContext
getAuthContext :: forall m.
( MonadHandler m, HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX
)
=> m AuthContext
getAuthContext = liftHandler $ do
authCtx <- AuthContext
<$> defaultMaybeAuthId
<*> runMaybeT (exceptTMaybe askBearerUnsafe)
<*> (fromMaybe def <$> lookupSessionJson SessionActiveAuthTags)
$logDebugS "getAuthContext" $ tshow authCtx
return authCtx
newtype IsDryRun = MkIsDryRun { unIsDryRun :: Bool }
deriving (Eq, Ord, Read, Show, Generic, Typeable)
isDryRun :: ( HasCallStack
, BearerAuthSite UniWorX
)
=> HandlerFor UniWorX Bool
isDryRun = fmap unIsDryRun . cached . fmap MkIsDryRun $ runDBRead isDryRunDB
isDryRunDB :: forall m backend.
( HasCallStack
, MonadAP m, MonadCatch m
, BearerAuthSite UniWorX
, WithRunDB backend (HandlerFor UniWorX) m
, BackendCompatible SqlReadBackend backend
)
=> m Bool
isDryRunDB = fmap unIsDryRun . cached . fmap MkIsDryRun $ orM
[ hasGlobalPostParam PostDryRun
, hasGlobalGetParam GetDryRun
, and2M bearerDryRun bearerRequired
]
where
bearerDryRun = has (_Just . _Object . ix "dry-run") <$> maybeCurrentBearerRestrictions @Value
bearerRequired = maybeT (return True) . catchIfMaybeT cPred $ do
mAuthId <- liftHandler defaultMaybeAuthId
currentRoute <- liftHandler $ maybe (permissionDeniedI MsgUnauthorizedToken404) return =<< getCurrentRoute
isWrite <- liftHandler $ isWriteRequest currentRoute
let noTokenAuth :: AuthDNF -> AuthDNF
noTokenAuth = over _dnfTerms . Set.filter . noneOf (re _nullable . folded) $ (== AuthToken) . plVar
dnf <- throwLeft $ routeAuthTags currentRoute
let eval :: forall m'. MonadAP m' => AuthTagsEval m'
eval dnf' mAuthId' route' isWrite' = evalAuthTags 'isDryRun (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId' route' isWrite'
in guardAuthResult <=< evalWriterT $ eval dnf mAuthId currentRoute isWrite
return False
cPred err = any ($ err)
[ is $ _HCError . _PermissionDenied
, is $ _HCError . _NotAuthenticated
]
askBearerUnsafe :: forall m.
( MonadHandler m, HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX
)
=> ExceptT AuthResult m (BearerToken UniWorX)
-- | This performs /no/ meaningful validation of the `BearerToken`
--
-- Use `requireBearerToken` or `maybeBearerToken` instead
askBearerUnsafe = ExceptT . $cachedHere . liftHandler . runExceptT $ do
bearer <- maybeMExceptT (unauthorizedI MsgUnauthorizedNoToken) askBearer
catch (decodeBearer bearer) $ \case
BearerTokenExpired -> throwError =<< unauthorizedI MsgUnauthorizedTokenExpired
BearerTokenNotStarted -> throwError =<< unauthorizedI MsgUnauthorizedTokenNotStarted
other -> do
$logWarnS "AuthToken" $ tshow other
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalid
validateBearer :: forall m.
( HasCallStack
, MonadHandler m, HandlerSite m ~ UniWorX
, MonadCatch m, MonadAP m
, BearerAuthSite UniWorX
)
=> Maybe (AuthId UniWorX)
-> Route UniWorX
-> Bool -- ^ @isWrite@
-> BearerToken UniWorX
-> m AuthResult
validateBearer mAuthId' route' isWrite' token' = $runCachedMemoT $ for4 memo validateBearer' mAuthId' route' isWrite' token'
where
validateBearer' :: _ -> _ -> _ -> _ -> CachedMemoT (Maybe (AuthId UniWorX), Route UniWorX, Bool, BearerToken UniWorX) AuthResult m AuthResult
validateBearer' mAuthId route isWrite BearerToken{..} = lift . exceptT return return $ do
iforM_ bearerRoutes $ \case
BearerTokenRouteEval -> \routes -> guardMExceptT (HashSet.member route routes) $ unauthorizedI MsgUnauthorizedTokenInvalidRoute
BearerTokenRouteAccess -> \routes -> maybeTMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidRoute) $ do
cRoute <- MaybeT getCurrentRoute
guard $ HashSet.member cRoute routes
let
-- Prevent infinite loops
noTokenAuth :: AuthDNF -> AuthDNF
noTokenAuth = over _dnfTerms . Set.filter . noneOf (re _nullable . folded) $ (== AuthToken) . plVar
eval :: forall m'. MonadAP m' => AuthTagsEval m'
eval dnf' mAuthId'' route'' isWrite'' = evalAuthTags 'validateBearer (AuthTagActive $ const True) eval (noTokenAuth dnf') mAuthId'' route'' isWrite''
bearerAuthority' <- hoist apRunDB $ do
bearerAuthority' <- flip foldMapM bearerAuthority $ \case
Left tVal
| JSON.Success groupName <- JSON.fromJSON tVal -> do
Entity _ primary <- maybeMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidAuthorityGroup) . getBy $ UniquePrimaryUserGroupMember groupName Active
case bearerImpersonate of
Nothing -> return . Set.singleton $ userGroupMemberUser primary
Just iuid | iuid == userGroupMemberUser primary -> return . Set.singleton $ userGroupMemberUser primary
| otherwise -> do
unlessM (lift $ exists [UserGroupMemberUser ==. iuid, UserGroupMemberGroup ==. groupName]) $
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidImpersonation
return $ Set.singleton iuid
| otherwise -> throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidAuthorityValue
Right uid -> case bearerImpersonate of
Just iuid | uid == iuid -> return $ Set.singleton uid
| otherwise -> do
cID <- encrypt iuid
unlessM (lift $ is _Authorized <$> evalAccessWithFor [(AuthToken, False)] (Just uid) (AdminHijackUserR cID) True) $
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidImpersonation
return $ Set.singleton iuid
Nothing -> return $ Set.singleton uid
guardMExceptT (not $ Set.null bearerAuthority') $ unauthorizedI MsgUnauthorizedTokenInvalidNoAuthority
forM_ bearerAuthority' $ \uid -> do
User{userTokensIssuedAfter} <- maybeMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidAuthority) $ get uid
guardMExceptT (Just bearerIssuedAt >= userTokensIssuedAfter) (unauthorizedI MsgUnauthorizedTokenExpired)
return bearerAuthority'
forM_ bearerAuthority' $ \uid -> do
authorityVal <- do
dnf <- throwLeft $ routeAuthTags route
lift . evalWriterT $ eval (noTokenAuth dnf) (Just uid) route isWrite
guardExceptT (is _Authorized authorityVal) authorityVal
whenIsJust bearerAddAuth $ \addDNF -> do
$logDebugS "validateToken" $ tshow addDNF
additionalVal <- lift . evalWriterT $ eval (noTokenAuth addDNF) mAuthId route isWrite
guardExceptT (is _Authorized additionalVal) additionalVal
return Authorized
maybeBearerToken :: ( HasCallStack
, MonadHandler m, HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX
, MonadAP m
, MonadCatch m
) => m (Maybe (BearerToken UniWorX))
maybeBearerToken = $cachedHere . runMaybeT $ catchIfMaybeT cPred requireBearerToken
where
cPred err = any ($ err)
[ is $ _HCError . _PermissionDenied
, is $ _HCError . _NotAuthenticated
]
requireBearerToken :: forall m.
( HasCallStack
, MonadHandler m, HandlerSite m ~ UniWorX
, BearerAuthSite UniWorX
, MonadAP m
, MonadCatch m
)
=> m (BearerToken UniWorX)
requireBearerToken = do
bearer <- exceptT (guardAuthResult >=> error "askToken should not throw `Authorized`") return askBearerUnsafe
mAuthId <- defaultMaybeAuthId -- `maybeAuthId` would be an infinite loop; this is equivalent to `maybeAuthId` but ignoring `bearerImpersonate` from any valid token
currentRoute <- maybe (permissionDeniedI MsgUnauthorizedToken404) return =<< getCurrentRoute
isWrite <- liftHandler $ isWriteRequest currentRoute
guardAuthResult =<< validateBearer mAuthId currentRoute isWrite bearer
return bearer
requireCurrentBearerRestrictions :: forall a m.
( HasCallStack
, MonadHandler m, HandlerSite m ~ UniWorX
, FromJSON a, ToJSON a
, BearerAuthSite UniWorX
, MonadAP m
, MonadCatch m
)
=> m (Maybe a)
requireCurrentBearerRestrictions = runMaybeT $ do
bearer <- lift requireBearerToken
route <- MaybeT getCurrentRoute
hoistMaybe $ bearer ^? _bearerRestrictionIx route
maybeCurrentBearerRestrictions :: forall a m.
( HasCallStack
, MonadHandler m, HandlerSite m ~ UniWorX
, FromJSON a, ToJSON a
, BearerAuthSite UniWorX
, MonadAP m
, MonadCatch m
)
=> m (Maybe a)
maybeCurrentBearerRestrictions = runMaybeT $ do
bearer <- MaybeT maybeBearerToken
route <- MaybeT getCurrentRoute
hoistMaybe $ bearer ^? _bearerRestrictionIx route
data AuthorizationCacheKey
= AuthCacheSchoolFunctionList SchoolFunction | AuthCacheSystemFunctionList SystemFunction
| AuthCacheLecturerList | AuthCacheExternalExamStaffList | AuthCacheCorrectorList | AuthCacheExamCorrectorList | AuthCacheTutorList | AuthCacheSubmissionGroupUserList
| AuthCacheCourseRegisteredList TermId SchoolId CourseShorthand
| AuthCacheVisibleSystemMessages
deriving (Eq, Ord, Read, Show, Generic, Typeable)
deriving anyclass (Hashable, Binary)
cacheAPSchoolFunction :: BearerAuthSite UniWorX
=> SchoolFunction
-> Maybe Expiry
-> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Set (AuthId UniWorX) -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-> AccessPredicate
cacheAPSchoolFunction f mExp = cacheAPDB mExp (AuthCacheSchoolFunctionList f) mkFunctionList
where
mkFunctionList = fmap (setOf $ folded . _Value) . E.select . E.from $ \userFunction -> do
E.where_ $ userFunction E.^. UserFunctionFunction E.==. E.val f
return $ userFunction E.^. UserFunctionUser
cacheAPSystemFunction :: BearerAuthSite UniWorX
=> SystemFunction
-> Maybe Expiry
-> (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Set (AuthId UniWorX) -> Either AccessPredicate (HandlerFor UniWorX AuthResult))
-> AccessPredicate
cacheAPSystemFunction f mExp = cacheAPDB mExp (AuthCacheSystemFunctionList f) mkFunctionList
where
mkFunctionList = fmap (setOf $ folded . _Value) . E.select . E.from $ \userSystemFunction -> do
E.where_ $ userSystemFunction E.^. UserSystemFunctionFunction E.==. E.val f
E.&&. E.not_ (userSystemFunction E.^. UserSystemFunctionIsOptOut)
return $ userSystemFunction E.^. UserSystemFunctionUser
tagAccessPredicate :: ( HasCallStack
, BearerAuthSite UniWorX
)
=> AuthTag -> AccessPredicate
tagAccessPredicate AuthFree = trueAP
tagAccessPredicate AuthAdmin = cacheAPSchoolFunction SchoolAdmin (Just $ Right diffHour) $ \mAuthId' route' _ adminList -> if
| maybe True (`Set.notMember` adminList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CourseR{} -> unauthorizedI MsgUnauthorizedSchoolAdmin
AllocationR{} -> unauthorizedI MsgUnauthorizedSchoolAdmin
SchoolR _ _ -> unauthorizedI MsgUnauthorizedSchoolAdmin
_other -> unauthorizedI MsgUnauthorizedSiteAdmin
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
-- Courses: access only to school admins
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isAdmin <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` userAdmin) -> do
E.on $ course E.^. CourseSchool E.==. userAdmin E.^. UserFunctionSchool
E.where_ $ userAdmin E.^. UserFunctionUser E.==. E.val authId
E.&&. userAdmin E.^. UserFunctionFunction E.==. E.val SchoolAdmin
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isAdmin $ unauthorizedI MsgUnauthorizedSchoolAdmin
return Authorized
-- Allocations: access only to school admins
AllocationR tid ssh ash _ -> $cachedHereBinary (mAuthId, tid, ssh, ash) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isAdmin <- lift . E.selectExists . E.from $ \(allocation `E.InnerJoin` userAdmin) -> do
E.on $ allocation E.^. AllocationSchool E.==. userAdmin E.^. UserFunctionSchool
E.where_ $ userAdmin E.^. UserFunctionUser E.==. E.val authId
E.&&. userAdmin E.^. UserFunctionFunction E.==. E.val SchoolAdmin
E.&&. allocation E.^. AllocationTerm E.==. E.val tid
E.&&. allocation E.^. AllocationSchool E.==. E.val ssh
E.&&. allocation E.^. AllocationShorthand E.==. E.val ash
guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin)
return Authorized
-- Schools: access only to school admins
SchoolR ssh _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isAdmin <- lift . existsBy $ UniqueUserFunction authId ssh SchoolAdmin
guardMExceptT isAdmin (unauthorizedI MsgUnauthorizedSchoolAdmin)
return Authorized
-- other routes: access to any admin is granted here
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
adrights <- lift $ selectFirst [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolAdmin] []
guardMExceptT (isJust adrights) (unauthorizedI MsgUnauthorizedSiteAdmin)
return Authorized
tagAccessPredicate AuthSystemExamOffice = cacheAPSystemFunction SystemExamOffice (Just $ Right diffHour) $ \mAuthId' _ _ examOfficeList -> if
| maybe True (`Set.notMember` examOfficeList) mAuthId' -> Right $ if
| is _Nothing mAuthId' -> return AuthenticationRequired
| otherwise -> unauthorizedI MsgUnauthorizedSystemExamOffice
| otherwise -> Left $ APDB $ \_ _ mAuthId _ _ -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isExamOffice <- lift $ exists [UserSystemFunctionUser ==. authId, UserSystemFunctionFunction ==. SystemExamOffice, UserSystemFunctionIsOptOut ==. False]
guardMExceptT isExamOffice $ unauthorizedI MsgUnauthorizedSystemExamOffice
return Authorized
tagAccessPredicate AuthStudent = cacheAPSystemFunction SystemStudent (Just $ Right diffHour) $ \mAuthId' _ _ studentList -> if
| maybe True (`Set.notMember` studentList) mAuthId' -> Right $ if
| is _Nothing mAuthId' -> return AuthenticationRequired
| otherwise -> unauthorizedI MsgUnauthorizedStudent
| otherwise -> Left $ APDB $ \_ _ mAuthId _ _ -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isStudent <- lift $ exists [UserSystemFunctionUser ==. authId, UserSystemFunctionFunction ==. SystemStudent, UserSystemFunctionIsOptOut ==. False]
guardMExceptT isStudent $ unauthorizedI MsgUnauthorizedStudent
return Authorized
tagAccessPredicate AuthExamOffice = cacheAPSchoolFunction SchoolExamOffice (Just $ Right diffHour) $ \mAuthId' route' _ examOfficeList -> if
| maybe True (`Set.notMember` examOfficeList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CExamR{} -> unauthorizedI MsgUnauthorizedExamExamOffice
EExamR{} -> unauthorizedI MsgUnauthorizedExternalExamExamOffice
CourseR{} -> unauthorizedI MsgUnauthorizedExamExamOffice
SchoolR _ _ -> unauthorizedI MsgUnauthorizedSchoolExamOffice
_other -> unauthorizedI MsgUnauthorizedExamOffice
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
CExamR tid ssh csh examn _ -> $cachedHereBinary (mAuthId, tid, ssh, csh, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasUsers <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examResult) -> do
E.on $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId
E.on $ exam E.^. ExamCourse E.==. course E.^. CourseId
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
E.where_ $ examOfficeExamResultAuth (E.val authId) examResult
guardMExceptT hasUsers (unauthorizedI MsgUnauthorizedExamExamOffice)
return Authorized
EExamR tid ssh coursen examn _ -> $cachedHereBinary (mAuthId, tid, ssh, coursen, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasUsers <- lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamResult) -> do
E.on $ eexam E.^. ExternalExamId E.==. eexamResult E.^. ExternalExamResultExam
E.where_ $ eexam E.^. ExternalExamTerm E.==. E.val tid
E.&&. eexam E.^. ExternalExamSchool E.==. E.val ssh
E.&&. eexam E.^. ExternalExamCourseName E.==. E.val coursen
E.&&. eexam E.^. ExternalExamExamName E.==. E.val examn
E.where_ $ examOfficeExternalExamResultAuth (E.val authId) eexamResult
guardMExceptT hasUsers $ unauthorizedI MsgUnauthorizedExternalExamExamOffice
return Authorized
CourseR _ ssh _ _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isExamOffice <- lift . existsBy $ UniqueUserFunction authId ssh SchoolExamOffice
guardMExceptT isExamOffice $ unauthorizedI MsgUnauthorizedExamExamOffice
return Authorized
SchoolR ssh _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isExamOffice <- lift . existsBy $ UniqueUserFunction authId ssh SchoolExamOffice
guardMExceptT isExamOffice (unauthorizedI MsgUnauthorizedSchoolExamOffice)
return Authorized
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isExamOffice <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolExamOffice]
guardMExceptT isExamOffice (unauthorizedI MsgUnauthorizedExamOffice)
return Authorized
tagAccessPredicate AuthEvaluation = cacheAPSchoolFunction SchoolEvaluation (Just $ Right diffHour) $ \mAuthId' _ _ evaluationList -> if
| maybe True (`Set.notMember` evaluationList) mAuthId' -> Right $ if
| is _Nothing mAuthId' -> return AuthenticationRequired
| otherwise -> unauthorizedI MsgUnauthorizedEvaluation
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
ParticipantsR _ ssh -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolEvaluation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedEvaluation
return Authorized
CourseR _ ssh _ _ -> $cachedHereBinary(mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolEvaluation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedEvaluation
return Authorized
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolEvaluation]
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedEvaluation
return Authorized
tagAccessPredicate AuthAllocationAdmin = cacheAPSchoolFunction SchoolAllocation (Just $ Right diffHour) $ \mAuthId' _ _ allocationList -> if
| maybe True (`Set.notMember` allocationList) mAuthId' -> Right $ if
| is _Nothing mAuthId' -> return AuthenticationRequired
| otherwise -> unauthorizedI MsgUnauthorizedAllocationAdmin
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
AllocationR _ ssh _ _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolAllocation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
CourseR _ ssh _ _ -> $cachedHereBinary (mAuthId, ssh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift . existsBy $ UniqueUserFunction authId ssh SchoolAllocation
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
_other -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isEvaluation <- lift $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolAllocation]
guardMExceptT isEvaluation $ unauthorizedI MsgUnauthorizedAllocationAdmin
return Authorized
tagAccessPredicate AuthToken = APDB $ \_ _ mAuthId route isWrite -> exceptT return return $
lift . validateBearer mAuthId route isWrite =<< askBearerUnsafe
tagAccessPredicate AuthNoEscalation = APDB $ \_ _ mAuthId route _ -> case route of
AdminHijackUserR cID -> $cachedHereBinary (mAuthId, cID) . exceptT return return $ do
myUid <- maybeExceptT AuthenticationRequired $ return mAuthId
uid <- decrypt cID
otherSchoolsFunctions <- lift . $cachedHereBinary uid $ Set.fromList . map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. uid] []
mySchools <- lift . $cachedHereBinary myUid $ Set.fromList . map (userFunctionSchool . entityVal) <$> selectList [UserFunctionUser ==. myUid, UserFunctionFunction ==. SchoolAdmin] []
guardMExceptT (otherSchoolsFunctions `Set.isSubsetOf` mySchools) (unauthorizedI MsgUnauthorizedAdminEscalation)
return Authorized
r -> $unsupportedAuthPredicate AuthNoEscalation r
tagAccessPredicate AuthDeprecated = APHandler $ \_ r _ -> do
$logWarnS "AccessControl" ("deprecated route: " <> tshow r)
addMessageI Error MsgDeprecatedRoute
allow <- getsYesod $ view _appAllowDeprecated
return $ bool (Unauthorized "Deprecated Route") Authorized allow
tagAccessPredicate AuthDevelopment = APHandler $ \_ r _ -> do
$logWarnS "AccessControl" ("route in development: " <> tshow r)
#ifdef DEVELOPMENT
return Authorized
#else
return $ Unauthorized "Route under development"
#endif
tagAccessPredicate AuthLecturer = cacheAPDB' (Just $ Right diffMinute) mkLecturerList $ \mAuthId' route' _ mLecturerList -> if
| Just lecturerList <- mLecturerList
, maybe True (`Set.notMember` lecturerList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CourseR{} -> unauthorizedI MsgUnauthorizedLecturer
AllocationR{} -> unauthorizedI MsgUnauthorizedAllocationLecturer
EExamR{} -> unauthorizedI MsgUnauthorizedExternalExamLecturer
_other -> unauthorizedI MsgUnauthorizedSchoolLecturer
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isLecturer <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
E.where_ $ lecturer E.^. LecturerUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isLecturer (unauthorizedI MsgUnauthorizedLecturer)
return Authorized
AllocationR tid ssh ash _ -> $cachedHereBinary (mAuthId, tid, ssh, ash) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isLecturer <- lift . E.selectExists . E.from $ \(allocation `E.InnerJoin` allocationCourse `E.InnerJoin` course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
E.on $ course E.^. CourseId E.==. allocationCourse E.^. AllocationCourseCourse
E.on $ allocation E.^. AllocationId E.==. allocationCourse E.^. AllocationCourseAllocation
E.where_ $ lecturer E.^. LecturerUser E.==. E.val authId
E.&&. allocation E.^. AllocationTerm E.==. E.val tid
E.&&. allocation E.^. AllocationSchool E.==. E.val ssh
E.&&. allocation E.^. AllocationShorthand E.==. E.val ash
guardMExceptT isLecturer $ unauthorizedI MsgUnauthorizedAllocationLecturer
return Authorized
EExamR tid ssh coursen examn _ -> $cachedHereBinary (mAuthId, tid, ssh, coursen, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isLecturer <- lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` staff) -> do
E.on $ eexam E.^. ExternalExamId E.==. staff E.^. ExternalExamStaffExam
E.where_ $ staff E.^. ExternalExamStaffUser E.==. E.val authId
E.&&. eexam E.^. ExternalExamTerm E.==. E.val tid
E.&&. eexam E.^. ExternalExamSchool E.==. E.val ssh
E.&&. eexam E.^. ExternalExamCourseName E.==. E.val coursen
E.&&. eexam E.^. ExternalExamExamName E.==. E.val examn
guardMExceptT isLecturer $ unauthorizedI MsgUnauthorizedExternalExamLecturer
return Authorized
-- lecturer for any school will do
_ -> $cachedHereBinary mAuthId . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSchoolLecturer) $ selectFirst [UserFunctionUser ==. authId, UserFunctionFunction ==. SchoolLecturer] []
return Authorized
where
mkLecturerList _ route _ = case route of
CourseR{} -> cacheLecturerList
AllocationR{} -> cacheLecturerList
EExamR{} -> Just
( AuthCacheExternalExamStaffList
, fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. ExternalExamStaffUser)
)
_other -> Just
( AuthCacheSchoolFunctionList SchoolLecturer
, fmap (setOf $ folded . _Value) . E.select . E.from $ \userFunction -> do
E.where_ $ userFunction E.^. UserFunctionFunction E.==. E.val SchoolLecturer
return $ userFunction E.^. UserFunctionUser
)
where
cacheLecturerList = Just
( AuthCacheLecturerList
, fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. LecturerUser)
)
tagAccessPredicate AuthCorrector = cacheAPDB (Just $ Right diffMinute) AuthCacheCorrectorList mkCorrectorList $ \mAuthId' route' _ correctorList -> if
| maybe True (`Set.notMember` correctorList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CSubmissionR{} -> unauthorizedI MsgUnauthorizedSubmissionCorrector
CSheetR{} -> unauthorizedI MsgUnauthorizedSheetCorrector
CourseR{} -> unauthorizedI MsgUnauthorizedCorrector
_other -> unauthorizedI MsgUnauthorizedCorrectorAny
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
case route of
CSubmissionR _ _ _ _ cID _ -> lift . $cachedHereBinary (authId, cID) . maybeT (unauthorizedI MsgUnauthorizedSubmissionCorrector) $ do
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
guardM . lift . E.selectExists . E.from $ \submission ->
E.where_ $ submission E.^. SubmissionId E.==. E.val sid
E.&&. submission E.^. SubmissionRatingBy E.==. E.justVal authId
return Authorized
CSheetR tid ssh csh shn _ -> lift . $cachedHereBinary (authId, tid, ssh, csh, shn) . maybeT (unauthorizedI MsgUnauthorizedSheetCorrector) $ do
guardM . lift . E.selectExists . E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
E.on $ sheetCorrector E.^. SheetCorrectorSheet E.==. sheet E.^. SheetId
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val authId
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. sheet E.^. SheetName E.==. E.val shn
return Authorized
CourseR tid ssh csh _ -> lift . $cachedHereBinary (mAuthId, tid, ssh, csh) . maybeT (unauthorizedI MsgUnauthorizedCorrector) $ do
guardM . lift . E.selectExists . E.from $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
E.on $ sheetCorrector E.^. SheetCorrectorSheet E.==. sheet E.^. SheetId
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val authId
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
return Authorized
_ -> lift . $cachedHereBinary mAuthId . maybeT (unauthorizedI MsgUnauthorizedCorrectorAny) $ do
guardM . lift . E.selectExists . E.from $ \sheetCorrector ->
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val authId
return Authorized
where
mkCorrectorList = do
submissionCorrectors <- E.select . E.from $ \submission -> E.distinctOnOrderBy [E.asc $ submission E.^. SubmissionRatingBy] $ do
E.where_ . E.isJust $ submission E.^. SubmissionRatingBy
return $ submission E.^. SubmissionRatingBy
let submissionCorrectors' = Set.fromDistinctAscList $ mapMaybe (preview $ _Value . _Just) submissionCorrectors
sheetCorrectors <- E.select . E.from $ \sheetCorrector -> E.distinctOnOrderBy [E.asc $ sheetCorrector E.^. SheetCorrectorUser] $
return $ sheetCorrector E.^. SheetCorrectorUser
let sheetCorrectors' = Set.fromDistinctAscList $ map (^. _Value) sheetCorrectors
return $ submissionCorrectors' `Set.union` sheetCorrectors'
tagAccessPredicate AuthExamCorrector = cacheAPDB (Just $ Right diffMinute) AuthCacheExamCorrectorList mkExamCorrectorList $ \mAuthId' route' _ examCorrectorList -> if
| maybe True (`Set.notMember` examCorrectorList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CExamR{} -> unauthorizedI MsgUnauthorizedExamCorrector
CourseR{} -> unauthorizedI MsgUnauthorizedExamCorrector
r -> $unsupportedAuthPredicate AuthExamCorrector r
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
CExamR tid ssh csh examn _ -> $cachedHereBinary (mAuthId, tid, ssh, csh, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isCorrector <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examCorrector) -> do
E.on $ examCorrector E.^. ExamCorrectorExam E.==. exam E.^. ExamId
E.on $ exam E.^. ExamCourse E.==. course E.^. CourseId
E.where_ $ examCorrector E.^. ExamCorrectorUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
guardMExceptT isCorrector $ unauthorizedI MsgUnauthorizedExamCorrector
return Authorized
CourseR tid ssh csh _ -> $cachedHereBinary (tid, ssh, csh) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isCorrector <- lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examCorrector) -> do
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.on $ exam E.^. ExamId E.==. examCorrector E.^. ExamCorrectorExam
E.where_ $ examCorrector E.^. ExamCorrectorUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isCorrector $ unauthorizedI MsgUnauthorizedExamCorrector
return Authorized
r -> $unsupportedAuthPredicate AuthExamCorrector r
where
mkExamCorrectorList = fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. ExamCorrectorUser)
tagAccessPredicate AuthTutor = cacheAPDB (Just $ Right diffMinute) AuthCacheTutorList mkTutorList $ \mAuthId' route' _ tutorList -> if
| maybe True (`Set.notMember` tutorList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CTutorialR{} -> unauthorizedI MsgUnauthorizedTutorialTutor
CourseR{} -> unauthorizedI MsgUnauthorizedCourseTutor
_other -> unauthorizedI MsgUnauthorizedTutor
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
resList <- $cachedHereBinary authId . lift . E.select . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
E.on $ tutor E.^. TutorTutorial E.==. tutorial E.^. TutorialId
E.on $ tutorial E.^. TutorialCourse E.==. course E.^. CourseId
E.where_ $ tutor E.^. TutorUser E.==. E.val authId
return (course E.^. CourseId, tutorial E.^. TutorialId)
let
resMap :: Map CourseId (Set TutorialId)
resMap = Map.fromListWith Set.union [ (cid, Set.singleton tutid) | (E.Value cid, E.Value tutid) <- resList ]
case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgUnauthorizedTutorialTutor) $ do
Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
Entity tutid _ <- $cachedHereBinary (cid, tutn) . MaybeT . lift . getBy $ UniqueTutorial cid tutn
guard $ tutid `Set.member` fromMaybe Set.empty (resMap !? cid)
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnauthorizedCourseTutor) $ do
Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh
guard $ cid `Set.member` Map.keysSet resMap
return Authorized
_ -> do
guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedTutor)
return Authorized
where
mkTutorList = fmap (setOf $ folded . _Value) . E.select . E.from $ return . (E.^. TutorUser)
tagAccessPredicate AuthTutorControl = APDB $ \_ _ _ route _ -> case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgUnauthorizedTutorialTutorControl) $ do
Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
Entity _ Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
guard tutorialTutorControlled
return Authorized
r -> $unsupportedAuthPredicate AuthTutorControl r
tagAccessPredicate AuthSubmissionGroup = APDB $ \_ _ mAuthId route _ -> case route of
CSubmissionR tid ssh csh shn cID _ -> maybeT (unauthorizedI MsgUnauthorizedSubmissionSubmissionGroup) $ do
course <- MaybeT . $cachedHereBinary (tid, ssh, csh) . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{..} <- MaybeT . $cachedHereBinary (course, shn) . getBy $ CourseSheet course shn
when (is _RegisteredGroups sheetGrouping) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
groups <- $cachedHereBinary cID . lift . fmap (Set.fromList . fmap E.unValue) . E.select . E.from $ \(submissionGroupUser `E.InnerJoin` submissionUser) -> do
E.on $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. submissionUser E.^. SubmissionUserUser
E.where_ $ submissionUser E.^. SubmissionUserSubmission E.==. E.val smId
return $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup
unless (Set.null groups) $ do
uid <- hoistMaybe mAuthId
guardM . lift $ exists [SubmissionGroupUserUser ==. uid, SubmissionGroupUserSubmissionGroup <-. Set.toList groups]
return Authorized
CSheetR tid ssh csh sheetn _ -> maybeT (unauthorizedI MsgUnauthorizedSheetSubmissionGroup) $ do
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{..} <- $cachedHereBinary (course, sheetn) . MaybeT . getBy $ CourseSheet course sheetn
when (is _RegisteredGroups sheetGrouping) $ do
uid <- hoistMaybe mAuthId
guardM . lift . E.selectExists . E.from $ \(submissionGroup `E.InnerJoin` submissionGroupUser) -> do
E.on $ submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup E.==. submissionGroup E.^. SubmissionGroupId
E.where_ $ submissionGroup E.^. SubmissionGroupCourse E.==. E.val course
E.&&. submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val uid
return Authorized
r -> $unsupportedAuthPredicate AuthSubmissionGroup r
tagAccessPredicate AuthTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrite -> case route of
CExamR tid ssh csh examn subRoute -> maybeT (unauthorizedI MsgUnauthorizedExamTime) $ do
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity eId Exam{..} <- $cachedHereBinary (course, examn) . MaybeT . getBy $ UniqueExam course examn
cTime <- liftIO getCurrentTime
registration <- case mAuthId of
Just uid -> $cachedHereBinary (eId, uid) . lift . getBy $ UniqueExamRegistration eId uid
Nothing -> return Nothing
let visible = NTop examVisibleFrom <= NTop (Just cTime)
case subRoute of
EShowR -> guard visible
EUsersR -> guard $ NTop examStart <= NTop (Just cTime)
&& NTop (Just cTime) <= NTop examFinished
ERegisterR
| is _Nothing registration
-> guard $ visible
&& NTop examRegisterFrom <= NTop (Just cTime)
&& NTop (Just cTime) <= NTop examRegisterTo
| otherwise
-> guard $ visible
&& NTop (Just cTime) <= NTop examDeregisterUntil
ERegisterOccR occn -> do
occId <- hoistMaybe <=< $cachedHereBinary (eId, occn) . lift . getKeyBy $ UniqueExamOccurrence eId occn
if
| (registration >>= examRegistrationOccurrence . entityVal) == Just occId
-> guard $ visible
&& NTop (Just cTime) <= NTop examDeregisterUntil
| otherwise
-> guard $ visible
&& NTop examRegisterFrom <= NTop (Just cTime)
&& NTop (Just cTime) <= NTop examRegisterTo
ECorrectR -> guard $ NTop (Just cTime) >= NTop examStart
&& NTop (Just cTime) <= NTop examFinished
_ -> return ()
return Authorized
CTutorialR tid ssh csh tutn TRegisterR -> maybeT (unauthorizedI MsgUnauthorizedTutorialTime) $ do
now <- liftIO getCurrentTime
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity tutId Tutorial{..} <- $cachedHereBinary (course, tutn) . MaybeT . getBy $ UniqueTutorial course tutn
registered <- case mAuthId of
Just uid -> $cachedHereBinary (tutId, uid) . lift . existsBy $ UniqueTutorialParticipant tutId uid
Nothing -> return False
if
| not registered
, maybe False (now >=) tutorialRegisterFrom
, maybe True (now <=) tutorialRegisterTo
-> return Authorized
| registered
, maybe True (now <=) tutorialDeregisterUntil
-> return Authorized
| otherwise
-> mzero
CSheetR tid ssh csh shn subRoute -> maybeT (unauthorizedI MsgUnauthorizedSheetTime) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _sid Sheet{..} <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn
cTime <- liftIO getCurrentTime
let
visible = NTop sheetVisibleFrom <= NTop (Just cTime)
active = NTop sheetActiveFrom <= NTop (Just cTime) && NTop (Just cTime) <= NTop sheetActiveTo
marking = NTop (Just cTime) > NTop sheetActiveTo
guard visible
case subRoute of
-- Single Files
SFileR SheetExercise _ -> guard $ NTop sheetActiveFrom <= NTop (Just cTime)
SFileR SheetHint _ -> guard $ maybe False (<= cTime) sheetHintFrom
SFileR SheetSolution _ -> guard $ maybe False (<= cTime) sheetSolutionFrom
SFileR _ _ -> mzero
-- Archives of SheetFileType
SZipR SheetExercise -> guard $ NTop sheetActiveFrom <= NTop (Just cTime)
SZipR SheetHint -> guard $ maybe False (<= cTime) sheetHintFrom
SZipR SheetSolution -> guard $ maybe False (<= cTime) sheetSolutionFrom
SZipR _ -> mzero
-- Submissions
SubmissionNewR -> guard active
SAssignR -> guard marking -- Correctors can only be assigned when the Sheet is inactive, since submissions are subject to change; this is assumed in Corrections.assignHandler
SubmissionR _ SubAssignR -> guard marking -- Correctors can only be assigned when the Sheet is inactive, since submissions are subject to change
SubmissionR _ _ -> guard active
_ -> return ()
return Authorized
CourseR tid ssh csh (MaterialR mnm _) -> maybeT (unauthorizedI MsgUnauthorizedMaterialTime) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _mid Material{materialVisibleFrom} <- $cachedHereBinary (cid, mnm) . MaybeT . getBy $ UniqueMaterial cid mnm
cTime <- liftIO getCurrentTime
let visible = NTop materialVisibleFrom <= NTop (Just cTime)
guard visible
return Authorized
CourseR tid ssh csh CRegisterR -> do
now <- liftIO getCurrentTime
mbc <- $cachedHereBinary (tid, ssh, csh) . getBy $ TermSchoolCourseShort tid ssh csh
registered <- cont (predDNFSingleton $ PLVariable AuthCourseRegistered) mAuthId route isWrite
case mbc of
(Just (Entity _ Course{courseRegisterFrom, courseRegisterTo}))
| not registered
, maybe False (now >=) courseRegisterFrom -- Nothing => no registration allowed
, maybe True (now <=) courseRegisterTo -> return Authorized
(Just (Entity cid Course{courseDeregisterUntil}))
| registered
-> maybeT (unauthorizedI MsgUnauthorizedCourseRegistrationTime) $ do
guard $ maybe True (now <=) courseDeregisterUntil
forM_ mAuthId $ \uid -> do
exams <- lift . E.select . E.from $ \exam -> do
E.where_ . E.exists . E.from $ \examRegistration ->
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. exam E.^. ExamId
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val uid
E.where_ $ exam E.^. ExamCourse E.==. E.val cid
return $ exam E.^. ExamDeregisterUntil
forM_ exams $ \(E.Value deregUntil) ->
guard $ NTop (Just now) <= NTop deregUntil
tutorials <- lift . E.select . E.from $ \tutorial -> do
E.where_ . E.exists . E.from $ \tutorialParticipant ->
E.where_ $ tutorialParticipant E.^. TutorialParticipantTutorial E.==. tutorial E.^. TutorialId
E.&&. tutorialParticipant E.^. TutorialParticipantUser E.==. E.val uid
E.where_ $ tutorial E.^. TutorialCourse E.==. E.val cid
return $ tutorial E.^. TutorialDeregisterUntil
forM_ tutorials $ \(E.Value deregUntil) ->
guard $ NTop (Just now) <= NTop deregUntil
return Authorized
_other -> unauthorizedI MsgUnauthorizedCourseRegistrationTime
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
Entity course Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course
allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation
case allocation of
Nothing -> do
cTime <- liftIO getCurrentTime
guard $ maybe False (cTime >=) courseRegisterFrom
guard $ maybe True (cTime <=) courseRegisterTo
Just Allocation{..} -> do
cTime <- liftIO getCurrentTime
guard $ NTop allocationRegisterFrom <= NTop (Just cTime)
guard $ NTop (Just cTime) <= NTop allocationRegisterTo
return Authorized
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegisterTime) $ do
-- Checks `registerFrom` and `registerTo`, override as further routes become available
now <- liftIO getCurrentTime
Entity _ Allocation{..} <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getBy $ TermSchoolAllocationShort tid ssh ash
guard $ NTop allocationRegisterFrom <= NTop (Just now)
guard $ NTop (Just now) <= NTop allocationRegisterTo
return Authorized
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
cTime <- liftIO getCurrentTime
let cacheTime = diffDay
massageVisible = Map.fromList . map (over _1 E.unValue . over (_2 . _1) E.unValue . over (_2 . _2) E.unValue)
visibleSystemMessages <- lift . memcacheAuth' @(Map SystemMessageId (Maybe UTCTime, Maybe UTCTime)) (Right cacheTime) AuthCacheVisibleSystemMessages . fmap massageVisible . E.select . E.from $ \systemMessage -> do
E.where_ $ E.maybe E.true (E.>=. E.val cTime) (systemMessage E.^. SystemMessageTo)
E.&&. E.maybe E.false (E.<=. E.val (realToFrac diffDay `addUTCTime` cTime)) (systemMessage E.^. SystemMessageFrom) -- good enough.
return
( systemMessage E.^. SystemMessageId
, ( systemMessage E.^. SystemMessageFrom
, systemMessage E.^. SystemMessageTo
)
)
(msgFrom, msgTo) <- hoistMaybe $ Map.lookup smId visibleSystemMessages
let cTime' = NTop $ Just cTime
guard $ NTop msgFrom <= cTime'
&& NTop msgTo >= cTime'
return Authorized
MessageHideR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageTime) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
SystemMessage{systemMessageFrom, systemMessageTo} <- $cachedHereBinary smId . MaybeT $ get smId
cTime <- NTop . Just <$> liftIO getCurrentTime
guard $ NTop systemMessageFrom <= cTime
&& NTop systemMessageTo >= cTime
return Authorized
CNewsR _ _ _ cID _ -> maybeT (unauthorizedI MsgUnauthorizedCourseNewsTime) $ do
nId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
CourseNews{courseNewsVisibleFrom} <- $cachedHereBinary nId . MaybeT $ get nId
cTime <- NTop . Just <$> liftIO getCurrentTime
guard $ NTop courseNewsVisibleFrom <= cTime
return Authorized
r -> $unsupportedAuthPredicate AuthTime r
tagAccessPredicate AuthStaffTime = APDB $ \_ _ _ route isWrite -> case route of
CApplicationR tid ssh csh _ _ -> maybeT (unauthorizedI MsgUnauthorizedApplicationTime) $ do
course <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
allocationCourse <- $cachedHereBinary course . lift . getBy $ UniqueAllocationCourse course
allocation <- for allocationCourse $ \(Entity _ AllocationCourse{..}) -> $cachedHereBinary allocationCourseAllocation . MaybeT $ get allocationCourseAllocation
case allocation of
Nothing -> return ()
Just Allocation{..} -> do
cTime <- liftIO getCurrentTime
guard $ NTop allocationStaffAllocationFrom <= NTop (Just cTime)
when isWrite $
guard $ NTop (Just cTime) <= NTop allocationStaffAllocationTo
return Authorized
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegisterTime) $ do
-- Checks `registerFrom` and `registerTo`, override as further routes become available
now <- liftIO getCurrentTime
Entity _ Allocation{..} <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getBy $ TermSchoolAllocationShort tid ssh ash
guard $ NTop allocationStaffAllocationFrom <= NTop (Just now)
guard $ NTop (Just now) <= NTop allocationStaffAllocationTo
return Authorized
r -> $unsupportedAuthPredicate AuthStaffTime r
tagAccessPredicate AuthAllocationTime = APDB $ \_ (runTACont -> cont) mAuthId route isWrite -> case route of
CourseR tid ssh csh CRegisterR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Nothing -> return Authorized
Just (_, Allocation{..}) -> do
registered <- cont (predDNFSingleton $ PLVariable AuthCourseRegistered) mAuthId route isWrite
if
| not registered
, NTop allocationRegisterByCourse >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseRegister
| registered
, NTop (Just now) >= NTop allocationOverrideDeregister
-> unauthorizedI MsgUnauthorizedAllocatedCourseDeregister
| otherwise
-> return Authorized
CourseR tid ssh csh CAddUserR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Just (_, Allocation{..})
| NTop allocationRegisterByStaffTo <= NTop (Just now)
|| NTop allocationRegisterByStaffFrom >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseRegister
_other -> return Authorized
CourseR tid ssh csh CDeleteR -> do
now <- liftIO getCurrentTime
mba <- mbAllocation tid ssh csh
case mba of
Just (_, Allocation{..})
| NTop allocationRegisterByStaffTo <= NTop (Just now)
|| NTop allocationRegisterByStaffFrom >= NTop (Just now)
-> unauthorizedI MsgUnauthorizedAllocatedCourseDelete
_other -> return Authorized
r -> $unsupportedAuthPredicate AuthAllocationTime r
where
mbAllocation tid ssh csh = $cachedHereBinary (tid, ssh, csh) . runMaybeT $ do
cid <- MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _ AllocationCourse{..} <- MaybeT . getBy $ UniqueAllocationCourse cid
(cid,) <$> MaybeT (get allocationCourseAllocation)
tagAccessPredicate AuthCourseTime = APDB $ \_ _ _mAuthId route _ -> case route of
CourseR tid ssh csh _ -> exceptT return return $ do
now <- liftIO getCurrentTime
courseVisible <- $cachedHereBinary (tid, ssh, csh) . lift . E.selectExists . E.from $ \course -> do
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. courseIsVisible now course E.nothing
guardMExceptT courseVisible (unauthorizedI MsgUnauthorizedCourseTime)
return Authorized
r -> $unsupportedAuthPredicate AuthCourseTime r
tagAccessPredicate AuthExamTime = APDB $ \_ _ _ route _ -> case route of
CSubmissionR tid ssh csh shn _cID CorrectionR -> maybeT (unauthorizedI MsgUnauthorizedCorrectionExamTime) $ do
cid <- MaybeT . $cachedHereBinary (tid, ssh, csh) . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _sid Sheet{..} <- MaybeT . $cachedHereBinary (cid, shn) . getBy $ CourseSheet cid shn
whenIsJust (sheetType ^? _examPart . from _SqlKey) $ \epId -> do
ExamPart{examPartExam} <- MaybeT . $cachedHereBinary epId $ get epId
Exam{..} <- MaybeT . $cachedHereBinary examPartExam $ get examPartExam
now <- liftIO getCurrentTime
guard $ NTop (Just now) >= NTop examFinished
return Authorized
r -> $unsupportedAuthPredicate AuthExamTime r
tagAccessPredicate AuthCourseRegistered = cacheAPDB' (Just $ Right diffMinute) mkAuthCacheCourseRegisteredList $ \mAuthId' route' _ mCourseRegisteredList -> if
| Just courseRegisteredList <- mCourseRegisteredList
, maybe True (`Set.notMember` courseRegisteredList) mAuthId' -> Right $ case route' of
_ | is _Nothing mAuthId' -> return AuthenticationRequired
CourseR{} -> unauthorizedI MsgUnauthorizedRegistered
r -> $unsupportedAuthPredicate AuthCourseRegistered r
| otherwise -> Left $ APDB $ \_ _ mAuthId route _ -> case route of
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val authId
E.&&. courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
r -> $unsupportedAuthPredicate AuthCourseRegistered r
where
mkAuthCacheCourseRegisteredList _ route _ = case route of
CourseR tid ssh csh _ -> Just
( AuthCacheCourseRegisteredList tid ssh csh
, fmap (setOf $ folded . _Value) . E.select . E.from $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
E.where_ $ courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
return $ courseParticipant E.^. CourseParticipantUser
)
_other -> Nothing
tagAccessPredicate AuthTutorialRegistered = APDB $ \_ _ mAuthId route _ -> case route of
CTutorialR tid ssh csh tutn _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh, tutn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. tutorial E.^. TutorialName E.==. E.val tutn
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isRegistered <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialParticipant) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialParticipant E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialParticipant E.^. TutorialParticipantUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT isRegistered (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
r -> $unsupportedAuthPredicate AuthTutorialRegistered r
tagAccessPredicate AuthExamOccurrenceRegistration = APDB $ \_ _ _ route _ -> case route of
CExamR tid ssh csh examn _ -> exceptT return return $ do
isOccurrenceRegistration <- $cachedHereBinary (tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam) -> do
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
E.&&. exam E.^. ExamOccurrenceRule E.==. E.val ExamRoomFifo
guardMExceptT isOccurrenceRegistration (unauthorizedI MsgUnauthorizedExamOccurrenceRegistration)
return Authorized
r -> $unsupportedAuthPredicate AuthExamOccurrenceRegistration r
tagAccessPredicate AuthExamOccurrenceRegistered = APDB $ \_ _ mAuthId route _ -> case route of
CExamR tid ssh csh examn (ERegisterOccR occn) -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn, occn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration `E.InnerJoin` examOccurrence) -> do
E.on $ E.just (examOccurrence E.^. ExamOccurrenceId) E.==. examRegistration E.^. ExamRegistrationOccurrence
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
E.&&. examOccurrence E.^. ExamOccurrenceName E.==. E.val occn
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
CExamR tid ssh csh examn _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
E.&&. E.not_ (E.isNothing $ examRegistration E.^. ExamRegistrationOccurrence)
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. E.not_ (E.isNothing $ examRegistration E.^. ExamRegistrationOccurrence)
guardMExceptT hasRegistration (unauthorizedI MsgUnauthorizedRegistered)
return Authorized
r -> $unsupportedAuthPredicate AuthExamOccurrenceRegistered r
tagAccessPredicate AuthExamRegistered = APDB $ \_ _ mAuthId route _ -> case route of
CExamR tid ssh csh examn _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
guardMExceptT hasRegistration $ unauthorizedI MsgUnauthorizedRegisteredExam
return Authorized
CSheetR tid ssh csh shn _ -> exceptT return return $ do
requiredExam' <- $cachedHereBinary (tid, ssh, csh, shn) . lift . E.selectMaybe . E.from $ \(course `E.InnerJoin` sheet) -> do
E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId
E.where_ $ course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. sheet E.^. SheetName E.==. E.val shn
return $ sheet E.^. SheetRequireExamRegistration
requiredExam <- maybeMExceptT (unauthorizedI MsgUnauthorizedRegisteredExam) . return $ E.unValue <$> requiredExam'
whenIsJust requiredExam $ \eId -> do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
isRegistered <- $cachedHereBinary (authId, eId) . lift . E.selectExists . E.from $ \examRegistration ->
E.where_ $ examRegistration E.^. ExamRegistrationExam E.==. E.val eId
E.&&. examRegistration E.^. ExamRegistrationUser E.==. E.val authId
guardMExceptT isRegistered $ unauthorizedI MsgUnauthorizedRegisteredExam
return Authorized
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasRegistration <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
E.on $ exam E.^. ExamId E.==. examRegistration E.^. ExamRegistrationExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT hasRegistration $ unauthorizedI MsgUnauthorizedRegisteredAnyExam
return Authorized
r -> $unsupportedAuthPredicate AuthExamRegistered r
tagAccessPredicate AuthExamResult = APDB $ \_ _ mAuthId route _ -> case route of
CExamR tid ssh csh examn _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasResult <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examResult) -> do
E.on $ exam E.^. ExamId E.==. examResult E.^. ExamResultExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examResult E.^. ExamResultUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
hasPartResult <- $cachedHereBinary (authId, tid, ssh, csh, examn) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examPart `E.InnerJoin` examPartResult) -> do
E.on $ examPartResult E.^. ExamPartResultExamPart E.==. examPart E.^. ExamPartId
E.on $ exam E.^. ExamId E.==. examPart E.^. ExamPartExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
E.&&. exam E.^. ExamName E.==. E.val examn
guardMExceptT (hasResult || hasPartResult) (unauthorizedI MsgUnauthorizedExamResult)
return Authorized
EExamR tid ssh coursen examn _ -> $cachedHereBinary (mAuthId, tid, ssh, coursen, examn) . exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasResult <- $cachedHereBinary (authId, tid, ssh, coursen, examn) . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamResult) -> do
E.on $ eexam E.^. ExternalExamId E.==. eexamResult E.^. ExternalExamResultExam
E.where_ $ eexamResult E.^. ExternalExamResultUser E.==. E.val authId
E.&&. eexam E.^. ExternalExamTerm E.==. E.val tid
E.&&. eexam E.^. ExternalExamSchool E.==. E.val ssh
E.&&. eexam E.^. ExternalExamCourseName E.==. E.val coursen
E.&&. eexam E.^. ExternalExamExamName E.==. E.val examn
guardMExceptT hasResult $ unauthorizedI MsgUnauthorizedExternalExamResult
return Authorized
CourseR tid ssh csh _ -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasResult <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examResult) -> do
E.on $ exam E.^. ExamId E.==. examResult E.^. ExamResultExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examResult E.^. ExamResultUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
hasPartResult <- $cachedHereBinary (authId, tid, ssh, csh) . lift . E.selectExists . E.from $ \(course `E.InnerJoin` exam `E.InnerJoin` examPart `E.InnerJoin` examPartResult) -> do
E.on $ examPartResult E.^. ExamPartResultExamPart E.==. examPart E.^. ExamPartId
E.on $ exam E.^. ExamId E.==. examPart E.^. ExamPartExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examPartResult E.^. ExamPartResultUser E.==. E.val authId
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
guardMExceptT (hasResult || hasPartResult) (unauthorizedI MsgUnauthorizedExamResult)
return Authorized
r -> $unsupportedAuthPredicate AuthExamRegistered r
tagAccessPredicate AuthAllocationRegistered = APDB $ \_ _ mAuthId route _ -> case route of
AllocationR tid ssh ash _ -> maybeT (unauthorizedI MsgUnauthorizedAllocationRegistered) $ do
uid <- hoistMaybe mAuthId
aId <- MaybeT . $cachedHereBinary (tid, ssh, ash) . getKeyBy $ TermSchoolAllocationShort tid ssh ash
void . MaybeT . $cachedHereBinary (uid, aId) . getKeyBy $ UniqueAllocationUser aId uid
return Authorized
r -> $unsupportedAuthPredicate AuthAllocationRegistered r
tagAccessPredicate AuthParticipant = APDB $ \_ _ mAuthId route _ -> case route of
CNewsR tid ssh csh cID _ -> maybeT (unauthorizedI MsgUnauthorizedParticipantSelf) $ do
nId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
CourseNews{courseNewsParticipantsOnly} <- $cachedHereBinary nId . MaybeT $ get nId
if | courseNewsParticipantsOnly -> do
uid <- hoistMaybe mAuthId
exceptT return (const mzero) . hoist lift $ isCourseParticipant tid ssh csh uid True
| otherwise
-> return Authorized
CourseR tid ssh csh (CUserR cID) -> exceptT return return $ do
participant <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedParticipant) (const True :: CryptoIDError -> Bool) $ decrypt cID
isCourseParticipant tid ssh csh participant False
unauthorizedI MsgUnauthorizedParticipant
r -> $unsupportedAuthPredicate AuthParticipant r
where
isCourseParticipant tid ssh csh participant onlyActive = do
let
authorizedIfExists :: E.From a => (a -> E.SqlQuery b) -> ExceptT AuthResult (ReaderT SqlReadBackend (HandlerFor UniWorX)) ()
authorizedIfExists = flip whenExceptT Authorized <=< lift . E.selectExists . E.from
-- participant is currently registered
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` courseParticipant) -> do
E.on $ course E.^. CourseId E.==. courseParticipant E.^. CourseParticipantCourse
E.where_ $ courseParticipant E.^. CourseParticipantUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
when onlyActive $
E.where_ $ courseParticipant E.^. CourseParticipantState E.==. E.val CourseParticipantActive
-- participant has at least one submission
unless onlyActive $
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` submission `E.InnerJoin` submissionUser) -> do
E.on $ submission E.^. SubmissionId E.==. submissionUser E.^. SubmissionUserSubmission
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.where_ $ submissionUser E.^. SubmissionUserUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is member of a submissionGroup
unless onlyActive $
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` submissionGroup `E.InnerJoin` submissionGroupUser) -> do
E.on $ submissionGroup E.^. SubmissionGroupId E.==. submissionGroupUser E.^. SubmissionGroupUserSubmissionGroup
E.on $ course E.^. CourseId E.==. submissionGroup E.^. SubmissionGroupCourse
E.where_ $ submissionGroupUser E.^. SubmissionGroupUserUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is a sheet corrector
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` sheet `E.InnerJoin` sheetCorrector) -> do
E.on $ sheet E.^. SheetId E.==. sheetCorrector E.^. SheetCorrectorSheet
E.on $ course E.^. CourseId E.==. sheet E.^. SheetCourse
E.where_ $ sheetCorrector E.^. SheetCorrectorUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is a tutorial user
unless onlyActive $
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutorialUser) -> do
E.on $ tutorial E.^. TutorialId E.==. tutorialUser E.^. TutorialParticipantTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutorialUser E.^. TutorialParticipantUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is tutor for this course
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` tutorial `E.InnerJoin` tutor) -> do
E.on $ tutorial E.^. TutorialId E.==. tutor E.^. TutorTutorial
E.on $ course E.^. CourseId E.==. tutorial E.^. TutorialCourse
E.where_ $ tutor E.^. TutorUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is exam corrector for this course
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` exam `E.InnerJoin` examCorrector) -> do
E.on $ exam E.^. ExamId E.==. examCorrector E.^. ExamCorrectorExam
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examCorrector E.^. ExamCorrectorUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is lecturer for this course
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` lecturer) -> do
E.on $ course E.^. CourseId E.==. lecturer E.^. LecturerCourse
E.where_ $ lecturer E.^. LecturerUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant has an exam result for this course
unless onlyActive $
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` exam `E.InnerJoin` examResult) -> do
E.on $ examResult E.^. ExamResultExam E.==. exam E.^. ExamId
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examResult E.^. ExamResultUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
-- participant is registered for an exam for this course
unless onlyActive $
mapExceptT ($cachedHereBinary (participant, tid, ssh, csh)) . authorizedIfExists $ \(course `E.InnerJoin` exam `E.InnerJoin` examRegistration) -> do
E.on $ examRegistration E.^. ExamRegistrationExam E.==. exam E.^. ExamId
E.on $ course E.^. CourseId E.==. exam E.^. ExamCourse
E.where_ $ examRegistration E.^. ExamRegistrationUser E.==. E.val participant
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
tagAccessPredicate AuthApplicant = APDB $ \_ _ mAuthId route _ -> case route of
CourseR tid ssh csh (CUserR cID) -> maybeT (unauthorizedI MsgUnauthorizedApplicant) $ do
uid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
isApplicant <- isCourseApplicant tid ssh csh uid
guard isApplicant
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnauthorizedApplicantSelf) $ do
uid <- hoistMaybe mAuthId
isApplicant <- isCourseApplicant tid ssh csh uid
guard isApplicant
return Authorized
r -> $unsupportedAuthPredicate AuthApplicant r
where
isCourseApplicant tid ssh csh uid = lift . $cachedHereBinary (uid, tid, ssh, csh) . E.selectExists . E.from $ \(course `E.InnerJoin` courseApplication) -> do
E.on $ course E.^. CourseId E.==. courseApplication E.^. CourseApplicationCourse
E.where_ $ courseApplication E.^. CourseApplicationUser E.==. E.val uid
E.&&. course E.^. CourseTerm E.==. E.val tid
E.&&. course E.^. CourseSchool E.==. E.val ssh
E.&&. course E.^. CourseShorthand E.==. E.val csh
tagAccessPredicate AuthCapacity = APDB $ \_ _ _ route _ -> case route of
CExamR tid ssh csh examn (ERegisterOccR occn) -> maybeT (unauthorizedI MsgExamOccurrenceNoCapacity) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
eid <- $cachedHereBinary (cid, examn) . MaybeT . getKeyBy $ UniqueExam cid examn
Entity occId ExamOccurrence{..} <- $cachedHereBinary (eid, occn) . MaybeT . getBy $ UniqueExamOccurrence eid occn
-- Nothing means unlimited size
whenIsJust examOccurrenceCapacity $ \capacity -> do
registered <- $cachedHereBinary occId . lift $ fromIntegral <$> count [ ExamRegistrationOccurrence ==. Just occId, ExamRegistrationExam ==. eid ]
guard $ capacity > registered
return Authorized
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgTutorialNoCapacity) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity tutId Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
registered <- $cachedHereBinary tutId . lift $ count [ TutorialParticipantTutorial ==. tutId ]
guard $ NTop tutorialCapacity > NTop (Just registered)
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNoCapacity) $ do
Entity cid Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
registered <- $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive ]
guard $ NTop courseCapacity > NTop (Just registered)
return Authorized
r -> $unsupportedAuthPredicate AuthCapacity r
tagAccessPredicate AuthRegisterGroup = APDB $ \_ _ mAuthId route _ -> case route of
CTutorialR tid ssh csh tutn _ -> maybeT (unauthorizedI MsgUnauthorizedTutorialRegisterGroup) $ do
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
Entity _ Tutorial{..} <- $cachedHereBinary (cid, tutn) . MaybeT . getBy $ UniqueTutorial cid tutn
case (tutorialRegGroup, mAuthId) of
(Nothing, _) -> return Authorized
(_, Nothing) -> return AuthenticationRequired
(Just rGroup, Just uid) -> do
hasOther <- $cachedHereBinary (uid, rGroup) . lift . E.selectExists . E.from $ \(tutorial `E.InnerJoin` participant) -> do
E.on $ tutorial E.^. TutorialId E.==. participant E.^. TutorialParticipantTutorial
E.&&. tutorial E.^. TutorialCourse E.==. E.val tutorialCourse
E.&&. tutorial E.^. TutorialRegGroup E.==. E.just (E.val rGroup)
E.&&. participant E.^. TutorialParticipantUser E.==. E.val uid
guard $ not hasOther
return Authorized
r -> $unsupportedAuthPredicate AuthRegisterGroup r
tagAccessPredicate AuthEmpty = APDB $ \_ _ mAuthId route _ -> case route of
EExamListR -> exceptT return return $ do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
hasExternalExams <- $cachedHereBinary authId . lift . E.selectExists . E.from $ \(eexam `E.InnerJoin` eexamStaff) -> do
E.on $ eexam E.^. ExternalExamId E.==. eexamStaff E.^. ExternalExamStaffExam
E.where_ $ eexamStaff E.^. ExternalExamStaffUser E.==. E.val authId
E.||. E.exists (E.from $ \externalExamResult ->
E.where_ $ externalExamResult E.^. ExternalExamResultExam E.==. eexam E.^. ExternalExamId
E.&&. externalExamResult E.^. ExternalExamResultUser E.==. E.val authId
)
guardMExceptT (not hasExternalExams) $ unauthorizedI MsgUnauthorizedExternalExamListNotEmpty
return Authorized
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgCourseNotEmpty) $ do
-- Entity cid Course{..} <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
cid <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh
assertM_ (<= 0) . $cachedHereBinary cid . lift $ count [ CourseParticipantCourse ==. cid ]
assertM_ not . $cachedHereBinary cid . lift $ E.selectExists . E.from $ \(sheet `E.InnerJoin` submission) -> do
E.on $ sheet E.^. SheetId E.==. submission E.^. SubmissionSheet
E.where_ $ sheet E.^. SheetCourse E.==. E.val cid
return Authorized
r -> $unsupportedAuthPredicate AuthEmpty r
tagAccessPredicate AuthMaterials = APDB $ \_ _ _ route _ -> case route of
CourseR tid ssh csh _ -> maybeT (unauthorizedI MsgUnfreeMaterials) $ do
Entity _ Course{..} <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
guard courseMaterialFree
return Authorized
r -> $unsupportedAuthPredicate AuthMaterials r
tagAccessPredicate AuthOwner = APDB $ \_ _ mAuthId route _ -> case route of
CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary (mAuthId, cID) . exceptT return return $ do
sid <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSubmissionOwner) (const True :: CryptoIDError -> Bool) $ decrypt cID
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
void . maybeMExceptT (unauthorizedI MsgUnauthorizedSubmissionOwner) . getBy $ UniqueSubmissionUser authId sid
return Authorized
r -> $unsupportedAuthPredicate AuthOwner r
tagAccessPredicate AuthPersonalisedSheetFiles = APDB $ \_ _ mAuthId route _ -> case route of
CSheetR tid ssh csh shn _ -> $cachedHereBinary (mAuthId, tid, ssh, csh, shn) . exceptT return return $ do
Entity shId Sheet{..} <- maybeTMExceptT (unauthorizedI MsgUnauthorizedSubmissionPersonalisedSheetFiles) $ do
cid <- MaybeT . $cachedHereBinary (tid, ssh, csh) . getKeyBy $ TermSchoolCourseShort tid ssh csh
MaybeT . $cachedHereBinary (cid, shn) . getBy $ CourseSheet cid shn
if | sheetAllowNonPersonalisedSubmission -> return Authorized
| otherwise -> do
authId <- maybeExceptT AuthenticationRequired $ return mAuthId
flip guardMExceptT (unauthorizedI MsgUnauthorizedSubmissionPersonalisedSheetFiles) <=< $cachedHereBinary (shId, authId) . lift $
E.selectExists . E.from $ \psFile ->
E.where_ $ psFile E.^. PersonalisedSheetFileSheet E.==. E.val shId
E.&&. psFile E.^. PersonalisedSheetFileUser E.==. E.val authId
E.&&. E.not_ (E.isNothing $ psFile E.^. PersonalisedSheetFileContent) -- directories don't count
return Authorized
r -> $unsupportedAuthPredicate AuthPersonalisedSheetFiles r
tagAccessPredicate AuthRated = APDB $ \_ _ _ route _ -> case route of
CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary cID . maybeT (unauthorizedI MsgUnauthorizedSubmissionRated) $ do
sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
sub <- MaybeT $ get sid
guard $ submissionRatingDone sub
return Authorized
r -> $unsupportedAuthPredicate AuthRated r
tagAccessPredicate AuthUserSubmissions = APDB $ \_ _ _ route _ -> case route of
CSheetR tid ssh csh shn _ -> $cachedHereBinary (tid, ssh, csh, shn) . maybeT (unauthorizedI MsgUnauthorizedUserSubmission) $ do
Entity cid _ <- MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{ sheetSubmissionMode = SubmissionMode{..} } <- MaybeT . getBy $ CourseSheet cid shn
guard $ is _Just submissionModeUser
return Authorized
r -> $unsupportedAuthPredicate AuthUserSubmissions r
tagAccessPredicate AuthCorrectorSubmissions = APDB $ \_ _ _ route _ -> case route of
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectorSubmission) $ do
Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{ sheetSubmissionMode = SubmissionMode{..} } <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn
guard submissionModeCorrector
return Authorized
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
tagAccessPredicate AuthCorrectionAnonymous = APDB $ \_ _ _ route _ -> case route of
CSheetR tid ssh csh shn _ -> maybeT (unauthorizedI MsgUnauthorizedCorrectionAnonymous) $ do
Entity cid _ <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getBy $ TermSchoolCourseShort tid ssh csh
Entity _ Sheet{ sheetAnonymousCorrection } <- $cachedHereBinary (cid, shn) . MaybeT . getBy $ CourseSheet cid shn
guard sheetAnonymousCorrection
return Authorized
r -> $unsupportedAuthPredicate AuthCorrectionAnonymous r
tagAccessPredicate AuthSelf = APDB $ \_ _ mAuthId route _ -> exceptT return return $ do
referencedUser' <- case route of
AdminUserR cID -> return $ Left cID
AdminUserDeleteR cID -> return $ Left cID
AdminHijackUserR cID -> return $ Left cID
UserNotificationR cID -> return $ Left cID
UserPasswordR cID -> return $ Left cID
CourseR _ _ _ (CUserR cID) -> return $ Left cID
CApplicationR _ _ _ cID _ -> do
appId <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSelf) (const True :: CryptoIDError -> Bool) $ decrypt cID
CourseApplication{..} <- maybeMExceptT (unauthorizedI MsgUnauthorizedSelf) . $cachedHereBinary appId $ get appId
return $ Right courseApplicationUser
_other -> throwError =<< $unsupportedAuthPredicate AuthSelf route
referencedUser <- case referencedUser' of
Right uid -> return uid
Left cID -> catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSelf) (const True :: CryptoIDError -> Bool) $ decrypt cID
case mAuthId of
Just uid
| uid == referencedUser -> return Authorized
Nothing -> return AuthenticationRequired
_other -> unauthorizedI MsgUnauthorizedSelf
tagAccessPredicate AuthIsLDAP = APDB $ \_ _ _ route _ -> exceptT return return $ do
referencedUser <- case route of
AdminUserR cID -> return cID
AdminUserDeleteR cID -> return cID
AdminHijackUserR cID -> return cID
UserNotificationR cID -> return cID
UserPasswordR cID -> return cID
CourseR _ _ _ (CUserR cID) -> return cID
_other -> throwError =<< $unsupportedAuthPredicate AuthIsLDAP route
referencedUser' <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSelf) (const True :: CryptoIDError -> Bool) $ decrypt referencedUser
maybeTMExceptT (unauthorizedI MsgUnauthorizedLDAP) $ do
User{..} <- MaybeT $ get referencedUser'
guard $ userAuthentication == AuthLDAP
return Authorized
tagAccessPredicate AuthIsPWHash = APDB $ \_ _ _ route _ -> exceptT return return $ do
referencedUser <- case route of
AdminUserR cID -> return cID
AdminUserDeleteR cID -> return cID
AdminHijackUserR cID -> return cID
UserNotificationR cID -> return cID
UserPasswordR cID -> return cID
CourseR _ _ _ (CUserR cID) -> return cID
_other -> throwError =<< $unsupportedAuthPredicate AuthIsPWHash route
referencedUser' <- catchIfMExceptT (const $ unauthorizedI MsgUnauthorizedSelf) (const True :: CryptoIDError -> Bool) $ decrypt referencedUser
maybeTMExceptT (unauthorizedI MsgUnauthorizedPWHash) $ do
User{..} <- MaybeT $ get referencedUser'
guard $ is _AuthPWHash userAuthentication
return Authorized
tagAccessPredicate AuthAuthentication = APDB $ \_ _ mAuthId route _ -> case route of
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
SystemMessage{..} <- $cachedHereBinary smId . MaybeT $ get smId
let isAuthenticated = isJust mAuthId
guard $ not systemMessageAuthenticatedOnly || isAuthenticated
return Authorized
MessageHideR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
smId <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID
SystemMessage{..} <- $cachedHereBinary smId . MaybeT $ get smId
let isAuthenticated = isJust mAuthId
guard $ not systemMessageAuthenticatedOnly || isAuthenticated
return Authorized
r -> $unsupportedAuthPredicate AuthAuthentication r
tagAccessPredicate AuthRead = APPure $ \_ _ isWrite -> do
MsgRenderer mr <- ask
return $ bool Authorized (Unauthorized $ mr MsgUnauthorizedWrite) isWrite
tagAccessPredicate AuthWrite = APPure $ \_ _ isWrite -> do
MsgRenderer mr <- ask
return $ bool (Unauthorized $ mr MsgUnauthorized) Authorized isWrite
runTACont :: forall m. MonadAP m
=> (forall m'. MonadAP m' => AuthTagsEval m')
-> AuthDNF -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m Bool
runTACont cont dnf mAuthId route isWrite = is _Authorized <$> evalWriterT (cont dnf mAuthId route isWrite)
authTagSpecificity :: AuthTag -> AuthTag -> Ordering
-- ^ Heuristic for which `AuthTag`s to evaluate first
authTagSpecificity = comparing $ NTop . flip findIndex eqClasses . elem
where
eqClasses :: [[AuthTag]]
-- ^ Constructors of `AuthTag` ordered (increasing) by execution order
eqClasses =
[ [ AuthFree, AuthDeprecated, AuthDevelopment ] -- Route wide
, [ AuthRead, AuthWrite, AuthToken ] -- Request wide
, [ AuthAdmin ] -- Site wide
, [ AuthLecturer, AuthCourseRegistered, AuthParticipant, AuthCourseTime, AuthTime, AuthMaterials, AuthUserSubmissions, AuthCorrectorSubmissions, AuthCapacity, AuthEmpty ] ++ [ AuthSelf, AuthNoEscalation ] ++ [ AuthAuthentication ] -- Course/User/SystemMessage wide
, [ AuthCorrector ] ++ [ AuthTutor ] ++ [ AuthTutorialRegistered, AuthRegisterGroup ] -- Tutorial/Material/Sheet wide
, [ AuthOwner, AuthRated ] -- Submission wide
]
defaultAuthDNF :: AuthDNF
defaultAuthDNF = predDNFVar AuthAdmin `predDNFOr` predDNFVar AuthToken
routeAuthTags :: Route UniWorX -> Either InvalidAuthTag AuthDNF
-- ^ DNF up to entailment, see `predDNFEntail`
routeAuthTags = fmap predDNFEntail . ofoldM parse defaultAuthDNF . routeAttrs
where
parse :: AuthDNF -> Text -> Either InvalidAuthTag AuthDNF
parse prev t = case fromNullable . Set.fromList =<< mapM fromPathPiece (Text.splitOn "AND" t) of
Just t' -> Right . predDNFOr prev . PredDNF $ Set.singleton t'
Nothing -> Left $ InvalidAuthTag t
broadenRoute :: AuthTag -> Route UniWorX -> Route UniWorX
broadenRoute aTag route = case (aTag, route) of
(AuthAdmin, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR
(AuthAdmin, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR
(AuthAdmin, SchoolR ssh _) -> SchoolR ssh SchoolEditR
(AuthAdmin, _) -> NewsR
(AuthStudent, _) -> NewsR
(AuthExamOffice, CExamR tid ssh csh examn _) -> CExamR tid ssh csh examn EShowR
(AuthExamOffice, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR
(AuthExamOffice, CourseR _ ssh _ _) -> SchoolR ssh SchoolEditR
(AuthExamOffice, SchoolR ssh _) -> SchoolR ssh SchoolEditR
(AuthExamOffice, _) -> NewsR
(AuthLecturer, CourseR tid ssh csh _) -> CourseR tid ssh csh CShowR
(AuthLecturer, AllocationR tid ssh ash _) -> AllocationR tid ssh ash AShowR
(AuthLecturer, EExamR tid ssh coursen examn _) -> EExamR tid ssh coursen examn EEShowR
(AuthLecturer, _) -> NewsR
_other -> route
evalAuthTags :: forall ctx m. (HasCallStack, Binary ctx, MonadAP m) => ctx -> AuthTagActive -> (forall m'. MonadAP m' => AuthTagsEval m') -> AuthTagsEval m
-- ^ `tell`s disabled predicates, identified as pivots
evalAuthTags ctx authActive@AuthTagActive{..} cont (map (Set.toList . toNullable) . Set.toList . dnfTerms -> authDNF') mAuthId route isWrite
= do
mr <- getMsgRenderer
let
contCtx = toStrict $ Binary.encode (ctx, authActive)
authVarSpecificity = authTagSpecificity `on` plVar
authDNF = sortBy (authVarSpecificity `on` maximumBy authVarSpecificity . impureNonNull) $ map (sortBy authVarSpecificity) authDNF'
authTagIsInactive = not . authTagIsActive
evalAuthTag :: AuthTag -> WriterT (Set AuthTag) m AuthResult
evalAuthTag authTag = lift . ($runCachedMemoT :: CachedMemoT (ctx, AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for5 memo (const evalAccessPred') ctx authTag mAuthId route'' isWrite
where
route'' = broadenRoute authTag route
evalAccessPred' authTag' mAuthId' route' isWrite' = lift $ do
$logDebugS "evalAccessPred" $ tshow (authTag', mAuthId', route', isWrite')
observeAuthTagEvaluation authTag' (classifyHandler route') $ do
res <- evalAccessPred (tagAccessPredicate authTag') contCtx cont mAuthId' route' isWrite'
return . (res, ) $ case res of
Authorized -> OutcomeAuthorized
Unauthorized _ -> OutcomeUnauthorized
AuthenticationRequired -> OutcomeAuthenticationRequired
evalAuthLiteral :: AuthLiteral -> WriterT (Set AuthTag) m AuthResult
evalAuthLiteral PLVariable{..} = evalAuthTag plVar
evalAuthLiteral PLNegated{..} = notAR mr plVar <$> evalAuthTag plVar
orAR', andAR' :: forall m'. Monad m' => m' AuthResult -> m' AuthResult -> m' AuthResult
orAR' = shortCircuitM (is _Authorized) (orAR mr)
andAR' = shortCircuitM (is _Unauthorized) (andAR mr)
evalDNF :: [[AuthLiteral]] -> WriterT (Set AuthTag) m AuthResult
evalDNF = maybe (return $ falseAR mr) (ofoldr1 orAR') . fromNullable . map evalConj
where
evalConj = maybe (return $ trueAR mr) (ofoldr1 andAR') . fromNullable . map evalAuthLiteral
$logDebugS "evalAuthTags" . tshow . (route, isWrite, ) $ map (map $ id &&& authTagIsActive . plVar) authDNF
result <- evalDNF $ filter (all $ authTagIsActive . plVar) authDNF
unless (is _Authorized result) . forM_ (filter (any $ authTagIsInactive . plVar) authDNF) $ \conj ->
whenM (allM conj (\aTag -> (return . not . authTagIsActive $ plVar aTag) `or2M` (not . is _Unauthorized <$> evalAuthLiteral aTag))) $ do
let pivots = filter (authTagIsInactive . plVar) conj
whenM (allM pivots $ fmap (is _Authorized) . evalAuthLiteral) $ do
let pivots' = plVar <$> pivots
$logDebugS "evalAuthTags" [st|Recording pivots: #{tshow pivots'}|]
tell $ Set.fromList pivots'
return result
evalAccessWithFor :: (HasCallStack, MonadThrow m, MonadAP m) => [(AuthTag, Bool)] -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m AuthResult
evalAccessWithFor assumptions mAuthId route isWrite = do
isSelf <- (== mAuthId) <$> liftHandler defaultMaybeAuthId
tagActive <- if
| isSelf -> fromMaybe def <$> lookupSessionJson SessionActiveAuthTags
| otherwise -> return . AuthTagActive $ const True
dnf <- throwLeft $ routeAuthTags route
let adjDNF = ala Endo foldMap (map ((=<<) . uncurry dnfAssumeValue) assumptions) . Just
evalAdj :: forall m'. MonadAP m' => AuthTagsEval m'
evalAdj (adjDNF -> dnf') mAuthId' route' isWrite' = case dnf' of
Nothing -> return Authorized
Just dnf'' -> evalAuthTags ('evalAccessWithFor, assumptions) tagActive evalAdj dnf'' mAuthId' route' isWrite'
in do
(result, deactivated) <- runWriterT $ evalAdj dnf mAuthId route isWrite
when isSelf $
tellSessionJson SessionInactiveAuthTags deactivated
return result
evalAccessFor :: (HasCallStack, MonadThrow m, MonadAP m) => Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m AuthResult
evalAccessFor = evalAccessWithFor []
evalAccessForDB :: (HasCallStack, MonadThrow m, MonadAP m, BackendCompatible SqlReadBackend backend) => Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> ReaderT backend m AuthResult
evalAccessForDB = evalAccessFor
evalAccessWith :: (HasCallStack, MonadThrow m, MonadAP m) => [(AuthTag, Bool)] -> Route UniWorX -> Bool -> m AuthResult
evalAccessWith assumptions route isWrite = do
mAuthId <- liftHandler maybeAuthId
evalAccessWithFor assumptions mAuthId route isWrite
evalAccessWithDB :: (HasCallStack, MonadThrow m, MonadAP m, BackendCompatible SqlReadBackend backend) => [(AuthTag, Bool)] -> Route UniWorX -> Bool -> ReaderT backend m AuthResult
evalAccessWithDB = evalAccessWith
evalAccess :: (HasCallStack, MonadThrow m, MonadAP m) => Route UniWorX -> Bool -> m AuthResult
evalAccess = evalAccessWith []
evalAccessDB :: (HasCallStack, MonadThrow m, MonadAP m, BackendCompatible SqlReadBackend backend) => Route UniWorX -> Bool -> ReaderT backend m AuthResult
evalAccessDB = evalAccess
-- | Check whether the current user is authorized by `evalAccess` for the given route
-- Convenience function for a commonly used code fragment
hasAccessTo :: (HasCallStack, MonadThrow m, MonadAP m) => Route UniWorX -> Bool -> m Bool
hasAccessTo route isWrite = (== Authorized) <$> evalAccess route isWrite
-- | Check whether the current user is authorized by `evalAccess` to read from the given route
-- Convenience function for a commonly used code fragment
hasReadAccessTo :: (HasCallStack, MonadThrow m, MonadAP m) => Route UniWorX -> m Bool
hasReadAccessTo = flip hasAccessTo False
-- | Check whether the current user is authorized by `evalAccess` to rwrite to the given route
-- Convenience function for a commonly used code fragment
hasWriteAccessTo :: (HasCallStack, MonadThrow m, MonadAP m) => Route UniWorX -> m Bool
hasWriteAccessTo = flip hasAccessTo True
wouldHaveAccessTo :: (HasCallStack, MonadThrow m, MonadAP m)
=> [(AuthTag, Bool)] -- ^ Assumptions
-> Route UniWorX
-> Bool
-> m Bool
wouldHaveAccessTo assumptions route isWrite = (== Authorized) <$> evalAccessWith assumptions route isWrite
wouldHaveReadAccessTo, wouldHaveWriteAccessTo
:: (HasCallStack, MonadThrow m, MonadAP m)
=> [(AuthTag, Bool)] -- ^ Assumptions
-> Route UniWorX
-> m Bool
wouldHaveReadAccessTo assumptions route = wouldHaveAccessTo assumptions route False
wouldHaveWriteAccessTo assumptions route = wouldHaveAccessTo assumptions route True
wouldHaveReadAccessToIff, wouldHaveWriteAccessToIff
:: (HasCallStack, MonadThrow m, MonadAP m)
=> [(AuthTag, Bool)] -- ^ Assumptions
-> Route UniWorX
-> m Bool
wouldHaveReadAccessToIff assumptions route = and2M (not <$> hasReadAccessTo route) $ wouldHaveReadAccessTo assumptions route
wouldHaveWriteAccessToIff assumptions route = and2M (not <$> hasWriteAccessTo route) $ wouldHaveWriteAccessTo assumptions route
authoritiveApproot :: Route UniWorX -> ApprootScope
authoritiveApproot = \case
CourseR _ _ _ (MaterialR _ (MFileR _)) -> ApprootUserGenerated
CourseR _ _ _ (MaterialR _ MArchiveR) -> ApprootUserGenerated
CourseR _ _ _ (SheetR _ (SFileR _ _)) -> ApprootUserGenerated
CourseR _ _ _ (SheetR _ (SZipR _)) -> ApprootUserGenerated
CourseR _ _ _ (SheetR _ (SubmissionR _ (SubDownloadR _ _))) -> ApprootUserGenerated
CourseR _ _ _ (SheetR _ (SubmissionR _ (SubArchiveR _))) -> ApprootUserGenerated
CourseR _ _ _ (CourseNewsR _ (CNFileR _)) -> ApprootUserGenerated
CourseR _ _ _ (CourseNewsR _ CNArchiveR) -> ApprootUserGenerated
CourseR _ _ _ CRegisterTemplateR -> ApprootUserGenerated
CourseR _ _ _ CAppsFilesR -> ApprootUserGenerated
CourseR _ _ _ (CourseApplicationR _ CAFilesR) -> ApprootUserGenerated
_other -> ApprootDefault