UserNotificationR

This commit is contained in:
Gregor Kleen 2019-04-05 15:23:10 +02:00
parent cc8823c7ca
commit af6821c7c8
13 changed files with 105 additions and 13 deletions

View File

@ -206,6 +206,7 @@ UnauthorizedOr l@Text r@Text: (#{l} ODER #{r})
UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt. UnauthorizedNoToken: Ihrer Anfrage war kein Authorisierungs-Token beigefügt.
UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen. UnauthorizedTokenExpired: Ihr Authorisierungs-Token ist abgelaufen.
UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig. UnauthorizedTokenNotStarted: Ihr Authorisierungs-Token ist noch nicht gültig.
UnauthorizedTokenInvalid: Ihr Authorisierungs-Token konnte nicht verarbeitet werden.
UnauthorizedTokenInvalidRoute: Ihr Authorisierungs-Token ist auf dieser Unterseite nicht gültig. UnauthorizedTokenInvalidRoute: Ihr Authorisierungs-Token ist auf dieser Unterseite nicht gültig.
UnauthorizedSiteAdmin: Sie sind kein System-weiter Administrator. UnauthorizedSiteAdmin: Sie sind kein System-weiter Administrator.
UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen. UnauthorizedSchoolAdmin: Sie sind nicht als Administrator für dieses Institut eingetragen.
@ -235,6 +236,7 @@ UnsupportedAuthPredicate authTagT@Text shownRoute@String: "#{authTagT}" wurde au
UnauthorizedDisabledTag authTag@AuthTag: Authorisierungsprädikat "#{toPathPiece authTag}" ist für Ihre Sitzung nicht aktiv UnauthorizedDisabledTag authTag@AuthTag: Authorisierungsprädikat "#{toPathPiece authTag}" ist für Ihre Sitzung nicht aktiv
UnknownAuthPredicate tag@String: Authorisierungsprädikat "#{tag}" ist dem System nicht bekannt UnknownAuthPredicate tag@String: Authorisierungsprädikat "#{tag}" ist dem System nicht bekannt
UnauthorizedRedirect: Die angeforderte Seite existiert nicht oder Sie haben keine Berechtigung, die angeforderte Seite zu sehen. UnauthorizedRedirect: Die angeforderte Seite existiert nicht oder Sie haben keine Berechtigung, die angeforderte Seite zu sehen.
UnauthorizedSelf: Aktueller Nutzer ist nicht angegebener Benutzer.
EMail: E-Mail EMail: E-Mail
EMailUnknown email@UserEmail: E-Mail #{email} gehört zu keinem bekannten Benutzer. EMailUnknown email@UserEmail: E-Mail #{email} gehört zu keinem bekannten Benutzer.
@ -276,6 +278,7 @@ ImpressumHeading: Impressum
DataProtHeading: Datenschutzerklärung DataProtHeading: Datenschutzerklärung
SystemMessageHeading: Uni2work Statusmeldung SystemMessageHeading: Uni2work Statusmeldung
SystemMessageListHeading: Uni2work Statusmeldungen SystemMessageListHeading: Uni2work Statusmeldungen
NotificationSettingsHeading displayName@Text: Benachrichtigungs-Einstellungen für #{displayName}
HomeOpenCourses: Kurse mit offener Registrierung HomeOpenCourses: Kurse mit offener Registrierung
HomeUpcomingSheets: Anstehende Übungsblätter HomeUpcomingSheets: Anstehende Übungsblätter
@ -292,7 +295,8 @@ Plugin: Plugin
Ident: Identifikation Ident: Identifikation
LastLogin: Letzter Login LastLogin: Letzter Login
Settings: Individuelle Benutzereinstellungen Settings: Individuelle Benutzereinstellungen
SettingsUpdate: Einstellungen wurden gespeichert. SettingsUpdate: Einstellungen erfolgreich gespeichert
NotificationSettingsUpdate: Benachrichtigungs-Einstellungen erfolgreich gespeichert
Never: Nie Never: Nie
MultiFileUploadInfo: (Mehrere Dateien mit Shift oder Strg auswählen) MultiFileUploadInfo: (Mehrere Dateien mit Shift oder Strg auswählen)
@ -668,6 +672,7 @@ MenuCourseMembers: Kursteilnehmer
MenuTermShow: Semester MenuTermShow: Semester
MenuSubmissionDelete: Abgabe löschen MenuSubmissionDelete: Abgabe löschen
MenuUsers: Benutzer MenuUsers: Benutzer
MenuUserNotifications: Benachrichtigungs-Einstellungen
MenuAdminTest: Admin-Demo MenuAdminTest: Admin-Demo
MenuMessageList: Systemnachrichten MenuMessageList: Systemnachrichten
MenuAdminErrMsg: Fehlermeldung entschlüsseln MenuAdminErrMsg: Fehlermeldung entschlüsseln
@ -718,6 +723,7 @@ AuthTagOwner: Nutzer ist Besitzer
AuthTagRated: Korrektur ist bewertet AuthTagRated: Korrektur ist bewertet
AuthTagUserSubmissions: Abgaben erfolgen durch Kursteilnehmer AuthTagUserSubmissions: Abgaben erfolgen durch Kursteilnehmer
AuthTagCorrectorSubmissions: Abgaben erfolgen durch Korrektoren AuthTagCorrectorSubmissions: Abgaben erfolgen durch Korrektoren
AuthTagSelf: Nutzer greift nur auf eigene Daten zu
AuthTagAuthentication: Nutzer ist angemeldet, falls erforderlich AuthTagAuthentication: Nutzer ist angemeldet, falls erforderlich
AuthTagRead: Zugriff ist nur lesend AuthTagRead: Zugriff ist nur lesend
AuthTagWrite: Zugriff ist i.A. schreibend AuthTagWrite: Zugriff ist i.A. schreibend

2
routes
View File

@ -16,6 +16,7 @@
-- !registered -- participant for this course (no effect outside of courses) -- !registered -- participant for this course (no effect outside of courses)
-- !participant -- connected with a given course (not necessarily registered), i.e. has a submission, is a corrector, etc. (no effect outside of courses) -- !participant -- connected with a given course (not necessarily registered), i.e. has a submission, is a corrector, etc. (no effect outside of courses)
-- !owner -- part of the group of owners of this submission -- !owner -- part of the group of owners of this submission
-- !self -- route refers to the currently logged in user themselves
-- !capacity -- course this route is associated with has at least one unit of participant capacity -- !capacity -- course this route is associated with has at least one unit of participant capacity
-- !empty -- course this route is associated with has no participants whatsoever -- !empty -- course this route is associated with has no participants whatsoever
-- --
@ -39,6 +40,7 @@
/users/#CryptoUUIDUser AdminUserR GET POST /users/#CryptoUUIDUser AdminUserR GET POST
/users/#CryptoUUIDUser/delete AdminUserDeleteR POST /users/#CryptoUUIDUser/delete AdminUserDeleteR POST
/users/#CryptoUUIDUser/hijack AdminHijackUserR POST !adminANDno-escalation /users/#CryptoUUIDUser/hijack AdminHijackUserR POST !adminANDno-escalation
/users/#CryptoUUIDUser/notifications UserNotificationR GET POST !self
/admin AdminR GET /admin AdminR GET
/admin/features AdminFeaturesR GET POST /admin/features AdminFeaturesR GET POST
/admin/test AdminTestR GET POST /admin/test AdminTestR GET POST

View File

@ -482,7 +482,9 @@ tagAccessPredicate AuthToken = APDB $ \mAuthId route isWrite -> exceptT return r
BearerToken{..} <- catch (decodeToken jwt) $ \case BearerToken{..} <- catch (decodeToken jwt) $ \case
BearerTokenExpired -> throwError =<< unauthorizedI MsgUnauthorizedTokenExpired BearerTokenExpired -> throwError =<< unauthorizedI MsgUnauthorizedTokenExpired
BearerTokenNotStarted -> throwError =<< unauthorizedI MsgUnauthorizedTokenNotStarted BearerTokenNotStarted -> throwError =<< unauthorizedI MsgUnauthorizedTokenNotStarted
other -> throwM other other -> do
$logWarnS "AuthToken" $ tshow other
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalid
unless (maybe True (HashSet.member route) tokenRoutes) $ unless (maybe True (HashSet.member route) tokenRoutes) $
throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidRoute throwError =<< unauthorizedI MsgUnauthorizedTokenInvalidRoute
authorityVal <- evalAccessFor (Just tokenAuthority) route isWrite authorityVal <- evalAccessFor (Just tokenAuthority) route isWrite
@ -735,6 +737,20 @@ tagAccessPredicate AuthCorrectorSubmissions = APDB $ \_ route _ -> case route of
guard $ sheetSubmissionMode == CorrectorSubmissions guard $ sheetSubmissionMode == CorrectorSubmissions
return Authorized return Authorized
r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r r -> $unsupportedAuthPredicate AuthCorrectorSubmissions r
tagAccessPredicate AuthSelf = APHandler $ \mAuthId 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
CourseR _ _ _ (CUserR cID) -> return cID
_other -> throwError =<< $unsupportedAuthPredicate AuthSelf route
referencedUser' <- decrypt referencedUser
case mAuthId of
Just uid
| uid == referencedUser' -> return Authorized
Nothing -> return AuthenticationRequired
_other -> unauthorizedI MsgUnauthorizedSelf
tagAccessPredicate AuthAuthentication = APDB $ \mAuthId route _ -> case route of tagAccessPredicate AuthAuthentication = APDB $ \mAuthId route _ -> case route of
MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do MessageR cID -> maybeT (unauthorizedI MsgUnauthorizedSystemMessageAuth) $ do
smId <- decrypt cID smId <- decrypt cID
@ -1465,6 +1481,16 @@ pageActions (AdminR) =
, menuItemAccessCallback' = return True , menuItemAccessCallback' = return True
} }
] ]
pageActions (AdminUserR cID) = [
MenuItem
{ menuItemType = PageActionPrime
, menuItemLabel = MsgMenuUserNotifications
, menuItemIcon = Nothing
, menuItemRoute = SomeRoute $ UserNotificationR cID
, menuItemModal = True
, menuItemAccessCallback' = return True
}
]
pageActions (InfoR) = [ pageActions (InfoR) = [
MenuItem MenuItem
{ menuItemType = PageActionPrime { menuItemType = PageActionPrime

View File

@ -42,11 +42,10 @@ makeSettingForm template = identifyForm FIDsettings $ \html -> do
& setTooltip MsgDownloadFilesTip & setTooltip MsgDownloadFilesTip
) (stgDownloadFiles <$> template) ) (stgDownloadFiles <$> template)
<* aformSection MsgFormNotifications <* aformSection MsgFormNotifications
<*> (NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True) <*> notificationForm (stgNotificationSettings <$> template)
return (result, widget) -- no validation required here return (result, widget) -- no validation required here
where where
themeList = [Option (display t) t (toPathPiece t) | t <- universeF] themeList = [Option (display t) t (toPathPiece t) | t <- universeF]
nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template)
-- --
-- Version with proper grouping: -- Version with proper grouping:
-- --
@ -76,6 +75,12 @@ makeSettingForm template = identifyForm FIDsettings $ \html -> do
-- <*> (NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True) -- <*> (NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True)
-- nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template) -- nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt . stgNotificationSettings <$> template)
notificationForm :: Maybe NotificationSettings -> AForm Handler NotificationSettings
notificationForm template = NotificationSettings <$> funcForm nsForm (fslI MsgNotificationSettings) True
where
nsForm nt = fromMaybe False <$> aopt checkBoxField (fslI nt) (Just $ flip notificationAllowed nt <$> template)
getProfileR, postProfileR :: Handler Html getProfileR, postProfileR :: Handler Html
getProfileR = postProfileR getProfileR = postProfileR
postProfileR = do postProfileR = do
@ -532,3 +537,25 @@ postAuthPredsR = do
siteLayoutMsg MsgAuthPredsActive $ do siteLayoutMsg MsgAuthPredsActive $ do
setTitleI MsgAuthPredsActive setTitleI MsgAuthPredsActive
$(widgetFile "authpreds") $(widgetFile "authpreds")
getUserNotificationR, postUserNotificationR :: CryptoUUIDUser -> Handler Html
getUserNotificationR = postUserNotificationR
postUserNotificationR cID = do
uid <- decrypt cID
User{userNotificationSettings, userDisplayName} <- runDB $ get404 uid
((nsRes, nsInnerWdgt), nsEnc) <- runFormPost . renderAForm FormStandard . notificationForm $ Just userNotificationSettings
isModal <- hasCustomHeader HeaderIsModal
let formWidget = wrapForm nsInnerWdgt def
{ formAction = Just . SomeRoute $ UserNotificationR cID
, formEncoding = nsEnc
, formAttrs = [ ("data-ajax-submit", "") | isModal ]
}
formResultModal nsRes (UserNotificationR cID) $ \ns -> do
lift . runDB $ update uid [ UserNotificationSettings =. ns ]
tell . pure =<< messageI Success MsgNotificationSettingsUpdate
siteLayoutMsg (MsgNotificationSettingsHeading userDisplayName) $
formWidget

