Mock Version: 3.5 ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -bs --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', '52d3114471f14fe2b425c961a180f092', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -bs --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.src.rpm Child return code was: 0 ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueraiseExc=FalseprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', 'f4abd1ea76ee40139cf6ad38b2e20f4f', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.4KBEwW + umask 022 + cd /builddir/build/BUILD + cd /builddir/build/BUILD + rm -rf kcapi-1.1.2 + /usr/bin/gzip -dc /builddir/build/SOURCES/kcapi-1.1.2.tar.gz + /usr/bin/tar -xof - + STATUS=0 + '[' 0 -ne 0 ']' + cd kcapi-1.1.2 + /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w . + RPM_EC=0 ++ jobs -p + exit 0 Executing(%generate_buildrequires): /bin/sh -e /var/tmp/rpm-tmp.YuYmqg + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + echo pyproject-rpm-macros + echo python3-devel + echo 'python3dist(pip) >= 19' + echo 'python3dist(packaging)' + '[' -f pyproject.toml ']' + echo '(python3dist(toml) if python3-devel < 3.11)' + rm -rfv '*.dist-info/' + '[' -f /usr/bin/python3 ']' + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + RPM_TOXENV=py39 + HOSTNAME=rpmbuild + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --generate-extras --python3_pkgversion 3 --wheeldir /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Import error: No module named 'toml' + RPM_EC=0 ++ jobs -p + exit 0 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.buildreqs.nosrc.rpm Child return code was: 11 Dynamic buildrequires detected Going to install missing buildrequires. See root.log for details. ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueraiseExc=FalseprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', 'acb587b4dcff4b759af51503d45857c0', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.iarWpR + umask 022 + cd /builddir/build/BUILD + cd /builddir/build/BUILD + rm -rf kcapi-1.1.2 + /usr/bin/gzip -dc /builddir/build/SOURCES/kcapi-1.1.2.tar.gz + /usr/bin/tar -xof - + STATUS=0 + '[' 0 -ne 0 ']' + cd kcapi-1.1.2 + /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w . + RPM_EC=0 ++ jobs -p + exit 0 Executing(%generate_buildrequires): /bin/sh -e /var/tmp/rpm-tmp.iGRwk8 + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + echo pyproject-rpm-macros + echo python3-devel + echo 'python3dist(pip) >= 19' + echo 'python3dist(packaging)' + '[' -f pyproject.toml ']' + echo '(python3dist(toml) if python3-devel < 3.11)' + rm -rfv '*.dist-info/' + '[' -f /usr/bin/python3 ']' + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + RPM_TOXENV=py39 + HOSTNAME=rpmbuild + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --generate-extras --python3_pkgversion 3 --wheeldir /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Handling setuptools>=61.0 from build-system.requires Requirement satisfied: setuptools>=61.0 (installed: setuptools 67.6.1) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) HOOK STDOUT: running egg_info HOOK STDOUT: writing kcapi.egg-info/PKG-INFO HOOK STDOUT: writing dependency_links to kcapi.egg-info/dependency_links.txt HOOK STDOUT: writing requirements to kcapi.egg-info/requires.txt HOOK STDOUT: writing top-level names to kcapi.egg-info/top_level.txt HOOK STDOUT: reading manifest file 'kcapi.egg-info/SOURCES.txt' HOOK STDOUT: adding license file 'LICENSE' HOOK STDOUT: writing manifest file 'kcapi.egg-info/SOURCES.txt' Handling wheel from get_requires_for_build_wheel Requirement not satisfied: wheel Exiting dependency generation pass: get_requires_for_build_wheel + RPM_EC=0 ++ jobs -p + exit 0 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.buildreqs.nosrc.rpm Child return code was: 11 Dynamic buildrequires detected Going to install missing buildrequires. See root.log for details. ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueraiseExc=FalseprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', 'd4e65618cbd544839af3f14c46f5745e', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.FCdIAC + umask 022 + cd /builddir/build/BUILD + cd /builddir/build/BUILD + rm -rf kcapi-1.1.2 + /usr/bin/gzip -dc /builddir/build/SOURCES/kcapi-1.1.2.tar.gz + /usr/bin/tar -xof - + STATUS=0 + '[' 0 -ne 0 ']' + cd kcapi-1.1.2 + /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w . + RPM_EC=0 ++ jobs -p + exit 0 Executing(%generate_buildrequires): /bin/sh -e /var/tmp/rpm-tmp.hjKuY6 + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + echo pyproject-rpm-macros + echo python3-devel + echo 'python3dist(pip) >= 19' + echo 'python3dist(packaging)' + '[' -f pyproject.toml ']' + echo '(python3dist(toml) if python3-devel < 3.11)' + rm -rfv '*.dist-info/' + '[' -f /usr/bin/python3 ']' + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + RPM_TOXENV=py39 + HOSTNAME=rpmbuild + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --generate-extras --python3_pkgversion 3 --wheeldir /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Handling setuptools>=61.0 from build-system.requires Requirement satisfied: setuptools>=61.0 (installed: setuptools 67.6.1) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) HOOK STDOUT: running egg_info HOOK STDOUT: writing kcapi.egg-info/PKG-INFO HOOK STDOUT: writing dependency_links to kcapi.egg-info/dependency_links.txt HOOK STDOUT: writing requirements to kcapi.egg-info/requires.txt HOOK STDOUT: writing top-level names to kcapi.egg-info/top_level.txt HOOK STDOUT: reading manifest file 'kcapi.egg-info/SOURCES.txt' HOOK STDOUT: adding license file 'LICENSE' HOOK STDOUT: writing manifest file 'kcapi.egg-info/SOURCES.txt' Handling wheel from get_requires_for_build_wheel Requirement satisfied: wheel (installed: wheel 0.36.2) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) Handling requests from hook generated metadata: Requires-Dist Requirement not satisfied: requests + RPM_EC=0 ++ jobs -p + exit 0 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.buildreqs.nosrc.rpm Child return code was: 11 Dynamic buildrequires detected Going to install missing buildrequires. See root.log for details. ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueraiseExc=FalseprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', '72abd678c66c475e80f5ee56dbba4576', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -br --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.w8N6QN + umask 022 + cd /builddir/build/BUILD + cd /builddir/build/BUILD + rm -rf kcapi-1.1.2 + /usr/bin/gzip -dc /builddir/build/SOURCES/kcapi-1.1.2.tar.gz + /usr/bin/tar -xof - + STATUS=0 + '[' 0 -ne 0 ']' + cd kcapi-1.1.2 + /usr/bin/chmod -Rf a+rX,u+w,g-w,o-w . + RPM_EC=0 ++ jobs -p + exit 0 Executing(%generate_buildrequires): /bin/sh -e /var/tmp/rpm-tmp.efYz5I + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + echo pyproject-rpm-macros + echo python3-devel + echo 'python3dist(pip) >= 19' + echo 'python3dist(packaging)' + '[' -f pyproject.toml ']' + echo '(python3dist(toml) if python3-devel < 3.11)' + rm -rfv '*.dist-info/' + '[' -f /usr/bin/python3 ']' + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + RPM_TOXENV=py39 + HOSTNAME=rpmbuild + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --generate-extras --python3_pkgversion 3 --wheeldir /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Handling setuptools>=61.0 from build-system.requires Requirement satisfied: setuptools>=61.0 (installed: setuptools 67.6.1) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) HOOK STDOUT: running egg_info HOOK STDOUT: writing kcapi.egg-info/PKG-INFO HOOK STDOUT: writing dependency_links to kcapi.egg-info/dependency_links.txt HOOK STDOUT: writing requirements to kcapi.egg-info/requires.txt HOOK STDOUT: writing top-level names to kcapi.egg-info/top_level.txt HOOK STDOUT: reading manifest file 'kcapi.egg-info/SOURCES.txt' HOOK STDOUT: adding license file 'LICENSE' HOOK STDOUT: writing manifest file 'kcapi.egg-info/SOURCES.txt' Handling wheel from get_requires_for_build_wheel Requirement satisfied: wheel (installed: wheel 0.36.2) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) Handling requests from hook generated metadata: Requires-Dist Requirement satisfied: requests (installed: requests 2.28.2) + RPM_EC=0 ++ jobs -p + exit 0 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.buildreqs.nosrc.rpm Child return code was: 11 Dynamic buildrequires detected Going to install missing buildrequires. See root.log for details. ENTER ['do_with_status'](['bash', '--login', '-c', '/usr/bin/rpmbuild -ba --noprep --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'], chrootPath='/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root'env={'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8'}shell=Falselogger=timeout=0uid=1001gid=135user='mockbuild'nspawn_args=['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11']unshare_net=TrueprintOutput=True) Using nspawn with args ['--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11'] Executing command: ['/usr/bin/systemd-nspawn', '-q', '-M', '945cf91bf3ce48e08be73f76e2397966', '-D', '/var/lib/mock/rhel+epel-9-x86_64-1680034751.438341/root', '-a', '-u', 'mockbuild', '--capability=cap_ipc_lock', '--rlimit=RLIMIT_NOFILE=10240', '--capability=cap_ipc_lock', '--bind=/tmp/mock-resolv.f5fpaluq:/etc/resolv.conf', '--bind=/dev/btrfs-control', '--bind=/dev/mapper/control', '--bind=/dev/loop-control', '--bind=/dev/loop0', '--bind=/dev/loop1', '--bind=/dev/loop2', '--bind=/dev/loop3', '--bind=/dev/loop4', '--bind=/dev/loop5', '--bind=/dev/loop6', '--bind=/dev/loop7', '--bind=/dev/loop8', '--bind=/dev/loop9', '--bind=/dev/loop10', '--bind=/dev/loop11', '--console=pipe', '--setenv=TERM=vt100', '--setenv=SHELL=/bin/bash', '--setenv=HOME=/builddir', '--setenv=HOSTNAME=mock', '--setenv=PATH=/usr/bin:/bin:/usr/sbin:/sbin', '--setenv=PROMPT_COMMAND=printf "\\033]0;\\007"', '--setenv=PS1= \\s-\\v\\$ ', '--setenv=LANG=C.UTF-8', '--resolv-conf=off', 'bash', '--login', '-c', '/usr/bin/rpmbuild -ba --noprep --target x86_64 --nodeps /builddir/build/SPECS/python-kcapi.spec'] with env {'TERM': 'vt100', 'SHELL': '/bin/bash', 'HOME': '/builddir', 'HOSTNAME': 'mock', 'PATH': '/usr/bin:/bin:/usr/sbin:/sbin', 'PROMPT_COMMAND': 'printf "\\033]0;\\007"', 'PS1': ' \\s-\\v\\$ ', 'LANG': 'C.UTF-8', 'SYSTEMD_NSPAWN_TMPFS_TMP': '0', 'SYSTEMD_SECCOMP': '0'} and shell False Building target platforms: x86_64 Building for target x86_64 setting SOURCE_DATE_EPOCH=1679961600 Executing(%generate_buildrequires): /bin/sh -e /var/tmp/rpm-tmp.mvocEy + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + echo pyproject-rpm-macros + echo python3-devel + echo 'python3dist(pip) >= 19' + echo 'python3dist(packaging)' + '[' -f pyproject.toml ']' + echo '(python3dist(toml) if python3-devel < 3.11)' + rm -rfv kcapi-1.1.2.dist-info/ removed 'kcapi-1.1.2.dist-info/LICENSE' removed 'kcapi-1.1.2.dist-info/METADATA' removed 'kcapi-1.1.2.dist-info/top_level.txt' removed directory 'kcapi-1.1.2.dist-info/' + '[' -f /usr/bin/python3 ']' + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + RPM_TOXENV=py39 + HOSTNAME=rpmbuild + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_buildrequires.py --generate-extras --python3_pkgversion 3 --wheeldir /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Handling setuptools>=61.0 from build-system.requires Requirement satisfied: setuptools>=61.0 (installed: setuptools 67.6.1) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) HOOK STDOUT: running egg_info HOOK STDOUT: writing kcapi.egg-info/PKG-INFO HOOK STDOUT: writing dependency_links to kcapi.egg-info/dependency_links.txt HOOK STDOUT: writing requirements to kcapi.egg-info/requires.txt HOOK STDOUT: writing top-level names to kcapi.egg-info/top_level.txt HOOK STDOUT: reading manifest file 'kcapi.egg-info/SOURCES.txt' HOOK STDOUT: adding license file 'LICENSE' HOOK STDOUT: writing manifest file 'kcapi.egg-info/SOURCES.txt' Handling wheel from get_requires_for_build_wheel Requirement satisfied: wheel (installed: wheel 0.36.2) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) Handling requests from hook generated metadata: Requires-Dist Requirement satisfied: requests (installed: requests 2.28.2) + RPM_EC=0 ++ jobs -p + exit 0 Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.IwO7B0 + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + mkdir -p /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + CFLAGS='-O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection' + LDFLAGS='-Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 ' + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + /usr/bin/python3 -Bs /usr/lib/rpm/redhat/pyproject_wheel.py /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Processing /builddir/build/BUILD/kcapi-1.1.2 Preparing metadata (pyproject.toml): started Running command Preparing metadata (pyproject.toml) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) running dist_info creating /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info writing /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/PKG-INFO writing dependency_links to /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/dependency_links.txt writing requirements to /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/requires.txt writing top-level names to /builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/top_level.txt writing manifest file '/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/SOURCES.txt' reading manifest file '/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/SOURCES.txt' adding license file 'LICENSE' writing manifest file '/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi.egg-info/SOURCES.txt' creating '/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-modern-metadata-whlq8cg6/kcapi-1.1.2.dist-info' adding license file "LICENSE" (matched pattern "LICEN[CS]E*") Preparing metadata (pyproject.toml): finished with status 'done' Building wheels for collected packages: kcapi Building wheel for kcapi (pyproject.toml): started Running command Building wheel for kcapi (pyproject.toml) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'readme' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `readme = '# Keycloak API\n\nPython module to automate Keycloak or Red Hat Single Sign-On (RHSSO) configuration.\n\n## How To Install\n\n```sh\npip install kcapi\n```\n\n\n## Testing\n\nTo run the test you would need a Keycloak instance, you can run one locally or in the [cloud]( https://developers.redhat.com/developer-sandbox/get-started) then you just have to follow this steps: \n\n```shell script\npython3.10 -m venv .venv\nsource .venv/bin/activate\npip install requests\n\n# Setup SSO server - go to https://developers.redhat.com/developer-sandbox/get-started,\n# launch sandbox environment, +Add, select some "Red Hat Single Sign-On..." template.\nexport KC_USER=admin\nexport KC_PASSWORD=admin_password\nexport KC_REALM=myrealm # do not use master realm, it cannot be removed\nexport KC_ENDPOINT=https://my-first-sso-me-me-dev.apps.sandbox.x8i5.p1.openshiftapps.com\n\npython -m unittest\n```\n\n\n## API\n\n### OpenID\n\nThis class takes care of OpenID login using [password owner credentials](https://datatracker.ietf.org/doc/html/rfc6749#section-1.3.3) flow.\n\n\n#### Constructor\n\n```python\nfrom rhsso import OpenID\n\noid_client = OpenID({\n "client_id": "admin-cli",\n "username": USER,\n "password": PASSWORD,\n "grant_type":"password",\n "realm" : "master"\n }, endpoint)\n```\n\n- **client_id**: Client Identifier in Keycloak.\n- **username**: Login username for the Realm.\n- **password**: Login password for the Realm.\n- **grant_type**: The grant type you want to use (usually ``password``).\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n##### getToken\n\nThis will initiate a session with the Keycloak server and will return a OpenID token back.\n\n```python\noid_client.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n\n##### createAdminClient\n\nThis static method should be used in order to access the master Realm in Keycloak.\n\n```python\n oidc = OpenID.createAdminClient(self.USER, self.PASSWORD)\n oidc.getToken() #glTeDLlmmpLYoAAUMcFQqNOMjw5dA\n```\n### Keycloak\n\nThis class builds all the Keycloak configuration REST resources by using REST conventions we can target the majority of Keycloak services.\n\n#### Constructor\n\n```Python\nkc = Keycloak(token, self.ENDPOINT)\n```\n\nThe constructor takes two parameters:\n\n- **token**: A token with enough priviledge to perform the desired operation.\n- **endpoint**: A Keycloak or RHSSO URL endpoint, something like: ``https://my_keycloak.com``. \n\n\n#### Methods\n\n#### build\nThis methods build a REST client (capabilities detailed below) targeting a specific Keycloak REST resource.\n\n```python\ngroups = kc.build(\'groups\', \'my_realm\')\n\n# Create a group called DC\nstate = groups.create({"name": "DC"}).isOk()\n\n```\n> In this example we build the \'groups\' API for ``my_realm`` Realm.\n\n##### Supported Resources\n\nHere is a quick list of supported resources:\n\n- **users**: [users API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_user).\n- **clients**: [client API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_client). \n- **groups**: [groups API](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n- **roles**: [roles API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_create_a_new_role_for_the_realm_or_client_2)\n- **identity-provider**: [identity provider API](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_get_identity_providers)\n\n\n> As long as you find a REST endpoint that follow the standard you can use this method to build a client around it, an example of this is the non well documented ``components`` endpoint.\n\n- **components**: This API allows you configure things like [user federation](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/user-storage-federation) or [Realm keys](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.2/html/server_administration_guide/admin_permissions#realm_keys).\n\n- **authentication**: Provide access to built-in and/or custom authentication flows.\n
\n\n#### admin\nSimilar to the ``build`` method but the client points to the ``master`` realm, allowing us operation such as realm creation.\n\n```python\n main_realm = kc.admin()\n\n # Creates a realm called my_realm\n main_realm.create({"enabled": "true", "id": my_realm, "realm": my_realm})\n```\n\n\n### REST API\n\nWhen you use the ``build`` or ``admin`` methods you will get back a **REST** class pointing to the Keycloak resource, keep in mind that this class don\'t check that the resource is valid, this is done to keep it flexible and to make it easy to adapt to new Keycloak REST API changes in the future. \n\n#### Usage\n\nIn order to create one you need to ``build`` method we have used before:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nstate = users.create(batman).isOk()\n```\n\n#### Methods\n\nFollowing the example above lets see the methods we have starting with the usual CRUD methods:\n\n#### create\n\nThis method ``POST`` a dictionary into any given resource:\n\n```python\nbatman = {\n "enabled":\'true\',\n "attributes":{},\n "username":"batman",\n "firstName":"Bruce",\n "lastName":"Wayne",\n "emailVerified":""\n}\n\nstate = users.create(batman).isOk()\n```\n\n- **dictionary**: Dictionary with the fields we want to POST to the server.\n\n\n\n\n#### update\n\nThis method performs a ``PUT`` on the resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.update(id, batman_update).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n#### remove\nThis method sends a ``DELETE`` to the pointed resource.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nstate = users.remove(id).isOk()\n```\n- **id**: Id of the resource in Keycloak.\n- **dictionary**: Dictionary representing the updated fields. \n\n\n#### get\nSend a ``GET`` request to retrieve a specific Keycloak resource.\n\n```python\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nuser = users.get(id).response()\n```\n\n#### all\n\nReturn all objects of a particular resource type.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\n# Create a user called batman in DC\nuser_list = users.all() #\xa0[ {id:\'xxx-yyy\', username: \'batman\', ...} ] \n```\n#### findFirst\nFinds a resource by passing an arbitrary key/value pair.\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.findFirst({"key":"username", "value": \'batman\'})\n```\n\n#### exist\nCheck if a resource matching the provided ``id`` exists:\n```Python\nusers = kc.build(\'users\', \'DC\')\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\nusers.exists(id) #True\n```\n\n#### existByKV\nCheck if a resource matching the provided key/value pair, exists.\n\n\n```Python\nusers = kc.build(\'users\', \'DC\')\n\nusers.existByKV("username", \'batman\') #False\n```\n\n\n### ResponseHandler\n\nEach **CRUD** method returns a ``ResponseHandler`` class with the following methods.\n\n#### Methods\n\n\n#### response\nreturns the requests [response object](https://docs.python-requests.org/en/latest/api/#requests.Response).\n\n```Python\nusers.update(id, batman_update).response().status_code #HTTP 201\n```\n\n\n#### isOk\n\nReturn ``True`` if the request complete successfully otherwise it will raise an exception.\n\n```Python\nstate = users.update(id, batman_update).isOk() # Return True here.\n```\n\n#### verify\n\nDoes the same as ``isOk`` but it allow you to chain more methods.\n\n```python\nbatman_update = {\n "firstName":"Bruno",\n "emailVerified": True\n}\n\nid = \'bf81a9d9-811f-4807-bd69-3d74eecbe9f4\'\n\ncookies = users.update(id, batman_update).verify().response().cookies # Get cookies.\n```\n\n\n## Specialisations\n\n### Realms \n\n### KeycloakCaches \n\nThis class handles the Keycloak caches. \n\n#### Instantiation \n\n```python\n# Creates a REST API instance target the Realms API. \nrealms = kc.build(\'realms\', \'my_realm\') \n\n# Gets the cache Realms cache API. \ncaches = realms.caches(self.REALM)\n```\n\n#### clearUserCache \nThis method tells Keycloak to clear the user cache.\n\n```python\ncaches.clearUserCache()\n```\n\n\n#### clearRealmCache \nThis method tells Keycloak to clear the realm cache.\n\n```python\ncaches.clearRealmCache()\n```\n\n\n#### clearKeyCache \nThis method tells Keycloak to clear the external public key cache for clients and identity providers.\n\n```python\ncaches.clearKeyCache()\n```\n\n> For more information on how this caches works follow this [link](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_installation_and_configuration_guide/server_cache_configuration).\n\n\n### Users\n\n#### updateCredentials\n\nUpdate user credentials.\n\n```js\nuser_credentials = {\n \'temporary\': False,\n \'value\':\'12345\'\n}\n\nstate = users.updateCredentials(user_info, user_credentials).isOk() # Updated user password.\n```\nWhere:\n- **temporary**: Boolean where if ``True`` provide a temporary password just for the first login. \n- **value**: String with the password.\n\n\n#### joinGroup\n\nAdd a user into a existing [group](https://access.redhat.com/documentation/en-us/red_hat_single_sign-on/7.0/html/server_administration_guide/groups).\n\nFirst we need a group:\n```python\ndef createDCGroup():\n group = kc.build(\'groups\', \'heroes\')\n return group.create({"name": "DC"}).isOk()\n```\n\nThen we can join the group the following way:\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\n> The API works by matching the first occurrence between the provided ``key/value`` for the two resources (User and Group), this can help in various situation for example if we want to target the user by ``uuid``.\n\n\nUsing ``uuid`` as user identifier.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "name", "value": "DC"}\n\n users.joinGroup(user, group).isOk()\n```\n\nOr we want to use the group ``id``:\n\n```python\n user = {"key": "uuid", "value": "23e4567-e89b-..."}\n group = {"key": "id", "value": "f8d91722-a1f0-45e..."}\n\n users.joinGroup(user, group).isOk()\n```\n> If the field criteria don\'t return a unique value, the first entry in the list will be used.\n#### leaveGroup\n\nRemove a user from a group.\n\n```python\n createDCGroup()\n\n users = kc.build(\'users\', \'heroes\')\n user = {"key": "username", "value": "batman"}\n group = {"key": "uuid", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n user = {"key": "uuid", "value": "12d3-a456-4"}\n group = {"key": "id", "value": "123e4567-e89b-..."}\n\n users.leaveGroup(user, group).isOk()\n\n```\n\n> The same rules for ``key/value`` discussed above also applies here.\n\n\n### Groups\n\nTo manage the relationship between realm level [roles](keycloak.org/docs/latest/server_admin/#assigning-permissions-and-access-using-roles-and-groups) and groups, we can use the **RealmsRolesMapping**.\n\nTo get an instance of this class you need to instantiate the ``group`` resource class:\n\n```Python\ngroups = kc.build(\'groups\', \'heroes\')\n```\n\nAnd use the method ``realmRoles`` passing a valid [group dictionary](https://access.redhat.com/webassets/avalon/d/red-hat-single-sign-on/version-7.0.0/restapi/#_grouprepresentation):\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n```\n\nThen we get a class with following methods:\n\n#### add\n\nAdd a list of existing roles to a group.\n\n```python\ndef makeRoles(self):\n roles = kc.build(\'roles\', self.realm)\n lvl1 = roles.create({"name": "level-1"}).isOk()\n lvl2 = roles.create({"name": "level-2"}).isOk()\n return lvl1 and lvl2\n\n\nif makeRoles():\n realmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\n realmsRoles.add(["level-1", "level-2"])\n```\n\n#### remove\nRemove a list of associated roles from a group.\n\n```python\nrealmsRoles = groups.realmRoles({"key":"name", "value":\'DC\'})\nrealmsRoles.remove(["level-1", "level-2"])\n```\n\n\n## Roles \n\n\n#### composite\nIn Keycloak we can map roles to other roles, this method allow you to do just that. \n\n```python\nrole_watch = self.kc.build(\'roles\', \'my-realm\').find(\'watch\')\nadded = role_watch.add_composite(\'view\')\n```'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'readme' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'readme' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'license' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `license = 'MIT'` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'license' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'license' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) /usr/lib/python3.9/site-packages/setuptools/config/_apply_pyprojecttoml.py:103: _WouldIgnoreField: 'dependencies' defined outside of `pyproject.toml` would be ignored. !! ########################################################################## # configuration would be ignored/result in error due to `pyproject.toml` # ########################################################################## The following seems to be defined outside of `pyproject.toml`: `dependencies = ['requests']` According to the spec (see the link below), however, setuptools CANNOT consider this value unless 'dependencies' is listed as `dynamic`. https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ For the time being, `setuptools` will still consider the given value (as a **transitional** measure), but please note that future releases of setuptools will follow strictly the standard. To prevent this warning, you can list 'dependencies' under `dynamic` or alternatively remove the `[project]` table from your file and rely entirely on other means of configuration. !! warnings.warn(msg, _WouldIgnoreField) running bdist_wheel running build running build_py creating build creating build/lib creating build/lib/kcapi copying kcapi/sso.py -> build/lib/kcapi copying kcapi/oid.py -> build/lib/kcapi copying kcapi/__init__.py -> build/lib/kcapi creating build/lib/kcapi/rest copying kcapi/rest/users.py -> build/lib/kcapi/rest copying kcapi/rest/url.py -> build/lib/kcapi/rest copying kcapi/rest/targets.py -> build/lib/kcapi/rest copying kcapi/rest/roles.py -> build/lib/kcapi/rest copying kcapi/rest/resp.py -> build/lib/kcapi/rest copying kcapi/rest/recovery.py -> build/lib/kcapi/rest copying kcapi/rest/realms.py -> build/lib/kcapi/rest copying kcapi/rest/idp.py -> build/lib/kcapi/rest copying kcapi/rest/helper.py -> build/lib/kcapi/rest copying kcapi/rest/groups.py -> build/lib/kcapi/rest copying kcapi/rest/crud.py -> build/lib/kcapi/rest copying kcapi/rest/clients.py -> build/lib/kcapi/rest copying kcapi/rest/client_scopes.py -> build/lib/kcapi/rest copying kcapi/rest/auth_flows.py -> build/lib/kcapi/rest copying kcapi/rest/__init__.py -> build/lib/kcapi/rest creating build/lib/kcapi/ie copying kcapi/ie/flows_publisher.py -> build/lib/kcapi/ie copying kcapi/ie/auth_flows.py -> build/lib/kcapi/ie copying kcapi/ie/__init__.py -> build/lib/kcapi/ie installing to build/bdist.linux-x86_64/wheel running install running install_lib creating build/bdist.linux-x86_64 creating build/bdist.linux-x86_64/wheel creating build/bdist.linux-x86_64/wheel/kcapi creating build/bdist.linux-x86_64/wheel/kcapi/ie copying build/lib/kcapi/ie/__init__.py -> build/bdist.linux-x86_64/wheel/kcapi/ie copying build/lib/kcapi/ie/auth_flows.py -> build/bdist.linux-x86_64/wheel/kcapi/ie copying build/lib/kcapi/ie/flows_publisher.py -> build/bdist.linux-x86_64/wheel/kcapi/ie creating build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/__init__.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/auth_flows.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/client_scopes.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/clients.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/crud.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/groups.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/helper.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/idp.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/realms.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/recovery.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/resp.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/roles.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/targets.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/url.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/rest/users.py -> build/bdist.linux-x86_64/wheel/kcapi/rest copying build/lib/kcapi/__init__.py -> build/bdist.linux-x86_64/wheel/kcapi copying build/lib/kcapi/oid.py -> build/bdist.linux-x86_64/wheel/kcapi copying build/lib/kcapi/sso.py -> build/bdist.linux-x86_64/wheel/kcapi running install_egg_info running egg_info writing kcapi.egg-info/PKG-INFO writing dependency_links to kcapi.egg-info/dependency_links.txt writing requirements to kcapi.egg-info/requires.txt writing top-level names to kcapi.egg-info/top_level.txt reading manifest file 'kcapi.egg-info/SOURCES.txt' adding license file 'LICENSE' writing manifest file 'kcapi.egg-info/SOURCES.txt' Copying kcapi.egg-info to build/bdist.linux-x86_64/wheel/kcapi-1.1.2-py3.9.egg-info running install_scripts adding license file "LICENSE" (matched pattern "LICEN[CS]E*") creating build/bdist.linux-x86_64/wheel/kcapi-1.1.2.dist-info/WHEEL creating '/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir/pip-wheel-xm4rissv/.tmp-z8uvyeg8/kcapi-1.1.2-py3-none-any.whl' and adding 'build/bdist.linux-x86_64/wheel' to it adding 'kcapi/__init__.py' adding 'kcapi/oid.py' adding 'kcapi/sso.py' adding 'kcapi/ie/__init__.py' adding 'kcapi/ie/auth_flows.py' adding 'kcapi/ie/flows_publisher.py' adding 'kcapi/rest/__init__.py' adding 'kcapi/rest/auth_flows.py' adding 'kcapi/rest/client_scopes.py' adding 'kcapi/rest/clients.py' adding 'kcapi/rest/crud.py' adding 'kcapi/rest/groups.py' adding 'kcapi/rest/helper.py' adding 'kcapi/rest/idp.py' adding 'kcapi/rest/realms.py' adding 'kcapi/rest/recovery.py' adding 'kcapi/rest/resp.py' adding 'kcapi/rest/roles.py' adding 'kcapi/rest/targets.py' adding 'kcapi/rest/url.py' adding 'kcapi/rest/users.py' adding 'kcapi-1.1.2.dist-info/LICENSE' adding 'kcapi-1.1.2.dist-info/METADATA' adding 'kcapi-1.1.2.dist-info/WHEEL' adding 'kcapi-1.1.2.dist-info/top_level.txt' adding 'kcapi-1.1.2.dist-info/RECORD' removing build/bdist.linux-x86_64/wheel Building wheel for kcapi (pyproject.toml): finished with status 'done' Created wheel for kcapi: filename=kcapi-1.1.2-py3-none-any.whl size=22279 sha256=81f451a68995a94e698704b5a91e8f13edda47f798925c48b15b33df5130912f Stored in directory: /builddir/.cache/pip/wheels/a2/59/ad/8240d0202de87e988d043e21cb4363d0180fbbcfdfa432d640 Successfully built kcapi + RPM_EC=0 ++ jobs -p + exit 0 Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.T36tfg + umask 022 + cd /builddir/build/BUILD + '[' /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 '!=' / ']' + rm -rf /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 ++ dirname /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 + mkdir -p /builddir/build/BUILDROOT + mkdir /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 + cd kcapi-1.1.2 ++ ls /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir/kcapi-1.1.2-py3-none-any.whl ++ sed -E 's/([^-]+)-([^-]+)-.+\.whl/\1==\2/' ++ xargs basename --multiple + specifier=kcapi==1.1.2 + TMPDIR=/builddir/build/BUILD/kcapi-1.1.2/.pyproject-builddir + /usr/bin/python3 -m pip install --root /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 --prefix /usr --no-deps --disable-pip-version-check --progress-bar off --verbose --ignore-installed --no-warn-script-location --no-index --no-cache-dir --find-links /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir kcapi==1.1.2 Using pip 22.0.4 from /usr/lib/python3.9/site-packages/pip (python 3.9) Looking in links: /builddir/build/BUILD/kcapi-1.1.2/pyproject-wheeldir Processing ./pyproject-wheeldir/kcapi-1.1.2-py3-none-any.whl Installing collected packages: kcapi Successfully installed kcapi-1.1.2 + '[' -d /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/bin ']' + rm -f /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-ghost-distinfo + site_dirs=() + '[' -d /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages ']' + site_dirs+=("/usr/lib/python3.9/site-packages") + '[' /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib64/python3.9/site-packages '!=' /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages ']' + '[' -d /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib64/python3.9/site-packages ']' + for site_dir in ${site_dirs[@]} + for distinfo in /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64$site_dir/*.dist-info + echo '%ghost /usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info' + sed -i s/pip/rpm/ /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/INSTALLER + PYTHONPATH=/usr/lib/rpm/redhat + /usr/bin/python3 -B /usr/lib/rpm/redhat/pyproject_preprocess_record.py --buildroot /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 --record /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/RECORD --output /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-record + rm -fv /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/RECORD removed '/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/RECORD' + rm -fv /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/REQUESTED removed '/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages/kcapi-1.1.2.dist-info/REQUESTED' ++ cut -f1 '-d ' ++ wc -l /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-ghost-distinfo + lines=1 + '[' 1 -ne 1 ']' + /usr/bin/python3 /usr/lib/rpm/redhat/pyproject_save_files.py --output-files /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-files --output-modules /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-modules --buildroot /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 --sitelib /usr/lib/python3.9/site-packages --sitearch /usr/lib64/python3.9/site-packages --python-version 3.9 --pyproject-record /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-record --prefix /usr '*' +auto + /usr/lib/rpm/find-debuginfo.sh -j2 --strict-build-id -m -i --build-id-seed 1.1.2-1.el9 --unique-debug-suffix -1.1.2-1.el9.x86_64 --unique-debug-src-base python-kcapi-1.1.2-1.el9.x86_64 --run-dwz --dwz-low-mem-die-limit 10000000 --dwz-max-die-limit 110000000 --remove-section .gnu.build.attributes -S debugsourcefiles.list /builddir/build/BUILD/kcapi-1.1.2 find: 'debug': No such file or directory + /usr/lib/rpm/check-buildroot + /usr/lib/rpm/redhat/brp-ldconfig + /usr/lib/rpm/brp-compress + /usr/lib/rpm/redhat/brp-strip-lto /usr/bin/strip + /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip + /usr/lib/rpm/redhat/brp-python-bytecompile '' 1 0 Bytecompiling .py files below /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9 using python3.9 + /usr/lib/rpm/brp-python-hardlink + /usr/lib/rpm/redhat/brp-mangle-shebangs Executing(%check): /bin/sh -e /var/tmp/rpm-tmp.1tGO1t + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + '[' '!' -f /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-modules ']' + PATH=/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/bin:/builddir/.local/bin:/builddir/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/sbin + PYTHONPATH=/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib64/python3.9/site-packages:/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages + _PYTHONSITE=/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib64/python3.9/site-packages:/builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64/usr/lib/python3.9/site-packages + PYTHONDONTWRITEBYTECODE=1 + /usr/bin/python3 -s /usr/lib/rpm/redhat/import_all_modules.py -f /builddir/build/BUILD/python-kcapi-1.1.2-1.el9.x86_64-pyproject-modules -t Check import: kcapi + RPM_EC=0 ++ jobs -p + exit 0 Processing files: python3-kcapi-1.1.2-1.el9.noarch Provides: python-kcapi = 1.1.2-1.el9 python3-kcapi = 1.1.2-1.el9 python3.9-kcapi = 1.1.2-1.el9 python3.9dist(kcapi) = 1.1.2 python3dist(kcapi) = 1.1.2 Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PartialHardlinkSets) <= 4.0.4-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1 Requires: python(abi) = 3.9 python3.9dist(requests) Obsoletes: python39-kcapi < 1.1.2-1.el9 Checking for unpackaged file(s): /usr/lib/rpm/check-files /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 Wrote: /builddir/build/SRPMS/python-kcapi-1.1.2-1.el9.src.rpm Wrote: /builddir/build/RPMS/python3-kcapi-1.1.2-1.el9.noarch.rpm Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.opXj65 + umask 022 + cd /builddir/build/BUILD + cd kcapi-1.1.2 + /usr/bin/rm -rf /builddir/build/BUILDROOT/python-kcapi-1.1.2-1.el9.x86_64 + RPM_EC=0 ++ jobs -p + exit 0 Child return code was: 0