Package: kcapi
Keycloak APIPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.How To InstallpipinstallkcapiTestingTo run the test you would need a Keycloak instance, you can run one locally or in thecloudthen you just have to follow this steps:python3.10-mvenv.venvsource.venv/bin/activate
pipinstallrequests# 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.exportKC_USER=adminexportKC_PASSWORD=admin_passwordexportKC_REALM=myrealm# do not use master realm, it cannot be removedexportKC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com

python-munittestAPIOpenIDThis class takes care of OpenID login usingpassword owner credentialsflow.ConstructorfromrhssoimportOpenIDoid_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 (usuallypassword).endpoint: A Keycloak or RHSSO URL endpoint, something like:https://my_keycloak.com.MethodsgetTokenThis will initiate a session with the Keycloak server and will return a OpenID token back.oid_client.getToken()#glTeDLlmmpLYoAAUMcFQqNOMjw5dAcreateAdminClientThis static method should be used in order to access the master Realm in Keycloak.oidc=OpenID.createAdminClient(self.USER,self.PASSWORD)oidc.getToken()#glTeDLlmmpLYoAAUMcFQqNOMjw5dAKeycloakThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.Constructorkc=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.MethodsbuildThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.groups=kc.build('groups','my_realm')# Create a group called DCstate=groups.create({"name":"DC"}).isOk()In this example we build the 'groups' API formy_realmRealm.Supported ResourcesHere is a quick list of supported resources:users:users API.clients:client API.groups:groups API.roles:roles APIidentity-provider:identity provider APIAs 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 documentedcomponentsendpoint.components: This API allows you configure things likeuser federationorRealm keys.authentication: Provide access to built-in and/or custom authentication flows.adminSimilar to thebuildmethod but the client points to themasterrealm, allowing us operation such as realm creation.main_realm=kc.admin()# Creates a realm called my_realmmain_realm.create({"enabled":"true","id":my_realm,"realm":my_realm})REST APIWhen you use thebuildoradminmethods you will get back aRESTclass 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.UsageIn order to create one you need tobuildmethod 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 DCstate=users.create(batman).isOk()MethodsFollowing the example above lets see the methods we have starting with the usual CRUD methods:createThis methodPOSTa 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.updateThis method performs aPUTon 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.removeThis method sends aDELETEto 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.getSend aGETrequest to retrieve a specific Keycloak resource.id='bf81a9d9-811f-4807-bd69-3d74eecbe9f4'user=users.get(id).response()allReturn all objects of a particular resource type.users=kc.build('users','DC')# Create a user called batman in DCuser_list=users.all()# [ {id:'xxx-yyy', username: 'batman', ...} ]findFirstFinds a resource by passing an arbitrary key/value pair.users=kc.build('users','DC')users.findFirst({"key":"username","value":'batman'})existCheck if a resource matching the providedidexists:users=kc.build('users','DC')id='bf81a9d9-811f-4807-bd69-3d74eecbe9f4'users.exists(id)#TrueexistByKVCheck if a resource matching the provided key/value pair, exists.users=kc.build('users','DC')users.existByKV("username",'batman')#FalseResponseHandlerEachCRUDmethod returns aResponseHandlerclass with the following methods.Methodsresponsereturns the requestsresponse object.users.update(id,batman_update).response().status_code#HTTP 201isOkReturnTrueif the request complete  successfully otherwise it will raise an exception.state=users.update(id,batman_update).isOk()# Return True here.verifyDoes the same asisOkbut 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.SpecialisationsRealmsKeycloakCachesThis 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)clearUserCacheThis method tells Keycloak to clear the user cache.caches.clearUserCache()clearRealmCacheThis method tells Keycloak to clear the realm cache.caches.clearRealmCache()clearKeyCacheThis 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 thislink.UsersupdateCredentialsUpdate user credentials.user_credentials={'temporary':False,'value':'12345'}state=users.updateCredentials(user_info,user_credentials).isOk()#Updateduserpassword.Where:temporary: Boolean where ifTrueprovide a temporary password just for the first login.value: String with the password.joinGroupAdd a user into a existinggroup.First we need a group:defcreateDCGroup():group=kc.build('groups','heroes')returngroup.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 providedkey/valuefor the two resources (User and Group), this can help in various situation for example if we want to target the user byuuid.Usinguuidas 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 groupid: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.leaveGroupRemove 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 forkey/valuediscussed above also applies here.GroupsTo manage the relationship between realm levelrolesand groups, we can use theRealmsRolesMapping.To get an instance of this class you need to instantiate thegroupresource class:groups=kc.build('groups','heroes')And use the methodrealmRolespassing a validgroup dictionary:realmsRoles=groups.realmRoles({"key":"name","value":'DC'})Then we get a class with following methods:addAdd a list of existing roles to a group.defmakeRoles(self):roles=kc.build('roles',self.realm)lvl1=roles.create({"name":"level-1"}).isOk()lvl2=roles.create({"name":"level-2"}).isOk()returnlvl1andlvl2ifmakeRoles():realmsRoles=groups.realmRoles({"key":"name","value":'DC'})realmsRoles.add(["level-1","level-2"])removeRemove a list of associated roles from a group.realmsRoles=groups.realmRoles({"key":"name","value":'DC'})realmsRoles.remove(["level-1","level-2"])RolescompositeIn 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')