mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
Add Keycloak roles module (#2930)
* implement simple realm and client role * fix documentation * code cleanup * separate realm and client roles functions * remove blank lines * add tests * fix linefeeds * fix indentation * fix error message * fix documentation * fix documentation * keycloak_role integration tests * keycloak_role integration tests * remove extra blank line * add version_added tag Co-authored-by: Felix Fontein <felix@fontein.de> Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
a3607a745e
commit
d7c6ba89f8
7 changed files with 1141 additions and 1 deletions
|
@ -43,8 +43,14 @@ URL_REALM = "{url}/admin/realms/{realm}"
|
||||||
URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token"
|
URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token"
|
||||||
URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}"
|
URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}"
|
||||||
URL_CLIENTS = "{url}/admin/realms/{realm}/clients"
|
URL_CLIENTS = "{url}/admin/realms/{realm}/clients"
|
||||||
|
|
||||||
URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles"
|
URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles"
|
||||||
|
URL_CLIENT_ROLE = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}"
|
||||||
|
URL_CLIENT_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}/composites"
|
||||||
|
|
||||||
URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles"
|
URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles"
|
||||||
|
URL_REALM_ROLE = "{url}/admin/realms/{realm}/roles/{name}"
|
||||||
|
URL_REALM_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/roles/{name}/composites"
|
||||||
|
|
||||||
URL_CLIENTTEMPLATE = "{url}/admin/realms/{realm}/client-templates/{id}"
|
URL_CLIENTTEMPLATE = "{url}/admin/realms/{realm}/client-templates/{id}"
|
||||||
URL_CLIENTTEMPLATES = "{url}/admin/realms/{realm}/client-templates"
|
URL_CLIENTTEMPLATES = "{url}/admin/realms/{realm}/client-templates"
|
||||||
|
@ -632,10 +638,197 @@ class KeycloakAPI(object):
|
||||||
try:
|
try:
|
||||||
return open_url(group_url, method='DELETE', headers=self.restheaders,
|
return open_url(group_url, method='DELETE', headers=self.restheaders,
|
||||||
validate_certs=self.validate_certs)
|
validate_certs=self.validate_certs)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.module.fail_json(msg="Unable to delete group %s: %s" % (groupid, str(e)))
|
self.module.fail_json(msg="Unable to delete group %s: %s" % (groupid, str(e)))
|
||||||
|
|
||||||
|
def get_realm_roles(self, realm='master'):
|
||||||
|
""" Obtains role representations for roles in a realm
|
||||||
|
|
||||||
|
:param realm: realm to be queried
|
||||||
|
:return: list of dicts of role representations
|
||||||
|
"""
|
||||||
|
rolelist_url = URL_REALM_ROLES.format(url=self.baseurl, realm=realm)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(rolelist_url, method='GET', headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs).read()))
|
||||||
|
except ValueError as e:
|
||||||
|
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for realm %s: %s'
|
||||||
|
% (realm, str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not obtain list of roles for realm %s: %s'
|
||||||
|
% (realm, str(e)))
|
||||||
|
|
||||||
|
def get_realm_role(self, name, realm='master'):
|
||||||
|
""" Fetch a keycloak role from the provided realm using the role's name.
|
||||||
|
|
||||||
|
If the role does not exist, None is returned.
|
||||||
|
:param name: Name of the role to fetch.
|
||||||
|
:param realm: Realm in which the role resides; default 'master'.
|
||||||
|
"""
|
||||||
|
role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=name)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(role_url, method="GET", headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs).read()))
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg='Could not fetch role %s in realm %s: %s'
|
||||||
|
% (name, realm, str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not fetch role %s in realm %s: %s'
|
||||||
|
% (name, realm, str(e)))
|
||||||
|
|
||||||
|
def create_realm_role(self, rolerep, realm='master'):
|
||||||
|
""" Create a Keycloak realm role.
|
||||||
|
|
||||||
|
:param rolerep: a RoleRepresentation of the role to be created. Must contain at minimum the field name.
|
||||||
|
:return: HTTPResponse object on success
|
||||||
|
"""
|
||||||
|
roles_url = URL_REALM_ROLES.format(url=self.baseurl, realm=realm)
|
||||||
|
try:
|
||||||
|
return open_url(roles_url, method='POST', headers=self.restheaders,
|
||||||
|
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not create role %s in realm %s: %s'
|
||||||
|
% (rolerep['name'], realm, str(e)))
|
||||||
|
|
||||||
|
def update_realm_role(self, rolerep, realm='master'):
|
||||||
|
""" Update an existing realm role.
|
||||||
|
|
||||||
|
:param rolerep: A RoleRepresentation of the updated role.
|
||||||
|
:return HTTPResponse object on success
|
||||||
|
"""
|
||||||
|
role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=rolerep['name'])
|
||||||
|
try:
|
||||||
|
return open_url(role_url, method='PUT', headers=self.restheaders,
|
||||||
|
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not update role %s in realm %s: %s'
|
||||||
|
% (rolerep['name'], realm, str(e)))
|
||||||
|
|
||||||
|
def delete_realm_role(self, name, realm='master'):
|
||||||
|
""" Delete a realm role.
|
||||||
|
|
||||||
|
:param name: The name of the role.
|
||||||
|
:param realm: The realm in which this role resides, default "master".
|
||||||
|
"""
|
||||||
|
role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=name)
|
||||||
|
try:
|
||||||
|
return open_url(role_url, method='DELETE', headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Unable to delete role %s in realm %s: %s'
|
||||||
|
% (name, realm, str(e)))
|
||||||
|
|
||||||
|
def get_client_roles(self, clientid, realm='master'):
|
||||||
|
""" Obtains role representations for client roles in a specific client
|
||||||
|
|
||||||
|
:param clientid: Client id to be queried
|
||||||
|
:param realm: Realm to be queried
|
||||||
|
:return: List of dicts of role representations
|
||||||
|
"""
|
||||||
|
cid = self.get_client_id(clientid, realm=realm)
|
||||||
|
if cid is None:
|
||||||
|
self.module.fail_json(msg='Could not find client %s in realm %s'
|
||||||
|
% (clientid, realm))
|
||||||
|
rolelist_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(rolelist_url, method='GET', headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs).read()))
|
||||||
|
except ValueError as e:
|
||||||
|
self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for client %s in realm %s: %s'
|
||||||
|
% (clientid, realm, str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not obtain list of roles for client %s in realm %s: %s'
|
||||||
|
% (clientid, realm, str(e)))
|
||||||
|
|
||||||
|
def get_client_role(self, name, clientid, realm='master'):
|
||||||
|
""" Fetch a keycloak client role from the provided realm using the role's name.
|
||||||
|
|
||||||
|
:param name: Name of the role to fetch.
|
||||||
|
:param clientid: Client id for the client role
|
||||||
|
:param realm: Realm in which the role resides
|
||||||
|
:return: Dict of role representation
|
||||||
|
If the role does not exist, None is returned.
|
||||||
|
"""
|
||||||
|
cid = self.get_client_id(clientid, realm=realm)
|
||||||
|
if cid is None:
|
||||||
|
self.module.fail_json(msg='Could not find client %s in realm %s'
|
||||||
|
% (clientid, realm))
|
||||||
|
role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=name)
|
||||||
|
try:
|
||||||
|
return json.loads(to_native(open_url(role_url, method="GET", headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs).read()))
|
||||||
|
except HTTPError as e:
|
||||||
|
if e.code == 404:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg='Could not fetch role %s in client %s of realm %s: %s'
|
||||||
|
% (name, clientid, realm, str(e)))
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not fetch role %s for client %s in realm %s: %s'
|
||||||
|
% (name, clientid, realm, str(e)))
|
||||||
|
|
||||||
|
def create_client_role(self, rolerep, clientid, realm='master'):
|
||||||
|
""" Create a Keycloak client role.
|
||||||
|
|
||||||
|
:param rolerep: a RoleRepresentation of the role to be created. Must contain at minimum the field name.
|
||||||
|
:param clientid: Client id for the client role
|
||||||
|
:param realm: Realm in which the role resides
|
||||||
|
:return: HTTPResponse object on success
|
||||||
|
"""
|
||||||
|
cid = self.get_client_id(clientid, realm=realm)
|
||||||
|
if cid is None:
|
||||||
|
self.module.fail_json(msg='Could not find client %s in realm %s'
|
||||||
|
% (clientid, realm))
|
||||||
|
roles_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid)
|
||||||
|
try:
|
||||||
|
return open_url(roles_url, method='POST', headers=self.restheaders,
|
||||||
|
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not create role %s for client %s in realm %s: %s'
|
||||||
|
% (rolerep['name'], clientid, realm, str(e)))
|
||||||
|
|
||||||
|
def update_client_role(self, rolerep, clientid, realm="master"):
|
||||||
|
""" Update an existing client role.
|
||||||
|
|
||||||
|
:param rolerep: A RoleRepresentation of the updated role.
|
||||||
|
:param clientid: Client id for the client role
|
||||||
|
:param realm: Realm in which the role resides
|
||||||
|
:return HTTPResponse object on success
|
||||||
|
"""
|
||||||
|
cid = self.get_client_id(clientid, realm=realm)
|
||||||
|
if cid is None:
|
||||||
|
self.module.fail_json(msg='Could not find client %s in realm %s'
|
||||||
|
% (clientid, realm))
|
||||||
|
role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=rolerep['name'])
|
||||||
|
try:
|
||||||
|
return open_url(role_url, method='PUT', headers=self.restheaders,
|
||||||
|
data=json.dumps(rolerep), validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Could not update role %s for client %s in realm %s: %s'
|
||||||
|
% (rolerep['name'], clientid, realm, str(e)))
|
||||||
|
|
||||||
|
def delete_client_role(self, name, clientid, realm="master"):
|
||||||
|
""" Delete a role. One of name or roleid must be provided.
|
||||||
|
|
||||||
|
:param name: The name of the role.
|
||||||
|
:param clientid: Client id for the client role
|
||||||
|
:param realm: Realm in which the role resides
|
||||||
|
"""
|
||||||
|
cid = self.get_client_id(clientid, realm=realm)
|
||||||
|
if cid is None:
|
||||||
|
self.module.fail_json(msg='Could not find client %s in realm %s'
|
||||||
|
% (clientid, realm))
|
||||||
|
role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=name)
|
||||||
|
try:
|
||||||
|
return open_url(role_url, method='DELETE', headers=self.restheaders,
|
||||||
|
validate_certs=self.validate_certs)
|
||||||
|
except Exception as e:
|
||||||
|
self.module.fail_json(msg='Unable to delete role %s for client %s in realm %s: %s'
|
||||||
|
% (name, clientid, realm, str(e)))
|
||||||
|
|
||||||
def get_authentication_flow_by_alias(self, alias, realm='master'):
|
def get_authentication_flow_by_alias(self, alias, realm='master'):
|
||||||
"""
|
"""
|
||||||
Get an authentication flow by it's alias
|
Get an authentication flow by it's alias
|
||||||
|
|
363
plugins/modules/identity/keycloak/keycloak_role.py
Normal file
363
plugins/modules/identity/keycloak/keycloak_role.py
Normal file
|
@ -0,0 +1,363 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright (c) 2019, Adam Goossens <adam.goossens@gmail.com>
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: keycloak_role
|
||||||
|
|
||||||
|
short_description: Allows administration of Keycloak roles via Keycloak API
|
||||||
|
|
||||||
|
version_added: 3.4.0
|
||||||
|
|
||||||
|
description:
|
||||||
|
- This module allows you to add, remove or modify Keycloak roles via the Keycloak REST API.
|
||||||
|
It requires access to the REST API via OpenID Connect; the user connecting and the client being
|
||||||
|
used must have the requisite access rights. In a default Keycloak installation, admin-cli
|
||||||
|
and an admin user would work, as would a separate client definition with the scope tailored
|
||||||
|
to your needs and a user having the expected roles.
|
||||||
|
|
||||||
|
- The names of module options are snake_cased versions of the camelCase ones found in the
|
||||||
|
Keycloak API and its documentation at U(https://www.keycloak.org/docs-api/8.0/rest-api/index.html).
|
||||||
|
|
||||||
|
- Attributes are multi-valued in the Keycloak API. All attributes are lists of individual values and will
|
||||||
|
be returned that way by this module. You may pass single values for attributes when calling the module,
|
||||||
|
and this will be translated into a list suitable for the API.
|
||||||
|
|
||||||
|
|
||||||
|
options:
|
||||||
|
state:
|
||||||
|
description:
|
||||||
|
- State of the role.
|
||||||
|
- On C(present), the role will be created if it does not yet exist, or updated with the parameters you provide.
|
||||||
|
- On C(absent), the role will be removed if it exists.
|
||||||
|
default: 'present'
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- present
|
||||||
|
- absent
|
||||||
|
|
||||||
|
name:
|
||||||
|
type: str
|
||||||
|
required: true
|
||||||
|
description:
|
||||||
|
- Name of the role.
|
||||||
|
- This parameter is required.
|
||||||
|
|
||||||
|
description:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The role description.
|
||||||
|
|
||||||
|
realm:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- The Keycloak realm under which this role resides.
|
||||||
|
default: 'master'
|
||||||
|
|
||||||
|
client_id:
|
||||||
|
type: str
|
||||||
|
description:
|
||||||
|
- If the role is a client role, the client id under which it resides.
|
||||||
|
- If this parameter is absent, the role is considered a realm role.
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
type: dict
|
||||||
|
description:
|
||||||
|
- A dict of key/value pairs to set as custom attributes for the role.
|
||||||
|
- Values may be single values (e.g. a string) or a list of strings.
|
||||||
|
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- community.general.keycloak
|
||||||
|
|
||||||
|
|
||||||
|
author:
|
||||||
|
- Laurent Paumier (@laurpaum)
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Create a Keycloak realm role, authentication with credentials
|
||||||
|
community.general.keycloak_role:
|
||||||
|
name: my-new-kc-role
|
||||||
|
realm: MyCustomRealm
|
||||||
|
state: present
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create a Keycloak realm role, authentication with token
|
||||||
|
community.general.keycloak_role:
|
||||||
|
name: my-new-kc-role
|
||||||
|
realm: MyCustomRealm
|
||||||
|
state: present
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
token: TOKEN
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create a Keycloak client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
name: my-new-kc-role
|
||||||
|
realm: MyCustomRealm
|
||||||
|
client_id: MyClient
|
||||||
|
state: present
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Delete a Keycloak role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
name: my-role-for-deletion
|
||||||
|
state: absent
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
delegate_to: localhost
|
||||||
|
|
||||||
|
- name: Create a keycloak role with some custom attributes
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_client_id: admin-cli
|
||||||
|
auth_keycloak_url: https://auth.example.com/auth
|
||||||
|
auth_realm: master
|
||||||
|
auth_username: USERNAME
|
||||||
|
auth_password: PASSWORD
|
||||||
|
name: my-new-role
|
||||||
|
attributes:
|
||||||
|
attrib1: value1
|
||||||
|
attrib2: value2
|
||||||
|
attrib3:
|
||||||
|
- with
|
||||||
|
- numerous
|
||||||
|
- individual
|
||||||
|
- list
|
||||||
|
- items
|
||||||
|
delegate_to: localhost
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
msg:
|
||||||
|
description: Message as to what action was taken
|
||||||
|
returned: always
|
||||||
|
type: str
|
||||||
|
sample: "Role myrole has been updated"
|
||||||
|
|
||||||
|
proposed:
|
||||||
|
description: Role representation of proposed changes to role
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"description": "My updated test description"
|
||||||
|
}
|
||||||
|
existing:
|
||||||
|
description: Role representation of existing role
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": true,
|
||||||
|
"composite": false,
|
||||||
|
"containerId": "9f03eb61-a826-4771-a9fd-930e06d2d36a",
|
||||||
|
"description": "My client test role",
|
||||||
|
"id": "561703dd-0f38-45ff-9a5a-0c978f794547",
|
||||||
|
"name": "myrole"
|
||||||
|
}
|
||||||
|
end_state:
|
||||||
|
description: Role representation of role after module execution (sample is truncated)
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": true,
|
||||||
|
"composite": false,
|
||||||
|
"containerId": "9f03eb61-a826-4771-a9fd-930e06d2d36a",
|
||||||
|
"description": "My updated client test role",
|
||||||
|
"id": "561703dd-0f38-45ff-9a5a-0c978f794547",
|
||||||
|
"name": "myrole"
|
||||||
|
}
|
||||||
|
'''
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak import KeycloakAPI, camel, \
|
||||||
|
keycloak_argument_spec, get_token, KeycloakError
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Module execution
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
argument_spec = keycloak_argument_spec()
|
||||||
|
meta_args = dict(
|
||||||
|
state=dict(type='str', default='present', choices=['present', 'absent']),
|
||||||
|
name=dict(type='str', required=True),
|
||||||
|
description=dict(type='str'),
|
||||||
|
realm=dict(type='str', default='master'),
|
||||||
|
client_id=dict(type='str'),
|
||||||
|
attributes=dict(type='dict'),
|
||||||
|
)
|
||||||
|
|
||||||
|
argument_spec.update(meta_args)
|
||||||
|
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
supports_check_mode=True,
|
||||||
|
required_one_of=([['token', 'auth_realm', 'auth_username', 'auth_password']]),
|
||||||
|
required_together=([['auth_realm', 'auth_username', 'auth_password']]))
|
||||||
|
|
||||||
|
result = dict(changed=False, msg='', diff={}, proposed={}, existing={}, end_state={})
|
||||||
|
|
||||||
|
# Obtain access token, initialize API
|
||||||
|
try:
|
||||||
|
connection_header = get_token(module.params)
|
||||||
|
except KeycloakError as e:
|
||||||
|
module.fail_json(msg=str(e))
|
||||||
|
|
||||||
|
kc = KeycloakAPI(module, connection_header)
|
||||||
|
|
||||||
|
realm = module.params.get('realm')
|
||||||
|
clientid = module.params.get('client_id')
|
||||||
|
name = module.params.get('name')
|
||||||
|
state = module.params.get('state')
|
||||||
|
|
||||||
|
# attributes in Keycloak have their values returned as lists
|
||||||
|
# via the API. attributes is a dict, so we'll transparently convert
|
||||||
|
# the values to lists.
|
||||||
|
if module.params.get('attributes') is not None:
|
||||||
|
for key, val in module.params['attributes'].items():
|
||||||
|
module.params['attributes'][key] = [val] if not isinstance(val, list) else val
|
||||||
|
|
||||||
|
# convert module parameters to client representation parameters (if they belong in there)
|
||||||
|
role_params = [x for x in module.params
|
||||||
|
if x not in list(keycloak_argument_spec().keys()) + ['state', 'realm', 'client_id', 'composites'] and
|
||||||
|
module.params.get(x) is not None]
|
||||||
|
|
||||||
|
# does the role already exist?
|
||||||
|
if clientid is None:
|
||||||
|
before_role = kc.get_realm_role(name, realm)
|
||||||
|
else:
|
||||||
|
before_role = kc.get_client_role(name, clientid, realm)
|
||||||
|
|
||||||
|
if before_role is None:
|
||||||
|
before_role = dict()
|
||||||
|
|
||||||
|
# build a changeset
|
||||||
|
changeset = dict()
|
||||||
|
|
||||||
|
for param in role_params:
|
||||||
|
new_param_value = module.params.get(param)
|
||||||
|
old_value = before_role[param] if param in before_role else None
|
||||||
|
if new_param_value != old_value:
|
||||||
|
changeset[camel(param)] = new_param_value
|
||||||
|
|
||||||
|
# prepare the new role
|
||||||
|
updated_role = before_role.copy()
|
||||||
|
updated_role.update(changeset)
|
||||||
|
|
||||||
|
result['proposed'] = changeset
|
||||||
|
result['existing'] = before_role
|
||||||
|
|
||||||
|
# if before_role is none, the role doesn't exist.
|
||||||
|
if before_role == dict():
|
||||||
|
if state == 'absent':
|
||||||
|
# nothing to do.
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after='')
|
||||||
|
result['changed'] = False
|
||||||
|
result['end_state'] = dict()
|
||||||
|
result['msg'] = 'Role does not exist; doing nothing.'
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# for 'present', create a new role.
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
if name is None:
|
||||||
|
module.fail_json(msg='name must be specified when creating a new role')
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before='', after=updated_role)
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# do it for real!
|
||||||
|
if clientid is None:
|
||||||
|
kc.create_realm_role(updated_role, realm)
|
||||||
|
after_role = kc.get_realm_role(name, realm)
|
||||||
|
else:
|
||||||
|
kc.create_client_role(updated_role, clientid, realm)
|
||||||
|
after_role = kc.get_client_role(name, clientid, realm)
|
||||||
|
|
||||||
|
result['end_state'] = after_role
|
||||||
|
|
||||||
|
result['msg'] = 'Role {name} has been created'.format(name=name)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
else:
|
||||||
|
if state == 'present':
|
||||||
|
# no changes
|
||||||
|
if updated_role == before_role:
|
||||||
|
result['changed'] = False
|
||||||
|
result['end_state'] = updated_role
|
||||||
|
result['msg'] = "No changes required to role {name}.".format(name=name)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# update the existing role
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=before_role, after=updated_role)
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# do the update
|
||||||
|
if clientid is None:
|
||||||
|
kc.update_realm_role(updated_role, realm)
|
||||||
|
after_role = kc.get_realm_role(name, realm)
|
||||||
|
else:
|
||||||
|
kc.update_client_role(updated_role, clientid, realm)
|
||||||
|
after_role = kc.get_client_role(name, clientid, realm)
|
||||||
|
|
||||||
|
result['end_state'] = after_role
|
||||||
|
|
||||||
|
result['msg'] = "Role {name} has been updated".format(name=name)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
elif state == 'absent':
|
||||||
|
result['changed'] = True
|
||||||
|
|
||||||
|
if module._diff:
|
||||||
|
result['diff'] = dict(before=before_role, after='')
|
||||||
|
|
||||||
|
if module.check_mode:
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
# delete for real
|
||||||
|
if clientid is None:
|
||||||
|
kc.delete_realm_role(name, realm)
|
||||||
|
else:
|
||||||
|
kc.delete_client_role(name, clientid, realm)
|
||||||
|
|
||||||
|
result['end_state'] = dict()
|
||||||
|
|
||||||
|
result['msg'] = "Role {name} has been deleted".format(name=name)
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
module.exit_json(**result)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
1
plugins/modules/keycloak_role.py
Symbolic link
1
plugins/modules/keycloak_role.py
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
./identity/keycloak/keycloak_role.py
|
1
tests/integration/targets/keycloak_role/aliases
Normal file
1
tests/integration/targets/keycloak_role/aliases
Normal file
|
@ -0,0 +1 @@
|
||||||
|
unsupported
|
246
tests/integration/targets/keycloak_role/tasks/main.yml
Normal file
246
tests/integration/targets/keycloak_role/tasks/main.yml
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
---
|
||||||
|
- name: Create realm
|
||||||
|
community.general.keycloak_realm:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
id: "{{ realm }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Create client
|
||||||
|
community.general.keycloak_client:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
state: present
|
||||||
|
register: client
|
||||||
|
|
||||||
|
- name: Create new realm role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_1 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert realm role created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.existing == {}
|
||||||
|
- result.end_state.name == "{{ role }}"
|
||||||
|
- result.end_state.containerId == "{{ realm }}"
|
||||||
|
|
||||||
|
- name: Create existing realm role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_1 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert realm role unchanged
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
|
||||||
|
- name: Update realm role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_2 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert realm role updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.existing.description == "{{ description_1 }}"
|
||||||
|
- result.end_state.description == "{{ description_2 }}"
|
||||||
|
|
||||||
|
- name: Delete existing realm role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert realm role deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state == {}
|
||||||
|
|
||||||
|
- name: Delete absent realm role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert realm role unchanged
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.end_state == {}
|
||||||
|
|
||||||
|
- name: Create new client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_1 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert client role created
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.existing == {}
|
||||||
|
- result.end_state.name == "{{ role }}"
|
||||||
|
- result.end_state.containerId == "{{ client.end_state.id }}"
|
||||||
|
|
||||||
|
- name: Create existing client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_1 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert client role unchanged
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
|
||||||
|
- name: Update client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
description: "{{ description_2 }}"
|
||||||
|
state: present
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert client role updated
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.existing.description == "{{ description_1 }}"
|
||||||
|
- result.end_state.description == "{{ description_2 }}"
|
||||||
|
|
||||||
|
- name: Delete existing client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert client role deleted
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
- result.end_state == {}
|
||||||
|
|
||||||
|
- name: Delete absent client role
|
||||||
|
community.general.keycloak_role:
|
||||||
|
auth_keycloak_url: "{{ url }}"
|
||||||
|
auth_realm: "{{ admin_realm }}"
|
||||||
|
auth_username: "{{ admin_user }}"
|
||||||
|
auth_password: "{{ admin_password }}"
|
||||||
|
realm: "{{ realm }}"
|
||||||
|
client_id: "{{ client_id }}"
|
||||||
|
name: "{{ role }}"
|
||||||
|
state: absent
|
||||||
|
register: result
|
||||||
|
|
||||||
|
- name: Debug
|
||||||
|
debug:
|
||||||
|
var: result
|
||||||
|
|
||||||
|
- name: Assert client role unchanged
|
||||||
|
assert:
|
||||||
|
that:
|
||||||
|
- result is not changed
|
||||||
|
- result.end_state == {}
|
10
tests/integration/targets/keycloak_role/vars/main.yml
Normal file
10
tests/integration/targets/keycloak_role/vars/main.yml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
url: http://localhost:8080/auth
|
||||||
|
admin_realm: master
|
||||||
|
admin_user: admin
|
||||||
|
admin_password: password
|
||||||
|
realm: myrealm
|
||||||
|
client_id: myclient
|
||||||
|
role: myrole
|
||||||
|
description_1: desc 1
|
||||||
|
description_2: desc 2
|
|
@ -0,0 +1,326 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
# Copyright: (c) 2021, Ansible Project
|
||||||
|
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||||
|
|
||||||
|
from __future__ import absolute_import, division, print_function
|
||||||
|
__metaclass__ = type
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from ansible_collections.community.general.tests.unit.compat import unittest
|
||||||
|
from ansible_collections.community.general.tests.unit.compat.mock import call, patch
|
||||||
|
from ansible_collections.community.general.tests.unit.plugins.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
|
||||||
|
|
||||||
|
from ansible_collections.community.general.plugins.modules.identity.keycloak import keycloak_role
|
||||||
|
|
||||||
|
from itertools import count
|
||||||
|
|
||||||
|
from ansible.module_utils.six import StringIO
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def patch_keycloak_api(get_realm_role, create_realm_role=None, update_realm_role=None, delete_realm_role=None):
|
||||||
|
"""Mock context manager for patching the methods in PwPolicyIPAClient that contact the IPA server
|
||||||
|
|
||||||
|
Patches the `login` and `_post_json` methods
|
||||||
|
|
||||||
|
Keyword arguments are passed to the mock object that patches `_post_json`
|
||||||
|
|
||||||
|
No arguments are passed to the mock object that patches `login` because no tests require it
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
with patch_ipa(return_value={}) as (mock_login, mock_post):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj = keycloak_role.KeycloakAPI
|
||||||
|
with patch.object(obj, 'get_realm_role', side_effect=get_realm_role) as mock_get_realm_role:
|
||||||
|
with patch.object(obj, 'create_realm_role', side_effect=create_realm_role) as mock_create_realm_role:
|
||||||
|
with patch.object(obj, 'update_realm_role', side_effect=update_realm_role) as mock_update_realm_role:
|
||||||
|
with patch.object(obj, 'delete_realm_role', side_effect=delete_realm_role) as mock_delete_realm_role:
|
||||||
|
yield mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role
|
||||||
|
|
||||||
|
|
||||||
|
def get_response(object_with_future_response, method, get_id_call_count):
|
||||||
|
if callable(object_with_future_response):
|
||||||
|
return object_with_future_response()
|
||||||
|
if isinstance(object_with_future_response, dict):
|
||||||
|
return get_response(
|
||||||
|
object_with_future_response[method], method, get_id_call_count)
|
||||||
|
if isinstance(object_with_future_response, list):
|
||||||
|
call_number = next(get_id_call_count)
|
||||||
|
return get_response(
|
||||||
|
object_with_future_response[call_number], method, get_id_call_count)
|
||||||
|
return object_with_future_response
|
||||||
|
|
||||||
|
|
||||||
|
def build_mocked_request(get_id_user_count, response_dict):
|
||||||
|
def _mocked_requests(*args, **kwargs):
|
||||||
|
url = args[0]
|
||||||
|
method = kwargs['method']
|
||||||
|
future_response = response_dict.get(url, None)
|
||||||
|
return get_response(future_response, method, get_id_user_count)
|
||||||
|
return _mocked_requests
|
||||||
|
|
||||||
|
|
||||||
|
def create_wrapper(text_as_string):
|
||||||
|
"""Allow to mock many times a call to one address.
|
||||||
|
Without this function, the StringIO is empty for the second call.
|
||||||
|
"""
|
||||||
|
def _create_wrapper():
|
||||||
|
return StringIO(text_as_string)
|
||||||
|
return _create_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def mock_good_connection():
|
||||||
|
token_response = {
|
||||||
|
'http://keycloak.url/auth/realms/master/protocol/openid-connect/token': create_wrapper('{"access_token": "alongtoken"}'), }
|
||||||
|
return patch(
|
||||||
|
'ansible_collections.community.general.plugins.module_utils.identity.keycloak.keycloak.open_url',
|
||||||
|
side_effect=build_mocked_request(count(), token_response),
|
||||||
|
autospec=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestKeycloakRealmRole(ModuleTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(TestKeycloakRealmRole, self).setUp()
|
||||||
|
self.module = keycloak_role
|
||||||
|
|
||||||
|
def test_create_when_absent(self):
|
||||||
|
"""Add a new realm role"""
|
||||||
|
|
||||||
|
module_args = {
|
||||||
|
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||||
|
'auth_password': 'admin',
|
||||||
|
'auth_realm': 'master',
|
||||||
|
'auth_username': 'admin',
|
||||||
|
'auth_client_id': 'admin-cli',
|
||||||
|
'validate_certs': True,
|
||||||
|
'realm': 'realm-name',
|
||||||
|
'name': 'role-name',
|
||||||
|
'description': 'role-description',
|
||||||
|
}
|
||||||
|
return_value_absent = [
|
||||||
|
None,
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return_value_created = [None]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
set_module_args(module_args)
|
||||||
|
|
||||||
|
# Run the module
|
||||||
|
|
||||||
|
with mock_good_connection():
|
||||||
|
with patch_keycloak_api(get_realm_role=return_value_absent, create_realm_role=return_value_created) \
|
||||||
|
as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
self.assertEqual(len(mock_get_realm_role.mock_calls), 2)
|
||||||
|
self.assertEqual(len(mock_create_realm_role.mock_calls), 1)
|
||||||
|
self.assertEqual(len(mock_update_realm_role.mock_calls), 0)
|
||||||
|
|
||||||
|
# Verify that the module's changed status matches what is expected
|
||||||
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
||||||
|
|
||||||
|
def test_create_when_present_with_change(self):
|
||||||
|
"""Update with change a realm role"""
|
||||||
|
|
||||||
|
module_args = {
|
||||||
|
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||||
|
'auth_password': 'admin',
|
||||||
|
'auth_realm': 'master',
|
||||||
|
'auth_username': 'admin',
|
||||||
|
'auth_client_id': 'admin-cli',
|
||||||
|
'validate_certs': True,
|
||||||
|
'realm': 'realm-name',
|
||||||
|
'name': 'role-name',
|
||||||
|
'description': 'new-role-description',
|
||||||
|
}
|
||||||
|
return_value_present = [
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "new-role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return_value_updated = [None]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
set_module_args(module_args)
|
||||||
|
|
||||||
|
# Run the module
|
||||||
|
|
||||||
|
with mock_good_connection():
|
||||||
|
with patch_keycloak_api(get_realm_role=return_value_present, update_realm_role=return_value_updated) \
|
||||||
|
as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
self.assertEqual(len(mock_get_realm_role.mock_calls), 2)
|
||||||
|
self.assertEqual(len(mock_create_realm_role.mock_calls), 0)
|
||||||
|
self.assertEqual(len(mock_update_realm_role.mock_calls), 1)
|
||||||
|
|
||||||
|
# Verify that the module's changed status matches what is expected
|
||||||
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
||||||
|
|
||||||
|
def test_create_when_present_no_change(self):
|
||||||
|
"""Update without change a realm role"""
|
||||||
|
|
||||||
|
module_args = {
|
||||||
|
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||||
|
'auth_password': 'admin',
|
||||||
|
'auth_realm': 'master',
|
||||||
|
'auth_username': 'admin',
|
||||||
|
'auth_client_id': 'admin-cli',
|
||||||
|
'validate_certs': True,
|
||||||
|
'realm': 'realm-name',
|
||||||
|
'name': 'role-name',
|
||||||
|
'description': 'role-description',
|
||||||
|
}
|
||||||
|
return_value_present = [
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return_value_updated = [None]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
set_module_args(module_args)
|
||||||
|
|
||||||
|
# Run the module
|
||||||
|
|
||||||
|
with mock_good_connection():
|
||||||
|
with patch_keycloak_api(get_realm_role=return_value_present, update_realm_role=return_value_updated) \
|
||||||
|
as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
self.assertEqual(len(mock_get_realm_role.mock_calls), 1)
|
||||||
|
self.assertEqual(len(mock_create_realm_role.mock_calls), 0)
|
||||||
|
self.assertEqual(len(mock_update_realm_role.mock_calls), 0)
|
||||||
|
|
||||||
|
# Verify that the module's changed status matches what is expected
|
||||||
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
||||||
|
|
||||||
|
def test_delete_when_absent(self):
|
||||||
|
"""Remove an absent realm role"""
|
||||||
|
|
||||||
|
module_args = {
|
||||||
|
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||||
|
'auth_password': 'admin',
|
||||||
|
'auth_realm': 'master',
|
||||||
|
'auth_username': 'admin',
|
||||||
|
'auth_client_id': 'admin-cli',
|
||||||
|
'validate_certs': True,
|
||||||
|
'realm': 'realm-name',
|
||||||
|
'name': 'role-name',
|
||||||
|
'state': 'absent'
|
||||||
|
}
|
||||||
|
return_value_absent = [None]
|
||||||
|
return_value_deleted = [None]
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
set_module_args(module_args)
|
||||||
|
|
||||||
|
# Run the module
|
||||||
|
|
||||||
|
with mock_good_connection():
|
||||||
|
with patch_keycloak_api(get_realm_role=return_value_absent, delete_realm_role=return_value_deleted) \
|
||||||
|
as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
self.assertEqual(len(mock_get_realm_role.mock_calls), 1)
|
||||||
|
self.assertEqual(len(mock_delete_realm_role.mock_calls), 0)
|
||||||
|
|
||||||
|
# Verify that the module's changed status matches what is expected
|
||||||
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
||||||
|
|
||||||
|
def test_delete_when_present(self):
|
||||||
|
"""Remove a present realm role"""
|
||||||
|
|
||||||
|
module_args = {
|
||||||
|
'auth_keycloak_url': 'http://keycloak.url/auth',
|
||||||
|
'auth_password': 'admin',
|
||||||
|
'auth_realm': 'master',
|
||||||
|
'auth_username': 'admin',
|
||||||
|
'auth_client_id': 'admin-cli',
|
||||||
|
'validate_certs': True,
|
||||||
|
'realm': 'realm-name',
|
||||||
|
'name': 'role-name',
|
||||||
|
'state': 'absent'
|
||||||
|
}
|
||||||
|
return_value_absent = [
|
||||||
|
{
|
||||||
|
"attributes": {},
|
||||||
|
"clientRole": False,
|
||||||
|
"composite": False,
|
||||||
|
"containerId": "realm-name",
|
||||||
|
"description": "role-description",
|
||||||
|
"id": "90f1cdb6-be88-496e-89c6-da1fb6bc6966",
|
||||||
|
"name": "role-name",
|
||||||
|
}
|
||||||
|
]
|
||||||
|
return_value_deleted = [None]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
set_module_args(module_args)
|
||||||
|
|
||||||
|
# Run the module
|
||||||
|
|
||||||
|
with mock_good_connection():
|
||||||
|
with patch_keycloak_api(get_realm_role=return_value_absent, delete_realm_role=return_value_deleted) \
|
||||||
|
as (mock_get_realm_role, mock_create_realm_role, mock_update_realm_role, mock_delete_realm_role):
|
||||||
|
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||||
|
self.module.main()
|
||||||
|
|
||||||
|
self.assertEqual(len(mock_get_realm_role.mock_calls), 1)
|
||||||
|
self.assertEqual(len(mock_delete_realm_role.mock_calls), 1)
|
||||||
|
|
||||||
|
# Verify that the module's changed status matches what is expected
|
||||||
|
self.assertIs(exec_info.exception.args[0]['changed'], changed)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
Reference in a new issue