diff --git a/README.md b/README.md index 63abe49..da88a9b 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['ac token_info = keycloak_openid.introspect(token['access_token']) # Decode Token -KEYCLOAK_PUBLIC_KEY = keycloak_openid.public_key() +KEYCLOAK_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\n" + keycloak_openid.public_key() + "\n-----END PUBLIC KEY-----" options = {"verify_signature": True, "verify_aud": True, "verify_exp": True} token_info = keycloak_openid.decode_token(token['access_token'], key=KEYCLOAK_PUBLIC_KEY, options=options) @@ -215,7 +215,7 @@ role = keycloak_admin.get_client_role(client_id="client_id", role_name="role_nam role_id = keycloak_admin.get_client_role_id(client_id="client_id", role_name="test") # Create client role -keycloak_admin.create_client_role(client_id='client_id', {'name': 'roleName', 'clientRole': True}) +keycloak_admin.create_client_role(client_role_id='client_id', {'name': 'roleName', 'clientRole': True}) # Assign client role to user. Note that BOTH role_name and role_id appear to be required. keycloak_admin.assign_client_role(client_id="client_id", user_id="user_id", role_id="role_id", role_name="test") diff --git a/keycloak/keycloak_admin.py b/keycloak/keycloak_admin.py index 9758cf0..473bc86 100644 --- a/keycloak/keycloak_admin.py +++ b/keycloak/keycloak_admin.py @@ -47,7 +47,8 @@ from .urls_patterns import URL_ADMIN_SERVER_INFO, URL_ADMIN_CLIENT_AUTHZ_RESOURC URL_ADMIN_REALM_ROLES_MEMBERS, URL_ADMIN_CLIENT_PROTOCOL_MAPPER, URL_ADMIN_CLIENT_SCOPES_MAPPERS, \ URL_ADMIN_FLOWS_EXECUTIONS_EXEUCUTION, URL_ADMIN_FLOWS_EXECUTIONS_FLOW, URL_ADMIN_FLOWS_COPY, \ URL_ADMIN_FLOWS_ALIAS, URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER, URL_ADMIN_AUTHENTICATOR_CONFIG, \ - URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE, URL_ADMIN_CLIENT_ALL_SESSIONS, URL_ADMIN_EVENTS, URL_ADMIN_USER_LOGOUT + URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE, URL_ADMIN_CLIENT_ALL_SESSIONS, URL_ADMIN_EVENTS, \ + URL_ADMIN_REALM_EXPORT, URL_ADMIN_DELETE_USER_ROLE, URL_ADMIN_USER_LOGOUT class KeycloakAdmin: @@ -223,6 +224,8 @@ class KeycloakAdmin: if not partial_results: break results.extend(partial_results) + if len(partial_results) < query['max']: + break page += 1 return results @@ -242,6 +245,22 @@ class KeycloakAdmin: data=json.dumps(payload)) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201]) + def export_realm(self, export_clients=False, export_groups_and_role=False): + """ + Export the realm configurations in the json format + + RealmRepresentation + https://www.keycloak.org/docs-api/5.0/rest-api/index.html#_partialexport + + :param export-clients: Skip if not want to export realm clients + :param export-groups-and-roles: Skip if not want to export realm groups and roles + + :return: realm configurations JSON + """ + params_path = {"realm-name": self.realm_name, "export-clients": export_clients, "export-groups-and-roles": export_groups_and_role } + data_raw = self.raw_post(URL_ADMIN_REALM_EXPORT.format(**params_path), data="") + return raise_error_from_response(data_raw, KeycloakGetError) + def get_realms(self): """ Lists all realms in Keycloak deployment @@ -1787,6 +1806,20 @@ class KeycloakAdmin: data=None, **query) return raise_error_from_response(data_raw, KeycloakGetError) + def set_events(self, payload): + """ + Set realm events configuration + + RealmEventsConfigRepresentation + https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmeventsconfigrepresentation + + :return: Http response + """ + params_path = {"realm-name": self.realm_name} + data_raw = self.raw_put(URL_ADMIN_EVENTS.format(**params_path), + data=json.dumps(payload)) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204]) + def raw_get(self, *args, **kwargs): """ Calls connection.raw_get. @@ -1840,14 +1873,17 @@ class KeycloakAdmin: return r def get_token(self): + token_realm_name = 'master' if self.client_secret_key else self.user_realm_name or self.realm_name self.keycloak_openid = KeycloakOpenID(server_url=self.server_url, client_id=self.client_id, - realm_name=self.user_realm_name or self.realm_name, verify=self.verify, + realm_name=token_realm_name, verify=self.verify, client_secret_key=self.client_secret_key, custom_headers=self.custom_headers) grant_type = ["password"] if self.client_secret_key: grant_type = ["client_credentials"] + if self.user_realm_name: + self.realm_name = self.user_realm_name self._token = self.keycloak_openid.token(self.username, self.password, grant_type=grant_type) @@ -1891,3 +1927,15 @@ class KeycloakAdmin: params_path = {"realm-name": self.realm_name, "id": client_id} data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)) return raise_error_from_response(data_raw, KeycloakGetError) + + + def delete_user_realm_role(self, user_id, payload): + """ + Delete realm-level role mappings + DELETE admin/realms/{realm-name}/users/{id}/role-mappings/realm + + """ + params_path = {"realm-name": self.realm_name, "id": str(user_id) } + data_raw = self.connection.raw_delete(URL_ADMIN_DELETE_USER_ROLE.format(**params_path), + data=json.dumps(payload)) + return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204]) \ No newline at end of file diff --git a/keycloak/keycloak_openid.py b/keycloak/keycloak_openid.py index 0f801ea..197dd26 100644 --- a/keycloak/keycloak_openid.py +++ b/keycloak/keycloak_openid.py @@ -189,7 +189,7 @@ class KeycloakOpenID: payload = {"username": username, "password": password, "client_id": self.client_id, "grant_type": grant_type, "code": code, "redirect_uri": redirect_uri} - if payload: + if extra: payload.update(extra) if totp: diff --git a/keycloak/urls_patterns.py b/keycloak/urls_patterns.py index 3bb0ccf..1332586 100644 --- a/keycloak/urls_patterns.py +++ b/keycloak/urls_patterns.py @@ -90,6 +90,7 @@ URL_ADMIN_IDP_MAPPERS = "admin/realms/{realm-name}/identity-provider/instances/{ URL_ADMIN_IDP = "admin/realms//{realm-name}/identity-provider/instances/{alias}" URL_ADMIN_REALM_ROLES_ROLE_BY_NAME = "admin/realms/{realm-name}/roles/{role-name}" URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE = "admin/realms/{realm-name}/roles/{role-name}/composites" +URL_ADMIN_REALM_EXPORT = "admin/realms/{realm-name}/partial-export?exportClients={export-clients}&exportGroupsAndRoles={export-groups-and-roles}" URL_ADMIN_FLOWS = "admin/realms/{realm-name}/authentication/flows" URL_ADMIN_FLOWS_ALIAS = "admin/realms/{realm-name}/authentication/flows/{flow-id}" @@ -106,4 +107,6 @@ URL_ADMIN_KEYS = "admin/realms/{realm-name}/keys" URL_ADMIN_USER_FEDERATED_IDENTITIES = "admin/realms/{realm-name}/users/{id}/federated-identity" URL_ADMIN_USER_FEDERATED_IDENTITY = "admin/realms/{realm-name}/users/{id}/federated-identity/{provider}" -URL_ADMIN_EVENTS = 'admin/realms/{realm-name}/events' \ No newline at end of file +URL_ADMIN_EVENTS = 'admin/realms/{realm-name}/events' + +URL_ADMIN_DELETE_USER_ROLE = "admin/realms/{realm-name}/users/{id}/role-mappings/realm" \ No newline at end of file