feat(auth): formatted output of user queries

This commit is contained in:
David Mosbach 2024-02-03 20:48:32 +00:00
parent 453034100b
commit d4cfce317d
4 changed files with 58 additions and 48 deletions

View File

@ -10,6 +10,7 @@ module Auth.OAuth2
, oauth2MockServer , oauth2MockServer
, mockPluginName , mockPluginName
, queryOAuth2User , queryOAuth2User
, UserDataException
) where ) where
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
@ -70,47 +71,54 @@ oauth2MockServer port =
---- User Queries ---- ---- User Queries ----
---------------------- ----------------------
data UserData = UD deriving (Show) data UserDataException = UserDataJSONException JSONException
instance FromJSON UserData where | UserDataInternalException Text
parseJSON _ = pure UD deriving Show
queryOAuth2User :: forall m . (MonadIO m, MonadThrow m, MonadHandler m) instance Exception UserDataException
queryOAuth2User :: forall j m . (FromJSON j, MonadIO m, MonadThrow m, MonadHandler m)
=> Text => Text
-> Text -> m (Either UserDataException j)
-> m (Either JSONException Value) queryOAuth2User userID = runExceptT $ do
queryOAuth2User authPlugin userID = do (queryUrl, tokenUrl) <- liftIO mkBaseUrls
(queryUrl, tokenUrl) <- liftIO $ mkBaseUrls authPlugin
req <- parseRequest $ "GET " ++ queryUrl ++ unpack userID req <- parseRequest $ "GET " ++ queryUrl ++ unpack userID
mTokens <- lookupSessionJson SessionOAuth2Token mTokens <- lookupSessionJson SessionOAuth2Token
unless (isJust mTokens) . liftIO . fail $ "Tried to load sesion Oauth2 tokens, but there are none" unless (isJust mTokens) . throwE $ UserDataInternalException "Tried to load session Oauth2 tokens, but there are none"
eNewToken <- refreshOAuth2Token @m (fromJust mTokens) tokenUrl (authPlugin == azurePluginName) # ifdef DEVELOPMENT
case eNewToken of let secure = False
Left e -> return $ Left e # else
Right newTokens -> do let secure = True
setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens) # endif
getResponseBody <$> httpJSONEither @m @Value (req newTokens <- refreshOAuth2Token @m (fromJust mTokens) tokenUrl secure
{ secure = authPlugin == azurePluginName setSessionJson SessionOAuth2Token (Just $ accessToken newTokens, refreshToken newTokens)
, requestHeaders = [("Authorization", encodeUtf8 . ("Bearer " <>) . atoken $ accessToken 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 :: Text -> IO (String, String)
mkBaseUrls authPlugin mkBaseUrls :: IO (String, String)
| authPlugin == azurePluginName = do mkBaseUrls = do
Just tenantID <- lookupEnv "AZURE_TENANT_ID" # ifndef DEVELOPMENT
return ( "https://graph.microsoft.com/v1.0/users/" Just tenantID <- lookupEnv "AZURE_TENANT_ID"
, "https://login.microsoftonline.com/" ++ tenantID ++ "/oauth2/v2.0" ) return ( "https://graph.microsoft.com/v1.0/users/"
| authPlugin == mockPluginName = do , "https://login.microsoftonline.com/" ++ tenantID ++ "/oauth2/v2.0" )
Just port <- lookupEnv "OAUTH2_SERVER_PORT" # else
let base = "http://localhost:" ++ port Just port <- lookupEnv "OAUTH2_SERVER_PORT"
return ( base ++ "/users/query?id=" let base = "http://localhost:" ++ port
, base ++ "/token" ) return ( base ++ "/users/query?id="
| otherwise = fail $ "Unsupported auth plugin: " ++ unpack authPlugin , base ++ "/token" )
# endif
refreshOAuth2Token :: forall m. (MonadIO m, MonadThrow m, MonadHandler m) refreshOAuth2Token :: forall m. (MonadIO m, MonadThrow m, MonadHandler m)
=> (Maybe AccessToken, Maybe RefreshToken) => (Maybe AccessToken, Maybe RefreshToken)
-> String -> String
-> Bool -> Bool
-> m (Either JSONException OAuth2Token) -> ExceptT UserDataException m OAuth2Token
refreshOAuth2Token (_, rToken) url secure refreshOAuth2Token (_, rToken) url secure
| isJust rToken = do | isJust rToken = do
req <- parseRequest $ "POST " ++ url req <- parseRequest $ "POST " ++ url
@ -125,8 +133,11 @@ refreshOAuth2Token (_, rToken) url secure
return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), ("scope", "openid profile")] return $ body ++ [("client_id", fromString clientID), ("client_secret", fromString clientSecret), ("scope", "openid profile")]
else return $ ("scope", "ID Profile") : body else return $ ("scope", "ID Profile") : body
$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 })
getResponseBody <$> httpJSONEither @m @OAuth2Token (urlEncodedBody body' req{ secure = secure }) eResult <- lift $ getResponseBody <$> httpJSONEither @m @OAuth2Token (urlEncodedBody body' req{ secure = secure })
| otherwise = liftIO $ fail "Could not refresh access token. Refresh token is missing." 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 instance Show RequestBody where
show (RequestBodyLBS x) = show x show (RequestBodyLBS x) = show x

