blob: 748b2e700c16da6b809f0bb96d14787e00909c84 [file] [log] [blame]
Eduardo Sousa819d34c2018-07-31 01:20:02 +01001# -*- coding: utf-8 -*-
2
Eduardo Sousad795f872019-02-05 16:05:53 +00003# Copyright 2018 Whitestack, LLC
4#
5# Licensed under the Apache License, Version 2.0 (the "License"); you may
6# not use this file except in compliance with the License. You may obtain
7# a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14# License for the specific language governing permissions and limitations
15# under the License.
16#
17# For those usages not covered by the Apache License, Version 2.0 please
18# contact: esousa@whitestack.com or glavado@whitestack.com
19##
20
Eduardo Sousa819d34c2018-07-31 01:20:02 +010021"""
22AuthconnKeystone implements implements the connector for
23Openstack Keystone and leverages the RBAC model, to bring
24it for OSM.
25"""
Eduardo Sousa44603902019-06-04 08:10:32 +010026
Eduardo Sousa819d34c2018-07-31 01:20:02 +010027
tierno23acf402019-08-28 13:36:34 +000028__author__ = "Eduardo Sousa <esousa@whitestack.com>, " \
29 "Pedro de la Cruz Ramos <pdelacruzramos@altran.com>"
Eduardo Sousa819d34c2018-07-31 01:20:02 +010030__date__ = "$27-jul-2018 23:59:59$"
31
tierno23acf402019-08-28 13:36:34 +000032from osm_nbi.authconn import Authconn, AuthException, AuthconnOperationException, AuthconnNotFoundException, \
tierno1f029d82019-06-13 22:37:04 +000033 AuthconnConflictException
Eduardo Sousa819d34c2018-07-31 01:20:02 +010034
35import logging
Eduardo Sousa29933fc2018-11-14 06:36:35 +000036import requests
Eduardo Sousa44603902019-06-04 08:10:32 +010037import time
Eduardo Sousa819d34c2018-07-31 01:20:02 +010038from keystoneauth1 import session
39from keystoneauth1.identity import v3
40from keystoneauth1.exceptions.base import ClientException
Eduardo Sousa29933fc2018-11-14 06:36:35 +000041from keystoneauth1.exceptions.http import Conflict
Eduardo Sousa819d34c2018-07-31 01:20:02 +010042from keystoneclient.v3 import client
43from http import HTTPStatus
tierno23acf402019-08-28 13:36:34 +000044from osm_nbi.validation import is_valid_uuid
Eduardo Sousa819d34c2018-07-31 01:20:02 +010045
46
47class AuthconnKeystone(Authconn):
delacruzramoad682a52019-12-10 16:26:34 +010048 def __init__(self, config, db):
49 Authconn.__init__(self, config, db)
Eduardo Sousa819d34c2018-07-31 01:20:02 +010050
51 self.logger = logging.getLogger("nbi.authenticator.keystone")
52
53 self.auth_url = "http://{0}:{1}/v3".format(config.get("auth_url", "keystone"), config.get("auth_port", "5000"))
tierno6486f742020-02-13 16:30:14 +000054 self.user_domain_name_list = config.get("user_domain_name", "default")
55 self.user_domain_name_list = self.user_domain_name_list.split(",")
Eduardo Sousa819d34c2018-07-31 01:20:02 +010056 self.admin_project = config.get("service_project", "service")
57 self.admin_username = config.get("service_username", "nbi")
58 self.admin_password = config.get("service_password", "nbi")
tierno6486f742020-02-13 16:30:14 +000059 self.project_domain_name_list = config.get("project_domain_name", "default")
60 self.project_domain_name_list = self.project_domain_name_list.split(",")
61 if len(self.user_domain_name_list) != len(self.project_domain_name_list):
62 raise ValueError("Invalid configuration parameter fo authenticate. 'project_domain_name' and "
63 "'user_domain_name' must be a comma-separated list with the same size. Revise "
64 "configuration or/and 'OSMNBI_AUTHENTICATION_PROJECT_DOMAIN_NAME', "
65 "'OSMNBI_AUTHENTICATION_USER_DOMAIN_NAME' Variables")
Eduardo Sousa819d34c2018-07-31 01:20:02 +010066
Eduardo Sousa29933fc2018-11-14 06:36:35 +000067 # Waiting for Keystone to be up
68 available = None
69 counter = 300
70 while available is None:
71 time.sleep(1)
72 try:
73 result = requests.get(self.auth_url)
74 available = True if result.status_code == 200 else None
75 except Exception:
76 counter -= 1
77 if counter == 0:
78 raise AuthException("Keystone not available after 300s timeout")
79
tierno6486f742020-02-13 16:30:14 +000080 self.auth = v3.Password(user_domain_name=self.user_domain_name_list[0],
Eduardo Sousa819d34c2018-07-31 01:20:02 +010081 username=self.admin_username,
82 password=self.admin_password,
tierno6486f742020-02-13 16:30:14 +000083 project_domain_name=self.project_domain_name_list[0],
Eduardo Sousa819d34c2018-07-31 01:20:02 +010084 project_name=self.admin_project,
85 auth_url=self.auth_url)
86 self.sess = session.Session(auth=self.auth)
87 self.keystone = client.Client(session=self.sess)
88
tierno6486f742020-02-13 16:30:14 +000089 def authenticate(self, credentials, token_info=None):
Eduardo Sousa819d34c2018-07-31 01:20:02 +010090 """
tierno701018c2019-06-25 11:13:14 +000091 Authenticate a user using username/password or token_info, plus project
tierno6486f742020-02-13 16:30:14 +000092 :param credentials: dictionary that contains:
93 username: name, id or None
94 password: password or None
95 project_id: name, id, or None. If None first found project will be used to get an scope token
96 project_domain_name: (Optional) To use a concrete domain for the project
97 user_domain_name: (Optional) To use a concrete domain for the project
98 other items are allowed and ignored
tierno701018c2019-06-25 11:13:14 +000099 :param token_info: previous token_info to obtain authorization
tierno38dcfeb2019-06-10 16:44:00 +0000100 :return: the scoped token info or raises an exception. The token is a dictionary with:
101 _id: token string id,
102 username: username,
103 project_id: scoped_token project_id,
104 project_name: scoped_token project_name,
105 expires: epoch time when it expires,
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100106
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100107 """
tierno6486f742020-02-13 16:30:14 +0000108 username = None
109 user_id = None
110 project_id = None
111 project_name = None
112 if credentials.get("project_domain_name"):
113 project_domain_name_list = (credentials["project_domain_name"], )
114 else:
115 project_domain_name_list = self.project_domain_name_list
116 if credentials.get("user_domain_name"):
117 user_domain_name_list = (credentials["user_domain_name"], )
118 else:
119 user_domain_name_list = self.user_domain_name_list
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100120
tierno6486f742020-02-13 16:30:14 +0000121 for index, project_domain_name in enumerate(project_domain_name_list):
122 user_domain_name = user_domain_name_list[index]
123 try:
124 if credentials.get("username"):
125 if is_valid_uuid(credentials["username"]):
126 user_id = credentials["username"]
127 else:
128 username = credentials["username"]
129
130 # get an unscoped token firstly
131 unscoped_token = self.keystone.get_raw_token_from_identity_service(
132 auth_url=self.auth_url,
133 user_id=user_id,
134 username=username,
135 password=credentials.get("password"),
136 user_domain_name=user_domain_name,
137 project_domain_name=project_domain_name)
138 elif token_info:
139 unscoped_token = self.keystone.tokens.validate(token=token_info.get("_id"))
tierno38dcfeb2019-06-10 16:44:00 +0000140 else:
tierno6486f742020-02-13 16:30:14 +0000141 raise AuthException("Provide credentials: username/password or Authorization Bearer token",
142 http_code=HTTPStatus.UNAUTHORIZED)
tierno38dcfeb2019-06-10 16:44:00 +0000143
tierno6486f742020-02-13 16:30:14 +0000144 if not credentials.get("project_id"):
145 # get first project for the user
146 project_list = self.keystone.projects.list(user=unscoped_token["user"]["id"])
147 if not project_list:
148 raise AuthException("The user {} has not any project and cannot be used for authentication".
149 format(credentials.get("username")), http_code=HTTPStatus.UNAUTHORIZED)
150 project_id = project_list[0].id
151 else:
152 if is_valid_uuid(credentials["project_id"]):
153 project_id = credentials["project_id"]
154 else:
155 project_name = credentials["project_id"]
156
157 scoped_token = self.keystone.get_raw_token_from_identity_service(
tierno38dcfeb2019-06-10 16:44:00 +0000158 auth_url=self.auth_url,
tierno6486f742020-02-13 16:30:14 +0000159 project_name=project_name,
160 project_id=project_id,
161 user_domain_name=user_domain_name,
162 project_domain_name=project_domain_name,
163 token=unscoped_token["auth_token"])
tierno38dcfeb2019-06-10 16:44:00 +0000164
tierno6486f742020-02-13 16:30:14 +0000165 auth_token = {
166 "_id": scoped_token.auth_token,
167 "id": scoped_token.auth_token,
168 "user_id": scoped_token.user_id,
169 "username": scoped_token.username,
170 "project_id": scoped_token.project_id,
171 "project_name": scoped_token.project_name,
172 "project_domain_name": scoped_token.project_domain_name,
173 "user_domain_name": scoped_token.user_domain_name,
174 "expires": scoped_token.expires.timestamp(),
175 "issued_at": scoped_token.issued.timestamp()
176 }
tierno38dcfeb2019-06-10 16:44:00 +0000177
tierno6486f742020-02-13 16:30:14 +0000178 return auth_token
179 except ClientException as e:
180 if index >= len(user_domain_name_list)-1 or index >= len(project_domain_name_list)-1:
181 # if last try, launch exception
182 # self.logger.exception("Error during user authentication using keystone: {}".format(e))
183 raise AuthException("Error during user authentication using Keystone: {}".format(e),
184 http_code=HTTPStatus.UNAUTHORIZED)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100185
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100186 def validate_token(self, token):
187 """
188 Check if the token is valid.
189
tierno701018c2019-06-25 11:13:14 +0000190 :param token: token id to be validated
tiernoa6bb45d2019-06-14 09:45:39 +0000191 :return: dictionary with information associated with the token:
192 "expires":
193 "_id": token_id,
194 "project_id": project_id,
195 "username": ,
196 "roles": list with dict containing {name, id}
197 If the token is not valid an exception is raised.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100198 """
199 if not token:
200 return
201
202 try:
203 token_info = self.keystone.tokens.validate(token=token)
tiernoa6bb45d2019-06-14 09:45:39 +0000204 ses = {
205 "_id": token_info["auth_token"],
tierno701018c2019-06-25 11:13:14 +0000206 "id": token_info["auth_token"],
tiernoa6bb45d2019-06-14 09:45:39 +0000207 "project_id": token_info["project"]["id"],
208 "project_name": token_info["project"]["name"],
209 "user_id": token_info["user"]["id"],
210 "username": token_info["user"]["name"],
211 "roles": token_info["roles"],
tierno701018c2019-06-25 11:13:14 +0000212 "expires": token_info.expires.timestamp(),
213 "issued_at": token_info.issued.timestamp()
tiernoa6bb45d2019-06-14 09:45:39 +0000214 }
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100215
tiernoa6bb45d2019-06-14 09:45:39 +0000216 return ses
tierno4015b472019-06-10 13:57:29 +0000217 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000218 # self.logger.exception("Error during token validation using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000219 raise AuthException("Error during token validation using Keystone: {}".format(e),
220 http_code=HTTPStatus.UNAUTHORIZED)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100221
222 def revoke_token(self, token):
223 """
224 Invalidate a token.
225
226 :param token: token to be revoked
227 """
228 try:
Eduardo Sousa29933fc2018-11-14 06:36:35 +0000229 self.logger.info("Revoking token: " + token)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100230 self.keystone.tokens.revoke_token(token=token)
231
232 return True
tierno4015b472019-06-10 13:57:29 +0000233 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000234 # self.logger.exception("Error during token revocation using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000235 raise AuthException("Error during token revocation using Keystone: {}".format(e),
236 http_code=HTTPStatus.UNAUTHORIZED)
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100237
delacruzramo01b15d32019-07-02 14:37:47 +0200238 def create_user(self, user_info):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100239 """
240 Create a user.
241
delacruzramo01b15d32019-07-02 14:37:47 +0200242 :param user_info: full user info.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100243 :raises AuthconnOperationException: if user creation failed.
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100244 :return: returns the id of the user in keystone.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100245 """
246 try:
tierno6486f742020-02-13 16:30:14 +0000247 new_user = self.keystone.users.create(
248 user_info["username"], password=user_info["password"],
249 domain=user_info.get("user_domain_name", self.user_domain_name_list[0]),
250 _admin=user_info["_admin"])
delacruzramo01b15d32019-07-02 14:37:47 +0200251 if "project_role_mappings" in user_info.keys():
252 for mapping in user_info["project_role_mappings"]:
253 self.assign_role_to_user(new_user.id, mapping["project"], mapping["role"])
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100254 return {"username": new_user.name, "_id": new_user.id}
tiernocf042d32019-06-13 09:06:40 +0000255 except Conflict as e:
256 # self.logger.exception("Error during user creation using keystone: {}".format(e))
257 raise AuthconnOperationException(e, http_code=HTTPStatus.CONFLICT)
tierno4015b472019-06-10 13:57:29 +0000258 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000259 # self.logger.exception("Error during user creation using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000260 raise AuthconnOperationException("Error during user creation using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100261
delacruzramo01b15d32019-07-02 14:37:47 +0200262 def update_user(self, user_info):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100263 """
tiernocf042d32019-06-13 09:06:40 +0000264 Change the user name and/or password.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100265
delacruzramo01b15d32019-07-02 14:37:47 +0200266 :param user_info: user info modifications
tiernocf042d32019-06-13 09:06:40 +0000267 :raises AuthconnOperationException: if change failed.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100268 """
269 try:
delacruzramo01b15d32019-07-02 14:37:47 +0200270 user = user_info.get("_id") or user_info.get("username")
tiernocf042d32019-06-13 09:06:40 +0000271 if is_valid_uuid(user):
delacruzramo01b15d32019-07-02 14:37:47 +0200272 user_obj_list = [self.keystone.users.get(user)]
tiernocf042d32019-06-13 09:06:40 +0000273 else:
274 user_obj_list = self.keystone.users.list(name=user)
delacruzramo01b15d32019-07-02 14:37:47 +0200275 if not user_obj_list:
276 raise AuthconnNotFoundException("User '{}' not found".format(user))
277 user_obj = user_obj_list[0]
278 user_id = user_obj.id
279 if user_info.get("password") or user_info.get("username") \
280 or user_info.get("add_project_role_mappings") or user_info.get("remove_project_role_mappings"):
delacruzramo15ec7062019-12-26 10:09:04 +0000281 ctime = user_obj._admin.get("created", 0) if hasattr(user_obj, "_admin") else 0
delacruzramo01b15d32019-07-02 14:37:47 +0200282 self.keystone.users.update(user_id, password=user_info.get("password"), name=user_info.get("username"),
delacruzramo15ec7062019-12-26 10:09:04 +0000283 _admin={"created": ctime, "modified": time.time()})
delacruzramo01b15d32019-07-02 14:37:47 +0200284 for mapping in user_info.get("remove_project_role_mappings", []):
285 self.remove_role_from_user(user_id, mapping["project"], mapping["role"])
286 for mapping in user_info.get("add_project_role_mappings", []):
287 self.assign_role_to_user(user_id, mapping["project"], mapping["role"])
tierno4015b472019-06-10 13:57:29 +0000288 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000289 # self.logger.exception("Error during user password/name update using keystone: {}".format(e))
delacruzramo01b15d32019-07-02 14:37:47 +0200290 raise AuthconnOperationException("Error during user update using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100291
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100292 def delete_user(self, user_id):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100293 """
294 Delete user.
295
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100296 :param user_id: user identifier.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100297 :raises AuthconnOperationException: if user deletion failed.
298 """
299 try:
tierno38dcfeb2019-06-10 16:44:00 +0000300 result, detail = self.keystone.users.delete(user_id)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100301 if result.status_code != 204:
tierno38dcfeb2019-06-10 16:44:00 +0000302 raise ClientException("error {} {}".format(result.status_code, detail))
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100303 return True
tierno4015b472019-06-10 13:57:29 +0000304 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000305 # self.logger.exception("Error during user deletion using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000306 raise AuthconnOperationException("Error during user deletion using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100307
tiernocf042d32019-06-13 09:06:40 +0000308 def get_user_list(self, filter_q=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100309 """
310 Get user list.
311
tiernocf042d32019-06-13 09:06:40 +0000312 :param filter_q: dictionary to filter user list by name (username is also admited) and/or _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100313 :return: returns a list of users.
314 """
315 try:
tiernocf042d32019-06-13 09:06:40 +0000316 filter_name = None
317 if filter_q:
318 filter_name = filter_q.get("name") or filter_q.get("username")
319 users = self.keystone.users.list(name=filter_name)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100320 users = [{
321 "username": user.name,
Eduardo Sousa203bad82019-05-23 01:41:18 +0100322 "_id": user.id,
delacruzramo01b15d32019-07-02 14:37:47 +0200323 "id": user.id,
324 "_admin": user.to_dict().get("_admin", {}) # TODO: REVISE
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100325 } for user in users if user.name != self.admin_username]
326
tiernocf042d32019-06-13 09:06:40 +0000327 if filter_q and filter_q.get("_id"):
328 users = [user for user in users if filter_q["_id"] == user["_id"]]
Eduardo Sousa2d5a5152019-05-20 15:41:54 +0100329
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100330 for user in users:
tierno1546f2a2019-08-20 15:38:11 +0000331 user["project_role_mappings"] = []
332 user["projects"] = []
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100333 projects = self.keystone.projects.list(user=user["_id"])
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100334 for project in projects:
tierno1546f2a2019-08-20 15:38:11 +0000335 user["projects"].append(project.name)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100336
tierno1546f2a2019-08-20 15:38:11 +0000337 roles = self.keystone.roles.list(user=user["_id"], project=project.id)
338 for role in roles:
339 prm = {
340 "project": project.id,
341 "project_name": project.name,
342 "role_name": role.name,
343 "role": role.id,
344 }
345 user["project_role_mappings"].append(prm)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100346
347 return users
tierno4015b472019-06-10 13:57:29 +0000348 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000349 # self.logger.exception("Error during user listing using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000350 raise AuthconnOperationException("Error during user listing using Keystone: {}".format(e))
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100351
tierno1f029d82019-06-13 22:37:04 +0000352 def get_role_list(self, filter_q=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100353 """
354 Get role list.
355
tierno1f029d82019-06-13 22:37:04 +0000356 :param filter_q: dictionary to filter role list by _id and/or name.
Eduardo Sousa37de0912019-05-23 02:17:22 +0100357 :return: returns the list of roles.
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100358 """
359 try:
tierno1f029d82019-06-13 22:37:04 +0000360 filter_name = None
361 if filter_q:
362 filter_name = filter_q.get("name")
363 roles_list = self.keystone.roles.list(name=filter_name)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100364
365 roles = [{
366 "name": role.name,
delacruzramo01b15d32019-07-02 14:37:47 +0200367 "_id": role.id,
368 "_admin": role.to_dict().get("_admin", {}),
369 "permissions": role.to_dict().get("permissions", {})
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100370 } for role in roles_list if role.name != "service"]
371
tierno1f029d82019-06-13 22:37:04 +0000372 if filter_q and filter_q.get("_id"):
373 roles = [role for role in roles if filter_q["_id"] == role["_id"]]
374
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100375 return roles
tierno4015b472019-06-10 13:57:29 +0000376 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000377 # self.logger.exception("Error during user role listing using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000378 raise AuthException("Error during user role listing using Keystone: {}".format(e),
379 http_code=HTTPStatus.UNAUTHORIZED)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100380
delacruzramo01b15d32019-07-02 14:37:47 +0200381 def create_role(self, role_info):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100382 """
383 Create a role.
384
delacruzramo01b15d32019-07-02 14:37:47 +0200385 :param role_info: full role info.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100386 :raises AuthconnOperationException: if role creation failed.
387 """
388 try:
delacruzramo01b15d32019-07-02 14:37:47 +0200389 result = self.keystone.roles.create(role_info["name"], permissions=role_info.get("permissions"),
390 _admin=role_info.get("_admin"))
tierno1f029d82019-06-13 22:37:04 +0000391 return result.id
Eduardo Sousa29933fc2018-11-14 06:36:35 +0000392 except Conflict as ex:
tierno1f029d82019-06-13 22:37:04 +0000393 raise AuthconnConflictException(str(ex))
tierno4015b472019-06-10 13:57:29 +0000394 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000395 # self.logger.exception("Error during role creation using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000396 raise AuthconnOperationException("Error during role creation using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100397
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100398 def delete_role(self, role_id):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100399 """
400 Delete a role.
401
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100402 :param role_id: role identifier.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100403 :raises AuthconnOperationException: if role deletion failed.
404 """
405 try:
tierno1f029d82019-06-13 22:37:04 +0000406 result, detail = self.keystone.roles.delete(role_id)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100407
408 if result.status_code != 204:
tierno38dcfeb2019-06-10 16:44:00 +0000409 raise ClientException("error {} {}".format(result.status_code, detail))
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100410
411 return True
tierno4015b472019-06-10 13:57:29 +0000412 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000413 # self.logger.exception("Error during role deletion using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000414 raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100415
delacruzramo01b15d32019-07-02 14:37:47 +0200416 def update_role(self, role_info):
tierno1f029d82019-06-13 22:37:04 +0000417 """
418 Change the name of a role
delacruzramo01b15d32019-07-02 14:37:47 +0200419 :param role_info: full role info
tierno1f029d82019-06-13 22:37:04 +0000420 :return: None
421 """
422 try:
delacruzramo01b15d32019-07-02 14:37:47 +0200423 rid = role_info["_id"]
424 if not is_valid_uuid(rid): # Is this required?
425 role_obj_list = self.keystone.roles.list(name=rid)
tierno1f029d82019-06-13 22:37:04 +0000426 if not role_obj_list:
delacruzramo01b15d32019-07-02 14:37:47 +0200427 raise AuthconnNotFoundException("Role '{}' not found".format(rid))
428 rid = role_obj_list[0].id
429 self.keystone.roles.update(rid, name=role_info["name"], permissions=role_info.get("permissions"),
430 _admin=role_info.get("_admin"))
tierno1f029d82019-06-13 22:37:04 +0000431 except ClientException as e:
432 # self.logger.exception("Error during role update using keystone: {}".format(e))
433 raise AuthconnOperationException("Error during role updating using Keystone: {}".format(e))
434
tierno38dcfeb2019-06-10 16:44:00 +0000435 def get_project_list(self, filter_q=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100436 """
437 Get all the projects.
438
Eduardo Sousafa54cd92019-05-20 15:58:41 +0100439 :param filter_q: dictionary to filter project list.
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100440 :return: list of projects
441 """
442 try:
tierno38dcfeb2019-06-10 16:44:00 +0000443 filter_name = None
444 if filter_q:
445 filter_name = filter_q.get("name")
446 projects = self.keystone.projects.list(name=filter_name)
447
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100448 projects = [{
449 "name": project.name,
delacruzramo01b15d32019-07-02 14:37:47 +0200450 "_id": project.id,
delacruzramo32bab472019-09-13 12:24:22 +0200451 "_admin": project.to_dict().get("_admin", {}), # TODO: REVISE
452 "quotas": project.to_dict().get("quotas", {}), # TODO: REVISE
tierno38dcfeb2019-06-10 16:44:00 +0000453 } for project in projects]
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100454
tierno38dcfeb2019-06-10 16:44:00 +0000455 if filter_q and filter_q.get("_id"):
Eduardo Sousafa54cd92019-05-20 15:58:41 +0100456 projects = [project for project in projects
tierno38dcfeb2019-06-10 16:44:00 +0000457 if filter_q["_id"] == project["_id"]]
Eduardo Sousafa54cd92019-05-20 15:58:41 +0100458
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100459 return projects
tierno4015b472019-06-10 13:57:29 +0000460 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000461 # self.logger.exception("Error during user project listing using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000462 raise AuthException("Error during user project listing using Keystone: {}".format(e),
463 http_code=HTTPStatus.UNAUTHORIZED)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100464
delacruzramo01b15d32019-07-02 14:37:47 +0200465 def create_project(self, project_info):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100466 """
467 Create a project.
468
delacruzramo01b15d32019-07-02 14:37:47 +0200469 :param project_info: full project info.
tierno4015b472019-06-10 13:57:29 +0000470 :return: the internal id of the created project
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100471 :raises AuthconnOperationException: if project creation failed.
472 """
473 try:
tierno6486f742020-02-13 16:30:14 +0000474 result = self.keystone.projects.create(
475 project_info["name"],
476 project_info.get("project_domain_name", self.project_domain_name_list[0]),
477 _admin=project_info["_admin"],
478 quotas=project_info.get("quotas", {})
479 )
tierno4015b472019-06-10 13:57:29 +0000480 return result.id
481 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000482 # self.logger.exception("Error during project creation using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000483 raise AuthconnOperationException("Error during project creation using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100484
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100485 def delete_project(self, project_id):
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100486 """
487 Delete a project.
488
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100489 :param project_id: project identifier.
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100490 :raises AuthconnOperationException: if project deletion failed.
491 """
492 try:
tierno38dcfeb2019-06-10 16:44:00 +0000493 # projects = self.keystone.projects.list()
494 # project_obj = [project for project in projects if project.id == project_id][0]
495 # result, _ = self.keystone.projects.delete(project_obj)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100496
tierno38dcfeb2019-06-10 16:44:00 +0000497 result, detail = self.keystone.projects.delete(project_id)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100498 if result.status_code != 204:
tierno38dcfeb2019-06-10 16:44:00 +0000499 raise ClientException("error {} {}".format(result.status_code, detail))
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100500
501 return True
tierno4015b472019-06-10 13:57:29 +0000502 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000503 # self.logger.exception("Error during project deletion using keystone: {}".format(e))
tierno4015b472019-06-10 13:57:29 +0000504 raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e))
505
delacruzramo01b15d32019-07-02 14:37:47 +0200506 def update_project(self, project_id, project_info):
tierno4015b472019-06-10 13:57:29 +0000507 """
508 Change the name of a project
509 :param project_id: project to be changed
delacruzramo01b15d32019-07-02 14:37:47 +0200510 :param project_info: full project info
tierno4015b472019-06-10 13:57:29 +0000511 :return: None
512 """
513 try:
delacruzramo32bab472019-09-13 12:24:22 +0200514 self.keystone.projects.update(project_id, name=project_info["name"],
515 _admin=project_info["_admin"],
516 quotas=project_info.get("quotas", {})
517 )
tierno4015b472019-06-10 13:57:29 +0000518 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000519 # self.logger.exception("Error during project update using keystone: {}".format(e))
delacruzramo01b15d32019-07-02 14:37:47 +0200520 raise AuthconnOperationException("Error during project update using Keystone: {}".format(e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100521
522 def assign_role_to_user(self, user, project, role):
523 """
524 Assigning a role to a user in a project.
525
526 :param user: username.
527 :param project: project name.
528 :param role: role name.
529 :raises AuthconnOperationException: if role assignment failed.
530 """
531 try:
Eduardo Sousa44603902019-06-04 08:10:32 +0100532 if is_valid_uuid(user):
533 user_obj = self.keystone.users.get(user)
534 else:
tiernocf042d32019-06-13 09:06:40 +0000535 user_obj_list = self.keystone.users.list(name=user)
536 if not user_obj_list:
537 raise AuthconnNotFoundException("User '{}' not found".format(user))
538 user_obj = user_obj_list[0]
Eduardo Sousa44603902019-06-04 08:10:32 +0100539
540 if is_valid_uuid(project):
541 project_obj = self.keystone.projects.get(project)
542 else:
tiernocf042d32019-06-13 09:06:40 +0000543 project_obj_list = self.keystone.projects.list(name=project)
544 if not project_obj_list:
545 raise AuthconnNotFoundException("Project '{}' not found".format(project))
546 project_obj = project_obj_list[0]
Eduardo Sousa44603902019-06-04 08:10:32 +0100547
548 if is_valid_uuid(role):
549 role_obj = self.keystone.roles.get(role)
550 else:
tiernocf042d32019-06-13 09:06:40 +0000551 role_obj_list = self.keystone.roles.list(name=role)
552 if not role_obj_list:
553 raise AuthconnNotFoundException("Role '{}' not found".format(role))
554 role_obj = role_obj_list[0]
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100555
Eduardo Sousa29933fc2018-11-14 06:36:35 +0000556 self.keystone.roles.grant(role_obj, user=user_obj, project=project_obj)
tierno4015b472019-06-10 13:57:29 +0000557 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000558 # self.logger.exception("Error during user role assignment using keystone: {}".format(e))
tiernocf042d32019-06-13 09:06:40 +0000559 raise AuthconnOperationException("Error during role '{}' assignment to user '{}' and project '{}' using "
560 "Keystone: {}".format(role, user, project, e))
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100561
562 def remove_role_from_user(self, user, project, role):
563 """
564 Remove a role from a user in a project.
565
566 :param user: username.
tiernocf042d32019-06-13 09:06:40 +0000567 :param project: project name or id.
568 :param role: role name or id.
569
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100570 :raises AuthconnOperationException: if role assignment revocation failed.
571 """
572 try:
tiernocf042d32019-06-13 09:06:40 +0000573 if is_valid_uuid(user):
574 user_obj = self.keystone.users.get(user)
575 else:
576 user_obj_list = self.keystone.users.list(name=user)
577 if not user_obj_list:
578 raise AuthconnNotFoundException("User '{}' not found".format(user))
579 user_obj = user_obj_list[0]
580
581 if is_valid_uuid(project):
582 project_obj = self.keystone.projects.get(project)
583 else:
584 project_obj_list = self.keystone.projects.list(name=project)
585 if not project_obj_list:
586 raise AuthconnNotFoundException("Project '{}' not found".format(project))
587 project_obj = project_obj_list[0]
588
589 if is_valid_uuid(role):
590 role_obj = self.keystone.roles.get(role)
591 else:
592 role_obj_list = self.keystone.roles.list(name=role)
593 if not role_obj_list:
594 raise AuthconnNotFoundException("Role '{}' not found".format(role))
595 role_obj = role_obj_list[0]
Eduardo Sousa819d34c2018-07-31 01:20:02 +0100596
Eduardo Sousa29933fc2018-11-14 06:36:35 +0000597 self.keystone.roles.revoke(role_obj, user=user_obj, project=project_obj)
tierno4015b472019-06-10 13:57:29 +0000598 except ClientException as e:
tierno701018c2019-06-25 11:13:14 +0000599 # self.logger.exception("Error during user role revocation using keystone: {}".format(e))
tiernocf042d32019-06-13 09:06:40 +0000600 raise AuthconnOperationException("Error during role '{}' revocation to user '{}' and project '{}' using "
601 "Keystone: {}".format(role, user, project, e))