Merge branch '139-single-sign-on-sso-routing-anpassen' into 142-userdata-oauth-mode
This commit is contained in:
commit
cf6ae898c4
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 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-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, David Mosbach <david.mosbach@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
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -126,10 +126,9 @@ database:
|
|||||||
database: "_env:PGDATABASE:uniworx"
|
database: "_env:PGDATABASE:uniworx"
|
||||||
poolsize: "_env:PGPOOLSIZE:990"
|
poolsize: "_env:PGPOOLSIZE:990"
|
||||||
|
|
||||||
auto-db-migrate: '_env:AUTO_DB_MIGRATE:true'
|
auto-db-migrate: "_env:AUTO_DB_MIGRATE:true"
|
||||||
|
|
||||||
# External sources used for user authentication and userdata lookups
|
# External sources used for user authentication and userdata lookups
|
||||||
# TODO: add SSO option for user-auth config
|
|
||||||
user-auth:
|
user-auth:
|
||||||
# mode: single-source
|
# mode: single-source
|
||||||
protocol: azureadv2
|
protocol: azureadv2
|
||||||
@ -150,6 +149,8 @@ user-auth:
|
|||||||
# timeout: "_env:LDAPTIMEOUT:5"
|
# timeout: "_env:LDAPTIMEOUT:5"
|
||||||
# search-timeout: "_env:LDAPSEARCHTIME:5"
|
# search-timeout: "_env:LDAPSEARCHTIME:5"
|
||||||
|
|
||||||
|
single-sign-on: "_env:OIDC_SSO:true"
|
||||||
|
|
||||||
# TODO: generalize for arbitrary auth protocols
|
# TODO: generalize for arbitrary auth protocols
|
||||||
# TODO: maybe use separate pools for external databases?
|
# TODO: maybe use separate pools for external databases?
|
||||||
ldap-pool:
|
ldap-pool:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 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>, Winnie Ros <winnie.ros@campus.lmu.de>
|
# SPDX-FileCopyrightText: 2022-2024 David Mosbach <david.mosbach@uniworx.de>, 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>, Winnie Ros <winnie.ros@campus.lmu.de>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -139,3 +139,6 @@ FormHoneypotNamePlaceholder: Name
|
|||||||
FormHoneypotComment: Kommentar
|
FormHoneypotComment: Kommentar
|
||||||
FormHoneypotCommentPlaceholder: Kommentar
|
FormHoneypotCommentPlaceholder: Kommentar
|
||||||
FormHoneypotFilled: Bitte füllen Sie keines der verstecken Felder aus
|
FormHoneypotFilled: Bitte füllen Sie keines der verstecken Felder aus
|
||||||
|
|
||||||
|
Logout: Abmeldung
|
||||||
|
SingleSignOut: Abmeldung bei Azure
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 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>, Winnie Ros <winnie.ros@campus.lmu.de>
|
# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, David Mosbach <david.mosbach@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>, Sarah Vaupel <sarah.vaupel@ifi.lmu.de>, Steffen Jost <jost@tcs.ifi.lmu.de>, Winnie Ros <winnie.ros@campus.lmu.de>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -140,3 +140,6 @@ FormHoneypotNamePlaceholder !ident-ok: Name
|
|||||||
FormHoneypotComment: Comment
|
FormHoneypotComment: Comment
|
||||||
FormHoneypotCommentPlaceholder: Comment
|
FormHoneypotCommentPlaceholder: Comment
|
||||||
FormHoneypotFilled: Please do not fill in any of the hidden fields
|
FormHoneypotFilled: Please do not fill in any of the hidden fields
|
||||||
|
|
||||||
|
Logout: Logout
|
||||||
|
SingleSignOut: Azure logout
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
|
# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, David Mosbach <david.mosbach@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@cip.ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ MenuPayments: Zahlungsbedingungen
|
|||||||
MenuInstance: Instanz-Identifikation
|
MenuInstance: Instanz-Identifikation
|
||||||
MenuHealth: Instanz-Zustand
|
MenuHealth: Instanz-Zustand
|
||||||
MenuHelp: Hilfe
|
MenuHelp: Hilfe
|
||||||
|
MenuAccount: Konto
|
||||||
MenuProfile: Anpassen
|
MenuProfile: Anpassen
|
||||||
MenuLogin !ident-ok: Login
|
MenuLogin !ident-ok: Login
|
||||||
MenuLogout !ident-ok: Logout
|
MenuLogout !ident-ok: Logout
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
# SPDX-FileCopyrightText: 2022-2024 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>,Winnie Ros <winnie.ros@campus.lmu.de>
|
# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, David Mosbach <david.mosbach@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Winnie Ros <winnie.ros@campus.lmu.de>
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -24,6 +24,7 @@ MenuPayments: Payment Terms
|
|||||||
MenuInstance: Instance identification
|
MenuInstance: Instance identification
|
||||||
MenuHealth: Instance health
|
MenuHealth: Instance health
|
||||||
MenuHelp: Support
|
MenuHelp: Support
|
||||||
|
MenuAccount: Account
|
||||||
MenuProfile: Settings
|
MenuProfile: Settings
|
||||||
MenuLogin: Login
|
MenuLogin: Login
|
||||||
MenuLogout: Logout
|
MenuLogout: Logout
|
||||||
|
|||||||
5
routes
5
routes
@ -1,4 +1,4 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022-2024 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-FileCopyrightText: 2022-2024 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>,David Mosbach <david.mosbach@uniworx.de>
|
||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -46,6 +46,9 @@
|
|||||||
/static StaticR EmbeddedStatic appStatic !free
|
/static StaticR EmbeddedStatic appStatic !free
|
||||||
/auth AuthR Auth getAuth !free
|
/auth AuthR Auth getAuth !free
|
||||||
|
|
||||||
|
/logout SOutR GET !free
|
||||||
|
/logout/ssout SSOutR GET !free -- single sign-out (OIDC)
|
||||||
|
|
||||||
/metrics MetricsR GET !free -- verify if this can be free
|
/metrics MetricsR GET !free -- verify if this can be free
|
||||||
|
|
||||||
/err ErrorR GET !free
|
/err ErrorR GET !free
|
||||||
|
|||||||
@ -9,7 +9,8 @@ let
|
|||||||
|
|
||||||
haskellPackages = pkgs.haskellPackages;
|
haskellPackages = pkgs.haskellPackages;
|
||||||
|
|
||||||
oauth2Flake = (builtins.getFlake "git+https://gitlab.uniworx.de/mosbach/oauth2-mock-server/?rev=d47908b4f7883b4b485abf1ee06645495ccdc7b3&ref=user-queries").packages.x86_64-linux;
|
oauth2Flake = (builtins.getFlake "git+https://gitlab.uniworx.de/mosbach/oauth2-mock-server/?rev=7b995e6cffa963a24eb5d0373b2d29089533284f&ref=main").packages.x86_64-linux;
|
||||||
|
|
||||||
|
|
||||||
oauth2MockServer = oauth2Flake.default;
|
oauth2MockServer = oauth2Flake.default;
|
||||||
mkOauth2DB = oauth2Flake.mkOauth2DB;
|
mkOauth2DB = oauth2Flake.mkOauth2DB;
|
||||||
|
|||||||
@ -163,6 +163,7 @@ import Handler.PrintCenter
|
|||||||
import Handler.ApiDocs
|
import Handler.ApiDocs
|
||||||
import Handler.Swagger
|
import Handler.Swagger
|
||||||
import Handler.Firm
|
import Handler.Firm
|
||||||
|
import Handler.SingleSignOut
|
||||||
|
|
||||||
import ServantApi () -- YesodSubDispatch instances
|
import ServantApi () -- YesodSubDispatch instances
|
||||||
import Servant.API
|
import Servant.API
|
||||||
|
|||||||
@ -13,13 +13,14 @@ module Auth.OAuth2
|
|||||||
, azureMockServer
|
, azureMockServer
|
||||||
, queryOAuth2User
|
, queryOAuth2User
|
||||||
, refreshOAuth2Token
|
, refreshOAuth2Token
|
||||||
|
, singleSignOut
|
||||||
) where
|
) where
|
||||||
|
|
||||||
-- import qualified Data.CaseInsensitive as CI
|
-- import qualified Data.CaseInsensitive as CI
|
||||||
import Data.Maybe (fromJust)
|
import Data.Maybe (fromJust)
|
||||||
import Data.Text
|
import Data.Text
|
||||||
|
|
||||||
import Import.NoFoundation hiding (unpack)
|
import Import.NoFoundation hiding (pack, unpack)
|
||||||
|
|
||||||
import Network.HTTP.Simple (httpJSONEither, getResponseBody, JSONException)
|
import Network.HTTP.Simple (httpJSONEither, getResponseBody, JSONException)
|
||||||
|
|
||||||
@ -102,9 +103,9 @@ azureUserPreferredLanguage = "preferredLanguage"
|
|||||||
-- = runMaybeT . catchIfMaybeT (is _AzureUserNoResult) $ azureUser conf (Creds apAzure (CI.original userIdent) [])
|
-- = runMaybeT . catchIfMaybeT (is _AzureUserNoResult) $ azureUser conf (Creds apAzure (CI.original userIdent) [])
|
||||||
|
|
||||||
|
|
||||||
----------------------------------------
|
-----------------------------------------------
|
||||||
---- OAuth2 development auth plugin ----
|
---- OAuth2 + OIDC development auth plugin ----
|
||||||
----------------------------------------
|
-----------------------------------------------
|
||||||
|
|
||||||
apAzureMock :: Text
|
apAzureMock :: Text
|
||||||
apAzureMock = "uniworx_dev"
|
apAzureMock = "uniworx_dev"
|
||||||
@ -119,7 +120,11 @@ azureMockServer port =
|
|||||||
let oa = OAuth2
|
let oa = OAuth2
|
||||||
{ oauth2ClientId = "42"
|
{ oauth2ClientId = "42"
|
||||||
, oauth2ClientSecret = Just "shhh"
|
, oauth2ClientSecret = Just "shhh"
|
||||||
, oauth2AuthorizeEndpoint = (fromString $ mockServerURL <> "/auth") `withQuery` [scopeParam " " ["ID", "Profile"]]
|
, oauth2AuthorizeEndpoint = (fromString $ mockServerURL <> "/auth")
|
||||||
|
`withQuery` [ scopeParam " " ["openid", "profile", "email", "offline_access"] -- TODO read scopes from config
|
||||||
|
, ("response_type", "code id_token")
|
||||||
|
, ("nonce", "Foo") -- TODO generate meaningful value
|
||||||
|
]
|
||||||
, oauth2TokenEndpoint = fromString $ mockServerURL <> "/token"
|
, oauth2TokenEndpoint = fromString $ mockServerURL <> "/token"
|
||||||
, oauth2RedirectUri = Nothing
|
, oauth2RedirectUri = Nothing
|
||||||
}
|
}
|
||||||
@ -165,7 +170,8 @@ queryOAuth2User userID = runExceptT $ do
|
|||||||
setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens)
|
setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens)
|
||||||
eResult <- lift $ getResponseBody <$> httpJSONEither @m @j (req
|
eResult <- lift $ getResponseBody <$> httpJSONEither @m @j (req
|
||||||
{ secure = secure
|
{ secure = secure
|
||||||
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken newTokens)] })
|
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken newTokens)]
|
||||||
|
})
|
||||||
case eResult of
|
case eResult of
|
||||||
Left x -> throwE $ UserDataJSONException x
|
Left x -> throwE $ UserDataJSONException x
|
||||||
Right x -> return x
|
Right x -> return x
|
||||||
@ -204,8 +210,8 @@ refreshOAuth2Token (_, rToken) url secure
|
|||||||
body' <- if secure then do
|
body' <- if secure then do
|
||||||
clientID <- liftIO $ fromJust <$> lookupEnv "CLIENT_ID"
|
clientID <- liftIO $ fromJust <$> lookupEnv "CLIENT_ID"
|
||||||
clientSecret <- liftIO $ fromJust <$> lookupEnv "CLIENT_SECRET"
|
clientSecret <- liftIO $ fromJust <$> lookupEnv "CLIENT_SECRET"
|
||||||
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), ("scope", "openid profile")]
|
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), scopeParam " " ["openid","profile"," offline_access"]] -- TODO read from config
|
||||||
else return $ scopeParam " " ["ID","Profile"] : body
|
else return $ scopeParam " " ["openid","profile","offline_access"] : body -- TODO read from config
|
||||||
$logErrorS "\27[31mAdmin Handler\27[0m" $ tshow (requestBody $ urlEncodedBody body' req{ secure = secure })
|
$logErrorS "\27[31mAdmin Handler\27[0m" $ tshow (requestBody $ urlEncodedBody body' req{ secure = secure })
|
||||||
eResult <- lift $ getResponseBody <$> httpJSONEither @m @OAuth2Token (urlEncodedBody body' req{ secure = secure })
|
eResult <- lift $ getResponseBody <$> httpJSONEither @m @OAuth2Token (urlEncodedBody body' req{ secure = secure })
|
||||||
case eResult of
|
case eResult of
|
||||||
@ -216,3 +222,25 @@ refreshOAuth2Token (_, rToken) url secure
|
|||||||
instance Show RequestBody where
|
instance Show RequestBody where
|
||||||
show (RequestBodyLBS x) = show x
|
show (RequestBodyLBS x) = show x
|
||||||
show _ = error ":("
|
show _ = error ":("
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------
|
||||||
|
---- Single Sign-Out ----
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
singleSignOut :: forall a m. (MonadHandler m)
|
||||||
|
=> Maybe Text -- ^ redirect uri
|
||||||
|
-> m a
|
||||||
|
singleSignOut mRedirect = do
|
||||||
|
# ifdef DEVELOPMENT
|
||||||
|
port <- liftIO $ fromJust <$> lookupEnv "OAUTH2_SERVER_PORT"
|
||||||
|
let base = "http://localhost:" <> pack port <> "/logout"
|
||||||
|
# else
|
||||||
|
let base = "" -- TODO find out fraport oidc end_session_endpoint
|
||||||
|
# endif
|
||||||
|
endpoint = case mRedirect of
|
||||||
|
Just r -> base <> "?post_logout_redirect_uri=" <> r
|
||||||
|
Nothing -> base
|
||||||
|
$logErrorS "\n\27[31mSSO\27[0m" endpoint
|
||||||
|
redirect endpoint
|
||||||
|
|
||||||
|
|||||||
@ -11,11 +11,14 @@ module Foundation.Instances
|
|||||||
, unsafeHandler
|
, unsafeHandler
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import qualified Prelude as P
|
||||||
|
|
||||||
import Import.NoFoundation
|
import Import.NoFoundation
|
||||||
|
|
||||||
import qualified Data.Text as Text
|
import qualified Data.Text as Text
|
||||||
import Data.List (inits)
|
import Data.List (inits)
|
||||||
|
|
||||||
|
import Yesod.Auth.OAuth2
|
||||||
import qualified Yesod.Core.Unsafe as Unsafe
|
import qualified Yesod.Core.Unsafe as Unsafe
|
||||||
import qualified Yesod.Auth.Message as Auth
|
import qualified Yesod.Auth.Message as Auth
|
||||||
|
|
||||||
@ -24,6 +27,7 @@ import Auth.OAuth2 (apAzure, apAzureMock)
|
|||||||
import Auth.LDAP
|
import Auth.LDAP
|
||||||
import Auth.PWHash
|
import Auth.PWHash
|
||||||
import Auth.Dummy
|
import Auth.Dummy
|
||||||
|
import Auth.OAuth2
|
||||||
|
|
||||||
import qualified Foundation.Yesod.Session as UniWorX
|
import qualified Foundation.Yesod.Session as UniWorX
|
||||||
import qualified Foundation.Yesod.Middleware as UniWorX
|
import qualified Foundation.Yesod.Middleware as UniWorX
|
||||||
@ -43,6 +47,8 @@ import Foundation.DB
|
|||||||
|
|
||||||
import Network.Wai.Parse (lbsBackEnd)
|
import Network.Wai.Parse (lbsBackEnd)
|
||||||
|
|
||||||
|
import System.Environment (lookupEnv)
|
||||||
|
|
||||||
import UnliftIO.Pool (withResource)
|
import UnliftIO.Pool (withResource)
|
||||||
|
|
||||||
import qualified Control.Monad.State.Class as State
|
import qualified Control.Monad.State.Class as State
|
||||||
@ -129,14 +135,23 @@ instance YesodAuth UniWorX where
|
|||||||
-- Where to send a user after logout
|
-- Where to send a user after logout
|
||||||
logoutDest _ = NewsR
|
logoutDest _ = NewsR
|
||||||
-- Override the above two destinations when a Referer: header is present
|
-- Override the above two destinations when a Referer: header is present
|
||||||
redirectToReferer _ = True
|
redirectToReferer _ = False
|
||||||
|
|
||||||
loginHandler = do
|
loginHandler = do
|
||||||
|
plugins <- getsYesod authPlugins
|
||||||
|
AppSettings{..} <- getsYesod appSettings'
|
||||||
|
|
||||||
|
when appSingleSignOn $ do
|
||||||
|
let plugin = P.head $ P.filter ((`elem` [mockPluginName, azurePluginName]) . apName) plugins
|
||||||
|
pieces = case oauth2Url (apName plugin) of
|
||||||
|
PluginR _ p -> p
|
||||||
|
_ -> error "Unexpected OAuth2 AuthRoute"
|
||||||
|
void $ apDispatch plugin "GET" pieces
|
||||||
|
|
||||||
toParent <- getRouteToParent
|
toParent <- getRouteToParent
|
||||||
liftHandler . defaultLayout $ do
|
liftHandler . defaultLayout $ do
|
||||||
plugins <- getsYesod authPlugins
|
|
||||||
$logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName plugins)
|
$logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName plugins)
|
||||||
|
mPort <- liftIO $ lookupEnv "OAUTH2_SERVER_PORT"
|
||||||
setTitleI MsgLoginTitle
|
setTitleI MsgLoginTitle
|
||||||
$(widgetFile "login")
|
$(widgetFile "login")
|
||||||
|
|
||||||
@ -158,6 +173,11 @@ instance YesodAuth UniWorX where
|
|||||||
|
|
||||||
addMessage Success . toHtml $ mr Auth.NowLoggedIn
|
addMessage Success . toHtml $ mr Auth.NowLoggedIn
|
||||||
|
|
||||||
|
-- onLogout = do
|
||||||
|
-- AppSettings{..} <- getsYesod appSettings'
|
||||||
|
-- when appSingleSignOn $ singleSignOut @UniWorX Nothing
|
||||||
|
|
||||||
|
|
||||||
onErrorHtml dest msg = do
|
onErrorHtml dest msg = do
|
||||||
addMessage Error $ toHtml msg
|
addMessage Error $ toHtml msg
|
||||||
redirect dest
|
redirect dest
|
||||||
|
|||||||
@ -73,6 +73,8 @@ breadcrumb :: ( BearerAuthSite UniWorX
|
|||||||
=> Route UniWorX
|
=> Route UniWorX
|
||||||
-> m Breadcrumb
|
-> m Breadcrumb
|
||||||
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just NewsR
|
breadcrumb (AuthR _) = i18nCrumb MsgMenuLogin $ Just NewsR
|
||||||
|
breadcrumb SOutR = i18nCrumb MsgLogout Nothing
|
||||||
|
breadcrumb SSOutR = i18nCrumb MsgSingleSignOut Nothing
|
||||||
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
breadcrumb (StaticR _) = i18nCrumb MsgBreadcrumbStatic Nothing
|
||||||
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
|
breadcrumb (WellKnownR _) = i18nCrumb MsgBreadcrumbWellKnown Nothing
|
||||||
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
breadcrumb MetricsR = i18nCrumb MsgBreadcrumbMetrics Nothing
|
||||||
@ -540,42 +542,37 @@ defaultLinks :: ( MonadHandler m
|
|||||||
, BearerAuthSite UniWorX
|
, BearerAuthSite UniWorX
|
||||||
) => m [Nav]
|
) => m [Nav]
|
||||||
defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header.
|
defaultLinks = fmap catMaybes . mapM runMaybeT $ -- Define the menu items of the header.
|
||||||
[ return NavHeader
|
[ return NavHeaderContainer
|
||||||
{ navHeaderRole = NavHeaderSecondary
|
{ navHeaderRole = NavHeaderSecondary
|
||||||
, navIcon = IconMenuLogout
|
, navLabel = SomeMessage MsgMenuAccount
|
||||||
, navLink = NavLink
|
, navIcon = IconMenuAccount
|
||||||
{ navLabel = MsgMenuLogout
|
, navChildren =
|
||||||
, navRoute = AuthR LogoutR
|
[ NavLink
|
||||||
, navAccess' = NavAccessHandler $ is _Just <$> maybeAuthId
|
{ navLabel = MsgMenuLogout
|
||||||
, navType = NavTypeLink { navModal = False }
|
, navRoute = SSOutR -- AuthR LogoutR
|
||||||
, navQuick' = mempty
|
, navAccess' = NavAccessHandler $ is _Just <$> maybeAuthId
|
||||||
, navForceActive = False
|
, navType = NavTypeLink { navModal = False }
|
||||||
}
|
, navQuick' = mempty
|
||||||
}
|
, navForceActive = False
|
||||||
, return NavHeader
|
}
|
||||||
{ navHeaderRole = NavHeaderSecondary
|
, NavLink
|
||||||
, navIcon = IconMenuLogin
|
{ navLabel = MsgMenuLogin
|
||||||
, navLink = NavLink
|
, navRoute = AuthR LoginR
|
||||||
{ navLabel = MsgMenuLogin
|
, navAccess' = NavAccessHandler $ is _Nothing <$> maybeAuthId
|
||||||
, navRoute = AuthR LoginR
|
, navType = NavTypeLink { navModal = False }
|
||||||
, navAccess' = NavAccessHandler $ is _Nothing <$> maybeAuthId
|
, navQuick' = mempty
|
||||||
, navType = NavTypeLink { navModal = True }
|
, navForceActive = False
|
||||||
, navQuick' = mempty
|
}
|
||||||
, navForceActive = False
|
, NavLink
|
||||||
}
|
{ navLabel = MsgMenuProfile
|
||||||
}
|
, navRoute = ProfileR
|
||||||
, return NavHeader
|
, navAccess' = NavAccessHandler $ is _Just <$> maybeAuthId
|
||||||
{ navHeaderRole = NavHeaderSecondary
|
, navType = NavTypeLink { navModal = False }
|
||||||
, navIcon = IconMenuProfile
|
, navQuick' = mempty
|
||||||
, navLink = NavLink
|
, navForceActive = False
|
||||||
{ navLabel = MsgMenuProfile
|
}
|
||||||
, navRoute = ProfileR
|
]
|
||||||
, navAccess' = NavAccessHandler $ is _Just <$> maybeAuthId
|
}
|
||||||
, navType = NavTypeLink { navModal = False }
|
|
||||||
, navQuick' = mempty
|
|
||||||
, navForceActive = False
|
|
||||||
}
|
|
||||||
}
|
|
||||||
, do
|
, do
|
||||||
mCurrentRoute <- getCurrentRoute
|
mCurrentRoute <- getCurrentRoute
|
||||||
|
|
||||||
|
|||||||
31
src/Handler/SingleSignOut.hs
Normal file
31
src/Handler/SingleSignOut.hs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
-- SPDX-FileCopyrightText: 2024 David Mosbach <david.mosbach@uniworx.de>
|
||||||
|
--
|
||||||
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
module Handler.SingleSignOut
|
||||||
|
( getSOutR
|
||||||
|
, getSSOutR
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Import
|
||||||
|
import Auth.OAuth2 (singleSignOut)
|
||||||
|
import qualified Network.Wai as W
|
||||||
|
|
||||||
|
|
||||||
|
getSOutR :: Handler Html
|
||||||
|
getSOutR = do
|
||||||
|
$logErrorS "\27[31mSOut\27[0m" "Redirect to LogoutR"
|
||||||
|
redirect $ AuthR LogoutR
|
||||||
|
|
||||||
|
getSSOutR :: Handler Html
|
||||||
|
getSSOutR = do
|
||||||
|
app <- getYesod
|
||||||
|
let redir = intercalate "/" . fst . renderRoute $ SOutR
|
||||||
|
root = case approot of
|
||||||
|
ApprootRequest f -> f app W.defaultRequest
|
||||||
|
_ -> error "approt implementation changed"
|
||||||
|
url = decodeUtf8 . urlEncode True . encodeUtf8 $ root <> "/" <> redir
|
||||||
|
AppSettings{..} <- getsYesod appSettings'
|
||||||
|
$logErrorS "\27[31mSSOut\27[0m" "Redirect to auth server"
|
||||||
|
if appSingleSignOn then singleSignOut (Just url) else redirect (AuthR LogoutR)
|
||||||
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022-2024 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>
|
-- SPDX-FileCopyrightText: 2022-2024 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>,David Mosbach <david.mosbach@uniworx.de>
|
||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -452,7 +452,14 @@ data AppSettings = AppSettings
|
|||||||
, appDatabaseConf :: PostgresConf
|
, appDatabaseConf :: PostgresConf
|
||||||
-- ^ Configuration settings for accessing the database.
|
-- ^ Configuration settings for accessing the database.
|
||||||
, appAutoDbMigrate :: Bool
|
, appAutoDbMigrate :: Bool
|
||||||
|
<<<<<<< HEAD
|
||||||
, appUserAuthConf :: UserAuthConf -- TODO: add SSO option for user-auth config
|
, appUserAuthConf :: UserAuthConf -- TODO: add SSO option for user-auth config
|
||||||
|
=======
|
||||||
|
, appSingleSignOn :: Bool
|
||||||
|
-- ^ Enable OIDC single sign-on
|
||||||
|
, appLdapConf :: Maybe (PointedList LdapConf)
|
||||||
|
-- ^ Configuration settings for CSV export/import to LMS (= Learn Management System)
|
||||||
|
>>>>>>> 139-single-sign-on-sso-routing-anpassen
|
||||||
, appLmsConf :: LmsConf
|
, appLmsConf :: LmsConf
|
||||||
-- ^ Configuration settings for CSV export/import to LMS (= Learn Management System) -- TODO, TODISCUSS: reimplement as user-auth source?
|
-- ^ Configuration settings for CSV export/import to LMS (= Learn Management System) -- TODO, TODISCUSS: reimplement as user-auth source?
|
||||||
, appAvsConf :: Maybe AvsConf
|
, appAvsConf :: Maybe AvsConf
|
||||||
@ -624,6 +631,7 @@ instance FromJSON AppSettings where
|
|||||||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||||||
appDatabaseConf <- o .: "database"
|
appDatabaseConf <- o .: "database"
|
||||||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||||||
|
<<<<<<< HEAD
|
||||||
-- TODO: reintroduce non-emptyness check for ldap hosts
|
-- TODO: reintroduce non-emptyness check for ldap hosts
|
||||||
-- let nonEmptyHost (UserDbLdap LdapConf{..}) = case ldapHost of
|
-- let nonEmptyHost (UserDbLdap LdapConf{..}) = case ldapHost of
|
||||||
-- Ldap.Tls host _ -> not $ null host
|
-- Ldap.Tls host _ -> not $ null host
|
||||||
@ -632,6 +640,13 @@ instance FromJSON AppSettings where
|
|||||||
appUserAuthConf <- o .: "user-auth"
|
appUserAuthConf <- o .: "user-auth"
|
||||||
-- P.fromList . mapMaybe (assertM nonEmptyHost) <$> o .:? "user-database" .!= []
|
-- P.fromList . mapMaybe (assertM nonEmptyHost) <$> o .:? "user-database" .!= []
|
||||||
appLdapPoolConf <- o .:? "ldap-pool"
|
appLdapPoolConf <- o .:? "ldap-pool"
|
||||||
|
=======
|
||||||
|
appSingleSignOn <- o .: "single-sign-on"
|
||||||
|
let nonEmptyHost LdapConf{..} = case ldapHost of
|
||||||
|
Ldap.Tls host _ -> not $ null host
|
||||||
|
Ldap.Plain host -> not $ null host
|
||||||
|
appLdapConf <- P.fromList . mapMaybe (assertM nonEmptyHost) <$> o .:? "ldap" .!= []
|
||||||
|
>>>>>>> 139-single-sign-on-sso-routing-anpassen
|
||||||
appLmsConf <- o .: "lms-direct"
|
appLmsConf <- o .: "lms-direct"
|
||||||
appAvsConf <- assertM (not . null . avsPass) <$> o .:? "avs"
|
appAvsConf <- assertM (not . null . avsPass) <$> o .:? "avs"
|
||||||
appLprConf <- o .: "lpr"
|
appLprConf <- o .: "lpr"
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
-- SPDX-FileCopyrightText: 2022-23 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>,Steffen Jost <s.jost@fraport.de>
|
-- SPDX-FileCopyrightText: 2022-24 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>,Sarah Vaupel <vaupel.sarah@campus.lmu.de>,Steffen Jost <jost@tcs.ifi.lmu.de>,Wolfgang Witt <Wolfgang.Witt@campus.lmu.de>,Steffen Jost <s.jost@fraport.de>,David Mosbach <david.mosbach@uniworx.de>
|
||||||
--
|
--
|
||||||
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
-- SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -81,6 +81,7 @@ data Icon
|
|||||||
| IconNavContainerClose | IconPageActionChildrenClose
|
| IconNavContainerClose | IconPageActionChildrenClose
|
||||||
| IconMenuNews
|
| IconMenuNews
|
||||||
| IconMenuHelp
|
| IconMenuHelp
|
||||||
|
| IconMenuAccount
|
||||||
| IconMenuProfile
|
| IconMenuProfile
|
||||||
| IconMenuLogin | IconMenuLogout
|
| IconMenuLogin | IconMenuLogout
|
||||||
| IconBreadcrumbsHome
|
| IconBreadcrumbsHome
|
||||||
@ -173,6 +174,7 @@ iconText = \case
|
|||||||
IconPageActionChildrenClose -> "chevron-up"
|
IconPageActionChildrenClose -> "chevron-up"
|
||||||
IconMenuNews -> "megaphone"
|
IconMenuNews -> "megaphone"
|
||||||
IconMenuHelp -> "question"
|
IconMenuHelp -> "question"
|
||||||
|
IconMenuAccount -> "user"
|
||||||
IconMenuProfile -> "cogs"
|
IconMenuProfile -> "cogs"
|
||||||
IconMenuLogin -> "sign-in-alt"
|
IconMenuLogin -> "sign-in-alt"
|
||||||
IconMenuLogout -> "sign-out-alt"
|
IconMenuLogout -> "sign-out-alt"
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
$newline never
|
$newline never
|
||||||
|
|
||||||
$# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel <sarah.vaupel@uniworx.de>, Gregor Kleen <gregor.kleen@ifi.lmu.de>, David Mosbach <david.mosbach@uniworx.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
|
$# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
@ -26,3 +26,8 @@ $forall AuthPlugin{apName, apLogin} <- plugins
|
|||||||
<section>
|
<section>
|
||||||
<h2>_{MsgDummyLoginTitle}
|
<h2>_{MsgDummyLoginTitle}
|
||||||
^{apLogin toParent}
|
^{apLogin toParent}
|
||||||
|
$maybe port <- mPort
|
||||||
|
<section>
|
||||||
|
<h2>SSO Dev Test
|
||||||
|
<a href=http://localhost:#{port}/test-sso>Test login via single sign-on
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user