X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_nbi%2Fadmin_topics.py;fp=osm_nbi%2Fadmin_topics.py;h=3b5da53290593e239a14ad176ca5e5489038182a;hb=b24258aa9716c1e375fde230a817f7c9faaf6c2a;hp=0000000000000000000000000000000000000000;hpb=cb83c941ebdf6a8807ffe3b1c3346c61e085b0bf;p=osm%2FNBI.git diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py new file mode 100644 index 0000000..3b5da53 --- /dev/null +++ b/osm_nbi/admin_topics.py @@ -0,0 +1,202 @@ +# -*- coding: utf-8 -*- + +# import logging +from uuid import uuid4 +from hashlib import sha256 +from http import HTTPStatus +from validation import user_new_schema, user_edit_schema, project_new_schema, project_edit_schema +from validation import vim_account_new_schema, vim_account_edit_schema, sdn_new_schema, sdn_edit_schema +from base_topic import BaseTopic, EngineException + +__author__ = "Alfonso Tierno " + + +class UserTopic(BaseTopic): + topic = "users" + topic_msg = "users" + schema_new = user_new_schema + schema_edit = user_edit_schema + + def __init__(self, db, fs, msg): + BaseTopic.__init__(self, db, fs, msg) + + @staticmethod + def _get_project_filter(session, write=False, show_all=True): + """ + Generates a filter dictionary for querying database users. + Current policy is admin can show all, non admin, only its own user. + :param session: contains "username", if user is "admin" and the working "project_id" + :param write: if operation is for reading (False) or writing (True) + :param show_all: if True it will show public or + :return: + """ + if session["admin"]: # allows all + return {} + else: + return {"username": session["username"]} + + def check_conflict_on_new(self, session, indata, force=False): + # check username not exists + if self.db.get_one(self.topic, {"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) + + def check_conflict_on_del(self, session, _id, force=False): + if _id == session["username"]: + raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT) + + @staticmethod + def format_on_new(content, project_id=None, make_public=False): + BaseTopic.format_on_new(content, make_public=False) + content["_id"] = content["username"] + salt = uuid4().hex + content["_admin"]["salt"] = salt + if content.get("password"): + content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest() + + @staticmethod + def format_on_edit(final_content, edit_content): + BaseTopic.format_on_edit(final_content, edit_content) + if edit_content.get("password"): + salt = uuid4().hex + final_content["_admin"]["salt"] = salt + final_content["password"] = sha256(edit_content["password"].encode('utf-8') + + salt.encode('utf-8')).hexdigest() + + def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None): + if not session["admin"]: + raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) + return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False): + if not session["admin"]: + raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) + return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force, + make_public=make_public) + + +class ProjectTopic(BaseTopic): + topic = "projects" + topic_msg = "projects" + schema_new = project_new_schema + schema_edit = project_edit_schema + + def __init__(self, db, fs, msg): + BaseTopic.__init__(self, db, fs, msg) + + def check_conflict_on_new(self, session, indata, force=False): + if not indata.get("name"): + raise EngineException("missing 'name'") + # check name not exists + if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False): + raise EngineException("name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT) + + @staticmethod + def format_on_new(content, project_id=None, make_public=False): + BaseTopic.format_on_new(content, None) + content["_id"] = content["name"] + + def check_conflict_on_del(self, session, _id, force=False): + if _id == session["project_id"]: + raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT) + if force: + return + _filter = {"projects": _id} + if self.db.get_list("users", _filter): + raise EngineException("There is some USER that contains this project", http_code=HTTPStatus.CONFLICT) + + def edit(self, session, _id, indata=None, kwargs=None, force=False, content=None): + if not session["admin"]: + raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) + return BaseTopic.edit(self, session, _id, indata=indata, kwargs=kwargs, force=force, content=content) + + def new(self, rollback, session, indata=None, kwargs=None, headers=None, force=False, make_public=False): + if not session["admin"]: + raise EngineException("needed admin privileges", http_code=HTTPStatus.UNAUTHORIZED) + return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers, force=force, + make_public=make_public) + + +class VimAccountTopic(BaseTopic): + topic = "vim_accounts" + topic_msg = "vim_account" + schema_new = vim_account_new_schema + schema_edit = vim_account_edit_schema + + def __init__(self, db, fs, msg): + BaseTopic.__init__(self, db, fs, msg) + + def check_conflict_on_new(self, session, indata, force=False): + self.check_unique_name(session, indata["name"], _id=None) + + def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): + if edit_content.get("name"): + self.check_unique_name(session, edit_content["name"], _id=_id) + + @staticmethod + def format_on_new(content, project_id=None, make_public=False): + BaseTopic.format_on_new(content, project_id=project_id, make_public=False) + content["_admin"]["operationalState"] = "PROCESSING" + + def delete(self, session, _id, force=False, dry_run=False): + """ + Delete item by its internal _id + :param session: contains the used login username, working project, and admin rights + :param _id: server internal id + :param force: indicates if deletion must be forced in case of conflict + :param dry_run: make checking but do not delete + :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ... + """ + # TODO add admin to filter, validate rights + if dry_run or force: # delete completely + return BaseTopic.delete(self, session, _id, force, dry_run) + else: # if not, sent to kafka + v = BaseTopic.delete(self, session, _id, force, dry_run=True) + self.db.set_one("vim_accounts", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status + self._send_msg("delete", {"_id": _id}) + return v # TODO indicate an offline operation to return 202 ACCEPTED + + +class SdnTopic(BaseTopic): + topic = "sdns" + topic_msg = "sdn" + schema_new = sdn_new_schema + schema_edit = sdn_edit_schema + + def __init__(self, db, fs, msg): + BaseTopic.__init__(self, db, fs, msg) + + def check_conflict_on_new(self, session, indata, force=False): + self.check_unique_name(session, indata["name"], _id=None) + + def check_conflict_on_edit(self, session, final_content, edit_content, _id, force=False): + if edit_content.get("name"): + self.check_unique_name(session, edit_content["name"], _id=_id) + + @staticmethod + def format_on_new(content, project_id=None, make_public=False): + BaseTopic.format_on_new(content, project_id=project_id, make_public=False) + content["_admin"]["operationalState"] = "PROCESSING" + + def delete(self, session, _id, force=False, dry_run=False): + """ + Delete item by its internal _id + :param session: contains the used login username, working project, and admin rights + :param _id: server internal id + :param force: indicates if deletion must be forced in case of conflict + :param dry_run: make checking but do not delete + :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ... + """ + if dry_run or force: # delete completely + return BaseTopic.delete(self, session, _id, force, dry_run) + else: # if not sent to kafka + v = BaseTopic.delete(self, session, _id, force, dry_run=True) + self.db.set_one("sdns", {"_id": _id}, {"_admin.to_delete": True}) # TODO change status + self._send_msg("delete", {"_id": _id}) + return v # TODO indicate an offline operation to return 202 ACCEPTED