feat(users): ldap-synchronise arbitrary subsets of users

This commit is contained in:
Gregor Kleen 2019-09-02 13:49:57 +02:00
parent 8a46a51de2
commit 07895368dd
11 changed files with 174 additions and 124 deletions

View File

@ -465,6 +465,8 @@ CloseAlert: Schliessen
Name: Name Name: Name
MatrikelNr: Matrikelnummer MatrikelNr: Matrikelnummer
LdapSynced: LDAP-Synchronisiert
LdapSyncedBefore: Letzte LDAP-Synchronisation vor
NoMatrikelKnown: Keine Matrikelnummer NoMatrikelKnown: Keine Matrikelnummer
Theme: Oberflächen Design Theme: Oberflächen Design
Favoriten: Anzahl gespeicherter Favoriten Favoriten: Anzahl gespeicherter Favoriten
@ -1433,6 +1435,7 @@ CsvColumnApplicationsRating: Bewertung der Bewerbung; "1.0", "1.3", "1.7", ...,
CsvColumnApplicationsComment: Kommentar zur Bewerbung; je nach Kurs-Einstellungen entweder nur als Notiz für die Kursverwalter oder Feedback für den Bewerber CsvColumnApplicationsComment: Kommentar zur Bewerbung; je nach Kurs-Einstellungen entweder nur als Notiz für die Kursverwalter oder Feedback für den Bewerber
Action: Aktion Action: Aktion
ActionNoUsersSelected: Keine Benutzer ausgewählt
DBCsvDuplicateKey: Zwei Zeilen der CSV-Dateien referenzieren den selben internen Datensatz und können daher nicht verarbeitet werden. DBCsvDuplicateKey: Zwei Zeilen der CSV-Dateien referenzieren den selben internen Datensatz und können daher nicht verarbeitet werden.
DBCsvDuplicateKeyTip: Entfernen Sie eine der unten aufgeführten Zeilen aus Ihren CSV-Dateien und versuchen Sie es erneut. DBCsvDuplicateKeyTip: Entfernen Sie eine der unten aufgeführten Zeilen aus Ihren CSV-Dateien und versuchen Sie es erneut.
@ -1591,6 +1594,6 @@ SchoolExamOffice: Prüfungsamt
ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden. ApplicationEditTip: Während des Bewerbungszeitraums können eigene Bewerbungen beliebig angepasst und auch wieder zurückgezogen werden.
BtnAdminSynchroniseLdap: Alle Ldap-Daten synchronisieren UserLdapSync: LDAP-Synchronisieren
LdapSynchronisationQueued: LDAP-Synchronisation angestoßen SynchroniseLdapUserQueued n@Int: LDAP-Synchronisation von #{n} #{pluralDE n "Benutzer" "Benutzern"} angestoßen
OldestLdapSynchronisation: Älteste LDAP-Synchronisation UserHijack: Sitzung übernehmen

2
routes
View File

@ -43,7 +43,7 @@
/robots.txt RobotsR GET !free /robots.txt RobotsR GET !free
/ HomeR GET !free / HomeR GET !free
/users UsersR GET -- no tags, i.e. admins only /users UsersR GET POST -- no tags, i.e. admins only
/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

View File

@ -15,6 +15,7 @@ import qualified Data.Binary as Binary
import Data.Time.Clock import Data.Time.Clock
import Data.Time.Calendar.Instances () import Data.Time.Calendar.Instances ()
import Web.PathPieces
instance Hashable DiffTime where instance Hashable DiffTime where
@ -31,6 +32,10 @@ instance PersistFieldSql NominalDiffTime where
deriving instance Generic UTCTime deriving instance Generic UTCTime
instance Hashable UTCTime instance Hashable UTCTime
instance PathPiece UTCTime where
toPathPiece = pack . formatTime defaultTimeLocale "%0Y-%m-%dT%H:%M:%S%Q%z"
fromPathPiece = parseTimeM False defaultTimeLocale "%Y-%m-%dT%H:%M:%S%Q%z" . unpack
instance Binary DiffTime where instance Binary DiffTime where
get = fromRational <$> Binary.get get = fromRational <$> Binary.get

