blob: daeb2603bad546dabc6482c6925d45ac7349b3d9 [file] [log] [blame]
tiernob24258a2018-10-04 18:39:49 +02001# -*- coding: utf-8 -*-
2
tiernod125caf2018-11-22 16:05:54 +00003# 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
tiernob24258a2018-10-04 18:39:49 +020016# import logging
17from uuid import uuid4
18from hashlib import sha256
19from http import HTTPStatus
Eduardo Sousa5c01e192019-05-08 02:35:47 +010020from time import time
garciadeblas4568a372021-03-24 09:19:48 +010021from osm_nbi.validation import (
22 user_new_schema,
23 user_edit_schema,
24 project_new_schema,
25 project_edit_schema,
26 vim_account_new_schema,
27 vim_account_edit_schema,
28 sdn_new_schema,
29 sdn_edit_schema,
30 wim_account_new_schema,
31 wim_account_edit_schema,
32 roles_new_schema,
33 roles_edit_schema,
34 k8scluster_new_schema,
35 k8scluster_edit_schema,
36 k8srepo_new_schema,
37 k8srepo_edit_schema,
38 vca_new_schema,
39 vca_edit_schema,
Patricia Reinoso43eac1e2022-10-26 08:55:54 +000040 paas_new_schema,
41 paas_edit_schema,
garciadeblas4568a372021-03-24 09:19:48 +010042 osmrepo_new_schema,
43 osmrepo_edit_schema,
44 validate_input,
45 ValidationError,
46 is_valid_uuid,
47) # To check that User/Project Names don't look like UUIDs
tierno23acf402019-08-28 13:36:34 +000048from osm_nbi.base_topic import BaseTopic, EngineException
49from osm_nbi.authconn import AuthconnNotFoundException, AuthconnConflictException
delacruzramo01b15d32019-07-02 14:37:47 +020050from osm_common.dbbase import deep_update_rfc7396
agarwalat53471982020-10-08 13:06:14 +000051import copy
tiernob24258a2018-10-04 18:39:49 +020052
53__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
54
55
56class UserTopic(BaseTopic):
57 topic = "users"
58 topic_msg = "users"
59 schema_new = user_new_schema
60 schema_edit = user_edit_schema
tierno65ca36d2019-02-12 19:27:52 +010061 multiproject = False
tiernob24258a2018-10-04 18:39:49 +020062
delacruzramo32bab472019-09-13 12:24:22 +020063 def __init__(self, db, fs, msg, auth):
64 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +020065
66 @staticmethod
tierno65ca36d2019-02-12 19:27:52 +010067 def _get_project_filter(session):
tiernob24258a2018-10-04 18:39:49 +020068 """
69 Generates a filter dictionary for querying database users.
70 Current policy is admin can show all, non admin, only its own user.
tierno65ca36d2019-02-12 19:27:52 +010071 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +020072 :return:
73 """
74 if session["admin"]: # allows all
75 return {}
76 else:
77 return {"username": session["username"]}
78
tierno65ca36d2019-02-12 19:27:52 +010079 def check_conflict_on_new(self, session, indata):
tiernob24258a2018-10-04 18:39:49 +020080 # check username not exists
garciadeblas4568a372021-03-24 09:19:48 +010081 if self.db.get_one(
82 self.topic,
83 {"username": indata.get("username")},
84 fail_on_empty=False,
85 fail_on_more=False,
86 ):
87 raise EngineException(
88 "username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT
89 )
tiernob24258a2018-10-04 18:39:49 +020090 # check projects
tierno65ca36d2019-02-12 19:27:52 +010091 if not session["force"]:
delacruzramoceb8baf2019-06-21 14:25:38 +020092 for p in indata.get("projects") or []:
delacruzramoc061f562019-04-05 11:00:02 +020093 # To allow project addressing by Name as well as ID
garciadeblas4568a372021-03-24 09:19:48 +010094 if not self.db.get_one(
95 "projects",
96 {BaseTopic.id_field("projects", p): p},
97 fail_on_empty=False,
98 fail_on_more=False,
99 ):
100 raise EngineException(
101 "project '{}' does not exist".format(p), HTTPStatus.CONFLICT
102 )
tiernob24258a2018-10-04 18:39:49 +0200103
tiernob4844ab2019-05-23 08:42:12 +0000104 def check_conflict_on_del(self, session, _id, db_content):
105 """
106 Check if deletion can be done because of dependencies if it is not force. To override
107 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
108 :param _id: internal _id
109 :param db_content: The database content of this item _id
110 :return: None if ok or raises EngineException with the conflict
111 """
tiernob24258a2018-10-04 18:39:49 +0200112 if _id == session["username"]:
garciadeblas4568a372021-03-24 09:19:48 +0100113 raise EngineException(
114 "You cannot delete your own user", http_code=HTTPStatus.CONFLICT
115 )
tiernob24258a2018-10-04 18:39:49 +0200116
117 @staticmethod
118 def format_on_new(content, project_id=None, make_public=False):
119 BaseTopic.format_on_new(content, make_public=False)
delacruzramoc061f562019-04-05 11:00:02 +0200120 # Removed so that the UUID is kept, to allow User Name modification
121 # content["_id"] = content["username"]
tiernob24258a2018-10-04 18:39:49 +0200122 salt = uuid4().hex
123 content["_admin"]["salt"] = salt
124 if content.get("password"):
garciadeblas4568a372021-03-24 09:19:48 +0100125 content["password"] = sha256(
126 content["password"].encode("utf-8") + salt.encode("utf-8")
127 ).hexdigest()
Eduardo Sousa339ed782019-05-28 14:25:00 +0100128 if content.get("project_role_mappings"):
garciadeblas4568a372021-03-24 09:19:48 +0100129 projects = [
130 mapping["project"] for mapping in content["project_role_mappings"]
131 ]
Eduardo Sousa339ed782019-05-28 14:25:00 +0100132
133 if content.get("projects"):
134 content["projects"] += projects
135 else:
136 content["projects"] = projects
tiernob24258a2018-10-04 18:39:49 +0200137
138 @staticmethod
139 def format_on_edit(final_content, edit_content):
140 BaseTopic.format_on_edit(final_content, edit_content)
141 if edit_content.get("password"):
142 salt = uuid4().hex
143 final_content["_admin"]["salt"] = salt
garciadeblas4568a372021-03-24 09:19:48 +0100144 final_content["password"] = sha256(
145 edit_content["password"].encode("utf-8") + salt.encode("utf-8")
146 ).hexdigest()
tiernobdebce92019-07-01 15:36:49 +0000147 return None
tiernob24258a2018-10-04 18:39:49 +0200148
tierno65ca36d2019-02-12 19:27:52 +0100149 def edit(self, session, _id, indata=None, kwargs=None, content=None):
tiernob24258a2018-10-04 18:39:49 +0200150 if not session["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +0100151 raise EngineException(
152 "needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED
153 )
delacruzramoc061f562019-04-05 11:00:02 +0200154 # Names that look like UUIDs are not allowed
155 name = (indata if indata else kwargs).get("username")
156 if is_valid_uuid(name):
garciadeblas4568a372021-03-24 09:19:48 +0100157 raise EngineException(
158 "Usernames that look like UUIDs are not allowed",
159 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
160 )
161 return BaseTopic.edit(
162 self, session, _id, indata=indata, kwargs=kwargs, content=content
163 )
tiernob24258a2018-10-04 18:39:49 +0200164
tierno65ca36d2019-02-12 19:27:52 +0100165 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200166 if not session["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +0100167 raise EngineException(
168 "needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED
169 )
delacruzramoc061f562019-04-05 11:00:02 +0200170 # Names that look like UUIDs are not allowed
171 name = indata["username"] if indata else kwargs["username"]
172 if is_valid_uuid(name):
garciadeblas4568a372021-03-24 09:19:48 +0100173 raise EngineException(
174 "Usernames that look like UUIDs are not allowed",
175 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
176 )
177 return BaseTopic.new(
178 self, rollback, session, indata=indata, kwargs=kwargs, headers=headers
179 )
tiernob24258a2018-10-04 18:39:49 +0200180
181
182class ProjectTopic(BaseTopic):
183 topic = "projects"
184 topic_msg = "projects"
185 schema_new = project_new_schema
186 schema_edit = project_edit_schema
tierno65ca36d2019-02-12 19:27:52 +0100187 multiproject = False
tiernob24258a2018-10-04 18:39:49 +0200188
delacruzramo32bab472019-09-13 12:24:22 +0200189 def __init__(self, db, fs, msg, auth):
190 BaseTopic.__init__(self, db, fs, msg, auth)
tiernob24258a2018-10-04 18:39:49 +0200191
tierno65ca36d2019-02-12 19:27:52 +0100192 @staticmethod
193 def _get_project_filter(session):
194 """
195 Generates a filter dictionary for querying database users.
196 Current policy is admin can show all, non admin, only its own user.
197 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
198 :return:
199 """
200 if session["admin"]: # allows all
201 return {}
202 else:
203 return {"_id.cont": session["project_id"]}
204
205 def check_conflict_on_new(self, session, indata):
tiernob24258a2018-10-04 18:39:49 +0200206 if not indata.get("name"):
207 raise EngineException("missing 'name'")
208 # check name not exists
garciadeblas4568a372021-03-24 09:19:48 +0100209 if self.db.get_one(
210 self.topic,
211 {"name": indata.get("name")},
212 fail_on_empty=False,
213 fail_on_more=False,
214 ):
215 raise EngineException(
216 "name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT
217 )
tiernob24258a2018-10-04 18:39:49 +0200218
219 @staticmethod
220 def format_on_new(content, project_id=None, make_public=False):
221 BaseTopic.format_on_new(content, None)
delacruzramoc061f562019-04-05 11:00:02 +0200222 # Removed so that the UUID is kept, to allow Project Name modification
223 # content["_id"] = content["name"]
tiernob24258a2018-10-04 18:39:49 +0200224
tiernob4844ab2019-05-23 08:42:12 +0000225 def check_conflict_on_del(self, session, _id, db_content):
226 """
227 Check if deletion can be done because of dependencies if it is not force. To override
228 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
229 :param _id: internal _id
230 :param db_content: The database content of this item _id
231 :return: None if ok or raises EngineException with the conflict
232 """
tierno65ca36d2019-02-12 19:27:52 +0100233 if _id in session["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +0100234 raise EngineException(
235 "You cannot delete your own project", http_code=HTTPStatus.CONFLICT
236 )
tierno65ca36d2019-02-12 19:27:52 +0100237 if session["force"]:
tiernob24258a2018-10-04 18:39:49 +0200238 return
239 _filter = {"projects": _id}
240 if self.db.get_list("users", _filter):
garciadeblas4568a372021-03-24 09:19:48 +0100241 raise EngineException(
242 "There is some USER that contains this project",
243 http_code=HTTPStatus.CONFLICT,
244 )
tiernob24258a2018-10-04 18:39:49 +0200245
tierno65ca36d2019-02-12 19:27:52 +0100246 def edit(self, session, _id, indata=None, kwargs=None, content=None):
tiernob24258a2018-10-04 18:39:49 +0200247 if not session["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +0100248 raise EngineException(
249 "needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED
250 )
delacruzramoc061f562019-04-05 11:00:02 +0200251 # Names that look like UUIDs are not allowed
252 name = (indata if indata else kwargs).get("name")
253 if is_valid_uuid(name):
garciadeblas4568a372021-03-24 09:19:48 +0100254 raise EngineException(
255 "Project names that look like UUIDs are not allowed",
256 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
257 )
258 return BaseTopic.edit(
259 self, session, _id, indata=indata, kwargs=kwargs, content=content
260 )
tiernob24258a2018-10-04 18:39:49 +0200261
tierno65ca36d2019-02-12 19:27:52 +0100262 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
tiernob24258a2018-10-04 18:39:49 +0200263 if not session["admin"]:
garciadeblas4568a372021-03-24 09:19:48 +0100264 raise EngineException(
265 "needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED
266 )
delacruzramoc061f562019-04-05 11:00:02 +0200267 # Names that look like UUIDs are not allowed
268 name = indata["name"] if indata else kwargs["name"]
269 if is_valid_uuid(name):
garciadeblas4568a372021-03-24 09:19:48 +0100270 raise EngineException(
271 "Project names that look like UUIDs are not allowed",
272 http_code=HTTPStatus.UNPROCESSABLE_ENTITY,
273 )
274 return BaseTopic.new(
275 self, rollback, session, indata=indata, kwargs=kwargs, headers=headers
276 )
tiernob24258a2018-10-04 18:39:49 +0200277
278
tiernobdebce92019-07-01 15:36:49 +0000279class CommonVimWimSdn(BaseTopic):
280 """Common class for VIM, WIM SDN just to unify methods that are equal to all of them"""
garciadeblas4568a372021-03-24 09:19:48 +0100281
282 config_to_encrypt = (
283 {}
284 ) # what keys at config must be encrypted because contains passwords
285 password_to_encrypt = "" # key that contains a password
tiernob24258a2018-10-04 18:39:49 +0200286
tiernobdebce92019-07-01 15:36:49 +0000287 @staticmethod
288 def _create_operation(op_type, params=None):
289 """
290 Creates a dictionary with the information to an operation, similar to ns-lcm-op
291 :param op_type: can be create, edit, delete
292 :param params: operation input parameters
293 :return: new dictionary with
294 """
295 now = time()
296 return {
297 "lcmOperationType": op_type,
298 "operationState": "PROCESSING",
299 "startTime": now,
300 "statusEnteredTime": now,
301 "detailed-status": "",
302 "operationParams": params,
303 }
tiernob24258a2018-10-04 18:39:49 +0200304
tierno65ca36d2019-02-12 19:27:52 +0100305 def check_conflict_on_new(self, session, indata):
tiernobdebce92019-07-01 15:36:49 +0000306 """
307 Check that the data to be inserted is valid. It is checked that name is unique
308 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
309 :param indata: data to be inserted
310 :return: None or raises EngineException
311 """
tiernob24258a2018-10-04 18:39:49 +0200312 self.check_unique_name(session, indata["name"], _id=None)
313
tierno65ca36d2019-02-12 19:27:52 +0100314 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
tiernobdebce92019-07-01 15:36:49 +0000315 """
316 Check that the data to be edited/uploaded is valid. It is checked that name is unique
317 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
318 :param final_content: data once modified. This method may change it.
319 :param edit_content: incremental data that contains the modifications to apply
320 :param _id: internal _id
321 :return: None or raises EngineException
322 """
tierno65ca36d2019-02-12 19:27:52 +0100323 if not session["force"] and edit_content.get("name"):
tiernob24258a2018-10-04 18:39:49 +0200324 self.check_unique_name(session, edit_content["name"], _id=_id)
325
bravofb995ea22021-02-10 10:57:52 -0300326 return final_content
327
tiernobdebce92019-07-01 15:36:49 +0000328 def format_on_edit(self, final_content, edit_content):
329 """
330 Modifies final_content inserting admin information upon edition
331 :param final_content: final content to be stored at database
332 :param edit_content: user requested update content
333 :return: operation id
334 """
delacruzramofe598fe2019-10-23 18:25:11 +0200335 super().format_on_edit(final_content, edit_content)
tiernobdebce92019-07-01 15:36:49 +0000336
tierno92c1c7d2018-11-12 15:22:37 +0100337 # encrypt passwords
338 schema_version = final_content.get("schema_version")
339 if schema_version:
tiernobdebce92019-07-01 15:36:49 +0000340 if edit_content.get(self.password_to_encrypt):
garciadeblas4568a372021-03-24 09:19:48 +0100341 final_content[self.password_to_encrypt] = self.db.encrypt(
342 edit_content[self.password_to_encrypt],
343 schema_version=schema_version,
344 salt=final_content["_id"],
345 )
346 config_to_encrypt_keys = self.config_to_encrypt.get(
347 schema_version
348 ) or self.config_to_encrypt.get("default")
tierno468aa242019-08-01 16:35:04 +0000349 if edit_content.get("config") and config_to_encrypt_keys:
350
351 for p in config_to_encrypt_keys:
tierno92c1c7d2018-11-12 15:22:37 +0100352 if edit_content["config"].get(p):
garciadeblas4568a372021-03-24 09:19:48 +0100353 final_content["config"][p] = self.db.encrypt(
354 edit_content["config"][p],
355 schema_version=schema_version,
356 salt=final_content["_id"],
357 )
tiernobdebce92019-07-01 15:36:49 +0000358
359 # create edit operation
360 final_content["_admin"]["operations"].append(self._create_operation("edit"))
garciadeblas4568a372021-03-24 09:19:48 +0100361 return "{}:{}".format(
362 final_content["_id"], len(final_content["_admin"]["operations"]) - 1
363 )
tierno92c1c7d2018-11-12 15:22:37 +0100364
365 def format_on_new(self, content, project_id=None, make_public=False):
tiernobdebce92019-07-01 15:36:49 +0000366 """
367 Modifies content descriptor to include _admin and insert create operation
368 :param content: descriptor to be modified
369 :param project_id: if included, it add project read/write permissions. Can be None or a list
370 :param make_public: if included it is generated as public for reading.
371 :return: op_id: operation id on asynchronous operation, None otherwise. In addition content is modified
372 """
373 super().format_on_new(content, project_id=project_id, make_public=make_public)
tierno468aa242019-08-01 16:35:04 +0000374 content["schema_version"] = schema_version = "1.11"
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000375 self._encrypt_password(content, schema_version)
376 self._encrypt_config_fields(content, schema_version)
377 content["_admin"]["operationalState"] = "PROCESSING"
378 self._insert_create_operation(content)
379 return "{}:0".format(content["_id"])
tierno92c1c7d2018-11-12 15:22:37 +0100380
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000381 def _encrypt_password(self, content, schema_version):
tiernobdebce92019-07-01 15:36:49 +0000382 if content.get(self.password_to_encrypt):
garciadeblas4568a372021-03-24 09:19:48 +0100383 content[self.password_to_encrypt] = self.db.encrypt(
384 content[self.password_to_encrypt],
385 schema_version=schema_version,
386 salt=content["_id"],
387 )
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000388
389 def _encrypt_config_fields(self, content, schema_version):
garciadeblas4568a372021-03-24 09:19:48 +0100390 config_to_encrypt_keys = self.config_to_encrypt.get(
391 schema_version
392 ) or self.config_to_encrypt.get("default")
tierno468aa242019-08-01 16:35:04 +0000393 if content.get("config") and config_to_encrypt_keys:
394 for p in config_to_encrypt_keys:
tierno92c1c7d2018-11-12 15:22:37 +0100395 if content["config"].get(p):
garciadeblas4568a372021-03-24 09:19:48 +0100396 content["config"][p] = self.db.encrypt(
397 content["config"][p],
398 schema_version=schema_version,
399 salt=content["_id"],
400 )
tierno92c1c7d2018-11-12 15:22:37 +0100401
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000402 def _insert_create_operation(self, content):
tiernobdebce92019-07-01 15:36:49 +0000403 # create operation
404 content["_admin"]["operations"] = [self._create_operation("create")]
405 content["_admin"]["current_operation"] = None
vijay.rd1eaf982021-05-14 11:54:59 +0000406 # create Resource in Openstack based VIM
407 if content.get("vim_type"):
408 if content["vim_type"] == "openstack":
409 compute = {
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000410 "ram": {"total": None, "used": None},
411 "vcpus": {"total": None, "used": None},
412 "instances": {"total": None, "used": None},
vijay.rd1eaf982021-05-14 11:54:59 +0000413 }
414 storage = {
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000415 "volumes": {"total": None, "used": None},
416 "snapshots": {"total": None, "used": None},
417 "storage": {"total": None, "used": None},
vijay.rd1eaf982021-05-14 11:54:59 +0000418 }
419 network = {
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000420 "networks": {"total": None, "used": None},
421 "subnets": {"total": None, "used": None},
422 "floating_ips": {"total": None, "used": None},
vijay.rd1eaf982021-05-14 11:54:59 +0000423 }
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000424 content["resources"] = {
425 "compute": compute,
426 "storage": storage,
427 "network": network,
428 }
tiernobdebce92019-07-01 15:36:49 +0000429
tiernobee3bad2019-12-05 12:26:01 +0000430 def delete(self, session, _id, dry_run=False, not_send_msg=None):
tiernob24258a2018-10-04 18:39:49 +0200431 """
432 Delete item by its internal _id
tierno65ca36d2019-02-12 19:27:52 +0100433 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tiernob24258a2018-10-04 18:39:49 +0200434 :param _id: server internal id
tiernob24258a2018-10-04 18:39:49 +0200435 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +0000436 :param not_send_msg: To not send message (False) or store content (list) instead
tiernobdebce92019-07-01 15:36:49 +0000437 :return: operation id if it is ordered to delete. None otherwise
tiernob24258a2018-10-04 18:39:49 +0200438 """
tiernobdebce92019-07-01 15:36:49 +0000439
440 filter_q = self._get_project_filter(session)
441 filter_q["_id"] = _id
442 db_content = self.db.get_one(self.topic, filter_q)
443
444 self.check_conflict_on_del(session, _id, db_content)
445 if dry_run:
446 return None
447
tiernof5f2e3f2020-03-23 14:42:10 +0000448 # remove reference from project_read if there are more projects referencing it. If it last one,
449 # do not remove reference, but order via kafka to delete it
450 if session["project_id"] and session["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +0100451 other_projects_referencing = next(
452 (
453 p
454 for p in db_content["_admin"]["projects_read"]
455 if p not in session["project_id"] and p != "ANY"
456 ),
457 None,
458 )
tiernobdebce92019-07-01 15:36:49 +0000459
tiernof5f2e3f2020-03-23 14:42:10 +0000460 # check if there are projects referencing it (apart from ANY, that means, public)....
461 if other_projects_referencing:
462 # remove references but not delete
garciadeblas4568a372021-03-24 09:19:48 +0100463 update_dict_pull = {
464 "_admin.projects_read": session["project_id"],
465 "_admin.projects_write": session["project_id"],
466 }
467 self.db.set_one(
468 self.topic, filter_q, update_dict=None, pull_list=update_dict_pull
469 )
tiernof5f2e3f2020-03-23 14:42:10 +0000470 return None
471 else:
garciadeblas4568a372021-03-24 09:19:48 +0100472 can_write = next(
473 (
474 p
475 for p in db_content["_admin"]["projects_write"]
476 if p == "ANY" or p in session["project_id"]
477 ),
478 None,
479 )
tiernof5f2e3f2020-03-23 14:42:10 +0000480 if not can_write:
garciadeblas4568a372021-03-24 09:19:48 +0100481 raise EngineException(
482 "You have not write permission to delete it",
483 http_code=HTTPStatus.UNAUTHORIZED,
484 )
tiernobdebce92019-07-01 15:36:49 +0000485
486 # It must be deleted
487 if session["force"]:
488 self.db.del_one(self.topic, {"_id": _id})
489 op_id = None
garciadeblas4568a372021-03-24 09:19:48 +0100490 self._send_msg(
491 "deleted", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg
492 )
tiernobdebce92019-07-01 15:36:49 +0000493 else:
tiernof5f2e3f2020-03-23 14:42:10 +0000494 update_dict = {"_admin.to_delete": True}
garciadeblas4568a372021-03-24 09:19:48 +0100495 self.db.set_one(
496 self.topic,
497 {"_id": _id},
498 update_dict=update_dict,
499 push={"_admin.operations": self._create_operation("delete")},
500 )
tiernobdebce92019-07-01 15:36:49 +0000501 # the number of operations is the operation_id. db_content does not contains the new operation inserted,
502 # so the -1 is not needed
garciadeblas4568a372021-03-24 09:19:48 +0100503 op_id = "{}:{}".format(
504 db_content["_id"], len(db_content["_admin"]["operations"])
505 )
506 self._send_msg(
507 "delete", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg
508 )
tiernobdebce92019-07-01 15:36:49 +0000509 return op_id
tiernob24258a2018-10-04 18:39:49 +0200510
511
tiernobdebce92019-07-01 15:36:49 +0000512class VimAccountTopic(CommonVimWimSdn):
513 topic = "vim_accounts"
514 topic_msg = "vim_account"
515 schema_new = vim_account_new_schema
516 schema_edit = vim_account_edit_schema
517 multiproject = True
518 password_to_encrypt = "vim_password"
garciadeblas4568a372021-03-24 09:19:48 +0100519 config_to_encrypt = {
520 "1.1": ("admin_password", "nsx_password", "vcenter_password"),
521 "default": (
522 "admin_password",
523 "nsx_password",
524 "vcenter_password",
525 "vrops_password",
526 ),
527 }
tiernobdebce92019-07-01 15:36:49 +0000528
delacruzramo35c998b2019-11-21 11:09:16 +0100529 def check_conflict_on_del(self, session, _id, db_content):
530 """
531 Check if deletion can be done because of dependencies if it is not force. To override
532 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
533 :param _id: internal _id
534 :param db_content: The database content of this item _id
535 :return: None if ok or raises EngineException with the conflict
536 """
537 if session["force"]:
538 return
539 # check if used by VNF
540 if self.db.get_list("vnfrs", {"vim-account-id": _id}):
garciadeblas4568a372021-03-24 09:19:48 +0100541 raise EngineException(
542 "There is at least one VNF using this VIM account",
543 http_code=HTTPStatus.CONFLICT,
544 )
delacruzramo35c998b2019-11-21 11:09:16 +0100545 super().check_conflict_on_del(session, _id, db_content)
546
tiernobdebce92019-07-01 15:36:49 +0000547
548class WimAccountTopic(CommonVimWimSdn):
tierno55ba2e62018-12-11 17:22:22 +0000549 topic = "wim_accounts"
550 topic_msg = "wim_account"
551 schema_new = wim_account_new_schema
552 schema_edit = wim_account_edit_schema
tierno65ca36d2019-02-12 19:27:52 +0100553 multiproject = True
gifrerenom44f5ec12022-03-07 16:57:25 +0000554 password_to_encrypt = "password"
tierno468aa242019-08-01 16:35:04 +0000555 config_to_encrypt = {}
tierno55ba2e62018-12-11 17:22:22 +0000556
557
tiernobdebce92019-07-01 15:36:49 +0000558class SdnTopic(CommonVimWimSdn):
tiernob24258a2018-10-04 18:39:49 +0200559 topic = "sdns"
560 topic_msg = "sdn"
tierno6b02b052020-06-02 10:07:41 +0000561 quota_name = "sdn_controllers"
tiernob24258a2018-10-04 18:39:49 +0200562 schema_new = sdn_new_schema
563 schema_edit = sdn_edit_schema
tierno65ca36d2019-02-12 19:27:52 +0100564 multiproject = True
tiernobdebce92019-07-01 15:36:49 +0000565 password_to_encrypt = "password"
tierno468aa242019-08-01 16:35:04 +0000566 config_to_encrypt = {}
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100567
tierno7adaeb02019-12-17 16:46:12 +0000568 def _obtain_url(self, input, create):
569 if input.get("ip") or input.get("port"):
garciadeblas4568a372021-03-24 09:19:48 +0100570 if not input.get("ip") or not input.get("port") or input.get("url"):
571 raise ValidationError(
572 "You must provide both 'ip' and 'port' (deprecated); or just 'url' (prefered)"
573 )
574 input["url"] = "http://{}:{}/".format(input["ip"], input["port"])
tierno7adaeb02019-12-17 16:46:12 +0000575 del input["ip"]
576 del input["port"]
garciadeblas4568a372021-03-24 09:19:48 +0100577 elif create and not input.get("url"):
tierno7adaeb02019-12-17 16:46:12 +0000578 raise ValidationError("You must provide 'url'")
579 return input
580
581 def _validate_input_new(self, input, force=False):
582 input = super()._validate_input_new(input, force)
583 return self._obtain_url(input, True)
584
Frank Brydendeba68e2020-07-27 13:55:11 +0000585 def _validate_input_edit(self, input, content, force=False):
586 input = super()._validate_input_edit(input, content, force)
tierno7adaeb02019-12-17 16:46:12 +0000587 return self._obtain_url(input, False)
588
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100589
delacruzramofe598fe2019-10-23 18:25:11 +0200590class K8sClusterTopic(CommonVimWimSdn):
591 topic = "k8sclusters"
592 topic_msg = "k8scluster"
593 schema_new = k8scluster_new_schema
594 schema_edit = k8scluster_edit_schema
595 multiproject = True
596 password_to_encrypt = None
597 config_to_encrypt = {}
598
599 def format_on_new(self, content, project_id=None, make_public=False):
600 oid = super().format_on_new(content, project_id, make_public)
garciadeblas4568a372021-03-24 09:19:48 +0100601 self.db.encrypt_decrypt_fields(
602 content["credentials"],
603 "encrypt",
604 ["password", "secret"],
605 schema_version=content["schema_version"],
606 salt=content["_id"],
607 )
delacruzramoc2d5fc62020-02-05 11:50:21 +0000608 # Add Helm/Juju Repo lists
609 repos = {"helm-chart": [], "juju-bundle": []}
610 for proj in content["_admin"]["projects_read"]:
garciadeblas4568a372021-03-24 09:19:48 +0100611 if proj != "ANY":
612 for repo in self.db.get_list(
613 "k8srepos", {"_admin.projects_read": proj}
614 ):
delacruzramoc2d5fc62020-02-05 11:50:21 +0000615 if repo["_id"] not in repos[repo["type"]]:
616 repos[repo["type"]].append(repo["_id"])
617 for k in repos:
garciadeblas4568a372021-03-24 09:19:48 +0100618 content["_admin"][k.replace("-", "_") + "_repos"] = repos[k]
delacruzramofe598fe2019-10-23 18:25:11 +0200619 return oid
620
621 def format_on_edit(self, final_content, edit_content):
622 if final_content.get("schema_version") and edit_content.get("credentials"):
garciadeblas4568a372021-03-24 09:19:48 +0100623 self.db.encrypt_decrypt_fields(
624 edit_content["credentials"],
625 "encrypt",
626 ["password", "secret"],
627 schema_version=final_content["schema_version"],
628 salt=final_content["_id"],
629 )
630 deep_update_rfc7396(
631 final_content["credentials"], edit_content["credentials"]
632 )
delacruzramofe598fe2019-10-23 18:25:11 +0200633 oid = super().format_on_edit(final_content, edit_content)
634 return oid
635
delacruzramoc2d5fc62020-02-05 11:50:21 +0000636 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
garciadeblas4568a372021-03-24 09:19:48 +0100637 final_content = super(CommonVimWimSdn, self).check_conflict_on_edit(
638 session, final_content, edit_content, _id
639 )
640 final_content = super().check_conflict_on_edit(
641 session, final_content, edit_content, _id
642 )
delacruzramoc2d5fc62020-02-05 11:50:21 +0000643 # Update Helm/Juju Repo lists
644 repos = {"helm-chart": [], "juju-bundle": []}
645 for proj in session.get("set_project", []):
garciadeblas4568a372021-03-24 09:19:48 +0100646 if proj != "ANY":
647 for repo in self.db.get_list(
648 "k8srepos", {"_admin.projects_read": proj}
649 ):
delacruzramoc2d5fc62020-02-05 11:50:21 +0000650 if repo["_id"] not in repos[repo["type"]]:
651 repos[repo["type"]].append(repo["_id"])
652 for k in repos:
garciadeblas4568a372021-03-24 09:19:48 +0100653 rlist = k.replace("-", "_") + "_repos"
delacruzramoc2d5fc62020-02-05 11:50:21 +0000654 if rlist not in final_content["_admin"]:
655 final_content["_admin"][rlist] = []
656 final_content["_admin"][rlist] += repos[k]
bravofb995ea22021-02-10 10:57:52 -0300657 return final_content
delacruzramoc2d5fc62020-02-05 11:50:21 +0000658
tiernoe19707b2020-04-21 13:08:04 +0000659 def check_conflict_on_del(self, session, _id, db_content):
660 """
661 Check if deletion can be done because of dependencies if it is not force. To override
662 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
663 :param _id: internal _id
664 :param db_content: The database content of this item _id
665 :return: None if ok or raises EngineException with the conflict
666 """
667 if session["force"]:
668 return
669 # check if used by VNF
670 filter_q = {"kdur.k8s-cluster.id": _id}
671 if session["project_id"]:
672 filter_q["_admin.projects_read.cont"] = session["project_id"]
673 if self.db.get_list("vnfrs", filter_q):
garciadeblas4568a372021-03-24 09:19:48 +0100674 raise EngineException(
675 "There is at least one VNF using this k8scluster",
676 http_code=HTTPStatus.CONFLICT,
677 )
tiernoe19707b2020-04-21 13:08:04 +0000678 super().check_conflict_on_del(session, _id, db_content)
679
delacruzramofe598fe2019-10-23 18:25:11 +0200680
David Garciaecb41322021-03-31 19:10:46 +0200681class VcaTopic(CommonVimWimSdn):
682 topic = "vca"
683 topic_msg = "vca"
684 schema_new = vca_new_schema
685 schema_edit = vca_edit_schema
686 multiproject = True
687 password_to_encrypt = None
688
689 def format_on_new(self, content, project_id=None, make_public=False):
690 oid = super().format_on_new(content, project_id, make_public)
691 content["schema_version"] = schema_version = "1.11"
692 for key in ["secret", "cacert"]:
693 content[key] = self.db.encrypt(
garciadeblas4568a372021-03-24 09:19:48 +0100694 content[key], schema_version=schema_version, salt=content["_id"]
David Garciaecb41322021-03-31 19:10:46 +0200695 )
696 return oid
697
698 def format_on_edit(self, final_content, edit_content):
699 oid = super().format_on_edit(final_content, edit_content)
700 schema_version = final_content.get("schema_version")
701 for key in ["secret", "cacert"]:
702 if key in edit_content:
703 final_content[key] = self.db.encrypt(
704 edit_content[key],
705 schema_version=schema_version,
garciadeblas4568a372021-03-24 09:19:48 +0100706 salt=final_content["_id"],
David Garciaecb41322021-03-31 19:10:46 +0200707 )
708 return oid
709
710 def check_conflict_on_del(self, session, _id, db_content):
711 """
712 Check if deletion can be done because of dependencies if it is not force. To override
713 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
714 :param _id: internal _id
715 :param db_content: The database content of this item _id
716 :return: None if ok or raises EngineException with the conflict
717 """
718 if session["force"]:
719 return
720 # check if used by VNF
721 filter_q = {"vca": _id}
722 if session["project_id"]:
723 filter_q["_admin.projects_read.cont"] = session["project_id"]
724 if self.db.get_list("vim_accounts", filter_q):
garciadeblas4568a372021-03-24 09:19:48 +0100725 raise EngineException(
726 "There is at least one VIM account using this vca",
727 http_code=HTTPStatus.CONFLICT,
728 )
David Garciaecb41322021-03-31 19:10:46 +0200729 super().check_conflict_on_del(session, _id, db_content)
730
731
Patricia Reinoso43eac1e2022-10-26 08:55:54 +0000732class PaasTopic(CommonVimWimSdn):
733 topic = "paas"
734 topic_msg = "paas"
735 schema_new = paas_new_schema
736 schema_edit = paas_edit_schema
737 multiproject = True
738 password_to_encrypt = "secret"
739 config_to_encrypt = {}
740
741 def format_on_edit(self, final_content, edit_content):
742 oid = super().format_on_edit(final_content, edit_content)
743 final_content["_admin"]["operationalState"] = "PROCESSING"
744 final_content["_admin"]["detailed-status"] = "Editing"
745 return oid
746
747 def _check_if_used_by_ns(self):
748 pass
749
750 def check_conflict_on_del(self, session, _id, db_content):
751 """
752 Check if deletion can be done because of dependencies if it is not force.
753 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
754 :param _id: internal _id
755 :param db_content: The database content of this item _id
756 :return: None if ok or raises EngineException with the conflict
757 """
758 if session["force"]:
759 return
760 self._check_if_used_by_ns()
761
762 super().check_conflict_on_del(session, _id, db_content)
763
764
delacruzramofe598fe2019-10-23 18:25:11 +0200765class K8sRepoTopic(CommonVimWimSdn):
766 topic = "k8srepos"
767 topic_msg = "k8srepo"
768 schema_new = k8srepo_new_schema
769 schema_edit = k8srepo_edit_schema
770 multiproject = True
771 password_to_encrypt = None
772 config_to_encrypt = {}
773
delacruzramoc2d5fc62020-02-05 11:50:21 +0000774 def format_on_new(self, content, project_id=None, make_public=False):
775 oid = super().format_on_new(content, project_id, make_public)
776 # Update Helm/Juju Repo lists
garciadeblas4568a372021-03-24 09:19:48 +0100777 repo_list = content["type"].replace("-", "_") + "_repos"
delacruzramoc2d5fc62020-02-05 11:50:21 +0000778 for proj in content["_admin"]["projects_read"]:
garciadeblas4568a372021-03-24 09:19:48 +0100779 if proj != "ANY":
780 self.db.set_list(
781 "k8sclusters",
782 {
783 "_admin.projects_read": proj,
784 "_admin." + repo_list + ".ne": content["_id"],
785 },
786 {},
787 push={"_admin." + repo_list: content["_id"]},
788 )
delacruzramoc2d5fc62020-02-05 11:50:21 +0000789 return oid
790
791 def delete(self, session, _id, dry_run=False, not_send_msg=None):
792 type = self.db.get_one("k8srepos", {"_id": _id})["type"]
793 oid = super().delete(session, _id, dry_run, not_send_msg)
794 if oid:
795 # Remove from Helm/Juju Repo lists
garciadeblas4568a372021-03-24 09:19:48 +0100796 repo_list = type.replace("-", "_") + "_repos"
797 self.db.set_list(
798 "k8sclusters",
799 {"_admin." + repo_list: _id},
800 {},
801 pull={"_admin." + repo_list: _id},
802 )
delacruzramoc2d5fc62020-02-05 11:50:21 +0000803 return oid
804
delacruzramofe598fe2019-10-23 18:25:11 +0200805
Felipe Vicensb66b0412020-05-06 10:11:00 +0200806class OsmRepoTopic(BaseTopic):
807 topic = "osmrepos"
808 topic_msg = "osmrepos"
809 schema_new = osmrepo_new_schema
810 schema_edit = osmrepo_edit_schema
811 multiproject = True
812 # TODO: Implement user/password
813
814
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100815class UserTopicAuth(UserTopic):
tierno65ca36d2019-02-12 19:27:52 +0100816 # topic = "users"
agarwalat53471982020-10-08 13:06:14 +0000817 topic_msg = "users"
Eduardo Sousaa16a4fa2019-05-23 01:41:18 +0100818 schema_new = user_new_schema
819 schema_edit = user_edit_schema
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100820
821 def __init__(self, db, fs, msg, auth):
delacruzramo32bab472019-09-13 12:24:22 +0200822 UserTopic.__init__(self, db, fs, msg, auth)
823 # self.auth = auth
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100824
tierno65ca36d2019-02-12 19:27:52 +0100825 def check_conflict_on_new(self, session, indata):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100826 """
827 Check that the data to be inserted is valid
828
tierno65ca36d2019-02-12 19:27:52 +0100829 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100830 :param indata: data to be inserted
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100831 :return: None or raises EngineException
832 """
833 username = indata.get("username")
tiernocf042d32019-06-13 09:06:40 +0000834 if is_valid_uuid(username):
garciadeblas4568a372021-03-24 09:19:48 +0100835 raise EngineException(
836 "username '{}' cannot have a uuid format".format(username),
837 HTTPStatus.UNPROCESSABLE_ENTITY,
838 )
tiernocf042d32019-06-13 09:06:40 +0000839
840 # Check that username is not used, regardless keystone already checks this
841 if self.auth.get_user_list(filter_q={"name": username}):
garciadeblas4568a372021-03-24 09:19:48 +0100842 raise EngineException(
843 "username '{}' is already used".format(username), HTTPStatus.CONFLICT
844 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100845
Eduardo Sousa339ed782019-05-28 14:25:00 +0100846 if "projects" in indata.keys():
tierno701018c2019-06-25 11:13:14 +0000847 # convert to new format project_role_mappings
delacruzramo01b15d32019-07-02 14:37:47 +0200848 role = self.auth.get_role_list({"name": "project_admin"})
849 if not role:
850 role = self.auth.get_role_list()
851 if not role:
garciadeblas4568a372021-03-24 09:19:48 +0100852 raise AuthconnNotFoundException(
853 "Can't find default role for user '{}'".format(username)
854 )
delacruzramo01b15d32019-07-02 14:37:47 +0200855 rid = role[0]["_id"]
tierno701018c2019-06-25 11:13:14 +0000856 if not indata.get("project_role_mappings"):
857 indata["project_role_mappings"] = []
858 for project in indata["projects"]:
delacruzramo01b15d32019-07-02 14:37:47 +0200859 pid = self.auth.get_project(project)["_id"]
860 prm = {"project": pid, "role": rid}
861 if prm not in indata["project_role_mappings"]:
862 indata["project_role_mappings"].append(prm)
tierno701018c2019-06-25 11:13:14 +0000863 # raise EngineException("Format invalid: the keyword 'projects' is not allowed for keystone authentication",
864 # HTTPStatus.BAD_REQUEST)
Eduardo Sousa339ed782019-05-28 14:25:00 +0100865
tierno65ca36d2019-02-12 19:27:52 +0100866 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100867 """
868 Check that the data to be edited/uploaded is valid
869
tierno65ca36d2019-02-12 19:27:52 +0100870 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100871 :param final_content: data once modified
872 :param edit_content: incremental data that contains the modifications to apply
873 :param _id: internal _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100874 :return: None or raises EngineException
875 """
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100876
tiernocf042d32019-06-13 09:06:40 +0000877 if "username" in edit_content:
878 username = edit_content.get("username")
879 if is_valid_uuid(username):
garciadeblas4568a372021-03-24 09:19:48 +0100880 raise EngineException(
881 "username '{}' cannot have an uuid format".format(username),
882 HTTPStatus.UNPROCESSABLE_ENTITY,
883 )
tiernocf042d32019-06-13 09:06:40 +0000884
885 # Check that username is not used, regardless keystone already checks this
886 if self.auth.get_user_list(filter_q={"name": username}):
garciadeblas4568a372021-03-24 09:19:48 +0100887 raise EngineException(
888 "username '{}' is already used".format(username),
889 HTTPStatus.CONFLICT,
890 )
tiernocf042d32019-06-13 09:06:40 +0000891
892 if final_content["username"] == "admin":
893 for mapping in edit_content.get("remove_project_role_mappings", ()):
garciadeblas4568a372021-03-24 09:19:48 +0100894 if mapping["project"] == "admin" and mapping.get("role") in (
895 None,
896 "system_admin",
897 ):
tiernocf042d32019-06-13 09:06:40 +0000898 # TODO make this also available for project id and role id
garciadeblas4568a372021-03-24 09:19:48 +0100899 raise EngineException(
900 "You cannot remove system_admin role from admin user",
901 http_code=HTTPStatus.FORBIDDEN,
902 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100903
bravofb995ea22021-02-10 10:57:52 -0300904 return final_content
905
tiernob4844ab2019-05-23 08:42:12 +0000906 def check_conflict_on_del(self, session, _id, db_content):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100907 """
908 Check if deletion can be done because of dependencies if it is not force. To override
tierno65ca36d2019-02-12 19:27:52 +0100909 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100910 :param _id: internal _id
tiernob4844ab2019-05-23 08:42:12 +0000911 :param db_content: The database content of this item _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100912 :return: None if ok or raises EngineException with the conflict
913 """
tiernocf042d32019-06-13 09:06:40 +0000914 if db_content["username"] == session["username"]:
garciadeblas4568a372021-03-24 09:19:48 +0100915 raise EngineException(
916 "You cannot delete your own login user ", http_code=HTTPStatus.CONFLICT
917 )
delacruzramo01b15d32019-07-02 14:37:47 +0200918 # TODO: Check that user is not logged in ? How? (Would require listing current tokens)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100919
Eduardo Sousaa16a4fa2019-05-23 01:41:18 +0100920 @staticmethod
921 def format_on_show(content):
922 """
Eduardo Sousa44603902019-06-04 08:10:32 +0100923 Modifies the content of the role information to separate the role
Eduardo Sousaa16a4fa2019-05-23 01:41:18 +0100924 metadata from the role definition.
925 """
926 project_role_mappings = []
927
delacruzramo01b15d32019-07-02 14:37:47 +0200928 if "projects" in content:
929 for project in content["projects"]:
930 for role in project["roles"]:
garciadeblas4568a372021-03-24 09:19:48 +0100931 project_role_mappings.append(
932 {
933 "project": project["_id"],
934 "project_name": project["name"],
935 "role": role["_id"],
936 "role_name": role["name"],
937 }
938 )
delacruzramo01b15d32019-07-02 14:37:47 +0200939 del content["projects"]
Eduardo Sousaa16a4fa2019-05-23 01:41:18 +0100940 content["project_role_mappings"] = project_role_mappings
941
Eduardo Sousa0b1d61b2019-05-30 19:55:52 +0100942 return content
943
tierno65ca36d2019-02-12 19:27:52 +0100944 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100945 """
946 Creates a new entry into the authentication backend.
947
948 NOTE: Overrides BaseTopic functionality because it doesn't require access to database.
949
950 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +0100951 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100952 :param indata: data to be inserted
953 :param kwargs: used to override the indata descriptor
954 :param headers: http request headers
delacruzramo01b15d32019-07-02 14:37:47 +0200955 :return: _id: identity of the inserted data, operation _id (None)
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100956 """
957 try:
958 content = BaseTopic._remove_envelop(indata)
959
960 # Override descriptor with query string kwargs
961 BaseTopic._update_input_with_kwargs(content, kwargs)
tierno65ca36d2019-02-12 19:27:52 +0100962 content = self._validate_input_new(content, session["force"])
963 self.check_conflict_on_new(session, content)
tiernocf042d32019-06-13 09:06:40 +0000964 # self.format_on_new(content, session["project_id"], make_public=session["public"])
delacruzramo01b15d32019-07-02 14:37:47 +0200965 now = time()
966 content["_admin"] = {"created": now, "modified": now}
967 prms = []
968 for prm in content.get("project_role_mappings", []):
969 proj = self.auth.get_project(prm["project"], not session["force"])
970 role = self.auth.get_role(prm["role"], not session["force"])
971 pid = proj["_id"] if proj else None
972 rid = role["_id"] if role else None
973 prl = {"project": pid, "role": rid}
974 if prl not in prms:
975 prms.append(prl)
976 content["project_role_mappings"] = prms
977 # _id = self.auth.create_user(content["username"], content["password"])["_id"]
978 _id = self.auth.create_user(content)["_id"]
Eduardo Sousa44603902019-06-04 08:10:32 +0100979
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100980 rollback.append({"topic": self.topic, "_id": _id})
tiernocf042d32019-06-13 09:06:40 +0000981 # del content["password"]
agarwalat53471982020-10-08 13:06:14 +0000982 self._send_msg("created", content, not_send_msg=None)
delacruzramo01b15d32019-07-02 14:37:47 +0200983 return _id, None
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100984 except ValidationError as e:
985 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
986
K Sai Kiran57589552021-01-27 21:38:34 +0530987 def show(self, session, _id, filter_q=None, api_req=False):
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100988 """
989 Get complete information on an topic
990
tierno65ca36d2019-02-12 19:27:52 +0100991 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
tierno5ec768a2020-03-31 09:46:44 +0000992 :param _id: server internal id or username
K Sai Kiran57589552021-01-27 21:38:34 +0530993 :param filter_q: dict: query parameter
K Sai Kirand010e3e2020-08-28 15:11:48 +0530994 :param api_req: True if this call is serving an external API request. False if serving internal request.
Eduardo Sousa5c01e192019-05-08 02:35:47 +0100995 :return: dictionary, raise exception if not found.
996 """
tiernocf042d32019-06-13 09:06:40 +0000997 # Allow _id to be a name or uuid
tiernoad6d5332020-02-19 14:29:49 +0000998 filter_q = {"username": _id}
delacruzramo029405d2019-09-26 10:52:56 +0200999 # users = self.auth.get_user_list(filter_q)
garciadeblas4568a372021-03-24 09:19:48 +01001000 users = self.list(session, filter_q) # To allow default filtering (Bug 853)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001001 if len(users) == 1:
tierno1546f2a2019-08-20 15:38:11 +00001002 return users[0]
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001003 elif len(users) > 1:
garciadeblas4568a372021-03-24 09:19:48 +01001004 raise EngineException(
1005 "Too many users found for '{}'".format(_id), HTTPStatus.CONFLICT
1006 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001007 else:
garciadeblas4568a372021-03-24 09:19:48 +01001008 raise EngineException(
1009 "User '{}' not found".format(_id), HTTPStatus.NOT_FOUND
1010 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001011
tierno65ca36d2019-02-12 19:27:52 +01001012 def edit(self, session, _id, indata=None, kwargs=None, content=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001013 """
1014 Updates an user entry.
1015
tierno65ca36d2019-02-12 19:27:52 +01001016 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001017 :param _id:
1018 :param indata: data to be inserted
1019 :param kwargs: used to override the indata descriptor
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001020 :param content:
1021 :return: _id: identity of the inserted data.
1022 """
1023 indata = self._remove_envelop(indata)
1024
1025 # Override descriptor with query string kwargs
1026 if kwargs:
1027 BaseTopic._update_input_with_kwargs(indata, kwargs)
1028 try:
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001029 if not content:
1030 content = self.show(session, _id)
Frank Brydendeba68e2020-07-27 13:55:11 +00001031 indata = self._validate_input_edit(indata, content, force=session["force"])
bravofb995ea22021-02-10 10:57:52 -03001032 content = self.check_conflict_on_edit(session, content, indata, _id=_id)
tiernocf042d32019-06-13 09:06:40 +00001033 # self.format_on_edit(content, indata)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001034
garciadeblas4568a372021-03-24 09:19:48 +01001035 if not (
1036 "password" in indata
1037 or "username" in indata
1038 or indata.get("remove_project_role_mappings")
1039 or indata.get("add_project_role_mappings")
1040 or indata.get("project_role_mappings")
1041 or indata.get("projects")
1042 or indata.get("add_projects")
1043 ):
tiernocf042d32019-06-13 09:06:40 +00001044 return _id
garciadeblas4568a372021-03-24 09:19:48 +01001045 if indata.get("project_role_mappings") and (
1046 indata.get("remove_project_role_mappings")
1047 or indata.get("add_project_role_mappings")
1048 ):
1049 raise EngineException(
1050 "Option 'project_role_mappings' is incompatible with 'add_project_role_mappings"
1051 "' or 'remove_project_role_mappings'",
1052 http_code=HTTPStatus.BAD_REQUEST,
1053 )
Eduardo Sousa44603902019-06-04 08:10:32 +01001054
delacruzramo01b15d32019-07-02 14:37:47 +02001055 if indata.get("projects") or indata.get("add_projects"):
1056 role = self.auth.get_role_list({"name": "project_admin"})
1057 if not role:
1058 role = self.auth.get_role_list()
1059 if not role:
garciadeblas4568a372021-03-24 09:19:48 +01001060 raise AuthconnNotFoundException(
1061 "Can't find a default role for user '{}'".format(
1062 content["username"]
1063 )
1064 )
delacruzramo01b15d32019-07-02 14:37:47 +02001065 rid = role[0]["_id"]
1066 if "add_project_role_mappings" not in indata:
1067 indata["add_project_role_mappings"] = []
tierno1546f2a2019-08-20 15:38:11 +00001068 if "remove_project_role_mappings" not in indata:
1069 indata["remove_project_role_mappings"] = []
1070 if isinstance(indata.get("projects"), dict):
1071 # backward compatible
1072 for k, v in indata["projects"].items():
1073 if k.startswith("$") and v is None:
garciadeblas4568a372021-03-24 09:19:48 +01001074 indata["remove_project_role_mappings"].append(
1075 {"project": k[1:]}
1076 )
tierno1546f2a2019-08-20 15:38:11 +00001077 elif k.startswith("$+"):
garciadeblas4568a372021-03-24 09:19:48 +01001078 indata["add_project_role_mappings"].append(
1079 {"project": v, "role": rid}
1080 )
tierno1546f2a2019-08-20 15:38:11 +00001081 del indata["projects"]
delacruzramo01b15d32019-07-02 14:37:47 +02001082 for proj in indata.get("projects", []) + indata.get("add_projects", []):
garciadeblas4568a372021-03-24 09:19:48 +01001083 indata["add_project_role_mappings"].append(
1084 {"project": proj, "role": rid}
1085 )
delacruzramo01b15d32019-07-02 14:37:47 +02001086
1087 # user = self.show(session, _id) # Already in 'content'
1088 original_mapping = content["project_role_mappings"]
Eduardo Sousa44603902019-06-04 08:10:32 +01001089
tiernocf042d32019-06-13 09:06:40 +00001090 mappings_to_add = []
1091 mappings_to_remove = []
Eduardo Sousa44603902019-06-04 08:10:32 +01001092
tiernocf042d32019-06-13 09:06:40 +00001093 # remove
1094 for to_remove in indata.get("remove_project_role_mappings", ()):
1095 for mapping in original_mapping:
garciadeblas4568a372021-03-24 09:19:48 +01001096 if to_remove["project"] in (
1097 mapping["project"],
1098 mapping["project_name"],
1099 ):
1100 if not to_remove.get("role") or to_remove["role"] in (
1101 mapping["role"],
1102 mapping["role_name"],
1103 ):
tiernocf042d32019-06-13 09:06:40 +00001104 mappings_to_remove.append(mapping)
Eduardo Sousa44603902019-06-04 08:10:32 +01001105
tiernocf042d32019-06-13 09:06:40 +00001106 # add
1107 for to_add in indata.get("add_project_role_mappings", ()):
1108 for mapping in original_mapping:
garciadeblas4568a372021-03-24 09:19:48 +01001109 if to_add["project"] in (
1110 mapping["project"],
1111 mapping["project_name"],
Patricia Reinoso43eac1e2022-10-26 08:55:54 +00001112 ) and to_add["role"] in (mapping["role"], mapping["role_name"]):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001113
garciadeblas4568a372021-03-24 09:19:48 +01001114 if mapping in mappings_to_remove: # do not remove
tiernocf042d32019-06-13 09:06:40 +00001115 mappings_to_remove.remove(mapping)
1116 break # do not add, it is already at user
1117 else:
delacruzramo01b15d32019-07-02 14:37:47 +02001118 pid = self.auth.get_project(to_add["project"])["_id"]
1119 rid = self.auth.get_role(to_add["role"])["_id"]
1120 mappings_to_add.append({"project": pid, "role": rid})
tiernocf042d32019-06-13 09:06:40 +00001121
1122 # set
1123 if indata.get("project_role_mappings"):
1124 for to_set in indata["project_role_mappings"]:
1125 for mapping in original_mapping:
garciadeblas4568a372021-03-24 09:19:48 +01001126 if to_set["project"] in (
1127 mapping["project"],
1128 mapping["project_name"],
Patricia Reinoso43eac1e2022-10-26 08:55:54 +00001129 ) and to_set["role"] in (mapping["role"], mapping["role_name"]):
garciadeblas4568a372021-03-24 09:19:48 +01001130 if mapping in mappings_to_remove: # do not remove
tiernocf042d32019-06-13 09:06:40 +00001131 mappings_to_remove.remove(mapping)
1132 break # do not add, it is already at user
1133 else:
delacruzramo01b15d32019-07-02 14:37:47 +02001134 pid = self.auth.get_project(to_set["project"])["_id"]
1135 rid = self.auth.get_role(to_set["role"])["_id"]
1136 mappings_to_add.append({"project": pid, "role": rid})
tiernocf042d32019-06-13 09:06:40 +00001137 for mapping in original_mapping:
1138 for to_set in indata["project_role_mappings"]:
garciadeblas4568a372021-03-24 09:19:48 +01001139 if to_set["project"] in (
1140 mapping["project"],
1141 mapping["project_name"],
Patricia Reinoso43eac1e2022-10-26 08:55:54 +00001142 ) and to_set["role"] in (mapping["role"], mapping["role_name"]):
tiernocf042d32019-06-13 09:06:40 +00001143 break
1144 else:
1145 # delete
garciadeblas4568a372021-03-24 09:19:48 +01001146 if mapping not in mappings_to_remove: # do not remove
tiernocf042d32019-06-13 09:06:40 +00001147 mappings_to_remove.append(mapping)
1148
garciadeblas4568a372021-03-24 09:19:48 +01001149 self.auth.update_user(
1150 {
1151 "_id": _id,
1152 "username": indata.get("username"),
1153 "password": indata.get("password"),
selvi.ja9a1fc82022-04-04 06:54:30 +00001154 "old_password": indata.get("old_password"),
garciadeblas4568a372021-03-24 09:19:48 +01001155 "add_project_role_mappings": mappings_to_add,
1156 "remove_project_role_mappings": mappings_to_remove,
1157 }
1158 )
1159 data_to_send = {"_id": _id, "changes": indata}
agarwalat53471982020-10-08 13:06:14 +00001160 self._send_msg("edited", data_to_send, not_send_msg=None)
tiernocf042d32019-06-13 09:06:40 +00001161
delacruzramo01b15d32019-07-02 14:37:47 +02001162 # return _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001163 except ValidationError as e:
1164 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
1165
tiernoc4e07d02020-08-14 14:25:32 +00001166 def list(self, session, filter_q=None, api_req=False):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001167 """
1168 Get a list of the topic that matches a filter
tierno65ca36d2019-02-12 19:27:52 +01001169 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001170 :param filter_q: filter of data to be applied
K Sai Kirand010e3e2020-08-28 15:11:48 +05301171 :param api_req: True if this call is serving an external API request. False if serving internal request.
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001172 :return: The list, it can be empty if no one match the filter.
1173 """
delacruzramo029405d2019-09-26 10:52:56 +02001174 user_list = self.auth.get_user_list(filter_q)
1175 if not session["allow_show_user_project_role"]:
1176 # Bug 853 - Default filtering
garciadeblas4568a372021-03-24 09:19:48 +01001177 user_list = [
1178 usr for usr in user_list if usr["username"] == session["username"]
1179 ]
delacruzramo029405d2019-09-26 10:52:56 +02001180 return user_list
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001181
tiernobee3bad2019-12-05 12:26:01 +00001182 def delete(self, session, _id, dry_run=False, not_send_msg=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001183 """
1184 Delete item by its internal _id
1185
tierno65ca36d2019-02-12 19:27:52 +01001186 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001187 :param _id: server internal id
1188 :param force: indicates if deletion must be forced in case of conflict
1189 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +00001190 :param not_send_msg: To not send message (False) or store content (list) instead
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001191 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
1192 """
tiernocf042d32019-06-13 09:06:40 +00001193 # Allow _id to be a name or uuid
delacruzramo01b15d32019-07-02 14:37:47 +02001194 user = self.auth.get_user(_id)
1195 uid = user["_id"]
1196 self.check_conflict_on_del(session, uid, user)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001197 if not dry_run:
delacruzramo01b15d32019-07-02 14:37:47 +02001198 v = self.auth.delete_user(uid)
agarwalat53471982020-10-08 13:06:14 +00001199 self._send_msg("deleted", user, not_send_msg=not_send_msg)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001200 return v
1201 return None
1202
1203
1204class ProjectTopicAuth(ProjectTopic):
tierno65ca36d2019-02-12 19:27:52 +01001205 # topic = "projects"
agarwalat53471982020-10-08 13:06:14 +00001206 topic_msg = "project"
Eduardo Sousa44603902019-06-04 08:10:32 +01001207 schema_new = project_new_schema
1208 schema_edit = project_edit_schema
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001209
1210 def __init__(self, db, fs, msg, auth):
delacruzramo32bab472019-09-13 12:24:22 +02001211 ProjectTopic.__init__(self, db, fs, msg, auth)
1212 # self.auth = auth
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001213
tierno65ca36d2019-02-12 19:27:52 +01001214 def check_conflict_on_new(self, session, indata):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001215 """
1216 Check that the data to be inserted is valid
1217
tierno65ca36d2019-02-12 19:27:52 +01001218 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001219 :param indata: data to be inserted
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001220 :return: None or raises EngineException
1221 """
tiernocf042d32019-06-13 09:06:40 +00001222 project_name = indata.get("name")
1223 if is_valid_uuid(project_name):
garciadeblas4568a372021-03-24 09:19:48 +01001224 raise EngineException(
1225 "project name '{}' cannot have an uuid format".format(project_name),
1226 HTTPStatus.UNPROCESSABLE_ENTITY,
1227 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001228
tiernocf042d32019-06-13 09:06:40 +00001229 project_list = self.auth.get_project_list(filter_q={"name": project_name})
1230
1231 if project_list:
garciadeblas4568a372021-03-24 09:19:48 +01001232 raise EngineException(
1233 "project '{}' exists".format(project_name), HTTPStatus.CONFLICT
1234 )
tiernocf042d32019-06-13 09:06:40 +00001235
1236 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
1237 """
1238 Check that the data to be edited/uploaded is valid
1239
1240 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1241 :param final_content: data once modified
1242 :param edit_content: incremental data that contains the modifications to apply
1243 :param _id: internal _id
1244 :return: None or raises EngineException
1245 """
1246
1247 project_name = edit_content.get("name")
delacruzramo01b15d32019-07-02 14:37:47 +02001248 if project_name != final_content["name"]: # It is a true renaming
tiernocf042d32019-06-13 09:06:40 +00001249 if is_valid_uuid(project_name):
garciadeblas4568a372021-03-24 09:19:48 +01001250 raise EngineException(
1251 "project name '{}' cannot have an uuid format".format(project_name),
1252 HTTPStatus.UNPROCESSABLE_ENTITY,
1253 )
tiernocf042d32019-06-13 09:06:40 +00001254
delacruzramo01b15d32019-07-02 14:37:47 +02001255 if final_content["name"] == "admin":
garciadeblas4568a372021-03-24 09:19:48 +01001256 raise EngineException(
1257 "You cannot rename project 'admin'", http_code=HTTPStatus.CONFLICT
1258 )
delacruzramo01b15d32019-07-02 14:37:47 +02001259
tiernocf042d32019-06-13 09:06:40 +00001260 # Check that project name is not used, regardless keystone already checks this
garciadeblas4568a372021-03-24 09:19:48 +01001261 if project_name and self.auth.get_project_list(
1262 filter_q={"name": project_name}
1263 ):
1264 raise EngineException(
1265 "project '{}' is already used".format(project_name),
1266 HTTPStatus.CONFLICT,
1267 )
bravofb995ea22021-02-10 10:57:52 -03001268 return final_content
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001269
tiernob4844ab2019-05-23 08:42:12 +00001270 def check_conflict_on_del(self, session, _id, db_content):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001271 """
1272 Check if deletion can be done because of dependencies if it is not force. To override
1273
tierno65ca36d2019-02-12 19:27:52 +01001274 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001275 :param _id: internal _id
tiernob4844ab2019-05-23 08:42:12 +00001276 :param db_content: The database content of this item _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001277 :return: None if ok or raises EngineException with the conflict
1278 """
delacruzramo01b15d32019-07-02 14:37:47 +02001279
1280 def check_rw_projects(topic, title, id_field):
1281 for desc in self.db.get_list(topic):
garciadeblas4568a372021-03-24 09:19:48 +01001282 if (
1283 _id
1284 in desc["_admin"]["projects_read"]
1285 + desc["_admin"]["projects_write"]
1286 ):
1287 raise EngineException(
1288 "Project '{}' ({}) is being used by {} '{}'".format(
1289 db_content["name"], _id, title, desc[id_field]
1290 ),
1291 HTTPStatus.CONFLICT,
1292 )
delacruzramo01b15d32019-07-02 14:37:47 +02001293
1294 if _id in session["project_id"]:
garciadeblas4568a372021-03-24 09:19:48 +01001295 raise EngineException(
1296 "You cannot delete your own project", http_code=HTTPStatus.CONFLICT
1297 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001298
delacruzramo01b15d32019-07-02 14:37:47 +02001299 if db_content["name"] == "admin":
garciadeblas4568a372021-03-24 09:19:48 +01001300 raise EngineException(
1301 "You cannot delete project 'admin'", http_code=HTTPStatus.CONFLICT
1302 )
delacruzramo01b15d32019-07-02 14:37:47 +02001303
1304 # If any user is using this project, raise CONFLICT exception
1305 if not session["force"]:
1306 for user in self.auth.get_user_list():
tierno1546f2a2019-08-20 15:38:11 +00001307 for prm in user.get("project_role_mappings"):
1308 if prm["project"] == _id:
garciadeblas4568a372021-03-24 09:19:48 +01001309 raise EngineException(
1310 "Project '{}' ({}) is being used by user '{}'".format(
1311 db_content["name"], _id, user["username"]
1312 ),
1313 HTTPStatus.CONFLICT,
1314 )
delacruzramo01b15d32019-07-02 14:37:47 +02001315
1316 # If any VNFD, NSD, NST, PDU, etc. is using this project, raise CONFLICT exception
1317 if not session["force"]:
1318 check_rw_projects("vnfds", "VNF Descriptor", "id")
1319 check_rw_projects("nsds", "NS Descriptor", "id")
1320 check_rw_projects("nsts", "NS Template", "id")
1321 check_rw_projects("pdus", "PDU Descriptor", "name")
1322
tierno65ca36d2019-02-12 19:27:52 +01001323 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001324 """
1325 Creates a new entry into the authentication backend.
1326
1327 NOTE: Overrides BaseTopic functionality because it doesn't require access to database.
1328
1329 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +01001330 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001331 :param indata: data to be inserted
1332 :param kwargs: used to override the indata descriptor
1333 :param headers: http request headers
delacruzramo01b15d32019-07-02 14:37:47 +02001334 :return: _id: identity of the inserted data, operation _id (None)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001335 """
1336 try:
1337 content = BaseTopic._remove_envelop(indata)
1338
1339 # Override descriptor with query string kwargs
1340 BaseTopic._update_input_with_kwargs(content, kwargs)
tierno65ca36d2019-02-12 19:27:52 +01001341 content = self._validate_input_new(content, session["force"])
1342 self.check_conflict_on_new(session, content)
garciadeblas4568a372021-03-24 09:19:48 +01001343 self.format_on_new(
1344 content, project_id=session["project_id"], make_public=session["public"]
1345 )
delacruzramo01b15d32019-07-02 14:37:47 +02001346 _id = self.auth.create_project(content)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001347 rollback.append({"topic": self.topic, "_id": _id})
agarwalat53471982020-10-08 13:06:14 +00001348 self._send_msg("created", content, not_send_msg=None)
delacruzramo01b15d32019-07-02 14:37:47 +02001349 return _id, None
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001350 except ValidationError as e:
1351 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
1352
K Sai Kiran57589552021-01-27 21:38:34 +05301353 def show(self, session, _id, filter_q=None, api_req=False):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001354 """
1355 Get complete information on an topic
1356
tierno65ca36d2019-02-12 19:27:52 +01001357 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001358 :param _id: server internal id
K Sai Kiran57589552021-01-27 21:38:34 +05301359 :param filter_q: dict: query parameter
K Sai Kirand010e3e2020-08-28 15:11:48 +05301360 :param api_req: True if this call is serving an external API request. False if serving internal request.
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001361 :return: dictionary, raise exception if not found.
1362 """
tiernocf042d32019-06-13 09:06:40 +00001363 # Allow _id to be a name or uuid
1364 filter_q = {self.id_field(self.topic, _id): _id}
delacruzramo029405d2019-09-26 10:52:56 +02001365 # projects = self.auth.get_project_list(filter_q=filter_q)
garciadeblas4568a372021-03-24 09:19:48 +01001366 projects = self.list(session, filter_q) # To allow default filtering (Bug 853)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001367 if len(projects) == 1:
1368 return projects[0]
1369 elif len(projects) > 1:
1370 raise EngineException("Too many projects found", HTTPStatus.CONFLICT)
1371 else:
1372 raise EngineException("Project not found", HTTPStatus.NOT_FOUND)
1373
tiernoc4e07d02020-08-14 14:25:32 +00001374 def list(self, session, filter_q=None, api_req=False):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001375 """
1376 Get a list of the topic that matches a filter
1377
tierno65ca36d2019-02-12 19:27:52 +01001378 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001379 :param filter_q: filter of data to be applied
1380 :return: The list, it can be empty if no one match the filter.
1381 """
delacruzramo029405d2019-09-26 10:52:56 +02001382 project_list = self.auth.get_project_list(filter_q)
1383 if not session["allow_show_user_project_role"]:
1384 # Bug 853 - Default filtering
1385 user = self.auth.get_user(session["username"])
1386 projects = [prm["project"] for prm in user["project_role_mappings"]]
1387 project_list = [proj for proj in project_list if proj["_id"] in projects]
1388 return project_list
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001389
tiernobee3bad2019-12-05 12:26:01 +00001390 def delete(self, session, _id, dry_run=False, not_send_msg=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001391 """
1392 Delete item by its internal _id
1393
tierno65ca36d2019-02-12 19:27:52 +01001394 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001395 :param _id: server internal id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001396 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +00001397 :param not_send_msg: To not send message (False) or store content (list) instead
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001398 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
1399 """
tiernocf042d32019-06-13 09:06:40 +00001400 # Allow _id to be a name or uuid
delacruzramo01b15d32019-07-02 14:37:47 +02001401 proj = self.auth.get_project(_id)
1402 pid = proj["_id"]
1403 self.check_conflict_on_del(session, pid, proj)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001404 if not dry_run:
delacruzramo01b15d32019-07-02 14:37:47 +02001405 v = self.auth.delete_project(pid)
agarwalat53471982020-10-08 13:06:14 +00001406 self._send_msg("deleted", proj, not_send_msg=None)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001407 return v
1408 return None
1409
tierno4015b472019-06-10 13:57:29 +00001410 def edit(self, session, _id, indata=None, kwargs=None, content=None):
1411 """
1412 Updates a project entry.
1413
1414 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1415 :param _id:
1416 :param indata: data to be inserted
1417 :param kwargs: used to override the indata descriptor
1418 :param content:
1419 :return: _id: identity of the inserted data.
1420 """
1421 indata = self._remove_envelop(indata)
1422
1423 # Override descriptor with query string kwargs
1424 if kwargs:
1425 BaseTopic._update_input_with_kwargs(indata, kwargs)
1426 try:
tierno4015b472019-06-10 13:57:29 +00001427 if not content:
1428 content = self.show(session, _id)
Frank Brydendeba68e2020-07-27 13:55:11 +00001429 indata = self._validate_input_edit(indata, content, force=session["force"])
bravofb995ea22021-02-10 10:57:52 -03001430 content = self.check_conflict_on_edit(session, content, indata, _id=_id)
delacruzramo01b15d32019-07-02 14:37:47 +02001431 self.format_on_edit(content, indata)
agarwalat53471982020-10-08 13:06:14 +00001432 content_original = copy.deepcopy(content)
delacruzramo32bab472019-09-13 12:24:22 +02001433 deep_update_rfc7396(content, indata)
delacruzramo01b15d32019-07-02 14:37:47 +02001434 self.auth.update_project(content["_id"], content)
agarwalat53471982020-10-08 13:06:14 +00001435 proj_data = {"_id": _id, "changes": indata, "original": content_original}
1436 self._send_msg("edited", proj_data, not_send_msg=None)
tierno4015b472019-06-10 13:57:29 +00001437 except ValidationError as e:
1438 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
1439
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001440
1441class RoleTopicAuth(BaseTopic):
delacruzramoceb8baf2019-06-21 14:25:38 +02001442 topic = "roles"
garciadeblas4568a372021-03-24 09:19:48 +01001443 topic_msg = None # "roles"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001444 schema_new = roles_new_schema
1445 schema_edit = roles_edit_schema
tierno65ca36d2019-02-12 19:27:52 +01001446 multiproject = False
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001447
tierno9e87a7f2020-03-23 09:24:10 +00001448 def __init__(self, db, fs, msg, auth):
delacruzramo32bab472019-09-13 12:24:22 +02001449 BaseTopic.__init__(self, db, fs, msg, auth)
1450 # self.auth = auth
tierno9e87a7f2020-03-23 09:24:10 +00001451 self.operations = auth.role_permissions
delacruzramo01b15d32019-07-02 14:37:47 +02001452 # self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001453
1454 @staticmethod
1455 def validate_role_definition(operations, role_definitions):
1456 """
1457 Validates the role definition against the operations defined in
1458 the resources to operations files.
1459
1460 :param operations: operations list
1461 :param role_definitions: role definition to test
1462 :return: None if ok, raises ValidationError exception on error
1463 """
tierno1f029d82019-06-13 22:37:04 +00001464 if not role_definitions.get("permissions"):
1465 return
1466 ignore_fields = ["admin", "default"]
1467 for role_def in role_definitions["permissions"].keys():
Eduardo Sousa37de0912019-05-23 02:17:22 +01001468 if role_def in ignore_fields:
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001469 continue
Eduardo Sousac7689372019-06-04 16:01:46 +01001470 if role_def[-1] == ":":
tierno1f029d82019-06-13 22:37:04 +00001471 raise ValidationError("Operation cannot end with ':'")
Eduardo Sousac5a18892019-06-06 14:51:23 +01001472
garciadeblas4568a372021-03-24 09:19:48 +01001473 match = next(
1474 (
1475 op
1476 for op in operations
1477 if op == role_def or op.startswith(role_def + ":")
1478 ),
1479 None,
1480 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001481
tierno97639b42020-08-04 12:48:15 +00001482 if not match:
tierno1f029d82019-06-13 22:37:04 +00001483 raise ValidationError("Invalid permission '{}'".format(role_def))
Eduardo Sousa37de0912019-05-23 02:17:22 +01001484
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001485 def _validate_input_new(self, input, force=False):
1486 """
1487 Validates input user content for a new entry.
1488
1489 :param input: user input content for the new topic
1490 :param force: may be used for being more tolerant
1491 :return: The same input content, or a changed version of it.
1492 """
1493 if self.schema_new:
1494 validate_input(input, self.schema_new)
Eduardo Sousa37de0912019-05-23 02:17:22 +01001495 self.validate_role_definition(self.operations, input)
Eduardo Sousac4650362019-06-04 13:24:22 +01001496
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001497 return input
1498
Frank Brydendeba68e2020-07-27 13:55:11 +00001499 def _validate_input_edit(self, input, content, force=False):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001500 """
1501 Validates input user content for updating an entry.
1502
1503 :param input: user input content for the new topic
1504 :param force: may be used for being more tolerant
1505 :return: The same input content, or a changed version of it.
1506 """
1507 if self.schema_edit:
1508 validate_input(input, self.schema_edit)
Eduardo Sousa37de0912019-05-23 02:17:22 +01001509 self.validate_role_definition(self.operations, input)
Eduardo Sousac4650362019-06-04 13:24:22 +01001510
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001511 return input
1512
tierno65ca36d2019-02-12 19:27:52 +01001513 def check_conflict_on_new(self, session, indata):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001514 """
1515 Check that the data to be inserted is valid
1516
tierno65ca36d2019-02-12 19:27:52 +01001517 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001518 :param indata: data to be inserted
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001519 :return: None or raises EngineException
1520 """
delacruzramo79e40f42019-10-10 16:36:40 +02001521 # check name is not uuid
1522 role_name = indata.get("name")
1523 if is_valid_uuid(role_name):
garciadeblas4568a372021-03-24 09:19:48 +01001524 raise EngineException(
1525 "role name '{}' cannot have an uuid format".format(role_name),
1526 HTTPStatus.UNPROCESSABLE_ENTITY,
1527 )
tierno1f029d82019-06-13 22:37:04 +00001528 # check name not exists
delacruzramo01b15d32019-07-02 14:37:47 +02001529 name = indata["name"]
1530 # if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
1531 if self.auth.get_role_list({"name": name}):
garciadeblas4568a372021-03-24 09:19:48 +01001532 raise EngineException(
1533 "role name '{}' exists".format(name), HTTPStatus.CONFLICT
1534 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001535
tierno65ca36d2019-02-12 19:27:52 +01001536 def check_conflict_on_edit(self, session, final_content, edit_content, _id):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001537 """
1538 Check that the data to be edited/uploaded is valid
1539
tierno65ca36d2019-02-12 19:27:52 +01001540 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001541 :param final_content: data once modified
1542 :param edit_content: incremental data that contains the modifications to apply
1543 :param _id: internal _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001544 :return: None or raises EngineException
1545 """
tierno1f029d82019-06-13 22:37:04 +00001546 if "default" not in final_content["permissions"]:
1547 final_content["permissions"]["default"] = False
1548 if "admin" not in final_content["permissions"]:
1549 final_content["permissions"]["admin"] = False
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001550
delacruzramo79e40f42019-10-10 16:36:40 +02001551 # check name is not uuid
1552 role_name = edit_content.get("name")
1553 if is_valid_uuid(role_name):
garciadeblas4568a372021-03-24 09:19:48 +01001554 raise EngineException(
1555 "role name '{}' cannot have an uuid format".format(role_name),
1556 HTTPStatus.UNPROCESSABLE_ENTITY,
1557 )
delacruzramo79e40f42019-10-10 16:36:40 +02001558
1559 # Check renaming of admin roles
1560 role = self.auth.get_role(_id)
1561 if role["name"] in ["system_admin", "project_admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001562 raise EngineException(
1563 "You cannot rename role '{}'".format(role["name"]),
1564 http_code=HTTPStatus.FORBIDDEN,
1565 )
delacruzramo79e40f42019-10-10 16:36:40 +02001566
tierno1f029d82019-06-13 22:37:04 +00001567 # check name not exists
1568 if "name" in edit_content:
1569 role_name = edit_content["name"]
delacruzramo01b15d32019-07-02 14:37:47 +02001570 # if self.db.get_one(self.topic, {"name":role_name,"_id.ne":_id}, fail_on_empty=False, fail_on_more=False):
1571 roles = self.auth.get_role_list({"name": role_name})
1572 if roles and roles[0][BaseTopic.id_field("roles", _id)] != _id:
garciadeblas4568a372021-03-24 09:19:48 +01001573 raise EngineException(
1574 "role name '{}' exists".format(role_name), HTTPStatus.CONFLICT
1575 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001576
bravofb995ea22021-02-10 10:57:52 -03001577 return final_content
1578
tiernob4844ab2019-05-23 08:42:12 +00001579 def check_conflict_on_del(self, session, _id, db_content):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001580 """
1581 Check if deletion can be done because of dependencies if it is not force. To override
1582
tierno65ca36d2019-02-12 19:27:52 +01001583 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001584 :param _id: internal _id
tiernob4844ab2019-05-23 08:42:12 +00001585 :param db_content: The database content of this item _id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001586 :return: None if ok or raises EngineException with the conflict
1587 """
delacruzramo01b15d32019-07-02 14:37:47 +02001588 role = self.auth.get_role(_id)
1589 if role["name"] in ["system_admin", "project_admin"]:
garciadeblas4568a372021-03-24 09:19:48 +01001590 raise EngineException(
1591 "You cannot delete role '{}'".format(role["name"]),
1592 http_code=HTTPStatus.FORBIDDEN,
1593 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001594
delacruzramo01b15d32019-07-02 14:37:47 +02001595 # If any user is using this role, raise CONFLICT exception
delacruzramoad682a52019-12-10 16:26:34 +01001596 if not session["force"]:
1597 for user in self.auth.get_user_list():
1598 for prm in user.get("project_role_mappings"):
1599 if prm["role"] == _id:
garciadeblas4568a372021-03-24 09:19:48 +01001600 raise EngineException(
1601 "Role '{}' ({}) is being used by user '{}'".format(
1602 role["name"], _id, user["username"]
1603 ),
1604 HTTPStatus.CONFLICT,
1605 )
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001606
1607 @staticmethod
garciadeblas4568a372021-03-24 09:19:48 +01001608 def format_on_new(content, project_id=None, make_public=False): # TO BE REMOVED ?
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001609 """
1610 Modifies content descriptor to include _admin
1611
1612 :param content: descriptor to be modified
1613 :param project_id: if included, it add project read/write permissions
1614 :param make_public: if included it is generated as public for reading.
1615 :return: None, but content is modified
1616 """
1617 now = time()
1618 if "_admin" not in content:
1619 content["_admin"] = {}
1620 if not content["_admin"].get("created"):
1621 content["_admin"]["created"] = now
1622 content["_admin"]["modified"] = now
Eduardo Sousac4650362019-06-04 13:24:22 +01001623
tierno1f029d82019-06-13 22:37:04 +00001624 if "permissions" not in content:
1625 content["permissions"] = {}
Eduardo Sousac4650362019-06-04 13:24:22 +01001626
tierno1f029d82019-06-13 22:37:04 +00001627 if "default" not in content["permissions"]:
1628 content["permissions"]["default"] = False
1629 if "admin" not in content["permissions"]:
1630 content["permissions"]["admin"] = False
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001631
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001632 @staticmethod
1633 def format_on_edit(final_content, edit_content):
1634 """
1635 Modifies final_content descriptor to include the modified date.
1636
1637 :param final_content: final descriptor generated
1638 :param edit_content: alterations to be include
1639 :return: None, but final_content is modified
1640 """
delacruzramo01b15d32019-07-02 14:37:47 +02001641 if "_admin" in final_content:
1642 final_content["_admin"]["modified"] = time()
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001643
tierno1f029d82019-06-13 22:37:04 +00001644 if "permissions" not in final_content:
1645 final_content["permissions"] = {}
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001646
tierno1f029d82019-06-13 22:37:04 +00001647 if "default" not in final_content["permissions"]:
1648 final_content["permissions"]["default"] = False
1649 if "admin" not in final_content["permissions"]:
1650 final_content["permissions"]["admin"] = False
tiernobdebce92019-07-01 15:36:49 +00001651 return None
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001652
K Sai Kiran57589552021-01-27 21:38:34 +05301653 def show(self, session, _id, filter_q=None, api_req=False):
delacruzramo01b15d32019-07-02 14:37:47 +02001654 """
1655 Get complete information on an topic
Eduardo Sousac4650362019-06-04 13:24:22 +01001656
delacruzramo01b15d32019-07-02 14:37:47 +02001657 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1658 :param _id: server internal id
K Sai Kiran57589552021-01-27 21:38:34 +05301659 :param filter_q: dict: query parameter
K Sai Kirand010e3e2020-08-28 15:11:48 +05301660 :param api_req: True if this call is serving an external API request. False if serving internal request.
delacruzramo01b15d32019-07-02 14:37:47 +02001661 :return: dictionary, raise exception if not found.
1662 """
1663 filter_q = {BaseTopic.id_field(self.topic, _id): _id}
delacruzramo029405d2019-09-26 10:52:56 +02001664 # roles = self.auth.get_role_list(filter_q)
garciadeblas4568a372021-03-24 09:19:48 +01001665 roles = self.list(session, filter_q) # To allow default filtering (Bug 853)
delacruzramo01b15d32019-07-02 14:37:47 +02001666 if not roles:
garciadeblas4568a372021-03-24 09:19:48 +01001667 raise AuthconnNotFoundException(
1668 "Not found any role with filter {}".format(filter_q)
1669 )
delacruzramo01b15d32019-07-02 14:37:47 +02001670 elif len(roles) > 1:
garciadeblas4568a372021-03-24 09:19:48 +01001671 raise AuthconnConflictException(
1672 "Found more than one role with filter {}".format(filter_q)
1673 )
delacruzramo01b15d32019-07-02 14:37:47 +02001674 return roles[0]
1675
tiernoc4e07d02020-08-14 14:25:32 +00001676 def list(self, session, filter_q=None, api_req=False):
delacruzramo01b15d32019-07-02 14:37:47 +02001677 """
1678 Get a list of the topic that matches a filter
1679
1680 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
1681 :param filter_q: filter of data to be applied
1682 :return: The list, it can be empty if no one match the filter.
1683 """
delacruzramo029405d2019-09-26 10:52:56 +02001684 role_list = self.auth.get_role_list(filter_q)
1685 if not session["allow_show_user_project_role"]:
1686 # Bug 853 - Default filtering
1687 user = self.auth.get_user(session["username"])
1688 roles = [prm["role"] for prm in user["project_role_mappings"]]
1689 role_list = [role for role in role_list if role["_id"] in roles]
1690 return role_list
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001691
tierno65ca36d2019-02-12 19:27:52 +01001692 def new(self, rollback, session, indata=None, kwargs=None, headers=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001693 """
1694 Creates a new entry into database.
1695
1696 :param rollback: list to append created items at database in case a rollback may to be done
tierno65ca36d2019-02-12 19:27:52 +01001697 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001698 :param indata: data to be inserted
1699 :param kwargs: used to override the indata descriptor
1700 :param headers: http request headers
delacruzramo01b15d32019-07-02 14:37:47 +02001701 :return: _id: identity of the inserted data, operation _id (None)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001702 """
1703 try:
tierno1f029d82019-06-13 22:37:04 +00001704 content = self._remove_envelop(indata)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001705
1706 # Override descriptor with query string kwargs
tierno1f029d82019-06-13 22:37:04 +00001707 self._update_input_with_kwargs(content, kwargs)
tierno65ca36d2019-02-12 19:27:52 +01001708 content = self._validate_input_new(content, session["force"])
1709 self.check_conflict_on_new(session, content)
garciadeblas4568a372021-03-24 09:19:48 +01001710 self.format_on_new(
1711 content, project_id=session["project_id"], make_public=session["public"]
1712 )
delacruzramo01b15d32019-07-02 14:37:47 +02001713 # role_name = content["name"]
1714 rid = self.auth.create_role(content)
1715 content["_id"] = rid
1716 # _id = self.db.create(self.topic, content)
1717 rollback.append({"topic": self.topic, "_id": rid})
tiernobee3bad2019-12-05 12:26:01 +00001718 # self._send_msg("created", content, not_send_msg=not_send_msg)
delacruzramo01b15d32019-07-02 14:37:47 +02001719 return rid, None
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001720 except ValidationError as e:
1721 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
1722
tiernobee3bad2019-12-05 12:26:01 +00001723 def delete(self, session, _id, dry_run=False, not_send_msg=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001724 """
1725 Delete item by its internal _id
1726
tierno65ca36d2019-02-12 19:27:52 +01001727 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001728 :param _id: server internal id
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001729 :param dry_run: make checking but do not delete
tiernobee3bad2019-12-05 12:26:01 +00001730 :param not_send_msg: To not send message (False) or store content (list) instead
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001731 :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
1732 """
delacruzramo01b15d32019-07-02 14:37:47 +02001733 filter_q = {BaseTopic.id_field(self.topic, _id): _id}
1734 roles = self.auth.get_role_list(filter_q)
1735 if not roles:
garciadeblas4568a372021-03-24 09:19:48 +01001736 raise AuthconnNotFoundException(
1737 "Not found any role with filter {}".format(filter_q)
1738 )
delacruzramo01b15d32019-07-02 14:37:47 +02001739 elif len(roles) > 1:
garciadeblas4568a372021-03-24 09:19:48 +01001740 raise AuthconnConflictException(
1741 "Found more than one role with filter {}".format(filter_q)
1742 )
delacruzramo01b15d32019-07-02 14:37:47 +02001743 rid = roles[0]["_id"]
1744 self.check_conflict_on_del(session, rid, None)
delacruzramoceb8baf2019-06-21 14:25:38 +02001745 # filter_q = {"_id": _id}
delacruzramo01b15d32019-07-02 14:37:47 +02001746 # filter_q = {BaseTopic.id_field(self.topic, _id): _id} # To allow role addressing by name
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001747 if not dry_run:
delacruzramo01b15d32019-07-02 14:37:47 +02001748 v = self.auth.delete_role(rid)
1749 # v = self.db.del_one(self.topic, filter_q)
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001750 return v
1751 return None
1752
tierno65ca36d2019-02-12 19:27:52 +01001753 def edit(self, session, _id, indata=None, kwargs=None, content=None):
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001754 """
1755 Updates a role entry.
1756
tierno65ca36d2019-02-12 19:27:52 +01001757 :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001758 :param _id:
1759 :param indata: data to be inserted
1760 :param kwargs: used to override the indata descriptor
Eduardo Sousa5c01e192019-05-08 02:35:47 +01001761 :param content:
1762 :return: _id: identity of the inserted data.
1763 """
delacruzramo01b15d32019-07-02 14:37:47 +02001764 if kwargs:
1765 self._update_input_with_kwargs(indata, kwargs)
1766 try:
delacruzramo01b15d32019-07-02 14:37:47 +02001767 if not content:
1768 content = self.show(session, _id)
Frank Brydendeba68e2020-07-27 13:55:11 +00001769 indata = self._validate_input_edit(indata, content, force=session["force"])
delacruzramo01b15d32019-07-02 14:37:47 +02001770 deep_update_rfc7396(content, indata)
bravofb995ea22021-02-10 10:57:52 -03001771 content = self.check_conflict_on_edit(session, content, indata, _id=_id)
delacruzramo01b15d32019-07-02 14:37:47 +02001772 self.format_on_edit(content, indata)
1773 self.auth.update_role(content)
1774 except ValidationError as e:
1775 raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)