Browse Source

Merged in add_idps (pull request #12)

Add get_idps

Approved-by: Marcos Pereira <marcospereira.mpj@gmail.com>
hotfix/merge
Martin Devlin 6 years ago
committed by Marcos Pereira
parent
commit
75ab5702eb
  1. 7
      CHANGELOG.md
  2. 18
      README.md
  3. 4
      docs/source/conf.py
  4. 14
      docs/source/index.rst
  5. 9
      keycloak/exceptions.py
  6. 101
      keycloak/keycloak_admin.py
  7. 3
      keycloak/urls_patterns.py
  8. 1
      requirements.txt
  9. 4
      setup.py

7
CHANGELOG.md

@ -1,4 +1,3 @@
Changelog Changelog
============ ============
@ -39,4 +38,8 @@ entitlement, instropect)
* Add groups functions * Add groups functions
* Add Admin Tasks for user and client role management * Add Admin Tasks for user and client role management
* Function to trigger user sync from provider * Function to trigger user sync from provider
* Optional parameter: verify
## [0.12.1] - 2018-08-04
* Add get_idps
* Rework group functions

18
README.md

@ -45,6 +45,9 @@ The documentation for python-keycloak is available on [readthedocs](http://pytho
* [Marcos Pereira](marcospereira.mpj@gmail.com) * [Marcos Pereira](marcospereira.mpj@gmail.com)
* [Martin Devlin](martin.devlin@pearson.com) * [Martin Devlin](martin.devlin@pearson.com)
* [Shon T. Urbas](shon.urbas@gmail.com>) * [Shon T. Urbas](shon.urbas@gmail.com>)
* [Markus Spanier]()
* [Remco Kranenburg]()
* [Remco Kranenburg]()
## Usage ## Usage
@ -203,8 +206,21 @@ groups = keycloak_admin.get_groups()
group = keycloak_admin.get_group(group_id='group_id') group = keycloak_admin.get_group(group_id='group_id')
# Get group by name # Get group by name
group = keycloak_admin.get_group_by_name(name_or_path='group_id', search_in_subgroups=True)
group = keycloak_admin.get_group_by_path(path='/group/subgroup', search_in_subgroups=True)
# Function to trigger user sync from provider # Function to trigger user sync from provider
sync_users(storage_id="storage_di", action="action") sync_users(storage_id="storage_di", action="action")
# Get client role id from name
role_id = keycloak_admin.get_client_role_id(client_id=client_id, role_name="test")
# Get all roles for the realm or client
realm_roles = keycloak_admin.get_roles()
# 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")
# Get all ID Providers
idps = keycloak_admin.get_idps()
``` ```

4
docs/source/conf.py

@ -60,9 +60,9 @@ author = 'Marcos Pereira'
# built documents. # built documents.
# #
# The short X.Y version. # The short X.Y version.
version = '0.12.0'
version = '0.12.1'
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.12.0'
release = '0.12.1'
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.

14
docs/source/index.rst

@ -43,6 +43,7 @@ python-keycloak depends on:
* Python 3 * Python 3
* `requests <http://docs.python-requests.org/en/master/>`_ * `requests <http://docs.python-requests.org/en/master/>`_
* `python-jose <http://python-jose.readthedocs.io/en/latest/>`_ * `python-jose <http://python-jose.readthedocs.io/en/latest/>`_
* `simplejson <https://simplejson.readthedocs.io/en/latest/>`_
Tests Dependencies Tests Dependencies
------------------ ------------------
@ -81,7 +82,8 @@ Main methods::
keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/", keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/",
client_id="example_client", client_id="example_client",
realm_name="example_realm", realm_name="example_realm",
client_secret_key="secret")
client_secret_key="secret",
verify=True)
# Get WellKnow # Get WellKnow
config_well_know = keycloak_openid.well_know() config_well_know = keycloak_openid.well_know()
@ -216,6 +218,12 @@ Main methods::
# Create client role # Create client role
keycloak_admin.create_client_role(client_id, "test") keycloak_admin.create_client_role(client_id, "test")
# Get client role id from name
role_id = keycloak_admin.get_client_role_id(client_id=client_id, role_name="test")
# Get all roles for the realm or client
realm_roles = keycloak_admin.get_roles()
# Assign client role to user. Note that BOTH role_name and role_id appear to be required. # 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") keycloak_admin.assign_client_role(client_id="client_id", user_id="user_id", role_id="role_id", role_name="test")
@ -228,8 +236,8 @@ Main methods::
# Get group # Get group
group = keycloak_admin.get_group(group_id='group_id') group = keycloak_admin.get_group(group_id='group_id')
# Get group by name
group = keycloak_admin.get_group_by_name(name_or_path='group_id', search_in_subgroups=True)
# Get group by path
group = keycloak_admin.get_group_by_path(path='/group/subgroup', search_in_subgroups=True)
# Function to trigger user sync from provider # Function to trigger user sync from provider
sync_users(storage_id="storage_di", action="action") sync_users(storage_id="storage_di", action="action")

