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.

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