feat(auth): oidc based sso for auth protected routes
This commit is contained in:
parent
956464659e
commit
fbe0e37d28
@ -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-2024 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
|
||||
|
||||
@ -131,6 +131,8 @@ database:
|
||||
|
||||
auto-db-migrate: '_env:AUTO_DB_MIGRATE:true'
|
||||
|
||||
single-sign-on: "_env:OIDC_SSO:true"
|
||||
|
||||
ldap:
|
||||
- host: "_env:LDAPHOST:"
|
||||
tls: "_env:LDAPTLS:"
|
||||
|
||||
@ -8,7 +8,7 @@ module Auth.OAuth2
|
||||
( AzureUserException(..)
|
||||
, azurePluginName
|
||||
, oauth2MockServer
|
||||
, mockPluginName
|
||||
, mockPluginName
|
||||
, queryOAuth2User
|
||||
, UserDataException
|
||||
) where
|
||||
@ -36,9 +36,9 @@ instance Exception AzureUserException
|
||||
azurePluginName :: Text
|
||||
azurePluginName = "azureadv2"
|
||||
|
||||
----------------------------------------
|
||||
---- OAuth2 development auth plugin ----
|
||||
----------------------------------------
|
||||
-----------------------------------------------
|
||||
---- OAuth2 + OIDC development auth plugin ----
|
||||
-----------------------------------------------
|
||||
|
||||
mockPluginName :: Text
|
||||
mockPluginName = "dev-oauth2-mock"
|
||||
@ -53,7 +53,11 @@ oauth2MockServer port =
|
||||
let oa = OAuth2
|
||||
{ oauth2ClientId = "42"
|
||||
, 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"
|
||||
, oauth2RedirectUri = Nothing
|
||||
}
|
||||
@ -94,7 +98,8 @@ queryOAuth2User userID = runExceptT $ do
|
||||
setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens)
|
||||
eResult <- lift $ getResponseBody <$> httpJSONEither @m @j (req
|
||||
{ secure = secure
|
||||
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken newTokens)] })
|
||||
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken newTokens)]
|
||||
})
|
||||
case eResult of
|
||||
Left x -> throwE $ UserDataJSONException x
|
||||
Right x -> return x
|
||||
@ -130,8 +135,8 @@ refreshOAuth2Token (_, rToken) url secure
|
||||
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
|
||||
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), ("scope", "openid profile offline_access")] -- TODO read from config
|
||||
else return $ ("scope", "openid profile offline_access") : body -- TODO read from config
|
||||
$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
|
||||
@ -142,3 +147,4 @@ refreshOAuth2Token (_, rToken) url secure
|
||||
instance Show RequestBody where
|
||||
show (RequestBodyLBS x) = show x
|
||||
show _ = error ":("
|
||||
|
||||
|
||||
@ -11,11 +11,14 @@ module Foundation.Instances
|
||||
, unsafeHandler
|
||||
) where
|
||||
|
||||
import qualified Prelude as P
|
||||
|
||||
import Import.NoFoundation
|
||||
|
||||
import qualified Data.Text as Text
|
||||
import Data.List (inits)
|
||||
|
||||
import Yesod.Auth.OAuth2
|
||||
import qualified Yesod.Core.Unsafe as Unsafe
|
||||
import qualified Yesod.Auth.Message as Auth
|
||||
|
||||
@ -23,6 +26,7 @@ import Utils.Form
|
||||
import Auth.LDAP
|
||||
import Auth.PWHash
|
||||
import Auth.Dummy
|
||||
import Auth.OAuth2
|
||||
|
||||
import qualified Foundation.Yesod.Session as UniWorX
|
||||
import qualified Foundation.Yesod.Middleware as UniWorX
|
||||
@ -133,17 +137,20 @@ instance YesodAuth UniWorX where
|
||||
redirectToReferer _ = True
|
||||
|
||||
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
|
||||
liftHandler . defaultLayout $ do
|
||||
plugins <- getsYesod authPlugins
|
||||
$logDebugS "Auth" $ "Enabled plugins: " <> Text.intercalate ", " (map apName plugins)
|
||||
|
||||
#ifdef DEVELOPMENT
|
||||
mPort <- liftIO $ lookupEnv "OAUTH2_SERVER_PORT"
|
||||
#else
|
||||
let mPort = Nothing
|
||||
#endif
|
||||
|
||||
setTitleI MsgLoginTitle
|
||||
$(widgetFile "login")
|
||||
|
||||
|
||||
@ -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>
|
||||
-- SPDX-FileCopyrightText: 2022-2024 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
|
||||
|
||||
@ -96,6 +96,8 @@ data AppSettings = AppSettings
|
||||
, appDatabaseConf :: PostgresConf
|
||||
-- ^ Configuration settings for accessing the database.
|
||||
, appAutoDbMigrate :: Bool
|
||||
, appSingleSignOn :: Bool
|
||||
-- ^ Enable OIDC single sign-on
|
||||
, appLdapConf :: Maybe (PointedList LdapConf)
|
||||
-- ^ Configuration settings for CSV export/import to LMS (= Learn Management System)
|
||||
, appLmsConf :: LmsConf
|
||||
@ -627,6 +629,7 @@ instance FromJSON AppSettings where
|
||||
appWebpackEntrypoints <- o .: "webpack-manifest"
|
||||
appDatabaseConf <- o .: "database"
|
||||
appAutoDbMigrate <- o .: "auto-db-migrate"
|
||||
appSingleSignOn <- o .: "single-sign-on"
|
||||
let nonEmptyHost LdapConf{..} = case ldapHost of
|
||||
Ldap.Tls host _ -> not $ null host
|
||||
Ldap.Plain host -> not $ null host
|
||||
|
||||
Reference in New Issue
Block a user