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.

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