View File

@ -29,43 +29,15 @@ import qualified Handler.Utils.TermCandidates as Candidates
-- import qualified Data.UUID.Cryptographic as UUID -- import qualified Data.UUID.Cryptographic as UUID
data AdminButton = BtnAdminSynchroniseLdap
deriving (Enum, Eq, Ord, Bounded, Read, Show, Generic, Typeable)
instance Universe AdminButton
instance Finite AdminButton
nullaryPathPiece ''AdminButton $ camelToPathPiece' 2
embedRenderMessage ''UniWorX ''AdminButton id
instance Button UniWorX AdminButton where
btnClasses _ = [BCIsButton, BCPrimary]
getAdminR :: Handler Html getAdminR :: Handler Html
getAdminR = do getAdminR = -- do
((ldapSyncRes, ldapSyncView), ldapSyncEnctype) <- runFormPost $ buttonForm' [BtnAdminSynchroniseLdap] siteLayoutMsg MsgAdminHeading $ do
setTitleI MsgAdminHeading
formResult ldapSyncRes $ \case [whamlet|
BtnAdminSynchroniseLdap -> do This shall become the Administrators' overview page.
queueJob' $ JobSynchroniseLdap 1 0 0 Its current purpose is to provide links to some important admin functions
addMessageI Success MsgLdapSynchronisationQueued |]
redirect AdminR
oldestLdapSync <- fmap (join . preview (_head . _Value)) . runDB . E.select . E.from $ \user -> do
E.orderBy [E.desc . E.isNothing $ user E.^. UserLastLdapSynchronisation, E.asc $ user E.^. UserLastLdapSynchronisation]
E.limit 1
return $ user E.^. UserLastLdapSynchronisation
oldestLdapSync' <- for oldestLdapSync $ formatTime SelFormatDateTime
siteLayoutMsg MsgAdminHeading $ do
setTitleI MsgAdminHeading
wrapForm $(widgetFile "admin/ldapSync") def
{ formAction = Just $ SomeRoute AdminR
, formSubmit = FormNoSubmit
, formEncoding = ldapSyncEnctype
}
-- BEGIN - Buttons needed only here -- BEGIN - Buttons needed only here
data ButtonCreate = CreateMath | CreateInf -- Dummy for Example data ButtonCreate = CreateMath | CreateInf -- Dummy for Example

View File

@ -206,11 +206,10 @@ makeCourseUserTable cid restrict colChoices psValidator = do
, dbParamsFormAction = Just $ SomeRoute currentRoute , dbParamsFormAction = Just $ SomeRoute currentRoute
, dbParamsFormAttrs = [] , dbParamsFormAttrs = []
, dbParamsFormSubmit = FormSubmit , dbParamsFormSubmit = FormSubmit
, dbParamsFormAdditional = \csrf -> do , dbParamsFormAdditional
(res,vw) <- mreq (selectField optionsFinite) "" Nothing = renderAForm FormStandard
let formWgt = toWidget csrf <> fvInput vw $ (, mempty) . First . Just
formRes = (, mempty) . First . Just <$> res <$> areq (selectField optionsFinite) (fslI MsgAction) Nothing
return (formRes,formWgt)
, dbParamsFormEvaluate = liftHandlerT . runFormPost , dbParamsFormEvaluate = liftHandlerT . runFormPost
, dbParamsFormResult = id , dbParamsFormResult = id
, dbParamsFormIdent = def , dbParamsFormIdent = def

View File

