Merge branch 'oauth2' into 142-userdata-oauth-mode

This commit is contained in:
Sarah Vaupel 2024-02-18 18:29:24 +01:00
commit c9f1bc4047
17 changed files with 575 additions and 24 deletions

64
.ports/assign.hs Normal file
View File

@ -0,0 +1,64 @@
-- SPDX-FileCopyrightText: 2024 David Mosbach <david.mosbach@uniworx.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
{-# Language OverloadedStrings, LambdaCase, TypeApplications #-}
import Data.Text (Text)
import qualified Data.Text as T
import System.Directory
import System.Environment
import System.IO
main :: IO ()
main = getArgs >>= \case
["--assign", offsetFile] -> parseOffsets offsetFile >>= uncurry nextOffset
["--remove", offset] -> removeOffset offset
_ -> fail "unsupported args"
parseOffsets :: FilePath -> IO (Int,Int)
parseOffsets offsetFile = do
user <- T.pack <$> getEnv "USER"
let pred x = "//" `T.isPrefixOf` x || T.null (T.strip x)
tokenise = map (filter (not . pred) . T.lines) . T.split (=='#')
extract = map tail . filter (\u -> not (null u) && user == (T.strip $ head u))
((extract . tokenise . T.pack) <$> readFile offsetFile) >>= \case
[[min,max]] -> return (read $ T.unpack min, read $ T.unpack max)
x -> print x >> fail "malformed offset file"
nextOffset :: Int -> Int -> IO ()
nextOffset min max
| min > max = nextOffset max min
| otherwise = do
home <- getEnv "HOME"
offset <- findFile [home] ".port-offsets" >>= \case
Nothing -> writeFile (home ++ "/.port-offsets") (show min) >> return min
Just path -> do
used <- (map (read @Int) . filter (not . null) . lines) <$> readFile path
o <- next min max used
appendFile path ('\n' : show o)
return o
print offset
where
next :: Int -> Int -> [Int] -> IO Int
next min max used
| min > max = fail "all offsets currently in use"
| min `elem` used = next (min+1) max used
| otherwise = return min
removeOffset :: String -> IO ()
removeOffset offset = do
home <- getEnv "HOME"
findFile [home] ".port-offsets" >>= \case
Nothing -> fail "offset file does not exist"
Just path -> do
remaining <- (filter (/= offset) . lines) <$> readFile path
run <- getEnv "XDG_RUNTIME_DIR"
(tempPath, fh) <- openTempFile run ".port-offsets"
let out = unlines remaining
hPutStr fh $ out
case T.null (T.strip $ T.pack out) of
True -> removeFile path
False -> writeFile path $ out
removeFile tempPath

24
.ports/offsets Normal file
View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2024 David Mosbach <david.mosbach@uniworx.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
# gkleen
-1000
-950
# ishka
-949
-899
# jost
-898
-848
# mosbach
-847
-797
# savau
-796
-746

View File

@ -141,7 +141,8 @@ MenuSap: SAP Schnittstelle
MenuAvs: AVS Schnittstelle
MenuAvsSynchError: AVS Problemübersicht
MenuLdap: LDAP Schnittstelle
MenuLdap !ident-ok: LDAP
MenuOAuth2 !ident-ok: OAuth2
MenuApc: Druckerei
MenuPrintSend: Manueller Briefversand
MenuPrintDownload: Brief herunterladen

View File

@ -141,7 +141,8 @@ MenuSap: SAP Interface
MenuAvs: AVS Interface
MenuAvsSynchError: AVS Problem Overview
MenuLdap: LDAP Interface
MenuLdap: LDAP
MenuOAuth2: OAuth2
MenuApc: Printing
MenuPrintSend: Send Letter
MenuPrintDownload: Download Letter

3
routes
View File

@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
-- SPDX-FileCopyrightText: 2022-2023 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
@ -70,6 +70,7 @@
/admin/avs AdminAvsR GET POST
/admin/avs/#CryptoUUIDUser AdminAvsUserR GET
/admin/ldap AdminLdapR GET POST
/admin/oauth2 AdminOAuth2R GET POST
/admin/problems AdminProblemsR GET
/admin/problems/no-contact ProblemUnreachableR GET
/admin/problems/no-avs-id ProblemWithoutAvsId GET

View File

@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2022-2023 Gregor Kleen <gregor@kleen.consulting>, Sarah Vaupel <sarah.vaupel@uniworx.de>, Steffen Jost <jost@tcs.ifi.lmu.de>
# SPDX-FileCopyrightText: 2022-2024 Gregor Kleen <gregor@kleen.consulting>, Sarah Vaupel <sarah.vaupel@uniworx.de>, Steffen Jost <jost@tcs.ifi.lmu.de>, David Mosbach <david.mosbach@uniworx.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
@ -9,6 +9,12 @@ let
haskellPackages = pkgs.haskellPackages;
oauth2Flake = (builtins.getFlake "git+https://gitlab.uniworx.de/mosbach/oauth2-mock-server/?rev=d47908b4f7883b4b485abf1ee06645495ccdc7b3&ref=user-queries").packages.x86_64-linux;
oauth2MockServer = oauth2Flake.default;
mkOauth2DB = oauth2Flake.mkOauth2DB;
killOauth2DB = oauth2Flake.killOauth2DB;
postgresSchema = pkgs.writeText "schema.sql" ''
CREATE USER uniworx WITH SUPERUSER;
CREATE DATABASE uniworx_test;
@ -21,6 +27,17 @@ let
local all all trust
'';
oauth2Schema = pkgs.writeText "oauth2_schema.sql" ''
CREATE USER oauth2mock WITH SUPERUSER;
CREATE DATABASE test_users;
GRANT ALL ON DATABASE test_users TO oauth2mock;
'';
oauth2Hba = pkgs.writeText "oauth2_hba_file" ''
local all all trust
'';
develop = pkgs.writeScriptBin "develop" ''
#!${pkgs.zsh}/bin/zsh -e
@ -44,6 +61,9 @@ let
type cleanup_cache_memcached &>/dev/null && cleanup_cache_memcached
type cleanup_minio &>/dev/null && cleanup_minio
type cleanup_maildev &>/dev/null && cleanup_maildev
[[ -z "$OAUTH2_PGDIR" ]] || source ${killOauth2DB}/bin/killOauth2DB
[[ -z "$OAUTH2_PGHOST" ]] || pkill oauth2-mock-ser
[[ -z "$PORT_OFFSET" ]] || runghc .ports/assign.hs --remove $PORT_OFFSET
[ -f "''${basePath}/.develop.env" ] && rm -vf "''${basePath}/.develop.env"
set +x
@ -51,7 +71,17 @@ let
trap cleanup EXIT
export PORT_OFFSET=$(((16#$(sha256sum <<<"$(hostname -f):''${basePath}" | head -c 16)) % 1000))
export PORT_OFFSET=$(runghc .ports/assign.hs --assign .ports/offsets)
# export PORT_OFFSET=$(((16#$(sha256sum <<<"$(hostname -f):''${basePath}" | head -c 16)) % 1000))
if [[ -z "$OAUTH2_PGHOST" ]]; then
set -xe
export OAUTH2_SERVER_PORT=$((9443 + $PORT_OFFSET))
export OAUTH2_DB_PORT=$((9444 + $PORT_OFFSET))
source ${mkOauth2DB}/bin/mkOauth2DB
${oauth2MockServer}/bin/oauth2-mock-server&
set +xe
fi
if [[ -z "$PGHOST" ]]; then
set -xe
@ -271,6 +301,9 @@ in pkgs.mkShell {
export CHROME_BIN=${pkgs.chromium}/bin/chromium
'';
OAUTH2_HBA = oauth2Hba;
OAUTH2_DB_SCHEMA = oauth2Schema;
OAUTH2_TEST_USERS = ./test/Database/test-users.yaml;
nativeBuildInputs = [develop inDevelop killallUni2work diffRunning]
++ (with pkgs;
[ stack nodejs-14_x postgresql_12 openldap exiftool memcached minio minio-client

View File

@ -62,7 +62,7 @@ import Jobs
import qualified Data.Text.Encoding as Text
import qualified Data.Text as Text
import Yesod.Auth.OAuth2.AzureADv2 (oauth2AzureADv2)
import Yesod.Auth.OAuth2.AzureADv2 (oauth2AzureADv2Scoped)
import Yesod.Auth.Util.PasswordStore
import qualified Data.ByteString.Lazy as LBS
@ -356,8 +356,8 @@ makeFoundation appSettings''@AppSettings{..} = do
return . uncurry p $ fromJust mArgs
appAuthPlugins <- liftIO $ sequence [
return azureMockServer
, loadPlugin (oauth2AzureADv2 tenantID) "AZURE_ADV2"
(oauth2MockServer . fromJust) <$> lookupEnv "OAUTH2_SERVER_PORT"
, loadPlugin (oauth2AzureADv2Scoped ["openid", "profile", "offline_access"] tenantID) "AZURE_ADV2"
]

View File

@ -14,12 +14,17 @@ module Auth.OAuth2
) where
import qualified Data.CaseInsensitive as CI
import Data.Maybe (fromJust)
import Data.Text
import Import.NoFoundation
import Import.NoFoundation hiding (unpack)
import Network.HTTP.Simple (httpJSONEither, getResponseBody, JSONException)
import System.Environment (lookupEnv)
import Yesod.Auth.OAuth2
import Yesod.Auth.OAuth2.Prelude
import Yesod.Auth.OAuth2.Prelude hiding (encodeUtf8)
-- | Plugin name of the OAuth2 yesod plugin for Azure ADv2
apAzure :: Text
@ -87,21 +92,98 @@ instance FromJSON UserID where
parseJSON = withObject "UserID" $ \o ->
UserID <$> o .: "id"
azureMockServer :: YesodAuth m => AuthPlugin m
azureMockServer =
azureMockServer :: YesodAuth m => String -> AuthPlugin m
azureMockServer port =
let oa = OAuth2
{ oauth2ClientId = "uniworx"
, oauth2ClientSecret = Just "shh"
, oauth2AuthorizeEndpoint = fromString $ mockServerURL <> "/authorize"
{ oauth2ClientId = "42"
, oauth2ClientSecret = Just "shhh"
, oauth2AuthorizeEndpoint = (fromString $ mockServerURL <> "/auth") `withQuery` [scopeParam " " ["ID", "Profile"]]
, oauth2TokenEndpoint = fromString $ mockServerURL <> "/token"
, oauth2RedirectUri = Nothing
}
mockServerURL = "0.0.0.0/"
profileSrc = fromString $ mockServerURL <> "/foo"
in authOAuth2 apAzureMock oa $ \manager token -> do
mockServerURL = "http://localhost:" <> fromString port
profileSrc = fromString $ mockServerURL <> "/users/me"
in authOAuth2 mockPluginName oa $ \manager token -> do
(UserID userID, userResponse) <- authGetProfile apAzureMock manager token profileSrc
return Creds
{ credsPlugin = apAzureMock
, credsIdent = userID
, credsExtra = setExtra token userResponse
}
----------------------
---- User Queries ----
----------------------
data UserDataException = UserDataJSONException JSONException
| UserDataInternalException Text
deriving Show
instance Exception UserDataException
queryOAuth2User :: forall j m . (FromJSON j, MonadIO m, MonadThrow m, MonadHandler m)
=> Text
-> m (Either UserDataException j)
queryOAuth2User userID = runExceptT $ do
(queryUrl, tokenUrl) <- liftIO mkBaseUrls
req <- parseRequest $ "GET " ++ queryUrl ++ unpack userID
mTokens <- lookupSessionJson SessionOAuth2Token
unless (isJust mTokens) . throwE $ UserDataInternalException "Tried to load session Oauth2 tokens, but there are none"
# ifdef DEVELOPMENT
let secure = False
# else
let secure = True
# endif
newTokens <- refreshOAuth2Token @m (fromJust mTokens) tokenUrl secure
setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens)
eResult <- lift $ getResponseBody <$> httpJSONEither @m @j (req
{ secure = secure
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken newTokens)] })
case eResult of
Left x -> throwE $ UserDataJSONException x
Right x -> return x
mkBaseUrls :: IO (String, String)
mkBaseUrls = do
# ifndef DEVELOPMENT
Just tenantID <- lookupEnv "AZURE_TENANT_ID"
return ( "https://graph.microsoft.com/v1.0/users/"
, "https://login.microsoftonline.com/" ++ tenantID ++ "/oauth2/v2.0" )
# else
Just port <- lookupEnv "OAUTH2_SERVER_PORT"
let base = "http://localhost:" ++ port
return ( base ++ "/users/query?id="
, base ++ "/token" )
# endif
refreshOAuth2Token :: forall m. (MonadIO m, MonadThrow m, MonadHandler m)
=> (Maybe AccessToken, Maybe RefreshToken)
-> String
-> Bool
-> ExceptT UserDataException m OAuth2Token
refreshOAuth2Token (_, rToken) url secure
| isJust rToken = do
req <- parseRequest $ "POST " ++ url
let
body =
[ ("grant_type", "refresh_token")
, ("refresh_token", encodeUtf8 . rtoken $ fromJust rToken)
]
body' <- if secure then do
clientID <- liftIO $ fromJust <$> lookupEnv "CLIENT_ID"
clientSecret <- liftIO $ fromJust <$> lookupEnv "CLIENT_SECRET"
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), ("scope", "openid profile")]
else return $ ("scope", "ID Profile") : body
$logErrorS "\27[31mAdmin Handler\27[0m" $ tshow (requestBody $ urlEncodedBody body' req{ secure = secure })
eResult <- lift $ getResponseBody <$> httpJSONEither @m @OAuth2Token (urlEncodedBody body' req{ secure = secure })
case eResult of
Left x -> throwE $ UserDataJSONException x
Right x -> return x
| otherwise = throwE $ UserDataInternalException "Could not refresh access token. Refresh token is missing."
instance Show RequestBody where
show (RequestBodyLBS x) = show x
show _ = error ":("

View File

@ -116,6 +116,7 @@ breadcrumb AdminJobsR = i18nCrumb MsgBreadcrumbAdminJobs $ Just
breadcrumb AdminAvsR = i18nCrumb MsgMenuAvs $ Just AdminR
breadcrumb AdminAvsUserR{} = i18nCrumb MsgAvsPersonInfo $ Just AdminAvsR
breadcrumb AdminLdapR = i18nCrumb MsgMenuLdap $ Just AdminR
breadcrumb AdminOAuth2R = i18nCrumb MsgMenuOAuth2 $ Just AdminR
breadcrumb AdminProblemsR = i18nCrumb MsgProblemsHeading $ Just AdminR
breadcrumb ProblemUnreachableR = i18nCrumb MsgProblemsUnreachableHeading $ Just AdminProblemsR
breadcrumb ProblemWithoutAvsId = i18nCrumb MsgProblemsNoAvsIdHeading $ Just AdminProblemsR
@ -861,6 +862,14 @@ defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the
, navQuick' = mempty
, navForceActive = False
}
, NavLink
{ navLabel = MsgMenuOAuth2
, navRoute = AdminOAuth2R
, navAccess' = NavAccessTrue
, navType = NavTypeLink { navModal = False }
, navQuick' = mempty
, navForceActive = False
}
]
}
, return NavHeaderContainer

View File

@ -9,7 +9,7 @@ module Foundation.Routes
( module Foundation.Routes.Definitions
, module Foundation.Routes
) where
import Import.NoFoundation
import Foundation.Type

View File

@ -18,6 +18,18 @@ import Auth.LDAP
import Auth.OAuth2
import Auth.PWHash (apHash)
import Foundation.Type
import Foundation.Types
import Foundation.I18n
import Handler.Utils.Profile
import Handler.Utils.LdapSystemFunctions
import Handler.Utils.Memcached
import Foundation.Authorization (AuthorizationCacheKey(..))
import Yesod.Auth.Message
import Yesod.Auth.OAuth2 (getAccessToken, getRefreshToken)
import qualified Control.Monad.Catch as C (Handler(..))
-- import qualified Data.Aeson as Json (encode)
@ -50,6 +62,9 @@ authenticate :: ( MonadHandler m, HandlerSite m ~ UniWorX
-> m (AuthenticationResult UniWorX)
authenticate creds@Creds{..} = liftHandler . runDB . withReaderT projectBackend $ do
$logErrorS "Auth" $ "\a\27[31m" <> tshow creds <> "\27[0m" -- TODO: debug only
setSessionJson SessionOAuth2Token $ (getAccessToken creds, getRefreshToken creds)
sess <- getSession
$logErrorS "OAuth" $ "\27[34m" <> tshow sess <> "\27[0m"
now <- liftIO getCurrentTime
userAuthConf <- getsYesod $ view _appUserAuthConf
@ -84,7 +99,17 @@ authenticate creds@Creds{..} = liftHandler . runDB . withReaderT projectBackend
$logErrorS "Auth" $ tshow err
mr <- getMessageRender
excRecovery . ServerError $ mr MsgInternalLoginError
-- TODO: handle azure exceptions or generalize LdapUserException
, C.Handler $ \case
AzureUserNoResult -> do
$logWarnS "OAuth" $ "User lookup failed after successful login for " <> credsIdent
excRecovery . UserError $ IdentifierNotFound credsIdent
AzureUserAmbiguous -> do
$logWarnS "OAuth" $ "Multiple OAuth results for " <> credsIdent
excRecovery . UserError $ IdentifierNotFound credsIdent
err -> do
$logErrorS "OAuth" $ tshow err
mr <- getMessageRender
excRecovery . ServerError $ mr MsgInternalLdapError -- TODO where does this come from?
, C.Handler $ \(cExc :: UserConversionException) -> do
$logErrorS "Auth" $ tshow cExc
mr <- getMessageRender

View File

@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>
-- SPDX-FileCopyrightText: 2022-2023 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>, Steffen Jost <jost@tcs.ifi.lmu.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
@ -31,6 +31,7 @@ import Handler.Admin.Tokens as Handler.Admin
import Handler.Admin.Crontab as Handler.Admin
import Handler.Admin.Avs as Handler.Admin
import Handler.Admin.Ldap as Handler.Admin
import Handler.Admin.OAuth2 as Handler.Admin
getAdminR :: Handler Html

View File

@ -0,0 +1,59 @@
-- SPDX-FileCopyrightText: 2023-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>,David Mosbach <david.mosbach@uniworx.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
module Handler.Admin.OAuth2
( getAdminOAuth2R
, postAdminOAuth2R
) where
import Import
-- import qualified Data.CaseInsensitive as CI
import Data.Aeson.Encode.Pretty (encodePretty)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
--import qualified Data.Text.Encoding as Text
--import Foundation.Yesod.Auth (CampusUserConversionException())
import Handler.Utils
import Auth.OAuth2 (queryOAuth2User)
getAdminOAuth2R, postAdminOAuth2R :: Handler Html
getAdminOAuth2R = postAdminOAuth2R
postAdminOAuth2R = do
((presult, pwidget), penctype) <- runFormPost $ identifyForm ("adminOAuth2Lookup"::Text) $ \html ->
flip (renderAForm FormStandard) html $ areq textField (fslI MsgAdminUserIdent) Nothing
let procFormPerson :: Text -> Handler (Maybe T.Text)
procFormPerson lid = do --return . Just $ "Mock reply for id " <> lid
eUserData <- queryOAuth2User @Value lid
case eUserData of
Left e -> throwM e
Right userData -> return . Just . T.decodeUtf8 $ encodePretty userData
mOAuth2Data <- formResultMaybe presult procFormPerson
--((uresult, uwidget), uenctype) <- runFormPost $ identifyForm ("adminOAuth2Upsert"::Text) $ \html ->
-- flip (renderAForm FormStandard) html $ areq textField (fslI MsgAdminUserIdent) Nothing
--let procFormUpsert :: Text -> Handler (Maybe (Either CampusUserConversionException (Entity User)))
-- procFormUpsert lid = pure <$> runDB (try $ ldapLookupAndUpsert lid)
--mbLdapUpsert <- formResultMaybe uresult procFormUpsert
actionUrl <- fromMaybe AdminOAuth2R <$> getCurrentRoute
siteLayoutMsg MsgMenuOAuth2 $ do
setTitleI MsgMenuOAuth2
let personForm = wrapForm pwidget def
{ formAction = Just $ SomeRoute actionUrl
, formEncoding = penctype
}
--upsertForm = wrapForm uwidget def
-- { formAction = Just $ SomeRoute actionUrl
-- , formEncoding = uenctype
-- }
--presentUtf8 lv = Text.intercalate ", " (either tshow id . Text.decodeUtf8' <$> lv)
--presentLatin1 lv = Text.intercalate ", " ( Text.decodeLatin1 <$> lv)
-- TODO: use i18nWidgetFile instead if this is to become permanent
$(widgetFile "oauth2")

View File

@ -1,4 +1,4 @@
-- SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
-- SPDX-FileCopyrightText: 2022-2024 Gregor Kleen <gregor.kleen@ifi.lmu.de>, David Mosbach <david.mosbach@uniworx.de>
--
-- SPDX-License-Identifier: AGPL-3.0-or-later
@ -20,6 +20,7 @@ data SessionKey = SessionActiveAuthTags | SessionInactiveAuthTags
| SessionLang
| SessionError
| SessionFiles
| SessionOAuth2Token
deriving (Eq, Ord, Enum, Bounded, Read, Show, Generic)
deriving anyclass (Universe, Finite)

View File

@ -1,6 +1,6 @@
$newline never
$# SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>
$# SPDX-FileCopyrightText: 2022-2024 Gregor Kleen <gregor.kleen@ifi.lmu.de>,David Mosbach <david.mosbach@uniworx.de>
$#
$# SPDX-License-Identifier: AGPL-3.0-or-later
@ -9,7 +9,7 @@ $forall AuthPlugin{apName, apLogin} <- plugins
<section>
<h2>Azure
^{apLogin toParent}
$elseif apName == "uniworx_dev"
$elseif apName == "dev-oauth2-mock"
<section>
<h2>_{MsgDummyLoginTitle}
^{apLogin toParent}

19
templates/oauth2.hamlet Normal file
View File

@ -0,0 +1,19 @@
$newline never
$# SPDX-FileCopyrightText: 2023 David Mosbach <david.mosbach@uniworx.de>
$#
$# SPDX-License-Identifier: AGPL-3.0-or-later
<section>
<p>
OAuth2 User Search:
^{personForm}
$maybe answers <- mOAuth2Data
<h1>
Antwort: #
<dl .deflist>
<dt>
<pre>
#{answers}
<dd>

View File

@ -0,0 +1,231 @@
# SPDX-FileCopyrightText: 2024 David Mosbach <david.mosbach@uniworx.de>
#
# SPDX-License-Identifier: AGPL-3.0-or-later
special-users:
- default: &default-user
userIdent: null
userAuthentication: AuthLDAP
userLastAuthentication: null
userTokensIssuedAfter: null
userMatrikelnummer: null
userEmail: ""
userDisplayEmail: null
userDisplayName: null
userSurname: ""
userFirstName: ""
userTitle: null
userMaxFavourites: userDefaultMaxFavourites
userMaxFavouriteTerms: userDefaultMaxFavouriteTerms
userTheme: ThemeDefault
userDateTimeFormat: userDefaultDateTimeFormat
userDateFormat: userDefaultDateFormat
userTimeFormat: userDefaultTimeFormat
userDownloadFiles: userDefaultDownloadFiles
userWarningDays: userDefaultWarningDays
userLanguages: null
userCreated: now
userNotificationSettings: def
userLastLdapSynchronisation: null
userLdapPrimaryKey: null
userCsvOptions: def
userSex: null
userBirthday: null
userShowSex: userDefaultShowSex
userTelephone: null
userMobile: null
userCompanyPersonalNumber: null
userCompanyDepartment: null
userPinPassword: null
userPostAddress: null
userPostLastUpdate: null
userPrefersPostal: true
userExamOfficeGetSynced: userDefaultExamOfficeGetSynced
userExamOfficeGetLabels: userDefaultExamOfficeGetLabels
- gkleen:
<<: *default-user
userIdent: "G.Kleen@campus.lmu.de"
userLastAuthentication: now
userTokensIssuedAfter: now
userEmail: "G.Kleen@campus.lmu.de"
userDisplayEmail: "gregor.kleen@ifi.lmu.de"
userDisplayName: "Gregor Kleen"
userSurname: "Kleen"
userFirstName: "Gregor Julius Arthur"
userMaxFavourites: 6
userMaxFavouriteTerms: 1
userLanguages: ["en"]
# userCsvOptions = def { csvFormat = csvPreset # CsvPresetRFC }
userSex: SexMale
userCompanyPersonalNumber: "00000"
userPostAddress: "Büro 127 \nMathematisches Institut der Ludwig-Maximilians-Universität München \nTheresienstr. 39 \nD-80333 München"
- fhamann:
<<: *default-user
userIdent: "felix.hamann@campus.lmu.de"
userEmail: "noEmailKnown"
userDisplayEmail: "felix.hamann@campus.lmu.de"
userDisplayName: "Felix Hamann"
userSurname: "Hamann"
userFirstName: "Felix"
# userCsvOptions = def { csvFormat = csvPreset # CsvPresetExcel }
userSex: SexMale
userPinPassword: "tomatenmarmelade"
userPostAddress: "Erdbeerweg 24 \n12345 Schlumpfhausen \nTraumland"
- jost:
<<: *default-user
userIdent: "jost@tcs.ifi.lmu.de"
userAuthentication: pwSimple
userMatrikelnummer: "12345678"
userEmail: "S.Jost@Fraport.de"
userDisplayEmail: "jost@tcs.ifi.lmu.de"
userDisplayName: "Steffen Jost"
userSurname: "Jost"
userFirstName: "Steffen"
userTitle: "Dr."
userMaxFavourites: 14
userMaxFavouriteTerms: 4
userTheme: ThemeMossGreen
userSex: SexMale
# userBirthday = Just $ n_day $ 35 * (-365)
userTelephone: "+49 69 690-71706"
userMobile: "0173 69 99 646"
userCompanyPersonalNumber: "57138"
userCompanyDepartment: "AVN-AR2"
- maxMuster:
<<: *default-user
userIdent: "max@campus.lmu.de"
userLastAuthentication: now
userMatrikelnummer: "1299"
userEmail: "max@campus.lmu.de"
userDisplayEmail: "max@max.com"
userDisplayName: "Max Musterstudent"
userSurname: "Musterstudent"
userFirstName: "Max"
userMaxFavourites: 7
userTheme: ThemeAberdeenReds
userLanguages: ["de"]
userSex: SexMale
# userBirthday = Just $ n_day $ 27 * (-365)
userPrefersPostal: false
- tinaTester:
<<: *default-user
userIdent: "tester@campus.lmu.de"
userAuthentication: null
userMatrikelnummer: "999"
userEmail: "tester@campus.lmu.de"
userDisplayEmail: "tina@tester.example"
userDisplayName: "Tina Tester"
userSurname: "vön Tërrör¿"
userFirstName: "Sabrina"
userTitle: "Magister"
userMaxFavourites: 5
userTheme: ThemeAberdeenReds
userLanguages: ["sn"]
userSex: SexNotApplicable
# userBirthday = Just $ n_day 3
userCompanyPersonalNumber: "12345"
userPrefersPostal: false
- svaupel:
<<: *default-user
userIdent: "vaupel.sarah@campus.lmu.de"
userEmail: "vaupel.sarah@campus.lmu.de"
userDisplayEmail: "vaupel.sarah@campus.lmu.de"
userDisplayName: "Sarah Vaupel"
userSurname: "Vaupel"
userFirstName: "Sarah"
userMaxFavourites: 14
userMaxFavouriteTerms: 4
userTheme: ThemeMossGreen
userLanguages: null
userSex: SexFemale
userPrefersPostal: false
- sbarth:
<<: *default-user
userIdent: "Stephan.Barth@campus.lmu.de"
userEmail: "Stephan.Barth@lmu.de"
userDisplayEmail: "stephan.barth@ifi.lmu.de"
userDisplayName: "Stephan Barth"
userSurname: "Barth"
userFirstName: "Stephan"
userTheme: ThemeMossGreen
userSex: SexMale
userPrefersPostal: false
userExamOfficeGetSynced: false
userExamOfficeGetLabels: true
- _stranger1:
userIdent: "AVSID:996699"
userEmail: "E996699@fraport.de"
userDisplayEmail: ""
userDisplayName: "Stranger One"
userSurname: "One"
userFirstName: "Stranger"
userTheme: ThemeMossGreen
userSex: SexMale
userCompanyPersonalNumber: "E996699"
userCompanyDepartment: "AVN-Strange"
userPrefersPostal: false
userExamOfficeGetSynced: false
userExamOfficeGetLabels: true
- _stranger2:
userIdent: "AVSID:669966"
userEmail: "E669966@fraport.de"
userDisplayEmail: ""
userDisplayName: "Stranger Two"
userSurname: "Stranger"
userFirstName: "Two"
userTheme: ThemeMossGreen
userSex: SexMale
userCompanyPersonalNumber: "669966"
userCompanyDepartment: "AVN-Strange"
userPrefersPostal: false
userExamOfficeGetSynced: false
userExamOfficeGetLabels: true
- _stranger3:
userIdent: "AVSID:6969"
userEmail: "E6969@fraport.de"
userDisplayEmail: ""
userDisplayName: "Stranger 3 Three"
userSurname: "Three"
userFirstName: "Stranger"
userTheme: ThemeMossGreen
userSex: SexMale
userCompanyPersonalNumber: "E996699"
userCompanyDepartment: "AVN-Strange"
userPostAddress: "Kartoffelweg 12 \n666 Höllensumpf \nFreiland"
userPrefersPostal: false
userExamOfficeGetSynced: false
userExamOfficeGetLabels: true
random-users:
firstNames: [ "James", "John", "Robert", "Michael"
, "William", "David", "Mary", "Richard"
, "Joseph", "Thomas", "Charles", "Daniel"
, "Matthew", "Patricia", "Jennifer", "Linda"
, "Elizabeth", "Barbara", "Anthony", "Donald"
, "Mark", "Paul", "Steven", "Andrew"
, "Kenneth", "Joshua", "George", "Kevin"
, "Brian", "Edward", "Susan", "Ronald"
]
surnames: [ "Smith", "Johnson", "Williams", "Brown"
, "Jones", "Miller", "Davis", "Garcia"
, "Rodriguez", "Wilson", "Martinez", "Anderson"
, "Taylor", "Thomas", "Hernandez", "Moore"
, "Martin", "Jackson", "Thompson", "White"
, "Lopez", "Lee", "Gonzalez", "Harris"
, "Clark", "Lewis", "Robinson", "Walker"
, "Perez", "Hall", "Young", "Allen"
]
middlenames: [ null, "Jamesson" ]