diff --git a/config/settings.yml b/config/settings.yml index ed8743679..99d993f07 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -136,7 +136,7 @@ user-auth: client-id: "_env:AZURECLIENTID:00000000-0000-0000-0000-000000000000" client-secret: "_env:AZURECLIENTSECRET:''" tenant-id: "_env:AZURETENANTID:00000000-0000-0000-0000-000000000000" - scopes: "_env:AZURESCOPES:[ID,Profile]" + scopes: "_env:AZURESCOPES:[email,openid,profile,offline_access]" # protocol: "ldap" # config: # host: "_env:LDAPHOST:" diff --git a/flake.lock b/flake.lock index 427561469..6c0c35c28 100644 --- a/flake.lock +++ b/flake.lock @@ -25,12 +25,12 @@ "rev": "40393c938111ac78232dc2c7eec5edb4a22d03e8", "revCount": 62, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/HaskellNet-SSL.git" + "url": "https://gitlab.uniworx.de/haskell/HaskellNet-SSL.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/HaskellNet-SSL.git" + "url": "https://gitlab.uniworx.de/haskell/HaskellNet-SSL.git" } }, "cabal-32": { @@ -92,12 +92,12 @@ "rev": "f8170266ab25b533576e96715bedffc5aa4f19fa", "revCount": 153, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/colonnade.git" + "url": "https://gitlab.uniworx.de/haskell/colonnade.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/colonnade.git" + "url": "https://gitlab.uniworx.de/haskell/colonnade.git" } }, "conduit-resumablesink": { @@ -109,12 +109,12 @@ "rev": "cbea6159c2975d42f948525e03e12fc390da53c5", "revCount": 10, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/conduit-resumablesink.git" + "url": "https://gitlab.uniworx.de/haskell/conduit-resumablesink.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/conduit-resumablesink.git" + "url": "https://gitlab.uniworx.de/haskell/conduit-resumablesink.git" } }, "cryptoids": { @@ -126,29 +126,29 @@ "rev": "130b0dcbf2b09ccdf387b50262f1efbbbf1819e3", "revCount": 44, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/cryptoids.git" + "url": "https://gitlab.uniworx.de/haskell/cryptoids.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/cryptoids.git" + "url": "https://gitlab.uniworx.de/haskell/cryptoids.git" } }, "cryptonite": { "flake": false, "locked": { - "lastModified": 1624444174, - "narHash": "sha256-sDMA4ej1NIModAt7PQvcgIknI3KwfzcAp9YQUSe4CWw=", + "lastModified": 1704764911, + "narHash": "sha256-VuEWT2Bd4aSJyRcXpB+lsGDqxrTHB/uRvILzYWLNfxk=", "ref": "uni2work", - "rev": "71a630edaf5f22c464e24fac8d9d310f4055ea1f", - "revCount": 1202, + "rev": "f78fca2504bb767d632a3bac8dbbc23367eff0e9", + "revCount": 1220, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/cryptonite.git" + "url": "https://gitlab.uniworx.de/haskell/cryptonite.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/cryptonite.git" + "url": "https://gitlab.uniworx.de/haskell/cryptonite.git" } }, "encoding": { @@ -160,12 +160,12 @@ "rev": "22fc3bb14841d8d50997aa47f1be3852e666f787", "revCount": 162, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/encoding.git" + "url": "https://gitlab.uniworx.de/haskell/encoding.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/encoding.git" + "url": "https://gitlab.uniworx.de/haskell/encoding.git" } }, "esqueleto": { @@ -177,12 +177,12 @@ "rev": "e18dd125c5ea26fa4e88bed079b61d8c1365ee37", "revCount": 708, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/esqueleto.git" + "url": "https://gitlab.uniworx.de/haskell/esqueleto.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/esqueleto.git" + "url": "https://gitlab.uniworx.de/haskell/esqueleto.git" } }, "flake-utils": { @@ -310,12 +310,12 @@ "rev": "01afaf599ba6f8a9d804c269e91d3190b249d3f0", "revCount": 61, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/ldap-client.git" + "url": "https://gitlab.uniworx.de/haskell/ldap-client.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/ldap-client.git" + "url": "https://gitlab.uniworx.de/haskell/ldap-client.git" } }, "memcached-binary": { @@ -327,29 +327,29 @@ "rev": "b7071df50bad3a251a544b984e4bf98fa09b8fae", "revCount": 28, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/memcached-binary.git" + "url": "https://gitlab.uniworx.de/haskell/memcached-binary.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/memcached-binary.git" + "url": "https://gitlab.uniworx.de/haskell/memcached-binary.git" } }, "minio-hs": { "flake": false, "locked": { - "lastModified": 1597069863, - "narHash": "sha256-JmMajaLT4+zt+w2koDkaloFL8ugmrQBlcYKj+78qn9M=", + "lastModified": 1711841413, + "narHash": "sha256-9IdjU1/Mzi4ZGhX7tFJhqliratSVRvDwe9AesD0lkt8=", "ref": "uni2work", - "rev": "42103ab247057c04c8ce7a83d9d4c160713a3df1", - "revCount": 197, + "rev": "cb25dd23c4cf62a956caad722d45ad6cf3cc5e3a", + "revCount": 224, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/minio-hs.git" + "url": "https://gitlab.uniworx.de/haskell/minio-hs.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/minio-hs.git" + "url": "https://gitlab.uniworx.de/haskell/minio-hs.git" } }, "nix-tools": { @@ -528,12 +528,12 @@ "rev": "b9d76def10da1260c7f6aa82bda32111f37a952b", "revCount": 174, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/serversession.git" + "url": "https://gitlab.uniworx.de/haskell/serversession.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/serversession.git" + "url": "https://gitlab.uniworx.de/haskell/serversession.git" } }, "stackage": { @@ -576,29 +576,31 @@ "rev": "dc928c3a456074b8777603bea20e81937321777f", "revCount": 114, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/xss-sanitize.git" + "url": "https://gitlab.uniworx.de/haskell/xss-sanitize.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/xss-sanitize.git" + "url": "https://gitlab.uniworx.de/haskell/xss-sanitize.git" } }, "yesod": { "flake": false, "locked": { - "lastModified": 1625061191, - "narHash": "sha256-K0X2MwUStChml1DlJ7t4yBMDwrMe6j/780nJtSy9Hss=", - "ref": "uni2work", - "rev": "a59f63e0336ee61f7a90b8778e9147305d3127bb", - "revCount": 5053, - "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/yesod.git" + "host": "gitlab.uniworx.de", + "lastModified": 1681915610, + "narHash": "sha256-HtJhPHDC7FTc7kyI3OtBKjgeUyEslIGpQiZJwO4PUec=", + "owner": "haskell", + "repo": "yesod", + "rev": "aa671eb41fdad360f2f7cb844f8de03479efe3f7", + "type": "gitlab" }, "original": { - "ref": "uni2work", - "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/yesod.git" + "host": "gitlab.uniworx.de", + "owner": "haskell", + "repo": "yesod", + "rev": "aa671eb41fdad360f2f7cb844f8de03479efe3f7", + "type": "gitlab" } }, "zip-stream": { @@ -610,12 +612,12 @@ "rev": "843683d024f767de236f74d24a3348f69181a720", "revCount": 39, "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/zip-stream.git" + "url": "https://gitlab.uniworx.de/haskell/zip-stream.git" }, "original": { "ref": "uni2work", "type": "git", - "url": "https://gitlab.ifi.lmu.de/uni2work/haskell/zip-stream.git" + "url": "https://gitlab.uniworx.de/haskell/zip-stream.git" } } }, diff --git a/flake.nix b/flake.nix index 2ecc482e0..56ca15e67 100644 --- a/flake.nix +++ b/flake.nix @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2023 Sarah Vaupel , Gregor Kleen +# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , Gregor Kleen # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -29,59 +29,64 @@ }; encoding = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/encoding.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/encoding.git?ref=uni2work"; flake = false; }; memcached-binary = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/memcached-binary.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/memcached-binary.git?ref=uni2work"; flake = false; }; conduit-resumablesink = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/conduit-resumablesink.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/conduit-resumablesink.git?ref=uni2work"; flake = false; }; HaskellNet-SSL = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/HaskellNet-SSL.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/HaskellNet-SSL.git?ref=uni2work"; flake = false; }; ldap-client = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/ldap-client.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/ldap-client.git?ref=uni2work"; flake = false; }; serversession = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/serversession.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/serversession.git?ref=uni2work"; flake = false; }; xss-sanitize = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/xss-sanitize.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/xss-sanitize.git?ref=uni2work"; flake = false; }; colonnade = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/colonnade.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/colonnade.git?ref=uni2work"; flake = false; }; minio-hs = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/minio-hs.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/minio-hs.git?ref=uni2work"; flake = false; }; cryptoids = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/cryptoids.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/cryptoids.git?ref=uni2work"; flake = false; }; zip-stream = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/zip-stream.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/zip-stream.git?ref=uni2work"; flake = false; }; yesod = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/yesod.git?ref=uni2work"; + url = "gitlab:haskell/yesod?host=gitlab.uniworx.de&rev=aa671eb41fdad360f2f7cb844f8de03479efe3f7"; flake = false; }; + # TODO: does not function due to missing dependencies in snapshot + # yesod-auth-oauth2 = { + # url = "git+https://gitlab.uniworx.de/haskell/yesod-auth-oauth2.git?ref=ghc-8.10.4"; + # flake = false; + # }; cryptonite = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/cryptonite.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/cryptonite.git?ref=uni2work"; flake = false; }; esqueleto = { - url = "git+https://gitlab.ifi.lmu.de/uni2work/haskell/esqueleto.git?ref=uni2work"; + url = "git+https://gitlab.uniworx.de/haskell/esqueleto.git?ref=uni2work"; flake = false; }; }; diff --git a/nix/uniworx/backend.nix b/nix/uniworx/backend.nix index 03fdb8431..722f912a8 100644 --- a/nix/uniworx/backend.nix +++ b/nix/uniworx/backend.nix @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022-2023 Gregor Kleen , Steffen Jost +# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , Gregor Kleen , Steffen Jost # # SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/package.yaml b/package.yaml index 4e46ac23e..d1190ffb5 100644 --- a/package.yaml +++ b/package.yaml @@ -6,7 +6,7 @@ dependencies: - yesod-core - yesod-persistent - yesod-auth - - yesod-auth-oauth2 + - yesod-auth-oauth2 >=0.7.3.0 - yesod-static - yesod-form - yesod-persistent diff --git a/src/Application.hs b/src/Application.hs index fbf55b8aa..225957aa7 100644 --- a/src/Application.hs +++ b/src/Application.hs @@ -331,7 +331,7 @@ makeFoundation appSettings''@AppSettings{..} = do , return $ oauth2AzureADScoped ["openid", "profile", "offline_access"] "42" "shhh" ] #else - let -- Auth Plugins + -- let -- Auth Plugins -- loadPlugin p prefix = do -- Loads given YesodAuthPlugin -- mID <- fmap Text.pack <$> appUserAuthConf ^? _UserAuthConfSingleSource . _AuthSourceConfAzureAdV2 . _azureConfClientId -- mSecret <- fmap Text.pack <$> appUserAuthConf ^? _UserAuthConfSingleSource . _AuthSourceConfAzureAdV2 . _azureConfClientSecret @@ -343,11 +343,17 @@ makeFoundation appSettings''@AppSettings{..} = do -- -> tshow azureConfTenantId -- _other -- -> error "Tenant ID missing!" - oauth2Plugins - | UserAuthConfSingleSource (AuthSourceConfAzureAdV2 AzureConf{..}) <- appUserAuthConf - = singleton $ oauth2AzureADScoped (Set.toList azureConfScopes) (tshow azureConfClientId) azureConfClientSecret - | otherwise - = mempty + oauth2Plugins <- if + | UserAuthConfSingleSource (AuthSourceConfAzureAdV2 AzureConf{..}) <- appUserAuthConf -> do + $logInfoS "OAuth2" "Successfully parsed OAuth2 config from AppSettings" + return . singleton $ oauth2AzureADScoped (Set.toList azureConfScopes) (tshow azureConfClientId) azureConfClientSecret + | otherwise -> do + when appSingleSignOn $ do + $logErrorS "OAuth2" "SingleSignOn via AzureADv2 is enabled, but user-auth config could not be parsed!" + when appAutoSignOn $ + $logErrorS "OAuth2" "SingleSignOn via AzureADv2 and AutoSignOn are enabled, but user-auth config could not be parsed! This will likely prevent the app from being accessible!" + $logInfoS "UserAuthConf" $ tshow appUserAuthConf + return mempty #endif let appAuthPlugins = oauth2Plugins diff --git a/src/Auth/OAuth2.hs b/src/Auth/OAuth2.hs index 55c1997da..765fe7636 100644 --- a/src/Auth/OAuth2.hs +++ b/src/Auth/OAuth2.hs @@ -19,13 +19,16 @@ module Auth.OAuth2 -- import qualified Data.CaseInsensitive as CI import Data.Maybe (fromJust) +import qualified Data.Set as Set import Data.Text import Import.NoFoundation hiding (pack, unpack) import Network.HTTP.Simple (httpJSONEither, getResponseBody, JSONException) +# ifdef DEVELOPMENT import System.Environment (lookupEnv) +# endif import Yesod.Auth.OAuth2 import Yesod.Auth.OAuth2.Prelude hiding (encodeUtf8) @@ -127,7 +130,7 @@ azureMockServer port = , ("nonce", "Foo") -- TODO generate meaningful value ] , oauthAccessTokenEndpoint = fromString $ mockServerURL <> "/token" - , oauthCallback = Nothing + , oauthCallback = Nothing -- TODO use approot as redirect uri? } mockServerURL = "http://localhost:" <> fromString port profileSrc = fromString $ mockServerURL <> "/users/me" @@ -195,31 +198,31 @@ mkBaseUrls = do refreshOAuth2Token :: forall m. ( MonadHandler m + , HasAppSettings (HandlerSite m) , MonadThrow m ) => (Maybe AccessToken, Maybe RefreshToken) -> String -> Bool -> ExceptT UserDataException m OAuth2Token -refreshOAuth2Token (_, rToken) url secure - | isJust rToken = do +refreshOAuth2Token (_, Nothing) _ _ = throwE $ UserDataInternalException "Could not refresh access token. Refresh token is missing." +refreshOAuth2Token (_, Just rToken) url secure = getsYesod (view $ _appUserAuthConf . _userAuthConfSingleSource) >>= \case + AuthSourceConfAzureAdV2 AzureConf{..} -> do req <- parseRequest $ "POST " ++ url let body = [ ("grant_type", "refresh_token") - , ("refresh_token", encodeUtf8 . rtoken $ fromJust rToken) + , ("refresh_token", encodeUtf8 $ rtoken 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), scopeParam " " ["openid","profile"," offline_access"]] -- TODO read from config - else return $ scopeParam " " ["openid","profile","offline_access"] : body -- TODO read from config - $logDebugS "\27[31mAdmin Handler\27[0m" $ tshow (requestBody $ urlEncodedBody body' req{ secure = secure }) + body' + | secure = body ++ [("client_id", fromString $ show azureConfClientId), ("client_secret", fromString $ unpack azureConfClientSecret), scopeParam " " $ Set.toList azureConfScopes] + | otherwise = scopeParam " " (Set.toList azureConfScopes) : body + $logInfoS "\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." + _other -> throwE $ UserDataInternalException "Could not refresh access token. Invalid/Conflicting auth source configuration." instance Show RequestBody where show (RequestBodyLBS x) = show x diff --git a/src/Foundation/Instances.hs b/src/Foundation/Instances.hs index 39b8ee163..eebb2db19 100644 --- a/src/Foundation/Instances.hs +++ b/src/Foundation/Instances.hs @@ -140,12 +140,17 @@ instance YesodAuth UniWorX where plugins <- getsYesod authPlugins AppSettings{..} <- getsYesod appSettings' - when appSingleSignOn $ do - let plugin = P.head $ P.filter ((`elem` [apAzureMock, apAzure]) . apName) plugins - pieces = case oauth2Url (apName plugin) of - PluginR _ p -> p - _ -> error "Unexpected OAuth2 AuthRoute" - void $ apDispatch plugin "GET" pieces + when appSingleSignOn $ + let azurePlugins = P.filter ((`elem` [apAzureMock, apAzure]) . apName) plugins + in if + | (plugin:_) <- azurePlugins + , PluginR _ pieces <- oauth2Url (apName plugin) -> do + $logInfoS "SSO" "Azure plugin with plugin url as expected. Calling apDispatch..." + void $ apDispatch plugin "GET" pieces + | not (null azurePlugins) -> do + $logErrorS "SSO" "Azure plugin initialized, but unexpected oauth2Url. Cannot apDispatch." + | otherwise -> do + $logErrorS "SSO" "No Azure plugin initialized despite SSO being enabled!" toParent <- getRouteToParent liftHandler . defaultLayout $ do diff --git a/stack-flake.yaml b/stack-flake.yaml index 09e2dd321..dfd2ddefb 100644 --- a/stack-flake.yaml +++ b/stack-flake.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2022 Gregor Kleen ,Sarah Vaupel +# SPDX-FileCopyrightText: 2022-2024 Sarah Vaupel , Gregor Kleen ,Sarah Vaupel # # SPDX-License-Identifier: AGPL-3.0-or-later @@ -55,6 +55,8 @@ extra-deps: subdirs: - gearhash - fastcdc + - git: https://github.com/freckle/yesod-auth-oauth2.git + commit: 342dac80e40b10f07694a7e9aa8bab6d03ed6d66 - classy-prelude-yesod-1.5.0@sha256:8f7e183bdfd6d2ea9674284c4f285294ab086aff60d9be4e5d7d2f3c1a2b05b7,1330 - acid-state-0.16.0.1@sha256:d43f6ee0b23338758156c500290c4405d769abefeb98e9bc112780dae09ece6f,6207 diff --git a/stack.yaml b/stack.yaml index 7346e8392..dc195001f 100644 --- a/stack.yaml +++ b/stack.yaml @@ -18,7 +18,7 @@ nix: packages: [] pure: false shell-file: ./stack.nix - add-gc-roots: true + add-gc-roots: false extra-package-dbs: [] diff --git a/stackage.nix b/stackage.nix index 3f04c00bb..397b68058 100644 --- a/stackage.nix +++ b/stackage.nix @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-or-later { nixpkgs ? import ./nixpkgs.nix -, snapshot ? "lts-13.21" +, snapshot ? "lts-18.0" }: let