@ -10,6 +10,7 @@ import Handler.Utils
import Handler.Utils.Tokens import Handler.Utils.Tokens
import Handler.Utils.Users import Handler.Utils.Users
import Handler.Utils.Invitations import Handler.Utils.Invitations
import Handler.Utils.Table.Cells
import qualified Auth.LDAP as Auth import qualified Auth.LDAP as Auth
@ -31,11 +32,10 @@ import Text.Hamlet (ihamlet)
import Data.Aeson hiding (Result(..)) import Data.Aeson hiding (Result(..))
hijackUserForm :: CryptoUUIDUser -> Form () hijackUserForm :: Form ()
hijackUserForm cID csrf = do hijackUserForm csrf = do
(uidResult, uidView) <- mforced hiddenField "" (cID :: CryptoUUIDUser) (btnResult, btnView) <- mopt (buttonField BtnHijack) "" Nothing
(btnResult, btnView) <- mreq (buttonField BtnHijack) "" Nothing return (btnResult >>= guard . is _Just, mconcat [toWidget csrf, fvInput btnView])
return (() <$ uidResult <* btnResult, mconcat [toWidget csrf, fvInput uidView, fvInput btnView])
-- In case of refactoring, use this: -- In case of refactoring, use this:
-- instance HasEntity (DBRow (Entity User)) User where -- instance HasEntity (DBRow (Entity User)) User where
@ -43,11 +43,21 @@ hijackUserForm cID csrf = do
-- instance HasUser (DBRow (Entity USer)) where -- instance HasUser (DBRow (Entity USer)) where
-- hasUser = _entityVal -- hasUser = _entityVal
getUsersR :: Handler Html data UserAction = UserLdapSync | UserHijack
getUsersR = do deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Universe UserAction
instance Finite UserAction
nullaryPathPiece ''UserAction $ camelToPathPiece' 1
embedRenderMessage ''UniWorX ''UserAction id
getUsersR, postUsersR :: Handler Html
getUsersR = postUsersR
postUsersR = do
let let
dbtColonnade = dbColonnade . mconcat $ dbtColonnade = mconcat $
[ dbRow [ dbRow
, dbSelect (applying _2) id (return . view (_dbrOutput . _entityKey))
, sortable (Just "name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM , sortable (Just "name") (i18nCell MsgName) $ \DBRow{ dbrOutput = Entity uid User{..} } -> anchorCellM
(AdminUserR <$> encrypt uid) (AdminUserR <$> encrypt uid)
(nameWidget userDisplayName userSurname) (nameWidget userDisplayName userSurname)
@ -58,9 +68,10 @@ getUsersR = do
-- (AdminUserR <$> encrypt uid) -- (AdminUserR <$> encrypt uid)
-- (toWidget . display $ last $ impureNonNull $ words $ userDisplayName) -- (toWidget . display $ last $ impureNonNull $ words $ userDisplayName)
, sortable (Just "auth-ldap") (i18nCell MsgAuthMode) $ \DBRow{ dbrOutput = Entity _ User{..} } -> i18nCell userAuthentication , sortable (Just "auth-ldap") (i18nCell MsgAuthMode) $ \DBRow{ dbrOutput = Entity _ User{..} } -> i18nCell userAuthentication
, sortable (Just "ldap-sync") (i18nCell MsgLdapSynced) $ \DBRow{ dbrOutput = Entity _ User{..} } -> maybe mempty dateTimeCell userLastLdapSynchronisation
, flip foldMap universeF $ \function -> , flip foldMap universeF $ \function ->
sortable Nothing (i18nCell function) $ \DBRow{ dbrOutput = Entity uid _ } -> flip (set' cellContents) mempty $ do sortable Nothing (i18nCell function) $ \DBRow{ dbrOutput = Entity uid _ } -> flip (set' cellContents) mempty $ do
schools <- lift . E.select . E.from $ \(school `E.InnerJoin` userFunction) -> do schools <- liftHandlerT . runDB . E.select . E.from $ \(school `E.InnerJoin` userFunction) -> do
E.on $ school E.^. SchoolId E.==. userFunction E.^. UserFunctionSchool E.on $ school E.^. SchoolId E.==. userFunction E.^. UserFunctionSchool
E.where_ $ userFunction E.^. UserFunctionUser E.==. E.val uid E.where_ $ userFunction E.^. UserFunctionUser E.==. E.val uid
E.&&. userFunction E.^. UserFunctionFunction E.==. E.val function E.&&. userFunction E.^. UserFunctionFunction E.==. E.val function
@ -72,29 +83,43 @@ getUsersR = do
$forall (E.Value sh) <- schools $forall (E.Value sh) <- schools
<li>#{sh} <li>#{sh}
|] |]
, sortable Nothing mempty $ \DBRow{ dbrOutput = Entity uid _ } -> cell $ do , sortable Nothing mempty $ \inp@DBRow{ dbrOutput = Entity uid _ } -> FormCell
cID <- encrypt uid { formCellAttrs = []
mayHijack <- (== Authorized) <$> evalAccess (AdminHijackUserR cID) True , formCellLens = id
myUid <- liftHandlerT maybeAuthId , formCellContents = do
when (mayHijack && Just uid /= myUid) $ do cID <- encrypt uid
(hijackView, hijackEnctype) <- liftHandlerT . generateFormPost $ hijackUserForm cID mayHijack <- (== Authorized) <$> evalAccess (AdminHijackUserR cID) True
wrapForm hijackView FormSettings myUid <- liftHandlerT maybeAuthId
{ formMethod = POST if
, formAction = Just . SomeRoute $ AdminHijackUserR cID | mayHijack
, formEncoding = hijackEnctype , Just uid /= myUid
, formAttrs = [] -> lift $ do
, formSubmit = FormNoSubmit let
, formAnchor = Nothing :: Maybe Text postprocess :: FormResult () -> FormResult (First UserAction, DBFormResult UserId Bool (DBRow (Entity User)))
} postprocess (FormSuccess ()) = FormSuccess (First $ Just UserHijack, DBFormResult $ Map.singleton uid (inp, const True))
postprocess FormMissing = FormSuccess mempty
postprocess (FormFailure errs) = FormFailure errs
over _1 postprocess <$> hijackUserForm mempty
| otherwise
-> return mempty
}
] ]
psValidator = def psValidator = def
& defaultSorting [SortAscBy "name", SortAscBy "display-name"] & defaultSorting [SortAscBy "name", SortAscBy "display-name"]
((), userList) <- runDB $ do (usersRes, userList) <- runDB $ do
schoolOptions <- map (CI.original . schoolName . entityVal &&& CI.original . unSchoolKey . entityKey) schoolOptions <- map (CI.original . schoolName . entityVal &&& CI.original . unSchoolKey . entityKey)
<$> selectList [] [Asc SchoolName] <$> selectList [] [Asc SchoolName]
dbTable psValidator DBTable let
postprocess :: FormResult (First UserAction, DBFormResult UserId Bool (DBRow (Entity User))) -> FormResult (UserAction, Set UserId)
postprocess inp = do
(First (Just act), usrMap) <- inp
let usrSet = Map.keysSet . Map.filter id $ getDBFormResult (const False) usrMap
return (act, usrSet)
over _1 postprocess <$> dbTable psValidator DBTable
{ dbtSQLQuery = return :: E.SqlExpr (Entity User) -> E.SqlQuery (E.SqlExpr (Entity User)) { dbtSQLQuery = return :: E.SqlExpr (Entity User) -> E.SqlQuery (E.SqlExpr (Entity User))
, dbtRowKey = (E.^. UserId) , dbtRowKey = (E.^. UserId)
, dbtColonnade , dbtColonnade
@ -112,6 +137,9 @@ getUsersR = do
, ( "auth-ldap" , ( "auth-ldap"
, SortColumn $ \user -> user E.^. UserAuthentication E.!=. E.val AuthLDAP , SortColumn $ \user -> user E.^. UserAuthentication E.!=. E.val AuthLDAP
) )
, ( "ldap-sync"
, SortColumn $ \user -> user E.^. UserLastLdapSynchronisation
)
] ]
, dbtFilter = Map.fromList -- OverloadedLists does not work with the templates , dbtFilter = Map.fromList -- OverloadedLists does not work with the templates
[ ( "user-search", FilterColumn $ \user (criteria :: Set.Set Text) -> [ ( "user-search", FilterColumn $ \user (criteria :: Set.Set Text) ->
@ -135,33 +163,68 @@ getUsersR = do
E.exists . E.from $ \ufunc -> E.where_ $ ufunc E.^. UserFunctionUser E.==. user E.^. UserId E.exists . E.from $ \ufunc -> E.where_ $ ufunc E.^. UserFunctionUser E.==. user E.^. UserId
E.&&. ufunc E.^. UserFunctionFunction `E.in_` schools E.&&. ufunc E.^. UserFunctionFunction `E.in_` schools
) )
, ( "ldap-sync", FilterColumn $ \user criteria -> if
| Just criteria' <- fromNullable criteria
-> let minTime = minimum (criteria' :: NonNull (Set UTCTime))
in E.maybe E.true (E.<=. E.val minTime) $ user E.^. UserLastLdapSynchronisation
| otherwise -> E.val True :: E.SqlExpr (E.Value Bool)
)
] ]
, dbtFilterUI = \mPrev -> mconcat , dbtFilterUI = \mPrev -> mconcat
[ prismAForm (singletonFilter "user-search") mPrev $ aopt textField (fslI MsgName) [ prismAForm (singletonFilter "user-search") mPrev $ aopt textField (fslI MsgName)
-- , prismAForm (singletonFilter "matriculation" ) mPrev $ aopt textField (fslI MsgMatrikelNr) -- , prismAForm (singletonFilter "matriculation" ) mPrev $ aopt textField (fslI MsgMatrikelNr)
, prismAForm (singletonFilter "matriculation" ) mPrev $ aopt matriculationField (fslI MsgMatrikelNr) , prismAForm (singletonFilter "matriculation") mPrev $ aopt matriculationField (fslI MsgMatrikelNr)
, prismAForm (singletonFilter "auth-ldap" . maybePrism _PathPiece) mPrev $ aopt (lift `hoistField` radioFieldList [(MsgAuthPWHash "", False), (MsgAuthLDAP, True)]) (fslI MsgAuthMode) , prismAForm (singletonFilter "auth-ldap" . maybePrism _PathPiece) mPrev $ aopt (lift `hoistField` selectFieldList [(MsgAuthPWHash "", False), (MsgAuthLDAP, True)]) (fslI MsgAuthMode)
, prismAForm (singletonFilter "school" ) mPrev $ aopt (lift `hoistField` selectFieldList schoolOptions) (fslI MsgCourseSchool) , prismAForm (singletonFilter "school") mPrev $ aopt (lift `hoistField` selectFieldList schoolOptions) (fslI MsgCourseSchool)
, prismAForm (singletonFilter "ldap-sync" . maybePrism _PathPiece) mPrev $ aopt utcTimeField (fslI MsgLdapSyncedBefore)
] ]
, dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout } , dbtStyle = def { dbsFilterLayout = defaultDBSFilterLayout }
, dbtParams = def , dbtParams = DBParamsForm
{ dbParamsFormMethod = POST
, dbParamsFormAction = Just $ SomeRoute UsersR
, dbParamsFormAttrs = []
, dbParamsFormSubmit = FormSubmit
, dbParamsFormAdditional
= renderAForm FormStandard
$ (, mempty) . First . Just
<$> areq (selectField $ optionsF [UserLdapSync]) (fslI MsgAction) Nothing
, dbParamsFormEvaluate = liftHandlerT . runFormPost
, dbParamsFormResult = id
, dbParamsFormIdent = def
}
, dbtIdent = "users" :: Text , dbtIdent = "users" :: Text
, dbtCsvEncode = noCsvEncode , dbtCsvEncode = noCsvEncode
, dbtCsvDecode = Nothing , dbtCsvDecode = Nothing
} }
formResult usersRes $ \case
(_, usersSet)
| Set.null usersSet -> do
addMessageI Info MsgActionNoUsersSelected
redirect UsersR
(UserLdapSync, userSet) -> do
runDBJobs . forM_ userSet $ \uid -> queueDBJob $ JobSynchroniseLdapUser uid
addMessageI Success . MsgSynchroniseLdapUserQueued $ Set.size userSet
redirect UsersR
(UserHijack, Set.minView -> Just (uid, _)) ->
hijackUser uid >>= sendResponse
_other -> error "Should not be possible"
defaultLayout $ do defaultLayout $ do
setTitleI MsgUserListTitle setTitleI MsgUserListTitle
$(widgetFile "users") $(widgetFile "users")
hijackUser :: UserId -> Handler TypedContent
hijackUser uid = do
User{userIdent} <- runDB $ get404 uid
setCredsRedirect $ Creds "dummy" (CI.original userIdent) []
postAdminHijackUserR :: CryptoUUIDUser -> Handler TypedContent postAdminHijackUserR :: CryptoUUIDUser -> Handler TypedContent
postAdminHijackUserR cID = do postAdminHijackUserR cID = do
uid <- decrypt cID uid <- decrypt cID
((hijackRes, _), _) <- runFormPost $ hijackUserForm cID ((hijackRes, _), _) <- runFormPost hijackUserForm
ret <- formResultMaybe hijackRes $ \() -> Just <$> do ret <- formResultMaybe hijackRes $ \() -> Just <$> hijackUser uid
User{userIdent} <- runDB $ get404 uid
setCredsRedirect $ Creds "dummy" (CI.original userIdent) []
maybe (redirect UsersR) return ret maybe (redirect UsersR) return ret

