1 # -*- coding: utf-8 -*-
3 # Copyright 2018 Whitestack, LLC
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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
17 # For those usages not covered by the Apache License, Version 2.0 please
18 # contact: esousa@whitestack.com or glavado@whitestack.com
22 AuthconnKeystone implements implements the connector for
23 Openstack Keystone and leverages the RBAC model, to bring
28 __author__
= "Eduardo Sousa <esousa@whitestack.com>"
29 __date__
= "$27-jul-2018 23:59:59$"
31 from authconn
import Authconn
, AuthException
, AuthconnOperationException
36 from keystoneauth1
import session
37 from keystoneauth1
.identity
import v3
38 from keystoneauth1
.exceptions
.base
import ClientException
39 from keystoneauth1
.exceptions
.http
import Conflict
40 from keystoneclient
.v3
import client
41 from http
import HTTPStatus
42 from validation
import is_valid_uuid
45 class AuthconnKeystone(Authconn
):
46 def __init__(self
, config
):
47 Authconn
.__init
__(self
, config
)
49 self
.logger
= logging
.getLogger("nbi.authenticator.keystone")
51 self
.auth_url
= "http://{0}:{1}/v3".format(config
.get("auth_url", "keystone"), config
.get("auth_port", "5000"))
52 self
.user_domain_name
= config
.get("user_domain_name", "default")
53 self
.admin_project
= config
.get("service_project", "service")
54 self
.admin_username
= config
.get("service_username", "nbi")
55 self
.admin_password
= config
.get("service_password", "nbi")
56 self
.project_domain_name
= config
.get("project_domain_name", "default")
58 # Waiting for Keystone to be up
61 while available
is None:
64 result
= requests
.get(self
.auth_url
)
65 available
= True if result
.status_code
== 200 else None
69 raise AuthException("Keystone not available after 300s timeout")
71 self
.auth
= v3
.Password(user_domain_name
=self
.user_domain_name
,
72 username
=self
.admin_username
,
73 password
=self
.admin_password
,
74 project_domain_name
=self
.project_domain_name
,
75 project_name
=self
.admin_project
,
76 auth_url
=self
.auth_url
)
77 self
.sess
= session
.Session(auth
=self
.auth
)
78 self
.keystone
= client
.Client(session
=self
.sess
)
80 def authenticate_with_user_password(self
, user
, password
):
82 Authenticate a user using username and password.
85 :param password: password
86 :return: an unscoped token that grants access to project list
89 user_id
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0].id
90 project_names
= [project
.name
for project
in self
.keystone
.projects
.list(user
=user_id
)]
92 token
= self
.keystone
.get_raw_token_from_identity_service(
93 auth_url
=self
.auth_url
,
96 user_domain_name
=self
.user_domain_name
,
97 project_domain_name
=self
.project_domain_name
)
99 return token
["auth_token"], project_names
100 except ClientException
as e
:
101 self
.logger
.exception("Error during user authentication using keystone. Method: basic: {}".format(e
))
102 raise AuthException("Error during user authentication using Keystone: {}".format(e
),
103 http_code
=HTTPStatus
.UNAUTHORIZED
)
105 def authenticate_with_token(self
, token
, project
=None):
107 Authenticate a user using a token. Can be used to revalidate the token
108 or to get a scoped token.
110 :param token: a valid token.
111 :param project: (optional) project for a scoped token.
112 :return: return a revalidated token, scoped if a project was passed or
113 the previous token was already scoped.
116 token_info
= self
.keystone
.tokens
.validate(token
=token
)
117 projects
= self
.keystone
.projects
.list(user
=token_info
["user"]["id"])
118 project_names
= [project
.name
for project
in projects
]
120 new_token
= self
.keystone
.get_raw_token_from_identity_service(
121 auth_url
=self
.auth_url
,
123 project_name
=project
,
124 user_domain_name
=self
.user_domain_name
,
125 project_domain_name
=self
.project_domain_name
)
127 return new_token
["auth_token"], project_names
128 except ClientException
as e
:
129 self
.logger
.exception("Error during user authentication using keystone. Method: bearer: {}".format(e
))
130 raise AuthException("Error during user authentication using Keystone: {}".format(e
),
131 http_code
=HTTPStatus
.UNAUTHORIZED
)
133 def validate_token(self
, token
):
135 Check if the token is valid.
137 :param token: token to validate
138 :return: dictionary with information associated with the token. If the
139 token is not valid, returns None.
145 token_info
= self
.keystone
.tokens
.validate(token
=token
)
148 except ClientException
as e
:
149 self
.logger
.exception("Error during token validation using keystone: {}".format(e
))
150 raise AuthException("Error during token validation using Keystone: {}".format(e
),
151 http_code
=HTTPStatus
.UNAUTHORIZED
)
153 def revoke_token(self
, token
):
157 :param token: token to be revoked
160 self
.logger
.info("Revoking token: " + token
)
161 self
.keystone
.tokens
.revoke_token(token
=token
)
164 except ClientException
as e
:
165 self
.logger
.exception("Error during token revocation using keystone: {}".format(e
))
166 raise AuthException("Error during token revocation using Keystone: {}".format(e
),
167 http_code
=HTTPStatus
.UNAUTHORIZED
)
169 def get_user_project_list(self
, token
):
171 Get all the projects associated with a user.
173 :param token: valid token
174 :return: list of projects
177 token_info
= self
.keystone
.tokens
.validate(token
=token
)
178 projects
= self
.keystone
.projects
.list(user
=token_info
["user"]["id"])
179 project_names
= [project
.name
for project
in projects
]
182 except ClientException
as e
:
183 self
.logger
.exception("Error during user project listing using keystone: {}".format(e
))
184 raise AuthException("Error during user project listing using Keystone: {}".format(e
),
185 http_code
=HTTPStatus
.UNAUTHORIZED
)
187 def get_user_role_list(self
, token
):
189 Get role list for a scoped project.
191 :param token: scoped token.
192 :return: returns the list of roles for the user in that project. If
193 the token is unscoped it returns None.
196 token_info
= self
.keystone
.tokens
.validate(token
=token
)
197 roles_info
= self
.keystone
.roles
.list(user
=token_info
["user"]["id"], project
=token_info
["project"]["id"])
199 roles
= [role
.name
for role
in roles_info
]
202 except ClientException
as e
:
203 self
.logger
.exception("Error during user role listing using keystone: {}".format(e
))
204 raise AuthException("Error during user role listing using Keystone: {}".format(e
),
205 http_code
=HTTPStatus
.UNAUTHORIZED
)
207 def create_user(self
, user
, password
):
211 :param user: username.
212 :param password: password.
213 :raises AuthconnOperationException: if user creation failed.
214 :return: returns the id of the user in keystone.
217 new_user
= self
.keystone
.users
.create(user
, password
=password
, domain
=self
.user_domain_name
)
218 return {"username": new_user
.name
, "_id": new_user
.id}
219 except ClientException
as e
:
220 self
.logger
.exception("Error during user creation using keystone: {}".format(e
))
221 raise AuthconnOperationException("Error during user creation using Keystone: {}".format(e
))
223 def change_password(self
, user
, new_password
):
225 Change the user password.
227 :param user: username.
228 :param new_password: new password.
229 :raises AuthconnOperationException: if user password change failed.
232 user_obj
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0]
233 self
.keystone
.users
.update(user_obj
, password
=new_password
)
234 except ClientException
as e
:
235 self
.logger
.exception("Error during user password update using keystone: {}".format(e
))
236 raise AuthconnOperationException("Error during user password update using Keystone: {}".format(e
))
238 def delete_user(self
, user_id
):
242 :param user_id: user identifier.
243 :raises AuthconnOperationException: if user deletion failed.
246 users
= self
.keystone
.users
.list()
247 user_obj
= [user
for user
in users
if user
.id == user_id
][0]
248 result
, _
= self
.keystone
.users
.delete(user_obj
)
250 if result
.status_code
!= 204:
251 raise ClientException("User was not deleted")
254 except ClientException
as e
:
255 self
.logger
.exception("Error during user deletion using keystone: {}".format(e
))
256 raise AuthconnOperationException("Error during user deletion using Keystone: {}".format(e
))
258 def get_user_list(self
, filter_q
={}):
262 :param filter_q: dictionary to filter user list.
263 :return: returns a list of users.
266 users
= self
.keystone
.users
.list()
268 "username": user
.name
,
271 } for user
in users
if user
.name
!= self
.admin_username
]
273 allowed_fields
= ["_id", "id", "username"]
274 for key
in filter_q
.keys():
275 if key
not in allowed_fields
:
278 users
= [user
for user
in users
279 if filter_q
[key
] == user
[key
]]
282 projects
= self
.keystone
.projects
.list(user
=user
["_id"])
284 "name": project
.name
,
287 } for project
in projects
]
289 for project
in projects
:
290 roles
= self
.keystone
.roles
.list(user
=user
["_id"], project
=project
["_id"])
296 project
["roles"] = roles
298 user
["projects"] = projects
301 except ClientException
as e
:
302 self
.logger
.exception("Error during user listing using keystone: {}".format(e
))
303 raise AuthconnOperationException("Error during user listing using Keystone: {}".format(e
))
305 def get_role_list(self
):
309 :return: returns the list of roles.
312 roles_list
= self
.keystone
.roles
.list()
317 } for role
in roles_list
if role
.name
!= "service"]
320 except ClientException
as e
:
321 self
.logger
.exception("Error during user role listing using keystone: {}".format(e
))
322 raise AuthException("Error during user role listing using Keystone: {}".format(e
),
323 http_code
=HTTPStatus
.UNAUTHORIZED
)
325 def create_role(self
, role
):
329 :param role: role name.
330 :raises AuthconnOperationException: if role creation failed.
333 result
= self
.keystone
.roles
.create(role
)
334 return {"name": result
.name
, "_id": result
.id}
335 except Conflict
as ex
:
336 self
.logger
.info("Duplicate entry: %s", str(ex
))
337 except ClientException
as e
:
338 self
.logger
.exception("Error during role creation using keystone: {}".format(e
))
339 raise AuthconnOperationException("Error during role creation using Keystone: {}".format(e
))
341 def delete_role(self
, role_id
):
345 :param role_id: role identifier.
346 :raises AuthconnOperationException: if role deletion failed.
349 roles
= self
.keystone
.roles
.list()
350 role_obj
= [role
for role
in roles
if role
.id == role_id
][0]
351 result
, _
= self
.keystone
.roles
.delete(role_obj
)
353 if result
.status_code
!= 204:
354 raise ClientException("Role was not deleted")
357 except ClientException
as e
:
358 self
.logger
.exception("Error during role deletion using keystone: {}".format(e
))
359 raise AuthconnOperationException("Error during role deletion using Keystone: {}".format(e
))
361 def get_project_list(self
, filter_q
={}):
363 Get all the projects.
365 :param filter_q: dictionary to filter project list.
366 :return: list of projects
369 projects
= self
.keystone
.projects
.list()
371 "name": project
.name
,
373 } for project
in projects
if project
.name
!= self
.admin_project
]
375 allowed_fields
= ["_id", "name"]
376 for key
in filter_q
.keys():
377 if key
not in allowed_fields
:
380 projects
= [project
for project
in projects
381 if filter_q
[key
] == project
[key
]]
384 except ClientException
as e
:
385 self
.logger
.exception("Error during user project listing using keystone: {}".format(e
))
386 raise AuthException("Error during user project listing using Keystone: {}".format(e
),
387 http_code
=HTTPStatus
.UNAUTHORIZED
)
389 def create_project(self
, project
):
393 :param project: project name.
394 :return: the internal id of the created project
395 :raises AuthconnOperationException: if project creation failed.
398 result
= self
.keystone
.projects
.create(project
, self
.project_domain_name
)
400 except ClientException
as e
:
401 self
.logger
.exception("Error during project creation using keystone: {}".format(e
))
402 raise AuthconnOperationException("Error during project creation using Keystone: {}".format(e
))
404 def delete_project(self
, project_id
):
408 :param project_id: project identifier.
409 :raises AuthconnOperationException: if project deletion failed.
412 projects
= self
.keystone
.projects
.list()
413 project_obj
= [project
for project
in projects
if project
.id == project_id
][0]
414 result
, _
= self
.keystone
.projects
.delete(project_obj
)
416 if result
.status_code
!= 204:
417 raise ClientException("Project was not deleted")
420 except ClientException
as e
:
421 self
.logger
.exception("Error during project deletion using keystone: {}".format(e
))
422 raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e
))
424 def update_project(self
, project_id
, new_name
):
426 Change the name of a project
427 :param project_id: project to be changed
428 :param new_name: new name
432 self
.keystone
.projects
.update(project_id
, name
=new_name
)
433 except ClientException
as e
:
434 self
.logger
.exception("Error during project update using keystone: {}".format(e
))
435 raise AuthconnOperationException("Error during project deletion using Keystone: {}".format(e
))
437 def assign_role_to_user(self
, user
, project
, role
):
439 Assigning a role to a user in a project.
441 :param user: username.
442 :param project: project name.
443 :param role: role name.
444 :raises AuthconnOperationException: if role assignment failed.
447 if is_valid_uuid(user
):
448 user_obj
= self
.keystone
.users
.get(user
)
450 user_obj
= self
.keystone
.users
.list(name
=user
)[0]
452 if is_valid_uuid(project
):
453 project_obj
= self
.keystone
.projects
.get(project
)
455 project_obj
= self
.keystone
.projects
.list(name
=project
)[0]
457 if is_valid_uuid(role
):
458 role_obj
= self
.keystone
.roles
.get(role
)
460 role_obj
= self
.keystone
.roles
.list(name
=role
)[0]
462 self
.keystone
.roles
.grant(role_obj
, user
=user_obj
, project
=project_obj
)
463 except ClientException
as e
:
464 self
.logger
.exception("Error during user role assignment using keystone: {}".format(e
))
465 raise AuthconnOperationException("Error during user role assignment using Keystone: {}".format(e
))
467 def remove_role_from_user(self
, user
, project
, role
):
469 Remove a role from a user in a project.
471 :param user: username.
472 :param project: project name.
473 :param role: role name.
474 :raises AuthconnOperationException: if role assignment revocation failed.
477 user_obj
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0]
478 project_obj
= list(filter(lambda x
: x
.name
== project
, self
.keystone
.projects
.list()))[0]
479 role_obj
= list(filter(lambda x
: x
.name
== role
, self
.keystone
.roles
.list()))[0]
481 self
.keystone
.roles
.revoke(role_obj
, user
=user_obj
, project
=project_obj
)
482 except ClientException
as e
:
483 self
.logger
.exception("Error during user role revocation using keystone: {}".format(e
))
484 raise AuthconnOperationException("Error during user role revocation using Keystone: {}".format(e
))