Skip to content

Commit 52b7165

Browse files
carillonatorJustin Ryan
and
Justin Ryan
authored
feat: add client scope client-specific role mappings (#605)
Co-authored-by: Justin Ryan <j.ryan@mwam.com>
1 parent 51d34c4 commit 52b7165

File tree

3 files changed

+399
-5
lines changed

3 files changed

+399
-5
lines changed

src/keycloak/keycloak_admin.py

+218-5
Original file line numberDiff line numberDiff line change
@@ -2602,7 +2602,9 @@ def get_realm_roles_of_client_scope(self, client_id):
26022602
return raise_error_from_response(data_raw, KeycloakGetError)
26032603

26042604
def assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles):
2605-
"""Assign client roles to a client's scope.
2605+
"""Assign client roles to a client's dedicated scope.
2606+
2607+
To assign roles to a client scope, use add_client_specific_roles_to_client_scope.
26062608
26072609
:param client_id: id of client (not client-id) who is assigned the roles
26082610
:type client_id: str
@@ -2626,7 +2628,9 @@ def assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id,
26262628
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
26272629

26282630
def delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles):
2629-
"""Delete client roles of a client's scope.
2631+
"""Delete client roles of a client's dedicated scope.
2632+
2633+
To delete roles from a client scope, use remove_client_specific_roles_of_client_scope.
26302634
26312635
:param client_id: id of client (not client-id) who is assigned the roles
26322636
:type client_id: str
@@ -2650,7 +2654,9 @@ def delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id,
26502654
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
26512655

26522656
def get_client_roles_of_client_scope(self, client_id, client_roles_owner_id):
2653-
"""Get all client roles for a client's scope.
2657+
"""Get all client roles for a client's dedicated scope.
2658+
2659+
To get roles for a client scope, use get_client_specific_roles_of_client_scope.
26542660
26552661
:param client_id: id of client (not client-id)
26562662
:type client_id: str
@@ -3574,6 +3580,104 @@ def add_default_optional_client_scope(self, scope_id):
35743580
)
35753581
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
35763582