View File

@ -6,3 +6,4 @@ import Foundation as Import
import Import.NoFoundation as Import import Import.NoFoundation as Import
import Utils.SystemMessage as Import import Utils.SystemMessage as Import
import Model.Token as Import

View File

@ -6,6 +6,7 @@ module Jobs.Handler.SendNotification.CorrectionsAssigned
import Import import Import
import Jobs.Handler.SendNotification.Utils
import Handler.Utils.Mail import Handler.Utils.Mail
import Text.Hamlet import Text.Hamlet
@ -27,6 +28,7 @@ dispatchNotificationCorrectionsAssigned nUser nSheet jRecipient = do
MsgRenderer mr <- getMailMsgRenderer MsgRenderer mr <- getMailMsgRenderer
let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm let termDesc = mr . ShortTermIdentifier $ unTermKey courseTerm
editNotifications <- mkEditNotifications jRecipient
addAlternatives $ do addAlternatives $ do
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/correctionsAssigned.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/correctionsAssigned.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -7,6 +7,7 @@ module Jobs.Handler.SendNotification.SheetActive
import Import import Import
import Handler.Utils.Mail import Handler.Utils.Mail
import Jobs.Handler.SendNotification.Utils
import Text.Hamlet import Text.Hamlet
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -26,6 +27,7 @@ dispatchNotificationSheetActive nSheet jRecipient = userMailT jRecipient $ do
csh = courseShorthand csh = courseShorthand
shn = sheetName shn = sheetName
editNotifications <- mkEditNotifications jRecipient
addAlternatives $ do addAlternatives $ do
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/sheetActive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/sheetActive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -8,6 +8,7 @@ module Jobs.Handler.SendNotification.SheetInactive
import Import import Import
import Handler.Utils.Mail import Handler.Utils.Mail
import Jobs.Handler.SendNotification.Utils
import Text.Hamlet import Text.Hamlet
import qualified Data.CaseInsensitive as CI import qualified Data.CaseInsensitive as CI
@ -29,8 +30,9 @@ dispatchNotificationSheetSoonInactive nSheet jRecipient = userMailT jRecipient $
csh = courseShorthand csh = courseShorthand
shn = sheetName shn = sheetName
editNotifications <- mkEditNotifications jRecipient
addAlternatives $ do addAlternatives $ do
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/sheetSoonInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/sheetSoonInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))
dispatchNotificationSheetInactive :: SheetId -> UserId -> Handler () dispatchNotificationSheetInactive :: SheetId -> UserId -> Handler ()
@ -54,7 +56,8 @@ dispatchNotificationSheetInactive nSheet jRecipient = userMailT jRecipient $ do
csh = courseShorthand csh = courseShorthand
shn = sheetName shn = sheetName
editNotifications <- mkEditNotifications jRecipient
addAlternatives $ do addAlternatives $ do
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/sheetInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/sheetInactive.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -9,6 +9,7 @@ import Import
import Utils.Lens import Utils.Lens
import Handler.Utils.DateTime import Handler.Utils.DateTime
import Handler.Utils.Mail import Handler.Utils.Mail
import Jobs.Handler.SendNotification.Utils
import Text.Hamlet import Text.Hamlet
import qualified Data.Aeson as Aeson import qualified Data.Aeson as Aeson
@ -34,6 +35,8 @@ dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipien
csh = courseShorthand csh = courseShorthand
shn = sheetName shn = sheetName
editNotifications <- mkEditNotifications jRecipient
-- TODO: provide convienience template-haskell for `addAlternatives` -- TODO: provide convienience template-haskell for `addAlternatives`
addAlternatives $ do addAlternatives $ do
provideAlternative $ Aeson.object provideAlternative $ Aeson.object
@ -51,5 +54,4 @@ dispatchNotificationSubmissionRated nSubmission jRecipient = userMailT jRecipien
, "course-school" Aeson..= courseSchool , "course-school" Aeson..= courseSchool
] ]
-- provideAlternative $ \(MsgRenderer mr) -> ($(textFile "templates/mail/submissionRated.txt") :: TextUrl (Route UniWorX)) -- textFile does not support control statements -- provideAlternative $ \(MsgRenderer mr) -> ($(textFile "templates/mail/submissionRated.txt") :: TextUrl (Route UniWorX)) -- textFile does not support control statements
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/submissionRated.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/submissionRated.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -8,6 +8,7 @@ import Import
import Handler.Utils.Database import Handler.Utils.Database
import Handler.Utils.Mail import Handler.Utils.Mail
import Jobs.Handler.SendNotification.Utils
import Text.Hamlet import Text.Hamlet
-- import qualified Data.CaseInsensitive as CI -- import qualified Data.CaseInsensitive as CI
@ -21,7 +22,7 @@ dispatchNotificationUserRightsUpdate nUser _originalRights jRecipient = userMai
return (user,adminSchools,lecturerSchools) return (user,adminSchools,lecturerSchools)
setSubjectI $ MsgMailSubjectUserRightsUpdate userDisplayName setSubjectI $ MsgMailSubjectUserRightsUpdate userDisplayName
-- MsgRenderer mr <- getMailMsgRenderer -- MsgRenderer mr <- getMailMsgRenderer
editNotifications <- mkEditNotifications jRecipient
addAlternatives $ do addAlternatives $ do
let editNotifications = $(ihamletFile "templates/mail/editNotifications.hamlet")
providePreferredAlternative ($(ihamletFile "templates/mail/userRightsUpdate.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX)) providePreferredAlternative ($(ihamletFile "templates/mail/userRightsUpdate.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -0,0 +1,20 @@
module Jobs.Handler.SendNotification.Utils
( mkEditNotifications
) where
import Import
import Text.Hamlet
import qualified Data.HashSet as HashSet
mkEditNotifications :: (MonadHandler m, HandlerSite m ~ UniWorX) => UserId -> m (HtmlUrlI18n UniWorXMessage (Route UniWorX))
mkEditNotifications uid = liftHandlerT $ do
cID <- encrypt uid
jwt <- encodeToken =<< bearerToken uid (Just . HashSet.singleton $ UserNotificationR cID) Nothing Nothing Nothing
let
editNotificationsUrl :: SomeRoute UniWorX
editNotificationsUrl = SomeRoute (UserNotificationR cID, [(toPathPiece GetToken, toPathPiece jwt)])
editNotificationsUrl' <- toTextUrl editNotificationsUrl
return ($(ihamletFile "templates/mail/editNotifications.hamlet") :: HtmlUrlI18n UniWorXMessage (Route UniWorX))

View File

@ -727,6 +727,7 @@ data AuthTag -- sortiert nach gewünschter Reihenfolge auf /authpreds, d.h. Prä
| AuthCorrectorSubmissions | AuthCorrectorSubmissions
| AuthCapacity | AuthCapacity
| AuthEmpty | AuthEmpty
| AuthSelf
| AuthAuthentication | AuthAuthentication
| AuthNoEscalation | AuthNoEscalation
| AuthRead | AuthRead

View File

@ -1,4 +1,3 @@
<p> <p>
<a href=@{ProfileR}> <a href=#{editNotificationsUrl'}>
_{MsgProfileHeading} _{MsgMailEditNotifications}
\ _{MsgMailEditNotifications}