diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 28fe57b4c..0eaaa8972 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,13 +32,13 @@ npm install: before_script: &npm - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list - - apt-get update -y + - apt update -y - npm install -g n - n 13.5.0 - export PATH="${N_PREFIX}/bin:$PATH" - npm install -g npm - hash -r - - apt-get -y install openssh-client exiftool + - apt -y install openssh-client exiftool - install -v -m 0700 -d ~/.ssh - install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts - install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config; @@ -93,9 +93,11 @@ yesod:build:dev: before_script: &haskell - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list - - apt-get update -y - - apt-get install -y --no-install-recommends locales-all - - apt-get install openssh-client -y + - apt-key add ${LLVM_APT_KEY} + - apt update -y + - apt install -y --no-install-recommends locales-all openssh-client llvm-9 + - ln -vsf llc-9 /usr/bin/llc + - ln -vsf opt-9 /usr/bin/opt - install -v -m 0700 -d ~/.ssh - install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts - install -v -T -m 0400 ${SSH_DEPLOY_KEY} ~/.ssh/deploy && echo "IdentityFile ~/.ssh/deploy" >> ~/.ssh/config; @@ -143,13 +145,13 @@ frontend:test: before_script: - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list - - apt-get update -y + - apt update -y - npm install -g n - n 13.5.0 - export PATH="${N_PREFIX}/bin:$PATH" - npm install -g npm - hash -r - - apt-get install -y --no-install-recommends chromium-browser + - apt install -y --no-install-recommends chromium-browser dependencies: - npm install retry: 2 @@ -243,8 +245,8 @@ deploy:uniworx3: before_script: - rm -rvf /etc/apt/sources.list /etc/apt/sources.list.d - install -v -T -m 0644 ${APT_SOURCES_LIST} /etc/apt/sources.list - - apt-get update -y - - apt-get install -y --no-install-recommends openssh-client + - apt update -y + - apt install -y --no-install-recommends openssh-client - install -v -m 0700 -d ~/.ssh - install -v -T -m 0644 ${SSH_KNOWN_HOSTS} ~/.ssh/known_hosts - install -v -T -m 0400 ${SSH_PRIVATE_KEY_UNIWORX3} ~/.ssh/uniworx3; echo "IdentityFile ~/.ssh/uniworx3" >> ~/.ssh/config; diff --git a/CHANGELOG.md b/CHANGELOG.md index eb67e3710..a1f9bec66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,69 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [19.0.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.6.0...v19.0.0) (2020-08-15) + + +### refactor + +* split foundation & llvm ([c68a01d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c68a01d)) + + +### BREAKING CHANGES + +* split foundation + + + +## [18.6.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.5.0...v18.6.0) (2020-08-11) + + +### Bug Fixes + +* **personalised-sheet-files:** more thorough check wrt sub-warnings ([0b0eaff](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0b0eaff)) +* hlint ([5ea7816](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/5ea7816)) +* **course-visibility:** (more) correct visibility check for favourites ([796a806](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/796a806)) +* **course-visibility:** account for active auth tags everywhere ([c99433c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/c99433c)) +* **course-visibility:** allow access for admin-like roles ([7569195](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7569195)) +* **course-visibility:** allow deregistration from invisible courses ([29da6e2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/29da6e2)) +* **course-visibility:** allow for caching Nothing results of getBy ([f129ce6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f129ce6)) +* **course-visibility:** check for mayEdit on course list ([b1d0893](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/b1d0893)) +* **course-visibility:** correctly count courses on AllocationListR ([7530287](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7530287)) +* **course-visibility:** fix favourites ([1ac3c08](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1ac3c08)) +* **course-visibility:** rework routes ([7ce60a3](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7ce60a3)) +* **course-visibility:** show icon to lecturers only ([cbb8e72](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cbb8e72)) +* **course-visibility:** visibility for admin-like users ([43f625b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/43f625b)) + + +### Features + +* **course-visibility:** account for visibility in routes ([cb0bf15](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/cb0bf15)) +* **course-visibility:** account for visibility on AllocationListR ([4185742](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/4185742)) +* **course-visibility:** account for visibility on AShowR ([df7a784](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/df7a784)) +* **course-visibility:** account for visibility on TShowR ([0ff07a5](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/0ff07a5)) +* **course-visibility:** add invisible icon to CShowR title ([6c0adde](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6c0adde)) +* **course-visibility:** add visibleFrom,visibleTo ([222d566](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/222d566)) +* **course-visibility:** allow access for exam correctors ([dfa70ee](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/dfa70ee)) +* **course-visibility:** display icon in course list for lecturers ([17dbccf](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/17dbccf)) +* **course-visibility:** error on visibleFrom > visibleTo ([9494019](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9494019)) +* **course-visibility:** hide invisible courses from favourites + icon ([d86fed7](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/d86fed7)) +* **course-visibility:** more precise description on CShowR ([6fbb2ea](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/6fbb2ea)) +* **course-visibility:** no invisible courses in course list ([24f1289](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/24f1289)) +* **course-visibility:** now as default visibleFrom for new courses ([7bdf8ca](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7bdf8ca)) +* **course-visibility:** redirect to NewsR after deregister (WIP!) ([183aa8d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/183aa8d)) +* **course-visibility:** reorder course form ([7af82bc](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/7af82bc)) +* **course-visibility:** rework visibility check for ZA courses ([a16eb1a](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/a16eb1a)) +* **course-visibility:** warn on deregister from invisible course ([16ad72d](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/16ad72d)) +* **course-visibility:** warn on invisibility during registration ([23aca1c](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/23aca1c)) +* **personalised-sheet-files:** collated ignore ([1fe63a2](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/1fe63a2)) +* **personalised-sheet-files:** download from CUsersR ([93d0ace](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/93d0ace)) +* **personalised-sheet-files:** finish upload functionality ([ed5fb6e](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/ed5fb6e)) +* **personalised-sheet-files:** i18n ([f452b2b](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/f452b2b)) +* **personalised-sheet-files:** introduce routes & work on crypto ([9ee44aa](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/9ee44aa)) +* **personalised-sheet-files:** participant interaction ([db205f6](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/commit/db205f6)) + + + ## [18.5.0](https://gitlab2.rz.ifi.lmu.de/uni2work/uni2work/compare/v18.4.0...v18.5.0) (2020-08-03) diff --git a/load/Load.hs b/load/Load.hs index c168796c1..7c0020ca4 100644 --- a/load/Load.hs +++ b/load/Load.hs @@ -96,7 +96,7 @@ sampleIntegral = sampleN scaleIntegral instance PathPiece DiffTime where - toPathPiece = toPathPiece . MkFixed @E12 . diffTimeToPicoseconds + toPathPiece = (toPathPiece :: Pico -> Text) . MkFixed . diffTimeToPicoseconds fromPathPiece t = fromPathPiece t <&> \(MkFixed ps :: Pico) -> picosecondsToDiffTime ps diff --git a/package-lock.json b/package-lock.json index db0d4db57..f3ca829a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "18.5.0", + "version": "19.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c5e99919c..ada03dbe8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "uni2work", - "version": "18.5.0", + "version": "19.0.0", "description": "", "keywords": [], "author": "", diff --git a/package.yaml b/package.yaml index 5b9412b43..53d3b395e 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: uniworx -version: 18.5.0 +version: 19.0.0 dependencies: - base @@ -63,7 +63,6 @@ dependencies: - cryptoids-class - binary - binary-instances - - cereal - mtl - esqueleto >=3.1.0 - mime-types @@ -210,6 +209,8 @@ default-extensions: - TypeFamilyDependencies - QuantifiedConstraints - EmptyDataDeriving + - StandaloneKindSignatures + - NoStarIsType ghc-options: - -Wall @@ -229,42 +230,41 @@ when: ghc-options: - -Werror - -fwarn-tabs + - condition: flag(dev) + then: + ghc-options: + - -O0 + - -ddump-splices + - -ddump-to-file + cpp-options: -DDEVELOPMENT + ghc-prof-options: + - -fprof-auto + else: + ghc-options: + - -O -fllvm # The library contains all of our application code. The executable # defined below is just a thin wrapper. library: source-dirs: src - when: - - condition: flag(dev) - then: - ghc-options: - - -O0 - - -ddump-splices - - -ddump-to-file - cpp-options: -DDEVELOPMENT - ghc-prof-options: - - -fprof-auto - else: - ghc-options: - - -O2 # Runnable executable for our application executables: uniworx: main: main.hs source-dirs: app - ghc-options: -threaded -rtsopts "-with-rtsopts=-N -T" dependencies: - uniworx when: - condition: flag(library-only) buildable: false + ghc-options: + - -threaded -rtsopts "-with-rtsopts=-N -T -xn" uniworxdb: main: Database.hs ghc-options: - -main-is Database - - -threaded - - -rtsopts "-with-rtsopts=-N -T" + - -threaded -rtsopts "-with-rtsopts=-N -T -xn" source-dirs: test dependencies: - uniworx @@ -277,8 +277,7 @@ executables: main: Load.hs ghc-options: - -main-is Load - - -threaded - - -rtsopts "-with-rtsopts=-N -T" + - -threaded -rtsopts "-with-rtsopts=-N -T -xn" source-dirs: load dependencies: - uniworx @@ -312,9 +311,7 @@ tests: - yesod-persistent ghc-options: - -fno-warn-orphans - - -threaded - - -rtsopts - - -with-rtsopts=-N + - -threaded -rtsopts "-with-rtsopts=-N -T -xn" hlint: main: Hlint.hs other-modules: [] diff --git a/src/Application.hs b/src/Application.hs index 65bdf4ea1..490040eed 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -94,13 +94,15 @@ import Handler.Utils.Routes (classifyHandler) import qualified Data.Acid.Memory as Acid import qualified Web.ServerSession.Backend.Acid as Acid - + import qualified Ldap.Client as Ldap (Host(Plain, Tls)) import qualified Network.Minio as Minio - + import Web.ServerSession.Core (StorageException(..)) +import GHC.RTS.Flags (getRTSFlags) + -- Import all relevant handler modules here. -- (HPack takes care to add new modules to our cabal file nowadays.) import Handler.News @@ -140,7 +142,7 @@ mkYesodDispatch "UniWorX" resourcesUniWorX makeFoundation :: (MonadResource m, MonadUnliftIO m, MonadCatch m) => AppSettings -> m UniWorX makeFoundation appSettings'@AppSettings{..} = do registerGHCMetrics - + -- Some basic initializations: HTTP connection manager, logger, and static -- subsite. appHttpManager <- newManager @@ -200,6 +202,7 @@ makeFoundation appSettings'@AppSettings{..} = do runAppLoggingT tempFoundation $ do $logInfoS "InstanceID" $ UUID.toText appInstanceID $logDebugS "Configuration" $ tshow appSettings' + $logDebugS "RTSFlags" . tshow =<< liftIO getRTSFlags smtpPool <- for appSmtpConf $ \c -> do $logDebugS "setup" "SMTP-Pool" @@ -353,7 +356,7 @@ makeApplication foundation = liftIO $ makeMiddleware foundation <*> toWaiAppPlai makeMiddleware :: MonadIO m => UniWorX -> m Middleware makeMiddleware app = do logWare <- makeLogWare - return $ observeHTTPRequestLatency classifyHandler . logWare . normalizeCookies . defaultMiddlewaresNoLogging + return $ observeHTTPRequestLatency classifyHandler . logWare . normalizeCookies . defaultMiddlewaresNoLogging where makeLogWare = do logWareMap <- liftIO $ newTVarIO HashMap.empty @@ -388,7 +391,7 @@ makeMiddleware app = do respond $ Wai.mapResponseHeaders (const resHdrs') res where parseSetCookie' :: ByteString -> IO (Maybe SetCookie) parseSetCookie' = fmap (either (\(_ :: SomeException) -> Nothing) Just) . try . evaluate . force . parseSetCookie - + go [] = return [] go (hdr@(hdrName, hdrValue) : hdrs) | hdrName == hSetCookie = do @@ -455,7 +458,7 @@ warpSettings foundation = defaultSettings Just (SessionDoesNotExist{} :: StorageException (AcidStorage SessionMap)) -> False _other -> True ] - + getAppDevSettings, getAppSettings :: MonadIO m => m AppSettings getAppDevSettings = liftIO $ loadYamlSettings [configSettingsYml] [configSettingsYmlValue] useEnv @@ -476,7 +479,7 @@ develMain = runResourceT $ do lift $ threadDelay 100e3 whenM (lift $ doesFileExist "yesod-devel/devel-terminate") $ callCC ($ ()) - + void . liftIO $ installHandler sigINT (Signals.Catch $ return ()) Nothing runAppLoggingT foundation $ handleJobs foundation void . liftIO $ awaitTermination `race` runSettings wsettings app diff --git a/src/Audit.hs b/src/Audit.hs index fb52cb96d..6027f80ea 100644 --- a/src/Audit.hs +++ b/src/Audit.hs @@ -54,7 +54,7 @@ getRemote = handle testHandler $ do guard $ h `elem` ["x-real-ip", "x-forwarded-for"] v' <- either (const mzero) return $ Text.decodeUtf8' v maybeToList $ IP.decode v' - + byRemoteHost wai = case Wai.remoteHost wai of Wai.SockAddrInet _ hAddr -> let (b1, b2, b3, b4) = Wai.hostAddressToTuple hAddr diff --git a/src/Audit/Types.hs b/src/Audit/Types.hs index 573a91af5..7b5757e94 100644 --- a/src/Audit/Types.hs +++ b/src/Audit/Types.hs @@ -23,7 +23,7 @@ data Transaction { transactionExam :: ExamId , transactionUser :: UserId } - + | TransactionExamPartResultEdit { transactionExamPart :: ExamPartId , transactionUser :: UserId @@ -88,7 +88,7 @@ data Transaction { transactionSubmission :: SubmissionId , transactionUser :: UserId } - + | TransactionSubmissionFileEdit { transactionSubmissionFile :: SubmissionFileId , transactionSubmission :: SubmissionId @@ -133,7 +133,7 @@ data Transaction { transactionExternalExam :: ExternalExamId , transactionSchool :: SchoolId } - + | TransactionExternalExamStaffEdit { transactionExternalExam :: ExternalExamId , transactionUser :: UserId diff --git a/src/Auth/Dummy.hs b/src/Auth/Dummy.hs index a1cd8ad3b..859b04554 100644 --- a/src/Auth/Dummy.hs +++ b/src/Auth/Dummy.hs @@ -45,7 +45,7 @@ dummyLogin = AuthPlugin{..} where apName :: Text apName = "dummy" - + apDispatch :: forall m. MonadAuthHandler site m => Text -> [Text] -> m TypedContent apDispatch method [] | encodeUtf8 method == methodPost = liftSubHandler $ do ((loginRes, _), _) <- runFormPost $ renderWForm FormStandard dummyForm @@ -62,7 +62,7 @@ dummyLogin = AuthPlugin{..} setCredsRedirect $ Creds apName (CI.original ident) [] apDispatch _ [] = badMethod apDispatch _ _ = notFound - + apLogin :: (Route Auth -> Route site) -> WidgetFor site () apLogin toMaster = do (login, loginEnctype) <- handlerToWidget . generateFormPost $ renderWForm FormStandard dummyForm diff --git a/src/Auth/LDAP.hs b/src/Auth/LDAP.hs index dac6bd1fd..9b57c8904 100644 --- a/src/Auth/LDAP.hs +++ b/src/Auth/LDAP.hs @@ -52,7 +52,7 @@ findUser conf@LdapConf{..} ldap ident retAttrs = fromMaybe [] <$> findM (assertM [ ldapUserDisplayName Ldap.:= Text.encodeUtf8 ident , ldapUserMatriculation Ldap.:= Text.encodeUtf8 ident ] - + findUserMatr :: LdapConf -> Ldap -> Text -> [Ldap.Attr] -> IO [Ldap.SearchEntry] findUserMatr conf@LdapConf{..} ldap userMatr retAttrs = fromMaybe [] <$> findM (assertM (not . null) . lift . flip (Ldap.search ldap ldapBase $ userSearchSettings conf) retAttrs) userFilters where @@ -76,8 +76,8 @@ ldapUserFirstName = Ldap.Attr "givenName" ldapUserSurname = Ldap.Attr "sn" ldapUserTitle = Ldap.Attr "title" ldapUserStudyFeatures = Ldap.Attr "dfnEduPersonFeaturesOfStudy" -ldapUserFieldName = Ldap.Attr "LMU-Stg-Fach" -ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString" +ldapUserFieldName = Ldap.Attr "LMU-Stg-Fach" +ldapUserSchoolAssociation = Ldap.Attr "LMU-IFI-eduPersonOrgUnitDNString" ldapSex = Ldap.Attr "schacGender" ldapUserSubTermsSemester = Ldap.Attr "LMU-Stg-FachundFS" @@ -145,7 +145,7 @@ campusUserMatr pool mode userMatr = either (throwM . CampusUserLdapError) return [] -> throwM CampusUserNoResult [Ldap.SearchEntry _ attrs] -> return attrs _otherwise -> throwM CampusUserAmbiguous - + campusUserMatr' :: (MonadMask m, MonadUnliftIO m, MonadLogger m) => Failover (LdapConf, LdapPool) -> FailoverMode -> UserMatriculation -> m (Maybe (Ldap.AttrList [])) campusUserMatr' pool mode = runMaybeT . catchIfMaybeT (is _CampusUserNoResult) . campusUserMatr pool mode @@ -177,7 +177,7 @@ campusLogin pool mode = AuthPlugin{..} where apName :: Text apName = apLdap - + apDispatch :: forall m. MonadAuthHandler site m => Text -> [Text] -> m TypedContent apDispatch method [] | encodeUtf8 method == methodPost = liftSubHandler $ do ((loginRes, _), _) <- runFormPost $ renderWForm FormStandard campusForm diff --git a/src/Control/Monad/Trans/Memo/StateCache/Instances.hs b/src/Control/Monad/Trans/Memo/StateCache/Instances.hs index e885eb655..5e5d6d977 100644 --- a/src/Control/Monad/Trans/Memo/StateCache/Instances.hs +++ b/src/Control/Monad/Trans/Memo/StateCache/Instances.hs @@ -16,7 +16,7 @@ instance MonadResource m => MonadResource (StateCache c m) where instance MonadLogger m => MonadLogger (StateCache c m) instance MonadLoggerIO m => MonadLoggerIO (StateCache c m) - + instance MonadHandler m => MonadHandler (StateCache c m) where type HandlerSite (StateCache c m) = HandlerSite m type SubHandlerSite (StateCache c m) = SubHandlerSite m diff --git a/src/Cron.hs b/src/Cron.hs index b448bf335..4697c4bf8 100644 --- a/src/Cron.hs +++ b/src/Cron.hs @@ -21,7 +21,7 @@ import qualified Data.Set as Set import Utils.Lens hiding (from, to) - + data CronDate = CronDate { cdYear, cdWeekYear, cdWeekOfYear, cdDayOfYear , cdMonth, cdWeekOfMonth, cdDayOfMonth @@ -101,7 +101,7 @@ instance Alternative CronNextMatch where _ <|> MatchAsap = MatchAsap MatchAsap <|> _ = MatchAsap (MatchAt a) <|> (MatchAt _) = MatchAt a - + listToMatch :: [a] -> CronNextMatch a listToMatch [] = MatchNone @@ -203,7 +203,7 @@ nextCronMatch tz mPrev prec now c@Cron{..} = onlyOnceWithinPrec $ case notAfter in case execRef now False cronInitial of MatchAsap | now < cutoffTime -> MatchAt cutoffTime - MatchAt ts + MatchAt ts | ts < cutoffTime -> MatchAt cutoffTime other -> other CronRepeatScheduled cronNext diff --git a/src/Cron/Types.hs b/src/Cron/Types.hs index 00eec5047..648f44449 100644 --- a/src/Cron/Types.hs +++ b/src/Cron/Types.hs @@ -11,12 +11,12 @@ import ClassyPrelude import Utils.Lens.TH -import Data.Time +import Data.Time import Numeric.Natural import qualified Data.Set as Set - + data CronMatch = CronMatchAny diff --git a/src/Crypto/Hash/Instances.hs b/src/Crypto/Hash/Instances.hs index 27304d542..93bf63516 100644 --- a/src/Crypto/Hash/Instances.hs +++ b/src/Crypto/Hash/Instances.hs @@ -26,7 +26,7 @@ instance HashAlgorithm hash => PersistField (Digest hash) where fromPersistValue _ = Left "Digest values must be converted from PersistByteString or PersistText" instance HashAlgorithm hash => PersistFieldSql (Digest hash) where - sqlType _ = SqlBlob + sqlType _ = SqlBlob instance HashAlgorithm hash => PathPiece (Digest hash) where toPathPiece = showToPathPiece diff --git a/src/CryptoID.hs b/src/CryptoID.hs index a53d59d57..8884fba25 100644 --- a/src/CryptoID.hs +++ b/src/CryptoID.hs @@ -38,7 +38,7 @@ encrypt :: forall plaintext ciphertext m. , Typeable ciphertext , PathPiece plaintext ) - => plaintext -> m (I.CryptoID ciphertext plaintext) + => plaintext -> m (I.CryptoID ciphertext plaintext) encrypt plain = $cachedHereBinary (toPathPiece plain) $ I.encrypt plain decrypt :: forall plaintext ciphertext m. @@ -47,7 +47,7 @@ decrypt :: forall plaintext ciphertext m. , Typeable plaintext , PathPiece ciphertext ) - => I.CryptoID ciphertext plaintext -> m plaintext + => I.CryptoID ciphertext plaintext -> m plaintext decrypt cipher = $cachedHereBinary (toPathPiece $ ciphertext cipher) $ I.decrypt cipher diff --git a/src/Data/CaseInsensitive/Instances.hs b/src/Data/CaseInsensitive/Instances.hs index 6596fe47e..512195097 100644 --- a/src/Data/CaseInsensitive/Instances.hs +++ b/src/Data/CaseInsensitive/Instances.hs @@ -38,7 +38,7 @@ instance PersistField (CI String) where toPersistValue ciText = PersistDbSpecific . Text.encodeUtf8 . pack $ CI.original ciText fromPersistValue (PersistDbSpecific bs) = Right . CI.mk . unpack $ Text.decodeUtf8 bs fromPersistValue x = Left . pack $ "Expected PersistDbSpecific, received: " ++ show x - + instance PersistFieldSql (CI Text) where sqlType _ = SqlOther "citext" @@ -77,8 +77,8 @@ instance ToWidget site a => ToWidget site (CI a) where instance RenderMessage site a => RenderMessage site (CI a) where renderMessage f ls msg = renderMessage f ls $ CI.original msg -instance Lift t => Lift (CI t) where - lift (CI.original -> orig) = [e|CI.mk $(lift orig)|] +instance (CI.FoldCase t, Lift t) => Lift (CI t) where + liftTyped (CI.original -> orig) = [||CI.mk $$(liftTyped orig)||] instance (CI.FoldCase s, PathPiece s) => PathPiece (CI s) where diff --git a/src/Data/Fixed/Instances.hs b/src/Data/Fixed/Instances.hs index 7593400e3..fafcba383 100644 --- a/src/Data/Fixed/Instances.hs +++ b/src/Data/Fixed/Instances.hs @@ -16,7 +16,7 @@ import Data.Proxy (Proxy(..)) import Data.Scientific import Data.Scientific.Instances () - + instance HasResolution a => ToMarkup (Fixed a) where toMarkup = toMarkup . showFixed True diff --git a/src/Data/Maybe/Instances.hs b/src/Data/Maybe/Instances.hs index 4b6eaf9e8..28c0e3557 100644 --- a/src/Data/Maybe/Instances.hs +++ b/src/Data/Maybe/Instances.hs @@ -10,4 +10,4 @@ import Text.Blaze (ToMarkup(..), string) instance ToMarkup a => ToMarkup (Maybe a) where toMarkup Nothing = string "" - toMarkup (Just x) = toMarkup x \ No newline at end of file + toMarkup (Just x) = toMarkup x diff --git a/src/Data/MonoTraversable/Instances.hs b/src/Data/MonoTraversable/Instances.hs index 13405c291..dcf89bd63 100644 --- a/src/Data/MonoTraversable/Instances.hs +++ b/src/Data/MonoTraversable/Instances.hs @@ -19,7 +19,7 @@ instance MonoFunctor All where instance MonoPointed Any where opoint = Any - + instance MonoPointed All where opoint = All diff --git a/src/Data/Scientific/Instances.hs b/src/Data/Scientific/Instances.hs index 85c46f844..cee91482d 100644 --- a/src/Data/Scientific/Instances.hs +++ b/src/Data/Scientific/Instances.hs @@ -11,5 +11,5 @@ import Web.PathPieces instance PathPiece Scientific where - toPathPiece = pack . formatScientific Fixed Nothing + toPathPiece = pack . formatScientific Fixed Nothing fromPathPiece = readFromPathPiece diff --git a/src/Data/Sum/Instances.hs b/src/Data/Sum/Instances.hs index 81c99f393..2b92dfcad 100644 --- a/src/Data/Sum/Instances.hs +++ b/src/Data/Sum/Instances.hs @@ -10,4 +10,4 @@ import Data.Monoid (Sum(..)) import Text.Blaze (ToMarkup(..)) instance ToMarkup a => ToMarkup (Sum a) where - toMarkup = toMarkup . getSum \ No newline at end of file + toMarkup = toMarkup . getSum diff --git a/src/Data/UUID/Instances.hs b/src/Data/UUID/Instances.hs index 38b20d104..c75d33ee9 100644 --- a/src/Data/UUID/Instances.hs +++ b/src/Data/UUID/Instances.hs @@ -10,7 +10,7 @@ import qualified Data.UUID as UUID import Database.Persist.Sql import Text.Blaze (ToMarkup(..)) - + instance PathPiece UUID where fromPathPiece = UUID.fromString . unpack diff --git a/src/Data/Universe/Instances/Reverse/MonoTraversable.hs b/src/Data/Universe/Instances/Reverse/MonoTraversable.hs index aaa50ca73..a9153690b 100644 --- a/src/Data/Universe/Instances/Reverse/MonoTraversable.hs +++ b/src/Data/Universe/Instances/Reverse/MonoTraversable.hs @@ -7,11 +7,11 @@ module Data.Universe.Instances.Reverse.MonoTraversable import Data.Universe import Data.MonoTraversable -import Data.Universe.Instances.Reverse - +import Data.Universe.Instances.Reverse + type instance Element (a -> b) = b instance Finite a => MonoFoldable (a -> b) instance (Ord a, Finite a) => MonoTraversable (a -> b) - + diff --git a/src/Data/Universe/TH.hs b/src/Data/Universe/TH.hs index 192182320..03250be58 100644 --- a/src/Data/Universe/TH.hs +++ b/src/Data/Universe/TH.hs @@ -23,7 +23,7 @@ import Data.List (elemIndex) getTVBName :: TyVarBndr -> Name getTVBName (PlainTV name ) = name getTVBName (KindedTV name _) = name - + finiteEnum :: Name -> DecsQ @@ -33,7 +33,7 @@ finiteEnum tName = do let datatype = foldl appT (conT datatypeName) $ map (varT . getTVBName) datatypeVars tUniverse = [e|universeF :: [$(datatype)]|] - + [d| instance Bounded $(datatype) where minBound = head $(tUniverse) diff --git a/src/Database/Esqueleto/Utils.hs b/src/Database/Esqueleto/Utils.hs index cf6a40a4f..b31708c48 100644 --- a/src/Database/Esqueleto/Utils.hs +++ b/src/Database/Esqueleto/Utils.hs @@ -196,7 +196,7 @@ orderByList :: PersistField a => [a] -> E.SqlExpr (E.Value a) -> E.SqlExpr (E.Va orderByList vals = let sortUni = zip [1..] vals -- memoize this, might not work due to polymorphism in \x -> E.case_ [ (x E.==. E.val u, E.val i) | (i,u) <- sortUni ] (E.val . succ $ List.length vals) - + orderByOrd :: (Ord a, Finite a, PersistField a) => E.SqlExpr (E.Value a) -> E.SqlExpr (E.Value Int) orderByOrd = orderByList $ List.sort universeF @@ -206,12 +206,12 @@ orderByEnum = orderByList $ List.sortOn fromEnum universeF lower :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) lower = E.unsafeSqlFunction "LOWER" - + strip :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) strip = E.unsafeSqlFunction "TRIM" infix 4 `ciEq` - + ciEq :: E.SqlString s => E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value s) -> E.SqlExpr (E.Value Bool) ciEq a b = lower a E.==. lower b @@ -249,7 +249,7 @@ maybe onNothing onJust val = E.case_ (onJust $ E.veryUnsafeCoerceSqlExprValue val) ] (E.else_ onNothing) - + infix 4 `maybeEq` maybeEq :: PersistField a diff --git a/src/Database/Esqueleto/Utils/TH.hs b/src/Database/Esqueleto/Utils/TH.hs index b0c6a3699..988915aa0 100644 --- a/src/Database/Esqueleto/Utils/TH.hs +++ b/src/Database/Esqueleto/Utils/TH.hs @@ -46,7 +46,7 @@ sqlInTuple arity = do xsV <- newName "xs" let - matchE = lam1E (tupP $ map (\vV -> conP 'E.Value [varP vV]) vVs) (foldr1 (\e1 e2 -> [e|$(e1) E.&&. $(e2)|]) . map (\(varE -> vE, varE -> xE) -> [e|E.val $(vE) `sqlEq` $(xE)|]) $ zip vVs xVs) + matchE = lam1E (tupP $ map (\vV -> conP 'E.Value [varP vV]) vVs) (foldr1 (\e1 e2 -> [e|$(e1) E.&&. $(e2)|]) $ zipWith (\(varE -> vE) (varE -> xE) -> [e|E.val $(vE) `sqlEq` $(xE)|]) vVs xVs) tupTy f = foldl (\typ v -> typ `appT` f (varT v)) (tupleT arity) tyVars instanceD (cxt $ map (\v -> [t|SqlEq $(varT v)|]) tyVars) [t|SqlIn $(tupTy $ \v -> [t|E.SqlExpr (E.Value $(v))|]) $(tupTy $ \v -> [t|E.Value $(v)|])|] diff --git a/src/Database/Persist/Sql/Types/Instances.hs b/src/Database/Persist/Sql/Types/Instances.hs new file mode 100644 index 000000000..b7c33572b --- /dev/null +++ b/src/Database/Persist/Sql/Types/Instances.hs @@ -0,0 +1,22 @@ +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module Database.Persist.Sql.Types.Instances + ( + ) where + +import ClassyPrelude + +import Database.Persist.Sql + + +instance BackendCompatible SqlWriteBackend SqlWriteBackend where + projectBackend = id + +instance BackendCompatible SqlReadBackend SqlReadBackend where + projectBackend = id + +instance BackendCompatible SqlReadBackend SqlBackend where + projectBackend = SqlReadBackend + +instance BackendCompatible SqlWriteBackend SqlBackend where + projectBackend = SqlWriteBackend diff --git a/src/Database/Persist/TH/Directory.hs b/src/Database/Persist/TH/Directory.hs index 66966913c..f0395102b 100644 --- a/src/Database/Persist/TH/Directory.hs +++ b/src/Database/Persist/TH/Directory.hs @@ -18,13 +18,13 @@ import qualified System.Directory.Tree as DirTree import Control.Monad.Trans.Maybe (MaybeT(MaybeT), runMaybeT) import Control.Lens - + persistDirectoryWith :: PersistSettings -> FilePath -> Q Exp persistDirectoryWith settings dir = do files <- runIO . flip DirTree.readDirectoryWith dir $ \fp -> runMaybeT $ do fn <- MaybeT . return . fromNullable $ takeFileName fp - guard . not $ head fn == '.' + guard $ head fn /= '.' guard . not $ head fn == '#' && last fn == '#' lift $ do @@ -32,5 +32,5 @@ persistDirectoryWith settings dir = do SIO.hSetEncoding h SIO.utf8_bom Text.hGetContents h mapM_ qAddDependentFile . toListOf (traverse . filtered (has $ _2 . _Just) . _1) $ DirTree.zipPaths files - + parseReferences settings . Text.intercalate "\n" . toListOf (traverse . _Just) $ DirTree.dirTree files diff --git a/src/Foundation.hs b/src/Foundation.hs index f999ac9db..6a9988f6c 100644 --- a/src/Foundation.hs +++ b/src/Foundation.hs @@ -1,9 +1,3 @@ -{-# LANGUAGE UndecidableInstances #-} -{-# LANGUAGE GeneralizedNewtypeDeriving #-} -{-# LANGUAGE DuplicateRecordFields #-} -{-# LANGUAGE OverloadedLabels #-} -{-# OPTIONS_GHC -fno-warn-orphans -fno-warn-incomplete-uni-patterns -fno-warn-redundant-constraints #-} -- MonadCrypto - module Foundation ( module Foundation ) where @@ -12,5333 +6,9 @@ import Foundation.Type as Foundation import Foundation.Types as Foundation import Foundation.I18n as Foundation import Foundation.Routes as Foundation - - -import Import.NoFoundation hiding (embedFile) -import Database.Persist.Sql (runSqlPool) -import Text.Hamlet (hamletFile) - -import Yesod.Auth.Message -import Auth.LDAP -import Auth.PWHash -import Auth.Dummy - -import qualified Network.Wai as W -import qualified Network.HTTP.Types.Header as W -import qualified Network.Wai.Middleware.HttpAuth as W (extractBearerAuth) - -import qualified Yesod.Core.Unsafe as Unsafe -import qualified Data.CaseInsensitive as CI - -import Data.ByteArray (convert) -import Crypto.Hash (SHAKE256, SHAKE128) -import Crypto.Hash.Conduit (sinkHash) -import qualified Data.UUID as UUID -import qualified Data.Binary as Binary - -import qualified Data.ByteString.Base64.URL as Base64 (encode) - -import qualified Data.ByteString.Lazy as Lazy.ByteString -import qualified Data.ByteString as ByteString - -import qualified Data.Text as Text -import qualified Data.Text.Encoding as Text - -import qualified Data.Set as Set -import Data.Map ((!?)) -import qualified Data.Map as Map -import qualified Data.HashSet as HashSet -import qualified Data.HashMap.Strict as HashMap -import qualified Data.List.NonEmpty as NonEmpty - -import Data.List ((!!), findIndex, inits) -import qualified Data.List as List - -import Data.Conduit.List (sourceList) - -import qualified Database.Esqueleto as E -import qualified Database.Esqueleto.Utils as E - -import Control.Monad.Except (MonadError(..)) -import Control.Monad.Trans.State (execStateT) -import Control.Monad.Writer.Class (MonadWriter(..)) -import Control.Monad.Memo.Class (MonadMemo(..), for4) -import Control.Monad.Reader.Class (MonadReader(local)) -import qualified Control.Monad.Catch as C - -import Handler.Utils.StudyFeatures -import Handler.Utils.SchoolLdap -import Handler.Utils.ExamOffice.Exam -import Handler.Utils.ExamOffice.ExternalExam -import Handler.Utils.ExamOffice.Course -import Handler.Utils.Profile -import Handler.Utils.Routes -import Handler.Utils.Memcached -import Utils.Course (courseIsVisible) -import Utils.Form -import Utils.Sheet -import Utils.SystemMessage -import Utils.Metrics - -import Text.Cassius (cassiusFile) - -import qualified Yesod.Auth.Message as Auth - -import qualified Data.Conduit.List as C - -import qualified Database.Memcached.Binary.IO as Memcached -import Data.Bits (Bits(zeroBits)) - -import Network.Wai.Parse (lbsBackEnd) - -import qualified Data.Aeson as JSON -import Data.Aeson.Lens hiding (_Value, key) - -import Data.FileEmbed (embedFile) - -import qualified Ldap.Client as Ldap - -import UnliftIO.Pool - -import qualified Web.ServerSession.Core as ServerSession -import qualified Web.ServerSession.Frontend.Yesod.Jwt as JwtSession - -import Web.Cookie - -import Yesod.Core.Types (GHState(..), HandlerData(..), HandlerContents, RunHandlerEnv(rheSite, rheChild)) -import Database.Persist.Sql (transactionUndo, SqlReadBackend(..)) - -import qualified Control.Retry as Retry -import GHC.IO.Exception (IOErrorType(OtherError)) - --- | Convenient Type Synonyms: -type DB = YesodDB UniWorX -type Form x = Html -> MForm (HandlerFor UniWorX) (FormResult x, Widget) -type MsgRenderer = MsgRendererS UniWorX -- see Utils -type MailM a = MailT (HandlerFor UniWorX) a - --- Requires `rendeRoute`, thus cannot currently be moved to Foundation.I18n -instance RenderMessage UniWorX (UnsupportedAuthPredicate AuthTag (Route UniWorX)) where - renderMessage f ls (UnsupportedAuthPredicate tag route) = mr . MsgUnsupportedAuthPredicate (mr tag) $ Text.intercalate "/" pieces - where - mr :: forall msg. RenderMessage UniWorX msg => msg -> Text - mr = renderMessage f ls - (pieces, _) = renderRoute route - -data NavQuickView - = NavQuickViewFavourite - | NavQuickViewPageActionSecondary - deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) - deriving (Universe, Finite) - -navQuick :: NavQuickView -> (NavQuickView -> Any) -navQuick x x' = Any $ x == x' - -data NavType - = NavTypeLink - { navModal :: Bool - } - | NavTypeButton - { navMethod :: StdMethod - , navData :: [(Text, Text)] - } - deriving (Eq, Ord, Read, Show, Generic, Typeable) - deriving anyclass (Binary) - -makeLenses_ ''NavType -makePrisms ''NavType - -data NavLevel = NavLevelTop | NavLevelInner - deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) - -data NavHeaderRole = NavHeaderPrimary | NavHeaderSecondary - deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) - -data NavLink = forall msg route. (RenderMessage UniWorX msg, HasRoute UniWorX route, RedirectUrl UniWorX route) => NavLink - { navLabel :: msg - , navRoute :: route - , navAccess' :: Handler Bool - , navType :: NavType - , navQuick' :: NavQuickView -> Any - , navForceActive :: Bool - } - -makeLenses_ ''NavLink - -instance HasRoute UniWorX NavLink where - urlRoute NavLink{..} = urlRoute navRoute -instance RedirectUrl UniWorX NavLink where - toTextUrl NavLink{..} = toTextUrl navRoute -instance RenderMessage UniWorX NavLink where - renderMessage app ls NavLink{..} = renderMessage app ls navLabel - -data Nav - = NavHeader - { navHeaderRole :: NavHeaderRole - , navIcon :: Icon - , navLink :: NavLink - } - | NavHeaderContainer - { navHeaderRole :: NavHeaderRole - , navLabel :: SomeMessage UniWorX - , navIcon :: Icon - , navChildren :: [NavLink] - } - | NavPageActionPrimary - { navLink :: NavLink - , navChildren :: [NavLink] - } - | NavPageActionSecondary - { navLink :: NavLink - } - | NavFooter - { navLink :: NavLink - } deriving (Generic, Typeable) - -makeLenses_ ''Nav -makePrisms ''Nav - -data NavChildren -type instance Children NavChildren a = ChildrenNavChildren a -type family ChildrenNavChildren a where - ChildrenNavChildren (SomeMessage UniWorX) = '[] - - ChildrenNavChildren a = Children ChGeneric a - -navAccess :: (MonadHandler m, HandlerSite m ~ UniWorX, MonadCatch m) => Nav -> MaybeT m Nav -navAccess = execStateT $ do - guardM $ preuse _navLink >>= maybe (return True) navLinkAccess - - _navChildren <~ (filterM navLinkAccess =<< use _navChildren) - whenM (hasn't _navLink <$> use id) $ - guardM $ not . null <$> use _navChildren - -navLinkAccess :: forall m. (MonadHandler m, HandlerSite m ~ UniWorX, MonadCatch m) => NavLink -> m Bool -navLinkAccess NavLink{..} = handle shortCircuit $ liftHandler navAccess' `and2M` accessCheck navType navRoute - where - shortCircuit :: HandlerContents -> m Bool - shortCircuit _ = return False - - accessCheck :: HasRoute UniWorX route => NavType -> route -> m Bool - accessCheck nt (urlRoute -> route) = do - authCtx <- getAuthContext - $memcachedByHere (Just $ Right 120) (authCtx, nt, route) $ - bool hasWriteAccessTo hasReadAccessTo (is _NavTypeLink nt) route - - -getTimeLocale' :: [Lang] -> TimeLocale -getTimeLocale' = $(timeLocaleMap [("de-de", "de_DE.utf8"), ("en-GB", "en_GB.utf8")]) - -appTZ :: TZ -appTZ = $(includeSystemTZ "Europe/Berlin") - -appLanguagesOpts :: ( MonadHandler m - , HandlerSite m ~ UniWorX - ) => m (OptionList Lang) --- ^ Authoritive list of supported Languages -appLanguagesOpts = do - MsgRenderer mr <- getMsgRenderer - let mkOption l = Option - { optionDisplay = mr $ MsgLanguage l - , optionInternalValue = l - , optionExternalValue = l - } - langOptions = map mkOption $ toList appLanguages - return $ mkOptionList langOptions - -instance RenderMessage UniWorX WeekDay where - renderMessage _ ls wDay = pack . fst $ wDays (getTimeLocale' ls) !! (fromEnum wDay `mod` 7) - -newtype ShortWeekDay = ShortWeekDay { longWeekDay :: WeekDay } - -instance RenderMessage UniWorX ShortWeekDay where - renderMessage _ ls (ShortWeekDay wDay) = pack . snd $ wDays (getTimeLocale' ls) !! (fromEnum wDay `mod` 7) - -instance Default DateTimeFormatter where - def = mkDateTimeFormatter (getTimeLocale' []) def appTZ - - --- Access Control -newtype InvalidAuthTag = InvalidAuthTag Text - deriving (Eq, Ord, Show, Read, Generic, Typeable) -instance Exception InvalidAuthTag - - -data AccessPredicate - = APPure (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Reader MsgRenderer AuthResult) - | APHandler (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> Handler AuthResult) - | APDB (Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> ReaderT SqlReadBackend Handler AuthResult) - -class (MonadHandler m, HandlerSite m ~ UniWorX) => MonadAP m where - evalAccessPred :: AccessPredicate -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m AuthResult - -instance {-# INCOHERENT #-} (MonadHandler m, HandlerSite m ~ UniWorX) => MonadAP m where - evalAccessPred aPred aid r w = liftHandler $ case aPred of - (APPure p) -> runReader (p aid r w) <$> getMsgRenderer - (APHandler p) -> p aid r w - (APDB p) -> runDBRead $ p aid r w - -instance (MonadHandler m, HandlerSite m ~ UniWorX, BackendCompatible SqlBackend backend) => MonadAP (ReaderT backend m) where - evalAccessPred aPred aid r w = mapReaderT liftHandler . withReaderT (SqlReadBackend . projectBackend) $ case aPred of - (APPure p) -> lift $ runReader (p aid r w) <$> getMsgRenderer - (APHandler p) -> lift $ p aid r w - (APDB p) -> p aid r w - - -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 - -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 UserId - , authCtxBearer :: Maybe (BearerToken UniWorX) - , authActiveTags :: AuthTagActive - } deriving (Eq, Read, Show, Generic, Typeable) - deriving anyclass (Hashable, Binary) - -getAuthContext :: forall m. - ( MonadHandler m - , HandlerSite m ~ UniWorX - , MonadCatch m - ) - => m AuthContext -getAuthContext = do - authCtx <- AuthContext - <$> maybeAuthId - <*> runMaybeT (exceptTMaybe askBearerUnsafe) - <*> (fromMaybe def <$> lookupSessionJson SessionActiveAuthTags) - - $logDebugS "getAuthContext" $ tshow authCtx - - return authCtx - - -askBearerUnsafe :: forall m. - ( MonadHandler m - , HandlerSite m ~ UniWorX - , MonadCatch m - ) - => ExceptT AuthResult m (BearerToken UniWorX) --- | This performs /no/ meaningful validation of the `BearerToken` --- --- Use `requireBearerToken` or `maybeBearerToken` instead -askBearerUnsafe = $cachedHere $ 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 :: Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> BearerToken UniWorX -> ReaderT SqlReadBackend Handler 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 (ReaderT SqlReadBackend Handler) AuthResult - validateBearer' mAuthId route isWrite BearerToken{..} = lift . exceptT return return $ do - guardMExceptT (maybe True (HashSet.member route) bearerRoutes) (unauthorizedI MsgUnauthorizedTokenInvalidRoute) - - bearerAuthority' <- flip foldMapM bearerAuthority $ \case - Left tVal - | JSON.Success groupName <- JSON.fromJSON tVal -> maybeT (throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidAuthorityGroup) . hoist lift $ do - Entity _ UserGroupMember{..} <- MaybeT . getBy $ UniquePrimaryUserGroupMember groupName Active - return $ Set.singleton userGroupMemberUser - | otherwise -> throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidAuthorityValue - Right uid -> return $ Set.singleton uid - - let - -- Prevent infinite loops - noTokenAuth :: AuthDNF -> AuthDNF - noTokenAuth = over _dnfTerms . Set.filter . noneOf (re _nullable . folded) $ (== AuthToken) . plVar - - guardMExceptT (not $ Set.null bearerAuthority') $ unauthorizedI MsgUnauthorizedTokenInvalidNoAuthority - - forM_ bearerAuthority' $ \uid -> do - User{userTokensIssuedAfter} <- maybeMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidAuthority) $ get uid - guardMExceptT (Just bearerIssuedAt >= userTokensIssuedAfter) (unauthorizedI MsgUnauthorizedTokenExpired) - - authorityVal <- do - dnf <- either throwM return $ routeAuthTags route - fmap fst . runWriterT $ evalAuthTags (AuthTagActive $ const True) (noTokenAuth dnf) (Just uid) route isWrite - guardExceptT (is _Authorized authorityVal) authorityVal - - whenIsJust bearerAddAuth $ \addDNF -> do - $logDebugS "validateToken" $ tshow addDNF - additionalVal <- fmap fst . runWriterT $ evalAuthTags (AuthTagActive $ const True) (noTokenAuth addDNF) mAuthId route isWrite - guardExceptT (is _Authorized additionalVal) additionalVal - - return Authorized - -maybeBearerToken :: (MonadHandler m, HandlerSite m ~ UniWorX, MonadCatch m) => m (Maybe (BearerToken UniWorX)) -maybeBearerToken = runMaybeT $ catchIfMaybeT cPred requireBearerToken - where - cPred err = any ($ err) - [ is $ _HCError . _PermissionDenied - , is $ _HCError . _NotAuthenticated - ] - -requireBearerToken :: (MonadHandler m, HandlerSite m ~ UniWorX) => m (BearerToken UniWorX) -requireBearerToken = liftHandler $ do - bearer <- exceptT (guardAuthResult >=> error "askToken should not throw `Authorized`") return askBearerUnsafe - mAuthId <- maybeAuthId - currentRoute <- maybe (permissionDeniedI MsgUnauthorizedToken404) return =<< getCurrentRoute - isWrite <- isWriteRequest currentRoute - guardAuthResult <=< runDBRead $ validateBearer mAuthId currentRoute isWrite bearer - return bearer - -requireCurrentBearerRestrictions :: forall a m. - ( MonadHandler m - , HandlerSite m ~ UniWorX - , FromJSON a - , ToJSON a - ) - => m (Maybe a) -requireCurrentBearerRestrictions = runMaybeT $ do - bearer <- requireBearerToken - route <- MaybeT getCurrentRoute - hoistMaybe $ bearer ^? _bearerRestrictionIx route - -maybeCurrentBearerRestrictions :: forall a m. - ( MonadHandler m - , HandlerSite m ~ UniWorX - , MonadCatch m - , FromJSON a - , ToJSON a - ) - => m (Maybe a) -maybeCurrentBearerRestrictions = runMaybeT $ do - bearer <- MaybeT maybeBearerToken - route <- MaybeT getCurrentRoute - hoistMaybe $ bearer ^? _bearerRestrictionIx route - -isDryRun :: forall m. - ( MonadHandler m - , HandlerSite m ~ UniWorX - , MonadCatch m - ) - => m Bool -isDryRun = $cachedHere $ orM - [ hasGlobalPostParam PostDryRun - , hasGlobalGetParam GetDryRun - , and2M bearerDryRun bearerRequired - ] - where - bearerDryRun = has (_Just . _Object . ix "dry-run") <$> maybeCurrentBearerRestrictions @Value - bearerRequired = maybeT (return True) . catchIfMaybeT cPred . liftHandler $ do - mAuthId <- maybeAuthId - currentRoute <- maybe (permissionDeniedI MsgUnauthorizedToken404) return =<< getCurrentRoute - isWrite <- isWriteRequest currentRoute - - let noTokenAuth :: AuthDNF -> AuthDNF - noTokenAuth = over _dnfTerms . Set.filter . noneOf (re _nullable . folded) $ (== AuthToken) . plVar - - dnf <- either throwM return $ routeAuthTags currentRoute - guardAuthResult <=< fmap fst . runWriterT $ evalAuthTags (AuthTagActive $ const True) (noTokenAuth dnf) mAuthId currentRoute isWrite - - return False - - cPred err = any ($ err) - [ is $ _HCError . _PermissionDenied - , is $ _HCError . _NotAuthenticated - ] - - -tagAccessPredicate :: AuthTag -> AccessPredicate -tagAccessPredicate AuthFree = trueAP -tagAccessPredicate AuthAdmin = 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 $ exists [UserFunctionUser ==. authId, UserFunctionFunction ==. 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 AuthExamOffice = 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 - _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 = 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 = 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 = 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 -tagAccessPredicate AuthCorrector = APDB $ \mAuthId route _ -> exceptT return return $ do - authId <- maybeExceptT AuthenticationRequired $ return mAuthId - resList <- $cachedHereBinary mAuthId . lift . E.select . 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 - return (course E.^. CourseId, sheet E.^. SheetId) - let - resMap :: Map CourseId (Set SheetId) - resMap = Map.fromListWith Set.union [ (cid, Set.singleton sid) | (E.Value cid, E.Value sid) <- resList ] - case route of - CSubmissionR _ _ _ _ cID _ -> $cachedHereBinary (mAuthId, cID) . maybeT (unauthorizedI MsgUnauthorizedSubmissionCorrector) $ do - sid <- catchIfMaybeT (const True :: CryptoIDError -> Bool) $ decrypt cID - Submission{..} <- MaybeT . lift $ get sid - guard $ maybe False (== authId) submissionRatingBy - return Authorized - CSheetR tid ssh csh shn _ -> $cachedHereBinary (mAuthId, tid, ssh, csh, shn) . maybeT (unauthorizedI MsgUnauthorizedSheetCorrector) $ do - Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh - Entity sid _ <- MaybeT . lift . getBy $ CourseSheet cid shn - guard $ sid `Set.member` fromMaybe Set.empty (resMap !? cid) - return Authorized - CourseR tid ssh csh _ -> $cachedHereBinary (mAuthId, tid, ssh, csh) . maybeT (unauthorizedI MsgUnauthorizedCorrector) $ do - Entity cid _ <- MaybeT . lift . getBy $ TermSchoolCourseShort tid ssh csh - guard $ cid `Set.member` Map.keysSet resMap - return Authorized - _ -> do - guardMExceptT (not $ Map.null resMap) (unauthorizedI MsgUnauthorizedCorrectorAny) - return Authorized -tagAccessPredicate AuthExamCorrector = 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 -tagAccessPredicate AuthTutor = 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 -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 <- $cachedHereBinary (tid, ssh, csh) . MaybeT . getKeyBy $ TermSchoolCourseShort tid ssh csh - Entity _ Sheet{..} <- $cachedHereBinary (course, shn) . MaybeT . getBy $ CourseSheet course shn - 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 || isn't _RegisteredGroups sheetGrouping) $ 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 $ \mAuthId route _ -> 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) - guard $ NTop (Just cTime) <= NTop allocationStaffAllocationTo - - return Authorized - - 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 <- case (mbc,mAuthId) of - (Just (Entity cid _), Just uid) -> $cachedHereBinary (uid, cid) $ exists [CourseParticipantUser ==. uid, CourseParticipantCourse ==. cid, CourseParticipantState ==. CourseParticipantActive] - _ -> return False - 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 - SystemMessage{systemMessageFrom, systemMessageTo} <- $cachedHereBinary smId . MaybeT $ get smId - cTime <- (NTop . Just) <$> liftIO getCurrentTime - guard $ NTop systemMessageFrom <= cTime - && NTop systemMessageTo >= 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 $ \mAuthId route _ -> case route of - CourseR tid ssh csh CRegisterR -> do - now <- liftIO getCurrentTime - mba <- mbAllocation tid ssh csh - case mba of - Nothing -> return Authorized - Just (cid, Allocation{..}) -> do - registered <- case mAuthId of - Just uid -> $cachedHereBinary (uid, cid) . existsBy $ UniqueParticipant uid cid - _ -> return False - 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 Nothing - guardMExceptT courseVisible (unauthorizedI MsgUnauthorizedCourseTime) - return Authorized - r -> $unsupportedAuthPredicate AuthCourseTime r -tagAccessPredicate AuthCourseRegistered = 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 -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 Handler) () - 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 - when (not 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 - when (not 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 - when (not 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 - when (not 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 - when (not 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 - - return () -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 - registered <- $cachedHereBinary occId . lift $ fromIntegral <$> count [ ExamRegistrationOccurrence ==. Just occId, ExamRegistrationExam ==. eid ] - guard $ examOccurrenceCapacity > 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 - 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 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 - - -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 = PredDNF $ Set.fromList - [ impureNonNull . Set.singleton $ PLVariable AuthAdmin - , impureNonNull . Set.singleton $ PLVariable AuthToken - ] - -routeAuthTags :: Route UniWorX -> Either InvalidAuthTag AuthDNF --- ^ DNF up to entailment: --- --- > (A_1 && A_2 && ...) OR' B OR' ... --- --- > A OR' B := ((A |- B) ==> A) && (A || B) -routeAuthTags = fmap (PredDNF . Set.mapMonotonic impureNonNull) . ofoldM partition' (Set.mapMonotonic toNullable $ dnfTerms defaultAuthDNF) . routeAttrs - where - partition' :: Set (Set AuthLiteral) -> Text -> Either InvalidAuthTag (Set (Set AuthLiteral)) - partition' prev t - | Just (Set.fromList . toNullable -> authTags) <- fromNullable =<< mapM fromPathPiece (Text.splitOn "AND" t) - = if - | oany (authTags `Set.isSubsetOf`) prev - -> Right prev - | otherwise - -> Right . Set.insert authTags $ Set.filter (not . (`Set.isSubsetOf` authTags)) prev - | otherwise - = Left $ InvalidAuthTag t - -evalAuthTags :: forall m. MonadAP m => AuthTagActive -> AuthDNF -> Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> WriterT (Set AuthTag) m AuthResult --- ^ `tell`s disabled predicates, identified as pivots -evalAuthTags AuthTagActive{..} (map (Set.toList . toNullable) . Set.toList . dnfTerms -> authDNF') mAuthId route isWrite - = do - mr <- getMsgRenderer - let - 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 (AuthTag, Maybe UserId, Route UniWorX, Bool) AuthResult m _ -> m _) $ for4 memo evalAccessPred' authTag mAuthId route isWrite - where - evalAccessPred' authTag' mAuthId' route' isWrite' = lift $ do - $logDebugS "evalAccessPred" $ tshow (authTag', mAuthId', route', isWrite') - evalAccessPred (tagAccessPredicate authTag') mAuthId' route' isWrite' - - 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 = foldr (\ats ar -> ar `orAR'` foldr (\aTag ar' -> ar' `andAR'` evalAuthLiteral aTag) (return $ trueAR mr) ats) (return $ falseAR mr) - - $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 - -evalAccessFor :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> m AuthResult -evalAccessFor mAuthId route isWrite = do - dnf <- either throwM return $ routeAuthTags route - fmap fst . runWriterT $ evalAuthTags (AuthTagActive $ const True) dnf mAuthId route isWrite - -evalAccessForDB :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX, BackendCompatible SqlBackend backend) => Maybe (AuthId UniWorX) -> Route UniWorX -> Bool -> ReaderT backend m AuthResult -evalAccessForDB = evalAccessFor - -evalAccessWith :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => [(AuthTag, Bool)] -> Route UniWorX -> Bool -> m AuthResult -evalAccessWith assumptions route isWrite = do - mAuthId <- liftHandler maybeAuthId - tagActive <- fromMaybe def <$> lookupSessionJson SessionActiveAuthTags - dnf <- either throwM return $ routeAuthTags route - let dnf' = ala Endo foldMap (map ((=<<) . uncurry dnfAssumeValue) assumptions) $ Just dnf - case dnf' of - Nothing -> return Authorized - Just dnf'' -> do - (result, deactivated) <- runWriterT $ evalAuthTags tagActive dnf'' mAuthId route isWrite - result <$ tellSessionJson SessionInactiveAuthTags deactivated - -evalAccessWithDB :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX, BackendCompatible SqlBackend backend) => [(AuthTag, Bool)] -> Route UniWorX -> Bool -> ReaderT backend m AuthResult -evalAccessWithDB = evalAccessWith - -evalAccess :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Route UniWorX -> Bool -> m AuthResult -evalAccess = evalAccessWith [] - -evalAccessDB :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX, BackendCompatible SqlBackend 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 :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => 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 :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => 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 :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Route UniWorX -> m Bool -hasWriteAccessTo = flip hasAccessTo True - -wouldHaveAccessTo :: ( MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX ) - => [(AuthTag, Bool)] -- ^ Assumptions - -> Route UniWorX - -> Bool - -> m Bool -wouldHaveAccessTo assumptions route isWrite = (== Authorized) <$> evalAccessWith assumptions route isWrite - -wouldHaveReadAccessTo, wouldHaveWriteAccessTo - :: ( MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX ) - => [(AuthTag, Bool)] -- ^ Assumptions - -> Route UniWorX - -> m Bool -wouldHaveReadAccessTo assumptions route = wouldHaveAccessTo assumptions route False -wouldHaveWriteAccessTo assumptions route = wouldHaveAccessTo assumptions route True - -wouldHaveReadAccessToIff, wouldHaveWriteAccessToIff - :: ( MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX ) - => [(AuthTag, Bool)] -- ^ Assumptions - -> Route UniWorX - -> m Bool -wouldHaveReadAccessToIff assumptions route = and2M (fmap not $ hasReadAccessTo route) $ wouldHaveReadAccessTo assumptions route -wouldHaveWriteAccessToIff assumptions route = and2M (fmap not $ hasWriteAccessTo route) $ wouldHaveWriteAccessTo assumptions route - --- | Conditional redirect that hides the URL if the user is not authorized for the route -redirectAccess :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Route UniWorX -> m a -redirectAccess url = do - -- must hide URL if not authorized - access <- evalAccess url False - case access of - Authorized -> redirect url - _ -> permissionDeniedI MsgUnauthorizedRedirect - -redirectAccessWith :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) => Status -> Route UniWorX -> m a -redirectAccessWith status url = do - -- must hide URL if not authorized - access <- evalAccess url False - case access of - Authorized -> redirectWith status url - _ -> permissionDeniedI MsgUnauthorizedRedirect - - --- | Verify that the currently logged in user is lecturer or corrector for at least one sheet for the given course -evalAccessCorrector :: (MonadThrow m, MonadHandler m, HandlerSite m ~ UniWorX) - => TermId -> SchoolId -> CourseShorthand -> m AuthResult -evalAccessCorrector tid ssh csh = evalAccess (CourseR tid ssh csh CNotesR) False - - -data instance ButtonClass UniWorX - = BCIsButton - | BCDefault - | BCPrimary - | BCSuccess - | BCInfo - | BCWarning - | BCDanger - | BCLink - | BCMassInputAdd | BCMassInputDelete - deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable) - deriving anyclass (Universe, Finite) - -instance PathPiece (ButtonClass UniWorX) where - toPathPiece BCIsButton = "btn" - toPathPiece bClass = ("btn-" <>) . camelToPathPiece' 1 $ tshow bClass - fromPathPiece = flip List.lookup $ map (toPathPiece &&& id) universeF - -instance Button UniWorX ButtonSubmit where - btnClasses BtnSubmit = [BCIsButton, BCPrimary] - - - --- Please see the documentation for the Yesod typeclass. There are a number --- of settings which can be configured by overriding methods here. -instance Yesod UniWorX where - -- Controls the base of generated URLs. For more information on modifying, - -- see: https://github.com/yesodweb/yesod/wiki/Overriding-approot - approot = ApprootRequest $ \app req -> - case app ^. _appRoot of - Nothing -> getApprootText guessApproot app req - Just root -> root - - makeSessionBackend app@UniWorX{ appSettings' = AppSettings{..}, ..} = notForBearer . sameSite $ case appSessionStore of - SessionStorageMemcachedSql sqlStore - -> mkBackend =<< stateSettings <$> ServerSession.createState sqlStore - SessionStorageAcid acidStore - | appServerSessionAcidFallback - -> mkBackend =<< stateSettings <$> ServerSession.createState acidStore - _other - -> return Nothing - where - cfg = JwtSession.ServerSessionJwtConfig - { sJwtJwkSet = appJSONWebKeySet - , sJwtStart = Nothing - , sJwtExpiration = appSessionTokenExpiration - , sJwtEncoding = appSessionTokenEncoding - , sJwtIssueBy = appInstanceID - , sJwtIssueFor = appClusterID - } - mkBackend :: forall sto. - ( ServerSession.SessionData sto ~ Map Text ByteString - , ServerSession.Storage sto - ) - => ServerSession.State sto -> IO (Maybe SessionBackend) - mkBackend = JwtSession.backend cfg (JwtSession.siteApproot app) - stateSettings :: forall sto. ServerSession.State sto -> ServerSession.State sto - stateSettings = ServerSession.setCookieName (toPathPiece CookieSession) . applyServerSessionSettings appServerSessionConfig - sameSite - | Just sameSiteStrict == cookieSameSite (getCookieSettings app CookieSession) - = strictSameSiteSessions - | Just sameSiteLax == cookieSameSite (getCookieSettings app CookieSession) - = laxSameSiteSessions - | otherwise - = id - notForBearer :: IO (Maybe SessionBackend) -> IO (Maybe SessionBackend) - notForBearer = fmap $ fmap notForBearer' - where notForBearer' :: SessionBackend -> SessionBackend - notForBearer' (SessionBackend load) - = let load' req - | aHdrs <- mapMaybe (\(h, v) -> v <$ guard (h == W.hAuthorization)) $ W.requestHeaders req - , any (is _Just) $ map W.extractBearerAuth aHdrs - = return (mempty, const $ return []) - | otherwise - = load req - in SessionBackend load' - - maximumContentLength app _ = app ^. _appMaximumContentLength - - -- Yesod Middleware allows you to run code before and after each handler function. - -- The defaultYesodMiddleware adds the response header "Vary: Accept, Accept-Language" and performs authorization checks. - -- Some users may also want to add the defaultCsrfMiddleware, which: - -- a) Sets a cookie with a CSRF token in it. - -- b) Validates that incoming write requests include that token in either a header or POST parameter. - -- To add it, chain it together with the defaultMiddleware: yesodMiddleware = defaultYesodMiddleware . defaultCsrfMiddleware - -- For details, see the CSRF documentation in the Yesod.Core.Handler module of the yesod-core package. - yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . observeYesodCacheSizeMiddleware . languagesMiddleware appLanguages . headerMessagesMiddleware . defaultYesodMiddleware . normalizeRouteMiddleware . updateFavouritesMiddleware - where - dryRunMiddleware :: Handler a -> Handler a - dryRunMiddleware handler = do - dryRun <- isDryRun - if | dryRun -> do - hData <- ask - prevState <- readIORef (handlerState hData) - let - restoreSession = - modifyIORef (handlerState hData) $ - \hst -> hst { ghsSession = ghsSession prevState - , ghsCache = ghsCache prevState - , ghsCacheBy = ghsCacheBy prevState - } - site' = (rheSite $ handlerEnv hData) { appMemcached = Nothing } - handler' = local (\hd -> hd { handlerEnv = (handlerEnv hd) { rheSite = site', rheChild = site' } }) handler - - addCustomHeader HeaderDryRun ("1" :: Text) - - handler' `finally` restoreSession - | otherwise -> handler - updateFavouritesMiddleware :: Handler a -> Handler a - updateFavouritesMiddleware handler = (*> handler) . runMaybeT $ do - route <- MaybeT getCurrentRoute - case route of -- update Course Favourites here - CourseR tid ssh csh _ -> do - void . lift . runDB . runMaybeT $ do - guardM . lift $ (== Authorized) <$> evalAccessDB (CourseR tid ssh csh CShowR) False - lift . updateFavourites $ Just (tid, ssh, csh) - _other -> return () - normalizeRouteMiddleware :: Handler a -> Handler a - normalizeRouteMiddleware handler = (*> handler) . runMaybeT $ do - route <- MaybeT getCurrentRoute - (route', getAny -> changed) <- lift . runDB . runWriterT $ foldM (&) route routeNormalizers - when changed $ do - $logDebugS "normalizeRouteMiddleware" [st|Redirecting to #{tshow route'}|] - redirectWith movedPermanently301 route' - headerMessagesMiddleware :: Handler a -> Handler a - headerMessagesMiddleware handler = (handler `finally`) . runMaybeT $ do - isModal <- hasCustomHeader HeaderIsModal - dbTableShortcircuit <- hasCustomHeader HeaderDBTableShortcircuit - massInputShortcircuit <- hasCustomHeader HeaderMassInputShortcircuit - $logDebugS "headerMessagesMiddleware" $ tshow (isModal, dbTableShortcircuit, massInputShortcircuit) - guard $ or - [ isModal - , dbTableShortcircuit - , massInputShortcircuit - ] - - lift . bracketOnError getMessages (mapM_ addMessage') $ - addCustomHeader HeaderAlerts . decodeUtf8 . urlEncode True . toStrict . JSON.encode - observeYesodCacheSizeMiddleware :: Handler a -> Handler a - observeYesodCacheSizeMiddleware handler = handler `finally` observeYesodCacheSize - csrfMiddleware :: Handler a -> Handler a - csrfMiddleware handler = do - hasBearer <- is _Just <$> lookupBearerAuth - - if | hasBearer -> local (\HandlerData{..} -> HandlerData{ handlerRequest = handlerRequest { reqToken = Nothing }, .. }) handler - | otherwise -> csrfSetCookieMiddleware' . defaultCsrfCheckMiddleware $ handler - where - csrfSetCookieMiddleware' handler' = do - mcsrf <- reqToken <$> getRequest - whenIsJust mcsrf $ setRegisteredCookie CookieXSRFToken - handler' - storeBearerMiddleware :: Handler a -> Handler a - storeBearerMiddleware handler = do - askBearer >>= \case - Just (Jwt bs) -> setSessionBS (toPathPiece SessionBearer) bs - Nothing -> return () - - handler - - -- Since we implement `errorHandler` ourselves we don't need `defaultMessageWidget` - defaultMessageWidget _title _body = error "defaultMessageWidget: undefined" - - errorHandler err = do - shouldEncrypt <- do - canDecrypt <- (== Authorized) <$> evalAccess AdminErrMsgR True - shouldEncrypt <- getsYesod $ view _appEncryptErrors - return $ shouldEncrypt && not canDecrypt - - sessErr <- bool return (_InternalError $ encodedSecretBox SecretBoxShort) shouldEncrypt err - setSessionJson SessionError sessErr - - selectRep $ do - provideRep $ do - mr <- getMessageRender - let - encrypted :: ToJSON a => a -> Widget -> Widget - encrypted plaintextJson plaintext = do - if - | shouldEncrypt -> do - ciphertext <- encodedSecretBox SecretBoxPretty plaintextJson - - [whamlet| -

_{MsgErrorResponseEncrypted} -

-                        #{ciphertext}
-                    |]
-                | otherwise -> plaintext
-
-            errPage = case err of
-              NotFound -> [whamlet|

_{MsgErrorResponseNotFound}|] - InternalError err' -> encrypted err' [whamlet|

#{err'}|] - InvalidArgs errs -> [whamlet| -