packages feed

keycloak-hs 0.0.0.2 → 0.0.0.3

raw patch · 3 files changed

+64/−71 lines, 3 filesdep +containersdep +jwtdep −aeson-better-errorsPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: containers, jwt

Dependencies removed: aeson-better-errors

API changes (from Hackage documentation)

- Keycloak.Client: decodeToken :: Token -> Either String TokenDec
- Keycloak.Types: TokenDec :: Text -> Int -> Int -> Int -> Text -> Text -> Text -> Text -> Text -> Int -> Text -> Text -> Value -> Value -> Value -> Text -> Text -> Text -> Text -> Text -> Text -> TokenDec
- Keycloak.Types: [acr] :: TokenDec -> Text
- Keycloak.Types: [allowedOrigins] :: TokenDec -> Value
- Keycloak.Types: [aud] :: TokenDec -> Text
- Keycloak.Types: [authTime] :: TokenDec -> Int
- Keycloak.Types: [azp] :: TokenDec -> Text
- Keycloak.Types: [email] :: TokenDec -> Text
- Keycloak.Types: [exp] :: TokenDec -> Int
- Keycloak.Types: [familyName] :: TokenDec -> Text
- Keycloak.Types: [givenName] :: TokenDec -> Text
- Keycloak.Types: [iat] :: TokenDec -> Int
- Keycloak.Types: [iss] :: TokenDec -> Text
- Keycloak.Types: [jti] :: TokenDec -> Text
- Keycloak.Types: [name] :: TokenDec -> Text
- Keycloak.Types: [nbf] :: TokenDec -> Int
- Keycloak.Types: [preferredUsername] :: TokenDec -> Text
- Keycloak.Types: [realmAccess] :: TokenDec -> Value
- Keycloak.Types: [ressourceAccess] :: TokenDec -> Value
- Keycloak.Types: [sub] :: TokenDec -> Text
- Keycloak.Types: [typ] :: TokenDec -> Text
- Keycloak.Types: data TokenDec
- Keycloak.Types: instance Data.Aeson.Types.FromJSON.FromJSON Keycloak.Types.Token
- Keycloak.Types: instance GHC.Generics.Generic Keycloak.Types.TokenDec
- Keycloak.Types: instance GHC.Show.Show Keycloak.Types.TokenDec
- Keycloak.Types: parseTokenDec :: Parse e TokenDec
+ Keycloak.Types: TokenRep :: Text -> Int -> Int -> Text -> Text -> Int -> Text -> Text -> TokenRep
+ Keycloak.Types: [accessToken] :: TokenRep -> Text
+ Keycloak.Types: [expiresIn] :: TokenRep -> Int
+ Keycloak.Types: [notBeforePolicy] :: TokenRep -> Int
+ Keycloak.Types: [refreshExpriresIn] :: TokenRep -> Int
+ Keycloak.Types: [refreshToken] :: TokenRep -> Text
+ Keycloak.Types: [tokenType] :: TokenRep -> Text
+ Keycloak.Types: data TokenRep
+ Keycloak.Types: instance Data.Aeson.Types.FromJSON.FromJSON Keycloak.Types.TokenRep
+ Keycloak.Types: instance GHC.Classes.Eq Keycloak.Types.TokenRep
+ Keycloak.Types: instance GHC.Show.Show Keycloak.Types.TokenRep
+ Keycloak.Types: tokACR :: Text
+ Keycloak.Types: tokAddress :: Text
+ Keycloak.Types: tokAtHash :: Text
+ Keycloak.Types: tokAuthTime :: Text
+ Keycloak.Types: tokBirthdate :: Text
+ Keycloak.Types: tokCHash :: Text
+ Keycloak.Types: tokClaimsLocales :: Text
+ Keycloak.Types: tokEmail :: Text
+ Keycloak.Types: tokEmailVerified :: Text
+ Keycloak.Types: tokFamilyName :: Text
+ Keycloak.Types: tokGender :: Text
+ Keycloak.Types: tokGivenName :: Text
+ Keycloak.Types: tokLocale :: Text
+ Keycloak.Types: tokMiddleName :: Text
+ Keycloak.Types: tokName :: Text
+ Keycloak.Types: tokNickName :: Text
+ Keycloak.Types: tokNonce :: Text
+ Keycloak.Types: tokPhoneNumber :: Text
+ Keycloak.Types: tokPhoneNumberVerified :: Text
+ Keycloak.Types: tokPicture :: Text
+ Keycloak.Types: tokPreferredUsername :: Text
+ Keycloak.Types: tokProfile :: Text
+ Keycloak.Types: tokSessionState :: Text
+ Keycloak.Types: tokUpdateAt :: Text
+ Keycloak.Types: tokWebsite :: Text
+ Keycloak.Types: tokZoneinfo :: Text
- Keycloak.Types: [scope] :: TokenDec -> Text
+ Keycloak.Types: [scope] :: TokenRep -> Text
- Keycloak.Types: [sessionState] :: TokenDec -> Text
+ Keycloak.Types: [sessionState] :: TokenRep -> Text

