diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..1aba38f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include LICENSE diff --git a/docs/source/conf.py b/docs/source/conf.py index 8614386..ba92885 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -60,9 +60,9 @@ author = 'Marcos Pereira' # built documents. # # The short X.Y version. -version = '0.19.0' +version = '0.20.0' # The full version, including alpha/beta/rc tags. -release = '0.19.0' +release = '0.20.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/source/index.rst b/docs/source/index.rst index af697da..597cfb2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -158,6 +158,14 @@ Main methods:: # realm_name="example_realm", # verify=True, # custom_headers={'CustomHeader': 'value'}) + # + # You can also authenticate with client_id and client_secret + #keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/", + # client_id="example_client", + # client_secret_key="secret", + # realm_name="example_realm", + # verify=True, + # custom_headers={'CustomHeader': 'value'}) # Add user new_user = keycloak_admin.create_user({"email": "example@example.com", diff --git a/keycloak/connection.py b/keycloak/connection.py index 4841792..12903a0 100644 --- a/keycloak/connection.py +++ b/keycloak/connection.py @@ -199,7 +199,7 @@ class ConnectionManager(object): raise KeycloakConnectionError( "Can't connect to server (%s)" % e) - def raw_delete(self, path, data, **kwargs): + def raw_delete(self, path, data={}, **kwargs): """ Submit delete request to the path. :arg diff --git a/keycloak/keycloak_admin.py b/keycloak/keycloak_admin.py index 6d7bd20..0bcf28f 100644 --- a/keycloak/keycloak_admin.py +++ b/keycloak/keycloak_admin.py @@ -28,6 +28,8 @@ import json from builtins import isinstance from typing import List, Iterable +from keycloak.urls_patterns import URL_ADMIN_GROUPS_REALM_ROLES, \ + URL_ADMIN_GET_GROUPS_REALM_ROLES from .connection import ConnectionManager from .exceptions import raise_error_from_response, KeycloakGetError from .keycloak_openid import KeycloakOpenID @@ -60,7 +62,7 @@ class KeycloakAdmin: _custom_headers = None _user_realm_name = None - def __init__(self, server_url, username, password, realm_name='master', client_id='admin-cli', verify=True, + def __init__(self, server_url, username=None, password=None, realm_name='master', client_id='admin-cli', verify=True, client_secret_key=None, custom_headers=None, user_realm_name=None, auto_refresh_token=None): """ @@ -936,6 +938,47 @@ class KeycloakAdmin: data=json.dumps(payload)) return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204) + def assign_group_realm_roles(self, group_id, roles): + """ + Assign realm roles to a group + + :param group_id: id of groupp + :param roles: roles list or role (use GroupRoleRepresentation) + :return Keycloak server response + """ + + payload = roles if isinstance(roles, list) else [roles] + params_path = {"realm-name": self.realm_name, "id": group_id} + data_raw = self.raw_post(URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), + data=json.dumps(payload)) + return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204) + + def delete_group_realm_roles(self, group_id, roles): + """ + Delete realm roles of a group + + :param group_id: id of group + :param roles: roles list or role (use GroupRoleRepresentation) + :return Keycloak server response + """ + + payload = roles if isinstance(roles, list) else [roles] + params_path = {"realm-name": self.realm_name, "id": group_id} + data_raw = self.raw_delete(URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), + data=json.dumps(payload)) + return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204) + + def get_group_realm_roles(self, group_id): + """ + Get all realm roles for a group. + + :param user_id: id of the group + :return: Keycloak server response (array RoleRepresentation) + """ + params_path = {"realm-name": self.realm_name, "id": group_id} + data_raw = self.raw_get(URL_ADMIN_GET_GROUPS_REALM_ROLES.format(**params_path)) + return raise_error_from_response(data_raw, KeycloakGetError) + def get_client_roles_of_user(self, user_id, client_id): """ Get all client roles for a user. @@ -1103,6 +1146,20 @@ class KeycloakAdmin: return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201) + def generate_client_secrets(self, client_id): + """ + + Generate a new secret for the client + https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_regeneratesecret + + :param client_id: id of client (not client-id) + :return: Keycloak server response (ClientRepresentation) + """ + + params_path = {"realm-name": self.realm_name, "id": client_id} + data_raw = self.raw_post(URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None) + return raise_error_from_response(data_raw, KeycloakGetError) + def get_client_secrets(self, client_id): """ diff --git a/keycloak/urls_patterns.py b/keycloak/urls_patterns.py index e3f4d95..19ca28f 100644 --- a/keycloak/urls_patterns.py +++ b/keycloak/urls_patterns.py @@ -43,6 +43,8 @@ URL_ADMIN_RESET_PASSWORD = "admin/realms/{realm-name}/users/{id}/reset-password" URL_ADMIN_GET_SESSIONS = "admin/realms/{realm-name}/users/{id}/sessions" URL_ADMIN_USER_CLIENT_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}" URL_ADMIN_USER_REALM_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/realm" +URL_ADMIN_GROUPS_REALM_ROLES = "admin/realms/{realm-name}/groups/{id}/role-mappings/realm" +URL_ADMIN_GET_GROUPS_REALM_ROLES = "admin/realms/{realm-name}/groups/{id}/role-mappings" URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/available" URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}/composite" URL_ADMIN_USER_GROUP = "admin/realms/{realm-name}/users/{id}/groups/{group-id}" diff --git a/setup.py b/setup.py index b89cd31..a0a550b 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ with open("README.md", "r") as fh: setup( name='python-keycloak', - version='0.19.0', + version='0.20.0', url='https://github.com/marcospereirampj/python-keycloak', license='The MIT License', author='Marcos Pereira',