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