9
keycloak/exceptions.py

@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import requests import requests
from simplejson import JSONDecodeError
class KeycloakError(Exception): class KeycloakError(Exception):
@ -67,16 +68,20 @@ class KeycloakInvalidTokenError(KeycloakOperationError):
pass pass
def raise_error_from_response(response, error, expected_code=200):
def raise_error_from_response(response, error, expected_code=200, skip_exists=False):
if expected_code == response.status_code: if expected_code == response.status_code:
if expected_code == requests.codes.no_content: if expected_code == requests.codes.no_content:
return {} return {}
try: try:
return response.json() return response.json()
except ValueError:
except JSONDecodeError as e:
return response.content return response.content
if skip_exists and response.status_code == 409:
return {"Already exists"}
try: try:
message = response.json()['message'] message = response.json()['message']
except (KeyError, ValueError): except (KeyError, ValueError):

101
keycloak/keycloak_admin.py

@ -123,6 +123,19 @@ class KeycloakAdmin:
data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path), **query) data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path), **query)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_idps(self):
"""
Returns a list of ID Providers,
IdentityProviderRepresentation
https://www.keycloak.org/docs-api/3.3/rest-api/index.html#_identityproviderrepresentation
:return: array IdentityProviderRepresentation
"""
params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_get(URL_ADMIN_IDPS.format(**params_path))
return raise_error_from_response(data_raw, KeycloakGetError)
def create_user(self, payload): def create_user(self, payload):
""" """
Create a new user Username must be unique Create a new user Username must be unique
@ -135,6 +148,12 @@ class KeycloakAdmin:
:return: UserRepresentation :return: UserRepresentation
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
exists = self.get_user_id(username=payload['username'])
if exists is not None:
return str(exists)
data_raw = self.connection.raw_post(URL_ADMIN_USERS.format(**params_path), data_raw = self.connection.raw_post(URL_ADMIN_USERS.format(**params_path),
data=json.dumps(payload)) data=json.dumps(payload))
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201) return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
@ -333,7 +352,28 @@ class KeycloakAdmin:
data_raw = self.connection.raw_get(URL_ADMIN_GROUP.format(**params_path)) data_raw = self.connection.raw_get(URL_ADMIN_GROUP.format(**params_path))
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_group_by_name(self, name_or_path, search_in_subgroups=False):
def get_subgroups(self, group, path):
"""
Utility function to iterate through nested group structures
GroupRepresentation
http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
:param name: group (GroupRepresentation)
:param path: group path (string)
:return: Keycloak server response (GroupRepresentation)
"""
for subgroup in group["subGroups"]:
if subgroup['path'] == path:
return subgroup
elif subgroup["subGroups"]:
for subgroup in group["subGroups"]:
return self.get_subgroups(subgroup, path)
return None
def get_group_by_path(self, path, search_in_subgroups=False):
""" """
Get group id based on name or path. Get group id based on name or path.
A straight name or path match with a top-level group will return first. A straight name or path match with a top-level group will return first.
@ -342,7 +382,6 @@ class KeycloakAdmin:
GroupRepresentation GroupRepresentation
http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
:param name: group name
:param path: group path :param path: group path
:param search_in_subgroups: True if want search in the subgroups :param search_in_subgroups: True if want search in the subgroups
:return: Keycloak server response (GroupRepresentation) :return: Keycloak server response (GroupRepresentation)
@ -352,48 +391,48 @@ class KeycloakAdmin:
# TODO: Review this code is necessary # TODO: Review this code is necessary
for group in groups: for group in groups:
if group['name'] == name_or_path or group['path'] == name_or_path:
if group['path'] == path:
return group return group
elif search_in_subgroups and group["subGroups"]: elif search_in_subgroups and group["subGroups"]:
for subgroup in group["subGroups"]:
if subgroup['name'] == name_or_path or subgroup['path'] == name_or_path:
return subgroup
res = self.get_subgroups(group, path)
if res != None:
return res
return None return None
def create_group(self, name=None, client_roles={}, realm_roles=[], sub_groups=[], path=None, parent=None):
def create_group(self, payload, parent=None, skip_exists=False):
""" """
Create a group in the Realm
Creates a group in the Realm
:param payload: GroupRepresentation
:param parent: parent group's id. Required to create a sub-group.
GroupRepresentation GroupRepresentation
http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
:param name: group name
:param client_roles: (Dict) Client roles to include in groupp # Not demonstrated to work
:param realm_roles: (List) Realm roles to include in group # Not demonstrated to work
:param sub_groups: (List) Subgroups to include in groupp # Not demonstrated to work
:param path: group path
:param parent: parent group's id. Required to create a sub-group.
:return: Keycloak server response (GroupRepresentation)
:return: Http response
""" """
name = payload['name']
path = payload['path']
exists = None
if name is None and path is not None:
path="/" + name
data = {"name": name or path,
"path": path,
"clientRoles": client_roles,
"realmRoles": realm_roles,
"subGroups": sub_groups}
elif path is not None:
exists = self.get_group_by_path(path=path, search_in_subgroups=True)
if exists is not None:
return str(exists)
if parent is None: if parent is None:
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_post(URL_ADMIN_GROUPS.format(**params_path), data_raw = self.connection.raw_post(URL_ADMIN_GROUPS.format(**params_path),
data=json.dumps(data))
data=json.dumps(payload))
else: else:
params_path = {"realm-name": self.realm_name, "id": parent}
params_path = {"realm-name": self.realm_name, "id": parent,}
data_raw = self.connection.raw_post(URL_ADMIN_GROUP_CHILD.format(**params_path), data_raw = self.connection.raw_post(URL_ADMIN_GROUP_CHILD.format(**params_path),
data=json.dumps(data))
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
data=json.dumps(payload))
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201, skip_exists=skip_exists)
def group_set_permissions(self, group_id, enabled=True): def group_set_permissions(self, group_id, enabled=True):
""" """
@ -496,7 +535,7 @@ class KeycloakAdmin:
return None return None
def create_client(self, payload):
def create_client(self, payload, skip_exists=False):
""" """
Create a client Create a client
@ -509,7 +548,7 @@ class KeycloakAdmin:
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.connection.raw_post(URL_ADMIN_CLIENTS.format(**params_path), data_raw = self.connection.raw_post(URL_ADMIN_CLIENTS.format(**params_path),
data=json.dumps(payload)) data=json.dumps(payload))
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201, skip_exists=skip_exists)
def delete_client(self, client_id): def delete_client(self, client_id):
""" """
@ -591,7 +630,7 @@ class KeycloakAdmin:
role = self.get_client_role(client_id, role_name) role = self.get_client_role(client_id, role_name)
return role.get("id") return role.get("id")
def create_client_role(self, payload):
def create_client_role(self, payload, skip_exists=False):
""" """
Create a client role Create a client role
@ -605,7 +644,7 @@ class KeycloakAdmin:
params_path = {"realm-name": self.realm_name, "id": self.client_id} params_path = {"realm-name": self.realm_name, "id": self.client_id}
data_raw = self.connection.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path), data_raw = self.connection.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path),
data=json.dumps(payload)) data=json.dumps(payload))
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201, skip_exists=skip_exists)
def delete_client_role(self, role_name): def delete_client_role(self, role_name):
""" """

