diff --git a/config/settings.yml b/config/settings.yml index 9c4060e61..4cf98ef99 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -249,3 +249,5 @@ token-buckets: initial-value: 0 fallback-personalised-sheet-files-keys-expire: 2419200 + +download-token-expire: 14400 diff --git a/load/Load.hs b/load/Load.hs index 7c0020ca4..1f8bcb1e0 100644 --- a/load/Load.hs +++ b/load/Load.hs @@ -367,5 +367,5 @@ sheetZipURI :: ReaderT SimulationContext IO URI sheetZipURI = do LoadOptions{..} <- asks loadOptions let zipURI = nullURI { uriPath = unpack . Text.intercalate "/" $ "." : zipPath } - where (zipPath, _) = renderRoute . CSheetR loadTerm loadSchool loadCourse loadSheet $ SZipR SheetExercise + where (zipPath, _) = renderRoute . CSheetR loadTerm loadSchool loadCourse loadSheet $ SZipR SheetExercise -- FIXME: Broken with ApprootUserGenerated return $ zipURI `relativeTo` loadBaseURI diff --git a/messages/uniworx/de-de-formal.msg b/messages/uniworx/de-de-formal.msg index 7288730c6..50a124c2d 100644 --- a/messages/uniworx/de-de-formal.msg +++ b/messages/uniworx/de-de-formal.msg @@ -466,6 +466,7 @@ UnauthorizedTokenInvalidNoAuthority: Ihr Authorisierungs-Token nennt keine Nutze UnauthorizedTokenInvalidAuthority: Ihr Authorisierungs-Token basiert auf den Rechten eines Nutzers, der nicht mehr existiert. UnauthorizedTokenInvalidAuthorityGroup: Ihr Authorisierungs-Token basiert auf den Rechten einer Gruppe von Nutzern, die nicht mehr existiert. UnauthorizedTokenInvalidAuthorityValue: Ihr Authorisierungs-Token basiert auf Rechten, deren Spezifikation nicht interpretiert werden konnte. +UnauthorizedTokenInvalidImpersonation: Ihr Authorisierungs-Token enthält die Anweisung sich als ein Nutzer auszugeben, dies ist jedoch nicht allen Benutzern, auf deren Rechten ihr Authorisierungs-Token basiert, erlaubt. UnauthorizedToken404: Authorisierungs-Tokens können nicht auf Fehlerseiten ausgewertet werden. UnauthorizedSiteAdmin: Sie sind kein System-weiter Administrator. UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen. diff --git a/messages/uniworx/en-eu.msg b/messages/uniworx/en-eu.msg index e2ed32e5f..f7a9ef2c5 100644 --- a/messages/uniworx/en-eu.msg +++ b/messages/uniworx/en-eu.msg @@ -463,6 +463,7 @@ UnauthorizedTokenInvalidNoAuthority: Your authorisation-token does not list any UnauthorizedTokenInvalidAuthority: Your authorisation-token is based in an user's rights who does not exist anymore. UnauthorizedTokenInvalidAuthorityGroup: Your authorisation-token is based in an user groups rights which does not exist anymore. UnauthorizedTokenInvalidAuthorityValue: The specification of the rights in which your authorisation-token is based, could not be interpreted. +UnauthorizedTokenInvalidImpersonation: Your authorisation-token contains an instruction to impersonate an user. Not all users on whose rights your token is based however are permitted to do so. UnauthorizedToken404: Authorisation-tokens cannot be processed on error pages. UnauthorizedSiteAdmin: You are no system-wide administrator. UnauthorizedSchoolAdmin: You are no administrator for this department. diff --git a/src/Data/Universe/Instances/Reverse/JSON.hs b/src/Data/Universe/Instances/Reverse/JSON.hs index 8aed56f14..e4ce07a11 100644 --- a/src/Data/Universe/Instances/Reverse/JSON.hs +++ b/src/Data/Universe/Instances/Reverse/JSON.hs @@ -22,8 +22,11 @@ instance (Eq a, Hashable a, Finite a, ToJSON b, ToJSONKey a) => ToJSON (a -> b) toJSON f = toJSON $ HashMap.fromList [(k, f k) | k <- universeF] instance (Eq a, Hashable a, Finite a, FromJSON b, FromJSONKey a) => FromJSON (a -> b) where - parseJSON val = do - vMap <- parseJSON val :: Parser (HashMap a b) - unless (HashSet.fromMap (HashMap.map (const ()) vMap) == HashSet.fromList universeF) $ - fail "Not all required keys found" - return (vMap !) + parseJSON val = asObject <|> asConst + where + asObject = do + vMap <- parseJSON val :: Parser (HashMap a b) + unless (HashSet.fromMap (HashMap.map (const ()) vMap) == HashSet.fromList universeF) $ + fail "Not all required keys found" + return (vMap !) + asConst = const <$> parseJSON val diff --git a/src/Foundation/Authorization.hs b/src/Foundation/Authorization.hs index ca773c059..55c83c173 100644 --- a/src/Foundation/Authorization.hs +++ b/src/Foundation/Authorization.hs @@ -17,6 +17,7 @@ module Foundation.Authorization , evalWorkflowRoleFor, evalWorkflowRoleFor' , hasWorkflowRole , mayViewWorkflowAction + , authoritiveApproot ) where import Import.NoFoundation hiding (Last(..)) @@ -218,15 +219,32 @@ validateBearer mAuthId' route' isWrite' token' = $runCachedMemoT $ for4 memo val where validateBearer' :: _ -> _ -> _ -> _ -> CachedMemoT (Maybe (AuthId UniWorX), Route UniWorX, Bool, BearerToken UniWorX) AuthResult (ReaderT SqlReadBackend (HandlerFor UniWorX)) AuthResult validateBearer' mAuthId route isWrite BearerToken{..} = lift . exceptT return return $ do - guardMExceptT (maybe True (HashSet.member route) bearerRoutes) (unauthorizedI MsgUnauthorizedTokenInvalidRoute) + iforM_ bearerRoutes $ \case + BearerTokenRouteEval -> \routes -> guardMExceptT (HashSet.member route routes) $ unauthorizedI MsgUnauthorizedTokenInvalidRoute + BearerTokenRouteAccess -> \routes -> maybeTMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidRoute) $ do + cRoute <- MaybeT getCurrentRoute + guard $ HashSet.member cRoute routes 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 + | JSON.Success groupName <- JSON.fromJSON tVal -> do + Entity _ primary <- maybeMExceptT (unauthorizedI MsgUnauthorizedTokenInvalidAuthorityGroup) . getBy $ UniquePrimaryUserGroupMember groupName Active + case bearerImpersonate of + Nothing -> return . Set.singleton $ userGroupMemberUser primary + Just iuid | iuid == userGroupMemberUser primary -> return . Set.singleton $ userGroupMemberUser primary + | otherwise -> do + unlessM (lift $ exists [UserGroupMemberUser ==. iuid, UserGroupMemberGroup ==. groupName]) $ + throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidImpersonation + return $ Set.singleton iuid | otherwise -> throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidAuthorityValue - Right uid -> return $ Set.singleton uid + Right uid -> case bearerImpersonate of + Just iuid | uid == iuid -> return $ Set.singleton uid + | otherwise -> do + cID <- encrypt iuid + unlessM (is _Authorized <$> evalAccessWithFor [(AuthToken, False)] (Just uid) (AdminHijackUserR cID) True) $ + throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidImpersonation + return $ Set.singleton iuid + Nothing -> return $ Set.singleton uid let -- Prevent infinite loops @@ -269,7 +287,7 @@ 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 + mAuthId <- defaultMaybeAuthId -- `maybeAuthId` would be an infinite loop; this is equivalent to `maybeAuthId` but ignoring `bearerImpersonate` from any valid token currentRoute <- maybe (permissionDeniedI MsgUnauthorizedToken404) return =<< getCurrentRoute isWrite <- isWriteRequest currentRoute guardAuthResult <=< runDBRead $ validateBearer mAuthId currentRoute isWrite bearer @@ -1726,3 +1744,19 @@ mayViewWorkflowAction mAuthId wwId WorkflowAction{..} = withReaderT (projectBack lift $ anyM wpvViewers evalWorkflowRole' ] return True + +authoritiveApproot :: Route UniWorX -> ApprootScope +authoritiveApproot = \case + CourseR _ _ _ (MaterialR _ (MFileR _)) -> ApprootUserGenerated + CourseR _ _ _ (MaterialR _ MArchiveR) -> ApprootUserGenerated + CourseR _ _ _ (SheetR _ (SFileR _ _)) -> ApprootUserGenerated + CourseR _ _ _ (SheetR _ (SZipR _)) -> ApprootUserGenerated + CourseR _ _ _ (SheetR _ (SubmissionR _ (SubDownloadR _ _))) -> ApprootUserGenerated + CourseR _ _ _ (SheetR _ (SubmissionR _ (SubArchiveR _))) -> ApprootUserGenerated + CourseR _ _ _ (CourseNewsR _ (CNFileR _)) -> ApprootUserGenerated + CourseR _ _ _ (CourseNewsR _ CNArchiveR) -> ApprootUserGenerated + CourseR _ _ _ CRegisterTemplateR -> ApprootUserGenerated + CourseR _ _ _ CAppsFilesR -> ApprootUserGenerated + CourseR _ _ _ (CourseApplicationR _ CAFilesR) -> ApprootUserGenerated + route | Just (_, WorkflowWorkflowR _ (WWFilesR _ _)) <- route ^? _WorkflowScopeRoute -> ApprootUserGenerated + _other -> ApprootDefault diff --git a/src/Foundation/Instances.hs b/src/Foundation/Instances.hs index 362af9d60..8159bcb00 100644 --- a/src/Foundation/Instances.hs +++ b/src/Foundation/Instances.hs @@ -79,10 +79,19 @@ 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 + case app ^. _appRoot . to ($ ApprootDefault) of Nothing -> getApprootText guessApproot app req Just root -> root + urlParamRenderOverride app route params = do + rApproot <- case authoritiveApproot route of + ApprootDefault -> mzero + rApproot -> return rApproot + guard $ views _appRoot ($ ApprootDefault) app /= views _appRoot ($ rApproot) app + approotText <- app ^. _appRoot . to ($ rApproot) + let (ps, params') = renderRoute route + return . joinPath app approotText ps $ params ++ params' + makeSessionBackend = UniWorX.makeSessionBackend maximumContentLength app _ = app ^. _appMaximumContentLength @@ -181,6 +190,11 @@ instance YesodAuth UniWorX where _other -> Auth.germanMessage where lang = Text.splitOn "-" $ selectLanguage' appLanguages ls + maybeAuthId = runMaybeT $ authIdFromBearer <|> MaybeT defaultMaybeAuthId + where authIdFromBearer = do + BearerToken{..} <- MaybeT maybeBearerToken + hoistMaybe bearerImpersonate + instance YesodAuthPersist UniWorX where getAuthEntity = liftHandler . runDBRead . get diff --git a/src/Foundation/Navigation.hs b/src/Foundation/Navigation.hs index 64f4a3466..42e7d3e56 100644 --- a/src/Foundation/Navigation.hs +++ b/src/Foundation/Navigation.hs @@ -7,6 +7,7 @@ module Foundation.Navigation ( NavQuickView(..), NavType(..), NavLevel(..), NavHeaderRole(..), NavLink(..), Nav(..), NavChildren , _navModal, _navMethod, _navData, _navLabel, _navType, _navForceActive, _navHeaderRole, _navIcon, _navLink, _navChildren , _NavHeader, _NavHeaderContainer, _NavPageActionPrimary, _NavPageActionSecondary, _NavFooter + , navBaseRoute, navLinkRoute , pageActions , pageQuickActions , defaultLinks @@ -25,6 +26,7 @@ import Foundation.DB import Handler.Utils.Memcached import Handler.Utils.ExamOffice.Course +import Handler.Utils.Download import Utils.Sheet import qualified Data.CaseInsensitive as CI @@ -427,6 +429,7 @@ data NavHeaderRole = NavHeaderPrimary | NavHeaderSecondary data NavLink = forall msg route. (RenderMessage UniWorX msg, HasRoute UniWorX route, RedirectUrl UniWorX route) => NavLink { navLabel :: msg , navRoute :: route + , navDownload :: Maybe (Maybe (ConduitT () (Either FileReference DBFile) Handler ())) , navAccess' :: Handler Bool , navType :: NavType , navQuick' :: NavQuickView -> Any @@ -435,13 +438,26 @@ data NavLink = forall msg route. (RenderMessage UniWorX msg, HasRoute UniWorX ro makeLenses_ ''NavLink -instance HasRoute UniWorX NavLink where - urlRoute NavLink{..} = urlRoute navRoute -instance RedirectUrl UniWorX NavLink where - toTextUrl NavLink{..} = toTextUrl navRoute +-- 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 +navBaseRoute :: NavLink -> Route UniWorX +navBaseRoute NavLink{navRoute} = urlRoute navRoute + +navLinkRoute :: ( MonadHandler m, HandlerSite m ~ UniWorX + , MonadCrypto m + , MonadCryptoKey m ~ CryptoIDKey + , YesodAuthPersist UniWorX, AuthEntity UniWorX ~ User, AuthId UniWorX ~ UserId + ) + => NavLink -> m (SomeRoute UniWorX) +navLinkRoute NavLink{..} = case navDownload of + Nothing -> return $ SomeRoute navRoute + Just mSource -> withFileDownloadTokenMaybe' (transPipe liftHandler <$> mSource) navRoute + data Nav = NavHeader { navHeaderRole :: NavHeaderRole @@ -507,6 +523,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuLogout , navRoute = AuthR LogoutR + , navDownload = Nothing , navAccess' = is _Just <$> maybeAuthId , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -519,6 +536,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuLogin , navRoute = AuthR LoginR + , navDownload = Nothing , navAccess' = is _Nothing <$> maybeAuthId , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -531,6 +549,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuProfile , navRoute = ProfileR + , navDownload = Nothing , navAccess' = is _Just <$> maybeAuthId , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -545,6 +564,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the let navChildren = flip map (toList appLanguages) $ \lang -> NavLink { navLabel = MsgLanguage lang , navRoute = (LangR, [(toPathPiece GetReferer, toPathPiece currentRoute) | currentRoute <- hoistMaybe mCurrentRoute ]) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeButton { navMethod = POST @@ -571,6 +591,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuHelp , navRoute = (HelpR, [(toPathPiece GetReferer, toPathPiece currentRoute) | currentRoute <- hoistMaybe mCurrentRoute ]) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -580,6 +601,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuDataProt , navRoute = LegalR :#: ("data-protection" :: Text) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -588,6 +610,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuTermsUse , navRoute = LegalR :#: ("terms-of-use" :: Text) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -596,6 +619,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuCopyright , navRoute = LegalR :#: ("copyright" :: Text) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -604,6 +628,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuImprint , navRoute = LegalR :#: ("imprint" :: Text) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -612,6 +637,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuInformation , navRoute = InfoR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -620,6 +646,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuFaq , navRoute = FaqR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -628,6 +655,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , return $ NavFooter NavLink { navLabel = MsgMenuGlossary , navRoute = GlossaryR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -639,6 +667,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuNews , navRoute = NewsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -651,6 +680,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuCourseList , navRoute = CourseListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -663,6 +693,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuCorrections , navRoute = CorrectionsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -675,6 +706,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuExamOfficeExams , navRoute = ExamOfficeR EOExamsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -692,6 +724,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuTopWorkflowInstanceList , navRoute = TopWorkflowInstanceListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -704,6 +737,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , navLink = NavLink { navLabel = MsgMenuTopWorkflowWorkflowListHeader , navRoute = TopWorkflowWorkflowListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -719,6 +753,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the [ NavLink { navLabel = MsgMenuUsers , navRoute = UsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -727,6 +762,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuSchoolList , navRoute = SchoolListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -735,6 +771,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgAdminFeaturesHeading , navRoute = AdminFeaturesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -743,6 +780,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuMessageList , navRoute = MessageListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -751,6 +789,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAdminErrMsg , navRoute = AdminErrMsgR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -759,6 +798,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAdminTokens , navRoute = AdminTokensR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -767,6 +807,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAdminWorkflowDefinitionList , navRoute = AdminWorkflowDefinitionListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -775,6 +816,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAdminCrontab , navRoute = AdminCrontabR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -783,6 +825,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAdminTest , navRoute = AdminTestR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -798,6 +841,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the [ NavLink { navLabel = MsgMenuCourseNew , navRoute = CourseNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -806,6 +850,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuExternalExamList , navRoute = EExamListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -814,6 +859,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuTermShow , navRoute = TermShowR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -822,6 +868,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgMenuAllocationList , navRoute = AllocationListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -830,6 +877,7 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the , NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR + , navDownload = Nothing , navAccess' = hasWriteAccessTo CourseNewR , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -851,6 +899,7 @@ pageActions NewsR = return { navLink = NavLink { navLabel = MsgMenuOpenCourses , navRoute = (CourseListR, [("courses-openregistration", toPathPiece True)]) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -862,6 +911,7 @@ pageActions NewsR = return { navLink = NavLink { navLabel = MsgMenuOpenAllocations , navRoute = (AllocationListR, [("allocations-active", toPathPiece True)]) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -892,6 +942,7 @@ pageActions (CourseR tid ssh csh CShowR) = do return NavLink { navLabel = examn , navRoute = CExamR tid ssh csh examn EShowR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewFavourite @@ -904,6 +955,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuCourseMembers , navRoute = CourseR tid ssh csh CUsersR + , navDownload = Nothing , navAccess' = let courseWhere course = course <$ do E.where_ $ course E.^. CourseTerm E.==. E.val tid @@ -928,6 +980,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuMaterialList , navRoute = CourseR tid ssh csh MaterialListR + , navDownload = Nothing , navAccess' = let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh MaterialNewR -- Always show for lecturers to create new material materialAccess mnm = hasReadAccessTo $ CMaterialR tid ssh csh mnm MShowR -- otherwise show only if the user can see at least one of the contents @@ -950,6 +1003,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuSheetList , navRoute = CourseR tid ssh csh SheetListR + , navDownload = Nothing , navAccess' = let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh SheetNewR -- Always show for lecturers to create new sheets sheetAccess shn = hasReadAccessTo $ CSheetR tid ssh csh shn SShowR -- othwerwise show only if the user can see at least one of the contents @@ -972,6 +1026,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuTutorialList , navRoute = CourseR tid ssh csh CTutorialListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewFavourite @@ -983,6 +1038,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuExamList , navRoute = CourseR tid ssh csh CExamListR + , navDownload = Nothing , navAccess' = let lecturerAccess = hasWriteAccessTo $ CourseR tid ssh csh CExamNewR examAccess examn = hasReadAccessTo $ CExamR tid ssh csh examn EShowR @@ -1007,6 +1063,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuCourseCommunication , navRoute = CourseR tid ssh csh CCommR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewFavourite @@ -1018,6 +1075,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuCourseExamOffice , navRoute = CourseR tid ssh csh CExamOfficeR + , navDownload = Nothing , navAccess' = do uid <- requireAuthId runDBRead $ do @@ -1034,6 +1092,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuCourseEdit , navRoute = CourseR tid ssh csh CEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1046,6 +1105,7 @@ pageActions (CourseR tid ssh csh CShowR) = do , navRoute = ( CourseNewR , [("tid", toPathPiece tid), ("ssh", toPathPiece ssh), ("csh", toPathPiece csh)] ) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1056,6 +1116,7 @@ pageActions (CourseR tid ssh csh CShowR) = do { navLink = NavLink { navLabel = MsgMenuCourseDelete , navRoute = CourseR tid ssh csh CDeleteR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1068,6 +1129,7 @@ pageActions (ExamOfficeR EOExamsR) = return { navLink = NavLink { navLabel = MsgMenuExamOfficeFields , navRoute = ExamOfficeR EOFieldsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1079,6 +1141,7 @@ pageActions (ExamOfficeR EOExamsR) = return { navLink = NavLink { navLabel = MsgMenuExamOfficeUsers , navRoute = ExamOfficeR EOUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1092,6 +1155,7 @@ pageActions SchoolListR = return { navLink = NavLink { navLabel = MsgMenuSchoolNew , navRoute = SchoolNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1105,6 +1169,7 @@ pageActions UsersR = return { navLink = NavLink { navLabel = MsgMenuLecturerInvite , navRoute = AdminNewFunctionaryInviteR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1116,6 +1181,7 @@ pageActions UsersR = return { navLink = NavLink { navLabel = MsgMenuUserAdd , navRoute = AdminUserAddR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1129,6 +1195,7 @@ pageActions (AdminUserR cID) = return { navLink = NavLink { navLabel = MsgMenuUserNotifications , navRoute = UserNotificationR cID + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1140,6 +1207,7 @@ pageActions (AdminUserR cID) = return { navLink = NavLink { navLabel = MsgMenuUserPassword , navRoute = UserPasswordR cID + , navDownload = Nothing , navAccess' = do uid <- decrypt cID User{userAuthentication} <- runDBRead $ get404 uid @@ -1156,6 +1224,7 @@ pageActions InfoR = return { navLink = NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR + , navDownload = Nothing , navAccess' = hasWriteAccessTo CourseNewR , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1167,6 +1236,7 @@ pageActions InfoR = return { navLink = NavLink { navLabel = MsgMenuLegal , navRoute = LegalR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1178,6 +1248,7 @@ pageActions InfoR = return { navLink = NavLink { navLabel = MsgMenuFaq , navRoute = FaqR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1189,6 +1260,7 @@ pageActions InfoR = return { navLink = NavLink { navLabel = MsgMenuGlossary , navRoute = GlossaryR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1202,6 +1274,7 @@ pageActions VersionR = return { navLink = NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR + , navDownload = Nothing , navAccess' = hasWriteAccessTo CourseNewR , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1213,6 +1286,7 @@ pageActions VersionR = return { navLink = NavLink { navLabel = MsgMenuLegal , navRoute = LegalR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1224,6 +1298,7 @@ pageActions VersionR = return { navLink = NavLink { navLabel = MsgMenuFaq , navRoute = FaqR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1235,6 +1310,7 @@ pageActions VersionR = return { navLink = NavLink { navLabel = MsgMenuGlossary , navRoute = GlossaryR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1248,6 +1324,7 @@ pageActions HealthR = return { navLink = NavLink { navLabel = MsgMenuInstance , navRoute = InstanceR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1261,6 +1338,7 @@ pageActions InstanceR = return { navLink = NavLink { navLabel = MsgMenuHealth , navRoute = HealthR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1274,6 +1352,7 @@ pageActions HelpR = return { navLink = NavLink { navLabel = MsgMenuFaq , navRoute = FaqR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1285,6 +1364,7 @@ pageActions HelpR = return { navLink = NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR + , navDownload = Nothing , navAccess' = hasWriteAccessTo CourseNewR , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1301,6 +1381,7 @@ pageActions HelpR = return return NavLink { navLabel , navRoute = InfoLecturerR :#: section + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1311,6 +1392,7 @@ pageActions HelpR = return { navLink = NavLink { navLabel = MsgMenuGlossary , navRoute = GlossaryR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1324,6 +1406,7 @@ pageActions ProfileR = return { navLink = NavLink { navLabel = MsgMenuProfileData , navRoute = ProfileDataR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1335,6 +1418,7 @@ pageActions ProfileR = return { navLink = NavLink { navLabel = MsgMenuAuthPreds , navRoute = AuthPredsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1346,6 +1430,7 @@ pageActions ProfileR = return { navLink = NavLink { navLabel = MsgCsvOptions , navRoute = CsvOptionsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1361,6 +1446,7 @@ pageActions TermShowR = do { navLink = NavLink { navLabel = MsgMenuTermCreate , navRoute = TermEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1372,6 +1458,7 @@ pageActions TermShowR = do { navLink = NavLink { navLabel = MsgMenuParticipantsList , navRoute = ParticipantsListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1385,6 +1472,7 @@ pageActions (AllocationR tid ssh ash AShowR) = return { navLink = NavLink { navLabel = MsgMenuAllocationInfo , navRoute = InfoAllocationR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1396,6 +1484,7 @@ pageActions (AllocationR tid ssh ash AShowR) = return { navLink = NavLink { navLabel = MsgMenuAllocationUsers , navRoute = AllocationR tid ssh ash AUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1407,6 +1496,7 @@ pageActions (AllocationR tid ssh ash AShowR) = return { navLink = NavLink { navLabel = MsgMenuAllocationCompute , navRoute = AllocationR tid ssh ash AComputeR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1420,6 +1510,7 @@ pageActions (AllocationR tid ssh ash AUsersR) = return { navLink = NavLink { navLabel = MsgMenuAllocationPriorities , navRoute = AllocationR tid ssh ash APriosR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1431,6 +1522,7 @@ pageActions (AllocationR tid ssh ash AUsersR) = return { navLink = NavLink { navLabel = MsgMenuAllocationCompute , navRoute = AllocationR tid ssh ash AComputeR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1442,6 +1534,7 @@ pageActions (AllocationR tid ssh ash AUsersR) = return { navLink = NavLink { navLabel = MsgMenuAllocationAddUser , navRoute = AllocationR tid ssh ash AAddUserR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1457,6 +1550,7 @@ pageActions CourseListR = do { navLink = NavLink { navLabel = MsgMenuCourseNew , navRoute = CourseNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1468,6 +1562,7 @@ pageActions CourseListR = do { navLink = NavLink { navLabel = MsgMenuAllocationList , navRoute = AllocationListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1479,6 +1574,7 @@ pageActions CourseListR = do { navLink = NavLink { navLabel = MsgMenuParticipantsList , navRoute = ParticipantsListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1492,6 +1588,7 @@ pageActions CourseNewR = return { navLink = NavLink { navLabel = MsgInfoLecturerTitle , navRoute = InfoLecturerR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1505,6 +1602,7 @@ pageActions (CourseR tid ssh csh CCorrectionsR) = return { navLink = NavLink { navLabel = MsgMenuCorrectionsAssign , navRoute = CourseR tid ssh csh CAssignR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite @@ -1521,6 +1619,7 @@ pageActions (CourseR tid ssh csh CCorrectionsR) = return , ("corrections-course", toPathPiece csh) ] ) + , navDownload = Nothing , navAccess' = do muid <- maybeAuthId case muid of @@ -1548,6 +1647,7 @@ pageActions (CourseR tid ssh csh SheetListR) = do { navLink = NavLink { navLabel = MsgMenuSubmissions , navRoute = CourseR tid ssh csh CCorrectionsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite @@ -1562,6 +1662,7 @@ pageActions (CourseR tid ssh csh SheetListR) = do { navLink = NavLink { navLabel = MsgMenuSheetCurrent , navRoute = CourseR tid ssh csh SheetCurrentR + , navDownload = Nothing , navAccess' = runDBRead . maybeT (return False) $ do void . MaybeT $ sheetCurrent tid ssh csh @@ -1576,6 +1677,7 @@ pageActions (CourseR tid ssh csh SheetListR) = do { navLink = NavLink { navLabel = MsgMenuSheetOldUnassigned , navRoute = CourseR tid ssh csh SheetOldUnassignedR + , navDownload = Nothing , navAccess' = runDBRead . maybeT (return False) $ do void . MaybeT $ sheetOldUnassigned tid ssh csh @@ -1592,6 +1694,7 @@ pageActions (CourseR tid ssh csh SheetListR) = do { navLink = NavLink { navLabel = MsgMenuSheetNew , navRoute = CourseR tid ssh csh SheetNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary <> navQuick NavQuickViewFavourite @@ -1605,6 +1708,7 @@ pageActions (CourseR tid ssh csh CUsersR) = return { navLink = NavLink { navLabel = MsgMenuCourseAddMembers , navRoute = CourseR tid ssh csh CAddUserR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1616,6 +1720,7 @@ pageActions (CourseR tid ssh csh CUsersR) = return { navLink = NavLink { navLabel = MsgMenuCourseApplications , navRoute = CourseR tid ssh csh CApplicationsR + , navDownload = Nothing , navAccess' = let courseWhere course = course <$ do E.where_ $ course E.^. CourseTerm E.==. E.val tid @@ -1643,6 +1748,7 @@ pageActions (CourseR tid ssh csh MaterialListR) = return { navLink = NavLink { navLabel = MsgMenuMaterialNew , navRoute = CourseR tid ssh csh MaterialNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1656,6 +1762,7 @@ pageActions (CMaterialR tid ssh csh mnm MShowR) = return { navLink = NavLink { navLabel = MsgMenuMaterialEdit , navRoute = CMaterialR tid ssh csh mnm MEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1667,6 +1774,7 @@ pageActions (CMaterialR tid ssh csh mnm MShowR) = return { navLink = NavLink { navLabel = MsgMenuMaterialDelete , navRoute = CMaterialR tid ssh csh mnm MDelR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -1679,6 +1787,7 @@ pageActions (CourseR tid ssh csh CTutorialListR) = return { navLink = NavLink { navLabel = MsgMenuTutorialNew , navRoute = CourseR tid ssh csh CTutorialNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1692,6 +1801,7 @@ pageActions (CTutorialR tid ssh csh tutn TEditR) = return { navLink = NavLink { navLabel = MsgMenuTutorialDelete , navRoute = CTutorialR tid ssh csh tutn TDeleteR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1704,6 +1814,7 @@ pageActions (CTutorialR tid ssh csh tutn TUsersR) = return { navLink = NavLink { navLabel = MsgMenuTutorialComm , navRoute = CTutorialR tid ssh csh tutn TCommR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1715,6 +1826,7 @@ pageActions (CTutorialR tid ssh csh tutn TUsersR) = return { navLink = NavLink { navLabel = MsgMenuTutorialEdit , navRoute = CTutorialR tid ssh csh tutn TEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1726,6 +1838,7 @@ pageActions (CTutorialR tid ssh csh tutn TUsersR) = return { navLink = NavLink { navLabel = MsgMenuTutorialDelete , navRoute = CTutorialR tid ssh csh tutn TDeleteR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1738,6 +1851,7 @@ pageActions (CourseR tid ssh csh CExamListR) = return { navLink = NavLink { navLabel = MsgMenuExamNew , navRoute = CourseR tid ssh csh CExamNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1754,6 +1868,7 @@ pageActions (CExamR tid ssh csh examn EShowR) = do { navLink = NavLink { navLabel = MsgMenuExamEdit , navRoute = CExamR tid ssh csh examn EEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1765,6 +1880,7 @@ pageActions (CExamR tid ssh csh examn EShowR) = do { navLink = NavLink { navLabel = MsgMenuExamUsers , navRoute = CExamR tid ssh csh examn EUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1776,6 +1892,7 @@ pageActions (CExamR tid ssh csh examn EShowR) = do { navLink = NavLink { navLabel = MsgMenuExamGrades , navRoute = CExamR tid ssh csh examn EGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1787,6 +1904,7 @@ pageActions (CExamR tid ssh csh examn EShowR) = do { navLink = NavLink { navLabel = MsgMenuExamCorrect , navRoute = CExamR tid ssh csh examn ECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1800,6 +1918,7 @@ pageActions (CExamR tid ssh csh examn ECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExamUsers , navRoute = CExamR tid ssh csh examn EUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1811,6 +1930,7 @@ pageActions (CExamR tid ssh csh examn ECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExamGrades , navRoute = CExamR tid ssh csh examn EGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1822,6 +1942,7 @@ pageActions (CExamR tid ssh csh examn ECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExamEdit , navRoute = CExamR tid ssh csh examn EEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1834,6 +1955,7 @@ pageActions (CExamR tid ssh csh examn EUsersR) = return { navLink = NavLink { navLabel = MsgMenuExamAddMembers , navRoute = CExamR tid ssh csh examn EAddUserR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1845,6 +1967,7 @@ pageActions (CExamR tid ssh csh examn EUsersR) = return { navLink = NavLink { navLabel = MsgMenuExamGrades , navRoute = CExamR tid ssh csh examn EGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1856,6 +1979,7 @@ pageActions (CExamR tid ssh csh examn EUsersR) = return { navLink = NavLink { navLabel = MsgMenuExamCorrect , navRoute = CExamR tid ssh csh examn ECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1869,6 +1993,7 @@ pageActions (CExamR tid ssh csh examn EGradesR) = return { navLink = NavLink { navLabel = MsgMenuExamUsers , navRoute = CExamR tid ssh csh examn EUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -1880,6 +2005,7 @@ pageActions (CExamR tid ssh csh examn EGradesR) = return { navLink = NavLink { navLabel = MsgMenuExamCorrect , navRoute = CExamR tid ssh csh examn ECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1895,6 +2021,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSubmissions , navRoute = CSheetR tid ssh csh shn SSubsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1909,6 +2036,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSubmissionOwn , navRoute = CSheetR tid ssh csh shn SubmissionOwnR + , navDownload = Nothing , navAccess' = runDBRead . maybeT (return False) $ do uid <- MaybeT $ liftHandler maybeAuthId @@ -1927,6 +2055,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSheetPersonalisedFiles , navRoute = CSheetR tid ssh csh shn SPersonalFilesR + , navDownload = Nothing , navAccess' = let onlyPersonalised = fmap (maybe False $ not . E.unValue) . E.selectMaybe . E.from $ \(sheet `E.InnerJoin` course) -> do E.on $ sheet E.^. SheetCourse E.==. course E.^. CourseId @@ -1953,6 +2082,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSheetEdit , navRoute = CSheetR tid ssh csh shn SEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1964,6 +2094,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSheetClone , navRoute = (CourseR tid ssh csh SheetNewR, [("shn", toPathPiece shn)]) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1974,6 +2105,7 @@ pageActions (CSheetR tid ssh csh shn SShowR) = do { navLink = NavLink { navLabel = MsgMenuSheetDelete , navRoute = CSheetR tid ssh csh shn SDelR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -1986,6 +2118,7 @@ pageActions (CSheetR tid ssh csh shn SSubsR) = return { navLink = NavLink { navLabel = MsgMenuSubmissionNew , navRoute = CSheetR tid ssh csh shn SubmissionNewR + , navDownload = Nothing , navAccess' = let submissionAccess = hasWriteAccessTo $ CSheetR tid ssh csh shn SSubsR hasNoSubmission = maybeT (return False) $ do @@ -2010,6 +2143,7 @@ pageActions (CSheetR tid ssh csh shn SSubsR) = return , ("corrections-sheet", toPathPiece shn) ] ) + , navDownload = Nothing , navAccess' = (== Authorized) <$> evalAccessCorrector tid ssh csh , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -2021,6 +2155,7 @@ pageActions (CSheetR tid ssh csh shn SSubsR) = return { navLink = NavLink { navLabel = MsgMenuCorrectionsAssign , navRoute = CSheetR tid ssh csh shn SAssignR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -2034,6 +2169,7 @@ pageActions (CSubmissionR tid ssh csh shn cid SubShowR) = return { navLink = NavLink { navLabel = MsgMenuCorrection , navRoute = CSubmissionR tid ssh csh shn cid CorrectionR + , navDownload = Nothing , navAccess' = hasWriteAccessTo $ CSubmissionR tid ssh csh shn cid CorrectionR , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2045,6 +2181,7 @@ pageActions (CSubmissionR tid ssh csh shn cid SubShowR) = return { navLink = NavLink { navLabel = MsgCorrectorAssignTitle , navRoute = CSubmissionR tid ssh csh shn cid SubAssignR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -2056,6 +2193,7 @@ pageActions (CSubmissionR tid ssh csh shn cid SubShowR) = return { navLink = NavLink { navLabel = MsgMenuSubmissionDelete , navRoute = CSubmissionR tid ssh csh shn cid SubDelR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2068,6 +2206,7 @@ pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) = return { navLink = NavLink { navLabel = MsgCorrectorAssignTitle , navRoute = CSubmissionR tid ssh csh shn cid SubAssignR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -2079,6 +2218,7 @@ pageActions (CSubmissionR tid ssh csh shn cid CorrectionR) = return { navLink = NavLink { navLabel = MsgMenuSubmissionDelete , navRoute = CSubmissionR tid ssh csh shn cid SubDelR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2091,6 +2231,7 @@ pageActions (CourseR tid ssh csh CApplicationsR) = return { navLink = NavLink { navLabel = MsgMenuCourseApplicationsFiles , navRoute = CourseR tid ssh csh CAppsFilesR + , navDownload = Just Nothing -- If `navAccess'` is True, we definitely have either exactly one generated file or more than one file , navAccess' = let appAccess (E.Value appId) = do cID <- encrypt appId @@ -2114,6 +2255,7 @@ pageActions (CourseR tid ssh csh CApplicationsR) = return { navLink = NavLink { navLabel = MsgMenuCourseMembers , navRoute = CourseR tid ssh csh CUsersR + , navDownload = Nothing , navAccess' = runDBRead $ do cid <- getKeyBy404 $ TermSchoolCourseShort tid ssh csh @@ -2130,6 +2272,7 @@ pageActions CorrectionsR = return { navLink = NavLink { navLabel = MsgMenuCorrectionsDownload , navRoute = CorrectionsDownloadR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -2141,6 +2284,7 @@ pageActions CorrectionsR = return { navLink = NavLink { navLabel = MsgMenuCorrectionsUpload , navRoute = CorrectionsUploadR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -2152,6 +2296,7 @@ pageActions CorrectionsR = return { navLink = NavLink { navLabel = MsgMenuCorrectionsCreate , navRoute = CorrectionsCreateR + , navDownload = Nothing , navAccess' = runDBRead . maybeT (return False) $ do uid <- MaybeT $ liftHandler maybeAuthId sheets <- lift . E.select . E.from $ \(course `E.InnerJoin` sheet) -> do @@ -2176,6 +2321,7 @@ pageActions CorrectionsR = return { navLink = NavLink { navLabel = MsgMenuCorrectionsGrade , navRoute = CorrectionsGradeR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2191,6 +2337,7 @@ pageActions CorrectionsGradeR = do { navLink = NavLink { navLabel = MsgMenuCorrections , navRoute = CorrectionsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2204,6 +2351,7 @@ pageActions EExamListR = return { navLink = NavLink { navLabel = MsgMenuExternalExamNew , navRoute = EExamNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2217,6 +2365,7 @@ pageActions (EExamR tid ssh coursen examn EEShowR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamEdit , navRoute = EExamR tid ssh coursen examn EEEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2228,6 +2377,7 @@ pageActions (EExamR tid ssh coursen examn EEShowR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamUsers , navRoute = EExamR tid ssh coursen examn EEUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2239,6 +2389,7 @@ pageActions (EExamR tid ssh coursen examn EEShowR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamGrades , navRoute = EExamR tid ssh coursen examn EEGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2250,6 +2401,7 @@ pageActions (EExamR tid ssh coursen examn EEShowR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamCorrect , navRoute = EExamR tid ssh coursen examn EECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2263,6 +2415,7 @@ pageActions (EExamR tid ssh coursen examn EEGradesR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamCorrect , navRoute = EExamR tid ssh coursen examn EECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2274,6 +2427,7 @@ pageActions (EExamR tid ssh coursen examn EEGradesR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamUsers , navRoute = EExamR tid ssh coursen examn EEUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2285,6 +2439,7 @@ pageActions (EExamR tid ssh coursen examn EEGradesR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamEdit , navRoute = EExamR tid ssh coursen examn EEEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2298,6 +2453,7 @@ pageActions (EExamR tid ssh coursen examn EECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamGrades , navRoute = EExamR tid ssh coursen examn EEGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2309,6 +2465,7 @@ pageActions (EExamR tid ssh coursen examn EECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamUsers , navRoute = EExamR tid ssh coursen examn EEUsersR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2320,6 +2477,7 @@ pageActions (EExamR tid ssh coursen examn EECorrectR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamEdit , navRoute = EExamR tid ssh coursen examn EEEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2333,6 +2491,7 @@ pageActions (EExamR tid ssh coursen examn EEUsersR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamGrades , navRoute = EExamR tid ssh coursen examn EEGradesR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2344,6 +2503,7 @@ pageActions (EExamR tid ssh coursen examn EEUsersR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamCorrect , navRoute = EExamR tid ssh coursen examn EECorrectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2355,6 +2515,7 @@ pageActions (EExamR tid ssh coursen examn EEUsersR) = return { navLink = NavLink { navLabel = MsgMenuExternalExamEdit , navRoute = EExamR tid ssh coursen examn EEEditR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2368,6 +2529,7 @@ pageActions ParticipantsListR = return { navLink = NavLink { navLabel = MsgCsvOptions , navRoute = CsvOptionsR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -2380,6 +2542,7 @@ pageActions ParticipantsListR = return { navLink = NavLink { navLabel = MsgMenuParticipantsIntersect , navRoute = ParticipantsIntersectR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False} , navQuick' = navQuick NavQuickViewPageActionSecondary @@ -2393,6 +2556,7 @@ pageActions AdminWorkflowDefinitionListR = return { navLink = NavLink { navLabel = MsgMenuAdminWorkflowDefinitionNew , navRoute = AdminWorkflowDefinitionNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2404,6 +2568,7 @@ pageActions AdminWorkflowDefinitionListR = return { navLink = NavLink { navLabel = MsgMenuAdminWorkflowInstanceList , navRoute = AdminWorkflowInstanceListR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2417,6 +2582,7 @@ pageActions (AdminWorkflowDefinitionR wds wdn AWDEditR) = return { navLink = NavLink { navLabel = MsgMenuAdminWorkflowDefinitionDelete , navRoute = AdminWorkflowDefinitionR wds wdn AWDDeleteR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -2427,6 +2593,7 @@ pageActions (AdminWorkflowDefinitionR wds wdn AWDEditR) = return { navLink = NavLink { navLabel = MsgMenuAdminWorkflowDefinitionInstantiate , navRoute = AdminWorkflowDefinitionR wds wdn AWDInstantiateR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = True } , navQuick' = mempty @@ -2440,6 +2607,7 @@ pageActions AdminWorkflowInstanceListR = return { navLink = NavLink { navLabel = MsgMenuAdminWorkflowInstanceNew , navRoute = AdminWorkflowInstanceNewR + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2453,6 +2621,7 @@ pageActions route | Just (rScope, WorkflowInstanceListR) <- route ^? _WorkflowSc { navLink = NavLink { navLabel = MsgMenuWorkflowWorkflowList , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowListR) + , navDownload = Nothing , navAccess' = runDB $ haveWorkflowWorkflows rScope , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2466,6 +2635,7 @@ pageActions route | Just (rScope, WorkflowInstanceR win WIEditR) <- route ^? _Wo { navLink = NavLink { navLabel = MsgMenuWorkflowInstanceDelete , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIDeleteR) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2476,6 +2646,7 @@ pageActions route | Just (rScope, WorkflowInstanceR win WIEditR) <- route ^? _Wo { navLink = NavLink { navLabel = MsgMenuWorkflowInstanceWorkflows , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIWorkflowsR) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2487,6 +2658,7 @@ pageActions route | Just (rScope, WorkflowInstanceR win WIEditR) <- route ^? _Wo { navLink = NavLink { navLabel = MsgMenuWorkflowInstanceInitiate , navRoute = _WorkflowScopeRoute # (rScope, WorkflowInstanceR win WIInitiateR) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2500,6 +2672,7 @@ pageActions route | Just (rScope, WorkflowWorkflowR cID WWWorkflowR) <- route ^? { navLink = NavLink { navLabel = MsgMenuWorkflowWorkflowEdit , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWEditR) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2510,6 +2683,7 @@ pageActions route | Just (rScope, WorkflowWorkflowR cID WWWorkflowR) <- route ^? { navLink = NavLink { navLabel = MsgMenuWorkflowWorkflowDelete , navRoute = _WorkflowScopeRoute # (rScope, WorkflowWorkflowR cID WWDeleteR) + , navDownload = Nothing , navAccess' = return True , navType = NavTypeLink { navModal = False } , navQuick' = mempty @@ -2522,6 +2696,7 @@ pageActions TopWorkflowInstanceListR = return { navLink = NavLink { navLabel = MsgMenuTopWorkflowWorkflowList , navRoute = TopWorkflowWorkflowListR + , navDownload = Nothing , navAccess' = runDB haveTopWorkflowWorkflows , navType = NavTypeLink { navModal = False } , navQuick' = mempty diff --git a/src/Foundation/SiteLayout.hs b/src/Foundation/SiteLayout.hs index 6c39decf0..279e9d66f 100644 --- a/src/Foundation/SiteLayout.hs +++ b/src/Foundation/SiteLayout.hs @@ -192,7 +192,7 @@ siteLayout' overrideHeading widget = do . observeFavouritesQuickActionsDuration $ do $logDebugS "FavouriteQuickActions" $ tshow cK <> " Starting..." items' <- pageQuickActions NavQuickViewFavourite courseRoute - items <- forM items' $ \n@NavLink{navLabel} -> (mr navLabel,) <$> toTextUrl n + items <- forM items' $ \n@NavLink{navLabel} -> fmap (mr navLabel,) $ toTextUrl =<< navLinkRoute n $logDebugS "FavouriteQuickActions" $ tshow cK <> " Done." return items $logDebugS "FavouriteQuickActions" $ tshow cK <> " returning " <> tshow (is _Just items) @@ -203,7 +203,7 @@ siteLayout' overrideHeading widget = do , maybe (return []) pageActions mcurrentRoute ] nav' <- catMaybes <$> mapM (runMaybeT . navAccess) nav'' - nav <- forM nav' $ \n -> (n,,,) <$> newIdent <*> traverse toTextUrl (n ^? _navLink) <*> traverse (\nc -> (nc,, ) <$> newIdent <*> toTextUrl nc) (n ^. _navChildren) + nav <- forM nav' $ \n -> (n,,,) <$> newIdent <*> traverse (toTextUrl <=< navLinkRoute) (n ^? _navLink) <*> traverse (\nc -> (nc,, ) <$> newIdent <*> (toTextUrl <=< navLinkRoute) nc) (n ^. _navChildren) mmsgs <- if | isModal -> return mempty @@ -225,9 +225,9 @@ siteLayout' overrideHeading widget = do -- ^ highlight last route in breadcrumbs, favorites taking priority highlight = (highR ==) . Just . urlRoute where crumbs = mcons mcurrentRoute $ view _1 <$> reverse parents - navItems = map (view _2) favourites ++ toListOf (folded . typesUsing @NavChildren @NavLink . to urlRoute) nav + navItems = map (view _2) favourites ++ toListOf (folded . typesUsing @NavChildren @NavLink . to navBaseRoute) nav highR = find (`elem` navItems) . uncurry (++) $ partition (`elem` map (view _2) favourites) crumbs - highlightNav = (||) <$> navForceActive <*> highlight + highlightNav = (||) <$> navForceActive <*> (highlight . navBaseRoute) favouriteTermReason :: TermIdentifier -> FavouriteReason -> [(Course, Route UniWorX, Maybe [(Text, Text)], FavouriteReason, Bool, Bool, Bool)] favouriteTermReason tid favReason' = favourites & filter (\(Course{..}, _, _, favReason, _, _, _) -> unTermKey courseTerm == tid && favReason == favReason') @@ -243,15 +243,16 @@ siteLayout' overrideHeading widget = do navWidget (n, navIdent, navRoute', navChildren') = case n of NavHeader{ navLink = navLink@NavLink{..}, .. } | NavTypeLink{..} <- navType - , navModal - -> customModal Modal - { modalTriggerId = Just navIdent - , modalId = Nothing - , modalTrigger = \mroute ident -> case mroute of - Just route -> $(widgetFile "widgets/navbar/item") - Nothing -> error "navWidget with non-link modal" - , modalContent = Left $ SomeRoute navLink - } + , navModal -> do + modalContent <- liftHandler $ Left <$> navLinkRoute navLink + customModal Modal + { modalTriggerId = Just navIdent + , modalId = Nothing + , modalTrigger = \mroute ident -> case mroute of + Just route -> $(widgetFile "widgets/navbar/item") + Nothing -> error "navWidget with non-link modal" + , modalContent + } | NavTypeLink{} <- navType -> let route = navRoute' ident = navIdent @@ -259,14 +260,15 @@ siteLayout' overrideHeading widget = do NavPageActionPrimary{ navLink = navLink@NavLink{..} } -> let pWidget | NavTypeLink{..} <- navType - , navModal - = customModal Modal + , navModal = do + modalContent <- liftHandler $ Left <$> navLinkRoute navLink + customModal Modal { modalTriggerId = Just navIdent , modalId = Nothing , modalTrigger = \mroute ident -> case mroute of Just route -> $(widgetFile "widgets/pageaction/primary") Nothing -> error "navWidget with non-link modal" - , modalContent = Left $ SomeRoute navLink + , modalContent } | NavTypeLink{} <- navType = let route = navRoute' @@ -279,15 +281,16 @@ siteLayout' overrideHeading widget = do in $(widgetFile "widgets/pageaction/primary-wrapper") NavPageActionSecondary{ navLink = navLink@NavLink{..} } | NavTypeLink{..} <- navType - , navModal - -> customModal Modal - { modalTriggerId = Just navIdent - , modalId = Nothing - , modalTrigger = \mroute ident -> case mroute of - Just route -> $(widgetFile "widgets/pageaction/secondary") - Nothing -> error "navWidget with non-link modal" - , modalContent = Left $ SomeRoute navLink - } + , navModal -> do + modalContent <- liftHandler $ Left <$> navLinkRoute navLink + customModal Modal + { modalTriggerId = Just navIdent + , modalId = Nothing + , modalTrigger = \mroute ident -> case mroute of + Just route -> $(widgetFile "widgets/pageaction/secondary") + Nothing -> error "navWidget with non-link modal" + , modalContent + } | NavTypeLink{} <- navType -> let route = navRoute' ident = navIdent @@ -307,25 +310,27 @@ siteLayout' overrideHeading widget = do navContainerItemWidget (n, _navIdent, _navRoute', _navChildren') (iN@NavLink{..}, iNavIdent, iNavRoute) = case n of NavHeaderContainer{} | NavTypeLink{..} <- navType - , navModal - -> customModal Modal - { modalTriggerId = Just iNavIdent - , modalId = Nothing - , modalTrigger = \mroute ident -> case mroute of - Just route -> $(widgetFile "widgets/navbar/navbar-container-item--link") - Nothing -> error "navWidget with non-link modal" - , modalContent = Left $ SomeRoute iN - } + , navModal -> do + modalContent <- liftHandler $ Left <$> navLinkRoute iN + customModal Modal + { modalTriggerId = Just iNavIdent + , modalId = Nothing + , modalTrigger = \mroute ident -> case mroute of + Just route -> $(widgetFile "widgets/navbar/navbar-container-item--link") + Nothing -> error "navWidget with non-link modal" + , modalContent + } | NavTypeLink{} <- navType -> let route = iNavRoute ident = iNavIdent in $(widgetFile "widgets/navbar/navbar-container-item--link") | NavTypeButton{..} <- navType -> do csrfToken <- reqToken <$> getRequest + formAction <- liftHandler $ Just <$> navLinkRoute iN wrapForm $(widgetFile "widgets/navbar/navbar-container-item--button") def { formMethod = navMethod , formSubmit = FormNoSubmit - , formAction = Just $ SomeRoute iN + , formAction } _other -> error "not implemented" diff --git a/src/Foundation/Yesod/Middleware.hs b/src/Foundation/Yesod/Middleware.hs index a95473ad7..a17b5b081 100644 --- a/src/Foundation/Yesod/Middleware.hs +++ b/src/Foundation/Yesod/Middleware.hs @@ -87,9 +87,14 @@ yesodMiddleware = storeBearerMiddleware . csrfMiddleware . dryRunMiddleware . ob csrfMiddleware :: HandlerFor UniWorX a -> HandlerFor UniWorX a csrfMiddleware handler = do hasBearer <- is _Just <$> lookupBearerAuth + reqHost <- W.requestHeaderHost <$> waiRequest + userGeneratedHost <- getsYesod $ \app -> + guardOnM (views _appRoot ($ ApprootDefault) app /= views _appRoot ($ ApprootUserGenerated) app) $ approotScopeHost ApprootUserGenerated app - if | hasBearer -> local (\HandlerData{..} -> HandlerData{ handlerRequest = handlerRequest { reqToken = Nothing }, .. }) handler - | otherwise -> csrfSetCookieMiddleware' . defaultCsrfCheckMiddleware $ handler + if | hasBearer || fromMaybe False ((==) <$> reqHost <*> userGeneratedHost) + -> local (\HandlerData{..} -> HandlerData{ handlerRequest = handlerRequest { reqToken = Nothing }, .. }) handler + | otherwise + -> csrfSetCookieMiddleware' . defaultCsrfCheckMiddleware $ handler where csrfSetCookieMiddleware' handler' = do mcsrf <- reqToken <$> getRequest @@ -150,7 +155,8 @@ routeNormalizers :: forall m backend. , BearerAuthSite UniWorX ) => [Route UniWorX -> WriterT Any (ReaderT backend m) (Route UniWorX)] routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) .) - [ normalizeRender + [ normalizeApproot + , normalizeRender , ncSchool , ncAllocation , ncCourse @@ -169,6 +175,12 @@ routeNormalizers = map (hoist (hoist liftHandler . withReaderT projectBackend) . , verifyMaterialVideo ] where + normalizeApproot route = (route <$) . runMaybeT $ do + reqHost <- MaybeT $ W.requestHeaderHost <$> waiRequest + approotHost <- MaybeT . getsYesod . approotScopeHost $ authoritiveApproot route + when (approotHost /= reqHost) $ + tell $ Any True + normalizeRender :: Route UniWorX -> WriterT Any (ReaderT SqlReadBackend (HandlerFor UniWorX)) (Route UniWorX) normalizeRender route = route <$ do YesodRequest{..} <- liftHandler getRequest diff --git a/src/Foundation/Yesod/Session.hs b/src/Foundation/Yesod/Session.hs index f8ed7274d..297e71431 100644 --- a/src/Foundation/Yesod/Session.hs +++ b/src/Foundation/Yesod/Session.hs @@ -17,46 +17,60 @@ import Web.Cookie makeSessionBackend :: Yesod UniWorX => UniWorX -> IO (Maybe SessionBackend) -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 . W.extractBearerAuth) aHdrs - = return (mempty, const $ return []) - | otherwise - = load req - in SessionBackend load' +makeSessionBackend app@UniWorX{ appSettings' = AppSettings{..}, ..} = notFor isUserGenerated . notFor isBearer . 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 + + notFor :: (W.Request -> IO Bool) -> IO (Maybe SessionBackend) -> IO (Maybe SessionBackend) + notFor f = fmap $ fmap notFor' + where notFor' :: SessionBackend -> SessionBackend + notFor' (SessionBackend load) = SessionBackend $ \req -> do + pMatches <- f req + if | not pMatches -> load req + | otherwise -> return (mempty, const $ return []) + + + isBearer req = return $ if + | aHdrs <- mapMaybe (\(h, v) -> v <$ guard (h == W.hAuthorization)) $ W.requestHeaders req + , any (is _Just . W.extractBearerAuth) aHdrs + -> True + | otherwise + -> False + + isUserGenerated req = return $ if + | Just approotHost <- approotScopeHost ApprootUserGenerated app + , Just reqHost <- W.requestHeaderHost req + , views _appRoot ($ ApprootUserGenerated) app /= views _appRoot ($ ApprootDefault) app + , reqHost == approotHost + -> True + | otherwise + -> False diff --git a/src/Handler/Admin/Tokens.hs b/src/Handler/Admin/Tokens.hs index 826f42f79..f78bb5c1b 100644 --- a/src/Handler/Admin/Tokens.hs +++ b/src/Handler/Admin/Tokens.hs @@ -86,7 +86,7 @@ postAdminTokensR = do & HashSet.insert (Right uid) & HashSet.map (left toJSON) - fmap Just . encodeBearer . set _bearerRestrictions btfRestrict =<< bearerToken btfAuthority' btfRoutes btfAddAuth btfExpiresAt btfStartsAt + fmap Just . encodeBearer . set _bearerRestrictions btfRestrict =<< bearerToken btfAuthority' Nothing (maybe HashMap.empty (HashMap.singleton BearerTokenRouteEval) btfRoutes) btfAddAuth btfExpiresAt btfStartsAt siteLayoutMsg MsgMenuAdminTokens $ do setTitleI MsgMenuAdminTokens diff --git a/src/Handler/Allocation/AddUser.hs b/src/Handler/Allocation/AddUser.hs index 689919c45..ea927ce9e 100644 --- a/src/Handler/Allocation/AddUser.hs +++ b/src/Handler/Allocation/AddUser.hs @@ -13,6 +13,8 @@ import qualified Data.Conduit.Combinators as C import qualified Database.Esqueleto as E +import Handler.Course.Show + data AllocationAddUserForm = AllocationAddUserForm { aauUser :: UserId @@ -115,7 +117,12 @@ allocationApplicationsForm aId courses FieldSettings{..} fvRequired = formToAFor afmApplicantEdit = True afmLecturer = True - appsRes' <- iforM courses $ \cId (course, allocCourse, hasApplicationTemplate) -> over _2 (course, allocCourse, hasApplicationTemplate, ) <$> applicationForm (Just aId) cId Nothing ApplicationFormMode{..} Nothing + appsRes' <- iforM courses $ \cId (course, allocCourse, hasApplicationTemplate) -> do + mApplicationTemplate <- runMaybeT $ do + guard hasApplicationTemplate + let Course{..} = course + liftHandler . runDB $ toTextUrl <=< withFileDownloadToken (courseRegisterTemplateSource courseTerm courseSchool courseShorthand) $ CourseR courseTerm courseSchool courseShorthand CRegisterTemplateR + over _2 (course, allocCourse, mApplicationTemplate, ) <$> applicationForm (Just aId) cId Nothing ApplicationFormMode{..} Nothing let appsRes = sequenceA $ view _1 <$> appsRes' appsViews = view _2 <$> appsRes' @@ -123,7 +130,7 @@ allocationApplicationsForm aId courses FieldSettings{..} fvRequired = formToAFor [whamlet| $newline never
#{aInst} - $if hasApplicationTemplate + $maybe templateUrl <- mApplicationTemplate
-
+
#{iconRegisterTemplate} _{MsgCourseApplicationTemplateApplication}