1# -*- coding: utf-8 -*- 2# Copyright (c) 2017, Eike Frost <ei@kefro.st> 3# 4# This code is part of Ansible, but is an independent component. 5# This particular file snippet, and this file snippet only, is BSD licensed. 6# Modules you write using this snippet, which is embedded dynamically by Ansible 7# still belong to the author of the module, and may assign their own license 8# to the complete work. 9# 10# Redistribution and use in source and binary forms, with or without modification, 11# are permitted provided that the following conditions are met: 12# 13# * Redistributions of source code must retain the above copyright 14# notice, this list of conditions and the following disclaimer. 15# * Redistributions in binary form must reproduce the above copyright notice, 16# this list of conditions and the following disclaimer in the documentation 17# and/or other materials provided with the distribution. 18# 19# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 24# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 26# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29from __future__ import absolute_import, division, print_function 30 31__metaclass__ = type 32 33import json 34import traceback 35 36from ansible.module_utils.urls import open_url 37from ansible.module_utils.six.moves.urllib.parse import urlencode, quote 38from ansible.module_utils.six.moves.urllib.error import HTTPError 39from ansible.module_utils.common.text.converters import to_native, to_text 40 41URL_REALMS = "{url}/admin/realms" 42URL_REALM = "{url}/admin/realms/{realm}" 43 44URL_TOKEN = "{url}/realms/{realm}/protocol/openid-connect/token" 45URL_CLIENT = "{url}/admin/realms/{realm}/clients/{id}" 46URL_CLIENTS = "{url}/admin/realms/{realm}/clients" 47 48URL_CLIENT_ROLES = "{url}/admin/realms/{realm}/clients/{id}/roles" 49URL_CLIENT_ROLE = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}" 50URL_CLIENT_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/clients/{id}/roles/{name}/composites" 51 52URL_REALM_ROLES = "{url}/admin/realms/{realm}/roles" 53URL_REALM_ROLE = "{url}/admin/realms/{realm}/roles/{name}" 54URL_REALM_ROLE_COMPOSITES = "{url}/admin/realms/{realm}/roles/{name}/composites" 55 56URL_CLIENTTEMPLATE = "{url}/admin/realms/{realm}/client-templates/{id}" 57URL_CLIENTTEMPLATES = "{url}/admin/realms/{realm}/client-templates" 58URL_GROUPS = "{url}/admin/realms/{realm}/groups" 59URL_GROUP = "{url}/admin/realms/{realm}/groups/{groupid}" 60 61URL_CLIENTSCOPES = "{url}/admin/realms/{realm}/client-scopes" 62URL_CLIENTSCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}" 63URL_CLIENTSCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models" 64URL_CLIENTSCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}" 65 66URL_CLIENT_ROLEMAPPINGS = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}" 67URL_CLIENT_ROLEMAPPINGS_AVAILABLE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/available" 68URL_CLIENT_ROLEMAPPINGS_COMPOSITE = "{url}/admin/realms/{realm}/groups/{id}/role-mappings/clients/{client}/composite" 69 70URL_AUTHENTICATION_FLOWS = "{url}/admin/realms/{realm}/authentication/flows" 71URL_AUTHENTICATION_FLOW = "{url}/admin/realms/{realm}/authentication/flows/{id}" 72URL_AUTHENTICATION_FLOW_COPY = "{url}/admin/realms/{realm}/authentication/flows/{copyfrom}/copy" 73URL_AUTHENTICATION_FLOW_EXECUTIONS = "{url}/admin/realms/{realm}/authentication/flows/{flowalias}/executions" 74URL_AUTHENTICATION_FLOW_EXECUTIONS_EXECUTION = "{url}/admin/realms/{realm}/authentication/flows/{flowalias}/executions/execution" 75URL_AUTHENTICATION_FLOW_EXECUTIONS_FLOW = "{url}/admin/realms/{realm}/authentication/flows/{flowalias}/executions/flow" 76URL_AUTHENTICATION_EXECUTION_CONFIG = "{url}/admin/realms/{realm}/authentication/executions/{id}/config" 77URL_AUTHENTICATION_EXECUTION_RAISE_PRIORITY = "{url}/admin/realms/{realm}/authentication/executions/{id}/raise-priority" 78URL_AUTHENTICATION_EXECUTION_LOWER_PRIORITY = "{url}/admin/realms/{realm}/authentication/executions/{id}/lower-priority" 79URL_AUTHENTICATION_CONFIG = "{url}/admin/realms/{realm}/authentication/config/{id}" 80 81URL_IDENTITY_PROVIDERS = "{url}/admin/realms/{realm}/identity-provider/instances" 82URL_IDENTITY_PROVIDER = "{url}/admin/realms/{realm}/identity-provider/instances/{alias}" 83URL_IDENTITY_PROVIDER_MAPPERS = "{url}/admin/realms/{realm}/identity-provider/instances/{alias}/mappers" 84URL_IDENTITY_PROVIDER_MAPPER = "{url}/admin/realms/{realm}/identity-provider/instances/{alias}/mappers/{id}" 85 86URL_COMPONENTS = "{url}/admin/realms/{realm}/components" 87URL_COMPONENT = "{url}/admin/realms/{realm}/components/{id}" 88 89 90def keycloak_argument_spec(): 91 """ 92 Returns argument_spec of options common to keycloak_*-modules 93 94 :return: argument_spec dict 95 """ 96 return dict( 97 auth_keycloak_url=dict(type='str', aliases=['url'], required=True, no_log=False), 98 auth_client_id=dict(type='str', default='admin-cli'), 99 auth_realm=dict(type='str'), 100 auth_client_secret=dict(type='str', default=None, no_log=True), 101 auth_username=dict(type='str', aliases=['username']), 102 auth_password=dict(type='str', aliases=['password'], no_log=True), 103 validate_certs=dict(type='bool', default=True), 104 token=dict(type='str', no_log=True), 105 ) 106 107 108def camel(words): 109 return words.split('_')[0] + ''.join(x.capitalize() or '_' for x in words.split('_')[1:]) 110 111 112class KeycloakError(Exception): 113 pass 114 115 116def get_token(module_params): 117 """ Obtains connection header with token for the authentication, 118 token already given or obtained from credentials 119 :param module_params: parameters of the module 120 :return: connection header 121 """ 122 token = module_params.get('token') 123 base_url = module_params.get('auth_keycloak_url') 124 125 if not base_url.lower().startswith(('http', 'https')): 126 raise KeycloakError("auth_url '%s' should either start with 'http' or 'https'." % base_url) 127 128 if token is None: 129 base_url = module_params.get('auth_keycloak_url') 130 validate_certs = module_params.get('validate_certs') 131 auth_realm = module_params.get('auth_realm') 132 client_id = module_params.get('auth_client_id') 133 auth_username = module_params.get('auth_username') 134 auth_password = module_params.get('auth_password') 135 client_secret = module_params.get('auth_client_secret') 136 auth_url = URL_TOKEN.format(url=base_url, realm=auth_realm) 137 temp_payload = { 138 'grant_type': 'password', 139 'client_id': client_id, 140 'client_secret': client_secret, 141 'username': auth_username, 142 'password': auth_password, 143 } 144 # Remove empty items, for instance missing client_secret 145 payload = dict( 146 (k, v) for k, v in temp_payload.items() if v is not None) 147 try: 148 r = json.loads(to_native(open_url(auth_url, method='POST', 149 validate_certs=validate_certs, 150 data=urlencode(payload)).read())) 151 except ValueError as e: 152 raise KeycloakError( 153 'API returned invalid JSON when trying to obtain access token from %s: %s' 154 % (auth_url, str(e))) 155 except Exception as e: 156 raise KeycloakError('Could not obtain access token from %s: %s' 157 % (auth_url, str(e))) 158 159 try: 160 token = r['access_token'] 161 except KeyError: 162 raise KeycloakError( 163 'Could not obtain access token from %s' % auth_url) 164 return { 165 'Authorization': 'Bearer ' + token, 166 'Content-Type': 'application/json' 167 } 168 169 170def is_struct_included(struct1, struct2, exclude=None): 171 """ 172 This function compare if the first parameter structure is included in the second. 173 The function use every elements of struct1 and validates they are present in the struct2 structure. 174 The two structure does not need to be equals for that function to return true. 175 Each elements are compared recursively. 176 :param struct1: 177 type: 178 dict for the initial call, can be dict, list, bool, int or str for recursive calls 179 description: 180 reference structure 181 :param struct2: 182 type: 183 dict for the initial call, can be dict, list, bool, int or str for recursive calls 184 description: 185 structure to compare with first parameter. 186 :param exclude: 187 type: 188 list 189 description: 190 Key to exclude from the comparison. 191 default: None 192 :return: 193 type: 194 bool 195 description: 196 Return True if all element of dict 1 are present in dict 2, return false otherwise. 197 """ 198 if isinstance(struct1, list) and isinstance(struct2, list): 199 for item1 in struct1: 200 if isinstance(item1, (list, dict)): 201 for item2 in struct2: 202 if not is_struct_included(item1, item2, exclude): 203 return False 204 else: 205 if item1 not in struct2: 206 return False 207 return True 208 elif isinstance(struct1, dict) and isinstance(struct2, dict): 209 try: 210 for key in struct1: 211 if not (exclude and key in exclude): 212 if not is_struct_included(struct1[key], struct2[key], exclude): 213 return False 214 return True 215 except KeyError: 216 return False 217 elif isinstance(struct1, bool) and isinstance(struct2, bool): 218 return struct1 == struct2 219 else: 220 return to_text(struct1, 'utf-8') == to_text(struct2, 'utf-8') 221 222 223class KeycloakAPI(object): 224 """ Keycloak API access; Keycloak uses OAuth 2.0 to protect its API, an access token for which 225 is obtained through OpenID connect 226 """ 227 def __init__(self, module, connection_header): 228 self.module = module 229 self.baseurl = self.module.params.get('auth_keycloak_url') 230 self.validate_certs = self.module.params.get('validate_certs') 231 self.restheaders = connection_header 232 233 def get_realm_by_id(self, realm='master'): 234 """ Obtain realm representation by id 235 236 :param realm: realm id 237 :return: dict of real, representation or None if none matching exist 238 """ 239 realm_url = URL_REALM.format(url=self.baseurl, realm=realm) 240 241 try: 242 return json.loads(to_native(open_url(realm_url, method='GET', headers=self.restheaders, 243 validate_certs=self.validate_certs).read())) 244 245 except HTTPError as e: 246 if e.code == 404: 247 return None 248 else: 249 self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)), 250 exception=traceback.format_exc()) 251 except ValueError as e: 252 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain realm %s: %s' % (realm, str(e)), 253 exception=traceback.format_exc()) 254 except Exception as e: 255 self.module.fail_json(msg='Could not obtain realm %s: %s' % (realm, str(e)), 256 exception=traceback.format_exc()) 257 258 def update_realm(self, realmrep, realm="master"): 259 """ Update an existing realm 260 :param realmrep: corresponding (partial/full) realm representation with updates 261 :param realm: realm to be updated in Keycloak 262 :return: HTTPResponse object on success 263 """ 264 realm_url = URL_REALM.format(url=self.baseurl, realm=realm) 265 266 try: 267 return open_url(realm_url, method='PUT', headers=self.restheaders, 268 data=json.dumps(realmrep), validate_certs=self.validate_certs) 269 except Exception as e: 270 self.module.fail_json(msg='Could not update realm %s: %s' % (realm, str(e)), 271 exception=traceback.format_exc()) 272 273 def create_realm(self, realmrep): 274 """ Create a realm in keycloak 275 :param realmrep: Realm representation of realm to be created. 276 :return: HTTPResponse object on success 277 """ 278 realm_url = URL_REALMS.format(url=self.baseurl) 279 280 try: 281 return open_url(realm_url, method='POST', headers=self.restheaders, 282 data=json.dumps(realmrep), validate_certs=self.validate_certs) 283 except Exception as e: 284 self.module.fail_json(msg='Could not create realm %s: %s' % (realmrep['id'], str(e)), 285 exception=traceback.format_exc()) 286 287 def delete_realm(self, realm="master"): 288 """ Delete a realm from Keycloak 289 290 :param realm: realm to be deleted 291 :return: HTTPResponse object on success 292 """ 293 realm_url = URL_REALM.format(url=self.baseurl, realm=realm) 294 295 try: 296 return open_url(realm_url, method='DELETE', headers=self.restheaders, 297 validate_certs=self.validate_certs) 298 except Exception as e: 299 self.module.fail_json(msg='Could not delete realm %s: %s' % (realm, str(e)), 300 exception=traceback.format_exc()) 301 302 def get_clients(self, realm='master', filter=None): 303 """ Obtains client representations for clients in a realm 304 305 :param realm: realm to be queried 306 :param filter: if defined, only the client with clientId specified in the filter is returned 307 :return: list of dicts of client representations 308 """ 309 clientlist_url = URL_CLIENTS.format(url=self.baseurl, realm=realm) 310 if filter is not None: 311 clientlist_url += '?clientId=%s' % filter 312 313 try: 314 return json.loads(to_native(open_url(clientlist_url, method='GET', headers=self.restheaders, 315 validate_certs=self.validate_certs).read())) 316 except ValueError as e: 317 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of clients for realm %s: %s' 318 % (realm, str(e))) 319 except Exception as e: 320 self.module.fail_json(msg='Could not obtain list of clients for realm %s: %s' 321 % (realm, str(e))) 322 323 def get_client_by_clientid(self, client_id, realm='master'): 324 """ Get client representation by clientId 325 :param client_id: The clientId to be queried 326 :param realm: realm from which to obtain the client representation 327 :return: dict with a client representation or None if none matching exist 328 """ 329 r = self.get_clients(realm=realm, filter=client_id) 330 if len(r) > 0: 331 return r[0] 332 else: 333 return None 334 335 def get_client_by_id(self, id, realm='master'): 336 """ Obtain client representation by id 337 338 :param id: id (not clientId) of client to be queried 339 :param realm: client from this realm 340 :return: dict of client representation or None if none matching exist 341 """ 342 client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) 343 344 try: 345 return json.loads(to_native(open_url(client_url, method='GET', headers=self.restheaders, 346 validate_certs=self.validate_certs).read())) 347 348 except HTTPError as e: 349 if e.code == 404: 350 return None 351 else: 352 self.module.fail_json(msg='Could not obtain client %s for realm %s: %s' 353 % (id, realm, str(e))) 354 except ValueError as e: 355 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client %s for realm %s: %s' 356 % (id, realm, str(e))) 357 except Exception as e: 358 self.module.fail_json(msg='Could not obtain client %s for realm %s: %s' 359 % (id, realm, str(e))) 360 361 def get_client_id(self, client_id, realm='master'): 362 """ Obtain id of client by client_id 363 364 :param client_id: client_id of client to be queried 365 :param realm: client template from this realm 366 :return: id of client (usually a UUID) 367 """ 368 result = self.get_client_by_clientid(client_id, realm) 369 if isinstance(result, dict) and 'id' in result: 370 return result['id'] 371 else: 372 return None 373 374 def update_client(self, id, clientrep, realm="master"): 375 """ Update an existing client 376 :param id: id (not clientId) of client to be updated in Keycloak 377 :param clientrep: corresponding (partial/full) client representation with updates 378 :param realm: realm the client is in 379 :return: HTTPResponse object on success 380 """ 381 client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) 382 383 try: 384 return open_url(client_url, method='PUT', headers=self.restheaders, 385 data=json.dumps(clientrep), validate_certs=self.validate_certs) 386 except Exception as e: 387 self.module.fail_json(msg='Could not update client %s in realm %s: %s' 388 % (id, realm, str(e))) 389 390 def create_client(self, clientrep, realm="master"): 391 """ Create a client in keycloak 392 :param clientrep: Client representation of client to be created. Must at least contain field clientId. 393 :param realm: realm for client to be created. 394 :return: HTTPResponse object on success 395 """ 396 client_url = URL_CLIENTS.format(url=self.baseurl, realm=realm) 397 398 try: 399 return open_url(client_url, method='POST', headers=self.restheaders, 400 data=json.dumps(clientrep), validate_certs=self.validate_certs) 401 except Exception as e: 402 self.module.fail_json(msg='Could not create client %s in realm %s: %s' 403 % (clientrep['clientId'], realm, str(e))) 404 405 def delete_client(self, id, realm="master"): 406 """ Delete a client from Keycloak 407 408 :param id: id (not clientId) of client to be deleted 409 :param realm: realm of client to be deleted 410 :return: HTTPResponse object on success 411 """ 412 client_url = URL_CLIENT.format(url=self.baseurl, realm=realm, id=id) 413 414 try: 415 return open_url(client_url, method='DELETE', headers=self.restheaders, 416 validate_certs=self.validate_certs) 417 except Exception as e: 418 self.module.fail_json(msg='Could not delete client %s in realm %s: %s' 419 % (id, realm, str(e))) 420 421 def get_client_roles_by_id(self, cid, realm="master"): 422 """ Fetch the roles of the a client on the Keycloak server. 423 424 :param cid: ID of the client from which to obtain the rolemappings. 425 :param realm: Realm from which to obtain the rolemappings. 426 :return: The rollemappings of specified group and client of the realm (default "master"). 427 """ 428 client_roles_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid) 429 try: 430 return json.loads(to_native(open_url(client_roles_url, method="GET", headers=self.restheaders, 431 validate_certs=self.validate_certs).read())) 432 except Exception as e: 433 self.module.fail_json(msg="Could not fetch rolemappings for client %s in realm %s: %s" 434 % (cid, realm, str(e))) 435 436 def get_client_role_by_name(self, gid, cid, name, realm="master"): 437 """ Get the role ID of a client. 438 439 :param gid: ID of the group from which to obtain the rolemappings. 440 :param cid: ID of the client from which to obtain the rolemappings. 441 :param name: Name of the role. 442 :param realm: Realm from which to obtain the rolemappings. 443 :return: The ID of the role, None if not found. 444 """ 445 rolemappings = self.get_client_roles_by_id(cid, realm=realm) 446 for role in rolemappings: 447 if name == role['name']: 448 return role['id'] 449 return None 450 451 def get_client_rolemapping_by_id(self, gid, cid, rid, realm='master'): 452 """ Obtain client representation by id 453 454 :param gid: ID of the group from which to obtain the rolemappings. 455 :param cid: ID of the client from which to obtain the rolemappings. 456 :param rid: ID of the role. 457 :param realm: client from this realm 458 :return: dict of rolemapping representation or None if none matching exist 459 """ 460 rolemappings_url = URL_CLIENT_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) 461 try: 462 rolemappings = json.loads(to_native(open_url(rolemappings_url, method="GET", headers=self.restheaders, 463 validate_certs=self.validate_certs).read())) 464 for role in rolemappings: 465 if rid == role['id']: 466 return role 467 except Exception as e: 468 self.module.fail_json(msg="Could not fetch rolemappings for client %s in group %s, realm %s: %s" 469 % (cid, gid, realm, str(e))) 470 return None 471 472 def get_client_available_rolemappings(self, gid, cid, realm="master"): 473 """ Fetch the available role of a client in a specified goup on the Keycloak server. 474 475 :param gid: ID of the group from which to obtain the rolemappings. 476 :param cid: ID of the client from which to obtain the rolemappings. 477 :param realm: Realm from which to obtain the rolemappings. 478 :return: The rollemappings of specified group and client of the realm (default "master"). 479 """ 480 available_rolemappings_url = URL_CLIENT_ROLEMAPPINGS_AVAILABLE.format(url=self.baseurl, realm=realm, id=gid, client=cid) 481 try: 482 return json.loads(to_native(open_url(available_rolemappings_url, method="GET", headers=self.restheaders, 483 validate_certs=self.validate_certs).read())) 484 except Exception as e: 485 self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" 486 % (cid, gid, realm, str(e))) 487 488 def get_client_composite_rolemappings(self, gid, cid, realm="master"): 489 """ Fetch the composite role of a client in a specified group on the Keycloak server. 490 491 :param gid: ID of the group from which to obtain the rolemappings. 492 :param cid: ID of the client from which to obtain the rolemappings. 493 :param realm: Realm from which to obtain the rolemappings. 494 :return: The rollemappings of specified group and client of the realm (default "master"). 495 """ 496 available_rolemappings_url = URL_CLIENT_ROLEMAPPINGS_COMPOSITE.format(url=self.baseurl, realm=realm, id=gid, client=cid) 497 try: 498 return json.loads(to_native(open_url(available_rolemappings_url, method="GET", headers=self.restheaders, 499 validate_certs=self.validate_certs).read())) 500 except Exception as e: 501 self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" 502 % (cid, gid, realm, str(e))) 503 504 def add_group_rolemapping(self, gid, cid, role_rep, realm="master"): 505 """ Fetch the composite role of a client in a specified goup on the Keycloak server. 506 507 :param gid: ID of the group from which to obtain the rolemappings. 508 :param cid: ID of the client from which to obtain the rolemappings. 509 :param role_rep: Representation of the role to assign. 510 :param realm: Realm from which to obtain the rolemappings. 511 :return: None. 512 """ 513 available_rolemappings_url = URL_CLIENT_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) 514 try: 515 open_url(available_rolemappings_url, method="POST", headers=self.restheaders, data=json.dumps(role_rep), validate_certs=self.validate_certs) 516 except Exception as e: 517 self.module.fail_json(msg="Could not fetch available rolemappings for client %s in group %s, realm %s: %s" 518 % (cid, gid, realm, str(e))) 519 520 def delete_group_rolemapping(self, gid, cid, role_rep, realm="master"): 521 """ Delete the rolemapping of a client in a specified group on the Keycloak server. 522 523 :param gid: ID of the group from which to obtain the rolemappings. 524 :param cid: ID of the client from which to obtain the rolemappings. 525 :param role_rep: Representation of the role to assign. 526 :param realm: Realm from which to obtain the rolemappings. 527 :return: None. 528 """ 529 available_rolemappings_url = URL_CLIENT_ROLEMAPPINGS.format(url=self.baseurl, realm=realm, id=gid, client=cid) 530 try: 531 open_url(available_rolemappings_url, method="DELETE", headers=self.restheaders, validate_certs=self.validate_certs) 532 except Exception as e: 533 self.module.fail_json(msg="Could not delete available rolemappings for client %s in group %s, realm %s: %s" 534 % (cid, gid, realm, str(e))) 535 536 def get_client_templates(self, realm='master'): 537 """ Obtains client template representations for client templates in a realm 538 539 :param realm: realm to be queried 540 :return: list of dicts of client representations 541 """ 542 url = URL_CLIENTTEMPLATES.format(url=self.baseurl, realm=realm) 543 544 try: 545 return json.loads(to_native(open_url(url, method='GET', headers=self.restheaders, 546 validate_certs=self.validate_certs).read())) 547 except ValueError as e: 548 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of client templates for realm %s: %s' 549 % (realm, str(e))) 550 except Exception as e: 551 self.module.fail_json(msg='Could not obtain list of client templates for realm %s: %s' 552 % (realm, str(e))) 553 554 def get_client_template_by_id(self, id, realm='master'): 555 """ Obtain client template representation by id 556 557 :param id: id (not name) of client template to be queried 558 :param realm: client template from this realm 559 :return: dict of client template representation or None if none matching exist 560 """ 561 url = URL_CLIENTTEMPLATE.format(url=self.baseurl, id=id, realm=realm) 562 563 try: 564 return json.loads(to_native(open_url(url, method='GET', headers=self.restheaders, 565 validate_certs=self.validate_certs).read())) 566 except ValueError as e: 567 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain client templates %s for realm %s: %s' 568 % (id, realm, str(e))) 569 except Exception as e: 570 self.module.fail_json(msg='Could not obtain client template %s for realm %s: %s' 571 % (id, realm, str(e))) 572 573 def get_client_template_by_name(self, name, realm='master'): 574 """ Obtain client template representation by name 575 576 :param name: name of client template to be queried 577 :param realm: client template from this realm 578 :return: dict of client template representation or None if none matching exist 579 """ 580 result = self.get_client_templates(realm) 581 if isinstance(result, list): 582 result = [x for x in result if x['name'] == name] 583 if len(result) > 0: 584 return result[0] 585 return None 586 587 def get_client_template_id(self, name, realm='master'): 588 """ Obtain client template id by name 589 590 :param name: name of client template to be queried 591 :param realm: client template from this realm 592 :return: client template id (usually a UUID) 593 """ 594 result = self.get_client_template_by_name(name, realm) 595 if isinstance(result, dict) and 'id' in result: 596 return result['id'] 597 else: 598 return None 599 600 def update_client_template(self, id, clienttrep, realm="master"): 601 """ Update an existing client template 602 :param id: id (not name) of client template to be updated in Keycloak 603 :param clienttrep: corresponding (partial/full) client template representation with updates 604 :param realm: realm the client template is in 605 :return: HTTPResponse object on success 606 """ 607 url = URL_CLIENTTEMPLATE.format(url=self.baseurl, realm=realm, id=id) 608 609 try: 610 return open_url(url, method='PUT', headers=self.restheaders, 611 data=json.dumps(clienttrep), validate_certs=self.validate_certs) 612 except Exception as e: 613 self.module.fail_json(msg='Could not update client template %s in realm %s: %s' 614 % (id, realm, str(e))) 615 616 def create_client_template(self, clienttrep, realm="master"): 617 """ Create a client in keycloak 618 :param clienttrep: Client template representation of client template to be created. Must at least contain field name 619 :param realm: realm for client template to be created in 620 :return: HTTPResponse object on success 621 """ 622 url = URL_CLIENTTEMPLATES.format(url=self.baseurl, realm=realm) 623 624 try: 625 return open_url(url, method='POST', headers=self.restheaders, 626 data=json.dumps(clienttrep), validate_certs=self.validate_certs) 627 except Exception as e: 628 self.module.fail_json(msg='Could not create client template %s in realm %s: %s' 629 % (clienttrep['clientId'], realm, str(e))) 630 631 def delete_client_template(self, id, realm="master"): 632 """ Delete a client template from Keycloak 633 634 :param id: id (not name) of client to be deleted 635 :param realm: realm of client template to be deleted 636 :return: HTTPResponse object on success 637 """ 638 url = URL_CLIENTTEMPLATE.format(url=self.baseurl, realm=realm, id=id) 639 640 try: 641 return open_url(url, method='DELETE', headers=self.restheaders, 642 validate_certs=self.validate_certs) 643 except Exception as e: 644 self.module.fail_json(msg='Could not delete client template %s in realm %s: %s' 645 % (id, realm, str(e))) 646 647 def get_clientscopes(self, realm="master"): 648 """ Fetch the name and ID of all clientscopes on the Keycloak server. 649 650 To fetch the full data of the group, make a subsequent call to 651 get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return. 652 653 :param realm: Realm in which the clientscope resides; default 'master'. 654 :return The clientscopes of this realm (default "master") 655 """ 656 clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) 657 try: 658 return json.loads(to_native(open_url(clientscopes_url, method="GET", headers=self.restheaders, 659 validate_certs=self.validate_certs).read())) 660 except Exception as e: 661 self.module.fail_json(msg="Could not fetch list of clientscopes in realm %s: %s" 662 % (realm, str(e))) 663 664 def get_clientscope_by_clientscopeid(self, cid, realm="master"): 665 """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. 666 667 If the clientscope does not exist, None is returned. 668 669 gid is a UUID provided by the Keycloak API 670 :param cid: UUID of the clientscope to be returned 671 :param realm: Realm in which the clientscope resides; default 'master'. 672 """ 673 clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=cid) 674 try: 675 return json.loads(to_native(open_url(clientscope_url, method="GET", headers=self.restheaders, 676 validate_certs=self.validate_certs).read())) 677 678 except HTTPError as e: 679 if e.code == 404: 680 return None 681 else: 682 self.module.fail_json(msg="Could not fetch clientscope %s in realm %s: %s" 683 % (cid, realm, str(e))) 684 except Exception as e: 685 self.module.fail_json(msg="Could not clientscope group %s in realm %s: %s" 686 % (cid, realm, str(e))) 687 688 def get_clientscope_by_name(self, name, realm="master"): 689 """ Fetch a keycloak clientscope within a realm based on its name. 690 691 The Keycloak API does not allow filtering of the clientscopes resource by name. 692 As a result, this method first retrieves the entire list of clientscopes - name and ID - 693 then performs a second query to fetch the group. 694 695 If the clientscope does not exist, None is returned. 696 :param name: Name of the clientscope to fetch. 697 :param realm: Realm in which the clientscope resides; default 'master' 698 """ 699 try: 700 all_clientscopes = self.get_clientscopes(realm=realm) 701 702 for clientscope in all_clientscopes: 703 if clientscope['name'] == name: 704 return self.get_clientscope_by_clientscopeid(clientscope['id'], realm=realm) 705 706 return None 707 708 except Exception as e: 709 self.module.fail_json(msg="Could not fetch clientscope %s in realm %s: %s" 710 % (name, realm, str(e))) 711 712 def create_clientscope(self, clientscoperep, realm="master"): 713 """ Create a Keycloak clientscope. 714 715 :param clientscoperep: a ClientScopeRepresentation of the clientscope to be created. Must contain at minimum the field name. 716 :return: HTTPResponse object on success 717 """ 718 clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) 719 try: 720 return open_url(clientscopes_url, method='POST', headers=self.restheaders, 721 data=json.dumps(clientscoperep), validate_certs=self.validate_certs) 722 except Exception as e: 723 self.module.fail_json(msg="Could not create clientscope %s in realm %s: %s" 724 % (clientscoperep['name'], realm, str(e))) 725 726 def update_clientscope(self, clientscoperep, realm="master"): 727 """ Update an existing clientscope. 728 729 :param grouprep: A GroupRepresentation of the updated group. 730 :return HTTPResponse object on success 731 """ 732 clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=clientscoperep['id']) 733 734 try: 735 return open_url(clientscope_url, method='PUT', headers=self.restheaders, 736 data=json.dumps(clientscoperep), validate_certs=self.validate_certs) 737 738 except Exception as e: 739 self.module.fail_json(msg='Could not update clientscope %s in realm %s: %s' 740 % (clientscoperep['name'], realm, str(e))) 741 742 def delete_clientscope(self, name=None, cid=None, realm="master"): 743 """ Delete a clientscope. One of name or cid must be provided. 744 745 Providing the clientscope ID is preferred as it avoids a second lookup to 746 convert a clientscope name to an ID. 747 748 :param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID. 749 :param cid: The ID of the clientscope (preferred to name). 750 :param realm: The realm in which this group resides, default "master". 751 """ 752 753 if cid is None and name is None: 754 # prefer an exception since this is almost certainly a programming error in the module itself. 755 raise Exception("Unable to delete group - one of group ID or name must be provided.") 756 757 # only lookup the name if cid isn't provided. 758 # in the case that both are provided, prefer the ID, since it's one 759 # less lookup. 760 if cid is None and name is not None: 761 for clientscope in self.get_clientscopes(realm=realm): 762 if clientscope['name'] == name: 763 cid = clientscope['id'] 764 break 765 766 # if the group doesn't exist - no problem, nothing to delete. 767 if cid is None: 768 return None 769 770 # should have a good cid by here. 771 clientscope_url = URL_CLIENTSCOPE.format(realm=realm, id=cid, url=self.baseurl) 772 try: 773 return open_url(clientscope_url, method='DELETE', headers=self.restheaders, 774 validate_certs=self.validate_certs) 775 776 except Exception as e: 777 self.module.fail_json(msg="Unable to delete clientscope %s: %s" % (cid, str(e))) 778 779 def get_clientscope_protocolmappers(self, cid, realm="master"): 780 """ Fetch the name and ID of all clientscopes on the Keycloak server. 781 782 To fetch the full data of the group, make a subsequent call to 783 get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return. 784 785 :param cid: id of clientscope (not name). 786 :param realm: Realm in which the clientscope resides; default 'master'. 787 :return The protocolmappers of this realm (default "master") 788 """ 789 protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm) 790 try: 791 return json.loads(to_native(open_url(protocolmappers_url, method="GET", headers=self.restheaders, 792 validate_certs=self.validate_certs).read())) 793 except Exception as e: 794 self.module.fail_json(msg="Could not fetch list of protocolmappers in realm %s: %s" 795 % (realm, str(e))) 796 797 def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="master"): 798 """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. 799 800 If the clientscope does not exist, None is returned. 801 802 gid is a UUID provided by the Keycloak API 803 804 :param cid: UUID of the protocolmapper to be returned 805 :param cid: UUID of the clientscope to be returned 806 :param realm: Realm in which the clientscope resides; default 'master'. 807 """ 808 protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid) 809 try: 810 return json.loads(to_native(open_url(protocolmapper_url, method="GET", headers=self.restheaders, 811 validate_certs=self.validate_certs).read())) 812 813 except HTTPError as e: 814 if e.code == 404: 815 return None 816 else: 817 self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" 818 % (pid, realm, str(e))) 819 except Exception as e: 820 self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" 821 % (cid, realm, str(e))) 822 823 def get_clientscope_protocolmapper_by_name(self, cid, name, realm="master"): 824 """ Fetch a keycloak clientscope within a realm based on its name. 825 826 The Keycloak API does not allow filtering of the clientscopes resource by name. 827 As a result, this method first retrieves the entire list of clientscopes - name and ID - 828 then performs a second query to fetch the group. 829 830 If the clientscope does not exist, None is returned. 831 :param cid: Id of the clientscope (not name). 832 :param name: Name of the protocolmapper to fetch. 833 :param realm: Realm in which the clientscope resides; default 'master' 834 """ 835 try: 836 all_protocolmappers = self.get_clientscope_protocolmappers(cid, realm=realm) 837 838 for protocolmapper in all_protocolmappers: 839 if protocolmapper['name'] == name: 840 return self.get_clientscope_protocolmapper_by_protocolmapperid(protocolmapper['id'], cid, realm=realm) 841 842 return None 843 844 except Exception as e: 845 self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" 846 % (name, realm, str(e))) 847 848 def create_clientscope_protocolmapper(self, cid, mapper_rep, realm="master"): 849 """ Create a Keycloak clientscope protocolmapper. 850 851 :param cid: Id of the clientscope. 852 :param mapper_rep: a ProtocolMapperRepresentation of the protocolmapper to be created. Must contain at minimum the field name. 853 :return: HTTPResponse object on success 854 """ 855 protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm) 856 try: 857 return open_url(protocolmappers_url, method='POST', headers=self.restheaders, 858 data=json.dumps(mapper_rep), validate_certs=self.validate_certs) 859 except Exception as e: 860 self.module.fail_json(msg="Could not create protocolmapper %s in realm %s: %s" 861 % (mapper_rep['name'], realm, str(e))) 862 863 def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"): 864 """ Update an existing clientscope. 865 866 :param cid: Id of the clientscope. 867 :param mapper_rep: A ProtocolMapperRepresentation of the updated protocolmapper. 868 :return HTTPResponse object on success 869 """ 870 protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep['id']) 871 872 try: 873 return open_url(protocolmapper_url, method='PUT', headers=self.restheaders, 874 data=json.dumps(mapper_rep), validate_certs=self.validate_certs) 875 876 except Exception as e: 877 self.module.fail_json(msg='Could not update protocolmappers for clientscope %s in realm %s: %s' 878 % (mapper_rep, realm, str(e))) 879 880 def get_groups(self, realm="master"): 881 """ Fetch the name and ID of all groups on the Keycloak server. 882 883 To fetch the full data of the group, make a subsequent call to 884 get_group_by_groupid, passing in the ID of the group you wish to return. 885 886 :param realm: Return the groups of this realm (default "master"). 887 """ 888 groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) 889 try: 890 return json.loads(to_native(open_url(groups_url, method="GET", headers=self.restheaders, 891 validate_certs=self.validate_certs).read())) 892 except Exception as e: 893 self.module.fail_json(msg="Could not fetch list of groups in realm %s: %s" 894 % (realm, str(e))) 895 896 def get_group_by_groupid(self, gid, realm="master"): 897 """ Fetch a keycloak group from the provided realm using the group's unique ID. 898 899 If the group does not exist, None is returned. 900 901 gid is a UUID provided by the Keycloak API 902 :param gid: UUID of the group to be returned 903 :param realm: Realm in which the group resides; default 'master'. 904 """ 905 groups_url = URL_GROUP.format(url=self.baseurl, realm=realm, groupid=gid) 906 try: 907 return json.loads(to_native(open_url(groups_url, method="GET", headers=self.restheaders, 908 validate_certs=self.validate_certs).read())) 909 910 except HTTPError as e: 911 if e.code == 404: 912 return None 913 else: 914 self.module.fail_json(msg="Could not fetch group %s in realm %s: %s" 915 % (gid, realm, str(e))) 916 except Exception as e: 917 self.module.fail_json(msg="Could not fetch group %s in realm %s: %s" 918 % (gid, realm, str(e))) 919 920 def get_group_by_name(self, name, realm="master"): 921 """ Fetch a keycloak group within a realm based on its name. 922 923 The Keycloak API does not allow filtering of the Groups resource by name. 924 As a result, this method first retrieves the entire list of groups - name and ID - 925 then performs a second query to fetch the group. 926 927 If the group does not exist, None is returned. 928 :param name: Name of the group to fetch. 929 :param realm: Realm in which the group resides; default 'master' 930 """ 931 groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) 932 try: 933 all_groups = self.get_groups(realm=realm) 934 935 for group in all_groups: 936 if group['name'] == name: 937 return self.get_group_by_groupid(group['id'], realm=realm) 938 939 return None 940 941 except Exception as e: 942 self.module.fail_json(msg="Could not fetch group %s in realm %s: %s" 943 % (name, realm, str(e))) 944 945 def create_group(self, grouprep, realm="master"): 946 """ Create a Keycloak group. 947 948 :param grouprep: a GroupRepresentation of the group to be created. Must contain at minimum the field name. 949 :return: HTTPResponse object on success 950 """ 951 groups_url = URL_GROUPS.format(url=self.baseurl, realm=realm) 952 try: 953 return open_url(groups_url, method='POST', headers=self.restheaders, 954 data=json.dumps(grouprep), validate_certs=self.validate_certs) 955 except Exception as e: 956 self.module.fail_json(msg="Could not create group %s in realm %s: %s" 957 % (grouprep['name'], realm, str(e))) 958 959 def update_group(self, grouprep, realm="master"): 960 """ Update an existing group. 961 962 :param grouprep: A GroupRepresentation of the updated group. 963 :return HTTPResponse object on success 964 """ 965 group_url = URL_GROUP.format(url=self.baseurl, realm=realm, groupid=grouprep['id']) 966 967 try: 968 return open_url(group_url, method='PUT', headers=self.restheaders, 969 data=json.dumps(grouprep), validate_certs=self.validate_certs) 970 except Exception as e: 971 self.module.fail_json(msg='Could not update group %s in realm %s: %s' 972 % (grouprep['name'], realm, str(e))) 973 974 def delete_group(self, name=None, groupid=None, realm="master"): 975 """ Delete a group. One of name or groupid must be provided. 976 977 Providing the group ID is preferred as it avoids a second lookup to 978 convert a group name to an ID. 979 980 :param name: The name of the group. A lookup will be performed to retrieve the group ID. 981 :param groupid: The ID of the group (preferred to name). 982 :param realm: The realm in which this group resides, default "master". 983 """ 984 985 if groupid is None and name is None: 986 # prefer an exception since this is almost certainly a programming error in the module itself. 987 raise Exception("Unable to delete group - one of group ID or name must be provided.") 988 989 # only lookup the name if groupid isn't provided. 990 # in the case that both are provided, prefer the ID, since it's one 991 # less lookup. 992 if groupid is None and name is not None: 993 for group in self.get_groups(realm=realm): 994 if group['name'] == name: 995 groupid = group['id'] 996 break 997 998 # if the group doesn't exist - no problem, nothing to delete. 999 if groupid is None: 1000 return None 1001 1002 # should have a good groupid by here. 1003 group_url = URL_GROUP.format(realm=realm, groupid=groupid, url=self.baseurl) 1004 try: 1005 return open_url(group_url, method='DELETE', headers=self.restheaders, 1006 validate_certs=self.validate_certs) 1007 except Exception as e: 1008 self.module.fail_json(msg="Unable to delete group %s: %s" % (groupid, str(e))) 1009 1010 def get_realm_roles(self, realm='master'): 1011 """ Obtains role representations for roles in a realm 1012 1013 :param realm: realm to be queried 1014 :return: list of dicts of role representations 1015 """ 1016 rolelist_url = URL_REALM_ROLES.format(url=self.baseurl, realm=realm) 1017 try: 1018 return json.loads(to_native(open_url(rolelist_url, method='GET', headers=self.restheaders, 1019 validate_certs=self.validate_certs).read())) 1020 except ValueError as e: 1021 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for realm %s: %s' 1022 % (realm, str(e))) 1023 except Exception as e: 1024 self.module.fail_json(msg='Could not obtain list of roles for realm %s: %s' 1025 % (realm, str(e))) 1026 1027 def get_realm_role(self, name, realm='master'): 1028 """ Fetch a keycloak role from the provided realm using the role's name. 1029 1030 If the role does not exist, None is returned. 1031 :param name: Name of the role to fetch. 1032 :param realm: Realm in which the role resides; default 'master'. 1033 """ 1034 role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=quote(name)) 1035 try: 1036 return json.loads(to_native(open_url(role_url, method="GET", headers=self.restheaders, 1037 validate_certs=self.validate_certs).read())) 1038 except HTTPError as e: 1039 if e.code == 404: 1040 return None 1041 else: 1042 self.module.fail_json(msg='Could not fetch role %s in realm %s: %s' 1043 % (name, realm, str(e))) 1044 except Exception as e: 1045 self.module.fail_json(msg='Could not fetch role %s in realm %s: %s' 1046 % (name, realm, str(e))) 1047 1048 def create_realm_role(self, rolerep, realm='master'): 1049 """ Create a Keycloak realm role. 1050 1051 :param rolerep: a RoleRepresentation of the role to be created. Must contain at minimum the field name. 1052 :return: HTTPResponse object on success 1053 """ 1054 roles_url = URL_REALM_ROLES.format(url=self.baseurl, realm=realm) 1055 try: 1056 return open_url(roles_url, method='POST', headers=self.restheaders, 1057 data=json.dumps(rolerep), validate_certs=self.validate_certs) 1058 except Exception as e: 1059 self.module.fail_json(msg='Could not create role %s in realm %s: %s' 1060 % (rolerep['name'], realm, str(e))) 1061 1062 def update_realm_role(self, rolerep, realm='master'): 1063 """ Update an existing realm role. 1064 1065 :param rolerep: A RoleRepresentation of the updated role. 1066 :return HTTPResponse object on success 1067 """ 1068 role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=quote(rolerep['name'])) 1069 try: 1070 return open_url(role_url, method='PUT', headers=self.restheaders, 1071 data=json.dumps(rolerep), validate_certs=self.validate_certs) 1072 except Exception as e: 1073 self.module.fail_json(msg='Could not update role %s in realm %s: %s' 1074 % (rolerep['name'], realm, str(e))) 1075 1076 def delete_realm_role(self, name, realm='master'): 1077 """ Delete a realm role. 1078 1079 :param name: The name of the role. 1080 :param realm: The realm in which this role resides, default "master". 1081 """ 1082 role_url = URL_REALM_ROLE.format(url=self.baseurl, realm=realm, name=quote(name)) 1083 try: 1084 return open_url(role_url, method='DELETE', headers=self.restheaders, 1085 validate_certs=self.validate_certs) 1086 except Exception as e: 1087 self.module.fail_json(msg='Unable to delete role %s in realm %s: %s' 1088 % (name, realm, str(e))) 1089 1090 def get_client_roles(self, clientid, realm='master'): 1091 """ Obtains role representations for client roles in a specific client 1092 1093 :param clientid: Client id to be queried 1094 :param realm: Realm to be queried 1095 :return: List of dicts of role representations 1096 """ 1097 cid = self.get_client_id(clientid, realm=realm) 1098 if cid is None: 1099 self.module.fail_json(msg='Could not find client %s in realm %s' 1100 % (clientid, realm)) 1101 rolelist_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid) 1102 try: 1103 return json.loads(to_native(open_url(rolelist_url, method='GET', headers=self.restheaders, 1104 validate_certs=self.validate_certs).read())) 1105 except ValueError as e: 1106 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of roles for client %s in realm %s: %s' 1107 % (clientid, realm, str(e))) 1108 except Exception as e: 1109 self.module.fail_json(msg='Could not obtain list of roles for client %s in realm %s: %s' 1110 % (clientid, realm, str(e))) 1111 1112 def get_client_role(self, name, clientid, realm='master'): 1113 """ Fetch a keycloak client role from the provided realm using the role's name. 1114 1115 :param name: Name of the role to fetch. 1116 :param clientid: Client id for the client role 1117 :param realm: Realm in which the role resides 1118 :return: Dict of role representation 1119 If the role does not exist, None is returned. 1120 """ 1121 cid = self.get_client_id(clientid, realm=realm) 1122 if cid is None: 1123 self.module.fail_json(msg='Could not find client %s in realm %s' 1124 % (clientid, realm)) 1125 role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=quote(name)) 1126 try: 1127 return json.loads(to_native(open_url(role_url, method="GET", headers=self.restheaders, 1128 validate_certs=self.validate_certs).read())) 1129 except HTTPError as e: 1130 if e.code == 404: 1131 return None 1132 else: 1133 self.module.fail_json(msg='Could not fetch role %s in client %s of realm %s: %s' 1134 % (name, clientid, realm, str(e))) 1135 except Exception as e: 1136 self.module.fail_json(msg='Could not fetch role %s for client %s in realm %s: %s' 1137 % (name, clientid, realm, str(e))) 1138 1139 def create_client_role(self, rolerep, clientid, realm='master'): 1140 """ Create a Keycloak client role. 1141 1142 :param rolerep: a RoleRepresentation of the role to be created. Must contain at minimum the field name. 1143 :param clientid: Client id for the client role 1144 :param realm: Realm in which the role resides 1145 :return: HTTPResponse object on success 1146 """ 1147 cid = self.get_client_id(clientid, realm=realm) 1148 if cid is None: 1149 self.module.fail_json(msg='Could not find client %s in realm %s' 1150 % (clientid, realm)) 1151 roles_url = URL_CLIENT_ROLES.format(url=self.baseurl, realm=realm, id=cid) 1152 try: 1153 return open_url(roles_url, method='POST', headers=self.restheaders, 1154 data=json.dumps(rolerep), validate_certs=self.validate_certs) 1155 except Exception as e: 1156 self.module.fail_json(msg='Could not create role %s for client %s in realm %s: %s' 1157 % (rolerep['name'], clientid, realm, str(e))) 1158 1159 def update_client_role(self, rolerep, clientid, realm="master"): 1160 """ Update an existing client role. 1161 1162 :param rolerep: A RoleRepresentation of the updated role. 1163 :param clientid: Client id for the client role 1164 :param realm: Realm in which the role resides 1165 :return HTTPResponse object on success 1166 """ 1167 cid = self.get_client_id(clientid, realm=realm) 1168 if cid is None: 1169 self.module.fail_json(msg='Could not find client %s in realm %s' 1170 % (clientid, realm)) 1171 role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=quote(rolerep['name'])) 1172 try: 1173 return open_url(role_url, method='PUT', headers=self.restheaders, 1174 data=json.dumps(rolerep), validate_certs=self.validate_certs) 1175 except Exception as e: 1176 self.module.fail_json(msg='Could not update role %s for client %s in realm %s: %s' 1177 % (rolerep['name'], clientid, realm, str(e))) 1178 1179 def delete_client_role(self, name, clientid, realm="master"): 1180 """ Delete a role. One of name or roleid must be provided. 1181 1182 :param name: The name of the role. 1183 :param clientid: Client id for the client role 1184 :param realm: Realm in which the role resides 1185 """ 1186 cid = self.get_client_id(clientid, realm=realm) 1187 if cid is None: 1188 self.module.fail_json(msg='Could not find client %s in realm %s' 1189 % (clientid, realm)) 1190 role_url = URL_CLIENT_ROLE.format(url=self.baseurl, realm=realm, id=cid, name=quote(name)) 1191 try: 1192 return open_url(role_url, method='DELETE', headers=self.restheaders, 1193 validate_certs=self.validate_certs) 1194 except Exception as e: 1195 self.module.fail_json(msg='Unable to delete role %s for client %s in realm %s: %s' 1196 % (name, clientid, realm, str(e))) 1197 1198 def get_authentication_flow_by_alias(self, alias, realm='master'): 1199 """ 1200 Get an authentication flow by it's alias 1201 :param alias: Alias of the authentication flow to get. 1202 :param realm: Realm. 1203 :return: Authentication flow representation. 1204 """ 1205 try: 1206 authentication_flow = {} 1207 # Check if the authentication flow exists on the Keycloak serveraders 1208 authentications = json.load(open_url(URL_AUTHENTICATION_FLOWS.format(url=self.baseurl, realm=realm), method='GET', headers=self.restheaders)) 1209 for authentication in authentications: 1210 if authentication["alias"] == alias: 1211 authentication_flow = authentication 1212 break 1213 return authentication_flow 1214 except Exception as e: 1215 self.module.fail_json(msg="Unable get authentication flow %s: %s" % (alias, str(e))) 1216 1217 def delete_authentication_flow_by_id(self, id, realm='master'): 1218 """ 1219 Delete an authentication flow from Keycloak 1220 :param id: id of authentication flow to be deleted 1221 :param realm: realm of client to be deleted 1222 :return: HTTPResponse object on success 1223 """ 1224 flow_url = URL_AUTHENTICATION_FLOW.format(url=self.baseurl, realm=realm, id=id) 1225 1226 try: 1227 return open_url(flow_url, method='DELETE', headers=self.restheaders, 1228 validate_certs=self.validate_certs) 1229 except Exception as e: 1230 self.module.fail_json(msg='Could not delete authentication flow %s in realm %s: %s' 1231 % (id, realm, str(e))) 1232 1233 def copy_auth_flow(self, config, realm='master'): 1234 """ 1235 Create a new authentication flow from a copy of another. 1236 :param config: Representation of the authentication flow to create. 1237 :param realm: Realm. 1238 :return: Representation of the new authentication flow. 1239 """ 1240 try: 1241 new_name = dict( 1242 newName=config["alias"] 1243 ) 1244 open_url( 1245 URL_AUTHENTICATION_FLOW_COPY.format( 1246 url=self.baseurl, 1247 realm=realm, 1248 copyfrom=quote(config["copyFrom"])), 1249 method='POST', 1250 headers=self.restheaders, 1251 data=json.dumps(new_name)) 1252 flow_list = json.load( 1253 open_url( 1254 URL_AUTHENTICATION_FLOWS.format(url=self.baseurl, 1255 realm=realm), 1256 method='GET', 1257 headers=self.restheaders)) 1258 for flow in flow_list: 1259 if flow["alias"] == config["alias"]: 1260 return flow 1261 return None 1262 except Exception as e: 1263 self.module.fail_json(msg='Could not copy authentication flow %s in realm %s: %s' 1264 % (config["alias"], realm, str(e))) 1265 1266 def create_empty_auth_flow(self, config, realm='master'): 1267 """ 1268 Create a new empty authentication flow. 1269 :param config: Representation of the authentication flow to create. 1270 :param realm: Realm. 1271 :return: Representation of the new authentication flow. 1272 """ 1273 try: 1274 new_flow = dict( 1275 alias=config["alias"], 1276 providerId=config["providerId"], 1277 description=config["description"], 1278 topLevel=True 1279 ) 1280 open_url( 1281 URL_AUTHENTICATION_FLOWS.format( 1282 url=self.baseurl, 1283 realm=realm), 1284 method='POST', 1285 headers=self.restheaders, 1286 data=json.dumps(new_flow)) 1287 flow_list = json.load( 1288 open_url( 1289 URL_AUTHENTICATION_FLOWS.format( 1290 url=self.baseurl, 1291 realm=realm), 1292 method='GET', 1293 headers=self.restheaders)) 1294 for flow in flow_list: 1295 if flow["alias"] == config["alias"]: 1296 return flow 1297 return None 1298 except Exception as e: 1299 self.module.fail_json(msg='Could not create empty authentication flow %s in realm %s: %s' 1300 % (config["alias"], realm, str(e))) 1301 1302 def update_authentication_executions(self, flowAlias, updatedExec, realm='master'): 1303 """ Update authentication executions 1304 1305 :param flowAlias: name of the parent flow 1306 :param updatedExec: JSON containing updated execution 1307 :return: HTTPResponse object on success 1308 """ 1309 try: 1310 open_url( 1311 URL_AUTHENTICATION_FLOW_EXECUTIONS.format( 1312 url=self.baseurl, 1313 realm=realm, 1314 flowalias=quote(flowAlias)), 1315 method='PUT', 1316 headers=self.restheaders, 1317 data=json.dumps(updatedExec)) 1318 except Exception as e: 1319 self.module.fail_json(msg="Unable to update executions %s: %s" % (updatedExec, str(e))) 1320 1321 def add_authenticationConfig_to_execution(self, executionId, authenticationConfig, realm='master'): 1322 """ Add autenticatorConfig to the execution 1323 1324 :param executionId: id of execution 1325 :param authenticationConfig: config to add to the execution 1326 :return: HTTPResponse object on success 1327 """ 1328 try: 1329 open_url( 1330 URL_AUTHENTICATION_EXECUTION_CONFIG.format( 1331 url=self.baseurl, 1332 realm=realm, 1333 id=executionId), 1334 method='POST', 1335 headers=self.restheaders, 1336 data=json.dumps(authenticationConfig)) 1337 except Exception as e: 1338 self.module.fail_json(msg="Unable to add authenticationConfig %s: %s" % (executionId, str(e))) 1339 1340 def create_subflow(self, subflowName, flowAlias, realm='master'): 1341 """ Create new sublow on the flow 1342 1343 :param subflowName: name of the subflow to create 1344 :param flowAlias: name of the parent flow 1345 :return: HTTPResponse object on success 1346 """ 1347 try: 1348 newSubFlow = {} 1349 newSubFlow["alias"] = subflowName 1350 newSubFlow["provider"] = "registration-page-form" 1351 newSubFlow["type"] = "basic-flow" 1352 open_url( 1353 URL_AUTHENTICATION_FLOW_EXECUTIONS_FLOW.format( 1354 url=self.baseurl, 1355 realm=realm, 1356 flowalias=quote(flowAlias)), 1357 method='POST', 1358 headers=self.restheaders, 1359 data=json.dumps(newSubFlow)) 1360 except Exception as e: 1361 self.module.fail_json(msg="Unable to create new subflow %s: %s" % (subflowName, str(e))) 1362 1363 def create_execution(self, execution, flowAlias, realm='master'): 1364 """ Create new execution on the flow 1365 1366 :param execution: name of execution to create 1367 :param flowAlias: name of the parent flow 1368 :return: HTTPResponse object on success 1369 """ 1370 try: 1371 newExec = {} 1372 newExec["provider"] = execution["providerId"] 1373 newExec["requirement"] = execution["requirement"] 1374 open_url( 1375 URL_AUTHENTICATION_FLOW_EXECUTIONS_EXECUTION.format( 1376 url=self.baseurl, 1377 realm=realm, 1378 flowalias=quote(flowAlias)), 1379 method='POST', 1380 headers=self.restheaders, 1381 data=json.dumps(newExec)) 1382 except Exception as e: 1383 self.module.fail_json(msg="Unable to create new execution %s: %s" % (execution["provider"], str(e))) 1384 1385 def change_execution_priority(self, executionId, diff, realm='master'): 1386 """ Raise or lower execution priority of diff time 1387 1388 :param executionId: id of execution to lower priority 1389 :param realm: realm the client is in 1390 :param diff: Integer number, raise of diff time if positive lower of diff time if negative 1391 :return: HTTPResponse object on success 1392 """ 1393 try: 1394 if diff > 0: 1395 for i in range(diff): 1396 open_url( 1397 URL_AUTHENTICATION_EXECUTION_RAISE_PRIORITY.format( 1398 url=self.baseurl, 1399 realm=realm, 1400 id=executionId), 1401 method='POST', 1402 headers=self.restheaders) 1403 elif diff < 0: 1404 for i in range(-diff): 1405 open_url( 1406 URL_AUTHENTICATION_EXECUTION_LOWER_PRIORITY.format( 1407 url=self.baseurl, 1408 realm=realm, 1409 id=executionId), 1410 method='POST', 1411 headers=self.restheaders) 1412 except Exception as e: 1413 self.module.fail_json(msg="Unable to change execution priority %s: %s" % (executionId, str(e))) 1414 1415 def get_executions_representation(self, config, realm='master'): 1416 """ 1417 Get a representation of the executions for an authentication flow. 1418 :param config: Representation of the authentication flow 1419 :param realm: Realm 1420 :return: Representation of the executions 1421 """ 1422 try: 1423 # Get executions created 1424 executions = json.load( 1425 open_url( 1426 URL_AUTHENTICATION_FLOW_EXECUTIONS.format( 1427 url=self.baseurl, 1428 realm=realm, 1429 flowalias=quote(config["alias"])), 1430 method='GET', 1431 headers=self.restheaders)) 1432 for execution in executions: 1433 if "authenticationConfig" in execution: 1434 execConfigId = execution["authenticationConfig"] 1435 execConfig = json.load( 1436 open_url( 1437 URL_AUTHENTICATION_CONFIG.format( 1438 url=self.baseurl, 1439 realm=realm, 1440 id=execConfigId), 1441 method='GET', 1442 headers=self.restheaders)) 1443 execution["authenticationConfig"] = execConfig 1444 return executions 1445 except Exception as e: 1446 self.module.fail_json(msg='Could not get executions for authentication flow %s in realm %s: %s' 1447 % (config["alias"], realm, str(e))) 1448 1449 def get_identity_providers(self, realm='master'): 1450 """ Fetch representations for identity providers in a realm 1451 :param realm: realm to be queried 1452 :return: list of representations for identity providers 1453 """ 1454 idps_url = URL_IDENTITY_PROVIDERS.format(url=self.baseurl, realm=realm) 1455 try: 1456 return json.loads(to_native(open_url(idps_url, method='GET', headers=self.restheaders, 1457 validate_certs=self.validate_certs).read())) 1458 except ValueError as e: 1459 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity providers for realm %s: %s' 1460 % (realm, str(e))) 1461 except Exception as e: 1462 self.module.fail_json(msg='Could not obtain list of identity providers for realm %s: %s' 1463 % (realm, str(e))) 1464 1465 def get_identity_provider(self, alias, realm='master'): 1466 """ Fetch identity provider representation from a realm using the idp's alias. 1467 If the identity provider does not exist, None is returned. 1468 :param alias: Alias of the identity provider to fetch. 1469 :param realm: Realm in which the identity provider resides; default 'master'. 1470 """ 1471 idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=alias) 1472 try: 1473 return json.loads(to_native(open_url(idp_url, method="GET", headers=self.restheaders, 1474 validate_certs=self.validate_certs).read())) 1475 except HTTPError as e: 1476 if e.code == 404: 1477 return None 1478 else: 1479 self.module.fail_json(msg='Could not fetch identity provider %s in realm %s: %s' 1480 % (alias, realm, str(e))) 1481 except Exception as e: 1482 self.module.fail_json(msg='Could not fetch identity provider %s in realm %s: %s' 1483 % (alias, realm, str(e))) 1484 1485 def create_identity_provider(self, idprep, realm='master'): 1486 """ Create an identity provider. 1487 :param idprep: Identity provider representation of the idp to be created. 1488 :param realm: Realm in which this identity provider resides, default "master". 1489 :return: HTTPResponse object on success 1490 """ 1491 idps_url = URL_IDENTITY_PROVIDERS.format(url=self.baseurl, realm=realm) 1492 try: 1493 return open_url(idps_url, method='POST', headers=self.restheaders, 1494 data=json.dumps(idprep), validate_certs=self.validate_certs) 1495 except Exception as e: 1496 self.module.fail_json(msg='Could not create identity provider %s in realm %s: %s' 1497 % (idprep['alias'], realm, str(e))) 1498 1499 def update_identity_provider(self, idprep, realm='master'): 1500 """ Update an existing identity provider. 1501 :param idprep: Identity provider representation of the idp to be updated. 1502 :param realm: Realm in which this identity provider resides, default "master". 1503 :return HTTPResponse object on success 1504 """ 1505 idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=idprep['alias']) 1506 try: 1507 return open_url(idp_url, method='PUT', headers=self.restheaders, 1508 data=json.dumps(idprep), validate_certs=self.validate_certs) 1509 except Exception as e: 1510 self.module.fail_json(msg='Could not update identity provider %s in realm %s: %s' 1511 % (idprep['alias'], realm, str(e))) 1512 1513 def delete_identity_provider(self, alias, realm='master'): 1514 """ Delete an identity provider. 1515 :param alias: Alias of the identity provider. 1516 :param realm: Realm in which this identity provider resides, default "master". 1517 """ 1518 idp_url = URL_IDENTITY_PROVIDER.format(url=self.baseurl, realm=realm, alias=alias) 1519 try: 1520 return open_url(idp_url, method='DELETE', headers=self.restheaders, 1521 validate_certs=self.validate_certs) 1522 except Exception as e: 1523 self.module.fail_json(msg='Unable to delete identity provider %s in realm %s: %s' 1524 % (alias, realm, str(e))) 1525 1526 def get_identity_provider_mappers(self, alias, realm='master'): 1527 """ Fetch representations for identity provider mappers 1528 :param alias: Alias of the identity provider. 1529 :param realm: realm to be queried 1530 :return: list of representations for identity provider mappers 1531 """ 1532 mappers_url = URL_IDENTITY_PROVIDER_MAPPERS.format(url=self.baseurl, realm=realm, alias=alias) 1533 try: 1534 return json.loads(to_native(open_url(mappers_url, method='GET', headers=self.restheaders, 1535 validate_certs=self.validate_certs).read())) 1536 except ValueError as e: 1537 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of identity provider mappers for idp %s in realm %s: %s' 1538 % (alias, realm, str(e))) 1539 except Exception as e: 1540 self.module.fail_json(msg='Could not obtain list of identity provider mappers for idp %s in realm %s: %s' 1541 % (alias, realm, str(e))) 1542 1543 def get_identity_provider_mapper(self, mid, alias, realm='master'): 1544 """ Fetch identity provider representation from a realm using the idp's alias. 1545 If the identity provider does not exist, None is returned. 1546 :param mid: Unique ID of the mapper to fetch. 1547 :param alias: Alias of the identity provider. 1548 :param realm: Realm in which the identity provider resides; default 'master'. 1549 """ 1550 mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mid) 1551 try: 1552 return json.loads(to_native(open_url(mapper_url, method="GET", headers=self.restheaders, 1553 validate_certs=self.validate_certs).read())) 1554 except HTTPError as e: 1555 if e.code == 404: 1556 return None 1557 else: 1558 self.module.fail_json(msg='Could not fetch mapper %s for identity provider %s in realm %s: %s' 1559 % (mid, alias, realm, str(e))) 1560 except Exception as e: 1561 self.module.fail_json(msg='Could not fetch mapper %s for identity provider %s in realm %s: %s' 1562 % (mid, alias, realm, str(e))) 1563 1564 def create_identity_provider_mapper(self, mapper, alias, realm='master'): 1565 """ Create an identity provider mapper. 1566 :param mapper: IdentityProviderMapperRepresentation of the mapper to be created. 1567 :param alias: Alias of the identity provider. 1568 :param realm: Realm in which this identity provider resides, default "master". 1569 :return: HTTPResponse object on success 1570 """ 1571 mappers_url = URL_IDENTITY_PROVIDER_MAPPERS.format(url=self.baseurl, realm=realm, alias=alias) 1572 try: 1573 return open_url(mappers_url, method='POST', headers=self.restheaders, 1574 data=json.dumps(mapper), validate_certs=self.validate_certs) 1575 except Exception as e: 1576 self.module.fail_json(msg='Could not create identity provider mapper %s for idp %s in realm %s: %s' 1577 % (mapper['name'], alias, realm, str(e))) 1578 1579 def update_identity_provider_mapper(self, mapper, alias, realm='master'): 1580 """ Update an existing identity provider. 1581 :param mapper: IdentityProviderMapperRepresentation of the mapper to be updated. 1582 :param alias: Alias of the identity provider. 1583 :param realm: Realm in which this identity provider resides, default "master". 1584 :return HTTPResponse object on success 1585 """ 1586 mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mapper['id']) 1587 try: 1588 return open_url(mapper_url, method='PUT', headers=self.restheaders, 1589 data=json.dumps(mapper), validate_certs=self.validate_certs) 1590 except Exception as e: 1591 self.module.fail_json(msg='Could not update mapper %s for identity provider %s in realm %s: %s' 1592 % (mapper['id'], alias, realm, str(e))) 1593 1594 def delete_identity_provider_mapper(self, mid, alias, realm='master'): 1595 """ Delete an identity provider. 1596 :param mid: Unique ID of the mapper to delete. 1597 :param alias: Alias of the identity provider. 1598 :param realm: Realm in which this identity provider resides, default "master". 1599 """ 1600 mapper_url = URL_IDENTITY_PROVIDER_MAPPER.format(url=self.baseurl, realm=realm, alias=alias, id=mid) 1601 try: 1602 return open_url(mapper_url, method='DELETE', headers=self.restheaders, 1603 validate_certs=self.validate_certs) 1604 except Exception as e: 1605 self.module.fail_json(msg='Unable to delete mapper %s for identity provider %s in realm %s: %s' 1606 % (mid, alias, realm, str(e))) 1607 1608 def get_components(self, filter=None, realm='master'): 1609 """ Fetch representations for components in a realm 1610 :param realm: realm to be queried 1611 :param filter: search filter 1612 :return: list of representations for components 1613 """ 1614 comps_url = URL_COMPONENTS.format(url=self.baseurl, realm=realm) 1615 if filter is not None: 1616 comps_url += '?%s' % filter 1617 1618 try: 1619 return json.loads(to_native(open_url(comps_url, method='GET', headers=self.restheaders, 1620 validate_certs=self.validate_certs).read())) 1621 except ValueError as e: 1622 self.module.fail_json(msg='API returned incorrect JSON when trying to obtain list of components for realm %s: %s' 1623 % (realm, str(e))) 1624 except Exception as e: 1625 self.module.fail_json(msg='Could not obtain list of components for realm %s: %s' 1626 % (realm, str(e))) 1627 1628 def get_component(self, cid, realm='master'): 1629 """ Fetch component representation from a realm using its cid. 1630 If the component does not exist, None is returned. 1631 :param cid: Unique ID of the component to fetch. 1632 :param realm: Realm in which the component resides; default 'master'. 1633 """ 1634 comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) 1635 try: 1636 return json.loads(to_native(open_url(comp_url, method="GET", headers=self.restheaders, 1637 validate_certs=self.validate_certs).read())) 1638 except HTTPError as e: 1639 if e.code == 404: 1640 return None 1641 else: 1642 self.module.fail_json(msg='Could not fetch component %s in realm %s: %s' 1643 % (cid, realm, str(e))) 1644 except Exception as e: 1645 self.module.fail_json(msg='Could not fetch component %s in realm %s: %s' 1646 % (cid, realm, str(e))) 1647 1648 def create_component(self, comprep, realm='master'): 1649 """ Create an component. 1650 :param comprep: Component representation of the component to be created. 1651 :param realm: Realm in which this component resides, default "master". 1652 :return: Component representation of the created component 1653 """ 1654 comps_url = URL_COMPONENTS.format(url=self.baseurl, realm=realm) 1655 try: 1656 resp = open_url(comps_url, method='POST', headers=self.restheaders, 1657 data=json.dumps(comprep), validate_certs=self.validate_certs) 1658 comp_url = resp.getheader('Location') 1659 if comp_url is None: 1660 self.module.fail_json(msg='Could not create component in realm %s: %s' 1661 % (realm, 'unexpected response')) 1662 return json.loads(to_native(open_url(comp_url, method="GET", headers=self.restheaders, 1663 validate_certs=self.validate_certs).read())) 1664 except Exception as e: 1665 self.module.fail_json(msg='Could not create component in realm %s: %s' 1666 % (realm, str(e))) 1667 1668 def update_component(self, comprep, realm='master'): 1669 """ Update an existing component. 1670 :param comprep: Component representation of the component to be updated. 1671 :param realm: Realm in which this component resides, default "master". 1672 :return HTTPResponse object on success 1673 """ 1674 cid = comprep.get('id') 1675 if cid is None: 1676 self.module.fail_json(msg='Cannot update component without id') 1677 comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) 1678 try: 1679 return open_url(comp_url, method='PUT', headers=self.restheaders, 1680 data=json.dumps(comprep), validate_certs=self.validate_certs) 1681 except Exception as e: 1682 self.module.fail_json(msg='Could not update component %s in realm %s: %s' 1683 % (cid, realm, str(e))) 1684 1685 def delete_component(self, cid, realm='master'): 1686 """ Delete an component. 1687 :param cid: Unique ID of the component. 1688 :param realm: Realm in which this component resides, default "master". 1689 """ 1690 comp_url = URL_COMPONENT.format(url=self.baseurl, realm=realm, id=cid) 1691 try: 1692 return open_url(comp_url, method='DELETE', headers=self.restheaders, 1693 validate_certs=self.validate_certs) 1694 except Exception as e: 1695 self.module.fail_json(msg='Unable to delete component %s in realm %s: %s' 1696 % (cid, realm, str(e))) 1697