3583+
def add_client_specific_roles_to_client_scope(
3584+
self, client_scope_id, client_roles_owner_id, roles
3585+
):
3586+
"""Assign client roles to a client scope.
3587+
3588+
To assign roles to a client's dedicated scope, use assign_client_roles_to_client_scope.
3589+
3590+
:param client_scope_id: client scope id
3591+
:type client_scope_id: str
3592+
:param client_roles_owner_id: id of client (not client-id) who has the roles
3593+
:type client_roles_owner_id: str
3594+
:param roles: roles list or role (use RoleRepresentation, must include id and name)
3595+
:type roles: list
3596+
:return: Keycloak server response
3597+
:rtype: dict
3598+
"""
3599+
payload = roles if isinstance(roles, list) else [roles]
3600+
params_path = {
3601+
"realm-name": self.connection.realm_name,
3602+
"scope-id": client_scope_id,
3603+
"client-id": client_roles_owner_id,
3604+
}
3605+
data_raw = self.connection.raw_post(
3606+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path),
3607+
data=json.dumps(payload),
3608+
)
3609+
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
3610+
3611+
def remove_client_specific_roles_of_client_scope(
3612+
self, client_scope_id, client_roles_owner_id, roles
3613+
):
3614+
"""Delete client roles of a client scope.
3615+
3616+
To delete roles from a client's dedicated scope, use delete_client_roles_of_client_scope.
3617+
3618+
:param client_scope_id: client scope id
3619+
:type client_scope_id: str
3620+
:param client_roles_owner_id: id of client (not client-id) who has the roles
3621+
:type client_roles_owner_id: str
3622+
:param roles: roles list or role (use RoleRepresentation, must include id and name)
3623+
:type roles: list
3624+
:return: Keycloak server response
3625+
:rtype: dict
3626+
"""
3627+
payload = roles if isinstance(roles, list) else [roles]
3628+
params_path = {
3629+
"realm-name": self.connection.realm_name,
3630+
"scope-id": client_scope_id,
3631+
"client-id": client_roles_owner_id,
3632+
}
3633+
data_raw = self.connection.raw_delete(
3634+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path),
3635+
data=json.dumps(payload),
3636+
)
3637+
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
3638+
3639+
def get_client_specific_roles_of_client_scope(self, client_scope_id, client_roles_owner_id):
3640+
"""Get client roles for a client scope, for a specific client.
3641+
3642+
To get roles for a client's dedicated scope, use get_client_roles_of_client_scope.
3643+
3644+
:param client_scope_id: client scope id
3645+
:type client_scope_id: str
3646+
:param client_roles_owner_id: id of client (not client-id) who has the roles
3647+
:type client_roles_owner_id: str
3648+
:return: Keycloak server response (array RoleRepresentation)
3649+
:rtype: dict
3650+
"""
3651+
params_path = {
3652+
"realm-name": self.connection.realm_name,
3653+
"scope-id": client_scope_id,
3654+
"client-id": client_roles_owner_id,
3655+
}
3656+
data_raw = self.connection.raw_get(
3657+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path)
3658+
)
3659+
return raise_error_from_response(data_raw, KeycloakGetError)
3660+
3661+
def get_all_roles_of_client_scope(self, client_scope_id):
3662+
"""Get all client roles for a client scope.
3663+
3664+
To get roles for a client's dedicated scope,
3665+
use get_client_roles_of_client_scope.
3666+
3667+
:param client_scope_id: client scope id
3668+
:type client_scope_id: str
3669+
:return: Keycloak server response (array RoleRepresentation)
3670+
:rtype: dict
3671+
"""
3672+
params_path = {
3673+
"realm-name": self.connection.realm_name,
3674+
"scope-id": client_scope_id,
3675+
}
3676+
data_raw = self.connection.raw_get(
3677+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path)
3678+
)
3679+
return raise_error_from_response(data_raw, KeycloakGetError)
3680+
35773681
def get_mappers_from_client(self, client_id):
35783682
"""List of all client mappers.
35793683
@@ -6816,7 +6920,9 @@ async def a_get_realm_roles_of_client_scope(self, client_id):
68166920
return raise_error_from_response(data_raw, KeycloakGetError)
68176921

68186922
async def a_assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles):
6819-
"""Assign client roles to a client's scope asynchronously.
6923+
"""Assign client roles to a client's dedicated scope asynchronously.
6924+
6925+
To assign roles to a client scope, use a_add_client_specific_roles_to_client_scope.
68206926
68216927
:param client_id: id of client (not client-id) who is assigned the roles
68226928
:type client_id: str
@@ -6840,7 +6946,9 @@ async def a_assign_client_roles_to_client_scope(self, client_id, client_roles_ow
68406946
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
68416947

68426948
async def a_delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles):
6843-
"""Delete client roles of a client's scope asynchronously.
6949+
"""Delete client roles of a client's dedicated scope asynchronously.
6950+
6951+
To remove roles from a client scope, use a_remove_client_specific_roles_of_client_scope.
68446952
68456953
:param client_id: id of client (not client-id) who is assigned the roles
68466954
:type client_id: str
@@ -6866,6 +6974,8 @@ async def a_delete_client_roles_of_client_scope(self, client_id, client_roles_ow
68666974
async def a_get_client_roles_of_client_scope(self, client_id, client_roles_owner_id):
68676975
"""Get all client roles for a client's scope asynchronously.
68686976
6977+
To get roles from a client scope, use a_get_client_roles_of_client_scope.
6978+
68696979
:param client_id: id of client (not client-id)
68706980
:type client_id: str
68716981
:param client_roles_owner_id: id of client (not client-id) who has the roles
@@ -7794,6 +7904,109 @@ async def a_add_default_optional_client_scope(self, scope_id):
77947904
)
77957905
return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
77967906

