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.

2379 lines
94 KiB

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