feature 5956. Split engine in several files
[osm/NBI.git] / osm_nbi / admin_topics.py
diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py
new file mode 100644 (file)
index 0000000..3b5da53
--- /dev/null
@@ -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 <alfonso.tiernosepulveda@telefonica.com>"
+
+
+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