X-Git-Url: https://osm.etsi.org/gitweb/?p=osm%2FNBI.git;a=blobdiff_plain;f=osm_nbi%2Fengine.py;h=53bf21989a5942face8a32165e5593061adec695;hp=1ffa6166b39f1836e61b3af8c36a9ae4172f5031;hb=56e698aea30098e7cfc0c5e3df9e771a4dd47f64;hpb=441dbbf62ad7b33b8f51cfa51bcaac37edbf40d5 diff --git a/osm_nbi/engine.py b/osm_nbi/engine.py index 1ffa616..53bf219 100644 --- a/osm_nbi/engine.py +++ b/osm_nbi/engine.py @@ -9,15 +9,14 @@ import tarfile import yaml import json import logging -from random import choice as random_choice from uuid import uuid4 from hashlib import sha256, md5 -from osm_common.dbbase import DbException +from osm_common.dbbase import DbException, deep_update from osm_common.fsbase import FsException from osm_common.msgbase import MsgException from http import HTTPStatus from time import time -from copy import deepcopy, copy +from copy import copy from validation import validate_input, ValidationError __author__ = "Alfonso Tierno " @@ -30,29 +29,6 @@ class EngineException(Exception): Exception.__init__(self, message) -def _deep_update(dict_to_change, dict_reference): - """ - Modifies one dictionary with the information of the other following https://tools.ietf.org/html/rfc7396 - :param dict_to_change: Ends modified - :param dict_reference: reference - :return: none - """ - for k in dict_reference: - if dict_reference[k] is None: # None->Anything - if k in dict_to_change: - del dict_to_change[k] - elif not isinstance(dict_reference[k], dict): # NotDict->Anything - dict_to_change[k] = dict_reference[k] - elif k not in dict_to_change: # Dict->Empty - dict_to_change[k] = deepcopy(dict_reference[k]) - _deep_update(dict_to_change[k], dict_reference[k]) - elif isinstance(dict_to_change[k], dict): # Dict->Dict - _deep_update(dict_to_change[k], dict_reference[k]) - else: # Dict->NotDict - dict_to_change[k] = deepcopy(dict_reference[k]) - _deep_update(dict_to_change[k], dict_reference[k]) - - def get_iterable(input): """ Returns an iterable, in case input is None it just returns an empty tuple @@ -123,102 +99,6 @@ class Engine(object): except (DbException, FsException, MsgException) as e: raise EngineException(str(e), http_code=e.http_code) - def authorize(self, token): - try: - if not token: - raise EngineException("Needed a token or Authorization http header", - http_code=HTTPStatus.UNAUTHORIZED) - if token not in self.tokens: - raise EngineException("Invalid token or Authorization http header", - http_code=HTTPStatus.UNAUTHORIZED) - session = self.tokens[token] - now = time() - if session["expires"] < now: - del self.tokens[token] - raise EngineException("Expired Token or Authorization http header", - http_code=HTTPStatus.UNAUTHORIZED) - return session - except EngineException: - if self.config["global"].get("test.user_not_authorized"): - return {"id": "fake-token-id-for-test", - "project_id": self.config["global"].get("test.project_not_authorized", "admin"), - "username": self.config["global"]["test.user_not_authorized"]} - else: - raise - - def new_token(self, session, indata, remote): - now = time() - user_content = None - - # Try using username/password - if indata.get("username"): - user_rows = self.db.get_list("users", {"username": indata.get("username")}) - user_content = None - if user_rows: - user_content = user_rows[0] - salt = user_content["_admin"]["salt"] - shadow_password = sha256(indata.get("password", "").encode('utf-8') + salt.encode('utf-8')).hexdigest() - if shadow_password != user_content["password"]: - user_content = None - if not user_content: - raise EngineException("Invalid username/password", http_code=HTTPStatus.UNAUTHORIZED) - elif session: - user_rows = self.db.get_list("users", {"username": session["username"]}) - if user_rows: - user_content = user_rows[0] - else: - raise EngineException("Invalid token", http_code=HTTPStatus.UNAUTHORIZED) - else: - raise EngineException("Provide credentials: username/password or Authorization Bearer token", - http_code=HTTPStatus.UNAUTHORIZED) - - token_id = ''.join(random_choice('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') - for _ in range(0, 32)) - if indata.get("project_id"): - project_id = indata.get("project_id") - if project_id not in user_content["projects"]: - raise EngineException("project {} not allowed for this user".format(project_id), - http_code=HTTPStatus.UNAUTHORIZED) - else: - project_id = user_content["projects"][0] - if project_id == "admin": - session_admin = True - else: - project = self.db.get_one("projects", {"_id": project_id}) - session_admin = project.get("admin", False) - new_session = {"issued_at": now, "expires": now+3600, - "_id": token_id, "id": token_id, "project_id": project_id, "username": user_content["username"], - "remote_port": remote.port, "admin": session_admin} - if remote.name: - new_session["remote_host"] = remote.name - elif remote.ip: - new_session["remote_host"] = remote.ip - - self.tokens[token_id] = new_session - return deepcopy(new_session) - - def get_token_list(self, session): - token_list = [] - for token_id, token_value in self.tokens.items(): - if token_value["username"] == session["username"]: - token_list.append(deepcopy(token_value)) - return token_list - - def get_token(self, session, token_id): - token_value = self.tokens.get(token_id) - if not token_value: - raise EngineException("token not found", http_code=HTTPStatus.NOT_FOUND) - if token_value["username"] != session["username"] and not session["admin"]: - raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) - return token_value - - def del_token(self, token_id): - try: - del self.tokens[token_id] - return "token '{}' deleted".format(token_id) - except KeyError: - raise EngineException("Token '{}' not found".format(token_id), http_code=HTTPStatus.NOT_FOUND) - @staticmethod def _remove_envelop(item, indata=None): """ @@ -254,20 +134,35 @@ class Engine(object): clean_indata = clean_indata['userDefinedData'] return clean_indata - def _check_dependencies_on_descriptor(self, session, item, descriptor_id): + def _check_project_dependencies(self, project_id): + """ + Check if a project can be deleted + :param session: + :param _id: + :return: + """ + # TODO Is it needed to check descriptors _admin.project_read/project_write?? + _filter = {"projects": project_id} + if self.db.get_list("users", _filter): + raise EngineException("There are users that uses this project", http_code=HTTPStatus.CONFLICT) + + def _check_dependencies_on_descriptor(self, session, item, descriptor_id, _id): """ Check that the descriptor to be deleded is not a dependency of others :param session: client session information :param item: can be vnfds, nsds - :param descriptor_id: id of descriptor to be deleted + :param descriptor_id: id (provided by client) of descriptor to be deleted + :param _id: internal id of descriptor to be deleted :return: None or raises exception """ if item == "vnfds": _filter = {"constituent-vnfd.ANYINDEX.vnfd-id-ref": descriptor_id} if self.get_item_list(session, "nsds", _filter): raise EngineException("There are nsd that depends on this VNFD", http_code=HTTPStatus.CONFLICT) + if self.get_item_list(session, "vnfrs", {"vnfd-id": _id}): + raise EngineException("There are vnfr that depends on this VNFD", http_code=HTTPStatus.CONFLICT) elif item == "nsds": - _filter = {"nsdId": descriptor_id} + _filter = {"nsdId": _id} if self.get_item_list(session, "nsrs", _filter): raise EngineException("There are nsr that depends on this NSD", http_code=HTTPStatus.CONFLICT) @@ -295,22 +190,35 @@ class Engine(object): raise EngineException("Descriptor error at nsdId='{}' references a non exist nsd".format(nsd_id), http_code=HTTPStatus.CONFLICT) + def _check_edition(self, session, item, indata, id, force=False): + if item == "users": + if indata.get("projects"): + if not session["admin"]: + raise EngineException("Needed admin privileges to edit user projects", HTTPStatus.UNAUTHORIZED) + if indata.get("password"): + # regenerate salt and encrypt password + salt = uuid4().hex + indata["_admin"] = {"salt": salt} + indata["password"] = sha256(indata["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest() + def _validate_new_data(self, session, item, indata, id=None, force=False): if item == "users": - if not indata.get("username"): - raise EngineException("missing 'username'", HTTPStatus.UNPROCESSABLE_ENTITY) - if not indata.get("password"): - raise EngineException("missing 'password'", HTTPStatus.UNPROCESSABLE_ENTITY) - if not indata.get("projects"): - raise EngineException("missing 'projects'", HTTPStatus.UNPROCESSABLE_ENTITY) # check username not exists - if self.db.get_one(item, {"username": indata.get("username")}, fail_on_empty=False, fail_on_more=False): + if not id and self.db.get_one(item, {"username": indata.get("username")}, fail_on_empty=False, + fail_on_more=False): raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT) + # check projects + if not force: + for p in indata["projects"]: + if p == "admin": + continue + if not self.db.get_one("projects", {"_id": p}, fail_on_empty=False, fail_on_more=False): + raise EngineException("project '{}' does not exists".format(p), HTTPStatus.CONFLICT) elif item == "projects": if not indata.get("name"): raise EngineException("missing 'name'") # check name not exists - if self.db.get_one(item, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): + if not id and self.db.get_one(item, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT) elif item in ("vnfds", "nsds"): filter = {"id": indata["id"]} @@ -451,10 +359,10 @@ class Engine(object): for in_vld in get_iterable(indata.get("vld")): for vldd in get_iterable(nsd.get("vld")): if in_vld["name"] == vldd["name"] or in_vld["name"] == vldd["id"]: - break - else: - raise EngineException("Invalid parameter vld:name='{}' is not present at nsd:vld".format( - in_vld["name"])) + break + else: + raise EngineException("Invalid parameter vld:name='{}' is not present at nsd:vld".format( + in_vld["name"])) def _format_new_data(self, session, item, indata): now = time() @@ -668,10 +576,11 @@ class Engine(object): "orchestration-progress": {}, # {"networks": {"active": 0, "total": 0}, "vms": {"active": 0, "total": 0}}, - "crete-time": now, + "create-time": now, "nsd-name-ref": nsd["name"], "operational-events": [], # "id", "timestamp", "description", "event", "nsd-ref": nsd["id"], + "nsdId": nsd["_id"], "instantiate_params": ns_request, "ns-instance-config-ref": nsr_id, "id": nsr_id, @@ -817,6 +726,9 @@ class Engine(object): :return: _id: identity of the inserted data. """ + if not session["admin"] and item in ("users", "projects"): + raise EngineException("Needed admin privileges to perform this operation", HTTPStatus.UNAUTHORIZED) + try: item_envelop = item if item in ("nsds", "vnfds"): @@ -825,7 +737,7 @@ class Engine(object): # Override descriptor with query string kwargs self._update_descriptor(content, kwargs) - if not indata and item not in ("nsds", "vnfds"): + if not content and item not in ("nsds", "vnfds"): raise EngineException("Empty payload") validate_input(content, item, new=True) @@ -834,7 +746,7 @@ class Engine(object): # in this case the input descriptor is not the data to be stored return self.new_nsr(rollback, session, ns_request=content) - self._validate_new_data(session, item_envelop, content, force) + self._validate_new_data(session, item_envelop, content, force=force) if item in ("nsds", "vnfds"): content = {"_admin": {"userDefinedData": content}} self._format_new_data(session, item, content) @@ -917,15 +829,15 @@ class Engine(object): # raise EngineException("Cannot get ns_instance '{}': {}".format(e), HTTPStatus.NOT_FOUND) def _add_read_filter(self, session, item, filter): - if session["project_id"] == "admin": # allows all + if session["admin"]: # allows all return filter if item == "users": filter["username"] = session["username"] - elif item in ("vnfds", "nsds", "nsrs"): + elif item in ("vnfds", "nsds", "nsrs", "vnfrs"): filter["_admin.projects_read.cont"] = ["ANY", session["project_id"]] def _add_delete_filter(self, session, item, filter): - if session["project_id"] != "admin" and item in ("users", "projects"): + if not session["admin"] and item in ("users", "projects"): raise EngineException("Only admin users can perform this task", http_code=HTTPStatus.FORBIDDEN) if item == "users": if filter.get("_id") == session["username"] or filter.get("username") == session["username"]: @@ -933,7 +845,7 @@ class Engine(object): elif item == "project": if filter.get("_id") == session["project_id"]: raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT) - elif item in ("vnfds", "nsds") and session["project_id"] != "admin": + elif item in ("vnfds", "nsds") and not session["admin"]: filter["_admin.projects_write.cont"] = ["ANY", session["project_id"]] def get_file(self, session, item, _id, path=None, accept_header=None): @@ -1049,7 +961,10 @@ class Engine(object): descriptor = self.get_item(session, item, _id) descriptor_id = descriptor.get("id") if descriptor_id: - self._check_dependencies_on_descriptor(session, item, descriptor_id) + self._check_dependencies_on_descriptor(session, item, descriptor_id, _id) + elif item == "projects": + if not force: + self._check_project_dependencies(_id) if item == "nsrs": nsr = self.db.get_one(item, filter) @@ -1163,8 +1078,9 @@ class Engine(object): except ValidationError as e: raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY) - _deep_update(content, indata) - self._validate_new_data(session, item, content, id, force) + self._check_edition(session, item, indata, id, force) + deep_update(content, indata) + self._validate_new_data(session, item, content, id=id, force=force) # self._format_new_data(session, item, content) self.db.replace(item, id, content) if item in ("vim_accounts", "sdns"): @@ -1187,6 +1103,8 @@ class Engine(object): :param force: If True avoid some dependence checks :return: dictionary, raise exception if not found. """ + if not session["admin"] and item == "projects": + raise EngineException("Needed admin privileges to perform this operation", HTTPStatus.UNAUTHORIZED) content = self.get_item(session, item, _id) return self._edit_item(session, item, _id, content, indata, kwargs, force)