feat(users): ldap-synchronise arbitrary subsets of users
This commit is contained in:
parent
8a46a51de2
commit
07895368dd
@ -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
2
routes
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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 }
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
<dl>
|
|
||||||
<dt>
|
|
||||||
_{MsgOldestLdapSynchronisation}
|
|
||||||
<dd>
|
|
||||||
$maybe time <- oldestLdapSync'
|
|
||||||
#{time}
|
|
||||||
$nothing
|
|
||||||
_{MsgNever}
|
|
||||||
|
|
||||||
^{ldapSyncView}
|
|
||||||
Loading…
Reference in New Issue
Block a user