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.

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