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.

4606 lines
170 KiB

7 years ago
6 years ago
6 years ago
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
4 years ago
4 years ago
4 years ago
4 years ago
10 months ago
10 months 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
10 months ago
10 months ago
7 years ago
6 years ago
6 years ago
6 years ago
6 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
  1. # -*- coding: utf-8 -*-
  2. #
  3. # The MIT License (MIT)
  4. #
  5. # Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com>
  6. #
  7. # Permission is hereby granted, free of charge, to any person obtaining a copy of
  8. # this software and associated documentation files (the "Software"), to deal in
  9. # the Software without restriction, including without limitation the rights to
  10. # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
  11. # the Software, and to permit persons to whom the Software is furnished to do so,
  12. # subject to the following conditions:
  13. #
  14. # The above copyright notice and this permission notice shall be included in all
  15. # copies or substantial portions of the Software.
  16. #
  17. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
  19. # FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  20. # COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
  21. # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
  22. # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  23. # Unless otherwise stated in the comments, "id", in e.g. user_id, refers to the
  24. # internal Keycloak server ID, usually a uuid string
  25. """The keycloak admin module."""
  26. import copy
  27. import json
  28. from builtins import isinstance
  29. from typing import Optional
  30. import deprecation
  31. from requests_toolbelt import MultipartEncoder
  32. from . import urls_patterns
  33. from ._version import __version__
  34. from .exceptions import (
  35. KeycloakDeleteError,
  36. KeycloakGetError,
  37. KeycloakPostError,
  38. KeycloakPutError,
  39. raise_error_from_response,
  40. )
  41. from .openid_connection import KeycloakOpenIDConnection
  42. class KeycloakAdmin:
  43. """Keycloak Admin client.
  44. :param server_url: Keycloak server url
  45. :type server_url: str
  46. :param username: admin username
  47. :type username: str
  48. :param password: admin password
  49. :type password: str
  50. :param token: access and refresh tokens
  51. :type token: dict
  52. :param totp: Time based OTP
  53. :type totp: str
  54. :param realm_name: realm name
  55. :type realm_name: str
  56. :param client_id: client id
  57. :type client_id: str
  58. :param verify: Boolean value to enable or disable certificate validation or a string
  59. containing a path to a CA bundle to use
  60. :type verify: Union[bool,str]
  61. :param client_secret_key: client secret key
  62. (optional, required only for access type confidential)
  63. :type client_secret_key: str
  64. :param custom_headers: dict of custom header to pass to each HTML request
  65. :type custom_headers: dict
  66. :param user_realm_name: The realm name of the user, if different from realm_name
  67. :type user_realm_name: str
  68. :param auto_refresh_token: list of methods that allows automatic token refresh.
  69. Ex: ['get', 'put', 'post', 'delete']
  70. :type auto_refresh_token: list
  71. :param timeout: connection timeout in seconds
  72. :type timeout: int
  73. :param connection: A KeycloakOpenIDConnection as an alternative to individual params.
  74. :type connection: KeycloakOpenIDConnection
  75. """
  76. PAGE_SIZE = 100
  77. _auto_refresh_token = None
  78. _connection: Optional[KeycloakOpenIDConnection] = None
  79. def __init__(
  80. self,
  81. server_url=None,
  82. username=None,
  83. password=None,
  84. token=None,
  85. totp=None,
  86. realm_name="master",
  87. client_id="admin-cli",
  88. verify=True,
  89. client_secret_key=None,
  90. custom_headers=None,
  91. user_realm_name=None,
  92. auto_refresh_token=None,
  93. timeout=60,
  94. connection: Optional[KeycloakOpenIDConnection] = None,
  95. ):
  96. """Init method.
  97. :param server_url: Keycloak server url
  98. :type server_url: str
  99. :param username: admin username
  100. :type username: str
  101. :param password: admin password
  102. :type password: str
  103. :param token: access and refresh tokens
  104. :type token: dict
  105. :param totp: Time based OTP
  106. :type totp: str
  107. :param realm_name: realm name
  108. :type realm_name: str
  109. :param client_id: client id
  110. :type client_id: str
  111. :param verify: Boolean value to enable or disable certificate validation or a string
  112. containing a path to a CA bundle to use
  113. :type verify: Union[bool,str]
  114. :param client_secret_key: client secret key
  115. (optional, required only for access type confidential)
  116. :type client_secret_key: str
  117. :param custom_headers: dict of custom header to pass to each HTML request
  118. :type custom_headers: dict
  119. :param user_realm_name: The realm name of the user, if different from realm_name
  120. :type user_realm_name: str
  121. :param auto_refresh_token: list of methods that allows automatic token refresh.
  122. Ex: ['get', 'put', 'post', 'delete']
  123. :type auto_refresh_token: list
  124. :param timeout: connection timeout in seconds
  125. :type timeout: int
  126. :param connection: An OpenID Connection as an alternative to individual params.
  127. :type connection: KeycloakOpenIDConnection
  128. """
  129. self.connection = connection or KeycloakOpenIDConnection(
  130. server_url=server_url,
  131. username=username,
  132. password=password,
  133. token=token,
  134. totp=totp,
  135. realm_name=realm_name,
  136. client_id=client_id,
  137. verify=verify,
  138. client_secret_key=client_secret_key,
  139. user_realm_name=user_realm_name,
  140. custom_headers=custom_headers,
  141. timeout=timeout,
  142. )
  143. if auto_refresh_token is not None:
  144. self.auto_refresh_token = auto_refresh_token
  145. @property
  146. @deprecation.deprecated(
  147. deprecated_in="2.13.0",
  148. removed_in="4.0.0",
  149. current_version=__version__,
  150. details="Use the connection.server_url property instead",
  151. )
  152. def server_url(self):
  153. """Get server url.
  154. :returns: Keycloak server url
  155. :rtype: str
  156. """
  157. return self.connection.server_url
  158. @server_url.setter
  159. @deprecation.deprecated(
  160. deprecated_in="2.13.0",
  161. removed_in="4.0.0",
  162. current_version=__version__,
  163. details="Use the connection.server_url property instead",
  164. )
  165. def server_url(self, value):
  166. self.connection.server_url = value
  167. @property
  168. @deprecation.deprecated(
  169. deprecated_in="2.13.0",
  170. removed_in="4.0.0",
  171. current_version=__version__,
  172. details="Use the connection.realm_name property instead",
  173. )
  174. def realm_name(self):
  175. """Get realm name.
  176. :returns: Realm name
  177. :rtype: str
  178. """
  179. return self.connection.realm_name
  180. @realm_name.setter
  181. @deprecation.deprecated(
  182. deprecated_in="2.13.0",
  183. removed_in="4.0.0",
  184. current_version=__version__,
  185. details="Use the connection.realm_name property instead",
  186. )
  187. def realm_name(self, value):
  188. self.connection.realm_name = value
  189. @property
  190. def connection(self) -> KeycloakOpenIDConnection:
  191. """Get connection.
  192. :returns: Connection manager
  193. :rtype: KeycloakOpenIDConnection
  194. """
  195. return self._connection
  196. @connection.setter
  197. def connection(self, value: KeycloakOpenIDConnection) -> None:
  198. self._connection = value
  199. @property
  200. @deprecation.deprecated(
  201. deprecated_in="2.13.0",
  202. removed_in="4.0.0",
  203. current_version=__version__,
  204. details="Use the connection.client_id property instead",
  205. )
  206. def client_id(self):
  207. """Get client id.
  208. :returns: Client id
  209. :rtype: str
  210. """
  211. return self.connection.client_id
  212. @client_id.setter
  213. @deprecation.deprecated(
  214. deprecated_in="2.13.0",
  215. removed_in="4.0.0",
  216. current_version=__version__,
  217. details="Use the connection.client_id property instead",
  218. )
  219. def client_id(self, value):
  220. self.connection.client_id = value
  221. @property
  222. @deprecation.deprecated(
  223. deprecated_in="2.13.0",
  224. removed_in="4.0.0",
  225. current_version=__version__,
  226. details="Use the connection.client_secret_key property instead",
  227. )
  228. def client_secret_key(self):
  229. """Get client secret key.
  230. :returns: Client secret key
  231. :rtype: str
  232. """
  233. return self.connection.client_secret_key
  234. @client_secret_key.setter
  235. @deprecation.deprecated(
  236. deprecated_in="2.13.0",
  237. removed_in="4.0.0",
  238. current_version=__version__,
  239. details="Use the connection.client_secret_key property instead",
  240. )
  241. def client_secret_key(self, value):
  242. self.connection.client_secret_key = value
  243. @property
  244. @deprecation.deprecated(
  245. deprecated_in="2.13.0",
  246. removed_in="4.0.0",
  247. current_version=__version__,
  248. details="Use the connection.verify property instead",
  249. )
  250. def verify(self):
  251. """Get verify.
  252. :returns: Verify indicator
  253. :rtype: bool
  254. """
  255. return self.connection.verify
  256. @verify.setter
  257. @deprecation.deprecated(
  258. deprecated_in="2.13.0",
  259. removed_in="4.0.0",
  260. current_version=__version__,
  261. details="Use the connection.verify property instead",
  262. )
  263. def verify(self, value):
  264. self.connection.verify = value
  265. @property
  266. @deprecation.deprecated(
  267. deprecated_in="2.13.0",
  268. removed_in="4.0.0",
  269. current_version=__version__,
  270. details="Use the connection.username property instead",
  271. )
  272. def username(self):
  273. """Get username.
  274. :returns: Admin username
  275. :rtype: str
  276. """
  277. return self.connection.username
  278. @username.setter
  279. @deprecation.deprecated(
  280. deprecated_in="2.13.0",
  281. removed_in="4.0.0",
  282. current_version=__version__,
  283. details="Use the connection.username property instead",
  284. )
  285. def username(self, value):
  286. self.connection.username = value
  287. @property
  288. @deprecation.deprecated(
  289. deprecated_in="2.13.0",
  290. removed_in="4.0.0",
  291. current_version=__version__,
  292. details="Use the connection.password property instead",
  293. )
  294. def password(self):
  295. """Get password.
  296. :returns: Admin password
  297. :rtype: str
  298. """
  299. return self.connection.password
  300. @password.setter
  301. @deprecation.deprecated(
  302. deprecated_in="2.13.0",
  303. removed_in="4.0.0",
  304. current_version=__version__,
  305. details="Use the connection.password property instead",
  306. )
  307. def password(self, value):
  308. self.connection.password = value
  309. @property
  310. @deprecation.deprecated(
  311. deprecated_in="2.13.0",
  312. removed_in="4.0.0",
  313. current_version=__version__,
  314. details="Use the connection.totp property instead",
  315. )
  316. def totp(self):
  317. """Get totp.
  318. :returns: TOTP
  319. :rtype: str
  320. """
  321. return self.connection.totp
  322. @totp.setter
  323. @deprecation.deprecated(
  324. deprecated_in="2.13.0",
  325. removed_in="4.0.0",
  326. current_version=__version__,
  327. details="Use the connection.totp property instead",
  328. )
  329. def totp(self, value):
  330. self.connection.totp = value
  331. @property
  332. @deprecation.deprecated(
  333. deprecated_in="2.13.0",
  334. removed_in="4.0.0",
  335. current_version=__version__,
  336. details="Use the connection.token property instead",
  337. )
  338. def token(self):
  339. """Get token.
  340. :returns: Access and refresh token
  341. :rtype: dict
  342. """
  343. return self.connection.token
  344. @token.setter
  345. @deprecation.deprecated(
  346. deprecated_in="2.13.0",
  347. removed_in="4.0.0",
  348. current_version=__version__,
  349. details="Use the connection.token property instead",
  350. )
  351. def token(self, value):
  352. self.connection.token = value
  353. @property
  354. @deprecation.deprecated(
  355. deprecated_in="2.13.0",
  356. removed_in="4.0.0",
  357. current_version=__version__,
  358. details="Use the connection.user_realm_name property instead",
  359. )
  360. def user_realm_name(self):
  361. """Get user realm name.
  362. :returns: User realm name
  363. :rtype: str
  364. """
  365. return self.connection.user_realm_name
  366. @user_realm_name.setter
  367. @deprecation.deprecated(
  368. deprecated_in="2.13.0",
  369. removed_in="4.0.0",
  370. current_version=__version__,
  371. details="Use the connection.user_realm_name property instead",
  372. )
  373. def user_realm_name(self, value):
  374. self.connection.user_realm_name = value
  375. @property
  376. @deprecation.deprecated(
  377. deprecated_in="2.13.0",
  378. removed_in="4.0.0",
  379. current_version=__version__,
  380. details="Use the connection.custom_headers property instead",
  381. )
  382. def custom_headers(self):
  383. """Get custom headers.
  384. :returns: Custom headers
  385. :rtype: dict
  386. """
  387. return self.connection.custom_headers
  388. @custom_headers.setter
  389. @deprecation.deprecated(
  390. deprecated_in="2.13.0",
  391. removed_in="4.0.0",
  392. current_version=__version__,
  393. details="Use the connection.custom_headers property instead",
  394. )
  395. def custom_headers(self, value):
  396. self.connection.custom_headers = value
  397. @property
  398. @deprecation.deprecated(
  399. deprecated_in="2.13.0",
  400. removed_in="4.0.0",
  401. current_version=__version__,
  402. details="Auto-refresh will be implicitly set for all requests",
  403. )
  404. def auto_refresh_token(self):
  405. """Get auto refresh token.
  406. :returns: List of methods for automatic token refresh
  407. :rtype: list
  408. """
  409. return self._auto_refresh_token
  410. @auto_refresh_token.setter
  411. @deprecation.deprecated(
  412. deprecated_in="2.13.0",
  413. removed_in="4.0.0",
  414. current_version=__version__,
  415. details="Auto-refresh will be implicitly set for all requests",
  416. )
  417. def auto_refresh_token(self, value):
  418. self._auto_refresh_token = value or []
  419. def __fetch_all(self, url, query=None):
  420. """Paginate over get requests.
  421. Wrapper function to paginate GET requests.
  422. :param url: The url on which the query is executed
  423. :type url: str
  424. :param query: Existing query parameters (optional)
  425. :type query: dict
  426. :return: Combined results of paginated queries
  427. :rtype: list
  428. """
  429. results = []
  430. # initialize query if it was called with None
  431. if not query:
  432. query = {}
  433. page = 0
  434. query["max"] = self.PAGE_SIZE
  435. # fetch until we can
  436. while True:
  437. query["first"] = page * self.PAGE_SIZE
  438. partial_results = raise_error_from_response(
  439. self.connection.raw_get(url, **query), KeycloakGetError
  440. )
  441. if not partial_results:
  442. break
  443. results.extend(partial_results)
  444. if len(partial_results) < query["max"]:
  445. break
  446. page += 1
  447. return results
  448. def __fetch_paginated(self, url, query=None):
  449. """Make a specific paginated request.
  450. :param url: The url on which the query is executed
  451. :type url: str
  452. :param query: Pagination settings
  453. :type query: dict
  454. :returns: Response
  455. :rtype: dict
  456. """
  457. query = query or {}
  458. return raise_error_from_response(self.connection.raw_get(url, **query), KeycloakGetError)
  459. def get_current_realm(self) -> str:
  460. """Return the currently configured realm.
  461. :returns: Currently configured realm name
  462. :rtype: str
  463. """
  464. return self.connection.realm_name
  465. def change_current_realm(self, realm_name: str) -> None:
  466. """Change the current realm.
  467. :param realm_name: The name of the realm to be configured as current
  468. :type realm_name: str
  469. """
  470. self.connection.realm_name = realm_name
  471. def import_realm(self, payload):
  472. """Import a new realm from a RealmRepresentation.
  473. Realm name must be unique.
  474. RealmRepresentation
  475. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  476. :param payload: RealmRepresentation
  477. :type payload: dict
  478. :return: RealmRepresentation
  479. :rtype: dict
  480. """
  481. data_raw = self.connection.raw_post(
  482. urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload)
  483. )
  484. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  485. def partial_import_realm(self, realm_name, payload):
  486. """Partial import realm configuration from PartialImportRepresentation.
  487. Realm partialImport is used for modifying configuration of existing realm.
  488. PartialImportRepresentation
  489. https://www.keycloak.org/docs-api/18.0/rest-api/#_partialimportrepresentation
  490. :param realm_name: Realm name (not the realm id)
  491. :type realm_name: str
  492. :param payload: PartialImportRepresentation
  493. :type payload: dict
  494. :return: PartialImportResponse
  495. :rtype: dict
  496. """
  497. params_path = {"realm-name": realm_name}
  498. data_raw = self.connection.raw_post(
  499. urls_patterns.URL_ADMIN_REALM_PARTIAL_IMPORT.format(**params_path),
  500. data=json.dumps(payload),
  501. )
  502. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200])
  503. def export_realm(self, export_clients=False, export_groups_and_role=False):
  504. """Export the realm configurations in the json format.
  505. RealmRepresentation
  506. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_partialexport
  507. :param export_clients: Skip if not want to export realm clients
  508. :type export_clients: bool
  509. :param export_groups_and_role: Skip if not want to export realm groups and roles
  510. :type export_groups_and_role: bool
  511. :return: realm configurations JSON
  512. :rtype: dict
  513. """
  514. params_path = {
  515. "realm-name": self.connection.realm_name,
  516. "export-clients": export_clients,
  517. "export-groups-and-roles": export_groups_and_role,
  518. }
  519. data_raw = self.connection.raw_post(
  520. urls_patterns.URL_ADMIN_REALM_EXPORT.format(**params_path), data=""
  521. )
  522. return raise_error_from_response(data_raw, KeycloakPostError)
  523. def get_realms(self):
  524. """List all realms in Keycloak deployment.
  525. :return: realms list
  526. :rtype: list
  527. """
  528. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALMS)
  529. return raise_error_from_response(data_raw, KeycloakGetError)
  530. def get_realm(self, realm_name):
  531. """Get a specific realm.
  532. RealmRepresentation:
  533. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
  534. :param realm_name: Realm name (not the realm id)
  535. :type realm_name: str
  536. :return: RealmRepresentation
  537. :rtype: dict
  538. """
  539. params_path = {"realm-name": realm_name}
  540. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_REALM.format(**params_path))
  541. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  542. def create_realm(self, payload, skip_exists=False):
  543. """Create a realm.
  544. RealmRepresentation:
  545. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  546. :param payload: RealmRepresentation
  547. :type payload: dict
  548. :param skip_exists: Skip if Realm already exist.
  549. :type skip_exists: bool
  550. :return: Keycloak server response (RealmRepresentation)
  551. :rtype: dict
  552. """
  553. data_raw = self.connection.raw_post(
  554. urls_patterns.URL_ADMIN_REALMS, data=json.dumps(payload)
  555. )
  556. return raise_error_from_response(
  557. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  558. )
  559. def update_realm(self, realm_name, payload):
  560. """Update a realm.
  561. This will only update top level attributes and will ignore any user,
  562. role, or client information in the payload.
  563. RealmRepresentation:
  564. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmrepresentation
  565. :param realm_name: Realm name (not the realm id)
  566. :type realm_name: str
  567. :param payload: RealmRepresentation
  568. :type payload: dict
  569. :return: Http response
  570. :rtype: dict
  571. """
  572. params_path = {"realm-name": realm_name}
  573. data_raw = self.connection.raw_put(
  574. urls_patterns.URL_ADMIN_REALM.format(**params_path), data=json.dumps(payload)
  575. )
  576. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  577. def delete_realm(self, realm_name):
  578. """Delete a realm.
  579. :param realm_name: Realm name (not the realm id)
  580. :type realm_name: str
  581. :return: Http response
  582. :rtype: dict
  583. """
  584. params_path = {"realm-name": realm_name}
  585. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_REALM.format(**params_path))
  586. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  587. def get_users(self, query=None):
  588. """Get all users.
  589. Return a list of users, filtered according to query parameters
  590. UserRepresentation
  591. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  592. :param query: Query parameters (optional)
  593. :type query: dict
  594. :return: users list
  595. :rtype: list
  596. """
  597. query = query or {}
  598. params_path = {"realm-name": self.connection.realm_name}
  599. url = urls_patterns.URL_ADMIN_USERS.format(**params_path)
  600. if "first" in query or "max" in query:
  601. return self.__fetch_paginated(url, query)
  602. return self.__fetch_all(url, query)
  603. def create_idp(self, payload):
  604. """Create an ID Provider.
  605. IdentityProviderRepresentation
  606. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
  607. :param: payload: IdentityProviderRepresentation
  608. :type payload: dict
  609. :returns: Keycloak server response
  610. :rtype: dict
  611. """
  612. params_path = {"realm-name": self.connection.realm_name}
  613. data_raw = self.connection.raw_post(
  614. urls_patterns.URL_ADMIN_IDPS.format(**params_path), data=json.dumps(payload)
  615. )
  616. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  617. def update_idp(self, idp_alias, payload):
  618. """Update an ID Provider.
  619. IdentityProviderRepresentation
  620. https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_identity_providers_resource
  621. :param: idp_alias: alias for IdP to update
  622. :type idp_alias: str
  623. :param: payload: The IdentityProviderRepresentation
  624. :type payload: dict
  625. :returns: Keycloak server response
  626. :rtype: dict
  627. """
  628. params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias}
  629. data_raw = self.connection.raw_put(
  630. urls_patterns.URL_ADMIN_IDP.format(**params_path), data=json.dumps(payload)
  631. )
  632. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  633. def add_mapper_to_idp(self, idp_alias, payload):
  634. """Create an ID Provider.
  635. IdentityProviderRepresentation
  636. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityprovidermapperrepresentation
  637. :param: idp_alias: alias for Idp to add mapper in
  638. :type idp_alias: str
  639. :param: payload: IdentityProviderMapperRepresentation
  640. :type payload: dict
  641. :returns: Keycloak server response
  642. :rtype: dict
  643. """
  644. params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias}
  645. data_raw = self.connection.raw_post(
  646. urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path), data=json.dumps(payload)
  647. )
  648. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  649. def update_mapper_in_idp(self, idp_alias, mapper_id, payload):
  650. """Update an IdP mapper.
  651. IdentityProviderMapperRepresentation
  652. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_update
  653. :param: idp_alias: alias for Idp to fetch mappers
  654. :type idp_alias: str
  655. :param: mapper_id: Mapper Id to update
  656. :type mapper_id: str
  657. :param: payload: IdentityProviderMapperRepresentation
  658. :type payload: dict
  659. :return: Http response
  660. :rtype: dict
  661. """
  662. params_path = {
  663. "realm-name": self.connection.realm_name,
  664. "idp-alias": idp_alias,
  665. "mapper-id": mapper_id,
  666. }
  667. data_raw = self.connection.raw_put(
  668. urls_patterns.URL_ADMIN_IDP_MAPPER_UPDATE.format(**params_path),
  669. data=json.dumps(payload),
  670. )
  671. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  672. def get_idp_mappers(self, idp_alias):
  673. """Get IDP mappers.
  674. Returns a list of ID Providers mappers
  675. IdentityProviderMapperRepresentation
  676. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmappers
  677. :param: idp_alias: alias for Idp to fetch mappers
  678. :type idp_alias: str
  679. :return: array IdentityProviderMapperRepresentation
  680. :rtype: list
  681. """
  682. params_path = {"realm-name": self.connection.realm_name, "idp-alias": idp_alias}
  683. data_raw = self.connection.raw_get(
  684. urls_patterns.URL_ADMIN_IDP_MAPPERS.format(**params_path)
  685. )
  686. return raise_error_from_response(data_raw, KeycloakGetError)
  687. def get_idps(self):
  688. """Get IDPs.
  689. Returns a list of ID Providers,
  690. IdentityProviderRepresentation
  691. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
  692. :return: array IdentityProviderRepresentation
  693. :rtype: list
  694. """
  695. params_path = {"realm-name": self.connection.realm_name}
  696. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDPS.format(**params_path))
  697. return raise_error_from_response(data_raw, KeycloakGetError)
  698. def get_idp(self, idp_alias):
  699. """Get IDP provider.
  700. Get the representation of a specific IDP Provider.
  701. IdentityProviderRepresentation
  702. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_identityproviderrepresentation
  703. :param: idp_alias: alias for IdP to get
  704. :type idp_alias: str
  705. :return: IdentityProviderRepresentation
  706. :rtype: dict
  707. """
  708. params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias}
  709. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_IDP.format(**params_path))
  710. return raise_error_from_response(data_raw, KeycloakGetError)
  711. def delete_idp(self, idp_alias):
  712. """Delete an ID Provider.
  713. :param: idp_alias: idp alias name
  714. :type idp_alias: str
  715. :returns: Keycloak server response
  716. :rtype: dict
  717. """
  718. params_path = {"realm-name": self.connection.realm_name, "alias": idp_alias}
  719. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_IDP.format(**params_path))
  720. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  721. def create_user(self, payload, exist_ok=False):
  722. """Create a new user.
  723. Username must be unique
  724. UserRepresentation
  725. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  726. :param payload: UserRepresentation
  727. :type payload: dict
  728. :param exist_ok: If False, raise KeycloakGetError if username already exists.
  729. Otherwise, return existing user ID.
  730. :type exist_ok: bool
  731. :return: UserRepresentation
  732. :rtype: dict
  733. """
  734. params_path = {"realm-name": self.connection.realm_name}
  735. if exist_ok:
  736. exists = self.get_user_id(username=payload["username"])
  737. if exists is not None:
  738. return str(exists)
  739. data_raw = self.connection.raw_post(
  740. urls_patterns.URL_ADMIN_USERS.format(**params_path), data=json.dumps(payload)
  741. )
  742. raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  743. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  744. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  745. def users_count(self, query=None):
  746. """Count users.
  747. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_users_resource
  748. :param query: (dict) Query parameters for users count
  749. :type query: dict
  750. :return: counter
  751. :rtype: int
  752. """
  753. query = query or dict()
  754. params_path = {"realm-name": self.connection.realm_name}
  755. data_raw = self.connection.raw_get(
  756. urls_patterns.URL_ADMIN_USERS_COUNT.format(**params_path), **query
  757. )
  758. return raise_error_from_response(data_raw, KeycloakGetError)
  759. def get_user_id(self, username):
  760. """Get internal keycloak user id from username.
  761. This is required for further actions against this user.
  762. UserRepresentation
  763. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  764. :param username: id in UserRepresentation
  765. :type username: str
  766. :return: user_id
  767. :rtype: str
  768. """
  769. lower_user_name = username.lower()
  770. users = self.get_users(query={"username": lower_user_name, "max": 1, "exact": True})
  771. return users[0]["id"] if len(users) == 1 else None
  772. def get_user(self, user_id):
  773. """Get representation of the user.
  774. UserRepresentation
  775. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userrepresentation
  776. :param user_id: User id
  777. :type user_id: str
  778. :return: UserRepresentation
  779. """
  780. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  781. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_USER.format(**params_path))
  782. return raise_error_from_response(data_raw, KeycloakGetError)
  783. def get_user_groups(self, user_id, query=None, brief_representation=True):
  784. """Get user groups.
  785. Returns a list of groups of which the user is a member
  786. :param user_id: User id
  787. :type user_id: str
  788. :param query: Additional query options
  789. :type query: dict
  790. :param brief_representation: whether to omit attributes in the response
  791. :type brief_representation: bool
  792. :return: user groups list
  793. :rtype: list
  794. """
  795. query = query or {}
  796. params = {"briefRepresentation": brief_representation}
  797. query.update(params)
  798. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  799. url = urls_patterns.URL_ADMIN_USER_GROUPS.format(**params_path)
  800. if "first" in query or "max" in query:
  801. return self.__fetch_paginated(url, query)
  802. return self.__fetch_all(url, query)
  803. def update_user(self, user_id, payload):
  804. """Update the user.
  805. :param user_id: User id
  806. :type user_id: str
  807. :param payload: UserRepresentation
  808. :type payload: dict
  809. :return: Http response
  810. :rtype: bytes
  811. """
  812. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  813. data_raw = self.connection.raw_put(
  814. urls_patterns.URL_ADMIN_USER.format(**params_path), data=json.dumps(payload)
  815. )
  816. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  817. def disable_user(self, user_id):
  818. """Disable the user from the realm. Disabled users can not log in.
  819. :param user_id: User id
  820. :type user_id: str
  821. :return: Http response
  822. :rtype: bytes
  823. """
  824. return self.update_user(user_id=user_id, payload={"enabled": False})
  825. def enable_user(self, user_id):
  826. """Enable the user from the realm.
  827. :param user_id: User id
  828. :type user_id: str
  829. :return: Http response
  830. :rtype: bytes
  831. """
  832. return self.update_user(user_id=user_id, payload={"enabled": True})
  833. def disable_all_users(self):
  834. """Disable all existing users."""
  835. users = self.get_users()
  836. for user in users:
  837. user_id = user["id"]
  838. self.disable_user(user_id=user_id)
  839. def enable_all_users(self):
  840. """Disable all existing users."""
  841. users = self.get_users()
  842. for user in users:
  843. user_id = user["id"]
  844. self.enable_user(user_id=user_id)
  845. def delete_user(self, user_id):
  846. """Delete the user.
  847. :param user_id: User id
  848. :type user_id: str
  849. :return: Http response
  850. :rtype: bytes
  851. """
  852. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  853. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_USER.format(**params_path))
  854. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  855. def set_user_password(self, user_id, password, temporary=True):
  856. """Set up a password for the user.
  857. If temporary is True, the user will have to reset
  858. the temporary password next time they log in.
  859. https://www.keycloak.org/docs-api/18.0/rest-api/#_users_resource
  860. https://www.keycloak.org/docs-api/18.0/rest-api/#_credentialrepresentation
  861. :param user_id: User id
  862. :type user_id: str
  863. :param password: New password
  864. :type password: str
  865. :param temporary: True if password is temporary
  866. :type temporary: bool
  867. :returns: Response
  868. :rtype: dict
  869. """
  870. payload = {"type": "password", "temporary": temporary, "value": password}
  871. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  872. data_raw = self.connection.raw_put(
  873. urls_patterns.URL_ADMIN_RESET_PASSWORD.format(**params_path), data=json.dumps(payload)
  874. )
  875. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  876. def get_credentials(self, user_id):
  877. """Get user credentials.
  878. Returns a list of credential belonging to the user.
  879. CredentialRepresentation
  880. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_credentialrepresentation
  881. :param: user_id: user id
  882. :type user_id: str
  883. :returns: Keycloak server response (CredentialRepresentation)
  884. :rtype: dict
  885. """
  886. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  887. data_raw = self.connection.raw_get(
  888. urls_patterns.URL_ADMIN_USER_CREDENTIALS.format(**params_path)
  889. )
  890. return raise_error_from_response(data_raw, KeycloakGetError)
  891. def delete_credential(self, user_id, credential_id):
  892. """Delete credential of the user.
  893. CredentialRepresentation
  894. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_credentialrepresentation
  895. :param: user_id: user id
  896. :type user_id: str
  897. :param: credential_id: credential id
  898. :type credential_id: str
  899. :return: Keycloak server response (ClientRepresentation)
  900. :rtype: bytes
  901. """
  902. params_path = {
  903. "realm-name": self.connection.realm_name,
  904. "id": user_id,
  905. "credential_id": credential_id,
  906. }
  907. data_raw = self.connection.raw_delete(
  908. urls_patterns.URL_ADMIN_USER_CREDENTIAL.format(**params_path)
  909. )
  910. return raise_error_from_response(data_raw, KeycloakDeleteError)
  911. def user_logout(self, user_id):
  912. """Log out the user.
  913. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_logout
  914. :param user_id: User id
  915. :type user_id: str
  916. :returns: Keycloak server response
  917. :rtype: bytes
  918. """
  919. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  920. data_raw = self.connection.raw_post(
  921. urls_patterns.URL_ADMIN_USER_LOGOUT.format(**params_path), data=""
  922. )
  923. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  924. def user_consents(self, user_id):
  925. """Get consents granted by the user.
  926. UserConsentRepresentation
  927. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_userconsentrepresentation
  928. :param user_id: User id
  929. :type user_id: str
  930. :returns: List of UserConsentRepresentations
  931. :rtype: list
  932. """
  933. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  934. data_raw = self.connection.raw_get(
  935. urls_patterns.URL_ADMIN_USER_CONSENTS.format(**params_path)
  936. )
  937. return raise_error_from_response(data_raw, KeycloakGetError)
  938. def get_user_social_logins(self, user_id):
  939. """Get user social logins.
  940. Returns a list of federated identities/social logins of which the user has been associated
  941. with
  942. :param user_id: User id
  943. :type user_id: str
  944. :returns: Federated identities list
  945. :rtype: list
  946. """
  947. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  948. data_raw = self.connection.raw_get(
  949. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path)
  950. )
  951. return raise_error_from_response(data_raw, KeycloakGetError)
  952. def add_user_social_login(self, user_id, provider_id, provider_userid, provider_username):
  953. """Add a federated identity / social login provider to the user.
  954. :param user_id: User id
  955. :type user_id: str
  956. :param provider_id: Social login provider id
  957. :type provider_id: str
  958. :param provider_userid: userid specified by the provider
  959. :type provider_userid: str
  960. :param provider_username: username specified by the provider
  961. :type provider_username: str
  962. :returns: Keycloak server response
  963. :rtype: bytes
  964. """
  965. payload = {
  966. "identityProvider": provider_id,
  967. "userId": provider_userid,
  968. "userName": provider_username,
  969. }
  970. params_path = {
  971. "realm-name": self.connection.realm_name,
  972. "id": user_id,
  973. "provider": provider_id,
  974. }
  975. data_raw = self.connection.raw_post(
  976. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path),
  977. data=json.dumps(payload),
  978. )
  979. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201, 204])
  980. def delete_user_social_login(self, user_id, provider_id):
  981. """Delete a federated identity / social login provider from the user.
  982. :param user_id: User id
  983. :type user_id: str
  984. :param provider_id: Social login provider id
  985. :type provider_id: str
  986. :returns: Keycloak server response
  987. :rtype: bytes
  988. """
  989. params_path = {
  990. "realm-name": self.connection.realm_name,
  991. "id": user_id,
  992. "provider": provider_id,
  993. }
  994. data_raw = self.connection.raw_delete(
  995. urls_patterns.URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path)
  996. )
  997. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  998. def send_update_account(
  999. self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None
  1000. ):
  1001. """Send an update account email to the user.
  1002. An email contains a link the user can click to perform a set of required actions.
  1003. :param user_id: User id
  1004. :type user_id: str
  1005. :param payload: A list of actions for the user to complete
  1006. :type payload: list
  1007. :param client_id: Client id (optional)
  1008. :type client_id: str
  1009. :param lifespan: Number of seconds after which the generated token expires (optional)
  1010. :type lifespan: int
  1011. :param redirect_uri: The redirect uri (optional)
  1012. :type redirect_uri: str
  1013. :returns: Keycloak server response
  1014. :rtype: bytes
  1015. """
  1016. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  1017. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  1018. data_raw = self.connection.raw_put(
  1019. urls_patterns.URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  1020. data=json.dumps(payload),
  1021. **params_query,
  1022. )
  1023. return raise_error_from_response(data_raw, KeycloakPutError)
  1024. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  1025. """Send a update account email to the user.
  1026. An email contains a link the user can click to perform a set of required actions.
  1027. :param user_id: User id
  1028. :type user_id: str
  1029. :param client_id: Client id (optional)
  1030. :type client_id: str
  1031. :param redirect_uri: Redirect uri (optional)
  1032. :type redirect_uri: str
  1033. :returns: Keycloak server response
  1034. :rtype: bytes
  1035. """
  1036. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  1037. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  1038. data_raw = self.connection.raw_put(
  1039. urls_patterns.URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  1040. data={},
  1041. **params_query,
  1042. )
  1043. return raise_error_from_response(data_raw, KeycloakPutError)
  1044. def get_sessions(self, user_id):
  1045. """Get sessions associated with the user.
  1046. UserSessionRepresentation
  1047. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_usersessionrepresentation
  1048. :param user_id: Id of user
  1049. :type user_id: str
  1050. :return: UserSessionRepresentation
  1051. :rtype: dict
  1052. """
  1053. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  1054. data_raw = self.connection.raw_get(
  1055. urls_patterns.URL_ADMIN_GET_SESSIONS.format(**params_path)
  1056. )
  1057. return raise_error_from_response(data_raw, KeycloakGetError)
  1058. def get_server_info(self):
  1059. """Get themes, social providers, etc. on this server.
  1060. ServerInfoRepresentation
  1061. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_serverinforepresentation
  1062. :return: ServerInfoRepresentation
  1063. :rtype: dict
  1064. """
  1065. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_SERVER_INFO)
  1066. return raise_error_from_response(data_raw, KeycloakGetError)
  1067. def get_groups(self, query=None):
  1068. """Get groups.
  1069. Returns a list of groups belonging to the realm
  1070. GroupRepresentation
  1071. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1072. :param query: Additional query options
  1073. :type query: dict
  1074. :return: array GroupRepresentation
  1075. :rtype: list
  1076. """
  1077. query = query or {}
  1078. params_path = {"realm-name": self.connection.realm_name}
  1079. url = urls_patterns.URL_ADMIN_GROUPS.format(**params_path)
  1080. if "first" in query or "max" in query:
  1081. groups = self.__fetch_paginated(url, query)
  1082. groups = self.__fetch_all(url, query)
  1083. # For version +23.0.0
  1084. for group in groups:
  1085. if group.get("subGroupCount"):
  1086. group["subGroups"] = self.get_group_children(group.get("id"))
  1087. return groups
  1088. def get_group(self, group_id):
  1089. """Get group by id.
  1090. Returns full group details
  1091. GroupRepresentation
  1092. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1093. :param group_id: The group id
  1094. :type group_id: str
  1095. :return: Keycloak server response (GroupRepresentation)
  1096. :rtype: dict
  1097. """
  1098. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1099. response = self.connection.raw_get(urls_patterns.URL_ADMIN_GROUP.format(**params_path))
  1100. if response.status_code >= 400:
  1101. return raise_error_from_response(response, KeycloakGetError)
  1102. # For version +23.0.0
  1103. group = response.json()
  1104. if group.get("subGroupCount"):
  1105. group["subGroups"] = self.get_group_children(group.get("id"))
  1106. return group
  1107. def get_subgroups(self, group, path):
  1108. """Get subgroups.
  1109. Utility function to iterate through nested group structures
  1110. GroupRepresentation
  1111. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1112. :param group: group (GroupRepresentation)
  1113. :type group: dict
  1114. :param path: group path (string)
  1115. :type path: str
  1116. :return: Keycloak server response (GroupRepresentation)
  1117. :rtype: dict
  1118. """
  1119. for subgroup in group["subGroups"]:
  1120. if subgroup["path"] == path:
  1121. return subgroup
  1122. elif subgroup["subGroups"]:
  1123. for subgroup in group["subGroups"]:
  1124. result = self.get_subgroups(subgroup, path)
  1125. if result:
  1126. return result
  1127. # went through the tree without hits
  1128. return None
  1129. def get_group_children(self, group_id):
  1130. """Get group children by id.
  1131. Returns full group children details
  1132. :param group_id: The group id
  1133. :type group_id: str
  1134. :return: Keycloak server response (GroupRepresentation)
  1135. :rtype: dict
  1136. """
  1137. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1138. data_raw = self.connection.raw_get(
  1139. urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path)
  1140. )
  1141. return raise_error_from_response(data_raw, KeycloakGetError)
  1142. def get_group_members(self, group_id, query=None):
  1143. """Get members by group id.
  1144. Returns group members
  1145. GroupRepresentation
  1146. https://www.keycloak.org/docs-api/18.0/rest-api/#_userrepresentation
  1147. :param group_id: The group id
  1148. :type group_id: str
  1149. :param query: Additional query parameters
  1150. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getmembers)
  1151. :type query: dict
  1152. :return: Keycloak server response (UserRepresentation)
  1153. :rtype: list
  1154. """
  1155. query = query or {}
  1156. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1157. url = urls_patterns.URL_ADMIN_GROUP_MEMBERS.format(**params_path)
  1158. if "first" in query or "max" in query:
  1159. return self.__fetch_paginated(url, query)
  1160. return self.__fetch_all(url, query)
  1161. def get_group_by_path(self, path):
  1162. """Get group id based on name or path.
  1163. Returns full group details for a group defined by path
  1164. GroupRepresentation
  1165. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1166. :param path: group path
  1167. :type path: str
  1168. :return: Keycloak server response (GroupRepresentation)
  1169. :rtype: dict
  1170. """
  1171. params_path = {"realm-name": self.connection.realm_name, "path": path}
  1172. data_raw = self.connection.raw_get(
  1173. urls_patterns.URL_ADMIN_GROUP_BY_PATH.format(**params_path)
  1174. )
  1175. return raise_error_from_response(data_raw, KeycloakGetError)
  1176. def create_group(self, payload, parent=None, skip_exists=False):
  1177. """Create a group in the Realm.
  1178. GroupRepresentation
  1179. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1180. :param payload: GroupRepresentation
  1181. :type payload: dict
  1182. :param parent: parent group's id. Required to create a sub-group.
  1183. :type parent: str
  1184. :param skip_exists: If true then do not raise an error if it already exists
  1185. :type skip_exists: bool
  1186. :return: Group id for newly created group or None for an existing group
  1187. :rtype: str
  1188. """
  1189. if parent is None:
  1190. params_path = {"realm-name": self.connection.realm_name}
  1191. data_raw = self.connection.raw_post(
  1192. urls_patterns.URL_ADMIN_GROUPS.format(**params_path), data=json.dumps(payload)
  1193. )
  1194. else:
  1195. params_path = {"realm-name": self.connection.realm_name, "id": parent}
  1196. data_raw = self.connection.raw_post(
  1197. urls_patterns.URL_ADMIN_GROUP_CHILD.format(**params_path), data=json.dumps(payload)
  1198. )
  1199. raise_error_from_response(
  1200. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1201. )
  1202. try:
  1203. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1204. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1205. except KeyError:
  1206. return
  1207. def update_group(self, group_id, payload):
  1208. """Update group, ignores subgroups.
  1209. GroupRepresentation
  1210. https://www.keycloak.org/docs-api/18.0/rest-api/#_grouprepresentation
  1211. :param group_id: id of group
  1212. :type group_id: str
  1213. :param payload: GroupRepresentation with updated information.
  1214. :type payload: dict
  1215. :return: Http response
  1216. :rtype: bytes
  1217. """
  1218. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1219. data_raw = self.connection.raw_put(
  1220. urls_patterns.URL_ADMIN_GROUP.format(**params_path), data=json.dumps(payload)
  1221. )
  1222. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1223. def group_set_permissions(self, group_id, enabled=True):
  1224. """Enable/Disable permissions for a group.
  1225. Cannot delete group if disabled
  1226. :param group_id: id of group
  1227. :type group_id: str
  1228. :param enabled: Enabled flag
  1229. :type enabled: bool
  1230. :return: Keycloak server response
  1231. :rtype: bytes
  1232. """
  1233. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1234. data_raw = self.connection.raw_put(
  1235. urls_patterns.URL_ADMIN_GROUP_PERMISSIONS.format(**params_path),
  1236. data=json.dumps({"enabled": enabled}),
  1237. )
  1238. return raise_error_from_response(data_raw, KeycloakPutError)
  1239. def group_user_add(self, user_id, group_id):
  1240. """Add user to group (user_id and group_id).
  1241. :param user_id: id of user
  1242. :type user_id: str
  1243. :param group_id: id of group to add to
  1244. :type group_id: str
  1245. :return: Keycloak server response
  1246. :rtype: bytes
  1247. """
  1248. params_path = {
  1249. "realm-name": self.connection.realm_name,
  1250. "id": user_id,
  1251. "group-id": group_id,
  1252. }
  1253. data_raw = self.connection.raw_put(
  1254. urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path), data=None
  1255. )
  1256. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1257. def group_user_remove(self, user_id, group_id):
  1258. """Remove user from group (user_id and group_id).
  1259. :param user_id: id of user
  1260. :type user_id: str
  1261. :param group_id: id of group to remove from
  1262. :type group_id: str
  1263. :return: Keycloak server response
  1264. :rtype: bytes
  1265. """
  1266. params_path = {
  1267. "realm-name": self.connection.realm_name,
  1268. "id": user_id,
  1269. "group-id": group_id,
  1270. }
  1271. data_raw = self.connection.raw_delete(
  1272. urls_patterns.URL_ADMIN_USER_GROUP.format(**params_path)
  1273. )
  1274. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1275. def delete_group(self, group_id):
  1276. """Delete a group in the Realm.
  1277. :param group_id: id of group to delete
  1278. :type group_id: str
  1279. :return: Keycloak server response
  1280. :rtype: bytes
  1281. """
  1282. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  1283. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_GROUP.format(**params_path))
  1284. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1285. def get_clients(self):
  1286. """Get clients.
  1287. Returns a list of clients belonging to the realm
  1288. ClientRepresentation
  1289. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1290. :return: Keycloak server response (ClientRepresentation)
  1291. :rtype: list
  1292. """
  1293. params_path = {"realm-name": self.connection.realm_name}
  1294. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENTS.format(**params_path))
  1295. return raise_error_from_response(data_raw, KeycloakGetError)
  1296. def get_client(self, client_id):
  1297. """Get representation of the client.
  1298. ClientRepresentation
  1299. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1300. :param client_id: id of client (not client-id)
  1301. :type client_id: str
  1302. :return: Keycloak server response (ClientRepresentation)
  1303. :rtype: dict
  1304. """
  1305. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1306. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_CLIENT.format(**params_path))
  1307. return raise_error_from_response(data_raw, KeycloakGetError)
  1308. def get_client_id(self, client_id):
  1309. """Get internal keycloak client id from client-id.
  1310. This is required for further actions against this client.
  1311. :param client_id: clientId in ClientRepresentation
  1312. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1313. :type client_id: str
  1314. :return: client_id (uuid as string)
  1315. :rtype: str
  1316. """
  1317. clients = self.get_clients()
  1318. for client in clients:
  1319. if client_id == client.get("clientId"):
  1320. return client["id"]
  1321. return None
  1322. def get_client_authz_settings(self, client_id):
  1323. """Get authorization json from client.
  1324. :param client_id: id in ClientRepresentation
  1325. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1326. :type client_id: str
  1327. :return: Keycloak server response
  1328. :rtype: dict
  1329. """
  1330. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1331. data_raw = self.connection.raw_get(
  1332. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path)
  1333. )
  1334. return raise_error_from_response(data_raw, KeycloakGetError)
  1335. def create_client_authz_resource(self, client_id, payload, skip_exists=False):
  1336. """Create resources of client.
  1337. :param client_id: id in ClientRepresentation
  1338. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1339. :type client_id: str
  1340. :param payload: ResourceRepresentation
  1341. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  1342. :type payload: dict
  1343. :param skip_exists: Skip the creation in case the resource exists
  1344. :type skip_exists: bool
  1345. :return: Keycloak server response
  1346. :rtype: bytes
  1347. """
  1348. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1349. data_raw = self.connection.raw_post(
  1350. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path),
  1351. data=json.dumps(payload),
  1352. )
  1353. return raise_error_from_response(
  1354. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1355. )
  1356. def update_client_authz_resource(self, client_id, resource_id, payload):
  1357. """Update resource of client.
  1358. Any parameter missing from the ResourceRepresentation in the payload WILL be set
  1359. to default by the Keycloak server.
  1360. :param client_id: id in ClientRepresentation
  1361. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1362. :type client_id: str
  1363. :param payload: ResourceRepresentation
  1364. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  1365. :type payload: dict
  1366. :param client_id: id in ClientRepresentation
  1367. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1368. :type client_id: str
  1369. :param resource_id: id in ResourceRepresentation
  1370. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  1371. :type resource_id: str
  1372. :return: Keycloak server response
  1373. :rtype: bytes
  1374. """
  1375. params_path = {
  1376. "realm-name": self.connection.realm_name,
  1377. "id": client_id,
  1378. "resource-id": resource_id,
  1379. }
  1380. data_raw = self.connection.raw_put(
  1381. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path),
  1382. data=json.dumps(payload),
  1383. )
  1384. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1385. def delete_client_authz_resource(self, client_id: str, resource_id: str):
  1386. """Delete a client resource.
  1387. :param client_id: id in ClientRepresentation
  1388. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1389. :type client_id: str
  1390. :param resource_id: id in ResourceRepresentation
  1391. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  1392. :type resource_id: str
  1393. :return: Keycloak server response
  1394. :rtype: bytes
  1395. """
  1396. params_path = {
  1397. "realm-name": self.connection.realm_name,
  1398. "id": client_id,
  1399. "resource-id": resource_id,
  1400. }
  1401. data_raw = self.connection.raw_delete(
  1402. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path)
  1403. )
  1404. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1405. def get_client_authz_resources(self, client_id):
  1406. """Get resources from client.
  1407. :param client_id: id in ClientRepresentation
  1408. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1409. :type client_id: str
  1410. :return: Keycloak server response (ResourceRepresentation)
  1411. :rtype: list
  1412. """
  1413. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1414. data_raw = self.connection.raw_get(
  1415. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path)
  1416. )
  1417. return raise_error_from_response(data_raw, KeycloakGetError)
  1418. def get_client_authz_resource(self, client_id: str, resource_id: str):
  1419. """Get a client resource.
  1420. :param client_id: id in ClientRepresentation
  1421. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1422. :type client_id: str
  1423. :param resource_id: id in ResourceRepresentation
  1424. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_resourcerepresentation
  1425. :type resource_id: str
  1426. :return: Keycloak server response (ResourceRepresentation)
  1427. :rtype: dict
  1428. """
  1429. params_path = {
  1430. "realm-name": self.connection.realm_name,
  1431. "id": client_id,
  1432. "resource-id": resource_id,
  1433. }
  1434. data_raw = self.connection.raw_get(
  1435. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE.format(**params_path)
  1436. )
  1437. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  1438. def create_client_authz_role_based_policy(self, client_id, payload, skip_exists=False):
  1439. """Create role-based policy of client.
  1440. Payload example::
  1441. payload={
  1442. "type": "role",
  1443. "logic": "POSITIVE",
  1444. "decisionStrategy": "UNANIMOUS",
  1445. "name": "Policy-1",
  1446. "roles": [
  1447. {
  1448. "id": id
  1449. }
  1450. ]
  1451. }
  1452. :param client_id: id in ClientRepresentation
  1453. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1454. :type client_id: str
  1455. :param payload: No Document
  1456. :type payload: dict
  1457. :param skip_exists: Skip creation in case the object exists
  1458. :type skip_exists: bool
  1459. :return: Keycloak server response
  1460. :rtype: bytes
  1461. """
  1462. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1463. data_raw = self.connection.raw_post(
  1464. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_ROLE_BASED_POLICY.format(**params_path),
  1465. data=json.dumps(payload),
  1466. )
  1467. return raise_error_from_response(
  1468. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1469. )
  1470. def create_client_authz_policy(self, client_id, payload, skip_exists=False):
  1471. """Create an authz policy of client.
  1472. Payload example::
  1473. payload={
  1474. "name": "Policy-time-based",
  1475. "type": "time",
  1476. "logic": "POSITIVE",
  1477. "decisionStrategy": "UNANIMOUS",
  1478. "config": {
  1479. "hourEnd": "18",
  1480. "hour": "9"
  1481. }
  1482. }
  1483. :param client_id: id in ClientRepresentation
  1484. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1485. :type client_id: str
  1486. :param payload: No Document
  1487. :type payload: dict
  1488. :param skip_exists: Skip creation in case the object exists
  1489. :type skip_exists: bool
  1490. :return: Keycloak server response
  1491. :rtype: bytes
  1492. """
  1493. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1494. data_raw = self.connection.raw_post(
  1495. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICIES.format(**params_path),
  1496. data=json.dumps(payload),
  1497. )
  1498. return raise_error_from_response(
  1499. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1500. )
  1501. def create_client_authz_resource_based_permission(self, client_id, payload, skip_exists=False):
  1502. """Create resource-based permission of client.
  1503. Payload example::
  1504. payload={
  1505. "type": "resource",
  1506. "logic": "POSITIVE",
  1507. "decisionStrategy": "UNANIMOUS",
  1508. "name": "Permission-Name",
  1509. "resources": [
  1510. resource_id
  1511. ],
  1512. "policies": [
  1513. policy_id
  1514. ]
  1515. :param client_id: id in ClientRepresentation
  1516. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1517. :type client_id: str
  1518. :param payload: PolicyRepresentation
  1519. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_policyrepresentation
  1520. :type payload: dict
  1521. :param skip_exists: Skip creation in case the object already exists
  1522. :type skip_exists: bool
  1523. :return: Keycloak server response
  1524. :rtype: bytes
  1525. """
  1526. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1527. data_raw = self.connection.raw_post(
  1528. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_RESOURCE_BASED_PERMISSION.format(**params_path),
  1529. data=json.dumps(payload),
  1530. )
  1531. return raise_error_from_response(
  1532. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1533. )
  1534. def get_client_authz_scopes(self, client_id):
  1535. """Get scopes from client.
  1536. :param client_id: id in ClientRepresentation
  1537. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1538. :type client_id: str
  1539. :return: Keycloak server response
  1540. :rtype: list
  1541. """
  1542. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1543. data_raw = self.connection.raw_get(
  1544. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path)
  1545. )
  1546. return raise_error_from_response(data_raw, KeycloakGetError)
  1547. def create_client_authz_scopes(self, client_id, payload):
  1548. """Create scopes for client.
  1549. :param client_id: id in ClientRepresentation
  1550. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1551. :param payload: ScopeRepresentation
  1552. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_ScopeRepresentation
  1553. :type payload: dict
  1554. :type client_id: str
  1555. :return: Keycloak server response
  1556. :rtype: bytes
  1557. """
  1558. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1559. data_raw = self.connection.raw_post(
  1560. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPES.format(**params_path),
  1561. data=json.dumps(payload),
  1562. )
  1563. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  1564. def get_client_authz_permissions(self, client_id):
  1565. """Get permissions from client.
  1566. :param client_id: id in ClientRepresentation
  1567. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1568. :type client_id: str
  1569. :return: Keycloak server response
  1570. :rtype: list
  1571. """
  1572. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1573. data_raw = self.connection.raw_get(
  1574. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_PERMISSIONS.format(**params_path)
  1575. )
  1576. return raise_error_from_response(data_raw, KeycloakGetError)
  1577. def get_client_authz_policies(self, client_id):
  1578. """Get policies from client.
  1579. :param client_id: id in ClientRepresentation
  1580. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1581. :type client_id: str
  1582. :return: Keycloak server response
  1583. :rtype: list
  1584. """
  1585. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1586. data_raw = self.connection.raw_get(
  1587. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICIES.format(**params_path)
  1588. )
  1589. return raise_error_from_response(data_raw, KeycloakGetError)
  1590. def delete_client_authz_policy(self, client_id, policy_id):
  1591. """Delete a policy from client.
  1592. :param client_id: id in ClientRepresentation
  1593. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1594. :type client_id: str
  1595. :param policy_id: id in PolicyRepresentation
  1596. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_policyrepresentation
  1597. :type policy_id: str
  1598. :return: Keycloak server response
  1599. :rtype: dict
  1600. """
  1601. params_path = {
  1602. "realm-name": self.connection.realm_name,
  1603. "id": client_id,
  1604. "policy-id": policy_id,
  1605. }
  1606. data_raw = self.connection.raw_delete(
  1607. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path)
  1608. )
  1609. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1610. def get_client_authz_policy(self, client_id, policy_id):
  1611. """Get a policy from client.
  1612. :param client_id: id in ClientRepresentation
  1613. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1614. :type client_id: str
  1615. :param policy_id: id in PolicyRepresentation
  1616. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_policyrepresentation
  1617. :type policy_id: str
  1618. :return: Keycloak server response
  1619. :rtype: dict
  1620. """
  1621. params_path = {
  1622. "realm-name": self.connection.realm_name,
  1623. "id": client_id,
  1624. "policy-id": policy_id,
  1625. }
  1626. data_raw = self.connection.raw_get(
  1627. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY.format(**params_path)
  1628. )
  1629. return raise_error_from_response(data_raw, KeycloakGetError)
  1630. def get_client_service_account_user(self, client_id):
  1631. """Get service account user from client.
  1632. :param client_id: id in ClientRepresentation
  1633. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1634. :type client_id: str
  1635. :return: UserRepresentation
  1636. :rtype: dict
  1637. """
  1638. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1639. data_raw = self.connection.raw_get(
  1640. urls_patterns.URL_ADMIN_CLIENT_SERVICE_ACCOUNT_USER.format(**params_path)
  1641. )
  1642. return raise_error_from_response(data_raw, KeycloakGetError)
  1643. def get_client_default_client_scopes(self, client_id):
  1644. """Get all default client scopes from client.
  1645. :param client_id: id of the client in which the new default client scope should be added
  1646. :type client_id: str
  1647. :return: list of client scopes with id and name
  1648. :rtype: list
  1649. """
  1650. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1651. data_raw = self.connection.raw_get(
  1652. urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPES.format(**params_path)
  1653. )
  1654. return raise_error_from_response(data_raw, KeycloakGetError)
  1655. def add_client_default_client_scope(self, client_id, client_scope_id, payload):
  1656. """Add a client scope to the default client scopes from client.
  1657. Payload example::
  1658. payload={
  1659. "realm":"testrealm",
  1660. "client":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  1661. "clientScopeId":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
  1662. }
  1663. :param client_id: id of the client in which the new default client scope should be added
  1664. :type client_id: str
  1665. :param client_scope_id: id of the new client scope that should be added
  1666. :type client_scope_id: str
  1667. :param payload: dictionary with realm, client and clientScopeId
  1668. :type payload: dict
  1669. :return: Http response
  1670. :rtype: bytes
  1671. """
  1672. params_path = {
  1673. "realm-name": self.connection.realm_name,
  1674. "id": client_id,
  1675. "client_scope_id": client_scope_id,
  1676. }
  1677. data_raw = self.connection.raw_put(
  1678. urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path),
  1679. data=json.dumps(payload),
  1680. )
  1681. return raise_error_from_response(data_raw, KeycloakPutError)
  1682. def delete_client_default_client_scope(self, client_id, client_scope_id):
  1683. """Delete a client scope from the default client scopes of the client.
  1684. :param client_id: id of the client in which the default client scope should be deleted
  1685. :type client_id: str
  1686. :param client_scope_id: id of the client scope that should be deleted
  1687. :type client_scope_id: str
  1688. :return: list of client scopes with id and name
  1689. :rtype: list
  1690. """
  1691. params_path = {
  1692. "realm-name": self.connection.realm_name,
  1693. "id": client_id,
  1694. "client_scope_id": client_scope_id,
  1695. }
  1696. data_raw = self.connection.raw_delete(
  1697. urls_patterns.URL_ADMIN_CLIENT_DEFAULT_CLIENT_SCOPE.format(**params_path)
  1698. )
  1699. return raise_error_from_response(data_raw, KeycloakDeleteError)
  1700. def get_client_optional_client_scopes(self, client_id):
  1701. """Get all optional client scopes from client.
  1702. :param client_id: id of the client in which the new optional client scope should be added
  1703. :type client_id: str
  1704. :return: list of client scopes with id and name
  1705. :rtype: list
  1706. """
  1707. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1708. data_raw = self.connection.raw_get(
  1709. urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPES.format(**params_path)
  1710. )
  1711. return raise_error_from_response(data_raw, KeycloakGetError)
  1712. def add_client_optional_client_scope(self, client_id, client_scope_id, payload):
  1713. """Add a client scope to the optional client scopes from client.
  1714. Payload example::
  1715. payload={
  1716. "realm":"testrealm",
  1717. "client":"aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
  1718. "clientScopeId":"bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
  1719. }
  1720. :param client_id: id of the client in which the new optional client scope should be added
  1721. :type client_id: str
  1722. :param client_scope_id: id of the new client scope that should be added
  1723. :type client_scope_id: str
  1724. :param payload: dictionary with realm, client and clientScopeId
  1725. :type payload: dict
  1726. :return: Http response
  1727. :rtype: bytes
  1728. """
  1729. params_path = {
  1730. "realm-name": self.connection.realm_name,
  1731. "id": client_id,
  1732. "client_scope_id": client_scope_id,
  1733. }
  1734. data_raw = self.connection.raw_put(
  1735. urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path),
  1736. data=json.dumps(payload),
  1737. )
  1738. return raise_error_from_response(data_raw, KeycloakPutError)
  1739. def delete_client_optional_client_scope(self, client_id, client_scope_id):
  1740. """Delete a client scope from the optional client scopes of the client.
  1741. :param client_id: id of the client in which the optional client scope should be deleted
  1742. :type client_id: str
  1743. :param client_scope_id: id of the client scope that should be deleted
  1744. :type client_scope_id: str
  1745. :return: list of client scopes with id and name
  1746. :rtype: list
  1747. """
  1748. params_path = {
  1749. "realm-name": self.connection.realm_name,
  1750. "id": client_id,
  1751. "client_scope_id": client_scope_id,
  1752. }
  1753. data_raw = self.connection.raw_delete(
  1754. urls_patterns.URL_ADMIN_CLIENT_OPTIONAL_CLIENT_SCOPE.format(**params_path)
  1755. )
  1756. return raise_error_from_response(data_raw, KeycloakDeleteError)
  1757. def create_initial_access_token(self, count: int = 1, expiration: int = 1):
  1758. """Create an initial access token.
  1759. :param count: Number of clients that can be registered
  1760. :type count: int
  1761. :param expiration: Days until expireation
  1762. :type expiration: int
  1763. :return: initial access token
  1764. :rtype: str
  1765. """
  1766. payload = {"count": count, "expiration": expiration}
  1767. params_path = {"realm-name": self.connection.realm_name}
  1768. data_raw = self.connection.raw_post(
  1769. urls_patterns.URL_ADMIN_CLIENT_INITIAL_ACCESS.format(**params_path),
  1770. data=json.dumps(payload),
  1771. )
  1772. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200])
  1773. def create_client(self, payload, skip_exists=False):
  1774. """Create a client.
  1775. ClientRepresentation:
  1776. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1777. :param skip_exists: If true then do not raise an error if client already exists
  1778. :type skip_exists: bool
  1779. :param payload: ClientRepresentation
  1780. :type payload: dict
  1781. :return: Client ID
  1782. :rtype: str
  1783. """
  1784. if skip_exists:
  1785. client_id = self.get_client_id(client_id=payload["clientId"])
  1786. if client_id is not None:
  1787. return client_id
  1788. params_path = {"realm-name": self.connection.realm_name}
  1789. data_raw = self.connection.raw_post(
  1790. urls_patterns.URL_ADMIN_CLIENTS.format(**params_path), data=json.dumps(payload)
  1791. )
  1792. raise_error_from_response(
  1793. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  1794. )
  1795. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  1796. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  1797. def update_client(self, client_id, payload):
  1798. """Update a client.
  1799. :param client_id: Client id
  1800. :type client_id: str
  1801. :param payload: ClientRepresentation
  1802. :type payload: dict
  1803. :return: Http response
  1804. :rtype: bytes
  1805. """
  1806. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1807. data_raw = self.connection.raw_put(
  1808. urls_patterns.URL_ADMIN_CLIENT.format(**params_path), data=json.dumps(payload)
  1809. )
  1810. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  1811. def delete_client(self, client_id):
  1812. """Get representation of the client.
  1813. ClientRepresentation
  1814. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  1815. :param client_id: keycloak client id (not oauth client-id)
  1816. :type client_id: str
  1817. :return: Keycloak server response (ClientRepresentation)
  1818. :rtype: bytes
  1819. """
  1820. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1821. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_CLIENT.format(**params_path))
  1822. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  1823. def get_client_installation_provider(self, client_id, provider_id):
  1824. """Get content for given installation provider.
  1825. Related documentation:
  1826. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource
  1827. Possible provider_id list available in the ServerInfoRepresentation#clientInstallations
  1828. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_serverinforepresentation
  1829. :param client_id: Client id
  1830. :type client_id: str
  1831. :param provider_id: provider id to specify response format
  1832. :type provider_id: str
  1833. :returns: Installation providers
  1834. :rtype: list
  1835. """
  1836. params_path = {
  1837. "realm-name": self.connection.realm_name,
  1838. "id": client_id,
  1839. "provider-id": provider_id,
  1840. }
  1841. data_raw = self.connection.raw_get(
  1842. urls_patterns.URL_ADMIN_CLIENT_INSTALLATION_PROVIDER.format(**params_path)
  1843. )
  1844. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  1845. def get_realm_roles(self, brief_representation=True, search_text=""):
  1846. """Get all roles for the realm or client.
  1847. RoleRepresentation
  1848. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1849. :param brief_representation: whether to omit role attributes in the response
  1850. :type brief_representation: bool
  1851. :param search_text: optional search text to limit the returned result.
  1852. :type search_text: str
  1853. :return: Keycloak server response (RoleRepresentation)
  1854. :rtype: list
  1855. """
  1856. url = urls_patterns.URL_ADMIN_REALM_ROLES
  1857. params_path = {"realm-name": self.connection.realm_name}
  1858. params = {"briefRepresentation": brief_representation}
  1859. data_raw = self.connection.raw_get(
  1860. urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), **params
  1861. )
  1862. # set the search_text path param, if it is a valid string
  1863. if search_text is not None and search_text.strip() != "":
  1864. params_path["search-text"] = search_text
  1865. url = urls_patterns.URL_ADMIN_REALM_ROLES_SEARCH
  1866. data_raw = self.connection.raw_get(url.format(**params_path), **params)
  1867. return raise_error_from_response(data_raw, KeycloakGetError)
  1868. def get_realm_role_groups(self, role_name, query=None, brief_representation=True):
  1869. """Get role groups of realm by role name.
  1870. :param role_name: Name of the role.
  1871. :type role_name: str
  1872. :param query: Additional Query parameters
  1873. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_parameters_226)
  1874. :type query: dict
  1875. :param brief_representation: whether to omit role attributes in the response
  1876. :type brief_representation: bool
  1877. :return: Keycloak Server Response (GroupRepresentation)
  1878. :rtype: list
  1879. """
  1880. query = query or {}
  1881. params = {"briefRepresentation": brief_representation}
  1882. query.update(params)
  1883. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  1884. url = urls_patterns.URL_ADMIN_REALM_ROLES_GROUPS.format(**params_path)
  1885. if "first" in query or "max" in query:
  1886. return self.__fetch_paginated(url, query)
  1887. return self.__fetch_all(url, query)
  1888. def get_realm_role_members(self, role_name, query=None):
  1889. """Get role members of realm by role name.
  1890. :param role_name: Name of the role.
  1891. :type role_name: str
  1892. :param query: Additional Query parameters
  1893. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_roles_resource)
  1894. :type query: dict
  1895. :return: Keycloak Server Response (UserRepresentation)
  1896. :rtype: list
  1897. """
  1898. query = query or dict()
  1899. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  1900. return self.__fetch_all(
  1901. urls_patterns.URL_ADMIN_REALM_ROLES_MEMBERS.format(**params_path), query
  1902. )
  1903. def get_default_realm_role_id(self):
  1904. """Get the ID of the default realm role.
  1905. :return: Realm role ID
  1906. :rtype: str
  1907. """
  1908. all_realm_roles = self.get_realm_roles()
  1909. default_realm_roles = [
  1910. realm_role
  1911. for realm_role in all_realm_roles
  1912. if realm_role["name"] == f"default-roles-{self.connection.realm_name}"
  1913. ]
  1914. return default_realm_roles[0]["id"]
  1915. def get_realm_default_roles(self):
  1916. """Get all the default realm roles.
  1917. :return: Keycloak Server Response (UserRepresentation)
  1918. :rtype: list
  1919. """
  1920. params_path = {
  1921. "realm-name": self.connection.realm_name,
  1922. "role-id": self.get_default_realm_role_id(),
  1923. }
  1924. data_raw = self.connection.raw_get(
  1925. urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES_REALM.format(**params_path)
  1926. )
  1927. return raise_error_from_response(data_raw, KeycloakGetError)
  1928. def remove_realm_default_roles(self, payload):
  1929. """Remove a set of default realm roles.
  1930. :param payload: List of RoleRepresentations
  1931. :type payload: list
  1932. :return: Keycloak Server Response
  1933. :rtype: dict
  1934. """
  1935. params_path = {
  1936. "realm-name": self.connection.realm_name,
  1937. "role-id": self.get_default_realm_role_id(),
  1938. }
  1939. data_raw = self.connection.raw_delete(
  1940. urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path),
  1941. data=json.dumps(payload),
  1942. )
  1943. return raise_error_from_response(data_raw, KeycloakDeleteError)
  1944. def add_realm_default_roles(self, payload):
  1945. """Add a set of default realm roles.
  1946. :param payload: List of RoleRepresentations
  1947. :type payload: list
  1948. :return: Keycloak Server Response
  1949. :rtype: dict
  1950. """
  1951. params_path = {
  1952. "realm-name": self.connection.realm_name,
  1953. "role-id": self.get_default_realm_role_id(),
  1954. }
  1955. data_raw = self.connection.raw_post(
  1956. urls_patterns.URL_ADMIN_REALM_ROLE_COMPOSITES.format(**params_path),
  1957. data=json.dumps(payload),
  1958. )
  1959. return raise_error_from_response(data_raw, KeycloakPostError)
  1960. def get_client_roles(self, client_id, brief_representation=True):
  1961. """Get all roles for the client.
  1962. RoleRepresentation
  1963. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1964. :param client_id: id of client (not client-id)
  1965. :type client_id: str
  1966. :param brief_representation: whether to omit role attributes in the response
  1967. :type brief_representation: bool
  1968. :return: Keycloak server response (RoleRepresentation)
  1969. :rtype: list
  1970. """
  1971. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  1972. params = {"briefRepresentation": brief_representation}
  1973. data_raw = self.connection.raw_get(
  1974. urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), **params
  1975. )
  1976. return raise_error_from_response(data_raw, KeycloakGetError)
  1977. def get_client_role(self, client_id, role_name):
  1978. """Get client role id by name.
  1979. This is required for further actions with this role.
  1980. RoleRepresentation
  1981. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  1982. :param client_id: id of client (not client-id)
  1983. :type client_id: str
  1984. :param role_name: role's name (not id!)
  1985. :type role_name: str
  1986. :return: role_id
  1987. :rtype: str
  1988. """
  1989. params_path = {
  1990. "realm-name": self.connection.realm_name,
  1991. "id": client_id,
  1992. "role-name": role_name,
  1993. }
  1994. data_raw = self.connection.raw_get(
  1995. urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path)
  1996. )
  1997. return raise_error_from_response(data_raw, KeycloakGetError)
  1998. def get_client_role_id(self, client_id, role_name):
  1999. """Get client role id by name.
  2000. This is required for further actions with this role.
  2001. RoleRepresentation
  2002. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2003. :param client_id: id of client (not client-id)
  2004. :type client_id: str
  2005. :param role_name: role's name (not id!)
  2006. :type role_name: str
  2007. :return: role_id
  2008. :rtype: str
  2009. """
  2010. role = self.get_client_role(client_id, role_name)
  2011. return role.get("id")
  2012. def create_client_role(self, client_role_id, payload, skip_exists=False):
  2013. """Create a client role.
  2014. RoleRepresentation
  2015. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2016. :param client_role_id: id of client (not client-id)
  2017. :type client_role_id: str
  2018. :param payload: RoleRepresentation
  2019. :type payload: dict
  2020. :param skip_exists: If true then do not raise an error if client role already exists
  2021. :type skip_exists: bool
  2022. :return: Client role name
  2023. :rtype: str
  2024. """
  2025. if skip_exists:
  2026. try:
  2027. res = self.get_client_role(client_id=client_role_id, role_name=payload["name"])
  2028. return res["name"]
  2029. except KeycloakGetError:
  2030. pass
  2031. params_path = {"realm-name": self.connection.realm_name, "id": client_role_id}
  2032. data_raw = self.connection.raw_post(
  2033. urls_patterns.URL_ADMIN_CLIENT_ROLES.format(**params_path), data=json.dumps(payload)
  2034. )
  2035. raise_error_from_response(
  2036. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  2037. )
  2038. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  2039. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  2040. def add_composite_client_roles_to_role(self, client_role_id, role_name, roles):
  2041. """Add composite roles to client role.
  2042. :param client_role_id: id of client (not client-id)
  2043. :type client_role_id: str
  2044. :param role_name: The name of the role
  2045. :type role_name: str
  2046. :param roles: roles list or role (use RoleRepresentation) to be updated
  2047. :type roles: list
  2048. :return: Keycloak server response
  2049. :rtype: bytes
  2050. """
  2051. payload = roles if isinstance(roles, list) else [roles]
  2052. params_path = {
  2053. "realm-name": self.connection.realm_name,
  2054. "id": client_role_id,
  2055. "role-name": role_name,
  2056. }
  2057. data_raw = self.connection.raw_post(
  2058. urls_patterns.URL_ADMIN_CLIENT_ROLES_COMPOSITE_CLIENT_ROLE.format(**params_path),
  2059. data=json.dumps(payload),
  2060. )
  2061. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2062. def update_client_role(self, client_id, role_name, payload):
  2063. """Update a client role.
  2064. RoleRepresentation
  2065. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2066. :param client_id: id of client (not client-id)
  2067. :type client_id: str
  2068. :param role_name: role's name (not id!)
  2069. :type role_name: str
  2070. :param payload: RoleRepresentation
  2071. :type payload: dict
  2072. :returns: Keycloak server response
  2073. :rtype: bytes
  2074. """
  2075. params_path = {
  2076. "realm-name": self.connection.realm_name,
  2077. "id": client_id,
  2078. "role-name": role_name,
  2079. }
  2080. data_raw = self.connection.raw_put(
  2081. urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path), data=json.dumps(payload)
  2082. )
  2083. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  2084. def delete_client_role(self, client_role_id, role_name):
  2085. """Delete a client role.
  2086. RoleRepresentation
  2087. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2088. :param client_role_id: id of client (not client-id)
  2089. :type client_role_id: str
  2090. :param role_name: role's name (not id!)
  2091. :type role_name: str
  2092. :returns: Keycloak server response
  2093. :rtype: bytes
  2094. """
  2095. params_path = {
  2096. "realm-name": self.connection.realm_name,
  2097. "id": client_role_id,
  2098. "role-name": role_name,
  2099. }
  2100. data_raw = self.connection.raw_delete(
  2101. urls_patterns.URL_ADMIN_CLIENT_ROLE.format(**params_path)
  2102. )
  2103. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2104. def assign_client_role(self, user_id, client_id, roles):
  2105. """Assign a client role to a user.
  2106. :param user_id: id of user
  2107. :type user_id: str
  2108. :param client_id: id of client (not client-id)
  2109. :type client_id: str
  2110. :param roles: roles list or role (use RoleRepresentation)
  2111. :type roles: list
  2112. :return: Keycloak server response
  2113. :rtype: bytes
  2114. """
  2115. payload = roles if isinstance(roles, list) else [roles]
  2116. params_path = {
  2117. "realm-name": self.connection.realm_name,
  2118. "id": user_id,
  2119. "client-id": client_id,
  2120. }
  2121. data_raw = self.connection.raw_post(
  2122. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  2123. data=json.dumps(payload),
  2124. )
  2125. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2126. def get_client_role_members(self, client_id, role_name, **query):
  2127. """Get members by client role.
  2128. :param client_id: The client id
  2129. :type client_id: str
  2130. :param role_name: the name of role to be queried.
  2131. :type role_name: str
  2132. :param query: Additional query parameters
  2133. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource)
  2134. :type query: dict
  2135. :return: Keycloak server response (UserRepresentation)
  2136. :rtype: list
  2137. """
  2138. params_path = {
  2139. "realm-name": self.connection.realm_name,
  2140. "id": client_id,
  2141. "role-name": role_name,
  2142. }
  2143. return self.__fetch_all(
  2144. urls_patterns.URL_ADMIN_CLIENT_ROLE_MEMBERS.format(**params_path), query
  2145. )
  2146. def get_client_role_groups(self, client_id, role_name, **query):
  2147. """Get group members by client role.
  2148. :param client_id: The client id
  2149. :type client_id: str
  2150. :param role_name: the name of role to be queried.
  2151. :type role_name: str
  2152. :param query: Additional query parameters
  2153. (see https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clients_resource)
  2154. :type query: dict
  2155. :return: Keycloak server response
  2156. :rtype: list
  2157. """
  2158. params_path = {
  2159. "realm-name": self.connection.realm_name,
  2160. "id": client_id,
  2161. "role-name": role_name,
  2162. }
  2163. return self.__fetch_all(
  2164. urls_patterns.URL_ADMIN_CLIENT_ROLE_GROUPS.format(**params_path), query
  2165. )
  2166. def get_role_by_id(self, role_id):
  2167. """Get a specific role’s representation.
  2168. RoleRepresentation
  2169. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2170. :param role_id: id of role
  2171. :type role_id: str
  2172. :return: Keycloak server response (RoleRepresentation)
  2173. :rtype: bytes
  2174. """
  2175. params_path = {"realm-name": self.connection.realm_name, "role-id": role_id}
  2176. data_raw = self.connection.raw_get(
  2177. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path)
  2178. )
  2179. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  2180. def update_role_by_id(self, role_id, payload):
  2181. """Update the role.
  2182. RoleRepresentation
  2183. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2184. :param payload: RoleRepresentation
  2185. :type payload: dict
  2186. :param role_id: id of role
  2187. :type role_id: str
  2188. :returns: Keycloak server response
  2189. :rtype: bytes
  2190. """
  2191. params_path = {"realm-name": self.connection.realm_name, "role-id": role_id}
  2192. data_raw = self.connection.raw_put(
  2193. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path),
  2194. data=json.dumps(payload),
  2195. )
  2196. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  2197. def delete_role_by_id(self, role_id):
  2198. """Delete a role by its id.
  2199. RoleRepresentation
  2200. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2201. :param role_id: id of role
  2202. :type role_id: str
  2203. :returns: Keycloak server response
  2204. :rtype: bytes
  2205. """
  2206. params_path = {"realm-name": self.connection.realm_name, "role-id": role_id}
  2207. data_raw = self.connection.raw_delete(
  2208. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path)
  2209. )
  2210. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2211. def create_realm_role(self, payload, skip_exists=False):
  2212. """Create a new role for the realm or client.
  2213. :param payload: The role (use RoleRepresentation)
  2214. :type payload: dict
  2215. :param skip_exists: If true then do not raise an error if realm role already exists
  2216. :type skip_exists: bool
  2217. :return: Realm role name
  2218. :rtype: str
  2219. """
  2220. if skip_exists:
  2221. try:
  2222. role = self.get_realm_role(role_name=payload["name"])
  2223. return role["name"]
  2224. except KeycloakGetError:
  2225. pass
  2226. params_path = {"realm-name": self.connection.realm_name}
  2227. data_raw = self.connection.raw_post(
  2228. urls_patterns.URL_ADMIN_REALM_ROLES.format(**params_path), data=json.dumps(payload)
  2229. )
  2230. raise_error_from_response(
  2231. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  2232. )
  2233. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  2234. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  2235. def get_realm_role(self, role_name):
  2236. """Get realm role by role name.
  2237. RoleRepresentation
  2238. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2239. :param role_name: role's name, not id!
  2240. :type role_name: str
  2241. :return: role
  2242. :rtype: dict
  2243. """
  2244. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2245. data_raw = self.connection.raw_get(
  2246. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path)
  2247. )
  2248. return raise_error_from_response(data_raw, KeycloakGetError)
  2249. def get_realm_role_by_id(self, role_id: str):
  2250. """Get realm role by role id.
  2251. RoleRepresentation
  2252. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_rolerepresentation
  2253. :param role_id: role's id, not name!
  2254. :type role_id: str
  2255. :return: role
  2256. :rtype: dict
  2257. """
  2258. params_path = {"realm-name": self.connection.realm_name, "role-id": role_id}
  2259. data_raw = self.connection.raw_get(
  2260. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_ID.format(**params_path)
  2261. )
  2262. return raise_error_from_response(data_raw, KeycloakGetError)
  2263. def update_realm_role(self, role_name, payload):
  2264. """Update a role for the realm by name.
  2265. :param role_name: The name of the role to be updated
  2266. :type role_name: str
  2267. :param payload: The role (use RoleRepresentation)
  2268. :type payload: dict
  2269. :return: Keycloak server response
  2270. :rtype: bytes
  2271. """
  2272. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2273. data_raw = self.connection.raw_put(
  2274. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path),
  2275. data=json.dumps(payload),
  2276. )
  2277. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  2278. def delete_realm_role(self, role_name):
  2279. """Delete a role for the realm by name.
  2280. :param role_name: The role name
  2281. :type role_name: str
  2282. :return: Keycloak server response
  2283. :rtype: bytes
  2284. """
  2285. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2286. data_raw = self.connection.raw_delete(
  2287. urls_patterns.URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path)
  2288. )
  2289. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2290. def add_composite_realm_roles_to_role(self, role_name, roles):
  2291. """Add composite roles to the role.
  2292. :param role_name: The name of the role
  2293. :type role_name: str
  2294. :param roles: roles list or role (use RoleRepresentation) to be updated
  2295. :type roles: list
  2296. :return: Keycloak server response
  2297. :rtype: bytes
  2298. """
  2299. payload = roles if isinstance(roles, list) else [roles]
  2300. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2301. data_raw = self.connection.raw_post(
  2302. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  2303. data=json.dumps(payload),
  2304. )
  2305. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2306. def remove_composite_realm_roles_to_role(self, role_name, roles):
  2307. """Remove composite roles from the role.
  2308. :param role_name: The name of the role
  2309. :type role_name: str
  2310. :param roles: roles list or role (use RoleRepresentation) to be removed
  2311. :type roles: list
  2312. :return: Keycloak server response
  2313. :rtype: bytes
  2314. """
  2315. payload = roles if isinstance(roles, list) else [roles]
  2316. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2317. data_raw = self.connection.raw_delete(
  2318. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  2319. data=json.dumps(payload),
  2320. )
  2321. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2322. def get_composite_realm_roles_of_role(self, role_name):
  2323. """Get composite roles of the role.
  2324. :param role_name: The name of the role
  2325. :type role_name: str
  2326. :return: Keycloak server response (array RoleRepresentation)
  2327. :rtype: list
  2328. """
  2329. params_path = {"realm-name": self.connection.realm_name, "role-name": role_name}
  2330. data_raw = self.connection.raw_get(
  2331. urls_patterns.URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path)
  2332. )
  2333. return raise_error_from_response(data_raw, KeycloakGetError)
  2334. def assign_realm_roles_to_client_scope(self, client_id, roles):
  2335. """Assign realm roles to a client's scope.
  2336. :param client_id: id of client (not client-id)
  2337. :type client_id: str
  2338. :param roles: roles list or role (use RoleRepresentation)
  2339. :type roles: list
  2340. :return: Keycloak server response
  2341. :rtype: dict
  2342. """
  2343. payload = roles if isinstance(roles, list) else [roles]
  2344. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  2345. data_raw = self.connection.raw_post(
  2346. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path),
  2347. data=json.dumps(payload),
  2348. )
  2349. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2350. def delete_realm_roles_of_client_scope(self, client_id, roles):
  2351. """Delete realm roles of a client's scope.
  2352. :param client_id: id of client (not client-id)
  2353. :type client_id: str
  2354. :param roles: roles list or role (use RoleRepresentation)
  2355. :type roles: list
  2356. :return: Keycloak server response
  2357. :rtype: dict
  2358. """
  2359. payload = roles if isinstance(roles, list) else [roles]
  2360. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  2361. data_raw = self.connection.raw_delete(
  2362. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path),
  2363. data=json.dumps(payload),
  2364. )
  2365. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2366. def get_realm_roles_of_client_scope(self, client_id):
  2367. """Get all realm roles for a client's scope.
  2368. :param client_id: id of client (not client-id)
  2369. :type client_id: str
  2370. :return: Keycloak server response (array RoleRepresentation)
  2371. :rtype: dict
  2372. """
  2373. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  2374. data_raw = self.connection.raw_get(
  2375. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_REALM_ROLES.format(**params_path)
  2376. )
  2377. return raise_error_from_response(data_raw, KeycloakGetError)
  2378. def assign_client_roles_to_client_scope(self, client_id, client_roles_owner_id, roles):
  2379. """Assign client roles to a client's scope.
  2380. :param client_id: id of client (not client-id) who is assigned the roles
  2381. :type client_id: str
  2382. :param client_roles_owner_id: id of client (not client-id) who has the roles
  2383. :type client_roles_owner_id: str
  2384. :param roles: roles list or role (use RoleRepresentation)
  2385. :type roles: list
  2386. :return: Keycloak server response
  2387. :rtype: dict
  2388. """
  2389. payload = roles if isinstance(roles, list) else [roles]
  2390. params_path = {
  2391. "realm-name": self.connection.realm_name,
  2392. "id": client_id,
  2393. "client": client_roles_owner_id,
  2394. }
  2395. data_raw = self.connection.raw_post(
  2396. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path),
  2397. data=json.dumps(payload),
  2398. )
  2399. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2400. def delete_client_roles_of_client_scope(self, client_id, client_roles_owner_id, roles):
  2401. """Delete client roles of a client's scope.
  2402. :param client_id: id of client (not client-id) who is assigned the roles
  2403. :type client_id: str
  2404. :param client_roles_owner_id: id of client (not client-id) who has the roles
  2405. :type client_roles_owner_id: str
  2406. :param roles: roles list or role (use RoleRepresentation)
  2407. :type roles: list
  2408. :return: Keycloak server response
  2409. :rtype: dict
  2410. """
  2411. payload = roles if isinstance(roles, list) else [roles]
  2412. params_path = {
  2413. "realm-name": self.connection.realm_name,
  2414. "id": client_id,
  2415. "client": client_roles_owner_id,
  2416. }
  2417. data_raw = self.connection.raw_delete(
  2418. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path),
  2419. data=json.dumps(payload),
  2420. )
  2421. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2422. def get_client_roles_of_client_scope(self, client_id, client_roles_owner_id):
  2423. """Get all client roles for a client's scope.
  2424. :param client_id: id of client (not client-id)
  2425. :type client_id: str
  2426. :param client_roles_owner_id: id of client (not client-id) who has the roles
  2427. :type client_roles_owner_id: str
  2428. :return: Keycloak server response (array RoleRepresentation)
  2429. :rtype: dict
  2430. """
  2431. params_path = {
  2432. "realm-name": self.connection.realm_name,
  2433. "id": client_id,
  2434. "client": client_roles_owner_id,
  2435. }
  2436. data_raw = self.connection.raw_get(
  2437. urls_patterns.URL_ADMIN_CLIENT_SCOPE_MAPPINGS_CLIENT_ROLES.format(**params_path)
  2438. )
  2439. return raise_error_from_response(data_raw, KeycloakGetError)
  2440. def assign_realm_roles(self, user_id, roles):
  2441. """Assign realm roles to a user.
  2442. :param user_id: id of user
  2443. :type user_id: str
  2444. :param roles: roles list or role (use RoleRepresentation)
  2445. :type roles: list
  2446. :return: Keycloak server response
  2447. :rtype: bytes
  2448. """
  2449. payload = roles if isinstance(roles, list) else [roles]
  2450. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  2451. data_raw = self.connection.raw_post(
  2452. urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path),
  2453. data=json.dumps(payload),
  2454. )
  2455. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2456. def delete_realm_roles_of_user(self, user_id, roles):
  2457. """Delete realm roles of a user.
  2458. :param user_id: id of user
  2459. :type user_id: str
  2460. :param roles: roles list or role (use RoleRepresentation)
  2461. :type roles: list
  2462. :return: Keycloak server response
  2463. :rtype: bytes
  2464. """
  2465. payload = roles if isinstance(roles, list) else [roles]
  2466. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  2467. data_raw = self.connection.raw_delete(
  2468. urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path),
  2469. data=json.dumps(payload),
  2470. )
  2471. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2472. def get_realm_roles_of_user(self, user_id):
  2473. """Get all realm roles for a user.
  2474. :param user_id: id of user
  2475. :type user_id: str
  2476. :return: Keycloak server response (array RoleRepresentation)
  2477. :rtype: list
  2478. """
  2479. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  2480. data_raw = self.connection.raw_get(
  2481. urls_patterns.URL_ADMIN_USER_REALM_ROLES.format(**params_path)
  2482. )
  2483. return raise_error_from_response(data_raw, KeycloakGetError)
  2484. def get_available_realm_roles_of_user(self, user_id):
  2485. """Get all available (i.e. unassigned) realm roles for a user.
  2486. :param user_id: id of user
  2487. :type user_id: str
  2488. :return: Keycloak server response (array RoleRepresentation)
  2489. :rtype: list
  2490. """
  2491. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  2492. data_raw = self.connection.raw_get(
  2493. urls_patterns.URL_ADMIN_USER_REALM_ROLES_AVAILABLE.format(**params_path)
  2494. )
  2495. return raise_error_from_response(data_raw, KeycloakGetError)
  2496. def get_composite_realm_roles_of_user(self, user_id, brief_representation=True):
  2497. """Get all composite (i.e. implicit) realm roles for a user.
  2498. :param user_id: id of user
  2499. :type user_id: str
  2500. :param brief_representation: whether to omit role attributes in the response
  2501. :type brief_representation: bool
  2502. :return: Keycloak server response (array RoleRepresentation)
  2503. :rtype: list
  2504. """
  2505. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  2506. params = {"briefRepresentation": brief_representation}
  2507. data_raw = self.connection.raw_get(
  2508. urls_patterns.URL_ADMIN_USER_REALM_ROLES_COMPOSITE.format(**params_path), **params
  2509. )
  2510. return raise_error_from_response(data_raw, KeycloakGetError)
  2511. def assign_group_realm_roles(self, group_id, roles):
  2512. """Assign realm roles to a group.
  2513. :param group_id: id of group
  2514. :type group_id: str
  2515. :param roles: roles list or role (use GroupRoleRepresentation)
  2516. :type roles: list
  2517. :return: Keycloak server response
  2518. :rtype: bytes
  2519. """
  2520. payload = roles if isinstance(roles, list) else [roles]
  2521. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  2522. data_raw = self.connection.raw_post(
  2523. urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  2524. data=json.dumps(payload),
  2525. )
  2526. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2527. def delete_group_realm_roles(self, group_id, roles):
  2528. """Delete realm roles of a group.
  2529. :param group_id: id of group
  2530. :type group_id: str
  2531. :param roles: roles list or role (use GroupRoleRepresentation)
  2532. :type roles: list
  2533. :return: Keycloak server response
  2534. :rtype: bytes
  2535. """
  2536. payload = roles if isinstance(roles, list) else [roles]
  2537. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  2538. data_raw = self.connection.raw_delete(
  2539. urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  2540. data=json.dumps(payload),
  2541. )
  2542. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2543. def get_group_realm_roles(self, group_id, brief_representation=True):
  2544. """Get all realm roles for a group.
  2545. :param group_id: id of the group
  2546. :type group_id: str
  2547. :param brief_representation: whether to omit role attributes in the response
  2548. :type brief_representation: bool
  2549. :return: Keycloak server response (array RoleRepresentation)
  2550. :rtype: list
  2551. """
  2552. params_path = {"realm-name": self.connection.realm_name, "id": group_id}
  2553. params = {"briefRepresentation": brief_representation}
  2554. data_raw = self.connection.raw_get(
  2555. urls_patterns.URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path), **params
  2556. )
  2557. return raise_error_from_response(data_raw, KeycloakGetError)
  2558. def assign_group_client_roles(self, group_id, client_id, roles):
  2559. """Assign client roles to a group.
  2560. :param group_id: id of group
  2561. :type group_id: str
  2562. :param client_id: id of client (not client-id)
  2563. :type client_id: str
  2564. :param roles: roles list or role (use GroupRoleRepresentation)
  2565. :type roles: list
  2566. :return: Keycloak server response
  2567. :rtype: bytes
  2568. """
  2569. payload = roles if isinstance(roles, list) else [roles]
  2570. params_path = {
  2571. "realm-name": self.connection.realm_name,
  2572. "id": group_id,
  2573. "client-id": client_id,
  2574. }
  2575. data_raw = self.connection.raw_post(
  2576. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  2577. data=json.dumps(payload),
  2578. )
  2579. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  2580. def get_group_client_roles(self, group_id, client_id):
  2581. """Get client roles of a group.
  2582. :param group_id: id of group
  2583. :type group_id: str
  2584. :param client_id: id of client (not client-id)
  2585. :type client_id: str
  2586. :return: Keycloak server response
  2587. :rtype: list
  2588. """
  2589. params_path = {
  2590. "realm-name": self.connection.realm_name,
  2591. "id": group_id,
  2592. "client-id": client_id,
  2593. }
  2594. data_raw = self.connection.raw_get(
  2595. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path)
  2596. )
  2597. return raise_error_from_response(data_raw, KeycloakGetError)
  2598. def delete_group_client_roles(self, group_id, client_id, roles):
  2599. """Delete client roles of a group.
  2600. :param group_id: id of group
  2601. :type group_id: str
  2602. :param client_id: id of client (not client-id)
  2603. :type client_id: str
  2604. :param roles: roles list or role (use GroupRoleRepresentation)
  2605. :type roles: list
  2606. :return: Keycloak server response (array RoleRepresentation)
  2607. :rtype: bytes
  2608. """
  2609. payload = roles if isinstance(roles, list) else [roles]
  2610. params_path = {
  2611. "realm-name": self.connection.realm_name,
  2612. "id": group_id,
  2613. "client-id": client_id,
  2614. }
  2615. data_raw = self.connection.raw_delete(
  2616. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  2617. data=json.dumps(payload),
  2618. )
  2619. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2620. def get_client_roles_of_user(self, user_id, client_id):
  2621. """Get all client roles for a user.
  2622. :param user_id: id of user
  2623. :type user_id: str
  2624. :param client_id: id of client (not client-id)
  2625. :type client_id: str
  2626. :return: Keycloak server response (array RoleRepresentation)
  2627. :rtype: list
  2628. """
  2629. return self._get_client_roles_of_user(
  2630. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES, user_id, client_id
  2631. )
  2632. def get_available_client_roles_of_user(self, user_id, client_id):
  2633. """Get available client role-mappings for a user.
  2634. :param user_id: id of user
  2635. :type user_id: str
  2636. :param client_id: id of client (not client-id)
  2637. :type client_id: str
  2638. :return: Keycloak server response (array RoleRepresentation)
  2639. :rtype: list
  2640. """
  2641. return self._get_client_roles_of_user(
  2642. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id
  2643. )
  2644. def get_composite_client_roles_of_user(self, user_id, client_id, brief_representation=False):
  2645. """Get composite client role-mappings for a user.
  2646. :param user_id: id of user
  2647. :type user_id: str
  2648. :param client_id: id of client (not client-id)
  2649. :type client_id: str
  2650. :param brief_representation: whether to omit attributes in the response
  2651. :type brief_representation: bool
  2652. :return: Keycloak server response (array RoleRepresentation)
  2653. :rtype: list
  2654. """
  2655. params = {"briefRepresentation": brief_representation}
  2656. return self._get_client_roles_of_user(
  2657. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id, **params
  2658. )
  2659. def _get_client_roles_of_user(
  2660. self, client_level_role_mapping_url, user_id, client_id, **params
  2661. ):
  2662. """Get client roles of a single user helper.
  2663. :param client_level_role_mapping_url: Url for the client role mapping
  2664. :type client_level_role_mapping_url: str
  2665. :param user_id: User id
  2666. :type user_id: str
  2667. :param client_id: Client id
  2668. :type client_id: str
  2669. :param params: Additional parameters
  2670. :type params: dict
  2671. :returns: Client roles of a user
  2672. :rtype: list
  2673. """
  2674. params_path = {
  2675. "realm-name": self.connection.realm_name,
  2676. "id": user_id,
  2677. "client-id": client_id,
  2678. }
  2679. data_raw = self.connection.raw_get(
  2680. client_level_role_mapping_url.format(**params_path), **params
  2681. )
  2682. return raise_error_from_response(data_raw, KeycloakGetError)
  2683. def delete_client_roles_of_user(self, user_id, client_id, roles):
  2684. """Delete client roles from a user.
  2685. :param user_id: id of user
  2686. :type user_id: str
  2687. :param client_id: id of client containing role (not client-id)
  2688. :type client_id: str
  2689. :param roles: roles list or role to delete (use RoleRepresentation)
  2690. :type roles: list
  2691. :return: Keycloak server response
  2692. :rtype: bytes
  2693. """
  2694. payload = roles if isinstance(roles, list) else [roles]
  2695. params_path = {
  2696. "realm-name": self.connection.realm_name,
  2697. "id": user_id,
  2698. "client-id": client_id,
  2699. }
  2700. data_raw = self.connection.raw_delete(
  2701. urls_patterns.URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  2702. data=json.dumps(payload),
  2703. )
  2704. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2705. def get_authentication_flows(self):
  2706. """Get authentication flows.
  2707. Returns all flow details
  2708. AuthenticationFlowRepresentation
  2709. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  2710. :return: Keycloak server response (AuthenticationFlowRepresentation)
  2711. :rtype: list
  2712. """
  2713. params_path = {"realm-name": self.connection.realm_name}
  2714. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_FLOWS.format(**params_path))
  2715. return raise_error_from_response(data_raw, KeycloakGetError)
  2716. def get_authentication_flow_for_id(self, flow_id):
  2717. """Get one authentication flow by it's id.
  2718. Returns all flow details
  2719. AuthenticationFlowRepresentation
  2720. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  2721. :param flow_id: the id of a flow NOT it's alias
  2722. :type flow_id: str
  2723. :return: Keycloak server response (AuthenticationFlowRepresentation)
  2724. :rtype: dict
  2725. """
  2726. params_path = {"realm-name": self.connection.realm_name, "flow-id": flow_id}
  2727. data_raw = self.connection.raw_get(
  2728. urls_patterns.URL_ADMIN_FLOWS_ALIAS.format(**params_path)
  2729. )
  2730. return raise_error_from_response(data_raw, KeycloakGetError)
  2731. def create_authentication_flow(self, payload, skip_exists=False):
  2732. """Create a new authentication flow.
  2733. AuthenticationFlowRepresentation
  2734. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  2735. :param payload: AuthenticationFlowRepresentation
  2736. :type payload: dict
  2737. :param skip_exists: Do not raise an error if authentication flow already exists
  2738. :type skip_exists: bool
  2739. :return: Keycloak server response (RoleRepresentation)
  2740. :rtype: bytes
  2741. """
  2742. params_path = {"realm-name": self.connection.realm_name}
  2743. data_raw = self.connection.raw_post(
  2744. urls_patterns.URL_ADMIN_FLOWS.format(**params_path), data=json.dumps(payload)
  2745. )
  2746. return raise_error_from_response(
  2747. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  2748. )
  2749. def copy_authentication_flow(self, payload, flow_alias):
  2750. """Copy existing authentication flow under a new name.
  2751. The new name is given as 'newName' attribute of the passed payload.
  2752. :param payload: JSON containing 'newName' attribute
  2753. :type payload: dict
  2754. :param flow_alias: the flow alias
  2755. :type flow_alias: str
  2756. :return: Keycloak server response (RoleRepresentation)
  2757. :rtype: bytes
  2758. """
  2759. params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias}
  2760. data_raw = self.connection.raw_post(
  2761. urls_patterns.URL_ADMIN_FLOWS_COPY.format(**params_path), data=json.dumps(payload)
  2762. )
  2763. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  2764. def delete_authentication_flow(self, flow_id):
  2765. """Delete authentication flow.
  2766. AuthenticationInfoRepresentation
  2767. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationinforepresentation
  2768. :param flow_id: authentication flow id
  2769. :type flow_id: str
  2770. :return: Keycloak server response
  2771. :rtype: bytes
  2772. """
  2773. params_path = {"realm-name": self.connection.realm_name, "id": flow_id}
  2774. data_raw = self.connection.raw_delete(urls_patterns.URL_ADMIN_FLOW.format(**params_path))
  2775. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2776. def get_authentication_flow_executions(self, flow_alias):
  2777. """Get authentication flow executions.
  2778. Returns all execution steps
  2779. :param flow_alias: the flow alias
  2780. :type flow_alias: str
  2781. :return: Response(json)
  2782. :rtype: list
  2783. """
  2784. params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias}
  2785. data_raw = self.connection.raw_get(
  2786. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path)
  2787. )
  2788. return raise_error_from_response(data_raw, KeycloakGetError)
  2789. def update_authentication_flow_executions(self, payload, flow_alias):
  2790. """Update an authentication flow execution.
  2791. AuthenticationExecutionInfoRepresentation
  2792. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  2793. :param payload: AuthenticationExecutionInfoRepresentation
  2794. :type payload: dict
  2795. :param flow_alias: The flow alias
  2796. :type flow_alias: str
  2797. :return: Keycloak server response
  2798. :rtype: bytes
  2799. """
  2800. params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias}
  2801. data_raw = self.connection.raw_put(
  2802. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path),
  2803. data=json.dumps(payload),
  2804. )
  2805. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[202, 204])
  2806. def get_authentication_flow_execution(self, execution_id):
  2807. """Get authentication flow execution.
  2808. AuthenticationExecutionInfoRepresentation
  2809. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  2810. :param execution_id: the execution ID
  2811. :type execution_id: str
  2812. :return: Response(json)
  2813. :rtype: dict
  2814. """
  2815. params_path = {"realm-name": self.connection.realm_name, "id": execution_id}
  2816. data_raw = self.connection.raw_get(
  2817. urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path)
  2818. )
  2819. return raise_error_from_response(data_raw, KeycloakGetError)
  2820. def create_authentication_flow_execution(self, payload, flow_alias):
  2821. """Create an authentication flow execution.
  2822. AuthenticationExecutionInfoRepresentation
  2823. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  2824. :param payload: AuthenticationExecutionInfoRepresentation
  2825. :type payload: dict
  2826. :param flow_alias: The flow alias
  2827. :type flow_alias: str
  2828. :return: Keycloak server response
  2829. :rtype: bytes
  2830. """
  2831. params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias}
  2832. data_raw = self.connection.raw_post(
  2833. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_EXECUTION.format(**params_path),
  2834. data=json.dumps(payload),
  2835. )
  2836. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  2837. def delete_authentication_flow_execution(self, execution_id):
  2838. """Delete authentication flow execution.
  2839. AuthenticationExecutionInfoRepresentation
  2840. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationexecutioninforepresentation
  2841. :param execution_id: keycloak client id (not oauth client-id)
  2842. :type execution_id: str
  2843. :return: Keycloak server response (json)
  2844. :rtype: bytes
  2845. """
  2846. params_path = {"realm-name": self.connection.realm_name, "id": execution_id}
  2847. data_raw = self.connection.raw_delete(
  2848. urls_patterns.URL_ADMIN_FLOWS_EXECUTION.format(**params_path)
  2849. )
  2850. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2851. def create_authentication_flow_subflow(self, payload, flow_alias, skip_exists=False):
  2852. """Create a new sub authentication flow for a given authentication flow.
  2853. AuthenticationFlowRepresentation
  2854. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticationflowrepresentation
  2855. :param payload: AuthenticationFlowRepresentation
  2856. :type payload: dict
  2857. :param flow_alias: The flow alias
  2858. :type flow_alias: str
  2859. :param skip_exists: Do not raise an error if authentication flow already exists
  2860. :type skip_exists: bool
  2861. :return: Keycloak server response (RoleRepresentation)
  2862. :rtype: bytes
  2863. """
  2864. params_path = {"realm-name": self.connection.realm_name, "flow-alias": flow_alias}
  2865. data_raw = self.connection.raw_post(
  2866. urls_patterns.URL_ADMIN_FLOWS_EXECUTIONS_FLOW.format(**params_path),
  2867. data=json.dumps(payload),
  2868. )
  2869. return raise_error_from_response(
  2870. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  2871. )
  2872. def get_authenticator_providers(self):
  2873. """Get authenticator providers list.
  2874. :return: Authenticator providers
  2875. :rtype: list
  2876. """
  2877. params_path = {"realm-name": self.connection.realm_name}
  2878. data_raw = self.connection.raw_get(
  2879. urls_patterns.URL_ADMIN_AUTHENTICATOR_PROVIDERS.format(**params_path)
  2880. )
  2881. return raise_error_from_response(data_raw, KeycloakGetError)
  2882. def get_authenticator_provider_config_description(self, provider_id):
  2883. """Get authenticator's provider configuration description.
  2884. AuthenticatorConfigInfoRepresentation
  2885. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticatorconfiginforepresentation
  2886. :param provider_id: Provider Id
  2887. :type provider_id: str
  2888. :return: AuthenticatorConfigInfoRepresentation
  2889. :rtype: dict
  2890. """
  2891. params_path = {"realm-name": self.connection.realm_name, "provider-id": provider_id}
  2892. data_raw = self.connection.raw_get(
  2893. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG_DESCRIPTION.format(**params_path)
  2894. )
  2895. return raise_error_from_response(data_raw, KeycloakGetError)
  2896. def get_authenticator_config(self, config_id):
  2897. """Get authenticator configuration.
  2898. Returns all configuration details.
  2899. :param config_id: Authenticator config id
  2900. :type config_id: str
  2901. :return: Response(json)
  2902. :rtype: dict
  2903. """
  2904. params_path = {"realm-name": self.connection.realm_name, "id": config_id}
  2905. data_raw = self.connection.raw_get(
  2906. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path)
  2907. )
  2908. return raise_error_from_response(data_raw, KeycloakGetError)
  2909. def update_authenticator_config(self, payload, config_id):
  2910. """Update an authenticator configuration.
  2911. AuthenticatorConfigRepresentation
  2912. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authenticatorconfigrepresentation
  2913. :param payload: AuthenticatorConfigRepresentation
  2914. :type payload: dict
  2915. :param config_id: Authenticator config id
  2916. :type config_id: str
  2917. :return: Response(json)
  2918. :rtype: bytes
  2919. """
  2920. params_path = {"realm-name": self.connection.realm_name, "id": config_id}
  2921. data_raw = self.connection.raw_put(
  2922. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path),
  2923. data=json.dumps(payload),
  2924. )
  2925. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  2926. def delete_authenticator_config(self, config_id):
  2927. """Delete a authenticator configuration.
  2928. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_authentication_management_resource
  2929. :param config_id: Authenticator config id
  2930. :type config_id: str
  2931. :return: Keycloak server Response
  2932. :rtype: bytes
  2933. """
  2934. params_path = {"realm-name": self.connection.realm_name, "id": config_id}
  2935. data_raw = self.connection.raw_delete(
  2936. urls_patterns.URL_ADMIN_AUTHENTICATOR_CONFIG.format(**params_path)
  2937. )
  2938. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  2939. def sync_users(self, storage_id, action):
  2940. """Trigger user sync from provider.
  2941. :param storage_id: The id of the user storage provider
  2942. :type storage_id: str
  2943. :param action: Action can be "triggerFullSync" or "triggerChangedUsersSync"
  2944. :type action: str
  2945. :return: Keycloak server response
  2946. :rtype: bytes
  2947. """
  2948. data = {"action": action}
  2949. params_query = {"action": action}
  2950. params_path = {"realm-name": self.connection.realm_name, "id": storage_id}
  2951. data_raw = self.connection.raw_post(
  2952. urls_patterns.URL_ADMIN_USER_STORAGE.format(**params_path),
  2953. data=json.dumps(data),
  2954. **params_query,
  2955. )
  2956. return raise_error_from_response(data_raw, KeycloakPostError)
  2957. def get_client_scopes(self):
  2958. """Get client scopes.
  2959. Get representation of the client scopes for the realm where we are connected to
  2960. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  2961. :return: Keycloak server response Array of (ClientScopeRepresentation)
  2962. :rtype: list
  2963. """
  2964. params_path = {"realm-name": self.connection.realm_name}
  2965. data_raw = self.connection.raw_get(
  2966. urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path)
  2967. )
  2968. return raise_error_from_response(data_raw, KeycloakGetError)
  2969. def get_client_scope(self, client_scope_id):
  2970. """Get client scope.
  2971. Get representation of the client scopes for the realm where we are connected to
  2972. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  2973. :param client_scope_id: The id of the client scope
  2974. :type client_scope_id: str
  2975. :return: Keycloak server response (ClientScopeRepresentation)
  2976. :rtype: dict
  2977. """
  2978. params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id}
  2979. data_raw = self.connection.raw_get(
  2980. urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path)
  2981. )
  2982. return raise_error_from_response(data_raw, KeycloakGetError)
  2983. def get_client_scope_by_name(self, client_scope_name):
  2984. """Get client scope by name.
  2985. Get representation of the client scope identified by the client scope name.
  2986. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  2987. :param client_scope_name: (str) Name of the client scope
  2988. :type client_scope_name: str
  2989. :returns: ClientScopeRepresentation or None
  2990. :rtype: dict
  2991. """
  2992. client_scopes = self.get_client_scopes()
  2993. for client_scope in client_scopes:
  2994. if client_scope["name"] == client_scope_name:
  2995. return client_scope
  2996. return None
  2997. def create_client_scope(self, payload, skip_exists=False):
  2998. """Create a client scope.
  2999. ClientScopeRepresentation:
  3000. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientscopes
  3001. :param payload: ClientScopeRepresentation
  3002. :type payload: dict
  3003. :param skip_exists: If true then do not raise an error if client scope already exists
  3004. :type skip_exists: bool
  3005. :return: Client scope id
  3006. :rtype: str
  3007. """
  3008. if skip_exists:
  3009. exists = self.get_client_scope_by_name(client_scope_name=payload["name"])
  3010. if exists is not None:
  3011. return exists["id"]
  3012. params_path = {"realm-name": self.connection.realm_name}
  3013. data_raw = self.connection.raw_post(
  3014. urls_patterns.URL_ADMIN_CLIENT_SCOPES.format(**params_path), data=json.dumps(payload)
  3015. )
  3016. raise_error_from_response(
  3017. data_raw, KeycloakPostError, expected_codes=[201], skip_exists=skip_exists
  3018. )
  3019. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  3020. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  3021. def update_client_scope(self, client_scope_id, payload):
  3022. """Update a client scope.
  3023. ClientScopeRepresentation:
  3024. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_client_scopes_resource
  3025. :param client_scope_id: The id of the client scope
  3026. :type client_scope_id: str
  3027. :param payload: ClientScopeRepresentation
  3028. :type payload: dict
  3029. :return: Keycloak server response (ClientScopeRepresentation)
  3030. :rtype: bytes
  3031. """
  3032. params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id}
  3033. data_raw = self.connection.raw_put(
  3034. urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path), data=json.dumps(payload)
  3035. )
  3036. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3037. def delete_client_scope(self, client_scope_id):
  3038. """Delete existing client scope.
  3039. ClientScopeRepresentation:
  3040. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_client_scopes_resource
  3041. :param client_scope_id: The id of the client scope
  3042. :type client_scope_id: str
  3043. :return: Keycloak server response
  3044. :rtype: bytes
  3045. """
  3046. params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id}
  3047. data_raw = self.connection.raw_delete(
  3048. urls_patterns.URL_ADMIN_CLIENT_SCOPE.format(**params_path)
  3049. )
  3050. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3051. def get_mappers_from_client_scope(self, client_scope_id):
  3052. """Get a list of all mappers connected to the client scope.
  3053. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocol_mappers_resource
  3054. :param client_scope_id: Client scope id
  3055. :type client_scope_id: str
  3056. :returns: Keycloak server response (ProtocolMapperRepresentation)
  3057. :rtype: list
  3058. """
  3059. params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id}
  3060. data_raw = self.connection.raw_get(
  3061. urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path)
  3062. )
  3063. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  3064. def add_mapper_to_client_scope(self, client_scope_id, payload):
  3065. """Add a mapper to a client scope.
  3066. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_create_mapper
  3067. :param client_scope_id: The id of the client scope
  3068. :type client_scope_id: str
  3069. :param payload: ProtocolMapperRepresentation
  3070. :type payload: dict
  3071. :return: Keycloak server Response
  3072. :rtype: bytes
  3073. """
  3074. params_path = {"realm-name": self.connection.realm_name, "scope-id": client_scope_id}
  3075. data_raw = self.connection.raw_post(
  3076. urls_patterns.URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path),
  3077. data=json.dumps(payload),
  3078. )
  3079. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  3080. def delete_mapper_from_client_scope(self, client_scope_id, protocol_mapper_id):
  3081. """Delete a mapper from a client scope.
  3082. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_delete_mapper
  3083. :param client_scope_id: The id of the client scope
  3084. :type client_scope_id: str
  3085. :param protocol_mapper_id: Protocol mapper id
  3086. :type protocol_mapper_id: str
  3087. :return: Keycloak server Response
  3088. :rtype: bytes
  3089. """
  3090. params_path = {
  3091. "realm-name": self.connection.realm_name,
  3092. "scope-id": client_scope_id,
  3093. "protocol-mapper-id": protocol_mapper_id,
  3094. }
  3095. data_raw = self.connection.raw_delete(
  3096. urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path)
  3097. )
  3098. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3099. def update_mapper_in_client_scope(self, client_scope_id, protocol_mapper_id, payload):
  3100. """Update an existing protocol mapper in a client scope.
  3101. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocol_mappers_resource
  3102. :param client_scope_id: The id of the client scope
  3103. :type client_scope_id: str
  3104. :param protocol_mapper_id: The id of the protocol mapper which exists in the client scope
  3105. and should to be updated
  3106. :type protocol_mapper_id: str
  3107. :param payload: ProtocolMapperRepresentation
  3108. :type payload: dict
  3109. :return: Keycloak server Response
  3110. :rtype: bytes
  3111. """
  3112. params_path = {
  3113. "realm-name": self.connection.realm_name,
  3114. "scope-id": client_scope_id,
  3115. "protocol-mapper-id": protocol_mapper_id,
  3116. }
  3117. data_raw = self.connection.raw_put(
  3118. urls_patterns.URL_ADMIN_CLIENT_SCOPES_MAPPERS.format(**params_path),
  3119. data=json.dumps(payload),
  3120. )
  3121. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3122. def get_default_default_client_scopes(self):
  3123. """Get default default client scopes.
  3124. Return list of default default client scopes
  3125. :return: Keycloak server response
  3126. :rtype: list
  3127. """
  3128. params_path = {"realm-name": self.connection.realm_name}
  3129. data_raw = self.connection.raw_get(
  3130. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPES.format(**params_path)
  3131. )
  3132. return raise_error_from_response(data_raw, KeycloakGetError)
  3133. def delete_default_default_client_scope(self, scope_id):
  3134. """Delete default default client scope.
  3135. :param scope_id: default default client scope id
  3136. :type scope_id: str
  3137. :return: Keycloak server response
  3138. :rtype: list
  3139. """
  3140. params_path = {"realm-name": self.connection.realm_name, "id": scope_id}
  3141. data_raw = self.connection.raw_delete(
  3142. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path)
  3143. )
  3144. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3145. def add_default_default_client_scope(self, scope_id):
  3146. """Add default default client scope.
  3147. :param scope_id: default default client scope id
  3148. :type scope_id: str
  3149. :return: Keycloak server response
  3150. :rtype: bytes
  3151. """
  3152. params_path = {"realm-name": self.connection.realm_name, "id": scope_id}
  3153. payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id}
  3154. data_raw = self.connection.raw_put(
  3155. urls_patterns.URL_ADMIN_DEFAULT_DEFAULT_CLIENT_SCOPE.format(**params_path),
  3156. data=json.dumps(payload),
  3157. )
  3158. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3159. def get_default_optional_client_scopes(self):
  3160. """Get default optional client scopes.
  3161. Return list of default optional client scopes
  3162. :return: Keycloak server response
  3163. :rtype: list
  3164. """
  3165. params_path = {"realm-name": self.connection.realm_name}
  3166. data_raw = self.connection.raw_get(
  3167. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPES.format(**params_path)
  3168. )
  3169. return raise_error_from_response(data_raw, KeycloakGetError)
  3170. def delete_default_optional_client_scope(self, scope_id):
  3171. """Delete default optional client scope.
  3172. :param scope_id: default optional client scope id
  3173. :type scope_id: str
  3174. :return: Keycloak server response
  3175. :rtype: bytes
  3176. """
  3177. params_path = {"realm-name": self.connection.realm_name, "id": scope_id}
  3178. data_raw = self.connection.raw_delete(
  3179. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path)
  3180. )
  3181. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3182. def add_default_optional_client_scope(self, scope_id):
  3183. """Add default optional client scope.
  3184. :param scope_id: default optional client scope id
  3185. :type scope_id: str
  3186. :return: Keycloak server response
  3187. :rtype: bytes
  3188. """
  3189. params_path = {"realm-name": self.connection.realm_name, "id": scope_id}
  3190. payload = {"realm": self.connection.realm_name, "clientScopeId": scope_id}
  3191. data_raw = self.connection.raw_put(
  3192. urls_patterns.URL_ADMIN_DEFAULT_OPTIONAL_CLIENT_SCOPE.format(**params_path),
  3193. data=json.dumps(payload),
  3194. )
  3195. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3196. def get_mappers_from_client(self, client_id):
  3197. """List of all client mappers.
  3198. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_protocolmapperrepresentation
  3199. :param client_id: Client id
  3200. :type client_id: str
  3201. :returns: KeycloakServerResponse (list of ProtocolMapperRepresentation)
  3202. :rtype: list
  3203. """
  3204. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3205. data_raw = self.connection.raw_get(
  3206. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path)
  3207. )
  3208. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[200])
  3209. def add_mapper_to_client(self, client_id, payload):
  3210. """Add a mapper to a client.
  3211. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_create_mapper
  3212. :param client_id: The id of the client
  3213. :type client_id: str
  3214. :param payload: ProtocolMapperRepresentation
  3215. :type payload: dict
  3216. :return: Keycloak server Response
  3217. :rtype: bytes
  3218. """
  3219. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3220. data_raw = self.connection.raw_post(
  3221. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPERS.format(**params_path),
  3222. data=json.dumps(payload),
  3223. )
  3224. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  3225. def update_client_mapper(self, client_id, mapper_id, payload):
  3226. """Update client mapper.
  3227. :param client_id: The id of the client
  3228. :type client_id: str
  3229. :param mapper_id: The id of the mapper to be deleted
  3230. :type mapper_id: str
  3231. :param payload: ProtocolMapperRepresentation
  3232. :type payload: dict
  3233. :return: Keycloak server response
  3234. :rtype: bytes
  3235. """
  3236. params_path = {
  3237. "realm-name": self.connection.realm_name,
  3238. "id": client_id,
  3239. "protocol-mapper-id": mapper_id,
  3240. }
  3241. data_raw = self.connection.raw_put(
  3242. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path),
  3243. data=json.dumps(payload),
  3244. )
  3245. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3246. def remove_client_mapper(self, client_id, client_mapper_id):
  3247. """Remove a mapper from the client.
  3248. https://www.keycloak.org/docs-api/15.0/rest-api/index.html#_protocol_mappers_resource
  3249. :param client_id: The id of the client
  3250. :type client_id: str
  3251. :param client_mapper_id: The id of the mapper to be deleted
  3252. :type client_mapper_id: str
  3253. :return: Keycloak server response
  3254. :rtype: bytes
  3255. """
  3256. params_path = {
  3257. "realm-name": self.connection.realm_name,
  3258. "id": client_id,
  3259. "protocol-mapper-id": client_mapper_id,
  3260. }
  3261. data_raw = self.connection.raw_delete(
  3262. urls_patterns.URL_ADMIN_CLIENT_PROTOCOL_MAPPER.format(**params_path)
  3263. )
  3264. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3265. def generate_client_secrets(self, client_id):
  3266. """Generate a new secret for the client.
  3267. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_regeneratesecret
  3268. :param client_id: id of client (not client-id)
  3269. :type client_id: str
  3270. :return: Keycloak server response (ClientRepresentation)
  3271. :rtype: bytes
  3272. """
  3273. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3274. data_raw = self.connection.raw_post(
  3275. urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None
  3276. )
  3277. return raise_error_from_response(data_raw, KeycloakPostError)
  3278. def get_client_secrets(self, client_id):
  3279. """Get representation of the client secrets.
  3280. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientsecret
  3281. :param client_id: id of client (not client-id)
  3282. :type client_id: str
  3283. :return: Keycloak server response (ClientRepresentation)
  3284. :rtype: list
  3285. """
  3286. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3287. data_raw = self.connection.raw_get(
  3288. urls_patterns.URL_ADMIN_CLIENT_SECRETS.format(**params_path)
  3289. )
  3290. return raise_error_from_response(data_raw, KeycloakGetError)
  3291. def get_components(self, query=None):
  3292. """Get components.
  3293. Return a list of components, filtered according to query parameters
  3294. ComponentRepresentation
  3295. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  3296. :param query: Query parameters (optional)
  3297. :type query: dict
  3298. :return: components list
  3299. :rtype: list
  3300. """
  3301. query = query or dict()
  3302. params_path = {"realm-name": self.connection.realm_name}
  3303. data_raw = self.connection.raw_get(
  3304. urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=None, **query
  3305. )
  3306. return raise_error_from_response(data_raw, KeycloakGetError)
  3307. def create_component(self, payload):
  3308. """Create a new component.
  3309. ComponentRepresentation
  3310. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  3311. :param payload: ComponentRepresentation
  3312. :type payload: dict
  3313. :return: Component id
  3314. :rtype: str
  3315. """
  3316. params_path = {"realm-name": self.connection.realm_name}
  3317. data_raw = self.connection.raw_post(
  3318. urls_patterns.URL_ADMIN_COMPONENTS.format(**params_path), data=json.dumps(payload)
  3319. )
  3320. raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  3321. _last_slash_idx = data_raw.headers["Location"].rindex("/")
  3322. return data_raw.headers["Location"][_last_slash_idx + 1 :] # noqa: E203
  3323. def get_component(self, component_id):
  3324. """Get representation of the component.
  3325. :param component_id: Component id
  3326. ComponentRepresentation
  3327. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  3328. :param component_id: Id of the component
  3329. :type component_id: str
  3330. :return: ComponentRepresentation
  3331. :rtype: dict
  3332. """
  3333. params_path = {"realm-name": self.connection.realm_name, "component-id": component_id}
  3334. data_raw = self.connection.raw_get(urls_patterns.URL_ADMIN_COMPONENT.format(**params_path))
  3335. return raise_error_from_response(data_raw, KeycloakGetError)
  3336. def update_component(self, component_id, payload):
  3337. """Update the component.
  3338. :param component_id: Component id
  3339. :type component_id: str
  3340. :param payload: ComponentRepresentation
  3341. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_componentrepresentation
  3342. :type payload: dict
  3343. :return: Http response
  3344. :rtype: bytes
  3345. """
  3346. params_path = {"realm-name": self.connection.realm_name, "component-id": component_id}
  3347. data_raw = self.connection.raw_put(
  3348. urls_patterns.URL_ADMIN_COMPONENT.format(**params_path), data=json.dumps(payload)
  3349. )
  3350. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3351. def delete_component(self, component_id):
  3352. """Delete the component.
  3353. :param component_id: Component id
  3354. :type component_id: str
  3355. :return: Http response
  3356. :rtype: bytes
  3357. """
  3358. params_path = {"realm-name": self.connection.realm_name, "component-id": component_id}
  3359. data_raw = self.connection.raw_delete(
  3360. urls_patterns.URL_ADMIN_COMPONENT.format(**params_path)
  3361. )
  3362. return raise_error_from_response(data_raw, KeycloakDeleteError, expected_codes=[204])
  3363. def get_keys(self):
  3364. """Get keys.
  3365. Return a list of keys, filtered according to query parameters
  3366. KeysMetadataRepresentation
  3367. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_key_resource
  3368. :return: keys list
  3369. :rtype: list
  3370. """
  3371. params_path = {"realm-name": self.connection.realm_name}
  3372. data_raw = self.connection.raw_get(
  3373. urls_patterns.URL_ADMIN_KEYS.format(**params_path), data=None
  3374. )
  3375. return raise_error_from_response(data_raw, KeycloakGetError)
  3376. def get_admin_events(self, query=None):
  3377. """Get Administrative events.
  3378. Return a list of events, filtered according to query parameters
  3379. AdminEvents Representation array
  3380. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getevents
  3381. https://www.keycloak.org/docs-api/22.0.1/rest-api/index.html#_get_adminrealmsrealmadmin_events
  3382. :param query: Additional query parameters
  3383. :type query: dict
  3384. :return: events list
  3385. :rtype: list
  3386. """
  3387. query = query or dict()
  3388. params_path = {"realm-name": self.connection.realm_name}
  3389. data_raw = self.connection.raw_get(
  3390. urls_patterns.URL_ADMIN_ADMIN_EVENTS.format(**params_path), data=None, **query
  3391. )
  3392. return raise_error_from_response(data_raw, KeycloakGetError)
  3393. def get_events(self, query=None):
  3394. """Get events.
  3395. Return a list of events, filtered according to query parameters
  3396. EventRepresentation array
  3397. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_eventrepresentation
  3398. :param query: Additional query parameters
  3399. :type query: dict
  3400. :return: events list
  3401. :rtype: list
  3402. """
  3403. query = query or dict()
  3404. params_path = {"realm-name": self.connection.realm_name}
  3405. data_raw = self.connection.raw_get(
  3406. urls_patterns.URL_ADMIN_USER_EVENTS.format(**params_path), data=None, **query
  3407. )
  3408. return raise_error_from_response(data_raw, KeycloakGetError)
  3409. def set_events(self, payload):
  3410. """Set realm events configuration.
  3411. RealmEventsConfigRepresentation
  3412. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_realmeventsconfigrepresentation
  3413. :param payload: Payload object for the events configuration
  3414. :type payload: dict
  3415. :return: Http response
  3416. :rtype: bytes
  3417. """
  3418. params_path = {"realm-name": self.connection.realm_name}
  3419. data_raw = self.connection.raw_put(
  3420. urls_patterns.URL_ADMIN_EVENTS_CONFIG.format(**params_path), data=json.dumps(payload)
  3421. )
  3422. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[204])
  3423. @deprecation.deprecated(
  3424. deprecated_in="2.13.0",
  3425. removed_in="4.0.0",
  3426. current_version=__version__,
  3427. details="Use the connection.raw_get function instead",
  3428. )
  3429. def raw_get(self, *args, **kwargs):
  3430. """Call connection.raw_get.
  3431. If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token
  3432. and try *get* once more.
  3433. :param args: Additional arguments
  3434. :type args: tuple
  3435. :param kwargs: Additional keyword arguments
  3436. :type kwargs: dict
  3437. :returns: Response
  3438. :rtype: Response
  3439. """
  3440. return self.connection.raw_get(*args, **kwargs)
  3441. @deprecation.deprecated(
  3442. deprecated_in="2.13.0",
  3443. removed_in="4.0.0",
  3444. current_version=__version__,
  3445. details="Use the connection.raw_post function instead",
  3446. )
  3447. def raw_post(self, *args, **kwargs):
  3448. """Call connection.raw_post.
  3449. If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token
  3450. and try *post* once more.
  3451. :param args: Additional arguments
  3452. :type args: tuple
  3453. :param kwargs: Additional keyword arguments
  3454. :type kwargs: dict
  3455. :returns: Response
  3456. :rtype: Response
  3457. """
  3458. return self.connection.raw_post(*args, **kwargs)
  3459. @deprecation.deprecated(
  3460. deprecated_in="2.13.0",
  3461. removed_in="4.0.0",
  3462. current_version=__version__,
  3463. details="Use the connection.raw_put function instead",
  3464. )
  3465. def raw_put(self, *args, **kwargs):
  3466. """Call connection.raw_put.
  3467. If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token
  3468. and try *put* once more.
  3469. :param args: Additional arguments
  3470. :type args: tuple
  3471. :param kwargs: Additional keyword arguments
  3472. :type kwargs: dict
  3473. :returns: Response
  3474. :rtype: Response
  3475. """
  3476. return self.connection.raw_put(*args, **kwargs)
  3477. @deprecation.deprecated(
  3478. deprecated_in="2.13.0",
  3479. removed_in="4.0.0",
  3480. current_version=__version__,
  3481. details="Use the connection.raw_delete function instead",
  3482. )
  3483. def raw_delete(self, *args, **kwargs):
  3484. """Call connection.raw_delete.
  3485. If auto_refresh is set for *delete* and *access_token* is expired,
  3486. it will refresh the token and try *delete* once more.
  3487. :param args: Additional arguments
  3488. :type args: tuple
  3489. :param kwargs: Additional keyword arguments
  3490. :type kwargs: dict
  3491. :returns: Response
  3492. :rtype: Response
  3493. """
  3494. return self.connection.raw_delete(*args, **kwargs)
  3495. @deprecation.deprecated(
  3496. deprecated_in="2.13.0",
  3497. removed_in="4.0.0",
  3498. current_version=__version__,
  3499. details="Use the connection.get_token function instead",
  3500. )
  3501. def get_token(self):
  3502. """Get admin token.
  3503. The admin token is then set in the `token` attribute.
  3504. :returns: token
  3505. :rtype: dict
  3506. """
  3507. return self.connection.get_token()
  3508. @deprecation.deprecated(
  3509. deprecated_in="2.13.0",
  3510. removed_in="4.0.0",
  3511. current_version=__version__,
  3512. details="Use the connection.refresh_token function instead",
  3513. )
  3514. def refresh_token(self):
  3515. """Refresh the token.
  3516. :returns: token
  3517. :rtype: dict
  3518. """
  3519. return self.connection.refresh_token()
  3520. def get_client_all_sessions(self, client_id):
  3521. """Get sessions associated with the client.
  3522. UserSessionRepresentation
  3523. http://www.keycloak.org/docs-api/18.0/rest-api/index.html#_usersessionrepresentation
  3524. :param client_id: id of client
  3525. :type client_id: str
  3526. :return: UserSessionRepresentation
  3527. :rtype: list
  3528. """
  3529. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3530. data_raw = self.connection.raw_get(
  3531. urls_patterns.URL_ADMIN_CLIENT_ALL_SESSIONS.format(**params_path)
  3532. )
  3533. return raise_error_from_response(data_raw, KeycloakGetError)
  3534. def get_client_sessions_stats(self):
  3535. """Get current session count for all clients with active sessions.
  3536. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_getclientsessionstats
  3537. :return: Dict of clients and session count
  3538. :rtype: dict
  3539. """
  3540. params_path = {"realm-name": self.connection.realm_name}
  3541. data_raw = self.connection.raw_get(
  3542. urls_patterns.URL_ADMIN_CLIENT_SESSION_STATS.format(**params_path)
  3543. )
  3544. return raise_error_from_response(data_raw, KeycloakGetError)
  3545. def get_client_management_permissions(self, client_id):
  3546. """Get management permissions for a client.
  3547. :param client_id: id in ClientRepresentation
  3548. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3549. :type client_id: str
  3550. :return: Keycloak server response
  3551. :rtype: list
  3552. """
  3553. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3554. data_raw = self.connection.raw_get(
  3555. urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path)
  3556. )
  3557. return raise_error_from_response(data_raw, KeycloakGetError)
  3558. def update_client_management_permissions(self, payload, client_id):
  3559. """Update management permissions for a client.
  3560. ManagementPermissionReference
  3561. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_managementpermissionreference
  3562. Payload example::
  3563. payload={
  3564. "enabled": true
  3565. }
  3566. :param payload: ManagementPermissionReference
  3567. :type payload: dict
  3568. :param client_id: id in ClientRepresentation
  3569. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3570. :type client_id: str
  3571. :return: Keycloak server response
  3572. :rtype: bytes
  3573. """
  3574. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3575. data_raw = self.connection.raw_put(
  3576. urls_patterns.URL_ADMIN_CLIENT_MANAGEMENT_PERMISSIONS.format(**params_path),
  3577. data=json.dumps(payload),
  3578. )
  3579. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[200])
  3580. def get_client_authz_policy_scopes(self, client_id, policy_id):
  3581. """Get scopes for a given policy.
  3582. :param client_id: id in ClientRepresentation
  3583. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3584. :type client_id: str
  3585. :param policy_id: No Document
  3586. :type policy_id: str
  3587. :return: Keycloak server response
  3588. :rtype: list
  3589. """
  3590. params_path = {
  3591. "realm-name": self.connection.realm_name,
  3592. "id": client_id,
  3593. "policy-id": policy_id,
  3594. }
  3595. data_raw = self.connection.raw_get(
  3596. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_SCOPES.format(**params_path)
  3597. )
  3598. return raise_error_from_response(data_raw, KeycloakGetError)
  3599. def get_client_authz_policy_resources(self, client_id, policy_id):
  3600. """Get resources for a given policy.
  3601. :param client_id: id in ClientRepresentation
  3602. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3603. :type client_id: str
  3604. :param policy_id: No Document
  3605. :type policy_id: str
  3606. :return: Keycloak server response
  3607. :rtype: list
  3608. """
  3609. params_path = {
  3610. "realm-name": self.connection.realm_name,
  3611. "id": client_id,
  3612. "policy-id": policy_id,
  3613. }
  3614. data_raw = self.connection.raw_get(
  3615. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_POLICY_RESOURCES.format(**params_path)
  3616. )
  3617. return raise_error_from_response(data_raw, KeycloakGetError)
  3618. def get_client_authz_scope_permission(self, client_id, scope_id):
  3619. """Get permissions for a given scope.
  3620. :param client_id: id in ClientRepresentation
  3621. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3622. :type client_id: str
  3623. :param scope_id: No Document
  3624. :type scope_id: str
  3625. :return: Keycloak server response
  3626. :rtype: list
  3627. """
  3628. params_path = {
  3629. "realm-name": self.connection.realm_name,
  3630. "id": client_id,
  3631. "scope-id": scope_id,
  3632. }
  3633. data_raw = self.connection.raw_get(
  3634. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path)
  3635. )
  3636. return raise_error_from_response(data_raw, KeycloakGetError)
  3637. def create_client_authz_scope_permission(self, payload, client_id):
  3638. """Create permissions for a authz scope.
  3639. Payload example::
  3640. payload={
  3641. "name": "My Permission Name",
  3642. "type": "scope",
  3643. "logic": "POSITIVE",
  3644. "decisionStrategy": "UNANIMOUS",
  3645. "resources": [some_resource_id],
  3646. "scopes": [some_scope_id],
  3647. "policies": [some_policy_id],
  3648. }
  3649. :param payload: No Document
  3650. :type payload: dict
  3651. :param client_id: id in ClientRepresentation
  3652. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3653. :type client_id: str
  3654. :return: Keycloak server response
  3655. :rtype: bytes
  3656. """
  3657. params_path = {"realm-name": self.realm_name, "id": client_id}
  3658. data_raw = self.raw_post(
  3659. urls_patterns.URL_ADMIN_ADD_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path),
  3660. data=json.dumps(payload),
  3661. )
  3662. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  3663. def update_client_authz_scope_permission(self, payload, client_id, scope_id):
  3664. """Update permissions for a given scope.
  3665. Payload example::
  3666. payload={
  3667. "id": scope_id,
  3668. "name": "My Permission Name",
  3669. "type": "scope",
  3670. "logic": "POSITIVE",
  3671. "decisionStrategy": "UNANIMOUS",
  3672. "resources": [some_resource_id],
  3673. "scopes": [some_scope_id],
  3674. "policies": [some_policy_id],
  3675. }
  3676. :param payload: No Document
  3677. :type payload: dict
  3678. :param client_id: id in ClientRepresentation
  3679. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3680. :type client_id: str
  3681. :param scope_id: No Document
  3682. :type scope_id: str
  3683. :return: Keycloak server response
  3684. :rtype: bytes
  3685. """
  3686. params_path = {
  3687. "realm-name": self.connection.realm_name,
  3688. "id": client_id,
  3689. "scope-id": scope_id,
  3690. }
  3691. data_raw = self.connection.raw_put(
  3692. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_SCOPE_PERMISSION.format(**params_path),
  3693. data=json.dumps(payload),
  3694. )
  3695. return raise_error_from_response(data_raw, KeycloakPutError, expected_codes=[201])
  3696. def get_client_authz_client_policies(self, client_id):
  3697. """Get policies for a given client.
  3698. :param client_id: id in ClientRepresentation
  3699. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3700. :type client_id: str
  3701. :return: Keycloak server response (RoleRepresentation)
  3702. :rtype: list
  3703. """
  3704. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3705. data_raw = self.connection.raw_get(
  3706. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path)
  3707. )
  3708. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[200])
  3709. def create_client_authz_client_policy(self, payload, client_id):
  3710. """Create a new policy for a given client.
  3711. Payload example::
  3712. payload={
  3713. "type": "client",
  3714. "logic": "POSITIVE",
  3715. "decisionStrategy": "UNANIMOUS",
  3716. "name": "My Policy",
  3717. "clients": [other_client_id],
  3718. }
  3719. :param payload: No Document
  3720. :type payload: dict
  3721. :param client_id: id in ClientRepresentation
  3722. https://www.keycloak.org/docs-api/18.0/rest-api/index.html#_clientrepresentation
  3723. :type client_id: str
  3724. :return: Keycloak server response (RoleRepresentation)
  3725. :rtype: bytes
  3726. """
  3727. params_path = {"realm-name": self.connection.realm_name, "id": client_id}
  3728. data_raw = self.connection.raw_post(
  3729. urls_patterns.URL_ADMIN_CLIENT_AUTHZ_CLIENT_POLICY.format(**params_path),
  3730. data=json.dumps(payload),
  3731. )
  3732. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[201])
  3733. def get_composite_client_roles_of_group(self, client_id, group_id, brief_representation=True):
  3734. """Get the composite client roles of the given group for the given client.
  3735. :param client_id: id of the client.
  3736. :type client_id: str
  3737. :param group_id: id of the group.
  3738. :type group_id: str
  3739. :param brief_representation: whether to omit attributes in the response
  3740. :type brief_representation: bool
  3741. :return: the composite client roles of the group (list of RoleRepresentation).
  3742. :rtype: list
  3743. """
  3744. params_path = {
  3745. "realm-name": self.connection.realm_name,
  3746. "id": group_id,
  3747. "client-id": client_id,
  3748. }
  3749. params = {"briefRepresentation": brief_representation}
  3750. data_raw = self.connection.raw_get(
  3751. urls_patterns.URL_ADMIN_GROUPS_CLIENT_ROLES_COMPOSITE.format(**params_path), **params
  3752. )
  3753. return raise_error_from_response(data_raw, KeycloakGetError)
  3754. def get_role_client_level_children(self, client_id, role_id):
  3755. """Get the child roles of which the given composite client role is composed of.
  3756. :param client_id: id of the client.
  3757. :type client_id: str
  3758. :param role_id: id of the role.
  3759. :type role_id: str
  3760. :return: the child roles (list of RoleRepresentation).
  3761. :rtype: list
  3762. """
  3763. params_path = {
  3764. "realm-name": self.connection.realm_name,
  3765. "role-id": role_id,
  3766. "client-id": client_id,
  3767. }
  3768. data_raw = self.connection.raw_get(
  3769. urls_patterns.URL_ADMIN_CLIENT_ROLE_CHILDREN.format(**params_path)
  3770. )
  3771. return raise_error_from_response(data_raw, KeycloakGetError)
  3772. def upload_certificate(self, client_id, certcont):
  3773. """Upload a new certificate for the client.
  3774. :param client_id: id of the client.
  3775. :type client_id: str
  3776. :param certcont: the content of the certificate.
  3777. :type certcont: str
  3778. :return: dictionary {"certificate": "<certcont>"},
  3779. where <certcont> is the content of the uploaded certificate.
  3780. :rtype: dict
  3781. """
  3782. params_path = {
  3783. "realm-name": self.connection.realm_name,
  3784. "id": client_id,
  3785. "attr": "jwt.credential",
  3786. }
  3787. m = MultipartEncoder(fields={"keystoreFormat": "Certificate PEM", "file": certcont})
  3788. new_headers = copy.deepcopy(self.connection.headers)
  3789. new_headers["Content-Type"] = m.content_type
  3790. self.connection.headers = new_headers
  3791. data_raw = self.connection.raw_post(
  3792. urls_patterns.URL_ADMIN_CLIENT_CERT_UPLOAD.format(**params_path),
  3793. data=m,
  3794. headers=new_headers,
  3795. )
  3796. return raise_error_from_response(data_raw, KeycloakPostError)
  3797. def get_required_action_by_alias(self, action_alias):
  3798. """Get a required action by its alias.
  3799. :param action_alias: the alias of the required action.
  3800. :type action_alias: str
  3801. :return: the required action (RequiredActionProviderRepresentation).
  3802. :rtype: dict
  3803. """
  3804. actions = self.get_required_actions()
  3805. for a in actions:
  3806. if a["alias"] == action_alias:
  3807. return a
  3808. return None
  3809. def get_required_actions(self):
  3810. """Get the required actions for the realms.
  3811. :return: the required actions (list of RequiredActionProviderRepresentation).
  3812. :rtype: list
  3813. """
  3814. params_path = {"realm-name": self.connection.realm_name}
  3815. data_raw = self.connection.raw_get(
  3816. urls_patterns.URL_ADMIN_REQUIRED_ACTIONS.format(**params_path)
  3817. )
  3818. return raise_error_from_response(data_raw, KeycloakGetError)
  3819. def update_required_action(self, action_alias, payload):
  3820. """Update a required action.
  3821. :param action_alias: the action alias.
  3822. :type action_alias: str
  3823. :param payload: the new required action (RequiredActionProviderRepresentation).
  3824. :type payload: dict
  3825. :return: empty dictionary.
  3826. :rtype: dict
  3827. """
  3828. if not isinstance(payload, str):
  3829. payload = json.dumps(payload)
  3830. params_path = {"realm-name": self.connection.realm_name, "action-alias": action_alias}
  3831. data_raw = self.connection.raw_put(
  3832. urls_patterns.URL_ADMIN_REQUIRED_ACTIONS_ALIAS.format(**params_path), data=payload
  3833. )
  3834. return raise_error_from_response(data_raw, KeycloakPutError)
  3835. def get_bruteforce_detection_status(self, user_id):
  3836. """Get bruteforce detection status for user.
  3837. :param user_id: User id
  3838. :type user_id: str
  3839. :return: Bruteforce status.
  3840. :rtype: dict
  3841. """
  3842. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  3843. data_raw = self.connection.raw_get(
  3844. urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path)
  3845. )
  3846. return raise_error_from_response(data_raw, KeycloakGetError)
  3847. def clear_bruteforce_attempts_for_user(self, user_id):
  3848. """Clear bruteforce attempts for user.
  3849. :param user_id: User id
  3850. :type user_id: str
  3851. :return: empty dictionary.
  3852. :rtype: dict
  3853. """
  3854. params_path = {"realm-name": self.connection.realm_name, "id": user_id}
  3855. data_raw = self.connection.raw_delete(
  3856. urls_patterns.URL_ADMIN_ATTACK_DETECTION_USER.format(**params_path)
  3857. )
  3858. return raise_error_from_response(data_raw, KeycloakDeleteError)
  3859. def clear_all_bruteforce_attempts(self):
  3860. """Clear bruteforce attempts for all users in realm.
  3861. :return: empty dictionary.
  3862. :rtype: dict
  3863. """
  3864. params_path = {"realm-name": self.connection.realm_name}
  3865. data_raw = self.connection.raw_delete(
  3866. urls_patterns.URL_ADMIN_ATTACK_DETECTION.format(**params_path)
  3867. )
  3868. return raise_error_from_response(data_raw, KeycloakDeleteError)
  3869. def clear_keys_cache(self):
  3870. """Clear keys cache.
  3871. :return: empty dictionary.
  3872. :rtype: dict
  3873. """
  3874. params_path = {"realm-name": self.connection.realm_name}
  3875. data_raw = self.connection.raw_post(
  3876. urls_patterns.URL_ADMIN_CLEAR_KEYS_CACHE.format(**params_path), data=""
  3877. )
  3878. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  3879. def clear_realm_cache(self):
  3880. """Clear realm cache.
  3881. :return: empty dictionary.
  3882. :rtype: dict
  3883. """
  3884. params_path = {"realm-name": self.connection.realm_name}
  3885. data_raw = self.connection.raw_post(
  3886. urls_patterns.URL_ADMIN_CLEAR_REALM_CACHE.format(**params_path), data=""
  3887. )
  3888. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])
  3889. def clear_user_cache(self):
  3890. """Clear user cache.
  3891. :return: empty dictionary.
  3892. :rtype: dict
  3893. """
  3894. params_path = {"realm-name": self.connection.realm_name}
  3895. data_raw = self.connection.raw_post(
  3896. urls_patterns.URL_ADMIN_CLEAR_USER_CACHE.format(**params_path), data=""
  3897. )
  3898. return raise_error_from_response(data_raw, KeycloakPostError, expected_codes=[204])