Browse Source

chore: merge master into branch

pull/373/head
Richard Nemeth 2 years ago
parent
commit
96d4f79282
  1. 24
      CHANGELOG.md
  2. 73
      README.md
  3. 185
      src/keycloak/keycloak_admin.py
  4. 17
      src/keycloak/keycloak_openid.py
  5. 4
      src/keycloak/urls_patterns.py
  6. 199
      tests/test_keycloak_admin.py

24
CHANGELOG.md

@ -1,5 +1,29 @@
# Changelog # Changelog
## v2.5.0 (2022-08-19)
### Feat
- added missing functionality to include attributes when returning realm roles according to specifications
## v2.4.0 (2022-08-19)
### Feat
- add client scope-mappings client roles operations
## v2.3.0 (2022-08-13)
### Feat
- Add token_type/scope to token exchange api
## v2.2.0 (2022-08-12)
### Feat
- add client scope-mappings realm roles operations
## v2.1.1 (2022-07-19) ## v2.1.1 (2022-07-19)
### Refactor ### Refactor

73
README.md

@ -65,9 +65,9 @@ from keycloak import KeycloakOpenID
# Configure client # Configure client
keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/", keycloak_openid = KeycloakOpenID(server_url="http://localhost:8080/auth/",
client_id="example_client",
realm_name="example_realm",
client_secret_key="secret")
client_id="example_client",
realm_name="example_realm",
client_secret_key="secret")
# Get WellKnow # Get WellKnow
config_well_known = keycloak_openid.well_known() config_well_known = keycloak_openid.well_known()
@ -110,7 +110,7 @@ rpt = keycloak_openid.entitlement(token['access_token'], "resource_id")
# Instropect RPT # Instropect RPT
token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['access_token'], rpt=rpt['rpt'], token_rpt_info = keycloak_openid.introspect(keycloak_openid.introspect(token['access_token'], rpt=rpt['rpt'],
token_type_hint="requesting_party_token"))
token_type_hint="requesting_party_token"))
# Introspect Token # Introspect Token
token_info = keycloak_openid.introspect(token['access_token']) token_info = keycloak_openid.introspect(token['access_token'])
@ -153,37 +153,37 @@ keycloak_admin = KeycloakAdmin(server_url="http://localhost:8080/auth/",
# Add user # Add user
new_user = keycloak_admin.create_user({"email": "example@example.com", new_user = keycloak_admin.create_user({"email": "example@example.com",
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example"})
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example"})
# Add user and raise exception if username already exists # Add user and raise exception if username already exists
# exist_ok currently defaults to True for backwards compatibility reasons # exist_ok currently defaults to True for backwards compatibility reasons
new_user = keycloak_admin.create_user({"email": "example@example.com", new_user = keycloak_admin.create_user({"email": "example@example.com",
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example"},
exist_ok=False)
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example"},
exist_ok=False)
# Add user and set password # Add user and set password
new_user = keycloak_admin.create_user({"email": "example@example.com", new_user = keycloak_admin.create_user({"email": "example@example.com",
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example",
"username": "example@example.com",
"enabled": True,
"firstName": "Example",
"lastName": "Example",
"credentials": [{"value": "secret","type": "password",}]}) "credentials": [{"value": "secret","type": "password",}]})
# Add user and specify a locale # Add user and specify a locale
new_user = keycloak_admin.create_user({"email": "example@example.fr", new_user = keycloak_admin.create_user({"email": "example@example.fr",
"username": "example@example.fr",
"enabled": True,
"firstName": "Example",
"lastName": "Example",
"attributes": {
"locale": ["fr"]
}})
"username": "example@example.fr",
"enabled": True,
"firstName": "Example",
"lastName": "Example",
"attributes": {
"locale": ["fr"]
}})
# User counter # User counter
count_users = keycloak_admin.users_count() count_users = keycloak_admin.users_count()
@ -312,6 +312,25 @@ keycloak_admin.assign_client_role(client_id=client_id, user_id=user_id, role_id=
# Assign realm roles to user # Assign realm roles to user
keycloak_admin.assign_realm_roles(user_id=user_id, roles=realm_roles) keycloak_admin.assign_realm_roles(user_id=user_id, roles=realm_roles)
# Assign realm roles to client's scope
keycloak_admin.assign_realm_roles_to_client_scope(client_id=client_id, roles=realm_roles)
# Get realm roles assigned to client's scope
keycloak_admin.get_realm_roles_of_client_scope(client_id=client_id)
# Remove realm roles assigned to client's scope
keycloak_admin.delete_realm_roles_of_client_scope(client_id=client_id, roles=realm_roles)
another_client_id = keycloak_admin.get_client_id("my-client-2")
# Assign client roles to client's scope
keycloak_admin.assign_client_roles_to_client_scope(client_id=another_client_id, client_roles_owner_id=client_id, roles=client_roles)
# Get client roles assigned to client's scope
keycloak_admin.get_client_roles_of_client_scope(client_id=another_client_id, client_roles_owner_id=client_id)
# Remove client roles assigned to client's scope
keycloak_admin.delete_client_roles_of_client_scope(client_id=another_client_id, client_roles_owner_id=client_id, roles=client_roles)
# Get all ID Providers # Get all ID Providers
idps = keycloak_admin.get_idps() idps = keycloak_admin.get_idps()
@ -319,4 +338,10 @@ idps = keycloak_admin.get_idps()
# Create a new Realm # Create a new Realm
keycloak_admin.create_realm(payload={"realm": "demo"}, skip_exists=False) keycloak_admin.create_realm(payload={"realm": "demo"}, skip_exists=False)
# Changing Realm
keycloak_admin = KeycloakAdmin(realm_name="main", ...)
keycloak_admin.get_users() # Get user in main realm
keycloak_admin.realm_name = "demo" # Change realm to 'demo'
keycloak_admin.get_users() # Get users in realm 'demo'
keycloak_admin.create_user(...) # Creates a new user in 'demo'
``` ```

185
src/keycloak/keycloak_admin.py

@ -726,18 +726,23 @@ class KeycloakAdmin:
data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path)) data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path))
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_user_groups(self, user_id):
def get_user_groups(self, user_id, brief_representation=True):
"""Get user groups. """Get user groups.
Returns a list of groups of which the user is a member Returns a list of groups of which the user is a member
:param user_id: User id :param user_id: User id
:type user_id: str :type user_id: str
:param brief_representation: whether to omit attributes in the response
:type brief_representation: bool
:return: user groups list :return: user groups list
:rtype: list :rtype: list
""" """
params = {"briefRepresentation": brief_representation}
params_path = {"realm-name": self.realm_name, "id": user_id} params_path = {"realm-name": self.realm_name, "id": user_id}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path))
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path), **params
)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def update_user(self, user_id, payload): def update_user(self, user_id, payload):
@ -1562,17 +1567,22 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200]) return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
def get_realm_roles(self):
def get_realm_roles(self, brief_representation=True):
"""Get all roles for the realm or client. """Get all roles for the realm or client.
RoleRepresentation RoleRepresentation
https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
:param brief_representation: whether to omit role attributes in the response
:type brief_representation: bool
:return: Keycloak server response (RoleRepresentation) :return: Keycloak server response (RoleRepresentation)
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name} params_path = {"realm-name": self.realm_name}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path))
params = {"briefRepresentation": brief_representation}
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), **params
)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_realm_role_members(self, role_name, query=None): def get_realm_role_members(self, role_name, query=None):
@ -1592,7 +1602,7 @@ class KeycloakAdmin:
urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query
) )
def get_client_roles(self, client_id):
def get_client_roles(self, client_id, brief_representation=True):
"""Get all roles for the client. """Get all roles for the client.
RoleRepresentation RoleRepresentation
@ -1600,11 +1610,16 @@ class KeycloakAdmin:
:param client_id: id of client (not client-id) :param client_id: id of client (not client-id)
:type client_id: str :type client_id: str
:param brief_representation: whether to omit role attributes in the response
:type brief_representation: bool
:return: Keycloak server response (RoleRepresentation) :return: Keycloak server response (RoleRepresentation)
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name, "id": client_id} params_path = {"realm-name": self.realm_name, "id": client_id}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path))
params = {"briefRepresentation": brief_representation}
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params
)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_client_role(self, client_id, role_name): def get_client_role(self, client_id, role_name):
@ -1617,7 +1632,7 @@ class KeycloakAdmin:
:param client_id: id of client (not client-id) :param client_id: id of client (not client-id)
:type client_id: str :type client_id: str
:param role_name: roles name (not id!)
:param role_name: role's name (not id!)
:type role_name: str :type role_name: str
:return: role_id :return: role_id
:rtype: str :rtype: str
@ -1915,6 +1930,124 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def assign_realm_roles_to_client_scope(self, client_id, roles):
"""Assign realm roles to a client's scope.
:param client_id: id of client (not client-id)
:type client_id: str
:param roles: roles list or role (use RoleRepresentation)
:type roles: list
:return: Keycloak server response
:rtype: dict
"""
payload = roles if isinstance(roles, list) else [roles]
params_path = {"realm-name": self.realm_name, "id": client_id}
data_raw = self.raw_post(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
def delete_realm_roles_of_client_scope(self, client_id, roles):
"""Delete realm roles of a client's scope.
:param client_id: id of client (not client-id)
:type client_id: str
:param roles: roles list or role (use RoleRepresentation)
:type roles: list
:return: Keycloak server response
:rtype: dict
"""
payload = roles if isinstance(roles, list) else [roles]
params_path = {"realm-name": self.realm_name, "id": client_id}
data_raw = self.raw_delete(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
def get_realm_roles_of_client_scope(self, client_id):
"""Get all realm roles for a client's scope.
:param client_id: id of client (not client-id)
:type client_id: str
:return: Keycloak server response (array RoleRepresentation)
:rtype: dict
"""
params_path = {"realm-name": self.realm_name, "id": client_id}
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
def assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles):
"""Assign client roles to a client's scope.
:param client_id: id of client (not client-id) who is assigned the roles
:type client_id: str
:param client_roles_owner_id: id of client (not client-id) who has the roles
:type client_roles_owner_id: str
:param roles: roles list or role (use RoleRepresentation)
:type roles: list
:return: Keycloak server response
:rtype: dict
"""
payload = roles if isinstance(roles, list) else [roles]
params_path = {
"realm-name": self.realm_name,
"id": client_id,
"client": client_roles_owner_id,
}
data_raw = self.raw_post(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
def delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles):
"""Delete client roles of a client's scope.
:param client_id: id of client (not client-id) who is assigned the roles
:type client_id: str
:param client_roles_owner_id: id of client (not client-id) who has the roles
:type client_roles_owner_id: str
:param roles: roles list or role (use RoleRepresentation)
:type roles: list
:return: Keycloak server response
:rtype: dict
"""
payload = roles if isinstance(roles, list) else [roles]
params_path = {
"realm-name": self.realm_name,
"id": client_id,
"client": client_roles_owner_id,
}
data_raw = self.raw_delete(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path),
data=json.dumps(payload),
)
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
def get_client_roles_of_client_scope(self, client_id, client_roles_owner_id):
"""Get all client roles for a client's scope.
:param client_id: id of client (not client-id)
:type client_id: str
:param client_roles_owner_id: id of client (not client-id) who has the roles
:type client_roles_owner_id: str
:return: Keycloak server response (array RoleRepresentation)
:rtype: dict
"""
params_path = {
"realm-name": self.realm_name,
"id": client_id,
"client": client_roles_owner_id,
}
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path)
)
return raise_error_from_response(data_raw, KeycloakGetError)
def assign_realm_roles(self, user_id, roles): def assign_realm_roles(self, user_id, roles):
"""Assign realm roles to a user. """Assign realm roles to a user.
@ -1977,17 +2110,20 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def get_composite_realm_roles_of_user(self, user_id):
def get_composite_realm_roles_of_user(self, user_id, brief_representation=True):
"""Get all composite (i.e. implicit) realm roles for a user. """Get all composite (i.e. implicit) realm roles for a user.
:param user_id: id of user :param user_id: id of user
:type user_id: str :type user_id: str
:param brief_representation: whether to omit role attributes in the response
:type brief_representation: bool
:return: Keycloak server response (array RoleRepresentation) :return: Keycloak server response (array RoleRepresentation)
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name, "id": user_id} params_path = {"realm-name": self.realm_name, "id": user_id}
params = {"briefRepresentation": brief_representation}
data_raw = self.raw_get( data_raw = self.raw_get(
urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path)
urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params
) )
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
@ -2027,16 +2163,21 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204]) return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
def get_group_realm_roles(self, group_id):
def get_group_realm_roles(self, group_id, brief_representation=True):
"""Get all realm roles for a group. """Get all realm roles for a group.
:param group_id: id of the group :param group_id: id of the group
:type group_id: str :type group_id: str
:param brief_representation: whether to omit role attributes in the response
:type brief_representation: bool
:return: Keycloak server response (array RoleRepresentation) :return: Keycloak server response (array RoleRepresentation)
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name, "id": group_id} params_path = {"realm-name": self.realm_name, "id": group_id}
data_raw = self.raw_get(urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path))
params = {"briefRepresentation": brief_representation}
data_raw = self.raw_get(
urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params
)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def assign_group_client_roles(self, group_id, client_id, roles): def assign_group_client_roles(self, group_id, client_id, roles):
@ -2121,21 +2262,26 @@ class KeycloakAdmin:
urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id
) )
def get_composite_client_roles_of_user(self, user_id, client_id):
def get_composite_client_roles_of_user(self, user_id, client_id, brief_representation=False):
"""Get composite client role-mappings for a user. """Get composite client role-mappings for a user.
:param user_id: id of user :param user_id: id of user
:type user_id: str :type user_id: str
:param client_id: id of client (not client-id) :param client_id: id of client (not client-id)
:type client_id: str :type client_id: str
:param brief_representation: whether to omit attributes in the response
:type brief_representation: bool
:return: Keycloak server response (array RoleRepresentation) :return: Keycloak server response (array RoleRepresentation)
:rtype: list :rtype: list
""" """
params = {"briefRepresentation": brief_representation}
return self._get_client_roles_of_user( return self._get_client_roles_of_user(
urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id
urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id, **params
) )
def _get_client_roles_of_user(self, client_level_role_mapping_url, user_id, client_id):
def _get_client_roles_of_user(
self, client_level_role_mapping_url, user_id, client_id, **params
):
"""Get client roles of a single user helper. """Get client roles of a single user helper.
:param client_level_role_mapping_url: Url for the client role mapping :param client_level_role_mapping_url: Url for the client role mapping
@ -2144,11 +2290,13 @@ class KeycloakAdmin:
:type user_id: str :type user_id: str
:param client_id: Client id :param client_id: Client id
:type client_id: str :type client_id: str
:param params: Additional parameters
:type params: dict
:returns: Client roles of a user :returns: Client roles of a user
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id} params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path))
data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path), **params)
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)
def delete_client_roles_of_user(self, user_id, client_id, roles): def delete_client_roles_of_user(self, user_id, client_id, roles):
@ -3352,19 +3500,22 @@ class KeycloakAdmin:
) )
return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201]) return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
def get_composite_client_roles_of_group(self, client_id, group_id):
def get_composite_client_roles_of_group(self, client_id, group_id, brief_representation=True):
"""Get the composite client roles of the given group for the given client. """Get the composite client roles of the given group for the given client.
:param client_id: id of the client. :param client_id: id of the client.
:type client_id: str :type client_id: str
:param group_id: id of the group. :param group_id: id of the group.
:type group_id: str :type group_id: str
:param brief_representation: whether to omit attributes in the response
:type brief_representation: bool
:return: the composite client roles of the group (list of RoleRepresentation). :return: the composite client roles of the group (list of RoleRepresentation).
:rtype: list :rtype: list
""" """
params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id} params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
params = {"briefRepresentation": brief_representation}
data_raw = self.raw_get( data_raw = self.raw_get(
urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path)
urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params
) )
return raise_error_from_response(data_raw, KeycloakGetError) return raise_error_from_response(data_raw, KeycloakGetError)

