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
35 from keystoneauth1
import session
36 from keystoneauth1
.identity
import v3
37 from keystoneauth1
.exceptions
.base
import ClientException
38 from keystoneauth1
.exceptions
.http
import Conflict
39 from keystoneclient
.v3
import client
40 from http
import HTTPStatus
43 class AuthconnKeystone(Authconn
):
44 def __init__(self
, config
):
45 Authconn
.__init
__(self
, config
)
47 self
.logger
= logging
.getLogger("nbi.authenticator.keystone")
49 self
.auth_url
= "http://{0}:{1}/v3".format(config
.get("auth_url", "keystone"), config
.get("auth_port", "5000"))
50 self
.user_domain_name
= config
.get("user_domain_name", "default")
51 self
.admin_project
= config
.get("service_project", "service")
52 self
.admin_username
= config
.get("service_username", "nbi")
53 self
.admin_password
= config
.get("service_password", "nbi")
54 self
.project_domain_name
= config
.get("project_domain_name", "default")
56 # Waiting for Keystone to be up
59 while available
is None:
62 result
= requests
.get(self
.auth_url
)
63 available
= True if result
.status_code
== 200 else None
67 raise AuthException("Keystone not available after 300s timeout")
69 self
.auth
= v3
.Password(user_domain_name
=self
.user_domain_name
,
70 username
=self
.admin_username
,
71 password
=self
.admin_password
,
72 project_domain_name
=self
.project_domain_name
,
73 project_name
=self
.admin_project
,
74 auth_url
=self
.auth_url
)
75 self
.sess
= session
.Session(auth
=self
.auth
)
76 self
.keystone
= client
.Client(session
=self
.sess
)
78 def authenticate_with_user_password(self
, user
, password
):
80 Authenticate a user using username and password.
83 :param password: password
84 :return: an unscoped token that grants access to project list
87 user_id
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0].id
88 project_names
= [project
.name
for project
in self
.keystone
.projects
.list(user
=user_id
)]
90 token
= self
.keystone
.get_raw_token_from_identity_service(
91 auth_url
=self
.auth_url
,
94 user_domain_name
=self
.user_domain_name
,
95 project_domain_name
=self
.project_domain_name
)
97 return token
["auth_token"], project_names
98 except ClientException
:
99 self
.logger
.exception("Error during user authentication using keystone. Method: basic")
100 raise AuthException("Error during user authentication using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
102 def authenticate_with_token(self
, token
, project
=None):
104 Authenticate a user using a token. Can be used to revalidate the token
105 or to get a scoped token.
107 :param token: a valid token.
108 :param project: (optional) project for a scoped token.
109 :return: return a revalidated token, scoped if a project was passed or
110 the previous token was already scoped.
113 token_info
= self
.keystone
.tokens
.validate(token
=token
)
114 projects
= self
.keystone
.projects
.list(user
=token_info
["user"]["id"])
115 project_names
= [project
.name
for project
in projects
]
117 new_token
= self
.keystone
.get_raw_token_from_identity_service(
118 auth_url
=self
.auth_url
,
120 project_name
=project
,
121 user_domain_name
=self
.user_domain_name
,
122 project_domain_name
=self
.project_domain_name
)
124 return new_token
["auth_token"], project_names
125 except ClientException
:
126 self
.logger
.exception("Error during user authentication using keystone. Method: bearer")
127 raise AuthException("Error during user authentication using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
129 def validate_token(self
, token
):
131 Check if the token is valid.
133 :param token: token to validate
134 :return: dictionary with information associated with the token. If the
135 token is not valid, returns None.
141 token_info
= self
.keystone
.tokens
.validate(token
=token
)
144 except ClientException
:
145 self
.logger
.exception("Error during token validation using keystone")
146 raise AuthException("Error during token validation using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
148 def revoke_token(self
, token
):
152 :param token: token to be revoked
155 self
.logger
.info("Revoking token: " + token
)
156 self
.keystone
.tokens
.revoke_token(token
=token
)
159 except ClientException
:
160 self
.logger
.exception("Error during token revocation using keystone")
161 raise AuthException("Error during token revocation using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
163 def get_user_project_list(self
, token
):
165 Get all the projects associated with a user.
167 :param token: valid token
168 :return: list of projects
171 token_info
= self
.keystone
.tokens
.validate(token
=token
)
172 projects
= self
.keystone
.projects
.list(user
=token_info
["user"]["id"])
173 project_names
= [project
.name
for project
in projects
]
176 except ClientException
:
177 self
.logger
.exception("Error during user project listing using keystone")
178 raise AuthException("Error during user project listing using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
180 def get_user_role_list(self
, token
):
182 Get role list for a scoped project.
184 :param token: scoped token.
185 :return: returns the list of roles for the user in that project. If
186 the token is unscoped it returns None.
189 token_info
= self
.keystone
.tokens
.validate(token
=token
)
190 roles_info
= self
.keystone
.roles
.list(user
=token_info
["user"]["id"], project
=token_info
["project"]["id"])
192 roles
= [role
.name
for role
in roles_info
]
195 except ClientException
:
196 self
.logger
.exception("Error during user role listing using keystone")
197 raise AuthException("Error during user role listing using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
199 def create_user(self
, user
, password
):
203 :param user: username.
204 :param password: password.
205 :raises AuthconnOperationException: if user creation failed.
206 :return: returns the id of the user in keystone.
209 new_user
= self
.keystone
.users
.create(user
, password
=password
, domain
=self
.user_domain_name
)
210 return {"username": new_user
.name
, "_id": new_user
.id}
211 except ClientException
:
212 self
.logger
.exception("Error during user creation using keystone")
213 raise AuthconnOperationException("Error during user creation using Keystone")
215 def change_password(self
, user
, new_password
):
217 Change the user password.
219 :param user: username.
220 :param new_password: new password.
221 :raises AuthconnOperationException: if user password change failed.
224 user_obj
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0]
225 self
.keystone
.users
.update(user_obj
, password
=new_password
)
226 except ClientException
:
227 self
.logger
.exception("Error during user password update using keystone")
228 raise AuthconnOperationException("Error during user password update using Keystone")
230 def delete_user(self
, user_id
):
234 :param user_id: user identifier.
235 :raises AuthconnOperationException: if user deletion failed.
238 users
= self
.keystone
.users
.list()
239 user_obj
= [user
for user
in users
if user
.id == user_id
][0]
240 result
, _
= self
.keystone
.users
.delete(user_obj
)
242 if result
.status_code
!= 204:
243 raise ClientException("User was not deleted")
246 except ClientException
:
247 self
.logger
.exception("Error during user deletion using keystone")
248 raise AuthconnOperationException("Error during user deletion using Keystone")
250 def get_user_list(self
):
254 :return: returns a list of users.
257 users
= self
.keystone
.users
.list()
259 "username": user
.name
,
261 } for user
in users
if user
.name
!= self
.admin_username
]
264 projects
= self
.keystone
.projects
.list(user
=user
["_id"])
266 "name": project
.name
,
268 } for project
in projects
]
270 for project
in projects
:
271 roles
= self
.keystone
.roles
.list(user
=user
["_id"], project
=project
["_id"])
276 project
["roles"] = roles
278 user
["projects"] = projects
281 except ClientException
:
282 self
.logger
.exception("Error during user listing using keystone")
283 raise AuthconnOperationException("Error during user listing using Keystone")
285 def get_role_list(self
):
289 :return: returns the list of roles for the user in that project. If
290 the token is unscoped it returns None.
293 roles_list
= self
.keystone
.roles
.list()
298 } for role
in roles_list
if role
.name
!= "service"]
301 except ClientException
:
302 self
.logger
.exception("Error during user role listing using keystone")
303 raise AuthException("Error during user role listing using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
305 def create_role(self
, role
):
309 :param role: role name.
310 :raises AuthconnOperationException: if role creation failed.
313 result
= self
.keystone
.roles
.create(role
)
314 return {"name": result
.name
, "_id": result
.id}
315 except Conflict
as ex
:
316 self
.logger
.info("Duplicate entry: %s", str(ex
))
317 except ClientException
:
318 self
.logger
.exception("Error during role creation using keystone")
319 raise AuthconnOperationException("Error during role creation using Keystone")
321 def delete_role(self
, role_id
):
325 :param role_id: role identifier.
326 :raises AuthconnOperationException: if role deletion failed.
329 roles
= self
.keystone
.roles
.list()
330 role_obj
= [role
for role
in roles
if role
.id == role_id
][0]
331 result
, _
= self
.keystone
.roles
.delete(role_obj
)
333 if result
.status_code
!= 204:
334 raise ClientException("Role was not deleted")
337 except ClientException
:
338 self
.logger
.exception("Error during role deletion using keystone")
339 raise AuthconnOperationException("Error during role deletion using Keystone")
341 def get_project_list(self
):
343 Get all the projects.
345 :return: list of projects
348 projects
= self
.keystone
.projects
.list()
350 "name": project
.name
,
352 } for project
in projects
if project
.name
!= self
.admin_project
]
355 except ClientException
:
356 self
.logger
.exception("Error during user project listing using keystone")
357 raise AuthException("Error during user project listing using Keystone", http_code
=HTTPStatus
.UNAUTHORIZED
)
359 def create_project(self
, project
):
363 :param project: project name.
364 :raises AuthconnOperationException: if project creation failed.
367 result
= self
.keystone
.projects
.create(project
, self
.project_domain_name
)
368 return {"name": result
.name
, "_id": result
.id}
369 except ClientException
:
370 self
.logger
.exception("Error during project creation using keystone")
371 raise AuthconnOperationException("Error during project creation using Keystone")
373 def delete_project(self
, project_id
):
377 :param project_id: project identifier.
378 :raises AuthconnOperationException: if project deletion failed.
381 projects
= self
.keystone
.projects
.list()
382 project_obj
= [project
for project
in projects
if project
.id == project_id
][0]
383 result
, _
= self
.keystone
.projects
.delete(project_obj
)
385 if result
.status_code
!= 204:
386 raise ClientException("Project was not deleted")
389 except ClientException
:
390 self
.logger
.exception("Error during project deletion using keystone")
391 raise AuthconnOperationException("Error during project deletion using Keystone")
393 def assign_role_to_user(self
, user
, project
, role
):
395 Assigning a role to a user in a project.
397 :param user: username.
398 :param project: project name.
399 :param role: role name.
400 :raises AuthconnOperationException: if role assignment failed.
403 user_obj
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0]
404 project_obj
= list(filter(lambda x
: x
.name
== project
, self
.keystone
.projects
.list()))[0]
405 role_obj
= list(filter(lambda x
: x
.name
== role
, self
.keystone
.roles
.list()))[0]
407 self
.keystone
.roles
.grant(role_obj
, user
=user_obj
, project
=project_obj
)
408 except ClientException
:
409 self
.logger
.exception("Error during user role assignment using keystone")
410 raise AuthconnOperationException("Error during user role assignment using Keystone")
412 def remove_role_from_user(self
, user
, project
, role
):
414 Remove a role from a user in a project.
416 :param user: username.
417 :param project: project name.
418 :param role: role name.
419 :raises AuthconnOperationException: if role assignment revocation failed.
422 user_obj
= list(filter(lambda x
: x
.name
== user
, self
.keystone
.users
.list()))[0]
423 project_obj
= list(filter(lambda x
: x
.name
== project
, self
.keystone
.projects
.list()))[0]
424 role_obj
= list(filter(lambda x
: x
.name
== role
, self
.keystone
.roles
.list()))[0]
426 self
.keystone
.roles
.revoke(role_obj
, user
=user_obj
, project
=project_obj
)
427 except ClientException
:
428 self
.logger
.exception("Error during user role revocation using keystone")
429 raise AuthconnOperationException("Error during user role revocation using Keystone")