View File

@ -24,6 +24,7 @@ import Handler.Utils.Memcached
import Foundation.Authorization (AuthorizationCacheKey(..)) import Foundation.Authorization (AuthorizationCacheKey(..))
import Yesod.Auth.Message import Yesod.Auth.Message
import Yesod.Auth.OAuth2 (getAccessToken, getRefreshToken)
import Auth.LDAP import Auth.LDAP
import Auth.OAuth2 import Auth.OAuth2
import Auth.PWHash (apHash) import Auth.PWHash (apHash)
@ -131,6 +132,9 @@ oAuthenticate :: ( MonadHandler m, HandlerSite m ~ UniWorX
=> Creds UniWorX -> m (AuthenticationResult UniWorX) => Creds UniWorX -> m (AuthenticationResult UniWorX)
oAuthenticate creds@Creds{..} = liftHandler . runDB . withReaderT projectBackend $ do oAuthenticate creds@Creds{..} = liftHandler . runDB . withReaderT projectBackend $ do
$logErrorS "OAuth" $ "\a\27[31m" <> tshow creds <> "\27[0m" $logErrorS "OAuth" $ "\a\27[31m" <> tshow creds <> "\27[0m"
setSessionJson SessionOAuth2Token $ (getAccessToken creds, getRefreshToken creds)
sess <- getSession
$logErrorS "OAuth" $ "\27[34m" <> tshow sess <> "\27[0m"
now <- liftIO getCurrentTime now <- liftIO getCurrentTime
let let

View File

@ -9,17 +9,14 @@ module Handler.Admin.OAuth2
import Import import Import
-- import qualified Data.CaseInsensitive as CI -- import qualified Data.CaseInsensitive as CI
import Data.Text() import Data.Aeson.Encode.Pretty (encodePretty)
--import qualified Data.Text as Text import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Encoding as T
--import qualified Data.Text.Encoding as Text --import qualified Data.Text.Encoding as Text
--import Foundation.Yesod.Auth (CampusUserConversionException()) --import Foundation.Yesod.Auth (CampusUserConversionException())
import Handler.Utils import Handler.Utils
# ifdef DEVELOPMENT import Auth.OAuth2 (queryOAuth2User)
import Auth.OAuth2 (queryOAuth2User, mockPluginName)
# else
import Auth.OAuth2 (queryOAuth2User, azurePluginName)
# endif
getAdminOAuth2R, postAdminOAuth2R :: Handler Html getAdminOAuth2R, postAdminOAuth2R :: Handler Html
@ -28,15 +25,12 @@ postAdminOAuth2R = do
((presult, pwidget), penctype) <- runFormPost $ identifyForm ("adminOAuth2Lookup"::Text) $ \html -> ((presult, pwidget), penctype) <- runFormPost $ identifyForm ("adminOAuth2Lookup"::Text) $ \html ->
flip (renderAForm FormStandard) html $ areq textField (fslI MsgAdminUserIdent) Nothing flip (renderAForm FormStandard) html $ areq textField (fslI MsgAdminUserIdent) Nothing
let procFormPerson :: Text -> Handler (Maybe Text) let procFormPerson :: Text -> Handler (Maybe T.Text)
procFormPerson lid = do --return . Just $ "Mock reply for id " <> lid procFormPerson lid = do --return . Just $ "Mock reply for id " <> lid
# ifdef DEVELOPMENT eUserData <- queryOAuth2User @Value lid
let authPlugin = mockPluginName case eUserData of
# else Left e -> throwM e
let authPlugin = azurePluginName Right userData -> return . Just . T.decodeUtf8 $ encodePretty userData
# endif
eUserData <- queryOAuth2User authPlugin lid
return . Just $ tshow eUserData
mOAuth2Data <- formResultMaybe presult procFormPerson mOAuth2Data <- formResultMaybe presult procFormPerson
--((uresult, uwidget), uenctype) <- runFormPost $ identifyForm ("adminOAuth2Upsert"::Text) $ \html -> --((uresult, uwidget), uenctype) <- runFormPost $ identifyForm ("adminOAuth2Upsert"::Text) $ \html ->

View File

@ -13,6 +13,7 @@ $# SPDX-License-Identifier: AGPL-3.0-or-later
Antwort: # Antwort: #
<dl .deflist> <dl .deflist>
<dt> <dt>
#{show answers} <pre>
#{answers}
<dd> <dd>