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.

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