17
src/keycloak/keycloak_openid.py

@ -334,7 +334,15 @@ class KeycloakOpenID:
data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload)
return raise_error_from_response(data_raw, KeycloakPostError) return raise_error_from_response(data_raw, KeycloakPostError)
def exchange_token(self, token: str, client_id: str, audience: str, subject: str) -> dict:
def exchange_token(
self,
token: str,
client_id: str,
audience: str,
subject: str,
requested_token_type: str = "urn:ietf:params:oauth:token-type:refresh_token",
scope: str = "",
) -> dict:
"""Exchange user token. """Exchange user token.
Use a token to obtain an entirely different token. See Use a token to obtain an entirely different token. See
@ -348,6 +356,10 @@ class KeycloakOpenID:
:type audience: str :type audience: str
:param subject: Subject :param subject: Subject
:type subject: str :type subject: str
:param requested_token_type: Token type specification
:type requested_token_type: str
:param scope: Scope
:type scope: str
:returns: Exchanged token :returns: Exchanged token
:rtype: dict :rtype: dict
""" """
@ -356,9 +368,10 @@ class KeycloakOpenID:
"grant_type": ["urn:ietf:params:oauth:grant-type:token-exchange"], "grant_type": ["urn:ietf:params:oauth:grant-type:token-exchange"],
"client_id": client_id, "client_id": client_id,
"subject_token": token, "subject_token": token,
"requested_token_type": "urn:ietf:params:oauth:token-type:refresh_token",
"requested_token_type": requested_token_type,
"audience": audience, "audience": audience,
"requested_subject": subject, "requested_subject": subject,
"scope": scope,
} }
payload = self._add_secret_key(payload) payload = self._add_secret_key(payload)
data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload) data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path), data=payload)

