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.

1604 lines
62 KiB

7 years ago
6 years ago
6 years ago
6 years ago
7 years ago
6 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
7 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
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 List, Iterable
  28. from keycloak.urls_patterns import URL_ADMIN_GROUPS_REALM_ROLES, \
  29. URL_ADMIN_GET_GROUPS_REALM_ROLES, URL_ADMIN_REALM_ROLES_ROLE_BY_NAME, URL_ADMIN_GROUPS_CLIENT_ROLES, \
  30. URL_ADMIN_GET_GROUPS_REALM_ROLES, URL_ADMIN_REALM_ROLES_ROLE_BY_NAME, \
  31. URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE
  32. from .connection import ConnectionManager
  33. from .exceptions import raise_error_from_response, KeycloakGetError
  34. from .keycloak_openid import KeycloakOpenID
  35. from .urls_patterns import URL_ADMIN_SERVER_INFO, URL_ADMIN_CLIENT_AUTHZ_RESOURCES, URL_ADMIN_CLIENT_ROLES, \
  36. URL_ADMIN_GET_SESSIONS, URL_ADMIN_RESET_PASSWORD, URL_ADMIN_SEND_UPDATE_ACCOUNT, \
  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, \
  42. 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_REALM, URL_ADMIN_COMPONENTS, URL_ADMIN_COMPONENT, URL_ADMIN_KEYS, \
  45. URL_ADMIN_USER_FEDERATED_IDENTITY, URL_ADMIN_USER_FEDERATED_IDENTITIES, \
  46. URL_ADMIN_FLOWS_EXECUTIONS_EXEUCUTION, URL_ADMIN_FLOWS_EXECUTIONS_FLOW, URL_ADMIN_FLOWS_COPY, \
  47. URL_ADMIN_FLOWS_ALIAS
  48. class KeycloakAdmin:
  49. PAGE_SIZE = 100
  50. _server_url = None
  51. _username = None
  52. _password = None
  53. _realm_name = None
  54. _client_id = None
  55. _verify = None
  56. _client_secret_key = None
  57. _auto_refresh_token = None
  58. _connection = None
  59. _token = None
  60. _custom_headers = None
  61. _user_realm_name = None
  62. def __init__(self, server_url, username=None, password=None, realm_name='master', client_id='admin-cli', verify=True,
  63. client_secret_key=None, custom_headers=None, user_realm_name=None, auto_refresh_token=None):
  64. """
  65. :param server_url: Keycloak server url
  66. :param username: admin username
  67. :param password: admin password
  68. :param realm_name: realm name
  69. :param client_id: client id
  70. :param verify: True if want check connection SSL
  71. :param client_secret_key: client secret key
  72. :param custom_headers: dict of custom header to pass to each HTML request
  73. :param user_realm_name: The realm name of the user, if different from realm_name
  74. :param auto_refresh_token: list of methods that allows automatic token refresh. ex: ['get', 'put', 'post', 'delete']
  75. """
  76. self.server_url = server_url
  77. self.username = username
  78. self.password = password
  79. self.realm_name = realm_name
  80. self.client_id = client_id
  81. self.verify = verify
  82. self.client_secret_key = client_secret_key
  83. self.auto_refresh_token = auto_refresh_token or []
  84. self.user_realm_name = user_realm_name
  85. self.custom_headers = custom_headers
  86. # Get token Admin
  87. self.get_token()
  88. @property
  89. def server_url(self):
  90. return self._server_url
  91. @server_url.setter
  92. def server_url(self, value):
  93. self._server_url = value
  94. @property
  95. def realm_name(self):
  96. return self._realm_name
  97. @realm_name.setter
  98. def realm_name(self, value):
  99. self._realm_name = value
  100. @property
  101. def connection(self):
  102. return self._connection
  103. @connection.setter
  104. def connection(self, value):
  105. self._connection = value
  106. @property
  107. def client_id(self):
  108. return self._client_id
  109. @client_id.setter
  110. def client_id(self, value):
  111. self._client_id = value
  112. @property
  113. def client_secret_key(self):
  114. return self._client_secret_key
  115. @client_secret_key.setter
  116. def client_secret_key(self, value):
  117. self._client_secret_key = value
  118. @property
  119. def verify(self):
  120. return self._verify
  121. @verify.setter
  122. def verify(self, value):
  123. self._verify = value
  124. @property
  125. def username(self):
  126. return self._username
  127. @username.setter
  128. def username(self, value):
  129. self._username = value
  130. @property
  131. def password(self):
  132. return self._password
  133. @password.setter
  134. def password(self, value):
  135. self._password = value
  136. @property
  137. def token(self):
  138. return self._token
  139. @token.setter
  140. def token(self, value):
  141. self._token = value
  142. @property
  143. def auto_refresh_token(self):
  144. return self._auto_refresh_token
  145. @property
  146. def user_realm_name(self):
  147. return self._user_realm_name
  148. @user_realm_name.setter
  149. def user_realm_name(self, value):
  150. self._user_realm_name = value
  151. @property
  152. def custom_headers(self):
  153. return self._custom_headers
  154. @custom_headers.setter
  155. def custom_headers(self, value):
  156. self._custom_headers = value
  157. @auto_refresh_token.setter
  158. def auto_refresh_token(self, value):
  159. allowed_methods = {'get', 'post', 'put', 'delete'}
  160. if not isinstance(value, Iterable):
  161. raise TypeError('Expected a list of strings among {allowed}'.format(allowed=allowed_methods))
  162. if not all(method in allowed_methods for method in value):
  163. raise TypeError('Unexpected method in auto_refresh_token, accepted methods are {allowed}'.format(allowed=allowed_methods))
  164. self._auto_refresh_token = value
  165. def __fetch_all(self, url, query=None):
  166. '''Wrapper function to paginate GET requests
  167. :param url: The url on which the query is executed
  168. :param query: Existing query parameters (optional)
  169. :return: Combined results of paginated queries
  170. '''
  171. results = []
  172. # initalize query if it was called with None
  173. if not query:
  174. query = {}
  175. page = 0
  176. query['max'] = self.PAGE_SIZE
  177. # fetch until we can
  178. while True:
  179. query['first'] = page*self.PAGE_SIZE
  180. partial_results = raise_error_from_response(
  181. self.raw_get(url, **query),
  182. KeycloakGetError)
  183. if not partial_results:
  184. break
  185. results.extend(partial_results)
  186. page += 1
  187. return results
  188. def import_realm(self, payload):
  189. """
  190. Import a new realm from a RealmRepresentation. Realm name must be unique.
  191. RealmRepresentation
  192. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
  193. :param payload: RealmRepresentation
  194. :return: RealmRepresentation
  195. """
  196. data_raw = self.raw_post(URL_ADMIN_REALMS,
  197. data=json.dumps(payload))
  198. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  199. def get_realms(self):
  200. """
  201. Lists all realms in Keycloak deployment
  202. :return: realms list
  203. """
  204. data_raw = self.raw_get(URL_ADMIN_REALMS)
  205. return raise_error_from_response(data_raw, KeycloakGetError)
  206. def create_realm(self, payload, skip_exists=False):
  207. """
  208. Create a realm
  209. RealmRepresentation:
  210. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
  211. :param payload: RealmRepresentation
  212. :param skip_exists: Skip if Realm already exist.
  213. :return: Keycloak server response (RealmRepresentation)
  214. """
  215. data_raw = self.raw_post(URL_ADMIN_REALMS,
  216. data=json.dumps(payload))
  217. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  218. def update_realm(self, realm_name, payload):
  219. """
  220. Update a realm. This wil only update top level attributes and will ignore any user,
  221. role, or client information in the payload.
  222. RealmRepresentation:
  223. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_realmrepresentation
  224. :param realm_name: Realm name (not the realm id)
  225. :param payload: RealmRepresentation
  226. :return: Http response
  227. """
  228. params_path = {"realm-name": realm_name}
  229. data_raw = self.raw_put(URL_ADMIN_REALM.format(**params_path),
  230. data=json.dumps(payload))
  231. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  232. def delete_realm(self, realm_name):
  233. """
  234. Delete a realm
  235. :param realm_name: Realm name (not the realm id)
  236. :return: Http response
  237. """
  238. params_path = {"realm-name": realm_name}
  239. data_raw = self.raw_delete(URL_ADMIN_REALM.format(**params_path))
  240. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  241. def get_users(self, query=None):
  242. """
  243. Return a list of users, filtered according to query parameters
  244. UserRepresentation
  245. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_userrepresentation
  246. :param query: Query parameters (optional)
  247. :return: users list
  248. """
  249. params_path = {"realm-name": self.realm_name}
  250. return self.__fetch_all(URL_ADMIN_USERS.format(**params_path), query)
  251. def get_idps(self):
  252. """
  253. Returns a list of ID Providers,
  254. IdentityProviderRepresentation
  255. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_identityproviderrepresentation
  256. :return: array IdentityProviderRepresentation
  257. """
  258. params_path = {"realm-name": self.realm_name}
  259. data_raw = self.raw_get(URL_ADMIN_IDPS.format(**params_path))
  260. return raise_error_from_response(data_raw, KeycloakGetError)
  261. def create_user(self, payload):
  262. """
  263. Create a new user. Username must be unique
  264. UserRepresentation
  265. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_userrepresentation
  266. :param payload: UserRepresentation
  267. :return: UserRepresentation
  268. """
  269. params_path = {"realm-name": self.realm_name}
  270. exists = self.get_user_id(username=payload['username'])
  271. if exists is not None:
  272. return str(exists)
  273. data_raw = self.raw_post(URL_ADMIN_USERS.format(**params_path),
  274. data=json.dumps(payload))
  275. raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  276. _last_slash_idx = data_raw.headers['Location'].rindex('/')
  277. return data_raw.headers['Location'][_last_slash_idx + 1:]
  278. def users_count(self):
  279. """
  280. User counter
  281. :return: counter
  282. """
  283. params_path = {"realm-name": self.realm_name}
  284. data_raw = self.raw_get(URL_ADMIN_USERS_COUNT.format(**params_path))
  285. return raise_error_from_response(data_raw, KeycloakGetError)
  286. def get_user_id(self, username):
  287. """
  288. Get internal keycloak user id from username
  289. This is required for further actions against this user.
  290. UserRepresentation
  291. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_userrepresentation
  292. :param username: id in UserRepresentation
  293. :return: user_id
  294. """
  295. users = self.get_users(query={"search": username})
  296. return next((user["id"] for user in users if user["username"] == username), None)
  297. def get_user(self, user_id):
  298. """
  299. Get representation of the user
  300. :param user_id: User id
  301. UserRepresentation
  302. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_userrepresentation
  303. :return: UserRepresentation
  304. """
  305. params_path = {"realm-name": self.realm_name, "id": user_id}
  306. data_raw = self.raw_get(URL_ADMIN_USER.format(**params_path))
  307. return raise_error_from_response(data_raw, KeycloakGetError)
  308. def get_user_groups(self, user_id):
  309. """
  310. Returns a list of groups of which the user is a member
  311. :param user_id: User id
  312. :return: user groups list
  313. """
  314. params_path = {"realm-name": self.realm_name, "id": user_id}
  315. data_raw = self.raw_get(URL_ADMIN_USER_GROUPS.format(**params_path))
  316. return raise_error_from_response(data_raw, KeycloakGetError)
  317. def update_user(self, user_id, payload):
  318. """
  319. Update the user
  320. :param user_id: User id
  321. :param payload: UserRepresentation
  322. :return: Http response
  323. """
  324. params_path = {"realm-name": self.realm_name, "id": user_id}
  325. data_raw = self.raw_put(URL_ADMIN_USER.format(**params_path),
  326. data=json.dumps(payload))
  327. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  328. def delete_user(self, user_id):
  329. """
  330. Delete the user
  331. :param user_id: User id
  332. :return: Http response
  333. """
  334. params_path = {"realm-name": self.realm_name, "id": user_id}
  335. data_raw = self.raw_delete(URL_ADMIN_USER.format(**params_path))
  336. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  337. def set_user_password(self, user_id, password, temporary=True):
  338. """
  339. Set up a password for the user. If temporary is True, the user will have to reset
  340. the temporary password next time they log in.
  341. https://www.keycloak.org/docs-api/8.0/rest-api/#_users_resource
  342. https://www.keycloak.org/docs-api/8.0/rest-api/#_credentialrepresentation
  343. :param user_id: User id
  344. :param password: New password
  345. :param temporary: True if password is temporary
  346. :return:
  347. """
  348. payload = {"type": "password", "temporary": temporary, "value": password}
  349. params_path = {"realm-name": self.realm_name, "id": user_id}
  350. data_raw = self.raw_put(URL_ADMIN_RESET_PASSWORD.format(**params_path),
  351. data=json.dumps(payload))
  352. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  353. def consents_user(self, user_id):
  354. """
  355. Get consents granted by the user
  356. :param user_id: User id
  357. :return: consents
  358. """
  359. params_path = {"realm-name": self.realm_name, "id": user_id}
  360. data_raw = self.raw_get(URL_ADMIN_USER_CONSENTS.format(**params_path))
  361. return raise_error_from_response(data_raw, KeycloakGetError)
  362. def get_user_social_logins(self, user_id):
  363. """
  364. Returns a list of federated identities/social logins of which the user has been associated with
  365. :param user_id: User id
  366. :return: federated identities list
  367. """
  368. params_path = {"realm-name": self.realm_name, "id": user_id}
  369. data_raw = self.raw_get(URL_ADMIN_USER_FEDERATED_IDENTITIES.format(**params_path))
  370. return raise_error_from_response(data_raw, KeycloakGetError)
  371. def add_user_social_login(self, user_id, provider_id, provider_userid, provider_username):
  372. """
  373. Add a federated identity / social login provider to the user
  374. :param user_id: User id
  375. :param provider_id: Social login provider id
  376. :param provider_userid: userid specified by the provider
  377. :param provider_username: username specified by the provider
  378. :return:
  379. """
  380. payload = {"identityProvider": provider_id, "userId": provider_userid, "userName": provider_username}
  381. params_path = {"realm-name": self.realm_name, "id": user_id, "provider": provider_id}
  382. data_raw = self.raw_post(URL_ADMIN_USER_FEDERATED_IDENTITY.format(**params_path), data=json.dumps(payload))
  383. def send_update_account(self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None):
  384. """
  385. Send an update account email to the user. An email contains a
  386. link the user can click to perform a set of required actions.
  387. :param user_id: User id
  388. :param payload: A list of actions for the user to complete
  389. :param client_id: Client id (optional)
  390. :param lifespan: Number of seconds after which the generated token expires (optional)
  391. :param redirect_uri: The redirect uri (optional)
  392. :return:
  393. """
  394. params_path = {"realm-name": self.realm_name, "id": user_id}
  395. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  396. data_raw = self.raw_put(URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  397. data=payload, **params_query)
  398. return raise_error_from_response(data_raw, KeycloakGetError)
  399. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  400. """
  401. Send a update account email to the user An email contains a
  402. link the user can click to perform a set of required actions.
  403. :param user_id: User id
  404. :param client_id: Client id (optional)
  405. :param redirect_uri: Redirect uri (optional)
  406. :return:
  407. """
  408. params_path = {"realm-name": self.realm_name, "id": user_id}
  409. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  410. data_raw = self.raw_put(URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  411. data={}, **params_query)
  412. return raise_error_from_response(data_raw, KeycloakGetError)
  413. def get_sessions(self, user_id):
  414. """
  415. Get sessions associated with the user
  416. :param user_id: id of user
  417. UserSessionRepresentation
  418. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_usersessionrepresentation
  419. :return: UserSessionRepresentation
  420. """
  421. params_path = {"realm-name": self.realm_name, "id": user_id}
  422. data_raw = self.raw_get(URL_ADMIN_GET_SESSIONS.format(**params_path))
  423. return raise_error_from_response(data_raw, KeycloakGetError)
  424. def get_server_info(self):
  425. """
  426. Get themes, social providers, auth providers, and event listeners available on this server
  427. ServerInfoRepresentation
  428. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_serverinforepresentation
  429. :return: ServerInfoRepresentation
  430. """
  431. data_raw = self.raw_get(URL_ADMIN_SERVER_INFO)
  432. return raise_error_from_response(data_raw, KeycloakGetError)
  433. def get_groups(self):
  434. """
  435. Returns a list of groups belonging to the realm
  436. GroupRepresentation
  437. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  438. :return: array GroupRepresentation
  439. """
  440. params_path = {"realm-name": self.realm_name}
  441. return self.__fetch_all(URL_ADMIN_GROUPS.format(**params_path))
  442. def get_group(self, group_id):
  443. """
  444. Get group by id. Returns full group details
  445. GroupRepresentation
  446. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  447. :param group_id: The group id
  448. :return: Keycloak server response (GroupRepresentation)
  449. """
  450. params_path = {"realm-name": self.realm_name, "id": group_id}
  451. data_raw = self.raw_get(URL_ADMIN_GROUP.format(**params_path))
  452. return raise_error_from_response(data_raw, KeycloakGetError)
  453. def get_subgroups(self, group, path):
  454. """
  455. Utility function to iterate through nested group structures
  456. GroupRepresentation
  457. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  458. :param name: group (GroupRepresentation)
  459. :param path: group path (string)
  460. :return: Keycloak server response (GroupRepresentation)
  461. """
  462. for subgroup in group["subGroups"]:
  463. if subgroup['path'] == path:
  464. return subgroup
  465. elif subgroup["subGroups"]:
  466. for subgroup in group["subGroups"]:
  467. result = self.get_subgroups(subgroup, path)
  468. if result:
  469. return result
  470. # went through the tree without hits
  471. return None
  472. def get_group_members(self, group_id, **query):
  473. """
  474. Get members by group id. Returns group members
  475. GroupRepresentation
  476. https://www.keycloak.org/docs-api/8.0/rest-api/#_userrepresentation
  477. :param group_id: The group id
  478. :param query: Additional query parameters (see https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_getmembers)
  479. :return: Keycloak server response (UserRepresentation)
  480. """
  481. params_path = {"realm-name": self.realm_name, "id": group_id}
  482. return self.__fetch_all(URL_ADMIN_GROUP_MEMBERS.format(**params_path), query)
  483. def get_group_by_path(self, path, search_in_subgroups=False):
  484. """
  485. Get group id based on name or path.
  486. A straight name or path match with a top-level group will return first.
  487. Subgroups are traversed, the first to match path (or name with path) is returned.
  488. GroupRepresentation
  489. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  490. :param path: group path
  491. :param search_in_subgroups: True if want search in the subgroups
  492. :return: Keycloak server response (GroupRepresentation)
  493. """
  494. groups = self.get_groups()
  495. # TODO: Review this code is necessary
  496. for group in groups:
  497. if group['path'] == path:
  498. return group
  499. elif search_in_subgroups and group["subGroups"]:
  500. for group in group["subGroups"]:
  501. if group['path'] == path:
  502. return group
  503. res = self.get_subgroups(group, path)
  504. if res != None:
  505. return res
  506. return None
  507. def create_group(self, payload, parent=None, skip_exists=False):
  508. """
  509. Creates a group in the Realm
  510. :param payload: GroupRepresentation
  511. :param parent: parent group's id. Required to create a sub-group.
  512. :param skip_exists: If true then do not raise an error if it already exists
  513. GroupRepresentation
  514. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  515. :return: Http response
  516. """
  517. if parent is None:
  518. params_path = {"realm-name": self.realm_name}
  519. data_raw = self.raw_post(URL_ADMIN_GROUPS.format(**params_path),
  520. data=json.dumps(payload))
  521. else:
  522. params_path = {"realm-name": self.realm_name, "id": parent, }
  523. data_raw = self.raw_post(URL_ADMIN_GROUP_CHILD.format(**params_path),
  524. data=json.dumps(payload))
  525. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  526. def update_group(self, group_id, payload):
  527. """
  528. Update group, ignores subgroups.
  529. :param group_id: id of group
  530. :param payload: GroupRepresentation with updated information.
  531. GroupRepresentation
  532. https://www.keycloak.org/docs-api/8.0/rest-api/#_grouprepresentation
  533. :return: Http response
  534. """
  535. params_path = {"realm-name": self.realm_name, "id": group_id}
  536. data_raw = self.raw_put(URL_ADMIN_GROUP.format(**params_path),
  537. data=json.dumps(payload))
  538. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  539. def group_set_permissions(self, group_id, enabled=True):
  540. """
  541. Enable/Disable permissions for a group. Cannot delete group if disabled
  542. :param group_id: id of group
  543. :param enabled: boolean
  544. :return: Keycloak server response
  545. """
  546. params_path = {"realm-name": self.realm_name, "id": group_id}
  547. data_raw = self.raw_put(URL_ADMIN_GROUP_PERMISSIONS.format(**params_path),
  548. data=json.dumps({"enabled": enabled}))
  549. return raise_error_from_response(data_raw, KeycloakGetError)
  550. def group_user_add(self, user_id, group_id):
  551. """
  552. Add user to group (user_id and group_id)
  553. :param user_id: id of user
  554. :param group_id: id of group to add to
  555. :return: Keycloak server response
  556. """
  557. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  558. data_raw = self.raw_put(URL_ADMIN_USER_GROUP.format(**params_path), data=None)
  559. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  560. def group_user_remove(self, user_id, group_id):
  561. """
  562. Remove user from group (user_id and group_id)
  563. :param user_id: id of user
  564. :param group_id: id of group to remove from
  565. :return: Keycloak server response
  566. """
  567. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  568. data_raw = self.raw_delete(URL_ADMIN_USER_GROUP.format(**params_path))
  569. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  570. def delete_group(self, group_id):
  571. """
  572. Deletes a group in the Realm
  573. :param group_id: id of group to delete
  574. :return: Keycloak server response
  575. """
  576. params_path = {"realm-name": self.realm_name, "id": group_id}
  577. data_raw = self.raw_delete(URL_ADMIN_GROUP.format(**params_path))
  578. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  579. def get_clients(self):
  580. """
  581. Returns a list of clients belonging to the realm
  582. ClientRepresentation
  583. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  584. :return: Keycloak server response (ClientRepresentation)
  585. """
  586. params_path = {"realm-name": self.realm_name}
  587. data_raw = self.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  588. return raise_error_from_response(data_raw, KeycloakGetError)
  589. def get_client(self, client_id):
  590. """
  591. Get representation of the client
  592. ClientRepresentation
  593. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  594. :param client_id: id of client (not client-id)
  595. :return: Keycloak server response (ClientRepresentation)
  596. """
  597. params_path = {"realm-name": self.realm_name, "id": client_id}
  598. data_raw = self.raw_get(URL_ADMIN_CLIENT.format(**params_path))
  599. return raise_error_from_response(data_raw, KeycloakGetError)
  600. def get_client_id(self, client_name):
  601. """
  602. Get internal keycloak client id from client-id.
  603. This is required for further actions against this client.
  604. :param client_name: name in ClientRepresentation
  605. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  606. :return: client_id (uuid as string)
  607. """
  608. clients = self.get_clients()
  609. for client in clients:
  610. if client_name == client.get('name') or client_name == client.get('clientId'):
  611. return client["id"]
  612. return None
  613. def get_client_authz_settings(self, client_id):
  614. """
  615. Get authorization json from client.
  616. :param client_id: id in ClientRepresentation
  617. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  618. :return: Keycloak server response
  619. """
  620. params_path = {"realm-name": self.realm_name, "id": client_id}
  621. data_raw = self.raw_get(URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path))
  622. return data_raw
  623. def get_client_authz_resources(self, client_id):
  624. """
  625. Get resources from client.
  626. :param client_id: id in ClientRepresentation
  627. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  628. :return: Keycloak server response
  629. """
  630. params_path = {"realm-name": self.realm_name, "id": client_id}
  631. data_raw = self.raw_get(URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path))
  632. return data_raw
  633. def create_client(self, payload, skip_exists=False):
  634. """
  635. Create a client
  636. ClientRepresentation: https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  637. :param skip_exists: If true then do not raise an error if client already exists
  638. :param payload: ClientRepresentation
  639. :return: Keycloak server response (UserRepresentation)
  640. """
  641. params_path = {"realm-name": self.realm_name}
  642. data_raw = self.raw_post(URL_ADMIN_CLIENTS.format(**params_path),
  643. data=json.dumps(payload))
  644. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  645. def update_client(self, client_id, payload):
  646. """
  647. Update a client
  648. :param client_id: Client id
  649. :param payload: ClientRepresentation
  650. :return: Http response
  651. """
  652. params_path = {"realm-name": self.realm_name, "id": client_id}
  653. data_raw = self.raw_put(URL_ADMIN_CLIENT.format(**params_path),
  654. data=json.dumps(payload))
  655. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  656. def delete_client(self, client_id):
  657. """
  658. Get representation of the client
  659. ClientRepresentation
  660. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_clientrepresentation
  661. :param client_id: keycloak client id (not oauth client-id)
  662. :return: Keycloak server response (ClientRepresentation)
  663. """
  664. params_path = {"realm-name": self.realm_name, "id": client_id}
  665. data_raw = self.raw_delete(URL_ADMIN_CLIENT.format(**params_path))
  666. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  667. def get_realm_roles(self):
  668. """
  669. Get all roles for the realm or client
  670. RoleRepresentation
  671. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  672. :return: Keycloak server response (RoleRepresentation)
  673. """
  674. params_path = {"realm-name": self.realm_name}
  675. data_raw = self.raw_get(URL_ADMIN_REALM_ROLES.format(**params_path))
  676. return raise_error_from_response(data_raw, KeycloakGetError)
  677. def get_client_roles(self, client_id):
  678. """
  679. Get all roles for the client
  680. :param client_id: id of client (not client-id)
  681. RoleRepresentation
  682. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  683. :return: Keycloak server response (RoleRepresentation)
  684. """
  685. params_path = {"realm-name": self.realm_name, "id": client_id}
  686. data_raw = self.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  687. return raise_error_from_response(data_raw, KeycloakGetError)
  688. def get_client_role(self, client_id, role_name):
  689. """
  690. Get client role id by name
  691. This is required for further actions with this role.
  692. :param client_id: id of client (not client-id)
  693. :param role_name: roles name (not id!)
  694. RoleRepresentation
  695. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  696. :return: role_id
  697. """
  698. params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name}
  699. data_raw = self.raw_get(URL_ADMIN_CLIENT_ROLE.format(**params_path))
  700. return raise_error_from_response(data_raw, KeycloakGetError)
  701. def get_client_role_id(self, client_id, role_name):
  702. """
  703. Warning: Deprecated
  704. Get client role id by name
  705. This is required for further actions with this role.
  706. :param client_id: id of client (not client-id)
  707. :param role_name: roles name (not id!)
  708. RoleRepresentation
  709. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  710. :return: role_id
  711. """
  712. role = self.get_client_role(client_id, role_name)
  713. return role.get("id")
  714. def create_client_role(self, client_role_id, payload, skip_exists=False):
  715. """
  716. Create a client role
  717. RoleRepresentation
  718. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  719. :param client_role_id: id of client (not client-id)
  720. :param payload: RoleRepresentation
  721. :param skip_exists: If true then do not raise an error if client role already exists
  722. :return: Keycloak server response (RoleRepresentation)
  723. """
  724. params_path = {"realm-name": self.realm_name, "id": client_role_id}
  725. data_raw = self.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path),
  726. data=json.dumps(payload))
  727. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  728. def delete_client_role(self, client_role_id, role_name):
  729. """
  730. Delete a client role
  731. RoleRepresentation
  732. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  733. :param client_role_id: id of client (not client-id)
  734. :param role_name: roles name (not id!)
  735. """
  736. params_path = {"realm-name": self.realm_name, "id": client_role_id, "role-name": role_name}
  737. data_raw = self.raw_delete(URL_ADMIN_CLIENT_ROLE.format(**params_path))
  738. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  739. def assign_client_role(self, user_id, client_id, roles):
  740. """
  741. Assign a client role to a user
  742. :param user_id: id of user
  743. :param client_id: id of client (not client-id)
  744. :param roles: roles list or role (use RoleRepresentation)
  745. :return Keycloak server response
  746. """
  747. payload = roles if isinstance(roles, list) else [roles]
  748. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  749. data_raw = self.raw_post(URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  750. data=json.dumps(payload))
  751. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  752. def create_realm_role(self, payload, skip_exists=False):
  753. """
  754. Create a new role for the realm or client
  755. :param payload: The role (use RoleRepresentation)
  756. :param skip_exists: If true then do not raise an error if realm role already exists
  757. :return Keycloak server response
  758. """
  759. params_path = {"realm-name": self.realm_name}
  760. data_raw = self.raw_post(URL_ADMIN_REALM_ROLES.format(**params_path),
  761. data=json.dumps(payload))
  762. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  763. def get_realm_role(self, role_name):
  764. """
  765. Get realm role by role name
  766. :param role_name: role's name, not id!
  767. RoleRepresentation
  768. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_rolerepresentation
  769. :return: role_id
  770. """
  771. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  772. data_raw = self.raw_get(URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path))
  773. return raise_error_from_response(data_raw, KeycloakGetError)
  774. def update_realm_role(self, role_name, payload):
  775. """
  776. Update a role for the realm by name
  777. :param role_name: The name of the role to be updated
  778. :param payload: The role (use RoleRepresentation)
  779. :return Keycloak server response
  780. """
  781. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  782. data_raw = self.connection.raw_put(URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path),
  783. data=json.dumps(payload))
  784. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  785. def delete_realm_role(self, role_name):
  786. """
  787. Delete a role for the realm by name
  788. :param payload: The role name {'role-name':'name-of-the-role'}
  789. :return Keycloak server response
  790. """
  791. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  792. data_raw = self.connection.raw_delete(
  793. URL_ADMIN_REALM_ROLES_ROLE_BY_NAME.format(**params_path))
  794. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  795. def add_composite_realm_roles_to_role(self, role_name, roles):
  796. """
  797. Add composite roles to the role
  798. :param role_name: The name of the role
  799. :param roles: roles list or role (use RoleRepresentation) to be updated
  800. :return Keycloak server response
  801. """
  802. payload = roles if isinstance(roles, list) else [roles]
  803. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  804. data_raw = self.raw_post(
  805. URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  806. data=json.dumps(payload))
  807. return raise_error_from_response(data_raw, KeycloakGetError,
  808. expected_codes=[204])
  809. def remove_composite_realm_roles_to_role(self, role_name, roles):
  810. """
  811. Remove composite roles from the role
  812. :param role_name: The name of the role
  813. :param roles: roles list or role (use RoleRepresentation) to be removed
  814. :return Keycloak server response
  815. """
  816. payload = roles if isinstance(roles, list) else [roles]
  817. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  818. data_raw = self.raw_delete(
  819. URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path),
  820. data=json.dumps(payload))
  821. return raise_error_from_response(data_raw, KeycloakGetError,
  822. expected_codes=[204])
  823. def get_composite_realm_roles_of_role(self, role_name):
  824. """
  825. Get composite roles of the role
  826. :param role_name: The name of the role
  827. :return Keycloak server response (array RoleRepresentation)
  828. """
  829. params_path = {"realm-name": self.realm_name, "role-name": role_name}
  830. data_raw = self.raw_get(
  831. URL_ADMIN_REALM_ROLES_COMPOSITE_REALM_ROLE.format(**params_path))
  832. return raise_error_from_response(data_raw, KeycloakGetError)
  833. def assign_realm_roles(self, user_id, client_id, roles):
  834. """
  835. Assign realm roles to a user
  836. :param user_id: id of user
  837. :param client_id: id of client containing role (not client-id)
  838. :param roles: roles list or role (use RoleRepresentation)
  839. :return Keycloak server response
  840. """
  841. payload = roles if isinstance(roles, list) else [roles]
  842. params_path = {"realm-name": self.realm_name, "id": user_id}
  843. data_raw = self.raw_post(URL_ADMIN_USER_REALM_ROLES.format(**params_path),
  844. data=json.dumps(payload))
  845. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  846. def assign_group_realm_roles(self, group_id, roles):
  847. """
  848. Assign realm roles to a group
  849. :param group_id: id of groupp
  850. :param roles: roles list or role (use GroupRoleRepresentation)
  851. :return Keycloak server response
  852. """
  853. payload = roles if isinstance(roles, list) else [roles]
  854. params_path = {"realm-name": self.realm_name, "id": group_id}
  855. data_raw = self.raw_post(URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  856. data=json.dumps(payload))
  857. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  858. def delete_group_realm_roles(self, group_id, roles):
  859. """
  860. Delete realm roles of a group
  861. :param group_id: id of group
  862. :param roles: roles list or role (use GroupRoleRepresentation)
  863. :return Keycloak server response
  864. """
  865. payload = roles if isinstance(roles, list) else [roles]
  866. params_path = {"realm-name": self.realm_name, "id": group_id}
  867. data_raw = self.raw_delete(URL_ADMIN_GROUPS_REALM_ROLES.format(**params_path),
  868. data=json.dumps(payload))
  869. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  870. def get_group_realm_roles(self, group_id):
  871. """
  872. Get all realm roles for a group.
  873. :param user_id: id of the group
  874. :return: Keycloak server response (array RoleRepresentation)
  875. """
  876. params_path = {"realm-name": self.realm_name, "id": group_id}
  877. data_raw = self.raw_get(URL_ADMIN_GET_GROUPS_REALM_ROLES.format(**params_path))
  878. return raise_error_from_response(data_raw, KeycloakGetError)
  879. def assign_group_client_roles(self, group_id, client_id, roles):
  880. """
  881. Assign client roles to a group
  882. :param group_id: id of group
  883. :param client_id: id of client (not client-id)
  884. :param roles: roles list or role (use GroupRoleRepresentation)
  885. :return Keycloak server response
  886. """
  887. payload = roles if isinstance(roles, list) else [roles]
  888. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  889. data_raw = self.raw_post(URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  890. data=json.dumps(payload))
  891. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  892. def delete_group_client_roles(self, group_id, client_id, roles):
  893. """
  894. Delete client roles of a group
  895. :param group_id: id of group
  896. :param client_id: id of client (not client-id)
  897. :param roles: roles list or role (use GroupRoleRepresentation)
  898. :return Keycloak server response
  899. """
  900. payload = roles if isinstance(roles, list) else [roles]
  901. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  902. data_raw = self.raw_get(URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path))
  903. return raise_error_from_response(data_raw, KeycloakGetError)
  904. def get_group_client_roles(self, group_id, client_id, roles):
  905. """
  906. Get client roles of a group
  907. :param group_id: id of group
  908. :param client_id: id of client (not client-id)
  909. :param roles: roles list or role (use GroupRoleRepresentation)
  910. :return Keycloak server response (array RoleRepresentation)
  911. """
  912. payload = roles if isinstance(roles, list) else [roles]
  913. params_path = {"realm-name": self.realm_name, "id": group_id, "client-id": client_id}
  914. data_raw = self.raw_delete(URL_ADMIN_GROUPS_CLIENT_ROLES.format(**params_path),
  915. data=json.dumps(payload))
  916. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  917. def get_client_roles_of_user(self, user_id, client_id):
  918. """
  919. Get all client roles for a user.
  920. :param user_id: id of user
  921. :param client_id: id of client (not client-id)
  922. :return: Keycloak server response (array RoleRepresentation)
  923. """
  924. return self._get_client_roles_of_user(URL_ADMIN_USER_CLIENT_ROLES, user_id, client_id)
  925. def get_available_client_roles_of_user(self, user_id, client_id):
  926. """
  927. Get available client role-mappings for a user.
  928. :param user_id: id of user
  929. :param client_id: id of client (not client-id)
  930. :return: Keycloak server response (array RoleRepresentation)
  931. """
  932. return self._get_client_roles_of_user(URL_ADMIN_USER_CLIENT_ROLES_AVAILABLE, user_id, client_id)
  933. def get_composite_client_roles_of_user(self, user_id, client_id):
  934. """
  935. Get composite client role-mappings for a user.
  936. :param user_id: id of user
  937. :param client_id: id of client (not client-id)
  938. :return: Keycloak server response (array RoleRepresentation)
  939. """
  940. return self._get_client_roles_of_user(URL_ADMIN_USER_CLIENT_ROLES_COMPOSITE, user_id, client_id)
  941. def _get_client_roles_of_user(self, client_level_role_mapping_url, user_id, client_id):
  942. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  943. data_raw = self.raw_get(client_level_role_mapping_url.format(**params_path))
  944. return raise_error_from_response(data_raw, KeycloakGetError)
  945. def delete_client_roles_of_user(self, user_id, client_id, roles):
  946. """
  947. Delete client roles from a user.
  948. :param user_id: id of user
  949. :param client_id: id of client containing role (not client-id)
  950. :param roles: roles list or role to delete (use RoleRepresentation)
  951. :return: Keycloak server response
  952. """
  953. payload = roles if isinstance(roles, list) else [roles]
  954. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  955. data_raw = self.raw_delete(URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  956. data=json.dumps(payload))
  957. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  958. def get_authentication_flows(self):
  959. """
  960. Get authentication flows. Returns all flow details
  961. AuthenticationFlowRepresentation
  962. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationflowrepresentation
  963. :return: Keycloak server response (AuthenticationFlowRepresentation)
  964. """
  965. params_path = {"realm-name": self.realm_name}
  966. data_raw = self.raw_get(URL_ADMIN_FLOWS.format(**params_path))
  967. return raise_error_from_response(data_raw, KeycloakGetError)
  968. def get_authentication_flow_for_id(self, flow_id):
  969. """
  970. Get one authentication flow by it's id/alias. Returns all flow details
  971. AuthenticationFlowRepresentation
  972. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationflowrepresentation
  973. :param flow_id: the id of a flow NOT it's alias
  974. :return: Keycloak server response (AuthenticationFlowRepresentation)
  975. """
  976. params_path = {"realm-name": self.realm_name, "flow-id": flow_id}
  977. data_raw = self.raw_get(URL_ADMIN_FLOWS_ALIAS.format(**params_path))
  978. return raise_error_from_response(data_raw, KeycloakGetError)
  979. def create_authentication_flow(self, payload, skip_exists=False):
  980. """
  981. Create a new authentication flow
  982. AuthenticationFlowRepresentation
  983. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationflowrepresentation
  984. :param payload: AuthenticationFlowRepresentation
  985. :param skip_exists: If true then do not raise an error if authentication flow already exists
  986. :return: Keycloak server response (RoleRepresentation)
  987. """
  988. params_path = {"realm-name": self.realm_name}
  989. data_raw = self.raw_post(URL_ADMIN_FLOWS.format(**params_path),
  990. data=payload)
  991. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  992. def copy_authentication_flow(self, payload, flow_alias):
  993. """
  994. Copy existing authentication flow under a new name. The new name is given as 'newName' attribute of the passed payload.
  995. :param payload: JSON containing 'newName' attribute
  996. :param flow_alias: the flow alias
  997. :return: Keycloak server response (RoleRepresentation)
  998. """
  999. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1000. data_raw = self.raw_post(URL_ADMIN_FLOWS_COPY.format(**params_path),
  1001. data=payload)
  1002. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  1003. def get_authentication_flow_executions(self, flow_alias):
  1004. """
  1005. Get authentication flow executions. Returns all execution steps
  1006. :param flow_alias: the flow alias
  1007. :return: Response(json)
  1008. """
  1009. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1010. data_raw = self.raw_get(URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path))
  1011. return raise_error_from_response(data_raw, KeycloakGetError)
  1012. def update_authentication_flow_executions(self, payload, flow_alias):
  1013. """
  1014. Update an authentication flow execution
  1015. AuthenticationExecutionInfoRepresentation
  1016. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1017. :param payload: AuthenticationExecutionInfoRepresentation
  1018. :param flow_alias: The flow alias
  1019. :return: Keycloak server response
  1020. """
  1021. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1022. data_raw = self.raw_put(URL_ADMIN_FLOWS_EXECUTIONS.format(**params_path),
  1023. data=payload)
  1024. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  1025. def create_authentication_flow_execution(self, payload, flow_alias):
  1026. """
  1027. Create an authentication flow execution
  1028. AuthenticationExecutionInfoRepresentation
  1029. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationexecutioninforepresentation
  1030. :param payload: AuthenticationExecutionInfoRepresentation
  1031. :param flow_alias: The flow alias
  1032. :return: Keycloak server response
  1033. """
  1034. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1035. data_raw = self.raw_post(URL_ADMIN_FLOWS_EXECUTIONS_EXEUCUTION.format(**params_path),
  1036. data=payload)
  1037. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  1038. def create_authentication_flow_subflow(self, payload, flow_alias, skip_exists=False):
  1039. """
  1040. Create a new sub authentication flow for a given authentication flow
  1041. AuthenticationFlowRepresentation
  1042. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_authenticationflowrepresentation
  1043. :param payload: AuthenticationFlowRepresentation
  1044. :param flow_alias: The flow alias
  1045. :param skip_exists: If true then do not raise an error if authentication flow already exists
  1046. :return: Keycloak server response (RoleRepresentation)
  1047. """
  1048. params_path = {"realm-name": self.realm_name, "flow-alias": flow_alias}
  1049. data_raw = self.raw_post(URL_ADMIN_FLOWS_EXECUTIONS_FLOW.format(**params_path),
  1050. data=payload)
  1051. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201], skip_exists=skip_exists)
  1052. def sync_users(self, storage_id, action):
  1053. """
  1054. Function to trigger user sync from provider
  1055. :param storage_id: The id of the user storage provider
  1056. :param action: Action can be "triggerFullSync" or "triggerChangedUsersSync"
  1057. :return:
  1058. """
  1059. data = {'action': action}
  1060. params_query = {"action": action}
  1061. params_path = {"realm-name": self.realm_name, "id": storage_id}
  1062. data_raw = self.raw_post(URL_ADMIN_USER_STORAGE.format(**params_path),
  1063. data=json.dumps(data), **params_query)
  1064. return raise_error_from_response(data_raw, KeycloakGetError)
  1065. def get_client_scopes(self):
  1066. """
  1067. Get representation of the client scopes for the realm where we are connected to
  1068. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_getclientscopes
  1069. :return: Keycloak server response Array of (ClientScopeRepresentation)
  1070. """
  1071. params_path = {"realm-name": self.realm_name}
  1072. data_raw = self.raw_get(URL_ADMIN_CLIENT_SCOPES.format(**params_path))
  1073. return raise_error_from_response(data_raw, KeycloakGetError)
  1074. def get_client_scope(self, client_scope_id):
  1075. """
  1076. Get representation of the client scopes for the realm where we are connected to
  1077. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_getclientscopes
  1078. :param client_scope_id: The id of the client scope
  1079. :return: Keycloak server response (ClientScopeRepresentation)
  1080. """
  1081. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1082. data_raw = self.raw_get(URL_ADMIN_CLIENT_SCOPE.format(**params_path))
  1083. return raise_error_from_response(data_raw, KeycloakGetError)
  1084. def add_mapper_to_client_scope(self, client_scope_id, payload):
  1085. """
  1086. Add a mapper to a client scope
  1087. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_create_mapper
  1088. :param client_scope_id: The id of the client scope
  1089. :param payload: ProtocolMapperRepresentation
  1090. :return: Keycloak server Response
  1091. """
  1092. params_path = {"realm-name": self.realm_name, "scope-id": client_scope_id}
  1093. data_raw = self.raw_post(
  1094. URL_ADMIN_CLIENT_SCOPES_ADD_MAPPER.format(**params_path), data=json.dumps(payload))
  1095. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  1096. def generate_client_secrets(self, client_id):
  1097. """
  1098. Generate a new secret for the client
  1099. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_regeneratesecret
  1100. :param client_id: id of client (not client-id)
  1101. :return: Keycloak server response (ClientRepresentation)
  1102. """
  1103. params_path = {"realm-name": self.realm_name, "id": client_id}
  1104. data_raw = self.raw_post(URL_ADMIN_CLIENT_SECRETS.format(**params_path), data=None)
  1105. return raise_error_from_response(data_raw, KeycloakGetError)
  1106. def get_client_secrets(self, client_id):
  1107. """
  1108. Get representation of the client secrets
  1109. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_getclientsecret
  1110. :param client_id: id of client (not client-id)
  1111. :return: Keycloak server response (ClientRepresentation)
  1112. """
  1113. params_path = {"realm-name": self.realm_name, "id": client_id}
  1114. data_raw = self.raw_get(URL_ADMIN_CLIENT_SECRETS.format(**params_path))
  1115. return raise_error_from_response(data_raw, KeycloakGetError)
  1116. def get_components(self, query=None):
  1117. """
  1118. Return a list of components, filtered according to query parameters
  1119. ComponentRepresentation
  1120. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_componentrepresentation
  1121. :param query: Query parameters (optional)
  1122. :return: components list
  1123. """
  1124. params_path = {"realm-name": self.realm_name}
  1125. data_raw = self.raw_get(URL_ADMIN_COMPONENTS.format(**params_path),
  1126. data=None, **query)
  1127. return raise_error_from_response(data_raw, KeycloakGetError)
  1128. def create_component(self, payload):
  1129. """
  1130. Create a new component.
  1131. ComponentRepresentation
  1132. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_componentrepresentation
  1133. :param payload: ComponentRepresentation
  1134. :return: UserRepresentation
  1135. """
  1136. params_path = {"realm-name": self.realm_name}
  1137. data_raw = self.raw_post(URL_ADMIN_COMPONENTS.format(**params_path),
  1138. data=json.dumps(payload))
  1139. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[201])
  1140. def get_component(self, component_id):
  1141. """
  1142. Get representation of the component
  1143. :param component_id: Component id
  1144. ComponentRepresentation
  1145. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_componentrepresentation
  1146. :return: ComponentRepresentation
  1147. """
  1148. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1149. data_raw = self.raw_get(URL_ADMIN_COMPONENT.format(**params_path))
  1150. return raise_error_from_response(data_raw, KeycloakGetError)
  1151. def update_component(self, component_id, payload):
  1152. """
  1153. Update the component
  1154. :param component_id: Component id
  1155. :param payload: ComponentRepresentation
  1156. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_componentrepresentation
  1157. :return: Http response
  1158. """
  1159. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1160. data_raw = self.raw_put(URL_ADMIN_COMPONENT.format(**params_path),
  1161. data=json.dumps(payload))
  1162. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  1163. def delete_component(self, component_id):
  1164. """
  1165. Delete the component
  1166. :param component_id: Component id
  1167. :return: Http response
  1168. """
  1169. params_path = {"realm-name": self.realm_name, "component-id": component_id}
  1170. data_raw = self.raw_delete(URL_ADMIN_COMPONENT.format(**params_path))
  1171. return raise_error_from_response(data_raw, KeycloakGetError, expected_codes=[204])
  1172. def get_keys(self):
  1173. """
  1174. Return a list of keys, filtered according to query parameters
  1175. KeysMetadataRepresentation
  1176. https://www.keycloak.org/docs-api/8.0/rest-api/index.html#_key_resource
  1177. :return: keys list
  1178. """
  1179. params_path = {"realm-name": self.realm_name}
  1180. data_raw = self.raw_get(URL_ADMIN_KEYS.format(**params_path),
  1181. data=None)
  1182. return raise_error_from_response(data_raw, KeycloakGetError)
  1183. def raw_get(self, *args, **kwargs):
  1184. """
  1185. Calls connection.raw_get.
  1186. If auto_refresh is set for *get* and *access_token* is expired, it will refresh the token
  1187. and try *get* once more.
  1188. """
  1189. r = self.connection.raw_get(*args, **kwargs)
  1190. if 'get' in self.auto_refresh_token and r.status_code == 401:
  1191. self.refresh_token()
  1192. return self.connection.raw_get(*args, **kwargs)
  1193. return r
  1194. def raw_post(self, *args, **kwargs):
  1195. """
  1196. Calls connection.raw_post.
  1197. If auto_refresh is set for *post* and *access_token* is expired, it will refresh the token
  1198. and try *post* once more.
  1199. """
  1200. r = self.connection.raw_post(*args, **kwargs)
  1201. if 'post' in self.auto_refresh_token and r.status_code == 401:
  1202. self.refresh_token()
  1203. return self.connection.raw_post(*args, **kwargs)
  1204. return r
  1205. def raw_put(self, *args, **kwargs):
  1206. """
  1207. Calls connection.raw_put.
  1208. If auto_refresh is set for *put* and *access_token* is expired, it will refresh the token
  1209. and try *put* once more.
  1210. """
  1211. r = self.connection.raw_put(*args, **kwargs)
  1212. if 'put' in self.auto_refresh_token and r.status_code == 401:
  1213. self.refresh_token()
  1214. return self.connection.raw_put(*args, **kwargs)
  1215. return r
  1216. def raw_delete(self, *args, **kwargs):
  1217. """
  1218. Calls connection.raw_delete.
  1219. If auto_refresh is set for *delete* and *access_token* is expired, it will refresh the token
  1220. and try *delete* once more.
  1221. """
  1222. r = self.connection.raw_delete(*args, **kwargs)
  1223. if 'delete' in self.auto_refresh_token and r.status_code == 401:
  1224. self.refresh_token()
  1225. return self.connection.raw_delete(*args, **kwargs)
  1226. return r
  1227. def get_token(self):
  1228. self.keycloak_openid = KeycloakOpenID(server_url=self.server_url, client_id=self.client_id,
  1229. realm_name=self.user_realm_name or self.realm_name, verify=self.verify,
  1230. client_secret_key=self.client_secret_key,
  1231. custom_headers=self.custom_headers)
  1232. grant_type = ["password"]
  1233. if self.client_secret_key:
  1234. grant_type = ["client_credentials"]
  1235. self._token = self.keycloak_openid.token(self.username, self.password, grant_type=grant_type)
  1236. headers = {
  1237. 'Authorization': 'Bearer ' + self.token.get('access_token'),
  1238. 'Content-Type': 'application/json'
  1239. }
  1240. if self.custom_headers is not None:
  1241. # merge custom headers to main headers
  1242. headers.update(self.custom_headers)
  1243. self._connection = ConnectionManager(base_url=self.server_url,
  1244. headers=headers,
  1245. timeout=60,
  1246. verify=self.verify)
  1247. def refresh_token(self):
  1248. refresh_token = self.token.get('refresh_token')
  1249. try:
  1250. self.token = self.keycloak_openid.refresh_token(refresh_token)
  1251. except KeycloakGetError as e:
  1252. if e.response_code == 400 and (b'Refresh token expired' in e.response_body or
  1253. b'Token is not active' in e.response_body):
  1254. self.get_token()
  1255. else:
  1256. raise
  1257. self.connection.add_param_headers('Authorization', 'Bearer ' + self.token.get('access_token'))