packages feed

keycloak-hs 2.0.3 → 2.1.0

raw patch · 10 files changed

+227/−199 lines, 10 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Keycloak: checkPermission :: ResourceId -> ScopeName -> JWT -> Keycloak ()
- Keycloak: createResource :: Resource -> JWT -> Keycloak ResourceId
- Keycloak: createUser :: User -> JWT -> Keycloak UserId
- Keycloak: deleteAllResources :: JWT -> Keycloak ()
- Keycloak: deleteResource :: ResourceId -> JWT -> Keycloak ()
- Keycloak: getAllResourceIds :: Keycloak [ResourceId]
- Keycloak: getClaimsUser :: ClaimsSet -> User
- Keycloak: getClientJWT :: Keycloak JWT
- Keycloak: getJWKs :: Keycloak [JWK]
- Keycloak: getJWT :: Username -> Password -> Keycloak JWT
- Keycloak: getPermissions :: [PermReq] -> JWT -> Keycloak [Permission]
- Keycloak: getResource :: ResourceId -> JWT -> Keycloak Resource
- Keycloak: getUser :: UserId -> JWT -> Keycloak User
- Keycloak: getUsers :: Maybe Max -> Maybe First -> Maybe Username -> JWT -> Keycloak [User]
- Keycloak: isAuthorized :: ResourceId -> ScopeName -> JWT -> Keycloak Bool
- Keycloak: updateResource :: Resource -> JWT -> Keycloak ResourceId
- Keycloak: updateUser :: UserId -> User -> JWT -> Keycloak ()
- Keycloak: verifyJWT :: JWK -> JWT -> Keycloak ClaimsSet
- Keycloak.Types: [_confBaseUrl] :: KCConfig -> Text
- Keycloak.Types: [_confClientId] :: KCConfig -> Text
- Keycloak.Types: [_confClientSecret] :: KCConfig -> Text
- Keycloak.Types: confBaseUrl :: Lens' KCConfig Text
- Keycloak.Types: confClientId :: Lens' KCConfig Text
- Keycloak.Types: confClientSecret :: Lens' KCConfig Text
- Keycloak.Types: defaultKCConfig :: KCConfig
- Keycloak.Utils: readString :: Value -> Maybe Text
+ Keycloak.Config: configureKeycloak :: FilePath -> IO KCConfig
+ Keycloak.Config: readConfig :: FilePath -> IO AdapterConfig
+ Keycloak.Types: AdapterConfig :: Realm -> ServerURL -> ClientId -> ClientCredentials -> AdapterConfig
+ Keycloak.Types: ClientCredentials :: Text -> ClientCredentials
+ Keycloak.Types: [_confAdapterConfig] :: KCConfig -> AdapterConfig
+ Keycloak.Types: [_confAuthServerUrl] :: AdapterConfig -> ServerURL
+ Keycloak.Types: [_confCredentials] :: AdapterConfig -> ClientCredentials
+ Keycloak.Types: [_confJWKs] :: KCConfig -> [JWK]
+ Keycloak.Types: [_confResource] :: AdapterConfig -> ClientId
+ Keycloak.Types: [_confSecret] :: ClientCredentials -> Text
+ Keycloak.Types: confAdapterConfig :: Lens' KCConfig AdapterConfig
+ Keycloak.Types: confAuthServerUrl :: Lens' AdapterConfig ServerURL
+ Keycloak.Types: confCredentials :: Lens' AdapterConfig ClientCredentials
+ Keycloak.Types: confJWKs :: Lens' KCConfig [JWK]
+ Keycloak.Types: confResource :: Lens' AdapterConfig ClientId
+ Keycloak.Types: confSecret :: Iso' ClientCredentials Text
+ Keycloak.Types: data AdapterConfig
+ Keycloak.Types: data ClientCredentials
+ Keycloak.Types: defaultAdapterConfig :: AdapterConfig
+ Keycloak.Types: instance Data.Aeson.Types.FromJSON.FromJSON Keycloak.Types.AdapterConfig
+ Keycloak.Types: instance Data.Aeson.Types.FromJSON.FromJSON Keycloak.Types.ClientCredentials
+ Keycloak.Types: instance Data.Aeson.Types.ToJSON.ToJSON Keycloak.Types.AdapterConfig
+ Keycloak.Types: instance Data.Aeson.Types.ToJSON.ToJSON Keycloak.Types.ClientCredentials
+ Keycloak.Types: instance GHC.Classes.Eq Keycloak.Types.AdapterConfig
+ Keycloak.Types: instance GHC.Classes.Eq Keycloak.Types.ClientCredentials
+ Keycloak.Types: instance GHC.Generics.Generic Keycloak.Types.AdapterConfig
+ Keycloak.Types: instance GHC.Generics.Generic Keycloak.Types.ClientCredentials
+ Keycloak.Types: instance GHC.Generics.Generic Keycloak.Types.KCConfig
+ Keycloak.Types: instance GHC.Show.Show Keycloak.Types.AdapterConfig
+ Keycloak.Types: instance GHC.Show.Show Keycloak.Types.ClientCredentials
+ Keycloak.Types: trainDrop :: Int -> Options
+ Keycloak.Types: type ClientId = Text
+ Keycloak.Types: type Realm = Text
+ Keycloak.Types: type ServerURL = Text
- Keycloak.Tokens: getJWKs :: Keycloak [JWK]
+ Keycloak.Tokens: getJWKs :: Realm -> ServerURL -> IO [JWK]
- Keycloak.Tokens: verifyJWT :: JWK -> JWT -> Keycloak ClaimsSet
+ Keycloak.Tokens: verifyJWT :: JWT -> Keycloak ClaimsSet
- Keycloak.Types: KCConfig :: Text -> Text -> Text -> Text -> KCConfig
+ Keycloak.Types: KCConfig :: AdapterConfig -> [JWK] -> KCConfig
- Keycloak.Types: [_confRealm] :: KCConfig -> Text
+ Keycloak.Types: [_confRealm] :: AdapterConfig -> Realm
- Keycloak.Types: confRealm :: Lens' KCConfig Text
+ Keycloak.Types: confRealm :: Lens' AdapterConfig Realm

