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.

463 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, username, email='', firstName='', lastName='', emailVerified=False, enabled=True):
  133. """
  134. Update the user
  135. :param user_id: User id
  136. :param payload: UserRepresentation
  137. :return: Http response
  138. """
  139. data = {"username": username, "email": email, "firstName": firstName, "lastName": lastName,
  140. "emailVerified": emailVerified, "enabled": enabled}
  141. params_path = {"realm-name": self.realm_name, "id": user_id}
  142. data_raw = self.connection.raw_put(URL_ADMIN_USER.format(**params_path),
  143. data=json.dumps(data))
  144. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  145. def delete_user(self, user_id):
  146. """
  147. Delete the user
  148. :param user_id: User id
  149. :return: Http response
  150. """
  151. params_path = {"realm-name": self.realm_name, "id": user_id}
  152. data_raw = self.connection.raw_delete(URL_ADMIN_USER.format(**params_path))
  153. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  154. def consents_user(self, user_id):
  155. """
  156. Get consents granted by the user
  157. :param user_id: User id
  158. :return: consents
  159. """
  160. params_path = {"realm-name": self.realm_name, "id": user_id}
  161. data_raw = self.connection.raw_get(URL_ADMIN_USER_CONSENTS.format(**params_path))
  162. return raise_error_from_response(data_raw, KeycloakGetError)
  163. def send_update_account(self, user_id, payload, client_id=None, lifespan=None, redirect_uri=None):
  164. """
  165. Send a update account email to the user An email contains a
  166. link the user can click to perform a set of required actions.
  167. :param user_id:
  168. :param payload:
  169. :param client_id:
  170. :param lifespan:
  171. :param redirect_uri:
  172. :return:
  173. """
  174. params_path = {"realm-name": self.realm_name, "id": user_id}
  175. params_query = {"client_id": client_id, "lifespan": lifespan, "redirect_uri": redirect_uri}
  176. data_raw = self.connection.raw_put(URL_ADMIN_SEND_UPDATE_ACCOUNT.format(**params_path),
  177. data=payload, **params_query)
  178. return raise_error_from_response(data_raw, KeycloakGetError)
  179. def send_verify_email(self, user_id, client_id=None, redirect_uri=None):
  180. """
  181. Send a update account email to the user An email contains a
  182. link the user can click to perform a set of required actions.
  183. :param user_id: User id
  184. :param client_id: Client id
  185. :param redirect_uri: Redirect uri
  186. :return:
  187. """
  188. params_path = {"realm-name": self.realm_name, "id": user_id}
  189. params_query = {"client_id": client_id, "redirect_uri": redirect_uri}
  190. data_raw = self.connection.raw_put(URL_ADMIN_SEND_VERIFY_EMAIL.format(**params_path),
  191. data={}, **params_query)
  192. return raise_error_from_response(data_raw, KeycloakGetError)
  193. def get_sessions(self, user_id):
  194. """
  195. Get sessions associated with the user
  196. :param user_id: User id
  197. UserSessionRepresentation
  198. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_usersessionrepresentation
  199. :return: UserSessionRepresentation
  200. """
  201. params_path = {"realm-name": self.realm_name, "id": user_id}
  202. data_raw = self.connection.raw_get(URL_ADMIN_GET_SESSIONS.format(**params_path))
  203. return raise_error_from_response(data_raw, KeycloakGetError)
  204. def get_server_info(self):
  205. """
  206. Get themes, social providers, auth providers, and event listeners available on this server
  207. ServerInfoRepresentation
  208. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_serverinforepresentation
  209. :return: ServerInfoRepresentation
  210. """
  211. data_raw = self.connection.raw_get(URL_ADMIN_SERVER_INFO)
  212. return raise_error_from_response(data_raw, KeycloakGetError)
  213. def get_clients(self):
  214. """
  215. Get clients belonging to the realm Returns a list of clients belonging to the realm
  216. ClientRepresentation
  217. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  218. :return: ClientRepresentation
  219. """
  220. params_path = {"realm-name": self.realm_name}
  221. data_raw = self.connection.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  222. return raise_error_from_response(data_raw, KeycloakGetError)
  223. def get_client_id(self, client_id_name):
  224. """
  225. Get internal keycloak client id from client-id.
  226. This is required for further actions against this client.
  227. :param client_id_name:
  228. clientId in ClientRepresentation
  229. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  230. :return: client_id (uuid as string)
  231. """
  232. params_path = {"realm-name": self.realm_name, "clientId": client_id_name}
  233. data_raw = self.connection.raw_get(URL_ADMIN_CLIENTS.format(**params_path))
  234. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  235. for client in data_content:
  236. client_id = json.dumps(client["clientId"]).strip('"')
  237. if client_id == client_id_name:
  238. return json.dumps(client["id"]).strip('"')
  239. return None
  240. def get_client(self, client_id):
  241. """
  242. Get representation of the client
  243. ClientRepresentation
  244. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  245. :param client_id: id of client (not client-id)
  246. :return: ClientRepresentation
  247. """
  248. params_path = {"realm-name": self.realm_name, "id": client_id}
  249. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT.format(**params_path))
  250. return raise_error_from_response(data_raw, KeycloakGetError)
  251. def create_client(self, name, client_id, redirect_urls, protocol="openid-connect", public_client=True, direct_access_grants=True):
  252. """
  253. Create a client
  254. :param name: name of client, payload (ClientRepresentation)
  255. ClientRepresentation
  256. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  257. """
  258. data={}
  259. data["name"]=name
  260. data["clientId"]=client_id
  261. data["redirectUris"]=redirect_urls
  262. data["protocol"]=protocol
  263. data["publicClient"]=public_client
  264. data["directAccessGrantsEnabled"]=direct_access_grants
  265. params_path = {"realm-name": self.realm_name}
  266. data_raw = self.connection.raw_post(URL_ADMIN_CLIENTS.format(**params_path),
  267. data=json.dumps(data))
  268. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  269. def delete_client(self, client_id):
  270. """
  271. Get representation of the client
  272. ClientRepresentation
  273. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_clientrepresentation
  274. :param client_id: id of client (not client-id)
  275. :return: ClientRepresentation
  276. """
  277. params_path = {"realm-name": self.realm_name, "id": client_id}
  278. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT.format(**params_path))
  279. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  280. def get_client_roles(self, client_id):
  281. """
  282. Get all roles for the client
  283. RoleRepresentation
  284. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  285. :param client_id: id of client (not client-id)
  286. :return: RoleRepresentation
  287. """
  288. params_path = {"realm-name": self.realm_name, "id": client_id}
  289. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  290. return raise_error_from_response(data_raw, KeycloakGetError)
  291. def get_client_role_id(self, client_id, role_name):
  292. """
  293. Get client role id
  294. This is required for further actions with this role.
  295. RoleRepresentation
  296. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  297. :param client_id: id of client (not client-id), role_name: name of role
  298. :return: role_id
  299. """
  300. params_path = {"realm-name": self.realm_name, "id": client_id}
  301. data_raw = self.connection.raw_get(URL_ADMIN_CLIENT_ROLES.format(**params_path))
  302. data_content = raise_error_from_response(data_raw, KeycloakGetError)
  303. for role in data_content:
  304. this_role_name = json.dumps(role["name"]).strip('"')
  305. if this_role_name == role_name:
  306. return json.dumps(role["id"]).strip('"')
  307. return None
  308. def get_roles(self):
  309. """
  310. Get all roles for the realm or client
  311. RoleRepresentation
  312. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  313. :return: RoleRepresentation
  314. """
  315. params_path = {"realm-name": self.realm_name}
  316. data_raw = self.connection.raw_get(URL_ADMIN_REALM_ROLES.format(**params_path))
  317. return raise_error_from_response(data_raw, KeycloakGetError)
  318. def create_client_role(self, client_id, role_name):
  319. """
  320. Create a client role
  321. :param client_id: id of client (not client-id), payload (RoleRepresentation)
  322. RoleRepresentation
  323. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  324. """
  325. data={}
  326. data["name"]=role_name
  327. data["clientRole"]=True
  328. params_path = {"realm-name": self.realm_name, "id": client_id}
  329. data_raw = self.connection.raw_post(URL_ADMIN_CLIENT_ROLES.format(**params_path),
  330. data=json.dumps(data))
  331. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=201)
  332. def delete_client_role(self, client_id, role_name):
  333. """
  334. Create a client role
  335. :param client_id: id of client (not client-id), payload (RoleRepresentation)
  336. RoleRepresentation
  337. http://www.keycloak.org/docs-api/3.3/rest-api/index.html#_rolerepresentation
  338. """
  339. data={}
  340. data["name"]=role_name
  341. data["clientRole"]=True
  342. params_path = {"realm-name": self.realm_name, "id": client_id}
  343. data_raw = self.connection.raw_delete(URL_ADMIN_CLIENT_ROLES.format(**params_path) + "/" + role_name,
  344. data=json.dumps(data))
  345. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
  346. def assign_client_role(self, user_id, client_id, role_id, role_name):
  347. """
  348. Assign a client role to a user
  349. :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)
  350. """
  351. payload=[{}]
  352. payload[0]["id"]=role_id
  353. payload[0]["name"]=role_name
  354. params_path = {"realm-name": self.realm_name, "id": user_id, "client-id": client_id}
  355. data_raw = self.connection.raw_post(URL_ADMIN_USER_CLIENT_ROLES.format(**params_path),
  356. data=json.dumps(payload))
  357. return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)