4
src/keycloak/urls_patterns.py

@ -91,6 +91,10 @@ URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE = URL_ADMIN_CLIENT_ROLE + "/composi
URL_ADMIN_CLIENT_ROLE_MEMBERS = URL_ADMIN_CLIENT + "/roles/{role-name}/users" URL_ADMIN_CLIENT_ROLE_MEMBERS = URL_ADMIN_CLIENT + "/roles/{role-name}/users"
URL_ADMIN_CLIENT_ROLE_GROUPS = URL_ADMIN_CLIENT + "/roles/{role-name}/groups" URL_ADMIN_CLIENT_ROLE_GROUPS = URL_ADMIN_CLIENT + "/roles/{role-name}/groups"
URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS = URL_ADMIN_CLIENT + "/management/permissions" URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS = URL_ADMIN_CLIENT + "/management/permissions"
URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES = URL_ADMIN_CLIENT + "/scope-mappings/realm"
URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES = (
URL_ADMIN_CLIENT + "/scope-mappings/clients/{client}"
)
URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT + "/authz/resource-server/settings" URL_ADMIN_CLIENT_AUTHZ_SETTINGS = URL_ADMIN_CLIENT + "/authz/resource-server/settings"
URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT + "/authz/resource-server/resource?max=-1" URL_ADMIN_CLIENT_AUTHZ_RESOURCES = URL_ADMIN_CLIENT + "/authz/resource-server/resource?max=-1"