Files

README.md view
@@ -16,29 +16,43 @@ Tutorial ======== -In this tutorial we'll learn how to use Keycloak-hs with a [small example](./examples/Main.hs).+In this tutorial, we'll configure Keycloak for running our [small example](./examples/Main.hs). First you should install and run Keycloak: [follow this tutorial](https://www.keycloak.org/docs/latest/getting_started/index.html). +The example is compiled and run this way:+```+$ stack install+$ example+```++But before running it, we should create a Client in Keycloak and retrieve the adapter config file.+In Keycloak admin panel, create the following:+- a realm named "demo"+- a client named "demo".++This file is now downloadable in your Client/Installation tab (JSON format).+Place it in this folder.+ Authentication --------------  Authentication with Keycloak is based on [JWTs](https://jwt.io/). -In Keycloak admin panel, create the following:-- a realm named "demo"-- a client named "demo".-- a user "demo" with password "demo"+In Keycloak, create a user named "demo" with password "demo". +Make sure that your user is enable, email verified, and the password is not temporary.+At this point, you should be able to retrieve tokens from Keycloak, verify them using this library, and extract a User from the tokens. -In the user, add an attribute, such as "phone".+Additionaly, you can add attributes to your user. In Keycloak UI, go in the User Attributes tab and add an attribute, such as "phone". In order for this attribute to appear in the token claims, we should also add a client "mapper". In the client "demo", click on "Mappers"/"add mappers".-Fill the name="demo", Mapper Type=User attribute, Token Claim Name="demo", Claim JSON Type=String, and save.--At this point, you should be able to retrieve tokens from Keycloak, verify them using this library, and extract a User from the tokens.+Fill the name="phone", Mapper Type=User attribute, Token Claim Name="phone", Claim JSON Type=String, and save.  Authorizations -------------- +Keycloak can also manage your resources and related access policies.+The idea is that, each time a user makes a request on your application, you will ask Keycloak "Can he really do that??".+ In the client "demo": - change "Access Type" to confidential - turn "Authorization Enabled" ON.@@ -63,23 +77,8 @@ - Select "view" in Scopes. - Select your previous policy "Demo user have access" in "Apply Policy". -That's it for the confguration of Keycloak. Keycloak is very complex, so you'll have fun exploring all the possibilities ;)--Example code--------------The folder example contains an [exemple of usage](./examples/Main.hs).-You should first input your "client secret", that can be found in the demo client "Credentials" tab in Keycloak admin panel.--Then run the example:-```-stack run example-```--The example first create a "client" token, necessary to create a resource in Keycloak.-It then create a Resourse, with a name, an optional type, URIs, scopes, owner and attributes.--We can then check if our user can access this resource, according to policies.-Finally, the example shows how to retrieve all permissions for a user.+That's it for the confguration of Keycloak.+You are now able to play with the "Authorization" part of the example.+Keycloak is very complex, so you'll have fun exploring all the possibilities ;)  Enjoy!
examples/Main.hs view
@@ -8,34 +8,24 @@ import System.Log.Logger  --- Kecyloak configuration.-kcConfig :: KCConfig-kcConfig = KCConfig {-  _confBaseUrl       = "http://localhost:8080/auth",-  _confRealm         = "demo",-  _confClientId      = "demo",-  _confClientSecret  = "3d792576-4e56-4c58-991a-49074e6a92ea"}- main :: IO () main = do   -- Keycloak-hs has logging, you can enable it for debugging.   updateGlobalLogger rootLoggerName (setLevel DEBUG) +  -- Read the Keycloak config file. You can retrieve this file in your Client/Installation tab (JSON format).+  kcConfig <- configureKeycloak "keycloak.json"+  putStrLn $ "Loaded Keycloak config: " ++ (show kcConfig)   -- We run all the commands in the 'Keycloak' Monad.   void $ flip runKeycloak kcConfig $ do     liftIO $ putStrLn "Starting tests..."   -    -- JWKs are public keys delivered by Keycloak to check the integrity of any JWT (user tokens).-    -- an application may retrieve these keys at startup and keep them.-    jwks <- getJWKs-    liftIO $ putStrLn $ "Got JWKs: \n" ++ (show jwks) ++ "\n\n"-       -- Get a JWT from Keycloak. A JWT can then be used to authentify yourself with an application.     jwt <- getJWT "demo" "demo"      liftIO $ putStrLn $ "Got JWT: \n" ++ (show jwt) ++ "\n\n"        -- Retrieve the claims contained in the JWT.-    claims <- verifyJWT (head jwks) jwt+    claims <- verifyJWT jwt     liftIO $ putStrLn $ "Claims decoded from Token: \n" ++ (show claims) ++ "\n\n"          -- get the user from the claim
keycloak-hs.cabal view
@@ -1,6 +1,6 @@ cabal-version: 1.12 name: keycloak-hs-version: 2.0.3+version: 2.1.0 license: BSD3 license-file: LICENSE copyright: 2019 Corentin Dupont@@ -26,6 +26,7 @@         Keycloak.Users         Keycloak.Utils         Keycloak.Types+        Keycloak.Config         Keycloak     hs-source-dirs: src     other-modules:
src/Keycloak.hs view
@@ -1,28 +1,12 @@ module Keycloak (-  -- | Tokens-  getJWKs,-  getJWT,-  getClientJWT,-  verifyJWT,-  getClaimsUser,-  isAuthorized,-  -- | Authorizations-  getPermissions,-  checkPermission,-  createResource,-  deleteResource,-  deleteAllResources,-  getResource,-  getAllResourceIds,-  updateResource,-  -- | Users-  getUsers,-  getUser,-  createUser,-  updateUser,-  module Keycloak.Types) where+  module Keycloak.Tokens,+  module Keycloak.Users,+  module Keycloak.Authorizations,+  module Keycloak.Types,+  module Keycloak.Config) where  import Keycloak.Tokens import Keycloak.Users import Keycloak.Authorizations import Keycloak.Types+import Keycloak.Config
src/Keycloak/Authorizations.hs view
@@ -1,31 +1,29 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} -{-|-This module helps you manage resources authorization with Keycloak.--In Keycloak, in the client, activate "Authorization Enabled" and set "Valid Redirect URIs" as "*".-You then need to create your scopes, policies and permissions in the authorization tab.-If you are unsure, set the "Policy Enforcement Mode" as permissive, so that a positive permission will be given with resources without policy.--The example below shows how to retrieve a token from Keycloak, and then retrieve the permissions of a user on a specific resource.--@--- Let's get a token for a specific user login/password-userToken <- getJWT "demo" "demo"---- Can I access this resource?-isAuth <- isAuthorized resId (ScopeName "view") userToken--liftIO $ putStrLn $ "User 'demo' can access resource 'demo': " ++ (show isAuth)---- We can also retrieve all the permissions for our user.-perms <- getPermissions [PermReq Nothing [ScopeName "view"]] userToken--liftIO $ putStrLn $ "All permissions: " ++ (show perms)-@---}+-- |+-- This module helps you manage resources authorization with Keycloak.+-- +-- In Keycloak, in the client, activate "Authorization Enabled" and set "Valid Redirect URIs" as "*".+-- You then need to create your scopes, policies and permissions in the authorization tab.+-- If you are unsure, set the "Policy Enforcement Mode" as permissive, so that a positive permission will be given with resources without policy.+-- +-- The example below shows how to retrieve a token from Keycloak, and then retrieve the permissions of a user on a specific resource.+-- +-- @+-- -- Let's get a token for a specific user login/password+-- userToken <- getJWT "demo" "demo"+-- +-- -- Can I access this resource?+-- isAuth <- isAuthorized resId (ScopeName "view") userToken+-- +-- liftIO $ putStrLn $ "User 'demo' can access resource 'demo': " ++ (show isAuth)+-- +-- -- We can also retrieve all the permissions for our user.+-- perms <- getPermissions [PermReq Nothing [ScopeName "view"]] userToken+-- +-- liftIO $ putStrLn $ "All permissions: " ++ (show perms)+-- @  module Keycloak.Authorizations where @@ -39,6 +37,7 @@ import           Keycloak.Types import           Keycloak.Tokens import           Keycloak.Utils as U+import           Control.Lens import           Network.HTTP.Types.Status import           Network.Wreq as W hiding (statusCode) import           Safe@@ -58,7 +57,7 @@ getPermissions :: [PermReq] -> JWT -> Keycloak [Permission] getPermissions reqs tok = do   debug "Get all permissions"-  client <- asks _confClientId+  client <- view $ confAdapterConfig.confResource   let dat = ["grant_type" := ("urn:ietf:params:oauth:grant-type:uma-ticket" :: Text),              "audience" := client,              "response_mode" := ("permissions" :: Text)] @@ -81,7 +80,7 @@ checkPermission :: ResourceId -> ScopeName -> JWT -> Keycloak () checkPermission (ResourceId res) (ScopeName scope) tok = do   debug $ "Checking permissions: " ++ (show res) ++ " " ++ (show scope)-  client <- asks _confClientId+  client <- view $ confAdapterConfig.confResource   let dat = ["grant_type" := ("urn:ietf:params:oauth:grant-type:uma-ticket" :: Text),              "audience" := client,              "permission"  := res <> "#" <> scope]
+ src/Keycloak/Config.hs view
@@ -0,0 +1,30 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns #-}++module Keycloak.Config where++import           Data.Aeson as JSON+import qualified Data.ByteString.Lazy as BL+import           Keycloak.Types+import           Keycloak.Tokens++-- | Read a configuration file.+-- This file can be found in Keycloak, in the Client Installation tab (JSON format).+readConfig :: FilePath -> IO AdapterConfig+readConfig f = do+  j <- BL.readFile f+  case eitherDecode j of+    Right c -> return c+    Left e -> error e++-- | Configure this library by reading the adapter JSON file, and getting signing keys from Keycloak.+-- The returned config can be used with 'runKeycloak' to run any function living in the 'Keycloak' Monad.+configureKeycloak :: FilePath -> IO KCConfig+configureKeycloak f = do+  adapterConf <- readConfig f+  jwks <- getJWKs (_confRealm adapterConf) (_confAuthServerUrl adapterConf)+  return $ KCConfig adapterConf jwks++
src/Keycloak/Tokens.hs view
@@ -3,89 +3,62 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} -{-|-Authentication with Keycloak is based on [JWTs](https://jwt.io/).-This module helps you retrieve tokens from Keycloak, and use them to authenticate your users.-In Keycloak, you need to configure a realm, a client and a user.--Users can also have additional attributes.-To see them in the Token, you need to add "protocol mappers" in the Client, that will copy the User attribute in the Token.--The example below retrieves a User token using Login/password, verifies it, and extract all the user details from it.--@--- Kecyloak configuration.-kcConfig :: KCConfig-kcConfig = KCConfig {-  _confBaseUrl       = "http://localhost:8080/auth",-  _confRealm         = "demo",-  _confClientId      = "demo",-  _confClientSecret  = "3d792576-4e56-4c58-991a-49074e6a92ea"}--main :: IO ()-main = do--  void $ flip runKeycloak kcConfig $ do-    liftIO $ putStrLn "Starting tests..."-  -    -- JWKs are public keys delivered by Keycloak to check the integrity of any JWT (user tokens).-    -- an application may retrieve these keys once at startup and keep them.-    jwks <- getJWKs-    liftIO $ putStrLn $ "Got JWKs: \n" ++ (show jwks) ++ "\n\n"-  -    -- Get a JWT from Keycloak. A JWT can then be used to authenticate yourself with an application.-    jwt <- getJWT "demo" "demo" -    liftIO $ putStrLn $ "Got JWT: \n" ++ (show jwt) ++ "\n\n"-  -    -- Retrieve the claims contained in the JWT.-    claims <- verifyJWT (head jwks) jwt-    liftIO $ putStrLn $ "Claims decoded from Token: \n" ++ (show claims) ++ "\n\n"-    -    -- get the user from the claim-    let user = getClaimsUser claims-    liftIO $ putStrLn $ "User decoded from claims: \n" ++ (show user) ++ "\n\n"-@--}+-- |+-- Authentication with Keycloak is based on [JWTs](https://jwt.io/).+-- This module helps you retrieve tokens from Keycloak, and use them to authenticate your users.+-- In Keycloak, you need to configure a realm, a client and a user.+-- +-- Users can also have additional attributes.+-- To see them in the Token, you need to add "protocol mappers" in the Client, that will copy the User attribute in the Token.+-- +-- The example below retrieves a User token using Login/password, verifies it, and extract all the user details from it.+-- +-- @+-- main :: IO ()+-- main = do+-- +--   --configure Keycloak with the adapter config file. You can retrieve this file in your Client/Installation tab (JSON format).+--   --This function will also get the signing keys from Keycloak, so make sure that Keycloak is on and configured!+--   kcConfig <- configureKeycloak "keycloak.json"+--+--   void $ flip runKeycloak kcConfig $ do+--   +--     -- Get a JWT from Keycloak. A JWT can then be used to authenticate yourself with an application.+--     jwt <- getJWT "demo" "demo" +--     liftIO $ putStrLn $ "Got JWT: \n" ++ (show jwt) ++ "\n\n"+--   +--     -- Retrieve the claims contained in the JWT.+--     claims <- verifyJWT jwt+--     liftIO $ putStrLn $ "Claims decoded from Token: \n" ++ (show claims) ++ "\n\n"+--     +--     -- get the user from the claim+--     let user = getClaimsUser claims+--     liftIO $ putStrLn $ "User decoded from claims: \n" ++ (show user) ++ "\n\n"+-- @  module Keycloak.Tokens where  import           Control.Lens hiding ((.=))-import           Control.Monad.Reader as R import           Control.Monad.Except (throwError) import           Crypto.JWT as JWT import           Data.Aeson as JSON import           Data.Aeson.Lens import           Data.Text as T hiding (head, tail, map) import           Data.Maybe-import qualified Data.HashMap.Strict as HM import           Data.String.Conversions import           Keycloak.Types import           Keycloak.Utils import           Network.Wreq as W hiding (statusCode) --- * Tokens --- | return JWKs from Keycloak. Its a set of keys that can be used to check signed tokens (JWTs)-getJWKs :: Keycloak [JWK]-getJWKs = do-  body <- keycloakGet' ("protocol/openid-connect/certs")-  info $ show body-  (JWKSet jwks) <- case eitherDecode body of-     Right ret -> do-       return ret-     Left (err2 :: String) -> do-       debug $ "Keycloak parse error: " ++ (show err2) -       throwError $ ParseError $ pack (show err2)-  return jwks - -- | Retrieve the user's token. This token can be used to authenticate the user. -- This token can be also used for every other Keycloak calls. getJWT :: Username -> Password -> Keycloak JWT getJWT username password = do    debug "Get user token"-  client <- asks _confClientId-  secret <- asks _confClientSecret+  client <- view $ confAdapterConfig.confResource+  secret <- view $ confAdapterConfig.confCredentials.confSecret   let dat = ["client_id" := client,               "client_secret" := secret,              "grant_type" := ("password" :: Text),@@ -105,8 +78,8 @@ getClientJWT :: Keycloak JWT getClientJWT = do   debug "Get client token"-  client <- asks _confClientId-  secret <- asks _confClientSecret+  client <- view $ confAdapterConfig.confResource+  secret <- view $ confAdapterConfig.confCredentials.confSecret   let dat = ["client_id" := client,               "client_secret" := secret,              "grant_type" := ("client_credentials" :: Text)]@@ -121,8 +94,10 @@   -- | Verify a JWT. If sucessful, the claims are returned. Otherwise, a JWTError is thrown. -verifyJWT :: JWK -> JWT -> Keycloak ClaimsSet-verifyJWT jwk jwt = verifyClaims (defaultJWTValidationSettings (const True)) jwk jwt+verifyJWT :: JWT -> Keycloak ClaimsSet+verifyJWT jwt = do+  jwks <- view confJWKs+  verifyClaims (defaultJWTValidationSettings (const True)) (head jwks) jwt  -- | Extract the user identity from a token. Additional attributes can be encoded in the token. getClaimsUser :: ClaimsSet -> User@@ -134,3 +109,19 @@                             , userAttributes  = preview unregisteredClaims claims}  +-- | return JWKs from Keycloak. Its a set of keys that can be used to check signed tokens (JWTs)+-- This is done for you in the 'configureKeycloak' function. JWKs are stored in the Keycloak State Monad.+getJWKs :: Realm -> ServerURL -> IO [JWK]+getJWKs realm baseUrl = do+  let opts = W.defaults+  let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/protocol/openid-connect/certs") +  info $ "Issuing KEYCLOAK GET with url: " ++ (show url) +  debug $ "  headers: " ++ (show $ opts ^. W.headers) +  res <- W.getWith opts url+  let body = fromJust $ res ^? responseBody+  info $ show body+  case eitherDecode body of+     Right (JWKSet jwks) -> return jwks+     Left (err2 :: String) -> do+       debug $ "Keycloak parse error: " ++ (show err2) +       error $ (show err2)
src/Keycloak/Types.hs view
@@ -47,21 +47,49 @@   _Error = _JWSError  --- | Configuration of Keycloak. data KCConfig = KCConfig {-  _confBaseUrl       :: Text,  -- ^ Base url where Keycloak resides-  _confRealm         :: Text,  -- ^ realm to use-  _confClientId      :: Text,  -- ^ client id-  _confClientSecret  :: Text}  -- ^ client secret, found in Client/Credentials tab-  deriving (Eq, Show)+  _confAdapterConfig :: AdapterConfig,+  _confJWKs :: [JWK]}+  deriving (Eq, Show, Generic) +type Realm = Text+type ClientId = Text+type ServerURL = Text++-- | Configuration of Keycloak.+data AdapterConfig = AdapterConfig {+  _confRealm         :: Realm,              -- ^ realm to use+  _confAuthServerUrl :: ServerURL,          -- ^ Base url where Keycloak resides+  _confResource      :: ClientId,           -- ^ client id+  _confCredentials   :: ClientCredentials}  -- ^ client secret, found in Client/Credentials tab+  deriving (Eq, Show, Generic)++instance ToJSON AdapterConfig where+  toJSON = genericToJSON $ trainDrop 5++instance FromJSON AdapterConfig where+  parseJSON = genericParseJSON $ trainDrop 5++data ClientCredentials = ClientCredentials {+  _confSecret :: Text}+  deriving (Eq, Show, Generic)++instance ToJSON ClientCredentials where+  toJSON = genericToJSON $ trainDrop 5++instance FromJSON ClientCredentials where+  parseJSON = genericParseJSON $ trainDrop 5++trainDrop :: Int -> Options+trainDrop n = defaultOptions {fieldLabelModifier = trainCase . drop n, omitNothingFields = True}+ -- | Default configuration-defaultKCConfig :: KCConfig-defaultKCConfig = KCConfig {-  _confBaseUrl       = "http://localhost:8080/auth",+defaultAdapterConfig :: AdapterConfig+defaultAdapterConfig = AdapterConfig {   _confRealm         = "waziup",-  _confClientId      = "api-server",-  _confClientSecret  = "4e9dcb80-efcd-484c-b3d7-1e95a0096ac0"}+  _confAuthServerUrl = "http://localhost:8080/auth",+  _confResource      = "api-server",+  _confCredentials   = ClientCredentials "4e9dcb80-efcd-484c-b3d7-1e95a0096ac0"}  -- | Run a Keycloak monad within IO. runKeycloak :: Keycloak a -> KCConfig -> IO (Either KCError a)@@ -285,3 +313,5 @@   makeLenses ''KCConfig+makeLenses ''ClientCredentials+makeLenses ''AdapterConfig
src/Keycloak/Users.hs view
@@ -3,24 +3,22 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ViewPatterns #-} -{-|-This module helps you manage users in Keycloak.-You can create, read and update users.-To activate this, you need to give the role "manage users" to your user in Keycloak.-For this, go in your user, select the "Role mappings" tab.-Then in "client Roles", select "realm management" and assign the role "manage-users".--Example usage:--@--- Get a JWT from Keycloak. A JWT can then be used to authenticate yourself.-jwt <- getJWT "demo" "demo" --users <- getUsers Nothing Nothing Nothing jwt-liftIO $ putStrLn $ "All Users: " ++ (show users)-@---}+-- |+-- This module helps you manage users in Keycloak.+-- You can create, read and update users.+-- To activate this, you need to give the role "manage users" to your user in Keycloak.+-- For this, go in your user, select the "Role mappings" tab.+-- Then in "client Roles", select "realm management" and assign the role "manage-users".+-- +-- Example usage:+-- +-- @+-- -- Get a JWT from Keycloak. A JWT can then be used to authenticate yourself.+-- jwt <- getJWT "demo" "demo" +-- +-- users <- getUsers Nothing Nothing Nothing jwt+-- liftIO $ putStrLn $ "All Users: " ++ (show users)+-- @  module Keycloak.Users where 
src/Keycloak/Utils.hs view
@@ -9,7 +9,6 @@ import           Control.Monad.Reader as R import qualified Control.Monad.Catch as C import           Control.Monad.Except (throwError, catchError, MonadError)-import           Data.Aeson as JSON import           Data.Text as T hiding (head, tail, map) import           Data.Maybe import           Data.List as L@@ -23,10 +22,12 @@ import           System.Log.Logger import           Crypto.JWT as JWT + -- | Perform post to Keycloak. keycloakPost :: (Postable dat, Show dat) => Path -> dat -> JWT -> Keycloak BL.ByteString-keycloakPost path dat jwt = do -  (KCConfig baseUrl realm _ _) <- ask+keycloakPost path dat jwt = do+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact jwt)]   let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK POST with url: " ++ (show url) @@ -43,7 +44,8 @@ -- | Perform post to Keycloak, without token. keycloakPost' :: (Postable dat, Show dat) => Path -> dat -> Keycloak BL.ByteString keycloakPost' path dat = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults   let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK POST with url: " ++ (show url) @@ -60,7 +62,8 @@ -- | Perform delete to Keycloak. keycloakDelete :: Path -> JWT -> Keycloak () keycloakDelete path jwt = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact jwt)]   let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK DELETE with url: " ++ (show url) @@ -75,7 +78,8 @@ -- | Perform get to Keycloak on admin API keycloakGet :: Path -> JWT -> Keycloak BL.ByteString keycloakGet path tok = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact tok)]   let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK GET with url: " ++ (show url) @@ -91,7 +95,8 @@ -- | Perform get to Keycloak on admin API, without token keycloakGet' :: Path -> Keycloak BL.ByteString keycloakGet' path = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults   let url = (unpack $ baseUrl <> "/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK GET with url: " ++ (show url) @@ -108,7 +113,8 @@ -- | Perform get to Keycloak on admin API keycloakAdminGet :: Path -> JWT -> Keycloak BL.ByteString keycloakAdminGet path tok = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact tok)]   let url = (unpack $ baseUrl <> "/admin/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK GET with url: " ++ (show url) @@ -124,7 +130,8 @@ -- | Perform post to Keycloak. keycloakAdminPost :: (Postable dat, Show dat) => Path -> dat -> JWT -> Keycloak BL.ByteString keycloakAdminPost path dat tok = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact tok)]   let url = (unpack $ baseUrl <> "/admin/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK POST with url: " ++ (show url) @@ -143,7 +150,8 @@ -- | Perform put to Keycloak. keycloakAdminPut :: (Putable dat, Show dat) => Path -> dat -> JWT -> Keycloak () keycloakAdminPut path dat tok = do -  (KCConfig baseUrl realm _ _) <- ask+  realm <- view (confAdapterConfig.confRealm)+  baseUrl <- view (confAdapterConfig.confAuthServerUrl)   let opts = W.defaults & W.header "Authorization" .~ ["Bearer " <> (convertString $ encodeCompact tok)]   let url = (unpack $ baseUrl <> "/admin/realms/" <> realm <> "/" <> path)    info $ "Issuing KEYCLOAK PUT with url: " ++ (show url) @@ -159,10 +167,6 @@  -- * Helpers -readString :: Value -> Maybe Text-readString (String a) = Just a-readString _ = Nothing- debug, warn, info, err :: (MonadIO m) => String -> m () debug s = liftIO $ debugM "Keycloak" s info s  = liftIO $ infoM "Keycloak" s@@ -175,4 +179,6 @@  try :: MonadError a m => m b -> m (Either a b) try act = catchError (Right <$> act) (return . Left)++