Skip to main content

A module to automate stuff in Keycloak.

Project description

Keycloak API

Python module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.

How To Install

pip install kcapi

Testing

To run the test you would need a Keycloak instance, you can run one locally or in the cloud then you just have to follow this steps:

python3.10 -m venv .venv
source .venv/bin/activate
pip install requests

# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,
# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.
export KC_USER=admin
export KC_PASSWORD=admin_password
export KC_REALM=myrealm  # do not use master realm, it cannot be removed
export KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com

python -m unittest

API

OpenID

This class takes care of OpenID login using password owner credentials flow.

Constructor

from rhsso import OpenID

oid_client = OpenID({
        "client_id": "admin-cli",
        "username": USER,
        "password": PASSWORD,
        "grant_type":"password",
        "realm" : "master"
        }, endpoint)
  • client_id: Client Identifier in Keycloak.
  • username: Login username for the Realm.
  • password: Login password for the Realm.
  • grant_type: The grant type you want to use (usually password).
  • endpoint: A Keycloak or RHSSO URL endpoint, something like: https://my_keycloak.com.

Methods

getToken

This will initiate a session with the Keycloak server and will return a OpenID token back.

oid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA
createAdminClient

This static method should be used in order to access the master Realm in Keycloak.

    oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)
    oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA

Keycloak

This class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.

Constructor

kc = Keycloak(token, self.ENDPOINT)

The constructor takes two parameters:

  • token: A token with enough priviledge to perform the desired operation.
  • endpoint: A Keycloak or RHSSO URL endpoint, something like: https://my_keycloak.com.

Methods

build

This methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.

groups = kc.build('groups', 'my_realm')

# Create a group called DC
state = groups.create({"name": "DC"}).isOk()

In this example we build the 'groups' API for my_realm Realm.

Supported Resources

Here is a quick list of supported resources:

As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented components endpoint.

  • components: This API allows you configure things like user federation or Realm keys.

  • authentication: Provide access to built-in and/or custom authentication flows.


admin

Similar to the build method but the client points to the master realm, allowing us operation such as realm creation.

    main_realm = kc.admin()

    # Creates a realm called my_realm
    main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})

REST API

When you use the build or admin methods you will get back a REST class pointing to the Keycloak resource, keep in mind that this class don't check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future.

Usage

In order to create one you need to build method we have used before:

batman = {
    "enabled":'true',
    "attributes":{},
    "username":"batman",
    "firstName":"Bruce",
    "lastName":"Wayne",
    "emailVerified":""
}

users = kc.build('users', 'DC')

# Create a user called batman in DC
state = users.create(batman).isOk()

Methods

Following the example above lets see the methods we have starting with the usual CRUD methods:

create

This method POST a dictionary into any given resource:

batman = {
    "enabled":'true',
    "attributes":{},
    "username":"batman",
    "firstName":"Bruce",
    "lastName":"Wayne",
    "emailVerified":""
}

state = users.create(batman).isOk()
  • dictionary: Dictionary with the fields we want to POST to the server.

update

This method performs a PUT on the resource.

batman_update = {
    "firstName":"Bruno",
    "emailVerified": True
}

id = 'bf81a9d9-811f-4807-bd69-3d74eecbe9f4'

state = users.update(id, batman_update).isOk()
  • id: Id of the resource in Keycloak.
  • dictionary: Dictionary representing the updated fields.

remove

This method sends a DELETE to the pointed resource.

batman_update = {
    "firstName":"Bruno",
    "emailVerified": True
}

id = 'bf81a9d9-811f-4807-bd69-3d74eecbe9f4'

state = users.remove(id).isOk()
  • id: Id of the resource in Keycloak.
  • dictionary: Dictionary representing the updated fields.

get

Send a GET request to retrieve a specific Keycloak resource.

id = 'bf81a9d9-811f-4807-bd69-3d74eecbe9f4'

user = users.get(id).response()

all

Return all objects of a particular resource type.

users = kc.build('users', 'DC')

# Create a user called batman in DC
user_list = users.all() # [ {id:'xxx-yyy', username: 'batman', ...} ]   

findFirst

Finds a resource by passing an arbitrary key/value pair.

users = kc.build('users', 'DC')

users.findFirst({"key":"username", "value": 'batman'})

exist

Check if a resource matching the provided id exists:

users = kc.build('users', 'DC')
id = 'bf81a9d9-811f-4807-bd69-3d74eecbe9f4'

users.exists(id) #True

existByKV

Check if a resource matching the provided key/value pair, exists.

