|
57 | 57 | URL_GROUPS = "{url}/admin/realms/{realm}/groups"
|
58 | 58 | URL_GROUP = "{url}/admin/realms/{realm}/groups/{groupid}"
|
59 | 59 |
|
| 60 | +URL_CLIENTSCOPES = "{url}/admin/realms/{realm}/client-scopes" |
| 61 | +URL_CLIENTSCOPE = "{url}/admin/realms/{realm}/client-scopes/{id}" |
| 62 | +URL_CLIENTSCOPE_PROTOCOLMAPPERS = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models" |
| 63 | +URL_CLIENTSCOPE_PROTOCOLMAPPER = "{url}/admin/realms/{realm}/client-scopes/{id}/protocol-mappers/models/{mapper_id}" |
| 64 | + |
60 | 65 | URL_AUTHENTICATION_FLOWS = "{url}/admin/realms/{realm}/authentication/flows"
|
61 | 66 | URL_AUTHENTICATION_FLOW = "{url}/admin/realms/{realm}/authentication/flows/{id}"
|
62 | 67 | URL_AUTHENTICATION_FLOW_COPY = "{url}/admin/realms/{realm}/authentication/flows/{copyfrom}/copy"
|
@@ -511,6 +516,239 @@ def delete_client_template(self, id, realm="master"):
|
511 | 516 | self.module.fail_json(msg='Could not delete client template %s in realm %s: %s'
|
512 | 517 | % (id, realm, str(e)))
|
513 | 518 |
|
| 519 | + def get_clientscopes(self, realm="master"): |
| 520 | + """ Fetch the name and ID of all clientscopes on the Keycloak server. |
| 521 | +
|
| 522 | + To fetch the full data of the group, make a subsequent call to |
| 523 | + get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return. |
| 524 | +
|
| 525 | + :param realm: Realm in which the clientscope resides; default 'master'. |
| 526 | + :return The clientscopes of this realm (default "master") |
| 527 | + """ |
| 528 | + clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) |
| 529 | + try: |
| 530 | + return json.loads(to_native(open_url(clientscopes_url, method="GET", headers=self.restheaders, |
| 531 | + validate_certs=self.validate_certs).read())) |
| 532 | + except Exception as e: |
| 533 | + self.module.fail_json(msg="Could not fetch list of clientscopes in realm %s: %s" |
| 534 | + % (realm, str(e))) |
| 535 | + |
| 536 | + def get_clientscope_by_clientscopeid(self, cid, realm="master"): |
| 537 | + """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. |
| 538 | +
|
| 539 | + If the clientscope does not exist, None is returned. |
| 540 | +
|
| 541 | + gid is a UUID provided by the Keycloak API |
| 542 | + :param cid: UUID of the clientscope to be returned |
| 543 | + :param realm: Realm in which the clientscope resides; default 'master'. |
| 544 | + """ |
| 545 | + clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=cid) |
| 546 | + try: |
| 547 | + return json.loads(to_native(open_url(clientscope_url, method="GET", headers=self.restheaders, |
| 548 | + validate_certs=self.validate_certs).read())) |
| 549 | + |
| 550 | + except HTTPError as e: |
| 551 | + if e.code == 404: |
| 552 | + return None |
| 553 | + else: |
| 554 | + self.module.fail_json(msg="Could not fetch clientscope %s in realm %s: %s" |
| 555 | + % (cid, realm, str(e))) |
| 556 | + except Exception as e: |
| 557 | + self.module.fail_json(msg="Could not clientscope group %s in realm %s: %s" |
| 558 | + % (cid, realm, str(e))) |
| 559 | + |
| 560 | + def get_clientscope_by_name(self, name, realm="master"): |
| 561 | + """ Fetch a keycloak clientscope within a realm based on its name. |
| 562 | +
|
| 563 | + The Keycloak API does not allow filtering of the clientscopes resource by name. |
| 564 | + As a result, this method first retrieves the entire list of clientscopes - name and ID - |
| 565 | + then performs a second query to fetch the group. |
| 566 | +
|
| 567 | + If the clientscope does not exist, None is returned. |
| 568 | + :param name: Name of the clientscope to fetch. |
| 569 | + :param realm: Realm in which the clientscope resides; default 'master' |
| 570 | + """ |
| 571 | + try: |
| 572 | + all_clientscopes = self.get_clientscopes(realm=realm) |
| 573 | + |
| 574 | + for clientscope in all_clientscopes: |
| 575 | + if clientscope['name'] == name: |
| 576 | + return self.get_clientscope_by_clientscopeid(clientscope['id'], realm=realm) |
| 577 | + |
| 578 | + return None |
| 579 | + |
| 580 | + except Exception as e: |
| 581 | + self.module.fail_json(msg="Could not fetch clientscope %s in realm %s: %s" |
| 582 | + % (name, realm, str(e))) |
| 583 | + |
| 584 | + def create_clientscope(self, clientscoperep, realm="master"): |
| 585 | + """ Create a Keycloak clientscope. |
| 586 | +
|
| 587 | + :param clientscoperep: a ClientScopeRepresentation of the clientscope to be created. Must contain at minimum the field name. |
| 588 | + :return: HTTPResponse object on success |
| 589 | + """ |
| 590 | + clientscopes_url = URL_CLIENTSCOPES.format(url=self.baseurl, realm=realm) |
| 591 | + try: |
| 592 | + return open_url(clientscopes_url, method='POST', headers=self.restheaders, |
| 593 | + data=json.dumps(clientscoperep), validate_certs=self.validate_certs) |
| 594 | + except Exception as e: |
| 595 | + self.module.fail_json(msg="Could not create clientscope %s in realm %s: %s" |
| 596 | + % (clientscoperep['name'], realm, str(e))) |
| 597 | + |
| 598 | + def update_clientscope(self, clientscoperep, realm="master"): |
| 599 | + """ Update an existing clientscope. |
| 600 | +
|
| 601 | + :param grouprep: A GroupRepresentation of the updated group. |
| 602 | + :return HTTPResponse object on success |
| 603 | + """ |
| 604 | + clientscope_url = URL_CLIENTSCOPE.format(url=self.baseurl, realm=realm, id=clientscoperep['id']) |
| 605 | + |
| 606 | + try: |
| 607 | + return open_url(clientscope_url, method='PUT', headers=self.restheaders, |
| 608 | + data=json.dumps(clientscoperep), validate_certs=self.validate_certs) |
| 609 | + |
| 610 | + except Exception as e: |
| 611 | + self.module.fail_json(msg='Could not update clientscope %s in realm %s: %s' |
| 612 | + % (clientscoperep['name'], realm, str(e))) |
| 613 | + |
| 614 | + def delete_clientscope(self, name=None, cid=None, realm="master"): |
| 615 | + """ Delete a clientscope. One of name or cid must be provided. |
| 616 | +
|
| 617 | + Providing the clientscope ID is preferred as it avoids a second lookup to |
| 618 | + convert a clientscope name to an ID. |
| 619 | +
|
| 620 | + :param name: The name of the clientscope. A lookup will be performed to retrieve the clientscope ID. |
| 621 | + :param cid: The ID of the clientscope (preferred to name). |
| 622 | + :param realm: The realm in which this group resides, default "master". |
| 623 | + """ |
| 624 | + |
| 625 | + if cid is None and name is None: |
| 626 | + # prefer an exception since this is almost certainly a programming error in the module itself. |
| 627 | + raise Exception("Unable to delete group - one of group ID or name must be provided.") |
| 628 | + |
| 629 | + # only lookup the name if cid isn't provided. |
| 630 | + # in the case that both are provided, prefer the ID, since it's one |
| 631 | + # less lookup. |
| 632 | + if cid is None and name is not None: |
| 633 | + for clientscope in self.get_clientscopes(realm=realm): |
| 634 | + if clientscope['name'] == name: |
| 635 | + cid = clientscope['id'] |
| 636 | + break |
| 637 | + |
| 638 | + # if the group doesn't exist - no problem, nothing to delete. |
| 639 | + if cid is None: |
| 640 | + return None |
| 641 | + |
| 642 | + # should have a good cid by here. |
| 643 | + clientscope_url = URL_CLIENTSCOPE.format(realm=realm, id=cid, url=self.baseurl) |
| 644 | + try: |
| 645 | + return open_url(clientscope_url, method='DELETE', headers=self.restheaders, |
| 646 | + validate_certs=self.validate_certs) |
| 647 | + |
| 648 | + except Exception as e: |
| 649 | + self.module.fail_json(msg="Unable to delete clientscope %s: %s" % (cid, str(e))) |
| 650 | + |
| 651 | + def get_clientscope_protocolmappers(self, cid, realm="master"): |
| 652 | + """ Fetch the name and ID of all clientscopes on the Keycloak server. |
| 653 | +
|
| 654 | + To fetch the full data of the group, make a subsequent call to |
| 655 | + get_clientscope_by_clientscopeid, passing in the ID of the group you wish to return. |
| 656 | +
|
| 657 | + :param cid: id of clientscope (not name). |
| 658 | + :param realm: Realm in which the clientscope resides; default 'master'. |
| 659 | + :return The protocolmappers of this realm (default "master") |
| 660 | + """ |
| 661 | + protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(id=cid, url=self.baseurl, realm=realm) |
| 662 | + try: |
| 663 | + return json.loads(to_native(open_url(protocolmappers_url, method="GET", headers=self.restheaders, |
| 664 | + validate_certs=self.validate_certs).read())) |
| 665 | + except Exception as e: |
| 666 | + self.module.fail_json(msg="Could not fetch list of protocolmappers in realm %s: %s" |
| 667 | + % (realm, str(e))) |
| 668 | + |
| 669 | + def get_clientscope_protocolmapper_by_protocolmapperid(self, pid, cid, realm="master"): |
| 670 | + """ Fetch a keycloak clientscope from the provided realm using the clientscope's unique ID. |
| 671 | +
|
| 672 | + If the clientscope does not exist, None is returned. |
| 673 | +
|
| 674 | + gid is a UUID provided by the Keycloak API |
| 675 | +
|
| 676 | + :param cid: UUID of the protocolmapper to be returned |
| 677 | + :param cid: UUID of the clientscope to be returned |
| 678 | + :param realm: Realm in which the clientscope resides; default 'master'. |
| 679 | + """ |
| 680 | + protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=pid) |
| 681 | + try: |
| 682 | + return json.loads(to_native(open_url(protocolmapper_url, method="GET", headers=self.restheaders, |
| 683 | + validate_certs=self.validate_certs).read())) |
| 684 | + |
| 685 | + except HTTPError as e: |
| 686 | + if e.code == 404: |
| 687 | + return None |
| 688 | + else: |
| 689 | + self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" |
| 690 | + % (pid, realm, str(e))) |
| 691 | + except Exception as e: |
| 692 | + self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" |
| 693 | + % (cid, realm, str(e))) |
| 694 | + |
| 695 | + def get_clientscope_protocolmapper_by_name(self, cid, name, realm="master"): |
| 696 | + """ Fetch a keycloak clientscope within a realm based on its name. |
| 697 | +
|
| 698 | + The Keycloak API does not allow filtering of the clientscopes resource by name. |
| 699 | + As a result, this method first retrieves the entire list of clientscopes - name and ID - |
| 700 | + then performs a second query to fetch the group. |
| 701 | +
|
| 702 | + If the clientscope does not exist, None is returned. |
| 703 | + :param cid: Id of the clientscope (not name). |
| 704 | + :param name: Name of the protocolmapper to fetch. |
| 705 | + :param realm: Realm in which the clientscope resides; default 'master' |
| 706 | + """ |
| 707 | + try: |
| 708 | + all_protocolmappers = self.get_clientscope_protocolmappers(cid, realm=realm) |
| 709 | + |
| 710 | + for protocolmapper in all_protocolmappers: |
| 711 | + if protocolmapper['name'] == name: |
| 712 | + return self.get_clientscope_protocolmapper_by_protocolmapperid(protocolmapper['id'], cid, realm=realm) |
| 713 | + |
| 714 | + return None |
| 715 | + |
| 716 | + except Exception as e: |
| 717 | + self.module.fail_json(msg="Could not fetch protocolmapper %s in realm %s: %s" |
| 718 | + % (name, realm, str(e))) |
| 719 | + |
| 720 | + def create_clientscope_protocolmapper(self, cid, mapper_rep, realm="master"): |
| 721 | + """ Create a Keycloak clientscope protocolmapper. |
| 722 | +
|
| 723 | + :param cid: Id of the clientscope. |
| 724 | + :param mapper_rep: a ProtocolMapperRepresentation of the protocolmapper to be created. Must contain at minimum the field name. |
| 725 | + :return: HTTPResponse object on success |
| 726 | + """ |
| 727 | + protocolmappers_url = URL_CLIENTSCOPE_PROTOCOLMAPPERS.format(url=self.baseurl, id=cid, realm=realm) |
| 728 | + try: |
| 729 | + return open_url(protocolmappers_url, method='POST', headers=self.restheaders, |
| 730 | + data=json.dumps(mapper_rep), validate_certs=self.validate_certs) |
| 731 | + except Exception as e: |
| 732 | + self.module.fail_json(msg="Could not create protocolmapper %s in realm %s: %s" |
| 733 | + % (mapper_rep['name'], realm, str(e))) |
| 734 | + |
| 735 | + def update_clientscope_protocolmappers(self, cid, mapper_rep, realm="master"): |
| 736 | + """ Update an existing clientscope. |
| 737 | +
|
| 738 | + :param cid: Id of the clientscope. |
| 739 | + :param mapper_rep: A ProtocolMapperRepresentation of the updated protocolmapper. |
| 740 | + :return HTTPResponse object on success |
| 741 | + """ |
| 742 | + protocolmapper_url = URL_CLIENTSCOPE_PROTOCOLMAPPER.format(url=self.baseurl, realm=realm, id=cid, mapper_id=mapper_rep['id']) |
| 743 | + |
| 744 | + try: |
| 745 | + return open_url(protocolmapper_url, method='PUT', headers=self.restheaders, |
| 746 | + data=json.dumps(mapper_rep), validate_certs=self.validate_certs) |
| 747 | + |
| 748 | + except Exception as e: |
| 749 | + self.module.fail_json(msg='Could not update protocolmappers for clientscope %s in realm %s: %s' |
| 750 | + % (mapper_rep, realm, str(e))) |
| 751 | + |
514 | 752 | def get_groups(self, realm="master"):
|
515 | 753 | """ Fetch the name and ID of all groups on the Keycloak server.
|
516 | 754 |
|
|
0 commit comments