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.

681 lines
25 KiB

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
  1. # -*- coding: utf-8 -*-
  2. #
  3. # Copyright (C) 2017 Marcos Pereira <marcospereira.mpj@gmail.com>
  4. #
  5. # This program is free software: you can redistribute it and/or modify
  6. # it under the terms of the GNU Lesser General Public License as published by
  7. # the Free Software Foundation, either version 3 of the License, or
  8. # (at your option) any later version.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. # GNU Lesser General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU Lesser General Public License
  16. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. # Unless otherwise stated in the comments, "id", in e.g. user_id, refers to the
  18. # internal Keycloak server ID, usually a uuid string
  19. from keycloak.urls_patterns import URL_ADMIN_CLIENT_ROLE
  20. from .urls_patterns import \
  21. URL_ADMIN_USERS_COUNT, URL_ADMIN_USER, URL_ADMIN_USER_CONSENTS, \
  22. URL_ADMIN_SEND_UPDATE_ACCOUNT, URL_ADMIN_RESET_PASSWORD, URL_ADMIN_SEND_VERIFY_EMAIL, URL_ADMIN_GET_SESSIONS, \
  23. URL_ADMIN_SERVER_INFO, URL_ADMIN_CLIENTS, URL_ADMIN_CLIENT, URL_ADMIN_CLIENT_ROLES, URL_ADMIN_REALM_ROLES, \
  24. URL_ADMIN_CLIENT_AUTHZ_SETTINGS, URL_ADMIN_CLIENT_AUTHZ_RESOURCES, \
  25. URL_ADMIN_GROUP, URL_ADMIN_GROUPS, URL_ADMIN_GROUP_CHILD, URL_ADMIN_USER_GROUP,\
  26. URL_ADMIN_GROUP_PERMISSIONS, URL_ADMIN_USER_CLIENT_ROLES, URL_ADMIN_USER_STORAGE
  27. from .keycloak_openid import KeycloakOpenID
  28. from .exceptions import raise_error_from_response, KeycloakGetError
  29. from .urls_patterns import (
  30. URL_ADMIN_USERS,
  31. )
  32. from .connection import ConnectionManager
  33. import json
  34. class KeycloakAdmin:
  35. def __init__(self, server_url, username, password, realm_name='master', client_id='admin-cli', verify=True):
  36. """
  37. :param server_url: Keycloak server url
  38. :param username: admin username
  39. :param password: admin password
  40. :param realm_name: realm name
  41. :param client_id: client id
  42. :param verify: True if want check connection SSL
  43. """
  44. self._username = username
  45. self._password = password
  46. self._client_id = client_id
  47. self._realm_name = realm_name
  48. # Get token Admin
  49. keycloak_openid = KeycloakOpenID(server_url=server_url, client_id=client_id, realm_name=realm_name,
  50. verify=verify)
  51. self._token = keycloak_openid.token(username, password)
  52. self._connection = ConnectionManager(base_url=server_url,
  53. headers={'Authorization': 'Bearer ' + self.token.get('access_token'),
  54. 'Content-Type': 'application/json'},
  55. timeout=60,
  56. verify=verify)
  57. @property
  58. def realm_name(self):
  59. return self._realm_name
  60. @realm_name.setter
  61. def realm_name(self, value):
  62. self._realm_name = value
  63. @property
  64. def connection(self):
  65. return self._connection
  66. @connection.setter
  67. def connection(self, value):
  68. self._connection = value
  69. @property
  70. def client_id(self):
  71. return self._client_id
  72. @client_id.setter
  73. def client_id(self, value):
  74. self._client_id = value
  75. @property
  76. def username(self):
  77. return self._username
  78. @username.setter
  79. def username(self, value):
  80. self._username = value
  81. @property
  82. def password(self):
  83. return self._password
  84. @password.setter
  85. def password(self, value):
  86. self._password = value
  87. @property
  88. def token(self):
  89. return self._token
  90. @token.setter
  91. def token(self, value):
  92. self._token = value
  93. def get_users(self, query=None):
  94. """
  95. Get users Returns a list of users, filtered according to query parameters
  96. :return: users list
  97. """
  98. params_path = {"realm-name": self.realm_name}
  99. data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path), **query)
  100. return raise_error_from_response(data_raw, KeycloakGetError)
  101. def create_user(self, payload):
  102. """
  103. Create a new user Username must be unique
  104. UserRepresentation
  105. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  106. :param payload: UserRepresentation
  107. :return: UserRepresentation
  108. """
  109. params_path = {"realm-name": self.realm_name}
  110. data_raw = self.connection.raw_post(URL_ADMIN_USERS.format(**params_path),
  111. data=json.dumps(payload))
  112. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  113. def users_count(self):
  114. """
  115. User counter
  116. :return: counter
  117. """
  118. params_path = {"realm-name": self.realm_name}
  119. data_raw = self.connection.raw_get(URL_ADMIN_USERS_COUNT.format(**params_path))
  120. return raise_error_from_response(data_raw, KeycloakGetError)
  121. def get_user_id(self, username):
  122. """
  123. Get internal keycloak user id from username
  124. This is required for further actions against this user.
  125. UserRepresentation
  126. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  127. :param username: id in UserRepresentation
  128. :return: user_id
  129. """
  130. params_path = {"realm-name": self.realm_name, "username": username}
  131. data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path))
  132. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  133. for user in data_content:
  134. this_use_rname = json.dumps(user["username"]).strip('"')
  135. if this_use_rname == username:
  136. return json.dumps(user["id"]).strip('"')
  137. return None
  138. def get_user(self, user_id):
  139. """
  140. Get representation of the user
  141. :param user_id: User id
  142. UserRepresentation: http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  143. :return: UserRepresentation
  144. """
  145. params_path = {"realm-name": self.realm_name, "id": user_id}
  146. data_raw = self.connection.raw_get(URL_ADMIN_USER.format(**params_path))
  147. return raise_error_from_response(data_raw, KeycloakGetError)
  148. def update_user(self, user_id, payload):
  149. """
  150. Update the user
  151. :param user_id: User id
  152. :param payload: UserRepresentation
  153. :return: Http response
  154. """
  155. params_path = {"realm-name": self.realm_name, "id": user_id}
  156. data_raw = self.connection.raw_put(URL_ADMIN_USER.format(**params_path),
  157. data=json.dumps(payload))
  158. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  159. def delete_user(self, user_id):
  160. """
  161. Delete the user
  162. :param user_id: User id
  163. :return: Http response
  164. """
  165. params_path = {"realm-name": self.realm_name, "id": user_id}
  166. data_raw = self.connection.raw_delete(URL_ADMIN_USER.format(**params_path))
  167. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  168. def set_user_password(self, user_id, password, temporary=True):
  169. """
  170. Set up a password for the user. If temporary is True, the user will have to reset
  171. the temporary password next time they log in.
  172. http://www.keycloak.org/docs-api/3.2/rest-api/#_users_resource
  173. http://www.keycloak.org/docs-api/3.2/rest-api/#_credentialrepresentation
  174. :param user_id: User id
  175. :param password: New password
  176. :param temporary: True if password is temporary
  177. :return:
  178. """
  179. payload = {"type": "password", "temporary": temporary, "value": password}
  180. params_path = {"realm-name": self.realm_name, "id": user_id}
  181. data_raw = self.connection.raw_put(URL_ADMIN_RESET_PASSWORD.format(**params_path),
  182. data=json.dumps(payload))
  183. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  184. def consents_user(self, user_id):
  185. """
  186. Get consents granted by the user
  187. :param user_id: User id
  188. :return: consents
  189. """
  190. params_path = {"realm-name": self.realm_name, "id": user_id}
  191. data_raw = self.connection.raw_get(URL_ADMIN_USER_CONSENTS.format(**params_path))
  192. return raise_error_from_response(data_raw, KeycloakGetError)
  193. def send_update_account(self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None):
  194. """
  195. Send a update account email to the user An email contains a
  196. link the user can click to perform a set of required actions.
  197. :param user_id:
  198. :param payload:
  199. :param client_id:
  200. :param lifespan:
  201. :param redirect_uri:
  202. :return:
  203. """
  204. params_path = {"realm-name": self.realm_name, "id": user_id}
  205. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  206. data_raw = self.connection.raw_put(URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  207. data=payload, **params_query)
  208. return raise_error_from_response(data_raw, KeycloakGetError)
  209. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  210. """
  211. Send a update account email to the user An email contains a
  212. link the user can click to perform a set of required actions.
  213. :param user_id: User id
  214. :param client_id: Client id
  215. :param redirect_uri: Redirect uri
  216. :return:
  217. """
  218. params_path = {"realm-name": self.realm_name, "id": user_id}
  219. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  220. data_raw = self.connection.raw_put(URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  221. data={}, **params_query)
  222. return raise_error_from_response(data_raw, KeycloakGetError)
  223. def get_sessions(self, user_id):
  224. """
  225. Get sessions associated with the user
  226. :param user_id: id of user
  227. UserSessionRepresentation
  228. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_usersessionrepresentation
  229. :return: UserSessionRepresentation
  230. """
  231. params_path = {"realm-name": self.realm_name, "id": user_id}
  232. data_raw = self.connection.raw_get(URL_ADMIN_GET_SESSIONS.format(**params_path))
  233. return raise_error_from_response(data_raw, KeycloakGetError)
  234. def get_server_info(self):
  235. """
  236. Get themes, social providers, auth providers, and event listeners available on this server
  237. ServerInfoRepresentation
  238. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_serverinforepresentation
  239. :return: ServerInfoRepresentation
  240. """
  241. data_raw = self.connection.raw_get(URL_ADMIN_SERVER_INFO)
  242. return raise_error_from_response(data_raw, KeycloakGetError)
  243. def get_groups(self):
  244. """
  245. Get groups belonging to the realm. Returns a list of groups belonging to the realm
  246. GroupRepresentation
  247. http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
  248. :return: array GroupRepresentation
  249. """
  250. params_path = {"realm-name": self.realm_name}
  251. data_raw = self.connection.raw_get(URL_ADMIN_GROUPS.format(**params_path))
  252. return raise_error_from_response(data_raw, KeycloakGetError)
  253. def get_group(self, group_id):
  254. """
  255. Get group by id. Returns full group details
  256. GroupRepresentation
  257. http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
  258. :return: Keycloak server response (GroupRepresentation)
  259. """
  260. params_path = {"realm-name": self.realm_name, "id": group_id}
  261. data_raw = self.connection.raw_get(URL_ADMIN_GROUP.format(**params_path))
  262. return raise_error_from_response(data_raw, KeycloakGetError)
  263. def get_group_by_name(self, name_or_path, search_in_subgroups=False):
  264. """
  265. Get group id based on name or path.
  266. A straight name or path match with a top-level group will return first.
  267. Subgroups are traversed, the first to match path (or name with path) is returned.
  268. GroupRepresentation
  269. http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
  270. :param name: group name
  271. :param path: group path
  272. :param search_in_subgroups: True if want search in the subgroups
  273. :return: Keycloak server response (GroupRepresentation)
  274. """
  275. groups = self.get_groups()
  276. # TODO: Review this code is necessary
  277. for group in groups:
  278. if group['name'] == name_or_path or group['path'] == name_or_path:
  279. return group
  280. elif search_in_subgroups and group["subGroups"]:
  281. for subgroup in group["subGroups"]:
  282. if subgroup['name'] == name_or_path or subgroup['path'] == name_or_path:
  283. return subgroup
  284. return None
  285. def create_group(self, name=None, client_roles={}, realm_roles=[], sub_groups=[], path=None, parent=None):
  286. """
  287. Create a group in the Realm
  288. GroupRepresentation
  289. http://www.keycloak.org/docs-api/3.2/rest-api/#_grouprepresentation
  290. :param name: group name
  291. :param client_roles: (Dict) Client roles to include in groupp # Not demonstrated to work
  292. :param realm_roles: (List) Realm roles to include in group # Not demonstrated to work
  293. :param sub_groups: (List) Subgroups to include in groupp # Not demonstrated to work
  294. :param path: group path
  295. :param parent: parent group's id. Required to create a sub-group.
  296. :return: Keycloak server response (GroupRepresentation)
  297. """
  298. data = {"name": name or path,
  299. "path": path,
  300. "clientRoles": client_roles,
  301. "realmRoles": realm_roles,
  302. "subGroups": sub_groups}
  303. if parent is None:
  304. params_path = {"realm-name": self.realm_name}
  305. data_raw = self.connection.raw_post(URL_ADMIN_GROUPS.format(**params_path),
  306. data=json.dumps(data))
  307. else:
  308. params_path = {"realm-name": self.realm_name, "id": parent}
  309. data_raw = self.connection.raw_post(URL_ADMIN_GROUP_CHILD.format(**params_path),
  310. data=json.dumps(data))
  311. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  312. def group_set_permissions(self, group_id, enabled=True):
  313. """
  314. Enable/Disable permissions for a group. Cannot delete group if disabled
  315. :param group_id: id of group
  316. :param enabled: boolean
  317. :return: Keycloak server response
  318. """
  319. params_path = {"realm-name": self.realm_name, "id": group_id}
  320. data_raw = self.connection.raw_put(URL_ADMIN_GROUP_PERMISSIONS.format(**params_path),
  321. data=json.dumps({"enabled": enabled}))
  322. return raise_error_from_response(data_raw, KeycloakGetError)
  323. def group_user_add(self, user_id, group_id):
  324. """
  325. Add user to group (user_id and group_id)
  326. :param group_id: id of group
  327. :param user_id: id of user
  328. :param group_id: id of group to add to
  329. :return: Keycloak server response
  330. """
  331. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  332. data_raw = self.connection.raw_put(URL_ADMIN_USER_GROUP.format(**params_path), data=None)
  333. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  334. def group_user_remove(self, user_id, group_id):
  335. """
  336. Remove user from group (user_id and group_id)
  337. :param group_id: id of group
  338. :param user_id: id of user
  339. :param group_id: id of group to add to
  340. :return: Keycloak server response
  341. """
  342. params_path = {"realm-name": self.realm_name, "id": user_id, "group-id": group_id}
  343. data_raw = self.connection.raw_delete(URL_ADMIN_USER_GROUP.format(**params_path))
  344. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  345. def delete_group(self, group_id):
  346. """
  347. Deletes a group in the Realm
  348. :param group_id: id of group to delete
  349. :return: Keycloak server response
  350. """
  351. params_path = {"realm-name": self.realm_name, "id": group_id}
  352. data_raw = self.connection.raw_delete(URL_ADMIN_GROUP.format(**params_path))
  353. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  354. def get_clients(self):
  355. """
  356. Get clients belonging to the realm Returns a list of clients belonging to the realm
  357. ClientRepresentation
  358. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  359. :return: Keycloak server response (ClientRepresentation)
  360. """
  361. params_path = {"realm-name": self.realm_name}
  362. data_raw = self.connection.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  363. return raise_error_from_response(data_raw, KeycloakGetError)
  364. def get_client(self, client_id):
  365. """
  366. Get representation of the client
  367. ClientRepresentation
  368. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  369. :param client_id: id of client (not client-id)
  370. :return: Keycloak server response (ClientRepresentation)
  371. """
  372. params_path = {"realm-name": self.realm_name, "id": client_id}
  373. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT.format(**params_path))
  374. return raise_error_from_response(data_raw, KeycloakGetError)
  375. def get_client_id(self, client_name):
  376. """
  377. Get internal keycloak client id from client-id.
  378. This is required for further actions against this client.
  379. :param client_name: name in ClientRepresentation
  380. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  381. :return: client_id (uuid as string)
  382. """
  383. clients = self.get_clients()
  384. for client in clients:
  385. if client_name == client.get('name') or client_name == client.get('clientId'):
  386. return client["id"]
  387. return None
  388. def get_client_authz_settings(self, client_id):
  389. """
  390. Get authorization json from client.
  391. :param client_id: id in ClientRepresentation
  392. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  393. :return: Keycloak server response
  394. """
  395. params_path = {"realm-name": self.realm_name, "id": client_id}
  396. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_AUTHZ_SETTINGS.format(**params_path))
  397. return data_raw
  398. def get_client_authz_resources(self, client_id):
  399. """
  400. Get resources from client.
  401. :param client_id: id in ClientRepresentation
  402. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  403. :return: Keycloak server response
  404. """
  405. params_path = {"realm-name": self.realm_name, "id": client_id}
  406. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_AUTHZ_RESOURCES.format(**params_path))
  407. return data_raw
  408. def create_client(self, payload):
  409. """
  410. Create a client
  411. ClientRepresentation: http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  412. :param payload: ClientRepresentation
  413. :return: Keycloak server response (UserRepresentation)
  414. """
  415. params_path = {"realm-name": self.realm_name}
  416. data_raw = self.connection.raw_post(URL_ADMIN_CLIENTS.format(**params_path),
  417. data=json.dumps(payload))
  418. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  419. def delete_client(self, client_id):
  420. """
  421. Get representation of the client
  422. ClientRepresentation
  423. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  424. :param client_id: keycloak client id (not oauth client-id)
  425. :return: Keycloak server response (ClientRepresentation)
  426. """
  427. params_path = {"realm-name": self.realm_name, "id": client_id}
  428. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT.format(**params_path))
  429. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  430. def get_realm_roles(self):
  431. """
  432. Get all roles for the realm or client
  433. RoleRepresentation
  434. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  435. :return: Keycloak server response (RoleRepresentation)
  436. """
  437. params_path = {"realm-name": self.realm_name}
  438. data_raw = self.connection.raw_get(URL_ADMIN_REALM_ROLES.format(**params_path))
  439. return raise_error_from_response(data_raw, KeycloakGetError)
  440. def get_client_roles(self, client_id):
  441. """
  442. Get all roles for the client
  443. :param client_id: id of client (not client-id)
  444. RoleRepresentation
  445. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  446. :return: Keycloak server response (RoleRepresentation)
  447. """
  448. params_path = {"realm-name": self.realm_name, "id": client_id}
  449. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  450. return raise_error_from_response(data_raw, KeycloakGetError)
  451. def get_client_role(self, client_id, role_name):
  452. """
  453. Get client role id by name
  454. This is required for further actions with this role.
  455. :param client_id: id of client (not client-id)
  456. :param role_name: roles name (not id!)
  457. RoleRepresentation
  458. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  459. :return: role_id
  460. """
  461. params_path = {"realm-name": self.realm_name, "id": client_id, "role-name": role_name}
  462. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLE.format(**params_path))
  463. return raise_error_from_response(data_raw, KeycloakGetError)
  464. def get_client_role_id(self, client_id, role_name):
  465. """
  466. Warning: Deprecated
  467. Get client role id by name
  468. This is required for further actions with this role.
  469. :param client_id: id of client (not client-id)
  470. :param role_name: roles name (not id!)
  471. RoleRepresentation
  472. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  473. :return: role_id
  474. """
  475. role = self.get_client_role(client_id, role_name)
  476. return role.get("id")
  477. def create_client_role(self, payload):
  478. """
  479. Create a client role
  480. RoleRepresentation
  481. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  482. :param payload: id of client (not client-id), role_name: name of role
  483. :return: Keycloak server response (RoleRepresentation)
  484. """
  485. params_path = {"realm-name": self.realm_name, "id": self.client_id}
  486. data_raw = self.connection.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path),
  487. data=json.dumps(payload))
  488. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  489. def delete_client_role(self, role_name):
  490. """
  491. Create a client role
  492. RoleRepresentation
  493. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  494. :param role_name: roles name (not id!)
  495. """
  496. params_path = {"realm-name": self.realm_name, "id": self.client_id, "role-name": role_name}
  497. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT_ROLE.format(**params_path))
  498. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  499. def assign_client_role(self, user_id, client_id, roles):
  500. """
  501. Assign a client role to a user
  502. :param client_id: id of client (not client-id)
  503. :param user_id: id of user
  504. :param client_id: id of client containing role,
  505. :param roles: roles list or role (use RoleRepresentation)
  506. :return Keycloak server response
  507. """
  508. payload = roles if isinstance(roles, list) else [roles]
  509. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  510. data_raw = self.connection.raw_post(URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  511. data=json.dumps(payload))
  512. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  513. def sync_users(self, storage_id, action):
  514. """
  515. Function to trigger user sync from provider
  516. :param storage_id:
  517. :param action:
  518. :return:
  519. """
  520. data = {'action': action}
  521. params_query = {"action": action}
  522. params_path = {"realm-name": self.realm_name, "id": storage_id}
  523. data_raw = self.connection.raw_post(URL_ADMIN_USER_STORAGE.format(**params_path),
  524. data=json.dumps(data), **params_query)
  525. return raise_error_from_response(data_raw, KeycloakGetError)