199
tests/test_keycloak_admin.py

@ -1100,6 +1100,205 @@ def test_realm_roles(admin: KeycloakAdmin, realm: str):
assert err.match('404: b\'{"error":"Could not find role"}\'') assert err.match('404: b\'{"error":"Could not find role"}\'')
@pytest.mark.parametrize(
"testcase, arg_brief_repr, includes_attributes",
[
("brief True", {"brief_representation": True}, False),
("brief False", {"brief_representation": False}, True),
("default", {}, False),
],
)
def test_role_attributes(
admin: KeycloakAdmin,
realm: str,
client: str,
arg_brief_repr: dict,
includes_attributes: bool,
testcase: str,
):
"""Test getting role attributes for bulk calls.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
:param arg_brief_repr: Brief representation
:type arg_brief_repr: dict
:param includes_attributes: Indicator whether to include attributes
:type includes_attributes: bool
:param testcase: Test case
:type testcase: str
"""
# setup
attribute_role = "test-realm-role-w-attr"
test_attrs = {"attr1": ["val1"], "attr2": ["val2-1", "val2-2"]}
role_id = admin.create_realm_role(
payload={"name": attribute_role, "attributes": test_attrs},
skip_exists=True,
)
assert role_id, role_id
cli_role_id = admin.create_client_role(
client,
payload={"name": attribute_role, "attributes": test_attrs},
skip_exists=True,
)
assert cli_role_id, cli_role_id
if not includes_attributes:
test_attrs = None
# tests
roles = admin.get_realm_roles(**arg_brief_repr)
roles_filtered = [role for role in roles if role["name"] == role_id]
assert roles_filtered, roles_filtered
role = roles_filtered[0]
assert role.get("attributes") == test_attrs, testcase
roles = admin.get_client_roles(client, **arg_brief_repr)
roles_filtered = [role for role in roles if role["name"] == cli_role_id]
assert roles_filtered, roles_filtered
role = roles_filtered[0]
assert role.get("attributes") == test_attrs, testcase
# cleanup
res = admin.delete_realm_role(role_name=attribute_role)
assert res == dict(), res
res = admin.delete_client_role(client, role_name=attribute_role)
assert res == dict(), res
def test_client_scope_realm_roles(admin: KeycloakAdmin, realm: str):
"""Test client realm roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
"""
admin.realm_name = realm
# Test get realm roles
roles = admin.get_realm_roles()
assert len(roles) == 3, roles
role_names = [x["name"] for x in roles]
assert "uma_authorization" in role_names, role_names
assert "offline_access" in role_names, role_names
# create realm role for test
role_id = admin.create_realm_role(payload={"name": "test-realm-role"}, skip_exists=True)
assert role_id, role_id
# Test realm role client assignment
client_id = admin.create_client(
payload={"name": "role-testing-client", "clientId": "role-testing-client"}
)
with pytest.raises(KeycloakPostError) as err:
admin.assign_realm_roles_to_client_scope(client_id=client_id, roles=["bad"])
assert err.match('500: b\'{"error":"unknown_error"}\'')
res = admin.assign_realm_roles_to_client_scope(
client_id=client_id,
roles=[
admin.get_realm_role(role_name="offline_access"),
admin.get_realm_role(role_name="test-realm-role"),
],
)
assert res == dict(), res
roles = admin.get_realm_roles_of_client_scope(client_id=client_id)
assert len(roles) == 2
client_role_names = [x["name"] for x in roles]
assert "offline_access" in client_role_names, client_role_names
assert "test-realm-role" in client_role_names, client_role_names
assert "uma_authorization" not in client_role_names, client_role_names
# Test remove realm role of client
with pytest.raises(KeycloakDeleteError) as err:
admin.delete_realm_roles_of_client_scope(client_id=client_id, roles=["bad"])
assert err.match('500: b\'{"error":"unknown_error"}\'')
res = admin.delete_realm_roles_of_client_scope(
client_id=client_id, roles=[admin.get_realm_role(role_name="offline_access")]
)
assert res == dict(), res
roles = admin.get_realm_roles_of_client_scope(client_id=client_id)
assert len(roles) == 1
assert "test-realm-role" in [x["name"] for x in roles]
res = admin.delete_realm_roles_of_client_scope(
client_id=client_id, roles=[admin.get_realm_role(role_name="test-realm-role")]
)
assert res == dict(), res
roles = admin.get_realm_roles_of_client_scope(client_id=client_id)
assert len(roles) == 0
def test_client_scope_client_roles(admin: KeycloakAdmin, realm: str, client: str):
"""Test client assignment of other client roles.
:param admin: Keycloak admin
:type admin: KeycloakAdmin
:param realm: Keycloak realm
:type realm: str
:param client: Keycloak client
:type client: str
"""
admin.realm_name = realm
client_id = admin.create_client(
payload={"name": "role-testing-client", "clientId": "role-testing-client"}
)
# Test get client roles
roles = admin.get_client_roles_of_client_scope(client_id, client)
assert len(roles) == 0, roles
# create client role for test
client_role_id = admin.create_client_role(
client_role_id=client, payload={"name": "client-role-test"}, skip_exists=True
)
assert client_role_id, client_role_id
# Test client role assignment to other client
with pytest.raises(KeycloakPostError) as err:
admin.assign_client_roles_to_client_scope(
client_id=client_id, client_roles_owner_id=client, roles=["bad"]
)
assert err.match('500: b\'{"error":"unknown_error"}\'')
res = admin.assign_client_roles_to_client_scope(
client_id=client_id,
client_roles_owner_id=client,
roles=[admin.get_client_role(client_id=client, role_name="client-role-test")],
)
assert res == dict(), res
roles = admin.get_client_roles_of_client_scope(
client_id=client_id, client_roles_owner_id=client
)
assert len(roles) == 1
client_role_names = [x["name"] for x in roles]
assert "client-role-test" in client_role_names, client_role_names
# Test remove realm role of client
with pytest.raises(KeycloakDeleteError) as err:
admin.delete_client_roles_of_client_scope(
client_id=client_id, client_roles_owner_id=client, roles=["bad"]
)
assert err.match('500: b\'{"error":"unknown_error"}\'')
res = admin.delete_client_roles_of_client_scope(
client_id=client_id,
client_roles_owner_id=client,
roles=[admin.get_client_role(client_id=client, role_name="client-role-test")],
)
assert res == dict(), res
roles = admin.get_client_roles_of_client_scope(
client_id=client_id, client_roles_owner_id=client
)
assert len(roles) == 0
def test_client_roles(admin: KeycloakAdmin, client: str): def test_client_roles(admin: KeycloakAdmin, client: str):
"""Test client roles. """Test client roles.

Loading…
Cancel
Save