mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-07-01 16:18:55 +00:00
[PR #12103/27ed9cf9 backport][stable-13] keycloak_clientscope: idempotency for clientscope protocolmappers (#12228)
keycloak_clientscope: idempotency for clientscope protocolmappers (#12103)
* delete_clientscope_protocolmapper
* add protocol_mappers_behavior
* add tests
* fix docstring
* use deepcopy to protect nested dicts
* fix test
* nox -Re formatters
* fix E713
* update version added
* fix typo
* use preferred lookup method
* Apply suggestions from code review
* improve option wording
* fix tests
* rm line
* fix typo
---------
(cherry picked from commit 27ed9cf919)
Co-authored-by: felix-grzelka <felix.grzelka@dataport.de>
Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
parent
b9e869d67e
commit
f5dbd0b1b7
3 changed files with 301 additions and 45 deletions
|
|
@ -24,9 +24,11 @@ def patch_keycloak_api(
|
|||
get_clientscope_by_clientscopeid=None,
|
||||
create_clientscope=None,
|
||||
update_clientscope=None,
|
||||
get_clientscope_protocolmappers=None,
|
||||
get_clientscope_protocolmapper_by_name=None,
|
||||
update_clientscope_protocolmappers=None,
|
||||
update_clientscope_protocolmapper=None,
|
||||
create_clientscope_protocolmapper=None,
|
||||
delete_clientscope_protocolmapper=None,
|
||||
delete_clientscope=None,
|
||||
):
|
||||
"""Mock context manager for patching the methods in PwPolicyIPAClient that contact the IPA server
|
||||
|
|
@ -65,24 +67,36 @@ def patch_keycloak_api(
|
|||
side_effect=get_clientscope_protocolmapper_by_name,
|
||||
) as mock_get_clientscope_protocolmapper_by_name:
|
||||
with patch.object(
|
||||
obj, "update_clientscope_protocolmappers", side_effect=update_clientscope_protocolmappers
|
||||
) as mock_update_clientscope_protocolmappers:
|
||||
obj, "update_clientscope_protocolmapper", side_effect=update_clientscope_protocolmapper
|
||||
) as mock_update_clientscope_protocolmapper:
|
||||
with patch.object(
|
||||
obj, "create_clientscope_protocolmapper", side_effect=create_clientscope_protocolmapper
|
||||
) as mock_create_clientscope_protocolmapper:
|
||||
with patch.object(
|
||||
obj, "delete_clientscope", side_effect=delete_clientscope
|
||||
) as mock_delete_clientscope:
|
||||
yield (
|
||||
mock_get_clientscope_by_name,
|
||||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
)
|
||||
with patch.object(
|
||||
obj,
|
||||
"delete_clientscope_protocolmapper",
|
||||
side_effect=delete_clientscope_protocolmapper,
|
||||
) as mock_delete_clientscope_protocolmapper:
|
||||
with patch.object(
|
||||
obj,
|
||||
"get_clientscope_protocolmappers",
|
||||
side_effect=get_clientscope_protocolmappers,
|
||||
) as mock_get_clientscope_protocolmappers:
|
||||
yield (
|
||||
mock_get_clientscope_by_name,
|
||||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
)
|
||||
|
||||
|
||||
def get_response(object_with_future_response, method, get_id_call_count):
|
||||
|
|
@ -163,9 +177,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -177,8 +193,9 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
|
|
@ -211,9 +228,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -225,8 +244,9 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
|
|
@ -259,9 +279,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -273,8 +295,9 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 1)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
|
|
@ -305,9 +328,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -319,8 +344,9 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
|
|
@ -440,9 +466,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -454,8 +482,9 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
|
|
@ -508,6 +537,7 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
},
|
||||
"name": "protocol3",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
"id": "a7f19adb-cc58-41b1-94ce-782dc255139b",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -628,9 +658,11 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmappers,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
|
|
@ -641,10 +673,184 @@ class TestKeycloakAuthentication(ModuleTestCase):
|
|||
self.assertEqual(mock_create_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 1)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 1)
|
||||
self.assertEqual(mock_get_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 3)
|
||||
self.assertEqual(mock_update_clientscope_protocolmappers.call_count, 3)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 3)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
self.assertIs(exec_info.exception.args[0]["changed"], changed)
|
||||
|
||||
def test_update_clientscope_with_deleted_protocolmappers(self):
|
||||
"""Add a new authentication flow from copy of an other flow"""
|
||||
|
||||
module_args = {
|
||||
"auth_keycloak_url": "http://keycloak.url/auth",
|
||||
"auth_username": "admin",
|
||||
"auth_password": "admin",
|
||||
"auth_realm": "master",
|
||||
"realm": "realm-name",
|
||||
"state": "present",
|
||||
"name": "my-new-kc-clientscope",
|
||||
"protocol_mappers_behavior": "exact",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"protocol": "openid-connect",
|
||||
"config": {
|
||||
"full.path": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
"claim.name": "protocol1_updated",
|
||||
},
|
||||
"name": "protocol1",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
},
|
||||
{
|
||||
"protocol": "openid-connect",
|
||||
"config": {
|
||||
"full.path": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
"claim.name": "protocol2_updated",
|
||||
},
|
||||
"name": "protocol2",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
},
|
||||
],
|
||||
}
|
||||
# before
|
||||
return_value_get_clientscope_by_name = [
|
||||
{
|
||||
"attributes": {},
|
||||
"id": "890ec72e-fe1d-4308-9f27-485ef7eaa182",
|
||||
"name": "my-new-kc-clientscope",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"protocol": "openid-connect",
|
||||
"config": {
|
||||
"full.path": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
"claim.name": "protocol1_updated",
|
||||
},
|
||||
"name": "protocol1",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
"id": "p1",
|
||||
},
|
||||
{
|
||||
"protocol": "openid-connect",
|
||||
"config": {
|
||||
"full.path": "true",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
"claim.name": "protocol2_updated",
|
||||
},
|
||||
"name": "protocol2",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
"id": "p2",
|
||||
},
|
||||
{
|
||||
"protocol": "openid-connect",
|
||||
"config": {
|
||||
"full.path": "true",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"userinfo.token.claim": "true",
|
||||
"claim.name": "protocol3_updated",
|
||||
},
|
||||
"name": "protocol3",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
"id": "p3",
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
# after
|
||||
return_value_get_clientscope_by_clientscopeid = [
|
||||
{
|
||||
"attributes": {},
|
||||
"id": "2286032f-451e-44d5-8be6-e45aac7983a1",
|
||||
"name": "my-new-kc-clientscope",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"config": {
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "protocol2_updated",
|
||||
"full.path": "true",
|
||||
"id.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
},
|
||||
"consentRequired": "false",
|
||||
"id": "p2",
|
||||
"name": "protocol2",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"access.token.claim": "false",
|
||||
"claim.name": "protocol1_updated",
|
||||
"full.path": "false",
|
||||
"id.token.claim": "false",
|
||||
"userinfo.token.claim": "false",
|
||||
},
|
||||
"consentRequired": "false",
|
||||
"id": "p1",
|
||||
"name": "protocol1",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
changed = True
|
||||
|
||||
# Run the module
|
||||
|
||||
with set_module_args(module_args):
|
||||
with mock_good_connection():
|
||||
with patch_keycloak_api(
|
||||
get_clientscope_by_name=return_value_get_clientscope_by_name,
|
||||
get_clientscope_by_clientscopeid=return_value_get_clientscope_by_clientscopeid,
|
||||
) as (
|
||||
mock_get_clientscope_by_name,
|
||||
mock_get_clientscope_by_clientscopeid,
|
||||
mock_create_clientscope,
|
||||
mock_update_clientscope,
|
||||
mock_get_clientscope_protocolmappers,
|
||||
mock_get_clientscope_protocolmapper_by_name,
|
||||
mock_update_clientscope_protocolmapper,
|
||||
mock_create_clientscope_protocolmapper,
|
||||
mock_delete_clientscope_protocolmapper,
|
||||
mock_delete_clientscope,
|
||||
):
|
||||
with self.assertRaises(AnsibleExitJson) as exec_info:
|
||||
self.module.main()
|
||||
|
||||
# Verify number of call on each mock
|
||||
self.assertEqual(mock_get_clientscope_by_name.call_count, 1)
|
||||
self.assertEqual(mock_create_clientscope.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_by_clientscopeid.call_count, 1)
|
||||
self.assertEqual(mock_update_clientscope.call_count, 1)
|
||||
# will not be called, because mock_get_clientscope_protocolmapper_by_name is patched
|
||||
self.assertEqual(mock_get_clientscope_protocolmappers.call_count, 0)
|
||||
self.assertEqual(mock_get_clientscope_protocolmapper_by_name.call_count, 2)
|
||||
self.assertEqual(mock_update_clientscope_protocolmapper.call_count, 2)
|
||||
self.assertEqual(mock_create_clientscope_protocolmapper.call_count, 0)
|
||||
self.assertEqual(mock_delete_clientscope_protocolmapper.call_count, 1)
|
||||
self.assertEqual(mock_delete_clientscope.call_count, 0)
|
||||
|
||||
# expect "p3" to be deleted
|
||||
mock_delete_clientscope_protocolmapper.assert_called_with(
|
||||
return_value_get_clientscope_by_name[0]["id"], "p3", realm=module_args["realm"]
|
||||
)
|
||||
|
||||
# Verify that the module's changed status matches what is expected
|
||||
self.assertIs(exec_info.exception.args[0]["changed"], changed)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue