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.

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