You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2812 lines
109 KiB

7 years ago
6 years ago
6 years ago
4 years ago
6 years ago
6 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
3 years ago
3 years ago
3 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
3 years ago
3 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
  1. # -*- coding: utf-8 -*-
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com>
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy of
  8. # this software and associated documentation files (the "Software"), to deal in
  9. # the Software without restriction, including without limitation the rights to
  10. # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  11. # the Software, and to permit persons to whom the Software is furnished to do so,
  12. # subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in all
  15. # copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  19. # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  20. # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  21. # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. # Unless otherwise stated in the comments, "id", in e.g. user_id, refers to the
  24. # internal Keycloak server ID, usually a uuid string
  25. """The keycloak admin module."""
  26. import json
  27. from builtins import isinstance
  28. from typing import Iterable
  29. from . import urls_patterns
  30. from .connection import ConnectionManager
  31. from .exceptions import (
  32. KeycloakDeleteError,
  33. KeycloakGetError,
  34. KeycloakPostError,
  35. KeycloakPutError,
  36. raise_error_from_response,
  37. )
  38. from .keycloak_openid import KeycloakOpenID
  39. class KeycloakAdmin:
  40. """Keycloak Admin client.
  41. :param server_url: Keycloak server url
  42. :param username: admin username
  43. :param password: admin password
  44. :param totp: Time based OTP
  45. :param realm_name: realm name
  46. :param client_id: client id
  47. :param verify: True if want check connection SSL
  48. :param client_secret_key: client secret key
  49. (optional, required only for access type confidential)
  50. :param custom_headers: dict of custom header to pass to each HTML request
  51. :param user_realm_name: The realm name of the user, if different from realm_name
  52. :param auto_refresh_token: list of methods that allows automatic token refresh.
  53. Ex: ['get', 'put', 'post', 'delete']
  54. :param timeout: connection timeout in seconds
  55. """
  56. PAGE_SIZE = 100
  57. _server_url = None
  58. _username = None
  59. _password = None
  60. _totp = None
  61. _realm_name = None
  62. _client_id = None
  63. _verify = None
  64. _client_secret_key = None
  65. _auto_refresh_token = None
  66. _connection = None
  67. _token = None
  68. _custom_headers = None
  69. _user_realm_name = None
  70. def __init__(
  71. self,
  72. server_url,
  73. username=None,
  74. password=None,
  75. totp=None,
  76. realm_name="master",
  77. client_id="admin-cli",
  78. verify=True,
  79. client_secret_key=None,
  80. custom_headers=None,
  81. user_realm_name=None,
  82. auto_refresh_token=None,
  83. timeout=60,
  84. ):
  85. """Init method."""
  86. self.server_url = server_url
  87. self.username = username
  88. self.password = password
  89. self.totp = totp
  90. self.realm_name = realm_name
  91. self.client_id = client_id
  92. self.verify = verify
  93. self.client_secret_key = client_secret_key
  94. self.auto_refresh_token = auto_refresh_token or []
  95. self.user_realm_name = user_realm_name
  96. self.custom_headers = custom_headers
  97. self.timeout = timeout
  98. # Get token Admin
  99. self.get_token()
  100. @property
  101. def server_url(self):
  102. """Get server url."""
  103. return self._server_url
  104. @server_url.setter
  105. def server_url(self, value):
  106. self._server_url = value
  107. @property
  108. def realm_name(self):
  109. """Get realm name."""
  110. return self._realm_name
  111. @realm_name.setter
  112. def realm_name(self, value):
  113. self._realm_name = value
  114. @property
  115. def connection(self):
  116. """Get connection."""
  117. return self._connection
  118. @connection.setter
  119. def connection(self, value):
  120. self._connection = value
  121. @property
  122. def client_id(self):
  123. """Get client id."""
  124. return self._client_id
  125. @client_id.setter
  126. def client_id(self, value):
  127. self._client_id = value
  128. @property
  129. def client_secret_key(self):
  130. """Get client secret key."""
  131. return self._client_secret_key
  132. @client_secret_key.setter
  133. def client_secret_key(self, value):
  134. self._client_secret_key = value
  135. @property
  136. def verify(self):
  137. """Get verify."""
  138. return self._verify
  139. @verify.setter
  140. def verify(self, value):
  141. self._verify = value
  142. @property
  143. def username(self):
  144. """Get username."""
  145. return self._username
  146. @username.setter
  147. def username(self, value):
  148. self._username = value
  149. @property
  150. def password(self):
  151. """Get password."""
  152. return self._password
  153. @password.setter
  154. def password(self, value):
  155. self._password = value
  156. @property
  157. def totp(self):
  158. """Get totp."""
  159. return self._totp
  160. @totp.setter
  161. def totp(self, value):
  162. self._totp = value
  163. @property
  164. def token(self):
  165. """Get token."""
  166. return self._token
  167. @token.setter
  168. def token(self, value):
  169. self._token = value
  170. @property
  171. def auto_refresh_token(self):
  172. """Get auto refresh token."""
  173. return self._auto_refresh_token
  174. @property
  175. def user_realm_name(self):
  176. """Get user realm name."""
  177. return self._user_realm_name
  178. @user_realm_name.setter
  179. def user_realm_name(self, value):
  180. self._user_realm_name = value
  181. @property
  182. def custom_headers(self):
  183. """Get custom headers."""
  184. return self._custom_headers
  185. @custom_headers.setter
  186. def custom_headers(self, value):
  187. self._custom_headers = value
  188. @auto_refresh_token.setter
  189. def auto_refresh_token(self, value):
  190. allowed_methods = {"get", "post", "put", "delete"}
  191. if not isinstance(value, Iterable):
  192. raise TypeError(
  193. "Expected a list of strings among {allowed}".format(allowed=allowed_methods)
  194. )
  195. if not all(method in allowed_methods for method in value):
  196. raise TypeError(
  197. "Unexpected method in auto_refresh_token, accepted methods are {allowed}".format(
  198. allowed=allowed_methods
  199. )
  200. )
  201. self._auto_refresh_token = value
  202. def __fetch_all(self, url, query=None):
  203. """Paginate over get requests.
  204. Wrapper function to paginate GET requests.
  205. :param url: The url on which the query is executed
  206. :param query: Existing query parameters (optional)
  207. :return: Combined results of paginated queries
  208. """
  209. results = []
  210. # initalize query if it was called with None
  211. if not query:
  212. query = {}
  213. page = 0
  214. query["max"] = self.PAGE_SIZE
  215. # fetch until we can
  216. while True:
  217. query["first"] = page * self.PAGE_SIZE
  218. partial_results = raise_error_from_response(
  219. self.raw_get(url, **query), KeycloakGetError
  220. )
  221. if not partial_results:
  222. break
  223. results.extend(partial_results)
  224. if len(partial_results) < query["max"]:
  225. break
  226. page += 1
  227. return results
  228. def __fetch_paginated(self, url, query=None):
  229. query = query or {}
  230. return raise_error_from_response(self.raw_get(url, **query), KeycloakGetError)
  231. def import_realm(self, payload):
  232. """Import a new realm from a RealmRepresentation.
  233. Realm name must be unique.
  234. RealmRepresentation
  235. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  236. :param payload: RealmRepresentation
  237. :return: RealmRepresentation
  238. """
  239. data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload))
  240. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  241. def export_realm(self, export_clients=False, export_groups_and_role=False):
  242. """Export the realm configurations in the json format.
  243. RealmRepresentation
  244. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_partialexport
  245. :param export-clients: Skip if not want to export realm clients
  246. :param export-groups-and-roles: Skip if not want to export realm groups and roles
  247. :return: realm configurations JSON
  248. """
  249. params_path = {
  250. "realm-name": self.realm_name,
  251. "export-clients": export_clients,
  252. "export-groups-and-roles": export_groups_and_role,
  253. }
  254. data_raw = self.raw_post(
  255. urls_patterns.URL_ADMIN_REALM_EXPORT.format(**params_path), data=""
  256. )
  257. return raise_error_from_response(data_raw, KeycloakPostError)
  258. def get_realms(self):
  259. """List all realms in Keycloak deployment.
  260. :return: realms list
  261. """
  262. data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALMS)
  263. return raise_error_from_response(data_raw, KeycloakGetError)
  264. def get_realm(self, realm_name):
  265. """Get a specific realm.
  266. RealmRepresentation:
  267. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
  268. :param realm_name: Realm name (not the realm id)
  269. :return: RealmRepresentation
  270. """
  271. params_path = {"realm-name": realm_name}
  272. data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path))
  273. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  274. def create_realm(self, payload, skip_exists=False):
  275. """Create a realm.
  276. RealmRepresentation:
  277. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  278. :param payload: RealmRepresentation
  279. :param skip_exists: Skip if Realm already exist.
  280. :return: Keycloak server response (RealmRepresentation)
  281. """
  282. data_raw = self.raw_post(urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload))
  283. return raise_error_from_response(
  284. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  285. )
  286. def update_realm(self, realm_name, payload):
  287. """Update a realm.
  288. This wil only update top level attributes and will ignore any user,
  289. role, or client information in the payload.
  290. RealmRepresentation:
  291. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  292. :param realm_name: Realm name (not the realm id)
  293. :param payload: RealmRepresentation
  294. :return: Http response
  295. """
  296. params_path = {"realm-name": realm_name}
  297. data_raw = self.raw_put(
  298. urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload)
  299. )
  300. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  301. def delete_realm(self, realm_name):
  302. """Delete a realm.
  303. :param realm_name: Realm name (not the realm id)
  304. :return: Http response
  305. """
  306. params_path = {"realm-name": realm_name}
  307. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path))
  308. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  309. def get_users(self, query=None):
  310. """Get all users.
  311. Return a list of users, filtered according to query parameters
  312. UserRepresentation
  313. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  314. :param query: Query parameters (optional)
  315. :return: users list
  316. """
  317. query = query or {}
  318. params_path = {"realm-name": self.realm_name}
  319. url = urls_patterns.URL_ADMIN_USERS.format(**params_path)
  320. if "first" in query or "max" in query:
  321. return self.__fetch_paginated(url, query)
  322. return self.__fetch_all(url, query)
  323. def create_idp(self, payload):
  324. """Create an ID Provider.
  325. IdentityProviderRepresentation
  326. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
  327. :param: payload: IdentityProviderRepresentation
  328. """
  329. params_path = {"realm-name": self.realm_name}
  330. data_raw = self.raw_post(
  331. urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload)
  332. )
  333. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  334. def update_idp(self, idp_alias, payload):
  335. """Update an ID Provider.
  336. IdentityProviderRepresentation
  337. https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_identity_providers_resource
  338. :param: alias: alias for IdP to update
  339. :param: payload: The IdentityProviderRepresentation
  340. """
  341. params_path = {"realm-name": self.realm_name, "alias": idp_alias}
  342. data_raw = self.raw_put(
  343. urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload)
  344. )
  345. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  346. def add_mapper_to_idp(self, idp_alias, payload):
  347. """Create an ID Provider.
  348. IdentityProviderRepresentation
  349. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityprovidermapperrepresentation
  350. :param: idp_alias: alias for Idp to add mapper in
  351. :param: payload: IdentityProviderMapperRepresentation
  352. """
  353. params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias}
  354. data_raw = self.raw_post(
  355. urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload)
  356. )
  357. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  358. def update_mapper_in_idp(self, idp_alias, mapper_id, payload):
  359. """Update an IdP mapper.
  360. IdentityProviderMapperRepresentation
  361. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_update
  362. :param: idp_alias: alias for Idp to fetch mappers
  363. :param: mapper_id: Mapper Id to update
  364. :param: payload: IdentityProviderMapperRepresentation
  365. :return: Http response
  366. """
  367. params_path = {
  368. "realm-name": self.realm_name,
  369. "idp-alias": idp_alias,
  370. "mapper-id": mapper_id,
  371. }
  372. data_raw = self.raw_put(
  373. urls_patterns.URL_ADMIN_IDP_MAPPER_UPDATE.format(**params_path),
  374. data=json.dumps(payload),
  375. )
  376. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  377. def get_idp_mappers(self, idp_alias):
  378. """Get IDP mappers.
  379. Returns a list of ID Providers mappers
  380. IdentityProviderMapperRepresentation
  381. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmappers
  382. :param: idp_alias: alias for Idp to fetch mappers
  383. :return: array IdentityProviderMapperRepresentation
  384. """
  385. params_path = {"realm-name": self.realm_name, "idp-alias": idp_alias}
  386. data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path))
  387. return raise_error_from_response(data_raw, KeycloakGetError)
  388. def get_idps(self):
  389. """Get IDPs.
  390. Returns a list of ID Providers,
  391. IdentityProviderRepresentation
  392. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
  393. :return: array IdentityProviderRepresentation
  394. """
  395. params_path = {"realm-name": self.realm_name}
  396. data_raw = self.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path))
  397. return raise_error_from_response(data_raw, KeycloakGetError)
  398. def delete_idp(self, idp_alias):
  399. """Delete an ID Provider.
  400. :param: idp_alias: idp alias name
  401. """
  402. params_path = {"realm-name": self.realm_name, "alias": idp_alias}
  403. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path))
  404. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  405. def create_user(self, payload, exist_ok=False):
  406. """Create a new user.
  407. Username must be unique
  408. UserRepresentation
  409. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  410. :param payload: UserRepresentation
  411. :param exist_ok: If False, raise KeycloakGetError if username already exists.
  412. Otherwise, return existing user ID.
  413. :return: UserRepresentation
  414. """
  415. params_path = {"realm-name": self.realm_name}
  416. if exist_ok:
  417. exists = self.get_user_id(username=payload["username"])
  418. if exists is not None:
  419. return str(exists)
  420. data_raw = self.raw_post(
  421. urls_patterns.URL_ADMIN_USERS.format(**params_path), data=json.dumps(payload)
  422. )
  423. raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  424. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  425. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  426. def users_count(self, query=None):
  427. """Count users.
  428. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_users_resource
  429. :param query: (dict) Query parameters for users count
  430. :return: counter
  431. """
  432. query = query or dict()
  433. params_path = {"realm-name": self.realm_name}
  434. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query)
  435. return raise_error_from_response(data_raw, KeycloakGetError)
  436. def get_user_id(self, username):
  437. """Get internal keycloak user id from username.
  438. This is required for further actions against this user.
  439. UserRepresentation
  440. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  441. :param username: id in UserRepresentation
  442. :return: user_id
  443. """
  444. lower_user_name = username.lower()
  445. users = self.get_users(query={"search": lower_user_name})
  446. return next((user["id"] for user in users if user["username"] == lower_user_name), None)
  447. def get_user(self, user_id):
  448. """Get representation of the user.
  449. :param user_id: User id
  450. UserRepresentation
  451. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  452. :return: UserRepresentation
  453. """
  454. params_path = {"realm-name": self.realm_name, "id": user_id}
  455. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path))
  456. return raise_error_from_response(data_raw, KeycloakGetError)
  457. def get_user_groups(self, user_id):
  458. """Get user groups.
  459. Returns a list of groups of which the user is a member
  460. :param user_id: User id
  461. :return: user groups list
  462. """
  463. params_path = {"realm-name": self.realm_name, "id": user_id}
  464. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path))
  465. return raise_error_from_response(data_raw, KeycloakGetError)
  466. def update_user(self, user_id, payload):
  467. """Update the user.
  468. :param user_id: User id
  469. :param payload: UserRepresentation
  470. :return: Http response
  471. """
  472. params_path = {"realm-name": self.realm_name, "id": user_id}
  473. data_raw = self.raw_put(
  474. urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload)
  475. )
  476. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  477. def delete_user(self, user_id):
  478. """Delete the user.
  479. :param user_id: User id
  480. :return: Http response
  481. """
  482. params_path = {"realm-name": self.realm_name, "id": user_id}
  483. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_USER.format(**params_path))
  484. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  485. def set_user_password(self, user_id, password, temporary=True):
  486. """Set up a password for the user.
  487. If temporary is True, the user will have to reset
  488. the temporary password next time they log in.
  489. https://www.keycloak.org/docs-api/18.0/rest-api/#_users_resource
  490. https://www.keycloak.org/docs-api/18.0/rest-api/#_credentialrepresentation
  491. :param user_id: User id
  492. :param password: New password
  493. :param temporary: True if password is temporary
  494. :return:
  495. """
  496. payload = {"type": "password", "temporary": temporary, "value": password}
  497. params_path = {"realm-name": self.realm_name, "id": user_id}
  498. data_raw = self.raw_put(
  499. urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload)
  500. )
  501. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  502. def get_credentials(self, user_id):
  503. """Get user credentials.
  504. Returns a list of credential belonging to the user.
  505. CredentialRepresentation
  506. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_credentialrepresentation
  507. :param: user_id: user id
  508. :return: Keycloak server response (CredentialRepresentation)
  509. """
  510. params_path = {"realm-name": self.realm_name, "id": user_id}
  511. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path))
  512. return raise_error_from_response(data_raw, KeycloakGetError)
  513. def delete_credential(self, user_id, credential_id):
  514. """Delete credential of the user.
  515. CredentialRepresentation
  516. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_credentialrepresentation
  517. :param: user_id: user id
  518. :param: credential_id: credential id
  519. :return: Keycloak server response (ClientRepresentation)
  520. """
  521. params_path = {
  522. "realm-name": self.realm_name,
  523. "id": user_id,
  524. "credential_id": credential_id,
  525. }
  526. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path))
  527. return raise_error_from_response(data_raw, KeycloakDeleteError)
  528. def user_logout(self, user_id):
  529. """Log out the user.
  530. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_logout
  531. :param user_id: User id
  532. :return:
  533. """
  534. params_path = {"realm-name": self.realm_name, "id": user_id}
  535. data_raw = self.raw_post(
  536. urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data=""
  537. )
  538. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  539. def user_consents(self, user_id):
  540. """Get consents granted by the user.
  541. UserConsentRepresentation
  542. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userconsentrepresentation
  543. :param user_id: User id
  544. :return: List of UserConsentRepresentations
  545. """
  546. params_path = {"realm-name": self.realm_name, "id": user_id}
  547. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path))
  548. return raise_error_from_response(data_raw, KeycloakGetError)
  549. def get_user_social_logins(self, user_id):
  550. """Get user social logins.
  551. Returns a list of federated identities/social logins of which the user has been associated
  552. with
  553. :param user_id: User id
  554. :return: federated identities list
  555. """
  556. params_path = {"realm-name": self.realm_name, "id": user_id}
  557. data_raw = self.raw_get(
  558. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path)
  559. )
  560. return raise_error_from_response(data_raw, KeycloakGetError)
  561. def add_user_social_login(self, user_id, provider_id, provider_userid, provider_username):
  562. """Add a federated identity / social login provider to the user.
  563. :param user_id: User id
  564. :param provider_id: Social login provider id
  565. :param provider_userid: userid specified by the provider
  566. :param provider_username: username specified by the provider
  567. :return:
  568. """
  569. payload = {
  570. "identityProvider": provider_id,
  571. "userId": provider_userid,
  572. "userName": provider_username,
  573. }
  574. params_path = {"realm-name": self.realm_name, "id": user_id, "provider": provider_id}
  575. data_raw = self.raw_post(
  576. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path),
  577. data=json.dumps(payload),
  578. )
  579. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201, 204])
  580. def delete_user_social_login(self, user_id, provider_id):
  581. """Delete a federated identity / social login provider from the user.
  582. :param user_id: User id
  583. :param provider_id: Social login provider id
  584. :return:
  585. """
  586. params_path = {"realm-name": self.realm_name, "id": user_id, "provider": provider_id}
  587. data_raw = self.raw_delete(
  588. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path)
  589. )
  590. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  591. def send_update_account(
  592. self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None
  593. ):
  594. """Send an update account email to the user.
  595. An email contains a link the user can click to perform a set of required actions.
  596. :param user_id: User id
  597. :param payload: A list of actions for the user to complete
  598. :param client_id: Client id (optional)
  599. :param lifespan: Number of seconds after which the generated token expires (optional)
  600. :param redirect_uri: The redirect uri (optional)
  601. :return:
  602. """
  603. params_path = {"realm-name": self.realm_name, "id": user_id}
  604. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  605. data_raw = self.raw_put(
  606. urls_patterns.URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  607. data=json.dumps(payload),
  608. **params_query
  609. )
  610. return raise_error_from_response(data_raw, KeycloakPutError)
  611. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  612. """Send a update account email to the user.
  613. An email contains a link the user can click to perform a set of required actions.
  614. :param user_id: User id
  615. :param client_id: Client id (optional)
  616. :param redirect_uri: Redirect uri (optional)
  617. :return:
  618. """
  619. params_path = {"realm-name": self.realm_name, "id": user_id}
  620. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  621. data_raw = self.raw_put(
  622. urls_patterns.URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  623. data={},
  624. **params_query
  625. )
  626. return raise_error_from_response(data_raw, KeycloakPutError)
  627. def get_sessions(self, user_id):
  628. """Get sessions associated with the user.
  629. :param user_id: id of user
  630. UserSessionRepresentation
  631. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_usersessionrepresentation
  632. :return: UserSessionRepresentation
  633. """
  634. params_path = {"realm-name": self.realm_name, "id": user_id}
  635. data_raw = self.raw_get(urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path))
  636. return raise_error_from_response(data_raw, KeycloakGetError)
  637. def get_server_info(self):
  638. """Get themes, social providers, auth providers, and event listeners available on this server.
  639. ServerInfoRepresentation
  640. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_serverinforepresentation
  641. :return: ServerInfoRepresentation
  642. """
  643. data_raw = self.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO)
  644. return raise_error_from_response(data_raw, KeycloakGetError)
  645. def get_groups(self, query=None):
  646. """Get groups.
  647. Returns a list of groups belonging to the realm
  648. GroupRepresentation
  649. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  650. :return: array GroupRepresentation
  651. """
  652. query = query or {}
  653. params_path = {"realm-name": self.realm_name}
  654. url = urls_patterns.URL_ADMIN_GROUPS.format(**params_path)
  655. if "first" in query or "max" in query:
  656. return self.__fetch_paginated(url, query)
  657. return self.__fetch_all(url, query)
  658. def get_group(self, group_id):
  659. """Get group by id.
  660. Returns full group details
  661. GroupRepresentation
  662. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  663. :param group_id: The group id
  664. :return: Keycloak server response (GroupRepresentation)
  665. """
  666. params_path = {"realm-name": self.realm_name, "id": group_id}
  667. data_raw = self.raw_get(urls_patterns.URL_ADMIN_GROUP.format(**params_path))
  668. return raise_error_from_response(data_raw, KeycloakGetError)
  669. def get_subgroups(self, group, path):
  670. """Get subgroups.
  671. Utility function to iterate through nested group structures
  672. GroupRepresentation
  673. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  674. :param name: group (GroupRepresentation)
  675. :param path: group path (string)
  676. :return: Keycloak server response (GroupRepresentation)
  677. """
  678. for subgroup in group["subGroups"]:
  679. if subgroup["path"] == path:
  680. return subgroup
  681. elif subgroup["subGroups"]:
  682. for subgroup in group["subGroups"]:
  683. result = self.get_subgroups(subgroup, path)
  684. if result:
  685. return result
  686. # went through the tree without hits
  687. return None
  688. def get_group_members(self, group_id, query=None):
  689. """Get members by group id.
  690. Returns group members
  691. GroupRepresentation
  692. https://www.keycloak.org/docs-api/18.0/rest-api/#_userrepresentation
  693. :param group_id: The group id
  694. :param query: Additional query parameters
  695. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmembers)
  696. :return: Keycloak server response (UserRepresentation)
  697. """
  698. query = query or {}
  699. params_path = {"realm-name": self.realm_name, "id": group_id}
  700. url = urls_patterns.URL_ADMIN_GROUP_MEMBERS.format(**params_path)
  701. if "first" in query or "max" in query:
  702. return self.__fetch_paginated(url, query)
  703. return self.__fetch_all(url, query)
  704. def get_group_by_path(self, path, search_in_subgroups=False):
  705. """Get group id based on name or path.
  706. A straight name or path match with a top-level group will return first.
  707. Subgroups are traversed, the first to match path (or name with path) is returned.
  708. GroupRepresentation
  709. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  710. :param path: group path
  711. :param search_in_subgroups: True if want search in the subgroups
  712. :return: Keycloak server response (GroupRepresentation)
  713. """
  714. groups = self.get_groups()
  715. # TODO: Review this code is necessary
  716. for group in groups:
  717. if group["path"] == path:
  718. return group
  719. elif search_in_subgroups and group["subGroups"]:
  720. for group in group["subGroups"]:
  721. if group["path"] == path:
  722. return group
  723. res = self.get_subgroups(group, path)
  724. if res is not None:
  725. return res
  726. return None
  727. def create_group(self, payload, parent=None, skip_exists=False):
  728. """Create a group in the Realm.
  729. :param payload: GroupRepresentation
  730. :param parent: parent group's id. Required to create a sub-group.
  731. :param skip_exists: If true then do not raise an error if it already exists
  732. GroupRepresentation
  733. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  734. :return: Group id for newly created group or None for an existing group
  735. """
  736. if parent is None:
  737. params_path = {"realm-name": self.realm_name}
  738. data_raw = self.raw_post(
  739. urls_patterns.URL_ADMIN_GROUPS.format(**params_path), data=json.dumps(payload)
  740. )
  741. else:
  742. params_path = {"realm-name": self.realm_name, "id": parent}
  743. data_raw = self.raw_post(
  744. urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), data=json.dumps(payload)
  745. )
  746. raise_error_from_response(
  747. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  748. )
  749. try:
  750. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  751. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  752. except KeyError:
  753. return
  754. def update_group(self, group_id, payload):
  755. """Update group, ignores subgroups.
  756. :param group_id: id of group
  757. :param payload: GroupRepresentation with updated information.
  758. GroupRepresentation
  759. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  760. :return: Http response
  761. """
  762. params_path = {"realm-name": self.realm_name, "id": group_id}
  763. data_raw = self.raw_put(
  764. urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload)
  765. )
  766. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  767. def group_set_permissions(self, group_id, enabled=True):
  768. """Enable/Disable permissions for a group.
  769. Cannot delete group if disabled
  770. :param group_id: id of group
  771. :param enabled: boolean
  772. :return: Keycloak server response
  773. """
  774. params_path = {"realm-name": self.realm_name, "id": group_id}
  775. data_raw = self.raw_put(
  776. urls_patterns.URL_ADMIN_GROUP_PERMISSIONS.format(**params_path),
  777. data=json.dumps({"enabled": enabled}),
  778. )
  779. return raise_error_from_response(data_raw, KeycloakPutError)
  780. def group_user_add(self, user_id, group_id):
  781. """Add user to group (user_id and group_id).
  782. :param user_id: id of user
  783. :param group_id: id of group to add to
  784. :return: Keycloak server response
  785. """
  786. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  787. data_raw = self.raw_put(
  788. urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None
  789. )
  790. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  791. def group_user_remove(self, user_id, group_id):
  792. """Remove user from group (user_id and group_id).
  793. :param user_id: id of user
  794. :param group_id: id of group to remove from
  795. :return: Keycloak server response
  796. """
  797. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  798. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path))
  799. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  800. def delete_group(self, group_id):
  801. """Delete a group in the Realm.
  802. :param group_id: id of group to delete
  803. :return: Keycloak server response
  804. """
  805. params_path = {"realm-name": self.realm_name, "id": group_id}
  806. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_GROUP.format(**params_path))
  807. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  808. def get_clients(self):
  809. """Get clients.
  810. Returns a list of clients belonging to the realm
  811. ClientRepresentation
  812. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  813. :return: Keycloak server response (ClientRepresentation)
  814. """
  815. params_path = {"realm-name": self.realm_name}
  816. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENTS.format(**params_path))
  817. return raise_error_from_response(data_raw, KeycloakGetError)
  818. def get_client(self, client_id):
  819. """Get representation of the client.
  820. ClientRepresentation
  821. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  822. :param client_id: id of client (not client-id)
  823. :return: Keycloak server response (ClientRepresentation)
  824. """
  825. params_path = {"realm-name": self.realm_name, "id": client_id}
  826. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT.format(**params_path))
  827. return raise_error_from_response(data_raw, KeycloakGetError)
  828. def get_client_id(self, client_name):
  829. """Get internal keycloak client id from client-id.
  830. This is required for further actions against this client.
  831. :param client_name: name in ClientRepresentation
  832. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  833. :return: client_id (uuid as string)
  834. """
  835. clients = self.get_clients()
  836. for client in clients:
  837. if client_name == client.get("name") or client_name == client.get("clientId"):
  838. return client["id"]
  839. return None
  840. def get_client_authz_settings(self, client_id):
  841. """Get authorization json from client.
  842. :param client_id: id in ClientRepresentation
  843. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  844. :return: Keycloak server response
  845. """
  846. params_path = {"realm-name": self.realm_name, "id": client_id}
  847. data_raw = self.raw_get(
  848. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path)
  849. )
  850. return raise_error_from_response(data_raw, KeycloakGetError)
  851. def create_client_authz_resource(self, client_id, payload, skip_exists=False):
  852. """Create resources of client.
  853. :param client_id: id in ClientRepresentation
  854. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  855. :param payload: ResourceRepresentation
  856. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  857. :return: Keycloak server response
  858. """
  859. params_path = {"realm-name": self.realm_name, "id": client_id}
  860. data_raw = self.raw_post(
  861. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path),
  862. data=json.dumps(payload),
  863. )
  864. return raise_error_from_response(
  865. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  866. )
  867. def get_client_authz_resources(self, client_id):
  868. """Get resources from client.
  869. :param client_id: id in ClientRepresentation
  870. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  871. :return: Keycloak server response
  872. """
  873. params_path = {"realm-name": self.realm_name, "id": client_id}
  874. data_raw = self.raw_get(
  875. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path)
  876. )
  877. return raise_error_from_response(data_raw, KeycloakGetError)
  878. def create_client_authz_role_based_policy(self, client_id, payload, skip_exists=False):
  879. """Create role-based policy of client.
  880. :param client_id: id in ClientRepresentation
  881. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  882. :param payload: No Document
  883. :return: Keycloak server response
  884. Payload example::
  885. payload={
  886. "type": "role",
  887. "logic": "POSITIVE",
  888. "decisionStrategy": "UNANIMOUS",
  889. "name": "Policy-1",
  890. "roles": [
  891. {
  892. "id": id
  893. }
  894. ]
  895. }
  896. """
  897. params_path = {"realm-name": self.realm_name, "id": client_id}
  898. data_raw = self.raw_post(
  899. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_ROLE_BASED_POLICY.format(**params_path),
  900. data=json.dumps(payload),
  901. )
  902. return raise_error_from_response(
  903. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  904. )
  905. def create_client_authz_resource_based_permission(self, client_id, payload, skip_exists=False):
  906. """Create resource-based permission of client.
  907. :param client_id: id in ClientRepresentation
  908. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  909. :param payload: PolicyRepresentation
  910. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_policyrepresentation
  911. :return: Keycloak server response
  912. Payload example::
  913. payload={
  914. "type": "resource",
  915. "logic": "POSITIVE",
  916. "decisionStrategy": "UNANIMOUS",
  917. "name": "Permission-Name",
  918. "resources": [
  919. resource_id
  920. ],
  921. "policies": [
  922. policy_id
  923. ]
  924. """
  925. params_path = {"realm-name": self.realm_name, "id": client_id}
  926. data_raw = self.raw_post(
  927. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_BASED_PERMISSION.format(**params_path),
  928. data=json.dumps(payload),
  929. )
  930. return raise_error_from_response(
  931. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  932. )
  933. def get_client_authz_scopes(self, client_id):
  934. """Get scopes from client.
  935. :param client_id: id in ClientRepresentation
  936. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  937. :return: Keycloak server response
  938. """
  939. params_path = {"realm-name": self.realm_name, "id": client_id}
  940. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path))
  941. return raise_error_from_response(data_raw, KeycloakGetError)
  942. def get_client_authz_permissions(self, client_id):
  943. """Get permissions from client.
  944. :param client_id: id in ClientRepresentation
  945. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  946. :return: Keycloak server response
  947. """
  948. params_path = {"realm-name": self.realm_name, "id": client_id}
  949. data_raw = self.raw_get(
  950. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path)
  951. )
  952. return raise_error_from_response(data_raw, KeycloakGetError)
  953. def get_client_authz_policies(self, client_id):
  954. """Get policies from client.
  955. :param client_id: id in ClientRepresentation
  956. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  957. :return: Keycloak server response
  958. """
  959. params_path = {"realm-name": self.realm_name, "id": client_id}
  960. data_raw = self.raw_get(
  961. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICIES.format(**params_path)
  962. )
  963. return raise_error_from_response(data_raw, KeycloakGetError)
  964. def get_client_service_account_user(self, client_id):
  965. """Get service account user from client.
  966. :param client_id: id in ClientRepresentation
  967. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  968. :return: UserRepresentation
  969. """
  970. params_path = {"realm-name": self.realm_name, "id": client_id}
  971. data_raw = self.raw_get(
  972. urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path)
  973. )
  974. return raise_error_from_response(data_raw, KeycloakGetError)
  975. def create_client(self, payload, skip_exists=False):
  976. """Create a client.
  977. ClientRepresentation:
  978. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  979. :param skip_exists: If true then do not raise an error if client already exists
  980. :param payload: ClientRepresentation
  981. :return: Client ID
  982. """
  983. if skip_exists:
  984. client_id = self.get_client_id(client_name=payload["name"])
  985. if client_id is not None:
  986. return client_id
  987. params_path = {"realm-name": self.realm_name}
  988. data_raw = self.raw_post(
  989. urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), data=json.dumps(payload)
  990. )
  991. raise_error_from_response(
  992. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  993. )
  994. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  995. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  996. def update_client(self, client_id, payload):
  997. """Update a client.
  998. :param client_id: Client id
  999. :param payload: ClientRepresentation
  1000. :return: Http response
  1001. """
  1002. params_path = {"realm-name": self.realm_name, "id": client_id}
  1003. data_raw = self.raw_put(
  1004. urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload)
  1005. )
  1006. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1007. def delete_client(self, client_id):
  1008. """Get representation of the client.
  1009. ClientRepresentation
  1010. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1011. :param client_id: keycloak client id (not oauth client-id)
  1012. :return: Keycloak server response (ClientRepresentation)
  1013. """
  1014. params_path = {"realm-name": self.realm_name, "id": client_id}
  1015. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_CLIENT.format(**params_path))
  1016. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1017. def get_client_installation_provider(self, client_id, provider_id):
  1018. """Get content for given installation provider.
  1019. Related documentation:
  1020. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource
  1021. Possible provider_id list available in the ServerInfoRepresentation#clientInstallations
  1022. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_serverinforepresentation
  1023. :param client_id: Client id
  1024. :param provider_id: provider id to specify response format
  1025. """
  1026. params_path = {"realm-name": self.realm_name, "id": client_id, "provider-id": provider_id}
  1027. data_raw = self.raw_get(
  1028. urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path)
  1029. )
  1030. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  1031. def get_realm_roles(self):
  1032. """Get all roles for the realm or client.
  1033. RoleRepresentation
  1034. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1035. :return: Keycloak server response (RoleRepresentation)
  1036. """
  1037. params_path = {"realm-name": self.realm_name}
  1038. data_raw = self.raw_get(urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path))
  1039. return raise_error_from_response(data_raw, KeycloakGetError)
  1040. def get_realm_role_members(self, role_name, query=None):
  1041. """Get role members of realm by role name.
  1042. :param role_name: Name of the role.
  1043. :param query: Additional Query parameters
  1044. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_roles_resource)
  1045. :return: Keycloak Server Response (UserRepresentation)
  1046. """
  1047. query = query or dict()
  1048. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1049. return self.__fetch_all(
  1050. urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query
  1051. )
  1052. def get_client_roles(self, client_id):
  1053. """Get all roles for the client.
  1054. :param client_id: id of client (not client-id)
  1055. RoleRepresentation
  1056. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1057. :return: Keycloak server response (RoleRepresentation)
  1058. """
  1059. params_path = {"realm-name": self.realm_name, "id": client_id}
  1060. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path))
  1061. return raise_error_from_response(data_raw, KeycloakGetError)
  1062. def get_client_role(self, client_id, role_name):
  1063. """Get client role id by name.
  1064. This is required for further actions with this role.
  1065. :param client_id: id of client (not client-id)
  1066. :param role_name: roles name (not id!)
  1067. RoleRepresentation
  1068. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1069. :return: role_id
  1070. """
  1071. params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name}
  1072. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path))
  1073. return raise_error_from_response(data_raw, KeycloakGetError)
  1074. def get_client_role_id(self, client_id, role_name):
  1075. """Get client role id by name.
  1076. This is required for further actions with this role.
  1077. :param client_id: id of client (not client-id)
  1078. :param role_name: roles name (not id!)
  1079. RoleRepresentation
  1080. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1081. :return: role_id
  1082. """
  1083. role = self.get_client_role(client_id, role_name)
  1084. return role.get("id")
  1085. def create_client_role(self, client_role_id, payload, skip_exists=False):
  1086. """Create a client role.
  1087. RoleRepresentation
  1088. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1089. :param client_role_id: id of client (not client-id)
  1090. :param payload: RoleRepresentation
  1091. :param skip_exists: If true then do not raise an error if client role already exists
  1092. :return: Client role name
  1093. """
  1094. if skip_exists:
  1095. try:
  1096. res = self.get_client_role(client_id=client_role_id, role_name=payload["name"])
  1097. return res["name"]
  1098. except KeycloakGetError:
  1099. pass
  1100. params_path = {"realm-name": self.realm_name, "id": client_role_id}
  1101. data_raw = self.raw_post(
  1102. urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), data=json.dumps(payload)
  1103. )
  1104. raise_error_from_response(
  1105. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1106. )
  1107. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1108. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1109. def add_composite_client_roles_to_role(self, client_role_id, role_name, roles):
  1110. """Add composite roles to client role.
  1111. :param client_role_id: id of client (not client-id)
  1112. :param role_name: The name of the role
  1113. :param roles: roles list or role (use RoleRepresentation) to be updated
  1114. :return: Keycloak server response
  1115. """
  1116. payload = roles if isinstance(roles, list) else [roles]
  1117. params_path = {"realm-name": self.realm_name, "id": client_role_id, "role-name": role_name}
  1118. data_raw = self.raw_post(
  1119. urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path),
  1120. data=json.dumps(payload),
  1121. )
  1122. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1123. def update_client_role(self, client_role_id, role_name, payload):
  1124. """Update a client role.
  1125. RoleRepresentation
  1126. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1127. :param client_role_id: id of client (not client-id)
  1128. :param role_name: role's name (not id!)
  1129. :param payload: RoleRepresentation
  1130. """
  1131. params_path = {"realm-name": self.realm_name, "id": client_role_id, "role-name": role_name}
  1132. data_raw = self.raw_put(
  1133. urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload)
  1134. )
  1135. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1136. def delete_client_role(self, client_role_id, role_name):
  1137. """Delete a client role.
  1138. RoleRepresentation
  1139. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1140. :param client_role_id: id of client (not client-id)
  1141. :param role_name: role's name (not id!)
  1142. """
  1143. params_path = {"realm-name": self.realm_name, "id": client_role_id, "role-name": role_name}
  1144. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path))
  1145. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1146. def assign_client_role(self, user_id, client_id, roles):
  1147. """Assign a client role to a user.
  1148. :param user_id: id of user
  1149. :param client_id: id of client (not client-id)
  1150. :param roles: roles list or role (use RoleRepresentation)
  1151. :return: Keycloak server response
  1152. """
  1153. payload = roles if isinstance(roles, list) else [roles]
  1154. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  1155. data_raw = self.raw_post(
  1156. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  1157. data=json.dumps(payload),
  1158. )
  1159. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1160. def get_client_role_members(self, client_id, role_name, **query):
  1161. """Get members by client role.
  1162. :param client_id: The client id
  1163. :param role_name: the name of role to be queried.
  1164. :param query: Additional query parameters
  1165. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource)
  1166. :return: Keycloak server response (UserRepresentation)
  1167. """
  1168. params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name}
  1169. return self.__fetch_all(
  1170. urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), query
  1171. )
  1172. def get_client_role_groups(self, client_id, role_name, **query):
  1173. """Get group members by client role.
  1174. :param client_id: The client id
  1175. :param role_name: the name of role to be queried.
  1176. :param query: Additional query parameters
  1177. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource)
  1178. :return: Keycloak server response
  1179. """
  1180. params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name}
  1181. return self.__fetch_all(
  1182. urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), query
  1183. )
  1184. def create_realm_role(self, payload, skip_exists=False):
  1185. """Create a new role for the realm or client.
  1186. :param payload: The role (use RoleRepresentation)
  1187. :param skip_exists: If true then do not raise an error if realm role already exists
  1188. :return: Realm role name
  1189. """
  1190. if skip_exists:
  1191. try:
  1192. role = self.get_realm_role(role_name=payload["name"])
  1193. return role["name"]
  1194. except KeycloakGetError:
  1195. pass
  1196. params_path = {"realm-name": self.realm_name}
  1197. data_raw = self.raw_post(
  1198. urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), data=json.dumps(payload)
  1199. )
  1200. raise_error_from_response(
  1201. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1202. )
  1203. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1204. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1205. def get_realm_role(self, role_name):
  1206. """Get realm role by role name.
  1207. :param role_name: role's name, not id!
  1208. RoleRepresentation
  1209. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1210. :return: role_id
  1211. """
  1212. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1213. data_raw = self.raw_get(
  1214. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path)
  1215. )
  1216. return raise_error_from_response(data_raw, KeycloakGetError)
  1217. def update_realm_role(self, role_name, payload):
  1218. """Update a role for the realm by name.
  1219. :param role_name: The name of the role to be updated
  1220. :param payload: The role (use RoleRepresentation)
  1221. :return: Keycloak server response
  1222. """
  1223. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1224. data_raw = self.raw_put(
  1225. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path),
  1226. data=json.dumps(payload),
  1227. )
  1228. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1229. def delete_realm_role(self, role_name):
  1230. """Delete a role for the realm by name.
  1231. :param payload: The role name {'role-name':'name-of-the-role'}
  1232. :return: Keycloak server response
  1233. """
  1234. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1235. data_raw = self.raw_delete(
  1236. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path)
  1237. )
  1238. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1239. def add_composite_realm_roles_to_role(self, role_name, roles):
  1240. """Add composite roles to the role.
  1241. :param role_name: The name of the role
  1242. :param roles: roles list or role (use RoleRepresentation) to be updated
  1243. :return: Keycloak server response
  1244. """
  1245. payload = roles if isinstance(roles, list) else [roles]
  1246. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1247. data_raw = self.raw_post(
  1248. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  1249. data=json.dumps(payload),
  1250. )
  1251. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1252. def remove_composite_realm_roles_to_role(self, role_name, roles):
  1253. """Remove composite roles from the role.
  1254. :param role_name: The name of the role
  1255. :param roles: roles list or role (use RoleRepresentation) to be removed
  1256. :return: Keycloak server response
  1257. """
  1258. payload = roles if isinstance(roles, list) else [roles]
  1259. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1260. data_raw = self.raw_delete(
  1261. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  1262. data=json.dumps(payload),
  1263. )
  1264. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1265. def get_composite_realm_roles_of_role(self, role_name):
  1266. """Get composite roles of the role.
  1267. :param role_name: The name of the role
  1268. :return: Keycloak server response (array RoleRepresentation)
  1269. """
  1270. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  1271. data_raw = self.raw_get(
  1272. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path)
  1273. )
  1274. return raise_error_from_response(data_raw, KeycloakGetError)
  1275. def assign_realm_roles(self, user_id, roles):
  1276. """Assign realm roles to a user.
  1277. :param user_id: id of user
  1278. :param roles: roles list or role (use RoleRepresentation)
  1279. :return: Keycloak server response
  1280. """
  1281. payload = roles if isinstance(roles, list) else [roles]
  1282. params_path = {"realm-name": self.realm_name, "id": user_id}
  1283. data_raw = self.raw_post(
  1284. urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path),
  1285. data=json.dumps(payload),
  1286. )
  1287. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1288. def delete_realm_roles_of_user(self, user_id, roles):
  1289. """Delete realm roles of a user.
  1290. :param user_id: id of user
  1291. :param roles: roles list or role (use RoleRepresentation)
  1292. :return: Keycloak server response
  1293. """
  1294. payload = roles if isinstance(roles, list) else [roles]
  1295. params_path = {"realm-name": self.realm_name, "id": user_id}
  1296. data_raw = self.raw_delete(
  1297. urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path),
  1298. data=json.dumps(payload),
  1299. )
  1300. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1301. def get_realm_roles_of_user(self, user_id):
  1302. """Get all realm roles for a user.
  1303. :param user_id: id of user
  1304. :return: Keycloak server response (array RoleRepresentation)
  1305. """
  1306. params_path = {"realm-name": self.realm_name, "id": user_id}
  1307. data_raw = self.raw_get(urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path))
  1308. return raise_error_from_response(data_raw, KeycloakGetError)
  1309. def get_available_realm_roles_of_user(self, user_id):
  1310. """Get all available (i.e. unassigned) realm roles for a user.
  1311. :param user_id: id of user
  1312. :return: Keycloak server response (array RoleRepresentation)
  1313. """
  1314. params_path = {"realm-name": self.realm_name, "id": user_id}
  1315. data_raw = self.raw_get(
  1316. urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path)
  1317. )
  1318. return raise_error_from_response(data_raw, KeycloakGetError)
  1319. def get_composite_realm_roles_of_user(self, user_id):
  1320. """Get all composite (i.e. implicit) realm roles for a user.
  1321. :param user_id: id of user
  1322. :return: Keycloak server response (array RoleRepresentation)
  1323. """
  1324. params_path = {"realm-name": self.realm_name, "id": user_id}
  1325. data_raw = self.raw_get(
  1326. urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path)
  1327. )
  1328. return raise_error_from_response(data_raw, KeycloakGetError)
  1329. def assign_group_realm_roles(self, group_id, roles):
  1330. """Assign realm roles to a group.
  1331. :param group_id: id of groupp
  1332. :param roles: roles list or role (use GroupRoleRepresentation)
  1333. :return: Keycloak server response
  1334. """
  1335. payload = roles if isinstance(roles, list) else [roles]
  1336. params_path = {"realm-name": self.realm_name, "id": group_id}
  1337. data_raw = self.raw_post(
  1338. urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  1339. data=json.dumps(payload),
  1340. )
  1341. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1342. def delete_group_realm_roles(self, group_id, roles):
  1343. """Delete realm roles of a group.
  1344. :param group_id: id of group
  1345. :param roles: roles list or role (use GroupRoleRepresentation)
  1346. :return: Keycloak server response
  1347. """
  1348. payload = roles if isinstance(roles, list) else [roles]
  1349. params_path = {"realm-name": self.realm_name, "id": group_id}
  1350. data_raw = self.raw_delete(
  1351. urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  1352. data=json.dumps(payload),
  1353. )
  1354. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1355. def get_group_realm_roles(self, group_id):
  1356. """Get all realm roles for a group.
  1357. :param user_id: id of the group
  1358. :return: Keycloak server response (array RoleRepresentation)
  1359. """
  1360. params_path = {"realm-name": self.realm_name, "id": group_id}
  1361. data_raw = self.raw_get(urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path))
  1362. return raise_error_from_response(data_raw, KeycloakGetError)
  1363. def assign_group_client_roles(self, group_id, client_id, roles):
  1364. """Assign client roles to a group.
  1365. :param group_id: id of group
  1366. :param client_id: id of client (not client-id)
  1367. :param roles: roles list or role (use GroupRoleRepresentation)
  1368. :return: Keycloak server response
  1369. """
  1370. payload = roles if isinstance(roles, list) else [roles]
  1371. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  1372. data_raw = self.raw_post(
  1373. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  1374. data=json.dumps(payload),
  1375. )
  1376. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  1377. def get_group_client_roles(self, group_id, client_id):
  1378. """Get client roles of a group.
  1379. :param group_id: id of group
  1380. :param client_id: id of client (not client-id)
  1381. :return: Keycloak server response
  1382. """
  1383. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  1384. data_raw = self.raw_get(urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path))
  1385. return raise_error_from_response(data_raw, KeycloakGetError)
  1386. def delete_group_client_roles(self, group_id, client_id, roles):
  1387. """Delete client roles of a group.
  1388. :param group_id: id of group
  1389. :param client_id: id of client (not client-id)
  1390. :param roles: roles list or role (use GroupRoleRepresentation)
  1391. :return: Keycloak server response (array RoleRepresentation)
  1392. """
  1393. payload = roles if isinstance(roles, list) else [roles]
  1394. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  1395. data_raw = self.raw_delete(
  1396. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  1397. data=json.dumps(payload),
  1398. )
  1399. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1400. def get_client_roles_of_user(self, user_id, client_id):
  1401. """Get all client roles for a user.
  1402. :param user_id: id of user
  1403. :param client_id: id of client (not client-id)
  1404. :return: Keycloak server response (array RoleRepresentation)
  1405. """
  1406. return self._get_client_roles_of_user(
  1407. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, user_id, client_id
  1408. )
  1409. def get_available_client_roles_of_user(self, user_id, client_id):
  1410. """Get available client role-mappings for a user.
  1411. :param user_id: id of user
  1412. :param client_id: id of client (not client-id)
  1413. :return: Keycloak server response (array RoleRepresentation)
  1414. """
  1415. return self._get_client_roles_of_user(
  1416. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id
  1417. )
  1418. def get_composite_client_roles_of_user(self, user_id, client_id):
  1419. """Get composite client role-mappings for a user.
  1420. :param user_id: id of user
  1421. :param client_id: id of client (not client-id)
  1422. :return: Keycloak server response (array RoleRepresentation)
  1423. """
  1424. return self._get_client_roles_of_user(
  1425. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id
  1426. )
  1427. def _get_client_roles_of_user(self, client_level_role_mapping_url, user_id, client_id):
  1428. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  1429. data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path))
  1430. return raise_error_from_response(data_raw, KeycloakGetError)
  1431. def delete_client_roles_of_user(self, user_id, client_id, roles):
  1432. """Delete client roles from a user.
  1433. :param user_id: id of user
  1434. :param client_id: id of client containing role (not client-id)
  1435. :param roles: roles list or role to delete (use RoleRepresentation)
  1436. :return: Keycloak server response
  1437. """
  1438. payload = roles if isinstance(roles, list) else [roles]
  1439. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  1440. data_raw = self.raw_delete(
  1441. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  1442. data=json.dumps(payload),
  1443. )
  1444. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1445. def get_authentication_flows(self):
  1446. """Get authentication flows.
  1447. Returns all flow details
  1448. AuthenticationFlowRepresentation
  1449. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  1450. :return: Keycloak server response (AuthenticationFlowRepresentation)
  1451. """
  1452. params_path = {"realm-name": self.realm_name}
  1453. data_raw = self.raw_get(urls_patterns.URL_ADMIN_FLOWS.format(**params_path))
  1454. return raise_error_from_response(data_raw, KeycloakGetError)
  1455. def get_authentication_flow_for_id(self, flow_id):
  1456. """Get one authentication flow by it's id.
  1457. Returns all flow details
  1458. AuthenticationFlowRepresentation
  1459. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  1460. :param flow_id: the id of a flow NOT it's alias
  1461. :return: Keycloak server response (AuthenticationFlowRepresentation)
  1462. """
  1463. params_path = {"realm-name": self.realm_name, "flow-id": flow_id}
  1464. data_raw = self.raw_get(urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path))
  1465. return raise_error_from_response(data_raw, KeycloakGetError)
  1466. def create_authentication_flow(self, payload, skip_exists=False):
  1467. """Create a new authentication flow.
  1468. AuthenticationFlowRepresentation
  1469. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  1470. :param payload: AuthenticationFlowRepresentation
  1471. :param skip_exists: Do not raise an error if authentication flow already exists
  1472. :return: Keycloak server response (RoleRepresentation)
  1473. """
  1474. params_path = {"realm-name": self.realm_name}
  1475. data_raw = self.raw_post(
  1476. urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload)
  1477. )
  1478. return raise_error_from_response(
  1479. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1480. )
  1481. def copy_authentication_flow(self, payload, flow_alias):
  1482. """Copy existing authentication flow under a new name.
  1483. The new name is given as 'newName' attribute of the passed payload.
  1484. :param payload: JSON containing 'newName' attribute
  1485. :param flow_alias: the flow alias
  1486. :return: Keycloak server response (RoleRepresentation)
  1487. """
  1488. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1489. data_raw = self.raw_post(
  1490. urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload)
  1491. )
  1492. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1493. def delete_authentication_flow(self, flow_id):
  1494. """Delete authentication flow.
  1495. AuthenticationInfoRepresentation
  1496. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationinforepresentation
  1497. :param flow_id: authentication flow id
  1498. :return: Keycloak server response
  1499. """
  1500. params_path = {"realm-name": self.realm_name, "id": flow_id}
  1501. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_FLOW.format(**params_path))
  1502. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1503. def get_authentication_flow_executions(self, flow_alias):
  1504. """Get authentication flow executions.
  1505. Returns all execution steps
  1506. :param flow_alias: the flow alias
  1507. :return: Response(json)
  1508. """
  1509. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1510. data_raw = self.raw_get(urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path))
  1511. return raise_error_from_response(data_raw, KeycloakGetError)
  1512. def update_authentication_flow_executions(self, payload, flow_alias):
  1513. """Update an authentication flow execution.
  1514. AuthenticationExecutionInfoRepresentation
  1515. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1516. :param payload: AuthenticationExecutionInfoRepresentation
  1517. :param flow_alias: The flow alias
  1518. :return: Keycloak server response
  1519. """
  1520. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1521. data_raw = self.raw_put(
  1522. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path),
  1523. data=json.dumps(payload),
  1524. )
  1525. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[202, 204])
  1526. def get_authentication_flow_execution(self, execution_id):
  1527. """Get authentication flow execution.
  1528. AuthenticationExecutionInfoRepresentation
  1529. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1530. :param execution_id: the execution ID
  1531. :return: Response(json)
  1532. """
  1533. params_path = {"realm-name": self.realm_name, "id": execution_id}
  1534. data_raw = self.raw_get(urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path))
  1535. return raise_error_from_response(data_raw, KeycloakGetError)
  1536. def create_authentication_flow_execution(self, payload, flow_alias):
  1537. """Create an authentication flow execution.
  1538. AuthenticationExecutionInfoRepresentation
  1539. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1540. :param payload: AuthenticationExecutionInfoRepresentation
  1541. :param flow_alias: The flow alias
  1542. :return: Keycloak server response
  1543. """
  1544. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1545. data_raw = self.raw_post(
  1546. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path),
  1547. data=json.dumps(payload),
  1548. )
  1549. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1550. def delete_authentication_flow_execution(self, execution_id):
  1551. """Delete authentication flow execution.
  1552. AuthenticationExecutionInfoRepresentation
  1553. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1554. :param execution_id: keycloak client id (not oauth client-id)
  1555. :return: Keycloak server response (json)
  1556. """
  1557. params_path = {"realm-name": self.realm_name, "id": execution_id}
  1558. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path))
  1559. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1560. def create_authentication_flow_subflow(self, payload, flow_alias, skip_exists=False):
  1561. """Create a new sub authentication flow for a given authentication flow.
  1562. AuthenticationFlowRepresentation
  1563. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  1564. :param payload: AuthenticationFlowRepresentation
  1565. :param flow_alias: The flow alias
  1566. :param skip_exists: Do not raise an error if authentication flow already exists
  1567. :return: Keycloak server response (RoleRepresentation)
  1568. """
  1569. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1570. data_raw = self.raw_post(
  1571. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_FLOW.format(**params_path),
  1572. data=json.dumps(payload),
  1573. )
  1574. return raise_error_from_response(
  1575. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1576. )
  1577. def get_authenticator_providers(self):
  1578. """Get authenticator providers list.
  1579. :return: Response(json)
  1580. """
  1581. params_path = {"realm-name": self.realm_name}
  1582. data_raw = self.raw_get(
  1583. urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path)
  1584. )
  1585. return raise_error_from_response(data_raw, KeycloakGetError)
  1586. def get_authenticator_provider_config_description(self, provider_id):
  1587. """Get authenticator's provider configuration description.
  1588. AuthenticatorConfigInfoRepresentation
  1589. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticatorconfiginforepresentation
  1590. :param provider_id: Provider Id
  1591. :return: AuthenticatorConfigInfoRepresentation
  1592. """
  1593. params_path = {"realm-name": self.realm_name, "provider-id": provider_id}
  1594. data_raw = self.raw_get(
  1595. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path)
  1596. )
  1597. return raise_error_from_response(data_raw, KeycloakGetError)
  1598. def get_authenticator_config(self, config_id):
  1599. """Get authenticator configuration.
  1600. Returns all configuration details.
  1601. :param config_id: Authenticator config id
  1602. :return: Response(json)
  1603. """
  1604. params_path = {"realm-name": self.realm_name, "id": config_id}
  1605. data_raw = self.raw_get(urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path))
  1606. return raise_error_from_response(data_raw, KeycloakGetError)
  1607. def update_authenticator_config(self, payload, config_id):
  1608. """Update an authenticator configuration.
  1609. AuthenticatorConfigRepresentation
  1610. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticatorconfigrepresentation
  1611. :param payload: AuthenticatorConfigRepresentation
  1612. :param config_id: Authenticator config id
  1613. :return: Response(json)
  1614. """
  1615. params_path = {"realm-name": self.realm_name, "id": config_id}
  1616. data_raw = self.raw_put(
  1617. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path),
  1618. data=json.dumps(payload),
  1619. )
  1620. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1621. def delete_authenticator_config(self, config_id):
  1622. """Delete a authenticator configuration.
  1623. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authentication_management_resource
  1624. :param config_id: Authenticator config id
  1625. :return: Keycloak server Response
  1626. """
  1627. params_path = {"realm-name": self.realm_name, "id": config_id}
  1628. data_raw = self.raw_delete(
  1629. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path)
  1630. )
  1631. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1632. def sync_users(self, storage_id, action):
  1633. """Trigger user sync from provider.
  1634. :param storage_id: The id of the user storage provider
  1635. :param action: Action can be "triggerFullSync" or "triggerChangedUsersSync"
  1636. :return:
  1637. """
  1638. data = {"action": action}
  1639. params_query = {"action": action}
  1640. params_path = {"realm-name": self.realm_name, "id": storage_id}
  1641. data_raw = self.raw_post(
  1642. urls_patterns.URL_ADMIN_USER_STORAGE.format(**params_path),
  1643. data=json.dumps(data),
  1644. **params_query
  1645. )
  1646. return raise_error_from_response(data_raw, KeycloakPostError)
  1647. def get_client_scopes(self):
  1648. """Get client scopes.
  1649. Get representation of the client scopes for the realm where we are connected to
  1650. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  1651. :return: Keycloak server response Array of (ClientScopeRepresentation)
  1652. """
  1653. params_path = {"realm-name": self.realm_name}
  1654. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path))
  1655. return raise_error_from_response(data_raw, KeycloakGetError)
  1656. def get_client_scope(self, client_scope_id):
  1657. """Get client scope.
  1658. Get representation of the client scopes for the realm where we are connected to
  1659. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  1660. :param client_scope_id: The id of the client scope
  1661. :return: Keycloak server response (ClientScopeRepresentation)
  1662. """
  1663. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1664. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path))
  1665. return raise_error_from_response(data_raw, KeycloakGetError)
  1666. def get_client_scope_by_name(self, client_scope_name):
  1667. """Get client scope by name.
  1668. Get representation of the client scope identified by the client scope name.
  1669. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  1670. :param client_scope_name: (str) Name of the client scope
  1671. :returns: ClientScopeRepresentation or None
  1672. """
  1673. client_scopes = self.get_client_scopes()
  1674. for client_scope in client_scopes:
  1675. if client_scope["name"] == client_scope_name:
  1676. return client_scope
  1677. return None
  1678. def create_client_scope(self, payload, skip_exists=False):
  1679. """Create a client scope.
  1680. ClientScopeRepresentation:
  1681. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  1682. :param payload: ClientScopeRepresentation
  1683. :param skip_exists: If true then do not raise an error if client scope already exists
  1684. :return: Client scope id
  1685. """
  1686. if skip_exists:
  1687. exists = self.get_client_scope_by_name(client_scope_name=payload["name"])
  1688. if exists is not None:
  1689. return exists["id"]
  1690. params_path = {"realm-name": self.realm_name}
  1691. data_raw = self.raw_post(
  1692. urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), data=json.dumps(payload)
  1693. )
  1694. raise_error_from_response(
  1695. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1696. )
  1697. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1698. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1699. def update_client_scope(self, client_scope_id, payload):
  1700. """Update a client scope.
  1701. ClientScopeRepresentation:
  1702. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_client_scopes_resource
  1703. :param client_scope_id: The id of the client scope
  1704. :param payload: ClientScopeRepresentation
  1705. :return: Keycloak server response (ClientScopeRepresentation)
  1706. """
  1707. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1708. data_raw = self.raw_put(
  1709. urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload)
  1710. )
  1711. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1712. def delete_client_scope(self, client_scope_id):
  1713. """Delete existing client scope.
  1714. ClientScopeRepresentation:
  1715. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_client_scopes_resource
  1716. :param client_scope_id: The id of the client scope
  1717. :return: Keycloak server response
  1718. """
  1719. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1720. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path))
  1721. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1722. def get_mappers_from_client_scope(self, client_scope_id):
  1723. """Get a list of all mappers connected to the client scope.
  1724. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocol_mappers_resource
  1725. :param client_scope_id: Client scope id
  1726. :returns: Keycloak server response (ProtocolMapperRepresentation)
  1727. """
  1728. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1729. data_raw = self.raw_get(
  1730. urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path)
  1731. )
  1732. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  1733. def add_mapper_to_client_scope(self, client_scope_id, payload):
  1734. """Add a mapper to a client scope.
  1735. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_create_mapper
  1736. :param client_scope_id: The id of the client scope
  1737. :param payload: ProtocolMapperRepresentation
  1738. :return: Keycloak server Response
  1739. """
  1740. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1741. data_raw = self.raw_post(
  1742. urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path),
  1743. data=json.dumps(payload),
  1744. )
  1745. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1746. def delete_mapper_from_client_scope(self, client_scope_id, protocol_mapper_id):
  1747. """Delete a mapper from a client scope.
  1748. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_delete_mapper
  1749. :param client_scope_id: The id of the client scope
  1750. :param protocol_mapper_id: Protocol mapper id
  1751. :return: Keycloak server Response
  1752. """
  1753. params_path = {
  1754. "realm-name": self.realm_name,
  1755. "scope-id": client_scope_id,
  1756. "protocol-mapper-id": protocol_mapper_id,
  1757. }
  1758. data_raw = self.raw_delete(
  1759. urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path)
  1760. )
  1761. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1762. def update_mapper_in_client_scope(self, client_scope_id, protocol_mapper_id, payload):
  1763. """Update an existing protocol mapper in a client scope.
  1764. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocol_mappers_resource
  1765. :param client_scope_id: The id of the client scope
  1766. :param protocol_mapper_id: The id of the protocol mapper which exists in the client scope
  1767. and should to be updated
  1768. :param payload: ProtocolMapperRepresentation
  1769. :return: Keycloak server Response
  1770. """
  1771. params_path = {
  1772. "realm-name": self.realm_name,
  1773. "scope-id": client_scope_id,
  1774. "protocol-mapper-id": protocol_mapper_id,
  1775. }
  1776. data_raw = self.raw_put(
  1777. urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path),
  1778. data=json.dumps(payload),
  1779. )
  1780. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1781. def get_default_default_client_scopes(self):
  1782. """Get default default client scopes.
  1783. Return list of default default client scopes
  1784. :return: Keycloak server response
  1785. """
  1786. params_path = {"realm-name": self.realm_name}
  1787. data_raw = self.raw_get(
  1788. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path)
  1789. )
  1790. return raise_error_from_response(data_raw, KeycloakGetError)
  1791. def delete_default_default_client_scope(self, scope_id):
  1792. """Delete default default client scope.
  1793. :param scope_id: default default client scope id
  1794. :return: Keycloak server response
  1795. """
  1796. params_path = {"realm-name": self.realm_name, "id": scope_id}
  1797. data_raw = self.raw_delete(
  1798. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path)
  1799. )
  1800. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1801. def add_default_default_client_scope(self, scope_id):
  1802. """Add default default client scope.
  1803. :param scope_id: default default client scope id
  1804. :return: Keycloak server response
  1805. """
  1806. params_path = {"realm-name": self.realm_name, "id": scope_id}
  1807. payload = {"realm": self.realm_name, "clientScopeId": scope_id}
  1808. data_raw = self.raw_put(
  1809. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path),
  1810. data=json.dumps(payload),
  1811. )
  1812. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1813. def get_default_optional_client_scopes(self):
  1814. """Get default optional client scopes.
  1815. Return list of default optional client scopes
  1816. :return: Keycloak server response
  1817. """
  1818. params_path = {"realm-name": self.realm_name}
  1819. data_raw = self.raw_get(
  1820. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path)
  1821. )
  1822. return raise_error_from_response(data_raw, KeycloakGetError)
  1823. def delete_default_optional_client_scope(self, scope_id):
  1824. """Delete default optional client scope.
  1825. :param scope_id: default optional client scope id
  1826. :return: Keycloak server response
  1827. """
  1828. params_path = {"realm-name": self.realm_name, "id": scope_id}
  1829. data_raw = self.raw_delete(
  1830. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path)
  1831. )
  1832. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1833. def add_default_optional_client_scope(self, scope_id):
  1834. """Add default optional client scope.
  1835. :param scope_id: default optional client scope id
  1836. :return: Keycloak server response
  1837. """
  1838. params_path = {"realm-name": self.realm_name, "id": scope_id}
  1839. payload = {"realm": self.realm_name, "clientScopeId": scope_id}
  1840. data_raw = self.raw_put(
  1841. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path),
  1842. data=json.dumps(payload),
  1843. )
  1844. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1845. def get_mappers_from_client(self, client_id):
  1846. """List of all client mappers.
  1847. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocolmapperrepresentation
  1848. :param client_id: Client id
  1849. :returns: KeycloakServerResponse (list of ProtocolMapperRepresentation)
  1850. """
  1851. params_path = {"realm-name": self.realm_name, "id": client_id}
  1852. data_raw = self.raw_get(
  1853. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path)
  1854. )
  1855. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200])
  1856. def add_mapper_to_client(self, client_id, payload):
  1857. """Add a mapper to a client.
  1858. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_create_mapper
  1859. :param client_id: The id of the client
  1860. :param payload: ProtocolMapperRepresentation
  1861. :return: Keycloak server Response
  1862. """
  1863. params_path = {"realm-name": self.realm_name, "id": client_id}
  1864. data_raw = self.raw_post(
  1865. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path),
  1866. data=json.dumps(payload),
  1867. )
  1868. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1869. def update_client_mapper(self, client_id, mapper_id, payload):
  1870. """Update client mapper.
  1871. :param client_id: The id of the client
  1872. :param client_mapper_id: The id of the mapper to be deleted
  1873. :param payload: ProtocolMapperRepresentation
  1874. :return: Keycloak server response
  1875. """
  1876. params_path = {
  1877. "realm-name": self.realm_name,
  1878. "id": client_id,
  1879. "protocol-mapper-id": mapper_id,
  1880. }
  1881. data_raw = self.raw_put(
  1882. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path),
  1883. data=json.dumps(payload),
  1884. )
  1885. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1886. def remove_client_mapper(self, client_id, client_mapper_id):
  1887. """Remove a mapper from the client.
  1888. https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_protocol_mappers_resource
  1889. :param client_id: The id of the client
  1890. :param client_mapper_id: The id of the mapper to be deleted
  1891. :return: Keycloak server response
  1892. """
  1893. params_path = {
  1894. "realm-name": self.realm_name,
  1895. "id": client_id,
  1896. "protocol-mapper-id": client_mapper_id,
  1897. }
  1898. data_raw = self.raw_delete(
  1899. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path)
  1900. )
  1901. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1902. def generate_client_secrets(self, client_id):
  1903. """Generate a new secret for the client.
  1904. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_regeneratesecret
  1905. :param client_id: id of client (not client-id)
  1906. :return: Keycloak server response (ClientRepresentation)
  1907. """
  1908. params_path = {"realm-name": self.realm_name, "id": client_id}
  1909. data_raw = self.raw_post(
  1910. urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None
  1911. )
  1912. return raise_error_from_response(data_raw, KeycloakPostError)
  1913. def get_client_secrets(self, client_id):
  1914. """Get representation of the client secrets.
  1915. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientsecret
  1916. :param client_id: id of client (not client-id)
  1917. :return: Keycloak server response (ClientRepresentation)
  1918. """
  1919. params_path = {"realm-name": self.realm_name, "id": client_id}
  1920. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path))
  1921. return raise_error_from_response(data_raw, KeycloakGetError)
  1922. def get_components(self, query=None):
  1923. """Get components.
  1924. Return a list of components, filtered according to query parameters
  1925. ComponentRepresentation
  1926. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  1927. :param query: Query parameters (optional)
  1928. :return: components list
  1929. """
  1930. query = query or dict()
  1931. params_path = {"realm-name": self.realm_name}
  1932. data_raw = self.raw_get(
  1933. urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=None, **query
  1934. )
  1935. return raise_error_from_response(data_raw, KeycloakGetError)
  1936. def create_component(self, payload):
  1937. """Create a new component.
  1938. ComponentRepresentation
  1939. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  1940. :param payload: ComponentRepresentation
  1941. :return: Component id
  1942. """
  1943. params_path = {"realm-name": self.realm_name}
  1944. data_raw = self.raw_post(
  1945. urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=json.dumps(payload)
  1946. )
  1947. raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1948. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1949. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1950. def get_component(self, component_id):
  1951. """Get representation of the component.
  1952. :param component_id: Component id
  1953. ComponentRepresentation
  1954. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  1955. :return: ComponentRepresentation
  1956. """
  1957. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1958. data_raw = self.raw_get(urls_patterns.URL_ADMIN_COMPONENT.format(**params_path))
  1959. return raise_error_from_response(data_raw, KeycloakGetError)
  1960. def update_component(self, component_id, payload):
  1961. """Update the component.
  1962. :param component_id: Component id
  1963. :param payload: ComponentRepresentation
  1964. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  1965. :return: Http response
  1966. """
  1967. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1968. data_raw = self.raw_put(
  1969. urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload)
  1970. )
  1971. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1972. def delete_component(self, component_id):
  1973. """Delete the component.
  1974. :param component_id: Component id
  1975. :return: Http response
  1976. """
  1977. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1978. data_raw = self.raw_delete(urls_patterns.URL_ADMIN_COMPONENT.format(**params_path))
  1979. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1980. def get_keys(self):
  1981. """Get keys.
  1982. Return a list of keys, filtered according to query parameters
  1983. KeysMetadataRepresentation
  1984. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_key_resource
  1985. :return: keys list
  1986. """
  1987. params_path = {"realm-name": self.realm_name}
  1988. data_raw = self.raw_get(urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None)
  1989. return raise_error_from_response(data_raw, KeycloakGetError)
  1990. def get_events(self, query=None):
  1991. """Get events.
  1992. Return a list of events, filtered according to query parameters
  1993. EventRepresentation array
  1994. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_eventrepresentation
  1995. :return: events list
  1996. """
  1997. query = query or dict()
  1998. params_path = {"realm-name": self.realm_name}
  1999. data_raw = self.raw_get(
  2000. urls_patterns.URL_ADMIN_EVENTS.format(**params_path), data=None, **query
  2001. )
  2002. return raise_error_from_response(data_raw, KeycloakGetError)
  2003. def set_events(self, payload):
  2004. """Set realm events configuration.
  2005. RealmEventsConfigRepresentation
  2006. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmeventsconfigrepresentation
  2007. :return: Http response
  2008. """
  2009. params_path = {"realm-name": self.realm_name}
  2010. data_raw = self.raw_put(
  2011. urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload)
  2012. )
  2013. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  2014. def raw_get(self, *args, **kwargs):
  2015. """Call connection.raw_get.
  2016. If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token
  2017. and try *get* once more.
  2018. """
  2019. r = self.connection.raw_get(*args, **kwargs)
  2020. if "get" in self.auto_refresh_token and r.status_code == 401:
  2021. self.refresh_token()
  2022. return self.connection.raw_get(*args, **kwargs)
  2023. return r
  2024. def raw_post(self, *args, **kwargs):
  2025. """Call connection.raw_post.
  2026. If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token
  2027. and try *post* once more.
  2028. """
  2029. r = self.connection.raw_post(*args, **kwargs)
  2030. if "post" in self.auto_refresh_token and r.status_code == 401:
  2031. self.refresh_token()
  2032. return self.connection.raw_post(*args, **kwargs)
  2033. return r
  2034. def raw_put(self, *args, **kwargs):
  2035. """Call connection.raw_put.
  2036. If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token
  2037. and try *put* once more.
  2038. """
  2039. r = self.connection.raw_put(*args, **kwargs)
  2040. if "put" in self.auto_refresh_token and r.status_code == 401:
  2041. self.refresh_token()
  2042. return self.connection.raw_put(*args, **kwargs)
  2043. return r
  2044. def raw_delete(self, *args, **kwargs):
  2045. """Call connection.raw_delete.
  2046. If auto_refresh is set for *delete* and *access_token* is expired,
  2047. it will refresh the token and try *delete* once more.
  2048. """
  2049. r = self.connection.raw_delete(*args, **kwargs)
  2050. if "delete" in self.auto_refresh_token and r.status_code == 401:
  2051. self.refresh_token()
  2052. return self.connection.raw_delete(*args, **kwargs)
  2053. return r
  2054. def get_token(self):
  2055. """Get admin token."""
  2056. if self.user_realm_name:
  2057. token_realm_name = self.user_realm_name
  2058. elif self.realm_name:
  2059. token_realm_name = self.realm_name
  2060. else:
  2061. token_realm_name = "master"
  2062. self.keycloak_openid = KeycloakOpenID(
  2063. server_url=self.server_url,
  2064. client_id=self.client_id,
  2065. realm_name=token_realm_name,
  2066. verify=self.verify,
  2067. client_secret_key=self.client_secret_key,
  2068. custom_headers=self.custom_headers,
  2069. timeout=self.timeout,
  2070. )
  2071. grant_type = []
  2072. if self.client_secret_key:
  2073. if self.user_realm_name:
  2074. self.realm_name = self.user_realm_name
  2075. grant_type.append("client_credentials")
  2076. elif self.username and self.password:
  2077. grant_type.append("password")
  2078. if grant_type:
  2079. self.token = self.keycloak_openid.token(
  2080. self.username, self.password, grant_type=grant_type, totp=self.totp
  2081. )
  2082. headers = {
  2083. "Authorization": "Bearer " + self.token.get("access_token"),
  2084. "Content-Type": "application/json",
  2085. }
  2086. else:
  2087. self.token = None
  2088. headers = {}
  2089. if self.custom_headers is not None:
  2090. # merge custom headers to main headers
  2091. headers.update(self.custom_headers)
  2092. self.connection = ConnectionManager(
  2093. base_url=self.server_url, headers=headers, timeout=60, verify=self.verify
  2094. )
  2095. def refresh_token(self):
  2096. """Refresh the token."""
  2097. refresh_token = self.token.get("refresh_token", None)
  2098. if refresh_token is None:
  2099. self.get_token()
  2100. else:
  2101. try:
  2102. self.token = self.keycloak_openid.refresh_token(refresh_token)
  2103. except KeycloakPostError as e:
  2104. list_errors = [
  2105. b"Refresh token expired",
  2106. b"Token is not active",
  2107. b"Session not active",
  2108. ]
  2109. if e.response_code == 400 and any(err in e.response_body for err in list_errors):
  2110. self.get_token()
  2111. else:
  2112. raise
  2113. self.connection.add_param_headers(
  2114. "Authorization", "Bearer " + self.token.get("access_token")
  2115. )
  2116. def get_client_all_sessions(self, client_id):
  2117. """Get sessions associated with the client.
  2118. :param client_id: id of client
  2119. UserSessionRepresentation
  2120. http://www.keycloak.org/docs-api/18.0/rest-api/index.html#_usersessionrepresentation
  2121. :return: UserSessionRepresentation
  2122. """
  2123. params_path = {"realm-name": self.realm_name, "id": client_id}
  2124. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path))
  2125. return raise_error_from_response(data_raw, KeycloakGetError)
  2126. def get_client_sessions_stats(self):
  2127. """Get current session count for all clients with active sessions.
  2128. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientsessionstats
  2129. :return: Dict of clients and session count
  2130. """
  2131. params_path = {"realm-name": self.realm_name}
  2132. data_raw = self.raw_get(urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path))
  2133. return raise_error_from_response(data_raw, KeycloakGetError)
  2134. def get_client_management_permissions(self, client_id):
  2135. """Get management permissions for a client.
  2136. :param client_id: id in ClientRepresentation
  2137. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2138. :return: Keycloak server response
  2139. """
  2140. params_path = {"realm-name": self.realm_name, "id": client_id}
  2141. data_raw = self.raw_get(
  2142. urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path)
  2143. )
  2144. return raise_error_from_response(data_raw, KeycloakGetError)
  2145. def update_client_management_permissions(self, payload, client_id):
  2146. """Update management permissions for a client.
  2147. ManagementPermissionReference
  2148. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_managementpermissionreference
  2149. :param payload: ManagementPermissionReference
  2150. :param client_id: id in ClientRepresentation
  2151. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2152. :return: Keycloak server response
  2153. Payload example::
  2154. payload={
  2155. "enabled": true
  2156. }
  2157. """
  2158. params_path = {"realm-name": self.realm_name, "id": client_id}
  2159. data_raw = self.raw_put(
  2160. urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path),
  2161. data=json.dumps(payload),
  2162. )
  2163. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[200])
  2164. def get_client_authz_policy_scopes(self, client_id, policy_id):
  2165. """Get scopes for a given policy.
  2166. :param client_id: id in ClientRepresentation
  2167. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2168. :param policy_id: No Document
  2169. :return: Keycloak server response
  2170. """
  2171. params_path = {"realm-name": self.realm_name, "id": client_id, "policy-id": policy_id}
  2172. data_raw = self.raw_get(
  2173. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path)
  2174. )
  2175. return raise_error_from_response(data_raw, KeycloakGetError)
  2176. def get_client_authz_policy_resources(self, client_id, policy_id):
  2177. """Get resources for a given policy.
  2178. :param client_id: id in ClientRepresentation
  2179. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2180. :param policy_id: No Document
  2181. :return: Keycloak server response
  2182. """
  2183. params_path = {"realm-name": self.realm_name, "id": client_id, "policy-id": policy_id}
  2184. data_raw = self.raw_get(
  2185. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path)
  2186. )
  2187. return raise_error_from_response(data_raw, KeycloakGetError)
  2188. def get_client_authz_scope_permission(self, client_id, scope_id):
  2189. """Get permissions for a given scope.
  2190. :param client_id: id in ClientRepresentation
  2191. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2192. :param scope_id: No Document
  2193. :return: Keycloak server response
  2194. """
  2195. params_path = {"realm-name": self.realm_name, "id": client_id, "scope-id": scope_id}
  2196. data_raw = self.raw_get(
  2197. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path)
  2198. )
  2199. return raise_error_from_response(data_raw, KeycloakGetError)
  2200. def update_client_authz_scope_permission(self, payload, client_id, scope_id):
  2201. """Update permissions for a given scope.
  2202. :param payload: No Document
  2203. :param client_id: id in ClientRepresentation
  2204. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2205. :param scope_id: No Document
  2206. :return: Keycloak server response
  2207. Payload example::
  2208. payload={
  2209. "id": scope_id,
  2210. "name": "My Permission Name",
  2211. "type": "scope",
  2212. "logic": "POSITIVE",
  2213. "decisionStrategy": "UNANIMOUS",
  2214. "resources": [some_resource_id],
  2215. "scopes": [some_scope_id],
  2216. "policies": [some_policy_id],
  2217. }
  2218. """
  2219. params_path = {"realm-name": self.realm_name, "id": client_id, "scope-id": scope_id}
  2220. data_raw = self.raw_put(
  2221. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path),
  2222. data=json.dumps(payload),
  2223. )
  2224. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
  2225. def get_client_authz_client_policies(self, client_id):
  2226. """Get policies for a given client.
  2227. :param client_id: id in ClientRepresentation
  2228. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2229. :return: Keycloak server response (RoleRepresentation)
  2230. """
  2231. params_path = {"realm-name": self.realm_name, "id": client_id}
  2232. data_raw = self.raw_get(
  2233. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path)
  2234. )
  2235. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  2236. def create_client_authz_client_policy(self, payload, client_id):
  2237. """Create a new policy for a given client.
  2238. :param payload: No Document
  2239. :param client_id: id in ClientRepresentation
  2240. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  2241. :return: Keycloak server response (RoleRepresentation)
  2242. Payload example::
  2243. payload={
  2244. "type": "client",
  2245. "logic": "POSITIVE",
  2246. "decisionStrategy": "UNANIMOUS",
  2247. "name": "My Policy",
  2248. "clients": [other_client_id],
  2249. }
  2250. """
  2251. params_path = {"realm-name": self.realm_name, "id": client_id}
  2252. data_raw = self.raw_post(
  2253. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path),
  2254. data=json.dumps(payload),
  2255. )
  2256. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])