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.

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