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.

2367 lines
93 KiB

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