View File

@ -31,7 +31,7 @@ module Handler.Utils.Table.Pagination
, linkEitherCell, linkEitherCellM, linkEitherCellM' , linkEitherCell, linkEitherCellM, linkEitherCellM'
, cellTooltip , cellTooltip
, listCell , listCell
, formCell, DBFormResult, getDBFormResult , formCell, DBFormResult(..), getDBFormResult
, dbRow, dbSelect , dbRow, dbSelect
, (&) , (&)
, module Control.Monad.Trans.Maybe , module Control.Monad.Trans.Maybe

View File

@ -1,5 +1,5 @@
module Jobs.Handler.SynchroniseLdap module Jobs.Handler.SynchroniseLdap
( dispatchJobSynchroniseLdap ( dispatchJobSynchroniseLdap, dispatchJobSynchroniseLdapUser
, SynchroniseLdapException(..) , SynchroniseLdapException(..)
) where ) where
@ -10,25 +10,23 @@ import qualified Data.CaseInsensitive as CI
import Auth.LDAP import Auth.LDAP
import Jobs.Queue
data SynchroniseLdapException data SynchroniseLdapException
= SynchroniseLdapNoLdap = SynchroniseLdapNoLdap
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable) deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic, Typeable)
instance Exception SynchroniseLdapException instance Exception SynchroniseLdapException
dispatchJobSynchroniseLdap :: Natural -> Natural -> Natural -> Handler () dispatchJobSynchroniseLdap :: Natural -> Natural -> Natural -> Handler ()
dispatchJobSynchroniseLdap numIterations epoch iteration = do dispatchJobSynchroniseLdap numIterations epoch iteration
UniWorX{ appSettings' = AppSettings{..}, .. } <- getYesod = runDBJobs . runConduit $
case (,) <$> appLdapConf <*> appLdapPool of readUsers .| filterIteration .| sinkDBJobs
Just (ldapConf, ldapPool) ->
runDB . runConduit $
readUsers .| filterIteration .| synchroniseUser ldapConf ldapPool
Nothing ->
throwM SynchroniseLdapNoLdap
where where
readUsers :: Source (YesodDB UniWorX) UserId readUsers :: Source (YesodJobDB UniWorX) UserId
readUsers = selectKeys [] [] readUsers = selectKeys [] []
filterIteration :: Conduit UserId (YesodDB UniWorX) User filterIteration :: Conduit UserId (YesodJobDB UniWorX) Job
filterIteration = C.mapMaybeM $ \userId -> runMaybeT $ do filterIteration = C.mapMaybeM $ \userId -> runMaybeT $ do
let let
userIteration, currentIteration :: Integer userIteration, currentIteration :: Integer
@ -37,19 +35,27 @@ dispatchJobSynchroniseLdap numIterations epoch iteration = do
$logDebugS "SynchroniseLdap" [st|User ##{tshow (fromSqlKey userId)}: sync on #{tshow userIteration}/#{tshow numIterations}, now #{tshow currentIteration}|] $logDebugS "SynchroniseLdap" [st|User ##{tshow (fromSqlKey userId)}: sync on #{tshow userIteration}/#{tshow numIterations}, now #{tshow currentIteration}|]
guard $ userIteration == currentIteration guard $ userIteration == currentIteration
MaybeT $ get userId return $ JobSynchroniseLdapUser userId
synchroniseUser :: LdapConf -> LdapPool -> Sink User (YesodDB UniWorX) () dispatchJobSynchroniseLdapUser :: UserId -> Handler ()
synchroniseUser conf pool = C.mapM_ $ \user -> void . runMaybeT . handleExc $ do dispatchJobSynchroniseLdapUser jUser = do
$logInfoS "SynchroniseLdap" [st|Synchronising #{userIdent user}|] UniWorX{ appSettings' = AppSettings{..}, .. } <- getYesod
case (,) <$> appLdapConf <*> appLdapPool of
ldapAttrs <- MaybeT $ campusUser' conf pool user Just (ldapConf, ldapPool) ->
void . lift $ upsertCampusUser ldapAttrs Creds runDB . void . runMaybeT . handleExc $ do
{ credsIdent = CI.original $ userIdent user user@User{userIdent} <- MaybeT $ get jUser
, credsPlugin = "dummy"
, credsExtra = [] $logInfoS "SynchroniseLdap" [st|Synchronising #{userIdent}|]
}
where ldapAttrs <- MaybeT $ campusUser' ldapConf ldapPool user
handleExc void . lift $ upsertCampusUser ldapAttrs Creds
= catchMPlus (Proxy @CampusUserException) { credsIdent = CI.original userIdent
. catchMPlus (Proxy @CampusUserConversionException) , credsPlugin = "dummy"
, credsExtra = []
}
Nothing ->
throwM SynchroniseLdapNoLdap
where
handleExc
= catchMPlus (Proxy @CampusUserException)
. catchMPlus (Proxy @CampusUserConversionException)

View File

@ -55,6 +55,8 @@ data Job = JobSendNotification { jRecipient :: UserId, jNotification :: Notifica
, jEpoch , jEpoch
, jIteration :: Natural , jIteration :: Natural
} }
| JobSynchroniseLdapUser { jUser :: UserId
}
deriving (Eq, Ord, Show, Read, Generic, Typeable) deriving (Eq, Ord, Show, Read, Generic, Typeable)
data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId } data Notification = NotificationSubmissionRated { nSubmission :: SubmissionId }
| NotificationSheetActive { nSheet :: SheetId } | NotificationSheetActive { nSheet :: SheetId }

View File

@ -489,14 +489,14 @@ reorderField optList = Field{..}
withNum t n = tshow n <> "." <> t withNum t n = tshow n <> "." <> t
$(widgetFile "widgets/permutation/permutation") $(widgetFile "widgets/permutation/permutation")
optionsFinite :: ( MonadHandler m optionsF :: ( MonadHandler m
, Finite a , RenderMessage site (Element mono)
, RenderMessage site a , HandlerSite m ~ site
, HandlerSite m ~ site , PathPiece (Element mono)
, PathPiece a , MonoFoldable mono
) )
=> m (OptionList a) => mono -> m (OptionList (Element mono))
optionsFinite = do optionsF (otoList -> opts) = do
mr <- getMessageRender mr <- getMessageRender
let let
mkOption a = Option mkOption a = Option
@ -504,7 +504,17 @@ optionsFinite = do
, optionInternalValue = a , optionInternalValue = a
, optionExternalValue = toPathPiece a , optionExternalValue = toPathPiece a
} }
return . mkOptionList $ mkOption <$> universeF return . mkOptionList $ mkOption <$> opts
optionsFinite :: ( MonadHandler m
, Finite a
, RenderMessage site a
, HandlerSite m ~ site
, PathPiece a
)
=> m (OptionList a)
optionsFinite = optionsF universeF
fractionalField :: forall m a. fractionalField :: forall m a.
( RealFrac a ( RealFrac a

View File

@ -1,10 +0,0 @@
<dl>
<dt>
_{MsgOldestLdapSynchronisation}
<dd>
$maybe time <- oldestLdapSync'
#{time}
$nothing
_{MsgNever}
^{ldapSyncView}