Files

keycloak-hs.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name:           keycloak-hs-version:        0.0.0.2+version:        0.0.0.3 description:    Please see the README on GitHub at <https://github.com/cdupont/keycloak-hs#readme> homepage:       https://github.com/cdupont/keycloak-hs#readme bug-reports:    https://github.com/cdupont/keycloak-hs/issues@@ -37,7 +37,6 @@     , text     , aeson     , aeson-casing-    , aeson-better-errors     , http-api-data     , http-types     , hslogger@@ -45,6 +44,8 @@     , wreq     , base64-bytestring     , exceptions+    , jwt+    , containers   default-language: Haskell2010  
src/Keycloak/Client.hs view
@@ -11,10 +11,10 @@ import           Control.Monad.Except (throwError, catchError, MonadError) import           Data.Aeson as JSON import           Data.Aeson.Types hiding ((.=))-import           Data.Aeson.BetterErrors as AB import           Data.Text hiding (head, tail, map) import           Data.Text.Encoding import           Data.Maybe+import           Data.Map hiding (map) import           Data.ByteString.Base64 as B64 import           Data.String.Conversions import           Data.Monoid hiding (First)@@ -30,11 +30,11 @@ import           System.Log.Logger import           Debug.Trace import           System.IO.Unsafe-+import           Web.JWT as JWT  -- * Permissions --- |Checks is a scope is permitted on a resource. An HTTP Exception 403 will be thrown if not.+-- | Checks if a scope is permitted on a resource. An HTTP Exception 403 will be thrown if not. checkPermission :: ResourceId -> ScopeName -> Token -> Keycloak () checkPermission (ResourceId res) scope tok = do   debug $ "Checking permissions: " ++ (show res) ++ " " ++ (show scope)@@ -91,7 +91,7 @@   case eitherDecode body of     Right ret -> do        debug $ "Keycloak success: " ++ (show ret) -      return ret+      return $ Token $ convertString $ accessToken ret     Left err2 -> do       debug $ "Keycloak parse error: " ++ (show err2)        throwError $ ParseError $ pack (show err2)@@ -109,25 +109,21 @@   case eitherDecode body of     Right ret -> do       debug $ "Keycloak success: " ++ (show ret) -      return $ ret+      return $ Token $ convertString $ accessToken ret     Left err2 -> do       debug $ "Keycloak parse error: " ++ (show err2)        throwError $ ParseError $ pack (show err2) -decodeToken :: Token -> Either String TokenDec-decodeToken (Token tok) = case (BS.split '.' tok) ^? element 1 of-    Nothing -> Left "Token is not formed correctly"-    Just part2 -> case AB.parse parseTokenDec (traceShowId $ convertString $ B64.decodeLenient $ traceShowId part2) of-      Right td -> Right td-      Left (e :: ParseError String) -> Left $ show e  -- | Extract user name from a token getUsername :: Token -> Maybe Username-getUsername tok = do -  case decodeToken tok of-    Right t -> Just $ preferredUsername t-    Left e -> do-      traceM $ "Error while decoding token: " ++ (show e)+getUsername (Token tok) = do +  case JWT.decode $ (traceShowId (convertString tok)) of+    Just t -> case (traceShowId (unClaimsMap $ unregisteredClaims $ claims t)) !? "preferred_username" of+      Just (String un) -> Just un+      _ -> Nothing+    Nothing -> do+      traceM $ "Error while decoding token"       Nothing  
src/Keycloak/Types.hs view
@@ -13,7 +13,6 @@ import           Data.Text.Encoding import           Data.Monoid import           Data.Maybe-import           Data.Aeson.BetterErrors as AB import qualified Data.ByteString as BS import qualified Data.Word8 as W8 (isSpace, _colon, toLower) import           Data.Char@@ -62,11 +61,7 @@ -- | Wrapper for tokens. newtype Token = Token {unToken :: BS.ByteString} deriving (Eq, Show, Generic) -instance FromJSON Token where-  parseJSON (Object v) = do-    t <- v .: "access_token"-    return $ Token $ encodeUtf8 t -+-- | perser for Authorization header instance FromHttpApiData Token where   parseQueryParam = parseHeader . encodeUtf8   parseHeader (extractBearerAuth -> Just tok) = Right $ Token tok@@ -79,58 +74,59 @@         then Just $ BS.dropWhile W8.isSpace y         else Nothing +-- | Create Authorization header instance ToHttpApiData Token where   toQueryParam (Token token) = "Bearer " <> (decodeUtf8 token)  --- | Token description returned by Keycloak-data TokenDec = TokenDec {-  jti               :: Text,-  exp               :: Int,-  nbf               :: Int,-  iat               :: Int,-  iss               :: Text,-  aud               :: Text,-  sub               :: Text,-  typ               :: Text,-  azp               :: Text,-  authTime          :: Int,-  sessionState      :: Text,-  acr               :: Text,-  allowedOrigins    :: Value,-  realmAccess       :: Value,-  ressourceAccess   :: Value,-  scope             :: Text,-  name              :: Text,-  preferredUsername :: Text,-  givenName         :: Text,-  familyName        :: Text,-  email             :: Text-  } deriving (Generic, Show)+-- | Keycloak Token additional claims+tokNonce, tokAuthTime, tokSessionState, tokAtHash, tokCHash, tokName, tokGivenName, tokFamilyName, tokMiddleName, tokNickName, tokPreferredUsername, tokProfile, tokPicture, tokWebsite, tokEmail, tokEmailVerified, tokGender, tokBirthdate, tokZoneinfo, tokLocale, tokPhoneNumber, tokPhoneNumberVerified,tokAddress, tokUpdateAt, tokClaimsLocales, tokACR :: Text+tokNonce = "nonce";+tokAuthTime = "auth_time";+tokSessionState = "session_state";+tokAtHash = "at_hash";+tokCHash = "c_hash";+tokName = "name";+tokGivenName = "given_name";+tokFamilyName = "family_name";+tokMiddleName = "middle_name";+tokNickName = "nickname";+tokPreferredUsername = "preferred_username";+tokProfile = "profile";+tokPicture = "picture";+tokWebsite = "website";+tokEmail = "email";+tokEmailVerified = "email_verified";+tokGender = "gender";+tokBirthdate = "birthdate";+tokZoneinfo = "zoneinfo";+tokLocale = "locale";+tokPhoneNumber = "phone_number";+tokPhoneNumberVerified = "phone_number_verified";+tokAddress = "address";+tokUpdateAt = "updated_at";+tokClaimsLocales = "claims_locales";+tokACR = "acr"; -parseTokenDec :: Parse e TokenDec-parseTokenDec = TokenDec <$>-    AB.key "jti" asText <*>-    AB.key "exp" asIntegral <*>-    AB.key "nbf" asIntegral <*>-    AB.key "iat" asIntegral <*>-    AB.key "iss" asText <*>-    AB.key "aud" asText <*>-    AB.key "sub" asText <*>-    AB.key "typ" asText <*>-    AB.key "azp" asText <*>-    AB.key "auth_time" asIntegral <*>-    AB.key "session_state" asText <*>-    AB.key "acr" asText <*>-    AB.key "allowed-origins" asValue <*>-    AB.key "realm_access" asValue <*>-    AB.key "resource_access" asValue <*>-    AB.key "scope" asText <*>-    AB.key "name" asText <*>-    AB.key "preferred_username" asText <*>-    AB.key "given_name" asText <*>-    AB.key "family_name" asText <*>-    AB.key "email" asText+-- | Token reply from Keycloak+data TokenRep = TokenRep {+  accessToken       :: Text,+  expiresIn         :: Int,+  refreshExpriresIn :: Int,+  refreshToken      :: Text,+  tokenType         :: Text,+  notBeforePolicy   :: Int,+  sessionState      :: Text,+  scope             :: Text} deriving (Show, Eq) +instance FromJSON TokenRep where+  parseJSON (Object v) = TokenRep <$> v .: "access_token"+                                  <*> v .: "expires_in"+                                  <*> v .: "refresh_expires_in"+                                  <*> v .: "refresh_token"+                                  <*> v .: "token_type"+                                  <*> v .: "not-before-policy"+                                  <*> v .: "session_state"+                                  <*> v .: "scope"  -- * Permission