Merge branch 'oauth2' into 142-userdata-oauth-mode
This commit is contained in:
commit
c9f1bc4047
64
.ports/assign.hs
Normal file
64
.ports/assign.hs
Normal 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
24
.ports/offsets
Normal 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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
3
routes
@ -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
|
||||
|
||||
37
shell.nix
37
shell.nix
@ -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
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -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 ":("
|
||||
|
||||
@ -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
|
||||
|
||||
@ -9,7 +9,7 @@ module Foundation.Routes
|
||||
( module Foundation.Routes.Definitions
|
||||
, module Foundation.Routes
|
||||
) where
|
||||
|
||||
|
||||
import Import.NoFoundation
|
||||
import Foundation.Type
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
59
src/Handler/Admin/OAuth2.hs
Normal file
59
src/Handler/Admin/OAuth2.hs
Normal 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")
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
19
templates/oauth2.hamlet
Normal 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>
|
||||
|
||||
231
test/Database/test-users.yaml
Normal file
231
test/Database/test-users.yaml
Normal 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" ]
|
||||
|
||||
Reference in New Issue
Block a user