1 |
|
# -*- coding: utf-8 -*- |
2 |
|
|
3 |
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
4 |
|
# you may not use this file except in compliance with the License. |
5 |
|
# You may obtain a copy of the License at |
6 |
|
# |
7 |
|
# http://www.apache.org/licenses/LICENSE-2.0 |
8 |
|
# |
9 |
|
# Unless required by applicable law or agreed to in writing, software |
10 |
|
# distributed under the License is distributed on an "AS IS" BASIS, |
11 |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
12 |
|
# implied. |
13 |
|
# See the License for the specific language governing permissions and |
14 |
|
# limitations under the License. |
15 |
|
|
16 |
|
# import logging |
17 |
1 |
from uuid import uuid4 |
18 |
1 |
from hashlib import sha256 |
19 |
1 |
from http import HTTPStatus |
20 |
1 |
from time import time |
21 |
1 |
from osm_nbi.validation import user_new_schema, user_edit_schema, project_new_schema, project_edit_schema, \ |
22 |
|
vim_account_new_schema, vim_account_edit_schema, sdn_new_schema, sdn_edit_schema, \ |
23 |
|
wim_account_new_schema, wim_account_edit_schema, roles_new_schema, roles_edit_schema, \ |
24 |
|
k8scluster_new_schema, k8scluster_edit_schema, k8srepo_new_schema, k8srepo_edit_schema, \ |
25 |
|
osmrepo_new_schema, osmrepo_edit_schema, \ |
26 |
|
validate_input, ValidationError, is_valid_uuid # To check that User/Project Names don't look like UUIDs |
27 |
1 |
from osm_nbi.base_topic import BaseTopic, EngineException |
28 |
1 |
from osm_nbi.authconn import AuthconnNotFoundException, AuthconnConflictException |
29 |
1 |
from osm_common.dbbase import deep_update_rfc7396 |
30 |
1 |
import copy |
31 |
|
|
32 |
1 |
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>" |
33 |
|
|
34 |
|
|
35 |
1 |
class UserTopic(BaseTopic): |
36 |
1 |
topic = "users" |
37 |
1 |
topic_msg = "users" |
38 |
1 |
schema_new = user_new_schema |
39 |
1 |
schema_edit = user_edit_schema |
40 |
1 |
multiproject = False |
41 |
|
|
42 |
1 |
def __init__(self, db, fs, msg, auth): |
43 |
1 |
BaseTopic.__init__(self, db, fs, msg, auth) |
44 |
|
|
45 |
1 |
@staticmethod |
46 |
|
def _get_project_filter(session): |
47 |
|
""" |
48 |
|
Generates a filter dictionary for querying database users. |
49 |
|
Current policy is admin can show all, non admin, only its own user. |
50 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
51 |
|
:return: |
52 |
|
""" |
53 |
0 |
if session["admin"]: # allows all |
54 |
0 |
return {} |
55 |
|
else: |
56 |
0 |
return {"username": session["username"]} |
57 |
|
|
58 |
1 |
def check_conflict_on_new(self, session, indata): |
59 |
|
# check username not exists |
60 |
0 |
if self.db.get_one(self.topic, {"username": indata.get("username")}, fail_on_empty=False, fail_on_more=False): |
61 |
0 |
raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT) |
62 |
|
# check projects |
63 |
0 |
if not session["force"]: |
64 |
0 |
for p in indata.get("projects") or []: |
65 |
|
# To allow project addressing by Name as well as ID |
66 |
0 |
if not self.db.get_one("projects", {BaseTopic.id_field("projects", p): p}, fail_on_empty=False, |
67 |
|
fail_on_more=False): |
68 |
0 |
raise EngineException("project '{}' does not exist".format(p), HTTPStatus.CONFLICT) |
69 |
|
|
70 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
71 |
|
""" |
72 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
73 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
74 |
|
:param _id: internal _id |
75 |
|
:param db_content: The database content of this item _id |
76 |
|
:return: None if ok or raises EngineException with the conflict |
77 |
|
""" |
78 |
0 |
if _id == session["username"]: |
79 |
0 |
raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT) |
80 |
|
|
81 |
1 |
@staticmethod |
82 |
1 |
def format_on_new(content, project_id=None, make_public=False): |
83 |
0 |
BaseTopic.format_on_new(content, make_public=False) |
84 |
|
# Removed so that the UUID is kept, to allow User Name modification |
85 |
|
# content["_id"] = content["username"] |
86 |
0 |
salt = uuid4().hex |
87 |
0 |
content["_admin"]["salt"] = salt |
88 |
0 |
if content.get("password"): |
89 |
0 |
content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest() |
90 |
0 |
if content.get("project_role_mappings"): |
91 |
0 |
projects = [mapping["project"] for mapping in content["project_role_mappings"]] |
92 |
|
|
93 |
0 |
if content.get("projects"): |
94 |
0 |
content["projects"] += projects |
95 |
|
else: |
96 |
0 |
content["projects"] = projects |
97 |
|
|
98 |
1 |
@staticmethod |
99 |
|
def format_on_edit(final_content, edit_content): |
100 |
0 |
BaseTopic.format_on_edit(final_content, edit_content) |
101 |
0 |
if edit_content.get("password"): |
102 |
0 |
salt = uuid4().hex |
103 |
0 |
final_content["_admin"]["salt"] = salt |
104 |
0 |
final_content["password"] = sha256(edit_content["password"].encode('utf-8') + |
105 |
|
salt.encode('utf-8')).hexdigest() |
106 |
0 |
return None |
107 |
|
|
108 |
1 |
def edit(self, session, _id, indata=None, kwargs=None, content=None): |
109 |
0 |
if not session["admin"]: |
110 |
0 |
raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) |
111 |
|
# Names that look like UUIDs are not allowed |
112 |
0 |
name = (indata if indata else kwargs).get("username") |
113 |
0 |
if is_valid_uuid(name): |
114 |
0 |
raise EngineException("Usernames that look like UUIDs are not allowed", |
115 |
|
http_code=HTTPStatus.UNPROCESSABLE_ENTITY) |
116 |
0 |
return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, content=content) |
117 |
|
|
118 |
1 |
def new(self, rollback, session, indata=None, kwargs=None, headers=None): |
119 |
0 |
if not session["admin"]: |
120 |
0 |
raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) |
121 |
|
# Names that look like UUIDs are not allowed |
122 |
0 |
name = indata["username"] if indata else kwargs["username"] |
123 |
0 |
if is_valid_uuid(name): |
124 |
0 |
raise EngineException("Usernames that look like UUIDs are not allowed", |
125 |
|
http_code=HTTPStatus.UNPROCESSABLE_ENTITY) |
126 |
0 |
return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers) |
127 |
|
|
128 |
|
|
129 |
1 |
class ProjectTopic(BaseTopic): |
130 |
1 |
topic = "projects" |
131 |
1 |
topic_msg = "projects" |
132 |
1 |
schema_new = project_new_schema |
133 |
1 |
schema_edit = project_edit_schema |
134 |
1 |
multiproject = False |
135 |
|
|
136 |
1 |
def __init__(self, db, fs, msg, auth): |
137 |
1 |
BaseTopic.__init__(self, db, fs, msg, auth) |
138 |
|
|
139 |
1 |
@staticmethod |
140 |
|
def _get_project_filter(session): |
141 |
|
""" |
142 |
|
Generates a filter dictionary for querying database users. |
143 |
|
Current policy is admin can show all, non admin, only its own user. |
144 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
145 |
|
:return: |
146 |
|
""" |
147 |
0 |
if session["admin"]: # allows all |
148 |
0 |
return {} |
149 |
|
else: |
150 |
0 |
return {"_id.cont": session["project_id"]} |
151 |
|
|
152 |
1 |
def check_conflict_on_new(self, session, indata): |
153 |
0 |
if not indata.get("name"): |
154 |
0 |
raise EngineException("missing 'name'") |
155 |
|
# check name not exists |
156 |
0 |
if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): |
157 |
0 |
raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT) |
158 |
|
|
159 |
1 |
@staticmethod |
160 |
1 |
def format_on_new(content, project_id=None, make_public=False): |
161 |
1 |
BaseTopic.format_on_new(content, None) |
162 |
|
# Removed so that the UUID is kept, to allow Project Name modification |
163 |
|
# content["_id"] = content["name"] |
164 |
|
|
165 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
166 |
|
""" |
167 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
168 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
169 |
|
:param _id: internal _id |
170 |
|
:param db_content: The database content of this item _id |
171 |
|
:return: None if ok or raises EngineException with the conflict |
172 |
|
""" |
173 |
0 |
if _id in session["project_id"]: |
174 |
0 |
raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT) |
175 |
0 |
if session["force"]: |
176 |
0 |
return |
177 |
0 |
_filter = {"projects": _id} |
178 |
0 |
if self.db.get_list("users", _filter): |
179 |
0 |
raise EngineException("There is some USER that contains this project", http_code=HTTPStatus.CONFLICT) |
180 |
|
|
181 |
1 |
def edit(self, session, _id, indata=None, kwargs=None, content=None): |
182 |
0 |
if not session["admin"]: |
183 |
0 |
raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) |
184 |
|
# Names that look like UUIDs are not allowed |
185 |
0 |
name = (indata if indata else kwargs).get("name") |
186 |
0 |
if is_valid_uuid(name): |
187 |
0 |
raise EngineException("Project names that look like UUIDs are not allowed", |
188 |
|
http_code=HTTPStatus.UNPROCESSABLE_ENTITY) |
189 |
0 |
return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, content=content) |
190 |
|
|
191 |
1 |
def new(self, rollback, session, indata=None, kwargs=None, headers=None): |
192 |
0 |
if not session["admin"]: |
193 |
0 |
raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) |
194 |
|
# Names that look like UUIDs are not allowed |
195 |
0 |
name = indata["name"] if indata else kwargs["name"] |
196 |
0 |
if is_valid_uuid(name): |
197 |
0 |
raise EngineException("Project names that look like UUIDs are not allowed", |
198 |
|
http_code=HTTPStatus.UNPROCESSABLE_ENTITY) |
199 |
0 |
return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers) |
200 |
|
|
201 |
|
|
202 |
1 |
class CommonVimWimSdn(BaseTopic): |
203 |
|
"""Common class for VIM, WIM SDN just to unify methods that are equal to all of them""" |
204 |
1 |
config_to_encrypt = {} # what keys at config must be encrypted because contains passwords |
205 |
1 |
password_to_encrypt = "" # key that contains a password |
206 |
|
|
207 |
1 |
@staticmethod |
208 |
1 |
def _create_operation(op_type, params=None): |
209 |
|
""" |
210 |
|
Creates a dictionary with the information to an operation, similar to ns-lcm-op |
211 |
|
:param op_type: can be create, edit, delete |
212 |
|
:param params: operation input parameters |
213 |
|
:return: new dictionary with |
214 |
|
""" |
215 |
1 |
now = time() |
216 |
1 |
return { |
217 |
|
"lcmOperationType": op_type, |
218 |
|
"operationState": "PROCESSING", |
219 |
|
"startTime": now, |
220 |
|
"statusEnteredTime": now, |
221 |
|
"detailed-status": "", |
222 |
|
"operationParams": params, |
223 |
|
} |
224 |
|
|
225 |
1 |
def check_conflict_on_new(self, session, indata): |
226 |
|
""" |
227 |
|
Check that the data to be inserted is valid. It is checked that name is unique |
228 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
229 |
|
:param indata: data to be inserted |
230 |
|
:return: None or raises EngineException |
231 |
|
""" |
232 |
1 |
self.check_unique_name(session, indata["name"], _id=None) |
233 |
|
|
234 |
1 |
def check_conflict_on_edit(self, session, final_content, edit_content, _id): |
235 |
|
""" |
236 |
|
Check that the data to be edited/uploaded is valid. It is checked that name is unique |
237 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
238 |
|
:param final_content: data once modified. This method may change it. |
239 |
|
:param edit_content: incremental data that contains the modifications to apply |
240 |
|
:param _id: internal _id |
241 |
|
:return: None or raises EngineException |
242 |
|
""" |
243 |
1 |
if not session["force"] and edit_content.get("name"): |
244 |
1 |
self.check_unique_name(session, edit_content["name"], _id=_id) |
245 |
|
|
246 |
1 |
return final_content |
247 |
|
|
248 |
1 |
def format_on_edit(self, final_content, edit_content): |
249 |
|
""" |
250 |
|
Modifies final_content inserting admin information upon edition |
251 |
|
:param final_content: final content to be stored at database |
252 |
|
:param edit_content: user requested update content |
253 |
|
:return: operation id |
254 |
|
""" |
255 |
1 |
super().format_on_edit(final_content, edit_content) |
256 |
|
|
257 |
|
# encrypt passwords |
258 |
1 |
schema_version = final_content.get("schema_version") |
259 |
1 |
if schema_version: |
260 |
0 |
if edit_content.get(self.password_to_encrypt): |
261 |
0 |
final_content[self.password_to_encrypt] = self.db.encrypt(edit_content[self.password_to_encrypt], |
262 |
|
schema_version=schema_version, |
263 |
|
salt=final_content["_id"]) |
264 |
0 |
config_to_encrypt_keys = self.config_to_encrypt.get(schema_version) or self.config_to_encrypt.get("default") |
265 |
0 |
if edit_content.get("config") and config_to_encrypt_keys: |
266 |
|
|
267 |
0 |
for p in config_to_encrypt_keys: |
268 |
0 |
if edit_content["config"].get(p): |
269 |
0 |
final_content["config"][p] = self.db.encrypt(edit_content["config"][p], |
270 |
|
schema_version=schema_version, |
271 |
|
salt=final_content["_id"]) |
272 |
|
|
273 |
|
# create edit operation |
274 |
1 |
final_content["_admin"]["operations"].append(self._create_operation("edit")) |
275 |
1 |
return "{}:{}".format(final_content["_id"], len(final_content["_admin"]["operations"]) - 1) |
276 |
|
|
277 |
1 |
def format_on_new(self, content, project_id=None, make_public=False): |
278 |
|
""" |
279 |
|
Modifies content descriptor to include _admin and insert create operation |
280 |
|
:param content: descriptor to be modified |
281 |
|
:param project_id: if included, it add project read/write permissions. Can be None or a list |
282 |
|
:param make_public: if included it is generated as public for reading. |
283 |
|
:return: op_id: operation id on asynchronous operation, None otherwise. In addition content is modified |
284 |
|
""" |
285 |
1 |
super().format_on_new(content, project_id=project_id, make_public=make_public) |
286 |
1 |
content["schema_version"] = schema_version = "1.11" |
287 |
|
|
288 |
|
# encrypt passwords |
289 |
1 |
if content.get(self.password_to_encrypt): |
290 |
0 |
content[self.password_to_encrypt] = self.db.encrypt(content[self.password_to_encrypt], |
291 |
|
schema_version=schema_version, |
292 |
|
salt=content["_id"]) |
293 |
1 |
config_to_encrypt_keys = self.config_to_encrypt.get(schema_version) or self.config_to_encrypt.get("default") |
294 |
1 |
if content.get("config") and config_to_encrypt_keys: |
295 |
0 |
for p in config_to_encrypt_keys: |
296 |
0 |
if content["config"].get(p): |
297 |
0 |
content["config"][p] = self.db.encrypt(content["config"][p], |
298 |
|
schema_version=schema_version, |
299 |
|
salt=content["_id"]) |
300 |
|
|
301 |
1 |
content["_admin"]["operationalState"] = "PROCESSING" |
302 |
|
|
303 |
|
# create operation |
304 |
1 |
content["_admin"]["operations"] = [self._create_operation("create")] |
305 |
1 |
content["_admin"]["current_operation"] = None |
306 |
|
|
307 |
1 |
return "{}:0".format(content["_id"]) |
308 |
|
|
309 |
1 |
def delete(self, session, _id, dry_run=False, not_send_msg=None): |
310 |
|
""" |
311 |
|
Delete item by its internal _id |
312 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
313 |
|
:param _id: server internal id |
314 |
|
:param dry_run: make checking but do not delete |
315 |
|
:param not_send_msg: To not send message (False) or store content (list) instead |
316 |
|
:return: operation id if it is ordered to delete. None otherwise |
317 |
|
""" |
318 |
|
|
319 |
1 |
filter_q = self._get_project_filter(session) |
320 |
1 |
filter_q["_id"] = _id |
321 |
1 |
db_content = self.db.get_one(self.topic, filter_q) |
322 |
|
|
323 |
1 |
self.check_conflict_on_del(session, _id, db_content) |
324 |
1 |
if dry_run: |
325 |
0 |
return None |
326 |
|
|
327 |
|
# remove reference from project_read if there are more projects referencing it. If it last one, |
328 |
|
# do not remove reference, but order via kafka to delete it |
329 |
1 |
if session["project_id"] and session["project_id"]: |
330 |
1 |
other_projects_referencing = next((p for p in db_content["_admin"]["projects_read"] |
331 |
|
if p not in session["project_id"] and p != "ANY"), None) |
332 |
|
|
333 |
|
# check if there are projects referencing it (apart from ANY, that means, public).... |
334 |
1 |
if other_projects_referencing: |
335 |
|
# remove references but not delete |
336 |
1 |
update_dict_pull = {"_admin.projects_read": session["project_id"], |
337 |
|
"_admin.projects_write": session["project_id"]} |
338 |
1 |
self.db.set_one(self.topic, filter_q, update_dict=None, pull_list=update_dict_pull) |
339 |
1 |
return None |
340 |
|
else: |
341 |
1 |
can_write = next((p for p in db_content["_admin"]["projects_write"] if p == "ANY" or |
342 |
|
p in session["project_id"]), None) |
343 |
1 |
if not can_write: |
344 |
0 |
raise EngineException("You have not write permission to delete it", |
345 |
|
http_code=HTTPStatus.UNAUTHORIZED) |
346 |
|
|
347 |
|
# It must be deleted |
348 |
1 |
if session["force"]: |
349 |
1 |
self.db.del_one(self.topic, {"_id": _id}) |
350 |
1 |
op_id = None |
351 |
1 |
self._send_msg("deleted", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg) |
352 |
|
else: |
353 |
1 |
update_dict = {"_admin.to_delete": True} |
354 |
1 |
self.db.set_one(self.topic, {"_id": _id}, |
355 |
|
update_dict=update_dict, |
356 |
|
push={"_admin.operations": self._create_operation("delete")} |
357 |
|
) |
358 |
|
# the number of operations is the operation_id. db_content does not contains the new operation inserted, |
359 |
|
# so the -1 is not needed |
360 |
1 |
op_id = "{}:{}".format(db_content["_id"], len(db_content["_admin"]["operations"])) |
361 |
1 |
self._send_msg("delete", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg) |
362 |
1 |
return op_id |
363 |
|
|
364 |
|
|
365 |
1 |
class VimAccountTopic(CommonVimWimSdn): |
366 |
1 |
topic = "vim_accounts" |
367 |
1 |
topic_msg = "vim_account" |
368 |
1 |
schema_new = vim_account_new_schema |
369 |
1 |
schema_edit = vim_account_edit_schema |
370 |
1 |
multiproject = True |
371 |
1 |
password_to_encrypt = "vim_password" |
372 |
1 |
config_to_encrypt = {"1.1": ("admin_password", "nsx_password", "vcenter_password"), |
373 |
|
"default": ("admin_password", "nsx_password", "vcenter_password", "vrops_password")} |
374 |
|
|
375 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
376 |
|
""" |
377 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
378 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
379 |
|
:param _id: internal _id |
380 |
|
:param db_content: The database content of this item _id |
381 |
|
:return: None if ok or raises EngineException with the conflict |
382 |
|
""" |
383 |
0 |
if session["force"]: |
384 |
0 |
return |
385 |
|
# check if used by VNF |
386 |
0 |
if self.db.get_list("vnfrs", {"vim-account-id": _id}): |
387 |
0 |
raise EngineException("There is at least one VNF using this VIM account", http_code=HTTPStatus.CONFLICT) |
388 |
0 |
super().check_conflict_on_del(session, _id, db_content) |
389 |
|
|
390 |
|
|
391 |
1 |
class WimAccountTopic(CommonVimWimSdn): |
392 |
1 |
topic = "wim_accounts" |
393 |
1 |
topic_msg = "wim_account" |
394 |
1 |
schema_new = wim_account_new_schema |
395 |
1 |
schema_edit = wim_account_edit_schema |
396 |
1 |
multiproject = True |
397 |
1 |
password_to_encrypt = "wim_password" |
398 |
1 |
config_to_encrypt = {} |
399 |
|
|
400 |
|
|
401 |
1 |
class SdnTopic(CommonVimWimSdn): |
402 |
1 |
topic = "sdns" |
403 |
1 |
topic_msg = "sdn" |
404 |
1 |
quota_name = "sdn_controllers" |
405 |
1 |
schema_new = sdn_new_schema |
406 |
1 |
schema_edit = sdn_edit_schema |
407 |
1 |
multiproject = True |
408 |
1 |
password_to_encrypt = "password" |
409 |
1 |
config_to_encrypt = {} |
410 |
|
|
411 |
1 |
def _obtain_url(self, input, create): |
412 |
0 |
if input.get("ip") or input.get("port"): |
413 |
0 |
if not input.get("ip") or not input.get("port") or input.get('url'): |
414 |
0 |
raise ValidationError("You must provide both 'ip' and 'port' (deprecated); or just 'url' (prefered)") |
415 |
0 |
input['url'] = "http://{}:{}/".format(input["ip"], input["port"]) |
416 |
0 |
del input["ip"] |
417 |
0 |
del input["port"] |
418 |
0 |
elif create and not input.get('url'): |
419 |
0 |
raise ValidationError("You must provide 'url'") |
420 |
0 |
return input |
421 |
|
|
422 |
1 |
def _validate_input_new(self, input, force=False): |
423 |
0 |
input = super()._validate_input_new(input, force) |
424 |
0 |
return self._obtain_url(input, True) |
425 |
|
|
426 |
1 |
def _validate_input_edit(self, input, content, force=False): |
427 |
0 |
input = super()._validate_input_edit(input, content, force) |
428 |
0 |
return self._obtain_url(input, False) |
429 |
|
|
430 |
|
|
431 |
1 |
class K8sClusterTopic(CommonVimWimSdn): |
432 |
1 |
topic = "k8sclusters" |
433 |
1 |
topic_msg = "k8scluster" |
434 |
1 |
schema_new = k8scluster_new_schema |
435 |
1 |
schema_edit = k8scluster_edit_schema |
436 |
1 |
multiproject = True |
437 |
1 |
password_to_encrypt = None |
438 |
1 |
config_to_encrypt = {} |
439 |
|
|
440 |
1 |
def format_on_new(self, content, project_id=None, make_public=False): |
441 |
0 |
oid = super().format_on_new(content, project_id, make_public) |
442 |
0 |
self.db.encrypt_decrypt_fields(content["credentials"], 'encrypt', ['password', 'secret'], |
443 |
|
schema_version=content["schema_version"], salt=content["_id"]) |
444 |
|
# Add Helm/Juju Repo lists |
445 |
0 |
repos = {"helm-chart": [], "juju-bundle": []} |
446 |
0 |
for proj in content["_admin"]["projects_read"]: |
447 |
0 |
if proj != 'ANY': |
448 |
0 |
for repo in self.db.get_list("k8srepos", {"_admin.projects_read": proj}): |
449 |
0 |
if repo["_id"] not in repos[repo["type"]]: |
450 |
0 |
repos[repo["type"]].append(repo["_id"]) |
451 |
0 |
for k in repos: |
452 |
0 |
content["_admin"][k.replace('-', '_')+"_repos"] = repos[k] |
453 |
0 |
return oid |
454 |
|
|
455 |
1 |
def format_on_edit(self, final_content, edit_content): |
456 |
0 |
if final_content.get("schema_version") and edit_content.get("credentials"): |
457 |
0 |
self.db.encrypt_decrypt_fields(edit_content["credentials"], 'encrypt', ['password', 'secret'], |
458 |
|
schema_version=final_content["schema_version"], salt=final_content["_id"]) |
459 |
0 |
deep_update_rfc7396(final_content["credentials"], edit_content["credentials"]) |
460 |
0 |
oid = super().format_on_edit(final_content, edit_content) |
461 |
0 |
return oid |
462 |
|
|
463 |
1 |
def check_conflict_on_edit(self, session, final_content, edit_content, _id): |
464 |
0 |
final_content = super(CommonVimWimSdn, self).check_conflict_on_edit(session, final_content, edit_content, _id) |
465 |
0 |
final_content = super().check_conflict_on_edit(session, final_content, edit_content, _id) |
466 |
|
# Update Helm/Juju Repo lists |
467 |
0 |
repos = {"helm-chart": [], "juju-bundle": []} |
468 |
0 |
for proj in session.get("set_project", []): |
469 |
0 |
if proj != 'ANY': |
470 |
0 |
for repo in self.db.get_list("k8srepos", {"_admin.projects_read": proj}): |
471 |
0 |
if repo["_id"] not in repos[repo["type"]]: |
472 |
0 |
repos[repo["type"]].append(repo["_id"]) |
473 |
0 |
for k in repos: |
474 |
0 |
rlist = k.replace('-', '_') + "_repos" |
475 |
0 |
if rlist not in final_content["_admin"]: |
476 |
0 |
final_content["_admin"][rlist] = [] |
477 |
0 |
final_content["_admin"][rlist] += repos[k] |
478 |
0 |
return final_content |
479 |
|
|
480 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
481 |
|
""" |
482 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
483 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
484 |
|
:param _id: internal _id |
485 |
|
:param db_content: The database content of this item _id |
486 |
|
:return: None if ok or raises EngineException with the conflict |
487 |
|
""" |
488 |
0 |
if session["force"]: |
489 |
0 |
return |
490 |
|
# check if used by VNF |
491 |
0 |
filter_q = {"kdur.k8s-cluster.id": _id} |
492 |
0 |
if session["project_id"]: |
493 |
0 |
filter_q["_admin.projects_read.cont"] = session["project_id"] |
494 |
0 |
if self.db.get_list("vnfrs", filter_q): |
495 |
0 |
raise EngineException("There is at least one VNF using this k8scluster", http_code=HTTPStatus.CONFLICT) |
496 |
0 |
super().check_conflict_on_del(session, _id, db_content) |
497 |
|
|
498 |
|
|
499 |
1 |
class K8sRepoTopic(CommonVimWimSdn): |
500 |
1 |
topic = "k8srepos" |
501 |
1 |
topic_msg = "k8srepo" |
502 |
1 |
schema_new = k8srepo_new_schema |
503 |
1 |
schema_edit = k8srepo_edit_schema |
504 |
1 |
multiproject = True |
505 |
1 |
password_to_encrypt = None |
506 |
1 |
config_to_encrypt = {} |
507 |
|
|
508 |
1 |
def format_on_new(self, content, project_id=None, make_public=False): |
509 |
0 |
oid = super().format_on_new(content, project_id, make_public) |
510 |
|
# Update Helm/Juju Repo lists |
511 |
0 |
repo_list = content["type"].replace('-', '_')+"_repos" |
512 |
0 |
for proj in content["_admin"]["projects_read"]: |
513 |
0 |
if proj != 'ANY': |
514 |
0 |
self.db.set_list("k8sclusters", |
515 |
|
{"_admin.projects_read": proj, "_admin."+repo_list+".ne": content["_id"]}, {}, |
516 |
|
push={"_admin."+repo_list: content["_id"]}) |
517 |
0 |
return oid |
518 |
|
|
519 |
1 |
def delete(self, session, _id, dry_run=False, not_send_msg=None): |
520 |
0 |
type = self.db.get_one("k8srepos", {"_id": _id})["type"] |
521 |
0 |
oid = super().delete(session, _id, dry_run, not_send_msg) |
522 |
0 |
if oid: |
523 |
|
# Remove from Helm/Juju Repo lists |
524 |
0 |
repo_list = type.replace('-', '_') + "_repos" |
525 |
0 |
self.db.set_list("k8sclusters", {"_admin."+repo_list: _id}, {}, pull={"_admin."+repo_list: _id}) |
526 |
0 |
return oid |
527 |
|
|
528 |
|
|
529 |
1 |
class OsmRepoTopic(BaseTopic): |
530 |
1 |
topic = "osmrepos" |
531 |
1 |
topic_msg = "osmrepos" |
532 |
1 |
schema_new = osmrepo_new_schema |
533 |
1 |
schema_edit = osmrepo_edit_schema |
534 |
1 |
multiproject = True |
535 |
|
# TODO: Implement user/password |
536 |
|
|
537 |
|
|
538 |
1 |
class UserTopicAuth(UserTopic): |
539 |
|
# topic = "users" |
540 |
1 |
topic_msg = "users" |
541 |
1 |
schema_new = user_new_schema |
542 |
1 |
schema_edit = user_edit_schema |
543 |
|
|
544 |
1 |
def __init__(self, db, fs, msg, auth): |
545 |
1 |
UserTopic.__init__(self, db, fs, msg, auth) |
546 |
|
# self.auth = auth |
547 |
|
|
548 |
1 |
def check_conflict_on_new(self, session, indata): |
549 |
|
""" |
550 |
|
Check that the data to be inserted is valid |
551 |
|
|
552 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
553 |
|
:param indata: data to be inserted |
554 |
|
:return: None or raises EngineException |
555 |
|
""" |
556 |
1 |
username = indata.get("username") |
557 |
1 |
if is_valid_uuid(username): |
558 |
1 |
raise EngineException("username '{}' cannot have a uuid format".format(username), |
559 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
560 |
|
|
561 |
|
# Check that username is not used, regardless keystone already checks this |
562 |
1 |
if self.auth.get_user_list(filter_q={"name": username}): |
563 |
1 |
raise EngineException("username '{}' is already used".format(username), HTTPStatus.CONFLICT) |
564 |
|
|
565 |
1 |
if "projects" in indata.keys(): |
566 |
|
# convert to new format project_role_mappings |
567 |
1 |
role = self.auth.get_role_list({"name": "project_admin"}) |
568 |
1 |
if not role: |
569 |
1 |
role = self.auth.get_role_list() |
570 |
1 |
if not role: |
571 |
1 |
raise AuthconnNotFoundException("Can't find default role for user '{}'".format(username)) |
572 |
1 |
rid = role[0]["_id"] |
573 |
1 |
if not indata.get("project_role_mappings"): |
574 |
1 |
indata["project_role_mappings"] = [] |
575 |
1 |
for project in indata["projects"]: |
576 |
1 |
pid = self.auth.get_project(project)["_id"] |
577 |
1 |
prm = {"project": pid, "role": rid} |
578 |
1 |
if prm not in indata["project_role_mappings"]: |
579 |
1 |
indata["project_role_mappings"].append(prm) |
580 |
|
# raise EngineException("Format invalid: the keyword 'projects' is not allowed for keystone authentication", |
581 |
|
# HTTPStatus.BAD_REQUEST) |
582 |
|
|
583 |
1 |
def check_conflict_on_edit(self, session, final_content, edit_content, _id): |
584 |
|
""" |
585 |
|
Check that the data to be edited/uploaded is valid |
586 |
|
|
587 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
588 |
|
:param final_content: data once modified |
589 |
|
:param edit_content: incremental data that contains the modifications to apply |
590 |
|
:param _id: internal _id |
591 |
|
:return: None or raises EngineException |
592 |
|
""" |
593 |
|
|
594 |
1 |
if "username" in edit_content: |
595 |
1 |
username = edit_content.get("username") |
596 |
1 |
if is_valid_uuid(username): |
597 |
1 |
raise EngineException("username '{}' cannot have an uuid format".format(username), |
598 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
599 |
|
|
600 |
|
# Check that username is not used, regardless keystone already checks this |
601 |
1 |
if self.auth.get_user_list(filter_q={"name": username}): |
602 |
1 |
raise EngineException("username '{}' is already used".format(username), HTTPStatus.CONFLICT) |
603 |
|
|
604 |
1 |
if final_content["username"] == "admin": |
605 |
1 |
for mapping in edit_content.get("remove_project_role_mappings", ()): |
606 |
1 |
if mapping["project"] == "admin" and mapping.get("role") in (None, "system_admin"): |
607 |
|
# TODO make this also available for project id and role id |
608 |
1 |
raise EngineException("You cannot remove system_admin role from admin user", |
609 |
|
http_code=HTTPStatus.FORBIDDEN) |
610 |
|
|
611 |
1 |
return final_content |
612 |
|
|
613 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
614 |
|
""" |
615 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
616 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
617 |
|
:param _id: internal _id |
618 |
|
:param db_content: The database content of this item _id |
619 |
|
:return: None if ok or raises EngineException with the conflict |
620 |
|
""" |
621 |
1 |
if db_content["username"] == session["username"]: |
622 |
1 |
raise EngineException("You cannot delete your own login user ", http_code=HTTPStatus.CONFLICT) |
623 |
|
# TODO: Check that user is not logged in ? How? (Would require listing current tokens) |
624 |
|
|
625 |
1 |
@staticmethod |
626 |
|
def format_on_show(content): |
627 |
|
""" |
628 |
|
Modifies the content of the role information to separate the role |
629 |
|
metadata from the role definition. |
630 |
|
""" |
631 |
0 |
project_role_mappings = [] |
632 |
|
|
633 |
0 |
if "projects" in content: |
634 |
0 |
for project in content["projects"]: |
635 |
0 |
for role in project["roles"]: |
636 |
0 |
project_role_mappings.append({"project": project["_id"], |
637 |
|
"project_name": project["name"], |
638 |
|
"role": role["_id"], |
639 |
|
"role_name": role["name"]}) |
640 |
0 |
del content["projects"] |
641 |
0 |
content["project_role_mappings"] = project_role_mappings |
642 |
|
|
643 |
0 |
return content |
644 |
|
|
645 |
1 |
def new(self, rollback, session, indata=None, kwargs=None, headers=None): |
646 |
|
""" |
647 |
|
Creates a new entry into the authentication backend. |
648 |
|
|
649 |
|
NOTE: Overrides BaseTopic functionality because it doesn't require access to database. |
650 |
|
|
651 |
|
:param rollback: list to append created items at database in case a rollback may to be done |
652 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
653 |
|
:param indata: data to be inserted |
654 |
|
:param kwargs: used to override the indata descriptor |
655 |
|
:param headers: http request headers |
656 |
|
:return: _id: identity of the inserted data, operation _id (None) |
657 |
|
""" |
658 |
1 |
try: |
659 |
1 |
content = BaseTopic._remove_envelop(indata) |
660 |
|
|
661 |
|
# Override descriptor with query string kwargs |
662 |
1 |
BaseTopic._update_input_with_kwargs(content, kwargs) |
663 |
1 |
content = self._validate_input_new(content, session["force"]) |
664 |
1 |
self.check_conflict_on_new(session, content) |
665 |
|
# self.format_on_new(content, session["project_id"], make_public=session["public"]) |
666 |
1 |
now = time() |
667 |
1 |
content["_admin"] = {"created": now, "modified": now} |
668 |
1 |
prms = [] |
669 |
1 |
for prm in content.get("project_role_mappings", []): |
670 |
1 |
proj = self.auth.get_project(prm["project"], not session["force"]) |
671 |
1 |
role = self.auth.get_role(prm["role"], not session["force"]) |
672 |
1 |
pid = proj["_id"] if proj else None |
673 |
1 |
rid = role["_id"] if role else None |
674 |
1 |
prl = {"project": pid, "role": rid} |
675 |
1 |
if prl not in prms: |
676 |
1 |
prms.append(prl) |
677 |
1 |
content["project_role_mappings"] = prms |
678 |
|
# _id = self.auth.create_user(content["username"], content["password"])["_id"] |
679 |
1 |
_id = self.auth.create_user(content)["_id"] |
680 |
|
|
681 |
1 |
rollback.append({"topic": self.topic, "_id": _id}) |
682 |
|
# del content["password"] |
683 |
1 |
self._send_msg("created", content, not_send_msg=None) |
684 |
1 |
return _id, None |
685 |
1 |
except ValidationError as e: |
686 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |
687 |
|
|
688 |
1 |
def show(self, session, _id, api_req=False): |
689 |
|
""" |
690 |
|
Get complete information on an topic |
691 |
|
|
692 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
693 |
|
:param _id: server internal id or username |
694 |
|
:param api_req: True if this call is serving an external API request. False if serving internal request. |
695 |
|
:return: dictionary, raise exception if not found. |
696 |
|
""" |
697 |
|
# Allow _id to be a name or uuid |
698 |
1 |
filter_q = {"username": _id} |
699 |
|
# users = self.auth.get_user_list(filter_q) |
700 |
1 |
users = self.list(session, filter_q) # To allow default filtering (Bug 853) |
701 |
1 |
if len(users) == 1: |
702 |
1 |
return users[0] |
703 |
0 |
elif len(users) > 1: |
704 |
0 |
raise EngineException("Too many users found for '{}'".format(_id), HTTPStatus.CONFLICT) |
705 |
|
else: |
706 |
0 |
raise EngineException("User '{}' not found".format(_id), HTTPStatus.NOT_FOUND) |
707 |
|
|
708 |
1 |
def edit(self, session, _id, indata=None, kwargs=None, content=None): |
709 |
|
""" |
710 |
|
Updates an user entry. |
711 |
|
|
712 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
713 |
|
:param _id: |
714 |
|
:param indata: data to be inserted |
715 |
|
:param kwargs: used to override the indata descriptor |
716 |
|
:param content: |
717 |
|
:return: _id: identity of the inserted data. |
718 |
|
""" |
719 |
1 |
indata = self._remove_envelop(indata) |
720 |
|
|
721 |
|
# Override descriptor with query string kwargs |
722 |
1 |
if kwargs: |
723 |
0 |
BaseTopic._update_input_with_kwargs(indata, kwargs) |
724 |
1 |
try: |
725 |
1 |
if not content: |
726 |
1 |
content = self.show(session, _id) |
727 |
1 |
indata = self._validate_input_edit(indata, content, force=session["force"]) |
728 |
1 |
content = self.check_conflict_on_edit(session, content, indata, _id=_id) |
729 |
|
# self.format_on_edit(content, indata) |
730 |
|
|
731 |
1 |
if not ("password" in indata or "username" in indata or indata.get("remove_project_role_mappings") or |
732 |
|
indata.get("add_project_role_mappings") or indata.get("project_role_mappings") or |
733 |
|
indata.get("projects") or indata.get("add_projects")): |
734 |
0 |
return _id |
735 |
1 |
if indata.get("project_role_mappings") \ |
736 |
|
and (indata.get("remove_project_role_mappings") or indata.get("add_project_role_mappings")): |
737 |
0 |
raise EngineException("Option 'project_role_mappings' is incompatible with 'add_project_role_mappings" |
738 |
|
"' or 'remove_project_role_mappings'", http_code=HTTPStatus.BAD_REQUEST) |
739 |
|
|
740 |
1 |
if indata.get("projects") or indata.get("add_projects"): |
741 |
1 |
role = self.auth.get_role_list({"name": "project_admin"}) |
742 |
1 |
if not role: |
743 |
1 |
role = self.auth.get_role_list() |
744 |
1 |
if not role: |
745 |
1 |
raise AuthconnNotFoundException("Can't find a default role for user '{}'" |
746 |
|
.format(content["username"])) |
747 |
0 |
rid = role[0]["_id"] |
748 |
0 |
if "add_project_role_mappings" not in indata: |
749 |
0 |
indata["add_project_role_mappings"] = [] |
750 |
0 |
if "remove_project_role_mappings" not in indata: |
751 |
0 |
indata["remove_project_role_mappings"] = [] |
752 |
0 |
if isinstance(indata.get("projects"), dict): |
753 |
|
# backward compatible |
754 |
0 |
for k, v in indata["projects"].items(): |
755 |
0 |
if k.startswith("$") and v is None: |
756 |
0 |
indata["remove_project_role_mappings"].append({"project": k[1:]}) |
757 |
0 |
elif k.startswith("$+"): |
758 |
0 |
indata["add_project_role_mappings"].append({"project": v, "role": rid}) |
759 |
0 |
del indata["projects"] |
760 |
0 |
for proj in indata.get("projects", []) + indata.get("add_projects", []): |
761 |
0 |
indata["add_project_role_mappings"].append({"project": proj, "role": rid}) |
762 |
|
|
763 |
|
# user = self.show(session, _id) # Already in 'content' |
764 |
1 |
original_mapping = content["project_role_mappings"] |
765 |
|
|
766 |
1 |
mappings_to_add = [] |
767 |
1 |
mappings_to_remove = [] |
768 |
|
|
769 |
|
# remove |
770 |
1 |
for to_remove in indata.get("remove_project_role_mappings", ()): |
771 |
1 |
for mapping in original_mapping: |
772 |
1 |
if to_remove["project"] in (mapping["project"], mapping["project_name"]): |
773 |
1 |
if not to_remove.get("role") or to_remove["role"] in (mapping["role"], mapping["role_name"]): |
774 |
1 |
mappings_to_remove.append(mapping) |
775 |
|
|
776 |
|
# add |
777 |
1 |
for to_add in indata.get("add_project_role_mappings", ()): |
778 |
1 |
for mapping in original_mapping: |
779 |
1 |
if to_add["project"] in (mapping["project"], mapping["project_name"]) and \ |
780 |
|
to_add["role"] in (mapping["role"], mapping["role_name"]): |
781 |
|
|
782 |
0 |
if mapping in mappings_to_remove: # do not remove |
783 |
0 |
mappings_to_remove.remove(mapping) |
784 |
0 |
break # do not add, it is already at user |
785 |
|
else: |
786 |
1 |
pid = self.auth.get_project(to_add["project"])["_id"] |
787 |
1 |
rid = self.auth.get_role(to_add["role"])["_id"] |
788 |
1 |
mappings_to_add.append({"project": pid, "role": rid}) |
789 |
|
|
790 |
|
# set |
791 |
1 |
if indata.get("project_role_mappings"): |
792 |
0 |
for to_set in indata["project_role_mappings"]: |
793 |
0 |
for mapping in original_mapping: |
794 |
0 |
if to_set["project"] in (mapping["project"], mapping["project_name"]) and \ |
795 |
|
to_set["role"] in (mapping["role"], mapping["role_name"]): |
796 |
0 |
if mapping in mappings_to_remove: # do not remove |
797 |
0 |
mappings_to_remove.remove(mapping) |
798 |
0 |
break # do not add, it is already at user |
799 |
|
else: |
800 |
0 |
pid = self.auth.get_project(to_set["project"])["_id"] |
801 |
0 |
rid = self.auth.get_role(to_set["role"])["_id"] |
802 |
0 |
mappings_to_add.append({"project": pid, "role": rid}) |
803 |
0 |
for mapping in original_mapping: |
804 |
0 |
for to_set in indata["project_role_mappings"]: |
805 |
0 |
if to_set["project"] in (mapping["project"], mapping["project_name"]) and \ |
806 |
|
to_set["role"] in (mapping["role"], mapping["role_name"]): |
807 |
0 |
break |
808 |
|
else: |
809 |
|
# delete |
810 |
0 |
if mapping not in mappings_to_remove: # do not remove |
811 |
0 |
mappings_to_remove.append(mapping) |
812 |
|
|
813 |
1 |
self.auth.update_user({"_id": _id, "username": indata.get("username"), "password": indata.get("password"), |
814 |
|
"add_project_role_mappings": mappings_to_add, |
815 |
|
"remove_project_role_mappings": mappings_to_remove |
816 |
|
}) |
817 |
1 |
data_to_send = {'_id': _id, "changes": indata} |
818 |
1 |
self._send_msg("edited", data_to_send, not_send_msg=None) |
819 |
|
|
820 |
|
# return _id |
821 |
1 |
except ValidationError as e: |
822 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |
823 |
|
|
824 |
1 |
def list(self, session, filter_q=None, api_req=False): |
825 |
|
""" |
826 |
|
Get a list of the topic that matches a filter |
827 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
828 |
|
:param filter_q: filter of data to be applied |
829 |
|
:param api_req: True if this call is serving an external API request. False if serving internal request. |
830 |
|
:return: The list, it can be empty if no one match the filter. |
831 |
|
""" |
832 |
1 |
user_list = self.auth.get_user_list(filter_q) |
833 |
1 |
if not session["allow_show_user_project_role"]: |
834 |
|
# Bug 853 - Default filtering |
835 |
0 |
user_list = [usr for usr in user_list if usr["username"] == session["username"]] |
836 |
1 |
return user_list |
837 |
|
|
838 |
1 |
def delete(self, session, _id, dry_run=False, not_send_msg=None): |
839 |
|
""" |
840 |
|
Delete item by its internal _id |
841 |
|
|
842 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
843 |
|
:param _id: server internal id |
844 |
|
:param force: indicates if deletion must be forced in case of conflict |
845 |
|
:param dry_run: make checking but do not delete |
846 |
|
:param not_send_msg: To not send message (False) or store content (list) instead |
847 |
|
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ... |
848 |
|
""" |
849 |
|
# Allow _id to be a name or uuid |
850 |
1 |
user = self.auth.get_user(_id) |
851 |
1 |
uid = user["_id"] |
852 |
1 |
self.check_conflict_on_del(session, uid, user) |
853 |
1 |
if not dry_run: |
854 |
1 |
v = self.auth.delete_user(uid) |
855 |
1 |
self._send_msg("deleted", user, not_send_msg=not_send_msg) |
856 |
1 |
return v |
857 |
0 |
return None |
858 |
|
|
859 |
|
|
860 |
1 |
class ProjectTopicAuth(ProjectTopic): |
861 |
|
# topic = "projects" |
862 |
1 |
topic_msg = "project" |
863 |
1 |
schema_new = project_new_schema |
864 |
1 |
schema_edit = project_edit_schema |
865 |
|
|
866 |
1 |
def __init__(self, db, fs, msg, auth): |
867 |
1 |
ProjectTopic.__init__(self, db, fs, msg, auth) |
868 |
|
# self.auth = auth |
869 |
|
|
870 |
1 |
def check_conflict_on_new(self, session, indata): |
871 |
|
""" |
872 |
|
Check that the data to be inserted is valid |
873 |
|
|
874 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
875 |
|
:param indata: data to be inserted |
876 |
|
:return: None or raises EngineException |
877 |
|
""" |
878 |
1 |
project_name = indata.get("name") |
879 |
1 |
if is_valid_uuid(project_name): |
880 |
1 |
raise EngineException("project name '{}' cannot have an uuid format".format(project_name), |
881 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
882 |
|
|
883 |
1 |
project_list = self.auth.get_project_list(filter_q={"name": project_name}) |
884 |
|
|
885 |
1 |
if project_list: |
886 |
1 |
raise EngineException("project '{}' exists".format(project_name), HTTPStatus.CONFLICT) |
887 |
|
|
888 |
1 |
def check_conflict_on_edit(self, session, final_content, edit_content, _id): |
889 |
|
""" |
890 |
|
Check that the data to be edited/uploaded is valid |
891 |
|
|
892 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
893 |
|
:param final_content: data once modified |
894 |
|
:param edit_content: incremental data that contains the modifications to apply |
895 |
|
:param _id: internal _id |
896 |
|
:return: None or raises EngineException |
897 |
|
""" |
898 |
|
|
899 |
1 |
project_name = edit_content.get("name") |
900 |
1 |
if project_name != final_content["name"]: # It is a true renaming |
901 |
1 |
if is_valid_uuid(project_name): |
902 |
1 |
raise EngineException("project name '{}' cannot have an uuid format".format(project_name), |
903 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
904 |
|
|
905 |
1 |
if final_content["name"] == "admin": |
906 |
1 |
raise EngineException("You cannot rename project 'admin'", http_code=HTTPStatus.CONFLICT) |
907 |
|
|
908 |
|
# Check that project name is not used, regardless keystone already checks this |
909 |
1 |
if project_name and self.auth.get_project_list(filter_q={"name": project_name}): |
910 |
1 |
raise EngineException("project '{}' is already used".format(project_name), HTTPStatus.CONFLICT) |
911 |
1 |
return final_content |
912 |
|
|
913 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
914 |
|
""" |
915 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
916 |
|
|
917 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
918 |
|
:param _id: internal _id |
919 |
|
:param db_content: The database content of this item _id |
920 |
|
:return: None if ok or raises EngineException with the conflict |
921 |
|
""" |
922 |
|
|
923 |
1 |
def check_rw_projects(topic, title, id_field): |
924 |
1 |
for desc in self.db.get_list(topic): |
925 |
1 |
if _id in desc["_admin"]["projects_read"] + desc["_admin"]["projects_write"]: |
926 |
1 |
raise EngineException("Project '{}' ({}) is being used by {} '{}'" |
927 |
|
.format(db_content["name"], _id, title, desc[id_field]), HTTPStatus.CONFLICT) |
928 |
|
|
929 |
1 |
if _id in session["project_id"]: |
930 |
1 |
raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT) |
931 |
|
|
932 |
1 |
if db_content["name"] == "admin": |
933 |
1 |
raise EngineException("You cannot delete project 'admin'", http_code=HTTPStatus.CONFLICT) |
934 |
|
|
935 |
|
# If any user is using this project, raise CONFLICT exception |
936 |
1 |
if not session["force"]: |
937 |
1 |
for user in self.auth.get_user_list(): |
938 |
1 |
for prm in user.get("project_role_mappings"): |
939 |
1 |
if prm["project"] == _id: |
940 |
1 |
raise EngineException("Project '{}' ({}) is being used by user '{}'" |
941 |
|
.format(db_content["name"], _id, user["username"]), HTTPStatus.CONFLICT) |
942 |
|
|
943 |
|
# If any VNFD, NSD, NST, PDU, etc. is using this project, raise CONFLICT exception |
944 |
1 |
if not session["force"]: |
945 |
1 |
check_rw_projects("vnfds", "VNF Descriptor", "id") |
946 |
1 |
check_rw_projects("nsds", "NS Descriptor", "id") |
947 |
1 |
check_rw_projects("nsts", "NS Template", "id") |
948 |
1 |
check_rw_projects("pdus", "PDU Descriptor", "name") |
949 |
|
|
950 |
1 |
def new(self, rollback, session, indata=None, kwargs=None, headers=None): |
951 |
|
""" |
952 |
|
Creates a new entry into the authentication backend. |
953 |
|
|
954 |
|
NOTE: Overrides BaseTopic functionality because it doesn't require access to database. |
955 |
|
|
956 |
|
:param rollback: list to append created items at database in case a rollback may to be done |
957 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
958 |
|
:param indata: data to be inserted |
959 |
|
:param kwargs: used to override the indata descriptor |
960 |
|
:param headers: http request headers |
961 |
|
:return: _id: identity of the inserted data, operation _id (None) |
962 |
|
""" |
963 |
1 |
try: |
964 |
1 |
content = BaseTopic._remove_envelop(indata) |
965 |
|
|
966 |
|
# Override descriptor with query string kwargs |
967 |
1 |
BaseTopic._update_input_with_kwargs(content, kwargs) |
968 |
1 |
content = self._validate_input_new(content, session["force"]) |
969 |
1 |
self.check_conflict_on_new(session, content) |
970 |
1 |
self.format_on_new(content, project_id=session["project_id"], make_public=session["public"]) |
971 |
1 |
_id = self.auth.create_project(content) |
972 |
1 |
rollback.append({"topic": self.topic, "_id": _id}) |
973 |
1 |
self._send_msg("created", content, not_send_msg=None) |
974 |
1 |
return _id, None |
975 |
1 |
except ValidationError as e: |
976 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |
977 |
|
|
978 |
1 |
def show(self, session, _id, api_req=False): |
979 |
|
""" |
980 |
|
Get complete information on an topic |
981 |
|
|
982 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
983 |
|
:param _id: server internal id |
984 |
|
:param api_req: True if this call is serving an external API request. False if serving internal request. |
985 |
|
:return: dictionary, raise exception if not found. |
986 |
|
""" |
987 |
|
# Allow _id to be a name or uuid |
988 |
1 |
filter_q = {self.id_field(self.topic, _id): _id} |
989 |
|
# projects = self.auth.get_project_list(filter_q=filter_q) |
990 |
1 |
projects = self.list(session, filter_q) # To allow default filtering (Bug 853) |
991 |
1 |
if len(projects) == 1: |
992 |
1 |
return projects[0] |
993 |
0 |
elif len(projects) > 1: |
994 |
0 |
raise EngineException("Too many projects found", HTTPStatus.CONFLICT) |
995 |
|
else: |
996 |
0 |
raise EngineException("Project not found", HTTPStatus.NOT_FOUND) |
997 |
|
|
998 |
1 |
def list(self, session, filter_q=None, api_req=False): |
999 |
|
""" |
1000 |
|
Get a list of the topic that matches a filter |
1001 |
|
|
1002 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1003 |
|
:param filter_q: filter of data to be applied |
1004 |
|
:return: The list, it can be empty if no one match the filter. |
1005 |
|
""" |
1006 |
1 |
project_list = self.auth.get_project_list(filter_q) |
1007 |
1 |
if not session["allow_show_user_project_role"]: |
1008 |
|
# Bug 853 - Default filtering |
1009 |
0 |
user = self.auth.get_user(session["username"]) |
1010 |
0 |
projects = [prm["project"] for prm in user["project_role_mappings"]] |
1011 |
0 |
project_list = [proj for proj in project_list if proj["_id"] in projects] |
1012 |
1 |
return project_list |
1013 |
|
|
1014 |
1 |
def delete(self, session, _id, dry_run=False, not_send_msg=None): |
1015 |
|
""" |
1016 |
|
Delete item by its internal _id |
1017 |
|
|
1018 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1019 |
|
:param _id: server internal id |
1020 |
|
:param dry_run: make checking but do not delete |
1021 |
|
:param not_send_msg: To not send message (False) or store content (list) instead |
1022 |
|
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ... |
1023 |
|
""" |
1024 |
|
# Allow _id to be a name or uuid |
1025 |
1 |
proj = self.auth.get_project(_id) |
1026 |
1 |
pid = proj["_id"] |
1027 |
1 |
self.check_conflict_on_del(session, pid, proj) |
1028 |
1 |
if not dry_run: |
1029 |
1 |
v = self.auth.delete_project(pid) |
1030 |
1 |
self._send_msg("deleted", proj, not_send_msg=None) |
1031 |
1 |
return v |
1032 |
0 |
return None |
1033 |
|
|
1034 |
1 |
def edit(self, session, _id, indata=None, kwargs=None, content=None): |
1035 |
|
""" |
1036 |
|
Updates a project entry. |
1037 |
|
|
1038 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1039 |
|
:param _id: |
1040 |
|
:param indata: data to be inserted |
1041 |
|
:param kwargs: used to override the indata descriptor |
1042 |
|
:param content: |
1043 |
|
:return: _id: identity of the inserted data. |
1044 |
|
""" |
1045 |
1 |
indata = self._remove_envelop(indata) |
1046 |
|
|
1047 |
|
# Override descriptor with query string kwargs |
1048 |
1 |
if kwargs: |
1049 |
0 |
BaseTopic._update_input_with_kwargs(indata, kwargs) |
1050 |
1 |
try: |
1051 |
1 |
if not content: |
1052 |
1 |
content = self.show(session, _id) |
1053 |
1 |
indata = self._validate_input_edit(indata, content, force=session["force"]) |
1054 |
1 |
content = self.check_conflict_on_edit(session, content, indata, _id=_id) |
1055 |
1 |
self.format_on_edit(content, indata) |
1056 |
1 |
content_original = copy.deepcopy(content) |
1057 |
1 |
deep_update_rfc7396(content, indata) |
1058 |
1 |
self.auth.update_project(content["_id"], content) |
1059 |
1 |
proj_data = {"_id": _id, "changes": indata, "original": content_original} |
1060 |
1 |
self._send_msg("edited", proj_data, not_send_msg=None) |
1061 |
1 |
except ValidationError as e: |
1062 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |
1063 |
|
|
1064 |
|
|
1065 |
1 |
class RoleTopicAuth(BaseTopic): |
1066 |
1 |
topic = "roles" |
1067 |
1 |
topic_msg = None # "roles" |
1068 |
1 |
schema_new = roles_new_schema |
1069 |
1 |
schema_edit = roles_edit_schema |
1070 |
1 |
multiproject = False |
1071 |
|
|
1072 |
1 |
def __init__(self, db, fs, msg, auth): |
1073 |
1 |
BaseTopic.__init__(self, db, fs, msg, auth) |
1074 |
|
# self.auth = auth |
1075 |
1 |
self.operations = auth.role_permissions |
1076 |
|
# self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles" |
1077 |
|
|
1078 |
1 |
@staticmethod |
1079 |
|
def validate_role_definition(operations, role_definitions): |
1080 |
|
""" |
1081 |
|
Validates the role definition against the operations defined in |
1082 |
|
the resources to operations files. |
1083 |
|
|
1084 |
|
:param operations: operations list |
1085 |
|
:param role_definitions: role definition to test |
1086 |
|
:return: None if ok, raises ValidationError exception on error |
1087 |
|
""" |
1088 |
1 |
if not role_definitions.get("permissions"): |
1089 |
1 |
return |
1090 |
1 |
ignore_fields = ["admin", "default"] |
1091 |
1 |
for role_def in role_definitions["permissions"].keys(): |
1092 |
1 |
if role_def in ignore_fields: |
1093 |
0 |
continue |
1094 |
1 |
if role_def[-1] == ":": |
1095 |
0 |
raise ValidationError("Operation cannot end with ':'") |
1096 |
|
|
1097 |
1 |
match = next((op for op in operations if op == role_def or op.startswith(role_def + ":")), None) |
1098 |
|
|
1099 |
1 |
if not match: |
1100 |
1 |
raise ValidationError("Invalid permission '{}'".format(role_def)) |
1101 |
|
|
1102 |
1 |
def _validate_input_new(self, input, force=False): |
1103 |
|
""" |
1104 |
|
Validates input user content for a new entry. |
1105 |
|
|
1106 |
|
:param input: user input content for the new topic |
1107 |
|
:param force: may be used for being more tolerant |
1108 |
|
:return: The same input content, or a changed version of it. |
1109 |
|
""" |
1110 |
1 |
if self.schema_new: |
1111 |
1 |
validate_input(input, self.schema_new) |
1112 |
1 |
self.validate_role_definition(self.operations, input) |
1113 |
|
|
1114 |
1 |
return input |
1115 |
|
|
1116 |
1 |
def _validate_input_edit(self, input, content, force=False): |
1117 |
|
""" |
1118 |
|
Validates input user content for updating an entry. |
1119 |
|
|
1120 |
|
:param input: user input content for the new topic |
1121 |
|
:param force: may be used for being more tolerant |
1122 |
|
:return: The same input content, or a changed version of it. |
1123 |
|
""" |
1124 |
1 |
if self.schema_edit: |
1125 |
1 |
validate_input(input, self.schema_edit) |
1126 |
1 |
self.validate_role_definition(self.operations, input) |
1127 |
|
|
1128 |
1 |
return input |
1129 |
|
|
1130 |
1 |
def check_conflict_on_new(self, session, indata): |
1131 |
|
""" |
1132 |
|
Check that the data to be inserted is valid |
1133 |
|
|
1134 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1135 |
|
:param indata: data to be inserted |
1136 |
|
:return: None or raises EngineException |
1137 |
|
""" |
1138 |
|
# check name is not uuid |
1139 |
1 |
role_name = indata.get("name") |
1140 |
1 |
if is_valid_uuid(role_name): |
1141 |
1 |
raise EngineException("role name '{}' cannot have an uuid format".format(role_name), |
1142 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
1143 |
|
# check name not exists |
1144 |
1 |
name = indata["name"] |
1145 |
|
# if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): |
1146 |
1 |
if self.auth.get_role_list({"name": name}): |
1147 |
1 |
raise EngineException("role name '{}' exists".format(name), HTTPStatus.CONFLICT) |
1148 |
|
|
1149 |
1 |
def check_conflict_on_edit(self, session, final_content, edit_content, _id): |
1150 |
|
""" |
1151 |
|
Check that the data to be edited/uploaded is valid |
1152 |
|
|
1153 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1154 |
|
:param final_content: data once modified |
1155 |
|
:param edit_content: incremental data that contains the modifications to apply |
1156 |
|
:param _id: internal _id |
1157 |
|
:return: None or raises EngineException |
1158 |
|
""" |
1159 |
1 |
if "default" not in final_content["permissions"]: |
1160 |
1 |
final_content["permissions"]["default"] = False |
1161 |
1 |
if "admin" not in final_content["permissions"]: |
1162 |
1 |
final_content["permissions"]["admin"] = False |
1163 |
|
|
1164 |
|
# check name is not uuid |
1165 |
1 |
role_name = edit_content.get("name") |
1166 |
1 |
if is_valid_uuid(role_name): |
1167 |
1 |
raise EngineException("role name '{}' cannot have an uuid format".format(role_name), |
1168 |
|
HTTPStatus.UNPROCESSABLE_ENTITY) |
1169 |
|
|
1170 |
|
# Check renaming of admin roles |
1171 |
1 |
role = self.auth.get_role(_id) |
1172 |
1 |
if role["name"] in ["system_admin", "project_admin"]: |
1173 |
1 |
raise EngineException("You cannot rename role '{}'".format(role["name"]), http_code=HTTPStatus.FORBIDDEN) |
1174 |
|
|
1175 |
|
# check name not exists |
1176 |
1 |
if "name" in edit_content: |
1177 |
1 |
role_name = edit_content["name"] |
1178 |
|
# if self.db.get_one(self.topic, {"name":role_name,"_id.ne":_id}, fail_on_empty=False, fail_on_more=False): |
1179 |
1 |
roles = self.auth.get_role_list({"name": role_name}) |
1180 |
1 |
if roles and roles[0][BaseTopic.id_field("roles", _id)] != _id: |
1181 |
1 |
raise EngineException("role name '{}' exists".format(role_name), HTTPStatus.CONFLICT) |
1182 |
|
|
1183 |
1 |
return final_content |
1184 |
|
|
1185 |
1 |
def check_conflict_on_del(self, session, _id, db_content): |
1186 |
|
""" |
1187 |
|
Check if deletion can be done because of dependencies if it is not force. To override |
1188 |
|
|
1189 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1190 |
|
:param _id: internal _id |
1191 |
|
:param db_content: The database content of this item _id |
1192 |
|
:return: None if ok or raises EngineException with the conflict |
1193 |
|
""" |
1194 |
1 |
role = self.auth.get_role(_id) |
1195 |
1 |
if role["name"] in ["system_admin", "project_admin"]: |
1196 |
1 |
raise EngineException("You cannot delete role '{}'".format(role["name"]), http_code=HTTPStatus.FORBIDDEN) |
1197 |
|
|
1198 |
|
# If any user is using this role, raise CONFLICT exception |
1199 |
1 |
if not session["force"]: |
1200 |
1 |
for user in self.auth.get_user_list(): |
1201 |
1 |
for prm in user.get("project_role_mappings"): |
1202 |
1 |
if prm["role"] == _id: |
1203 |
1 |
raise EngineException("Role '{}' ({}) is being used by user '{}'" |
1204 |
|
.format(role["name"], _id, user["username"]), HTTPStatus.CONFLICT) |
1205 |
|
|
1206 |
1 |
@staticmethod |
1207 |
1 |
def format_on_new(content, project_id=None, make_public=False): # TO BE REMOVED ? |
1208 |
|
""" |
1209 |
|
Modifies content descriptor to include _admin |
1210 |
|
|
1211 |
|
:param content: descriptor to be modified |
1212 |
|
:param project_id: if included, it add project read/write permissions |
1213 |
|
:param make_public: if included it is generated as public for reading. |
1214 |
|
:return: None, but content is modified |
1215 |
|
""" |
1216 |
1 |
now = time() |
1217 |
1 |
if "_admin" not in content: |
1218 |
1 |
content["_admin"] = {} |
1219 |
1 |
if not content["_admin"].get("created"): |
1220 |
1 |
content["_admin"]["created"] = now |
1221 |
1 |
content["_admin"]["modified"] = now |
1222 |
|
|
1223 |
1 |
if "permissions" not in content: |
1224 |
0 |
content["permissions"] = {} |
1225 |
|
|
1226 |
1 |
if "default" not in content["permissions"]: |
1227 |
1 |
content["permissions"]["default"] = False |
1228 |
1 |
if "admin" not in content["permissions"]: |
1229 |
1 |
content["permissions"]["admin"] = False |
1230 |
|
|
1231 |
1 |
@staticmethod |
1232 |
|
def format_on_edit(final_content, edit_content): |
1233 |
|
""" |
1234 |
|
Modifies final_content descriptor to include the modified date. |
1235 |
|
|
1236 |
|
:param final_content: final descriptor generated |
1237 |
|
:param edit_content: alterations to be include |
1238 |
|
:return: None, but final_content is modified |
1239 |
|
""" |
1240 |
1 |
if "_admin" in final_content: |
1241 |
1 |
final_content["_admin"]["modified"] = time() |
1242 |
|
|
1243 |
1 |
if "permissions" not in final_content: |
1244 |
0 |
final_content["permissions"] = {} |
1245 |
|
|
1246 |
1 |
if "default" not in final_content["permissions"]: |
1247 |
0 |
final_content["permissions"]["default"] = False |
1248 |
1 |
if "admin" not in final_content["permissions"]: |
1249 |
0 |
final_content["permissions"]["admin"] = False |
1250 |
1 |
return None |
1251 |
|
|
1252 |
1 |
def show(self, session, _id, api_req=False): |
1253 |
|
""" |
1254 |
|
Get complete information on an topic |
1255 |
|
|
1256 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1257 |
|
:param _id: server internal id |
1258 |
|
:param api_req: True if this call is serving an external API request. False if serving internal request. |
1259 |
|
:return: dictionary, raise exception if not found. |
1260 |
|
""" |
1261 |
1 |
filter_q = {BaseTopic.id_field(self.topic, _id): _id} |
1262 |
|
# roles = self.auth.get_role_list(filter_q) |
1263 |
1 |
roles = self.list(session, filter_q) # To allow default filtering (Bug 853) |
1264 |
1 |
if not roles: |
1265 |
0 |
raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q)) |
1266 |
1 |
elif len(roles) > 1: |
1267 |
0 |
raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q)) |
1268 |
1 |
return roles[0] |
1269 |
|
|
1270 |
1 |
def list(self, session, filter_q=None, api_req=False): |
1271 |
|
""" |
1272 |
|
Get a list of the topic that matches a filter |
1273 |
|
|
1274 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1275 |
|
:param filter_q: filter of data to be applied |
1276 |
|
:return: The list, it can be empty if no one match the filter. |
1277 |
|
""" |
1278 |
1 |
role_list = self.auth.get_role_list(filter_q) |
1279 |
1 |
if not session["allow_show_user_project_role"]: |
1280 |
|
# Bug 853 - Default filtering |
1281 |
0 |
user = self.auth.get_user(session["username"]) |
1282 |
0 |
roles = [prm["role"] for prm in user["project_role_mappings"]] |
1283 |
0 |
role_list = [role for role in role_list if role["_id"] in roles] |
1284 |
1 |
return role_list |
1285 |
|
|
1286 |
1 |
def new(self, rollback, session, indata=None, kwargs=None, headers=None): |
1287 |
|
""" |
1288 |
|
Creates a new entry into database. |
1289 |
|
|
1290 |
|
:param rollback: list to append created items at database in case a rollback may to be done |
1291 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1292 |
|
:param indata: data to be inserted |
1293 |
|
:param kwargs: used to override the indata descriptor |
1294 |
|
:param headers: http request headers |
1295 |
|
:return: _id: identity of the inserted data, operation _id (None) |
1296 |
|
""" |
1297 |
1 |
try: |
1298 |
1 |
content = self._remove_envelop(indata) |
1299 |
|
|
1300 |
|
# Override descriptor with query string kwargs |
1301 |
1 |
self._update_input_with_kwargs(content, kwargs) |
1302 |
1 |
content = self._validate_input_new(content, session["force"]) |
1303 |
1 |
self.check_conflict_on_new(session, content) |
1304 |
1 |
self.format_on_new(content, project_id=session["project_id"], make_public=session["public"]) |
1305 |
|
# role_name = content["name"] |
1306 |
1 |
rid = self.auth.create_role(content) |
1307 |
1 |
content["_id"] = rid |
1308 |
|
# _id = self.db.create(self.topic, content) |
1309 |
1 |
rollback.append({"topic": self.topic, "_id": rid}) |
1310 |
|
# self._send_msg("created", content, not_send_msg=not_send_msg) |
1311 |
1 |
return rid, None |
1312 |
1 |
except ValidationError as e: |
1313 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |
1314 |
|
|
1315 |
1 |
def delete(self, session, _id, dry_run=False, not_send_msg=None): |
1316 |
|
""" |
1317 |
|
Delete item by its internal _id |
1318 |
|
|
1319 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1320 |
|
:param _id: server internal id |
1321 |
|
:param dry_run: make checking but do not delete |
1322 |
|
:param not_send_msg: To not send message (False) or store content (list) instead |
1323 |
|
:return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ... |
1324 |
|
""" |
1325 |
1 |
filter_q = {BaseTopic.id_field(self.topic, _id): _id} |
1326 |
1 |
roles = self.auth.get_role_list(filter_q) |
1327 |
1 |
if not roles: |
1328 |
0 |
raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q)) |
1329 |
1 |
elif len(roles) > 1: |
1330 |
0 |
raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q)) |
1331 |
1 |
rid = roles[0]["_id"] |
1332 |
1 |
self.check_conflict_on_del(session, rid, None) |
1333 |
|
# filter_q = {"_id": _id} |
1334 |
|
# filter_q = {BaseTopic.id_field(self.topic, _id): _id} # To allow role addressing by name |
1335 |
1 |
if not dry_run: |
1336 |
1 |
v = self.auth.delete_role(rid) |
1337 |
|
# v = self.db.del_one(self.topic, filter_q) |
1338 |
1 |
return v |
1339 |
0 |
return None |
1340 |
|
|
1341 |
1 |
def edit(self, session, _id, indata=None, kwargs=None, content=None): |
1342 |
|
""" |
1343 |
|
Updates a role entry. |
1344 |
|
|
1345 |
|
:param session: contains "username", "admin", "force", "public", "project_id", "set_project" |
1346 |
|
:param _id: |
1347 |
|
:param indata: data to be inserted |
1348 |
|
:param kwargs: used to override the indata descriptor |
1349 |
|
:param content: |
1350 |
|
:return: _id: identity of the inserted data. |
1351 |
|
""" |
1352 |
1 |
if kwargs: |
1353 |
0 |
self._update_input_with_kwargs(indata, kwargs) |
1354 |
1 |
try: |
1355 |
1 |
if not content: |
1356 |
1 |
content = self.show(session, _id) |
1357 |
1 |
indata = self._validate_input_edit(indata, content, force=session["force"]) |
1358 |
1 |
deep_update_rfc7396(content, indata) |
1359 |
1 |
content = self.check_conflict_on_edit(session, content, indata, _id=_id) |
1360 |
1 |
self.format_on_edit(content, indata) |
1361 |
1 |
self.auth.update_role(content) |
1362 |
1 |
except ValidationError as e: |
1363 |
1 |
raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) |