3
keycloak/urls_patterns.py

@ -35,6 +35,7 @@ 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_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_CLIENT_ROLES = "admin/realms/{realm-name}/users/{id}/role-mappings/clients/{client-id}"
URL_ADMIN_USER_GROUP = "admin/realms/{realm-name}/users/{id}/groups/{group-id}" URL_ADMIN_USER_GROUP = "admin/realms/{realm-name}/users/{id}/groups/{group-id}"
URL_ADMIN_USER_PASSWORD = "admin/realms/{realm-name}/users/{id}/reset-password"
URL_ADMIN_SERVER_INFO = "admin/serverinfo" URL_ADMIN_SERVER_INFO = "admin/serverinfo"
@ -51,3 +52,5 @@ URL_ADMIN_CLIENT_ROLE = "admin/realms/{realm-name}/clients/{id}/roles/{role-name
URL_ADMIN_REALM_ROLES = "admin/realms/{realm-name}/roles" URL_ADMIN_REALM_ROLES = "admin/realms/{realm-name}/roles"
URL_ADMIN_USER_STORAGE = "admin/realms/{realm-name}/user-storage/{id}/sync" URL_ADMIN_USER_STORAGE = "admin/realms/{realm-name}/user-storage/{id}/sync"
URL_ADMIN_IDPS = "admin/realms/{realm}/identity-provider/instances"

1
requirements.txt

@ -1,3 +1,4 @@
requests>=2.18.4 requests>=2.18.4
httmock>=1.2.5 httmock>=1.2.5
python-jose>=1.4.0 python-jose>=1.4.0
simplejson

4
setup.py

@ -4,7 +4,7 @@ from setuptools import setup
setup( setup(
name='python-keycloak', name='python-keycloak',
version='0.11.1',
version='0.12.1',
url='https://bitbucket.org/agriness/python-keycloak', url='https://bitbucket.org/agriness/python-keycloak',
license='GNU General Public License - V3', license='GNU General Public License - V3',
author='Marcos Pereira', author='Marcos Pereira',
@ -12,7 +12,7 @@ setup(
keywords='keycloak openid', keywords='keycloak openid',
description=u'python-keycloak is a Python package providing access to the Keycloak API.', description=u'python-keycloak is a Python package providing access to the Keycloak API.',
packages=['keycloak', 'keycloak.authorization', 'keycloak.tests'], packages=['keycloak', 'keycloak.authorization', 'keycloak.tests'],
install_requires=['requests==2.18.4', 'httmock==1.2.5', 'python-jose==1.4.0'],
install_requires=['requests==2.18.4', 'httmock==1.2.5', 'python-jose==1.4.0', 'simplejson'],
classifiers=[ classifiers=[
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',

Loading…
Cancel
Save