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.

2308 lines
91 KiB

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