users = kc.build('users', 'DC')

users.existByKV("username", 'batman') #False

ResponseHandler

Each CRUD method returns a ResponseHandler class with the following methods.

Methods

response

returns the requests response object.

users.update(id, batman_update).response().status_code #HTTP 201

isOk

Return True if the request complete successfully otherwise it will raise an exception.

state = users.update(id, batman_update).isOk() # Return True here.

verify

Does the same as isOk but it allow you to chain more methods.

batman_update = {
    "firstName":"Bruno",
    "emailVerified": True
}

id = 'bf81a9d9-811f-4807-bd69-3d74eecbe9f4'

cookies = users.update(id, batman_update).verify().response().cookies # Get cookies.

Specialisations

Realms

KeycloakCaches

This class handles the Keycloak caches.

Instantiation

# Creates a REST API instance target the Realms API. 
realms = kc.build('realms', 'my_realm') 

# Gets the cache Realms cache API. 
caches = realms.caches(self.REALM)

clearUserCache

This method tells Keycloak to clear the user cache.

caches.clearUserCache()

clearRealmCache

This method tells Keycloak to clear the realm cache.

caches.clearRealmCache()

clearKeyCache

This method tells Keycloak to clear the external public key cache for clients and identity providers.

caches.clearKeyCache()

For more information on how this caches works follow this link.

Users

updateCredentials

Update user credentials.

user_credentials = {
          'temporary': False,
          'value':'12345'
}

state = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.

Where:

  • temporary: Boolean where if True provide a temporary password just for the first login.
  • value: String with the password.

joinGroup

Add a user into a existing group.

First we need a group:

def createDCGroup():
  group = kc.build('groups', 'heroes')
  return group.create({"name": "DC"}).isOk()

Then we can join the group the following way:

  createDCGroup()

  users = kc.build('users', 'heroes')
  user = {"key": "username", "value": "batman"}
  group = {"key": "name", "value": "DC"}

  users.joinGroup(user, group).isOk()

The API works by matching the first occurrence between the provided key/value for the two resources (User and Group), this can help in various situation for example if we want to target the user by uuid.

Using uuid as user identifier.

  createDCGroup()

  users = kc.build('users', 'heroes')
  user = {"key": "uuid", "value": "23e4567-e89b-..."}
  group = {"key": "name", "value": "DC"}

  users.joinGroup(user, group).isOk()

Or we want to use the group id:

  user = {"key": "uuid", "value": "23e4567-e89b-..."}
  group = {"key": "id", "value": "f8d91722-a1f0-45e..."}

  users.joinGroup(user, group).isOk()

If the field criteria don't return a unique value, the first entry in the list will be used.

leaveGroup

Remove a user from a group.

  createDCGroup()

  users = kc.build('users', 'heroes')
  user = {"key": "username", "value": "batman"}
  group = {"key": "uuid", "value": "123e4567-e89b-..."}

  users.leaveGroup(user, group).isOk()

  user = {"key": "uuid", "value": "12d3-a456-4"}
  group = {"key": "id", "value": "123e4567-e89b-..."}

  users.leaveGroup(user, group).isOk()

The same rules for key/value discussed above also applies here.

Groups

To manage the relationship between realm level roles and groups, we can use the RealmsRolesMapping.

To get an instance of this class you need to instantiate the group resource class:

groups = kc.build('groups', 'heroes')

And use the method realmRoles passing a valid group dictionary:

realmsRoles = groups.realmRoles({"key":"name", "value":'DC'})

Then we get a class with following methods:

add

Add a list of existing roles to a group.

def makeRoles(self):
    roles = kc.build('roles', self.realm)
    lvl1 = roles.create({"name": "level-1"}).isOk()
    lvl2 = roles.create({"name": "level-2"}).isOk()
    return lvl1 and lvl2


if makeRoles():
  realmsRoles = groups.realmRoles({"key":"name", "value":'DC'})
  realmsRoles.add(["level-1", "level-2"])

remove

Remove a list of associated roles from a group.

realmsRoles = groups.realmRoles({"key":"name", "value":'DC'})
realmsRoles.remove(["level-1", "level-2"])

Roles

composite

In Keycloak we can map roles to other roles, this method allow you to do just that.

role_watch = self.kc.build('roles', 'my-realm').find('watch')
added = role_watch.add_composite('view')

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

kcapi-1.1.2.tar.gz (31.2 kB view hashes)

Uploaded Source

Built Distribution

kcapi-1.1.2-py3-none-any.whl (22.3 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page