7907+
async def a_add_client_specific_roles_to_client_scope(
7908+
self, client_scope_id, client_roles_owner_id, roles
7909+
):
7910+
"""Assign client roles to a client scope asynchronously.
7911+
7912+
To assign roles to a client's dedicated scope, use
7913+
a_assign_client_roles_to_client_scope.
7914+
7915+
:param client_scope_id: client scope id
7916+
:type client_scope_id: str
7917+
:param client_roles_owner_id: id of client (not client-id) who has the roles
7918+
:type client_roles_owner_id: str
7919+
:param roles: roles list or role (use RoleRepresentation, must include id and name)
7920+
:type roles: list
7921+
:return: Keycloak server response
7922+
:rtype: dict
7923+
"""
7924+
payload = roles if isinstance(roles, list) else [roles]
7925+
params_path = {
7926+
"realm-name": self.connection.realm_name,
7927+
"scope-id": client_scope_id,
7928+
"client-id": client_roles_owner_id,
7929+
}
7930+
data_raw = await self.connection.a_raw_post(
7931+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path),
7932+
data=json.dumps(payload),
7933+
)
7934+
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
7935+
7936+
async def a_remove_client_specific_roles_of_client_scope(
7937+
self, client_scope_id, client_roles_owner_id, roles
7938+
):
7939+
"""Delete client roles of a client scope asynchronously.
7940+
7941+
To delete roles from a client's dedicated scope,
7942+
use a_delete_client_roles_of_client_scope.
7943+
7944+
:param client_scope_id: client scope id
7945+
:type client_scope_id: str
7946+
:param client_roles_owner_id: id of client (not client-id) who has the roles
7947+
:type client_roles_owner_id: str
7948+
:param roles: roles list or role (use RoleRepresentation, must include id and name)
7949+
:type roles: list
7950+
:return: Keycloak server response
7951+
:rtype: dict
7952+
"""
7953+
payload = roles if isinstance(roles, list) else [roles]
7954+
params_path = {
7955+
"realm-name": self.connection.realm_name,
7956+
"scope-id": client_scope_id,
7957+
"client-id": client_roles_owner_id,
7958+
}
7959+
data_raw = await self.connection.a_raw_delete(
7960+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path),
7961+
data=json.dumps(payload),
7962+
)
7963+
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
7964+
7965+
async def a_get_client_specific_roles_of_client_scope(
7966+
self, client_scope_id, client_roles_owner_id
7967+
):
7968+
"""Get all client roles for a client scope asynchronously.
7969+
7970+
To get roles for a client's dedicated scope,
7971+
use a_get_client_roles_of_client_scope.
7972+
7973+
:param client_scope_id: client scope id
7974+
:type client_scope_id: str
7975+
:param client_roles_owner_id: id of client (not client-id) who has the roles
7976+
:type client_roles_owner_id: str
7977+
:return: Keycloak server response (array RoleRepresentation)
7978+
:rtype: dict
7979+
"""
7980+
params_path = {
7981+
"realm-name": self.connection.realm_name,
7982+
"scope-id": client_scope_id,
7983+
"client-id": client_roles_owner_id,
7984+
}
7985+
data_raw = await self.connection.a_raw_get(
7986+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT.format(**params_path)
7987+
)
7988+
return raise_error_from_response(data_raw, KeycloakGetError)
7989+
7990+
async def a_get_all_roles_of_client_scope(self, client_scope_id):
7991+
"""Get all client roles for a client scope.
7992+
7993+
To get roles for a client's dedicated scope,
7994+
use a_get_client_roles_of_client_scope.
7995+
7996+
:param client_scope_id: client scope id
7997+
:type client_scope_id: str
7998+
:return: Keycloak server response (array RoleRepresentation)
7999+
:rtype: dict
8000+
"""
8001+
params_path = {
8002+
"realm-name": self.connection.realm_name,
8003+
"scope-id": client_scope_id,
8004+
}
8005+
data_raw = self.connection.raw_get(
8006+
urls_patterns.URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS.format(**params_path)
8007+
)
8008+
return raise_error_from_response(data_raw, KeycloakGetError)
8009+
77978010
async def a_get_mappers_from_client(self, client_id):
77988011
"""List of all client mappers asynchronously.
77998012

src/keycloak/urls_patterns.py

+5
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@
148148
URL_ADMIN_CLIENT_SCOPE = URL_ADMIN_CLIENT_SCOPES + "/{scope-id}"
149149
URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER = URL_ADMIN_CLIENT_SCOPE + "/protocol-mappers/models"
150150
URL_ADMIN_CLIENT_SCOPES_MAPPERS = URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER + "/{protocol-mapper-id}"
151+
URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS = URL_ADMIN_CLIENT_SCOPE + "/scope-mappings"
152+
URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_REALM = URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS + "/realm"
153+
URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS_CLIENT = (
154+
URL_ADMIN_CLIENT_SCOPE_ROLE_MAPPINGS + "/clients/{client-id}"
155+
)
151156

152157
URL_ADMIN_REALM_ROLES = "admin/realms/{realm-name}/roles"
153158
URL_ADMIN_REALM_ROLES_SEARCH = URL_ADMIN_REALM_ROLES + "?search={search-text}"

0 commit comments

Comments
 (0)