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.

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