1 |
|
# -*- coding: utf-8 -*- |
2 |
|
|
3 |
|
# 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 |
|
|
21 |
1 |
""" |
22 |
|
Authconn implements an Abstract class for the Auth backend connector |
23 |
|
plugins with the definition of the methods to be implemented. |
24 |
|
""" |
25 |
|
|
26 |
1 |
__author__ = ( |
27 |
|
"Eduardo Sousa <esousa@whitestack.com>, " |
28 |
|
"Pedro de la Cruz Ramos <pdelacruzramos@altran.com>" |
29 |
|
) |
30 |
1 |
__date__ = "$27-jul-2018 23:59:59$" |
31 |
|
|
32 |
1 |
from http import HTTPStatus |
33 |
1 |
from osm_nbi.base_topic import BaseTopic |
34 |
|
|
35 |
|
|
36 |
1 |
class AuthException(Exception): |
37 |
|
""" |
38 |
|
Authentication error, because token, user password not recognized |
39 |
|
""" |
40 |
|
|
41 |
1 |
def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED): |
42 |
0 |
super(AuthException, self).__init__(message) |
43 |
0 |
self.http_code = http_code |
44 |
|
|
45 |
|
|
46 |
1 |
class AuthExceptionUnauthorized(AuthException): |
47 |
|
""" |
48 |
|
Authentication error, because not having rights to make this operation |
49 |
|
""" |
50 |
|
|
51 |
1 |
pass |
52 |
|
|
53 |
|
|
54 |
1 |
class AuthconnException(Exception): |
55 |
|
""" |
56 |
|
Common and base class Exception for all authconn exceptions. |
57 |
|
""" |
58 |
|
|
59 |
1 |
def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED): |
60 |
1 |
super(AuthconnException, self).__init__(message) |
61 |
1 |
self.http_code = http_code |
62 |
|
|
63 |
|
|
64 |
1 |
class AuthconnConnectionException(AuthconnException): |
65 |
|
""" |
66 |
|
Connectivity error with Auth backend. |
67 |
|
""" |
68 |
|
|
69 |
1 |
def __init__(self, message, http_code=HTTPStatus.BAD_GATEWAY): |
70 |
0 |
super(AuthconnConnectionException, self).__init__(message, http_code) |
71 |
|
|
72 |
|
|
73 |
1 |
class AuthconnNotSupportedException(AuthconnException): |
74 |
|
""" |
75 |
|
The request is not supported by the Auth backend. |
76 |
|
""" |
77 |
|
|
78 |
1 |
def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED): |
79 |
0 |
super(AuthconnNotSupportedException, self).__init__(message, http_code) |
80 |
|
|
81 |
|
|
82 |
1 |
class AuthconnNotImplementedException(AuthconnException): |
83 |
|
""" |
84 |
|
The method is not implemented by the Auth backend. |
85 |
|
""" |
86 |
|
|
87 |
1 |
def __init__(self, message, http_code=HTTPStatus.NOT_IMPLEMENTED): |
88 |
0 |
super(AuthconnNotImplementedException, self).__init__(message, http_code) |
89 |
|
|
90 |
|
|
91 |
1 |
class AuthconnOperationException(AuthconnException): |
92 |
|
""" |
93 |
|
The operation executed failed. |
94 |
|
""" |
95 |
|
|
96 |
1 |
def __init__(self, message, http_code=HTTPStatus.INTERNAL_SERVER_ERROR): |
97 |
0 |
super(AuthconnOperationException, self).__init__(message, http_code) |
98 |
|
|
99 |
|
|
100 |
1 |
class AuthconnNotFoundException(AuthconnException): |
101 |
|
""" |
102 |
|
The operation executed failed because element not found. |
103 |
|
""" |
104 |
|
|
105 |
1 |
def __init__(self, message, http_code=HTTPStatus.NOT_FOUND): |
106 |
1 |
super().__init__(message, http_code) |
107 |
|
|
108 |
|
|
109 |
1 |
class AuthconnConflictException(AuthconnException): |
110 |
|
""" |
111 |
|
The operation has conflicts. |
112 |
|
""" |
113 |
|
|
114 |
1 |
def __init__(self, message, http_code=HTTPStatus.CONFLICT): |
115 |
0 |
super().__init__(message, http_code) |
116 |
|
|
117 |
|
|
118 |
1 |
class Authconn: |
119 |
|
""" |
120 |
|
Abstract base class for all the Auth backend connector plugins. |
121 |
|
Each Auth backend connector plugin must be a subclass of |
122 |
|
Authconn class. |
123 |
|
""" |
124 |
|
|
125 |
1 |
def __init__(self, config, db, role_permissions): |
126 |
|
""" |
127 |
|
Constructor of the Authconn class. |
128 |
|
:param config: configuration dictionary containing all the |
129 |
|
necessary configuration parameters. |
130 |
|
:param db: internal database classs |
131 |
|
:param role_permissions: read only role permission list |
132 |
|
""" |
133 |
1 |
self.config = config |
134 |
1 |
self.role_permissions = role_permissions |
135 |
|
|
136 |
1 |
def authenticate(self, credentials, token_info=None): |
137 |
|
""" |
138 |
|
Authenticate a user using username/password or token_info, plus project |
139 |
|
:param credentials: dictionary that contains: |
140 |
|
username: name, id or None |
141 |
|
password: password or None |
142 |
|
project_id: name, id, or None. If None first found project will be used to get an scope token |
143 |
|
other items are allowed for specific auth backends |
144 |
|
:param token_info: previous token_info to obtain authorization |
145 |
|
:return: the scoped token info or raises an exception. The token is a dictionary with: |
146 |
|
_id: token string id, |
147 |
|
username: username, |
148 |
|
project_id: scoped_token project_id, |
149 |
|
project_name: scoped_token project_name, |
150 |
|
expires: epoch time when it expires, |
151 |
|
|
152 |
|
""" |
153 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
154 |
|
|
155 |
1 |
def validate_token(self, token): |
156 |
|
""" |
157 |
|
Check if the token is valid. |
158 |
|
|
159 |
|
:param token: token to validate |
160 |
|
:return: dictionary with information associated with the token. If the |
161 |
|
token is not valid, returns None. |
162 |
|
""" |
163 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
164 |
|
|
165 |
1 |
def revoke_token(self, token): |
166 |
|
""" |
167 |
|
Invalidate a token. |
168 |
|
|
169 |
|
:param token: token to be revoked |
170 |
|
""" |
171 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
172 |
|
|
173 |
1 |
def create_user(self, user_info): |
174 |
|
""" |
175 |
|
Create a user. |
176 |
|
|
177 |
|
:param user_info: full user info. |
178 |
|
:raises AuthconnOperationException: if user creation failed. |
179 |
|
""" |
180 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
181 |
|
|
182 |
1 |
def update_user(self, user_info): |
183 |
|
""" |
184 |
|
Change the user name and/or password. |
185 |
|
|
186 |
|
:param user_info: user info modifications |
187 |
|
:raises AuthconnNotImplementedException: if function not implemented |
188 |
|
""" |
189 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
190 |
|
|
191 |
1 |
def delete_user(self, user_id): |
192 |
|
""" |
193 |
|
Delete user. |
194 |
|
|
195 |
|
:param user_id: user identifier. |
196 |
|
:raises AuthconnOperationException: if user deletion failed. |
197 |
|
""" |
198 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
199 |
|
|
200 |
1 |
def get_user_list(self, filter_q=None): |
201 |
|
""" |
202 |
|
Get user list. |
203 |
|
|
204 |
|
:param filter_q: dictionary to filter user list by name (username is also admited) and/or _id |
205 |
|
:return: returns a list of users. |
206 |
|
""" |
207 |
|
|
208 |
1 |
def get_user(self, _id, fail=True): |
209 |
|
""" |
210 |
|
Get one user |
211 |
|
:param _id: id or name |
212 |
|
:param fail: True to raise exception on not found. False to return None on not found |
213 |
|
:return: dictionary with the user information |
214 |
|
""" |
215 |
0 |
filt = {BaseTopic.id_field("users", _id): _id} |
216 |
0 |
users = self.get_user_list(filt) |
217 |
0 |
if not users: |
218 |
0 |
if fail: |
219 |
0 |
raise AuthconnNotFoundException( |
220 |
|
"User with {} not found".format(filt), |
221 |
|
http_code=HTTPStatus.NOT_FOUND, |
222 |
|
) |
223 |
|
else: |
224 |
0 |
return None |
225 |
0 |
return users[0] |
226 |
|
|
227 |
1 |
def create_role(self, role_info): |
228 |
|
""" |
229 |
|
Create a role. |
230 |
|
|
231 |
|
:param role_info: full role info. |
232 |
|
:raises AuthconnOperationException: if role creation failed. |
233 |
|
""" |
234 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
235 |
|
|
236 |
1 |
def delete_role(self, role_id): |
237 |
|
""" |
238 |
|
Delete a role. |
239 |
|
|
240 |
|
:param role_id: role identifier. |
241 |
|
:raises AuthconnOperationException: if user deletion failed. |
242 |
|
""" |
243 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
244 |
|
|
245 |
1 |
def get_role_list(self, filter_q=None): |
246 |
|
""" |
247 |
|
Get all the roles. |
248 |
|
|
249 |
|
:param filter_q: dictionary to filter role list by _id and/or name. |
250 |
|
:return: list of roles |
251 |
|
""" |
252 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
253 |
|
|
254 |
1 |
def get_role(self, _id, fail=True): |
255 |
|
""" |
256 |
|
Get one role |
257 |
|
:param _id: id or name |
258 |
|
:param fail: True to raise exception on not found. False to return None on not found |
259 |
|
:return: dictionary with the role information |
260 |
|
""" |
261 |
0 |
filt = {BaseTopic.id_field("roles", _id): _id} |
262 |
0 |
roles = self.get_role_list(filt) |
263 |
0 |
if not roles: |
264 |
0 |
if fail: |
265 |
0 |
raise AuthconnNotFoundException("Role with {} not found".format(filt)) |
266 |
|
else: |
267 |
0 |
return None |
268 |
0 |
return roles[0] |
269 |
|
|
270 |
1 |
def update_role(self, role_info): |
271 |
|
""" |
272 |
|
Change the information of a role |
273 |
|
:param role_info: full role info |
274 |
|
:return: None |
275 |
|
""" |
276 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
277 |
|
|
278 |
1 |
def create_project(self, project_info): |
279 |
|
""" |
280 |
|
Create a project. |
281 |
|
|
282 |
|
:param project_info: full project info. |
283 |
|
:return: the internal id of the created project |
284 |
|
:raises AuthconnOperationException: if project creation failed. |
285 |
|
""" |
286 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
287 |
|
|
288 |
1 |
def delete_project(self, project_id): |
289 |
|
""" |
290 |
|
Delete a project. |
291 |
|
|
292 |
|
:param project_id: project identifier. |
293 |
|
:raises AuthconnOperationException: if project deletion failed. |
294 |
|
""" |
295 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
296 |
|
|
297 |
1 |
def get_project_list(self, filter_q=None): |
298 |
|
""" |
299 |
|
Get all the projects. |
300 |
|
|
301 |
|
:param filter_q: dictionary to filter project list, by "name" and/or "_id" |
302 |
|
:return: list of projects |
303 |
|
""" |
304 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |
305 |
|
|
306 |
1 |
def get_project(self, _id, fail=True): |
307 |
|
""" |
308 |
|
Get one project |
309 |
|
:param _id: id or name |
310 |
|
:param fail: True to raise exception on not found. False to return None on not found |
311 |
|
:return: dictionary with the project information |
312 |
|
""" |
313 |
0 |
filt = {BaseTopic.id_field("projects", _id): _id} |
314 |
0 |
projs = self.get_project_list(filt) |
315 |
0 |
if not projs: |
316 |
0 |
if fail: |
317 |
0 |
raise AuthconnNotFoundException( |
318 |
|
"project with {} not found".format(filt) |
319 |
|
) |
320 |
|
else: |
321 |
0 |
return None |
322 |
0 |
return projs[0] |
323 |
|
|
324 |
1 |
def update_project(self, project_id, project_info): |
325 |
|
""" |
326 |
|
Change the information of a project |
327 |
|
:param project_id: project to be changed |
328 |
|
:param project_info: full project info |
329 |
|
:return: None |
330 |
|
""" |
331 |
0 |
raise AuthconnNotImplementedException("Should have implemented this") |