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.

469 lines
17 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
  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. from .urls_patterns import URL_ADMIN_USERS_COUNT, URL_ADMIN_USER, URL_ADMIN_USER_CONSENTS, \
  18. URL_ADMIN_SEND_UPDATE_ACCOUNT, URL_ADMIN_RESET_PASSWORD, URL_ADMIN_SEND_VERIFY_EMAIL, URL_ADMIN_GET_SESSIONS, \
  19. URL_ADMIN_SERVER_INFO, URL_ADMIN_CLIENTS, URL_ADMIN_CLIENT, URL_ADMIN_CLIENT_ROLES, URL_ADMIN_REALM_ROLES, \
  20. URL_ADMIN_USER_CLIENT_ROLES, URL_ADMIN_USER_STORAGE
  21. from .keycloak_openid import KeycloakOpenID
  22. from .exceptions import raise_error_from_response, KeycloakGetError
  23. from .urls_patterns import (
  24. URL_ADMIN_USERS,
  25. )
  26. from .connection import ConnectionManager
  27. import json
  28. class KeycloakAdmin:
  29. def __init__(self, server_url, verify, username, password, realm_name='master', client_id='admin-cli'):
  30. self._username = username
  31. self._password = password
  32. self._client_id = client_id
  33. self._realm_name = realm_name
  34. # Get token Admin
  35. keycloak_openid = KeycloakOpenID(server_url=server_url, client_id=client_id, realm_name=realm_name, verify=verify)
  36. self._token = keycloak_openid.token(username, password)
  37. self._connection = ConnectionManager(base_url=server_url,
  38. headers={'Authorization': 'Bearer ' + self.token.get('access_token'),
  39. 'Content-Type': 'application/json'},
  40. timeout=60,
  41. verify=verify)
  42. @property
  43. def realm_name(self):
  44. return self._realm_name
  45. @realm_name.setter
  46. def realm_name(self, value):
  47. self._realm_name = value
  48. @property
  49. def connection(self):
  50. return self._connection
  51. @connection.setter
  52. def connection(self, value):
  53. self._connection = value
  54. @property
  55. def client_id(self):
  56. return self._client_id
  57. @client_id.setter
  58. def client_id(self, value):
  59. self._client_id = value
  60. @property
  61. def username(self):
  62. return self._username
  63. @username.setter
  64. def username(self, value):
  65. self._username = value
  66. @property
  67. def password(self):
  68. return self._password
  69. @password.setter
  70. def password(self, value):
  71. self._password = value
  72. @property
  73. def token(self):
  74. return self._token
  75. @token.setter
  76. def token(self, value):
  77. self._token = value
  78. def get_users(self, query=None):
  79. """
  80. Get users Returns a list of users, filtered according to query parameters
  81. :return: users list
  82. """
  83. params_path = {"realm-name": self.realm_name}
  84. data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path), **query)
  85. return raise_error_from_response(data_raw, KeycloakGetError)
  86. def create_user(self, payload):
  87. """
  88. Create a new user Username must be unique
  89. UserRepresentation
  90. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  91. :param payload: UserRepresentation
  92. :return: UserRepresentation
  93. """
  94. params_path = {"realm-name": self.realm_name}
  95. data_raw = self.connection.raw_post(URL_ADMIN_USERS.format(**params_path),
  96. data=json.dumps(payload))
  97. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  98. def users_count(self):
  99. """
  100. User counter
  101. :return: counter
  102. """
  103. params_path = {"realm-name": self.realm_name}
  104. data_raw = self.connection.raw_get(URL_ADMIN_USERS_COUNT.format(**params_path))
  105. return raise_error_from_response(data_raw, KeycloakGetError)
  106. def get_user_id(self, username):
  107. """
  108. Get internal keycloak user id from username
  109. This is required for further actions against this user.
  110. :param username:
  111. clientId in UserRepresentation
  112. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  113. :return: user_id (uuid as string)
  114. """
  115. params_path = {"realm-name": self.realm_name, "username": username}
  116. data_raw = self.connection.raw_get(URL_ADMIN_USERS.format(**params_path))
  117. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  118. for user in data_content:
  119. thisusername = json.dumps(user["username"]).strip('"')
  120. if thisusername == username:
  121. return json.dumps(user["id"]).strip('"')
  122. return None
  123. def get_user(self, user_id):
  124. """
  125. Get representation of the user
  126. :param user_id: User id
  127. UserRepresentation: http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_userrepresentation
  128. :return: UserRepresentation
  129. """
  130. params_path = {"realm-name": self.realm_name, "id": user_id}
  131. data_raw = self.connection.raw_get(URL_ADMIN_USER.format(**params_path))
  132. return raise_error_from_response(data_raw, KeycloakGetError)
  133. def update_user(self, user_id, payload):
  134. """
  135. Update the user
  136. :param user_id: User id
  137. :param payload: UserRepresentation
  138. :return: Http response
  139. """
  140. params_path = {"realm-name": self.realm_name, "id": user_id}
  141. data_raw = self.connection.raw_put(URL_ADMIN_USER.format(**params_path),
  142. data=json.dumps(payload))
  143. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  144. def delete_user(self, user_id):
  145. """
  146. Delete the user
  147. :param user_id: User id
  148. :return: Http response
  149. """
  150. params_path = {"realm-name": self.realm_name, "id": user_id}
  151. data_raw = self.connection.raw_delete(URL_ADMIN_USER.format(**params_path))
  152. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  153. def consents_user(self, user_id):
  154. """
  155. Get consents granted by the user
  156. :param user_id: User id
  157. :return: consents
  158. """
  159. params_path = {"realm-name": self.realm_name, "id": user_id}
  160. data_raw = self.connection.raw_get(URL_ADMIN_USER_CONSENTS.format(**params_path))
  161. return raise_error_from_response(data_raw, KeycloakGetError)
  162. def send_update_account(self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None):
  163. """
  164. Send a update account email to the user An email contains a
  165. link the user can click to perform a set of required actions.
  166. :param user_id:
  167. :param payload:
  168. :param client_id:
  169. :param lifespan:
  170. :param redirect_uri:
  171. :return:
  172. """
  173. params_path = {"realm-name": self.realm_name, "id": user_id}
  174. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  175. data_raw = self.connection.raw_put(URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  176. data=payload, **params_query)
  177. return raise_error_from_response(data_raw, KeycloakGetError)
  178. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  179. """
  180. Send a update account email to the user An email contains a
  181. link the user can click to perform a set of required actions.
  182. :param user_id: User id
  183. :param client_id: Client id
  184. :param redirect_uri: Redirect uri
  185. :return:
  186. """
  187. params_path = {"realm-name": self.realm_name, "id": user_id}
  188. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  189. data_raw = self.connection.raw_put(URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  190. data={}, **params_query)
  191. return raise_error_from_response(data_raw, KeycloakGetError)
  192. def get_sessions(self, user_id):
  193. """
  194. Get sessions associated with the user
  195. :param user_id: User id
  196. UserSessionRepresentation
  197. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_usersessionrepresentation
  198. :return: UserSessionRepresentation
  199. """
  200. params_path = {"realm-name": self.realm_name, "id": user_id}
  201. data_raw = self.connection.raw_get(URL_ADMIN_GET_SESSIONS.format(**params_path))
  202. return raise_error_from_response(data_raw, KeycloakGetError)
  203. def get_server_info(self):
  204. """
  205. Get themes, social providers, auth providers, and event listeners available on this server
  206. ServerInfoRepresentation
  207. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_serverinforepresentation
  208. :return: ServerInfoRepresentation
  209. """
  210. data_raw = self.connection.raw_get(URL_ADMIN_SERVER_INFO)
  211. return raise_error_from_response(data_raw, KeycloakGetError)
  212. def get_clients(self):
  213. """
  214. Get clients belonging to the realm Returns a list of clients belonging to the realm
  215. ClientRepresentation
  216. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  217. :return: ClientRepresentation
  218. """
  219. params_path = {"realm-name": self.realm_name}
  220. data_raw = self.connection.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  221. return raise_error_from_response(data_raw, KeycloakGetError)
  222. def get_client_id(self, client_id_name):
  223. """
  224. Get internal keycloak client id from client-id.
  225. This is required for further actions against this client.
  226. :param client_id_name:
  227. clientId in ClientRepresentation
  228. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  229. :return: client_id (uuid as string)
  230. """
  231. params_path = {"realm-name": self.realm_name, "clientId": client_id_name}
  232. data_raw = self.connection.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  233. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  234. for client in data_content:
  235. client_id = json.dumps(client["clientId"]).strip('"')
  236. if client_id == client_id_name:
  237. return json.dumps(client["id"]).strip('"')
  238. return None
  239. def get_client(self, client_id):
  240. """
  241. Get representation of the client
  242. ClientRepresentation
  243. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  244. :param client_id: id of client (not client-id)
  245. :return: ClientRepresentation
  246. """
  247. params_path = {"realm-name": self.realm_name, "id": client_id}
  248. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT.format(**params_path))
  249. return raise_error_from_response(data_raw, KeycloakGetError)
  250. def create_client(self, name, client_id, redirect_urls, protocol="openid-connect", public_client=True, direct_access_grants=True):
  251. """
  252. Create a client
  253. :param name: name of client, payload (ClientRepresentation)
  254. ClientRepresentation
  255. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  256. """
  257. data={}
  258. data["name"]=name
  259. data["clientId"]=client_id
  260. data["redirectUris"]=redirect_urls
  261. data["protocol"]=protocol
  262. data["publicClient"]=public_client
  263. data["directAccessGrantsEnabled"]=direct_access_grants
  264. params_path = {"realm-name": self.realm_name}
  265. data_raw = self.connection.raw_post(URL_ADMIN_CLIENTS.format(**params_path),
  266. data=json.dumps(data))
  267. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  268. def delete_client(self, client_id):
  269. """
  270. Get representation of the client
  271. ClientRepresentation
  272. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  273. :param client_id: id of client (not client-id)
  274. :return: ClientRepresentation
  275. """
  276. params_path = {"realm-name": self.realm_name, "id": client_id}
  277. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT.format(**params_path))
  278. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  279. def get_client_roles(self, client_id):
  280. """
  281. Get all roles for the client
  282. RoleRepresentation
  283. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  284. :param client_id: id of client (not client-id)
  285. :return: RoleRepresentation
  286. """
  287. params_path = {"realm-name": self.realm_name, "id": client_id}
  288. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  289. return raise_error_from_response(data_raw, KeycloakGetError)
  290. def get_client_role_id(self, client_id, role_name):
  291. """
  292. Get client role id
  293. This is required for further actions with this role.
  294. RoleRepresentation
  295. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  296. :param client_id: id of client (not client-id), role_name: name of role
  297. :return: role_id
  298. """
  299. params_path = {"realm-name": self.realm_name, "id": client_id}
  300. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  301. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  302. for role in data_content:
  303. this_role_name = json.dumps(role["name"]).strip('"')
  304. if this_role_name == role_name:
  305. return json.dumps(role["id"]).strip('"')
  306. return None
  307. def get_roles(self):
  308. """
  309. Get all roles for the realm or client
  310. RoleRepresentation
  311. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  312. :return: RoleRepresentation
  313. """
  314. params_path = {"realm-name": self.realm_name}
  315. data_raw = self.connection.raw_get(URL_ADMIN_REALM_ROLES.format(**params_path))
  316. return raise_error_from_response(data_raw, KeycloakGetError)
  317. def create_client_role(self, client_id, role_name):
  318. """
  319. Create a client role
  320. :param client_id: id of client (not client-id), payload (RoleRepresentation)
  321. RoleRepresentation
  322. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  323. """
  324. data={}
  325. data["name"]=role_name
  326. data["clientRole"]=True
  327. params_path = {"realm-name": self.realm_name, "id": client_id}
  328. data_raw = self.connection.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path),
  329. data=json.dumps(data))
  330. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  331. def delete_client_role(self, client_id, role_name):
  332. """
  333. Create a client role
  334. :param client_id: id of client (not client-id), payload (RoleRepresentation)
  335. RoleRepresentation
  336. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  337. """
  338. data={}
  339. data["name"]=role_name
  340. data["clientRole"]=True
  341. params_path = {"realm-name": self.realm_name, "id": client_id}
  342. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT_ROLES.format(**params_path) + "/" + role_name,
  343. data=json.dumps(data))
  344. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  345. def assign_client_role(self, user_id, client_id, role_id, role_name):
  346. """
  347. Assign a client role to a user
  348. :param client_id: id of client (not client-id), user_id: id of user, client_id: id of client containing role, role_id: client role id, role_name: client role name)
  349. """
  350. payload=[{}]
  351. payload[0]["id"]=role_id
  352. payload[0]["name"]=role_name
  353. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  354. data_raw = self.connection.raw_post(URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  355. data=json.dumps(payload))
  356. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  357. def sync_users(self, storage_id, action):
  358. data = {'action': action}
  359. params_path = {"realm-name": self.realm_name, "id": storage_id}
  360. params_query = {"action": action}
  361. data_raw = self.connection.raw_post(URL_ADMIN_USER_STORAGE.format(**params_path),
  362. data=json.dumps(data), **params_query)
  363. return raise_error_from_response(data_raw, KeycloakGetError)