avoid k8scluster deletion when in use
[osm/NBI.git] / osm_nbi / admin_topics.py
index d894a09..bc13c9f 100644 (file)
@@ -18,13 +18,14 @@ from uuid import uuid4
 from hashlib import sha256
 from http import HTTPStatus
 from time import time
 from hashlib import sha256
 from http import HTTPStatus
 from time import time
-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 validation import wim_account_new_schema, wim_account_edit_schema, roles_new_schema, roles_edit_schema
-from validation import validate_input
-from validation import ValidationError
-from validation import is_valid_uuid    # To check that User/Project Names don't look like UUIDs
-from base_topic import BaseTopic, EngineException
+from osm_nbi.validation import user_new_schema, user_edit_schema, project_new_schema, project_edit_schema, \
+    vim_account_new_schema, vim_account_edit_schema, sdn_new_schema, sdn_edit_schema, \
+    wim_account_new_schema, wim_account_edit_schema, roles_new_schema, roles_edit_schema, \
+    k8scluster_new_schema, k8scluster_edit_schema, k8srepo_new_schema, k8srepo_edit_schema, \
+    validate_input, ValidationError, is_valid_uuid    # To check that User/Project Names don't look like UUIDs
+from osm_nbi.base_topic import BaseTopic, EngineException
+from osm_nbi.authconn import AuthconnNotFoundException, AuthconnConflictException
+from osm_common.dbbase import deep_update_rfc7396
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
@@ -36,8 +37,8 @@ class UserTopic(BaseTopic):
     schema_edit = user_edit_schema
     multiproject = False
 
     schema_edit = user_edit_schema
     multiproject = False
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _get_project_filter(session):
 
     @staticmethod
     def _get_project_filter(session):
@@ -58,13 +59,20 @@ class UserTopic(BaseTopic):
             raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
         # check projects
         if not session["force"]:
             raise EngineException("username '{}' exists".format(indata["username"]), HTTPStatus.CONFLICT)
         # check projects
         if not session["force"]:
-            for p in indata.get("projects"):
+            for p in indata.get("projects") or []:
                 # To allow project addressing by Name as well as ID
                 if not self.db.get_one("projects", {BaseTopic.id_field("projects", p): p}, fail_on_empty=False,
                                        fail_on_more=False):
                     raise EngineException("project '{}' does not exist".format(p), HTTPStatus.CONFLICT)
 
                 # To allow project addressing by Name as well as ID
                 if not self.db.get_one("projects", {BaseTopic.id_field("projects", p): p}, fail_on_empty=False,
                                        fail_on_more=False):
                     raise EngineException("project '{}' does not exist".format(p), HTTPStatus.CONFLICT)
 
-    def check_conflict_on_del(self, session, _id):
+    def check_conflict_on_del(self, session, _id, db_content):
+        """
+        Check if deletion can be done because of dependencies if it is not force. To override
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: internal _id
+        :param db_content: The database content of this item _id
+        :return: None if ok or raises EngineException with the conflict
+        """
         if _id == session["username"]:
             raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT)
 
         if _id == session["username"]:
             raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT)
 
@@ -77,6 +85,13 @@ class UserTopic(BaseTopic):
         content["_admin"]["salt"] = salt
         if content.get("password"):
             content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
         content["_admin"]["salt"] = salt
         if content.get("password"):
             content["password"] = sha256(content["password"].encode('utf-8') + salt.encode('utf-8')).hexdigest()
+        if content.get("project_role_mappings"):
+            projects = [mapping["project"] for mapping in content["project_role_mappings"]]
+
+            if content.get("projects"):
+                content["projects"] += projects
+            else:
+                content["projects"] = projects
 
     @staticmethod
     def format_on_edit(final_content, edit_content):
 
     @staticmethod
     def format_on_edit(final_content, edit_content):
@@ -86,6 +101,7 @@ class UserTopic(BaseTopic):
             final_content["_admin"]["salt"] = salt
             final_content["password"] = sha256(edit_content["password"].encode('utf-8') +
                                                salt.encode('utf-8')).hexdigest()
             final_content["_admin"]["salt"] = salt
             final_content["password"] = sha256(edit_content["password"].encode('utf-8') +
                                                salt.encode('utf-8')).hexdigest()
+        return None
 
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         if not session["admin"]:
 
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         if not session["admin"]:
@@ -115,8 +131,8 @@ class ProjectTopic(BaseTopic):
     schema_edit = project_edit_schema
     multiproject = False
 
     schema_edit = project_edit_schema
     multiproject = False
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
 
     @staticmethod
     def _get_project_filter(session):
 
     @staticmethod
     def _get_project_filter(session):
@@ -144,7 +160,14 @@ class ProjectTopic(BaseTopic):
         # Removed so that the UUID is kept, to allow Project Name modification
         # content["_id"] = content["name"]
 
         # Removed so that the UUID is kept, to allow Project Name modification
         # content["_id"] = content["name"]
 
-    def check_conflict_on_del(self, session, _id):
+    def check_conflict_on_del(self, session, _id, db_content):
+        """
+        Check if deletion can be done because of dependencies if it is not force. To override
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id: internal _id
+        :param db_content: The database content of this item _id
+        :return: None if ok or raises EngineException with the conflict
+        """
         if _id in session["project_id"]:
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
         if session["force"]:
         if _id in session["project_id"]:
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
         if session["force"]:
@@ -174,193 +197,338 @@ class ProjectTopic(BaseTopic):
         return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers)
 
 
         return BaseTopic.new(self, rollback, session, indata=indata, kwargs=kwargs, headers=headers)
 
 
-class VimAccountTopic(BaseTopic):
-    topic = "vim_accounts"
-    topic_msg = "vim_account"
-    schema_new = vim_account_new_schema
-    schema_edit = vim_account_edit_schema
-    vim_config_encrypted = ("admin_password", "nsx_password", "vcenter_password")
-    multiproject = True
+class CommonVimWimSdn(BaseTopic):
+    """Common class for VIM, WIM SDN just to unify methods that are equal to all of them"""
+    config_to_encrypt = {}     # what keys at config must be encrypted because contains passwords
+    password_to_encrypt = ""   # key that contains a password
 
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    @staticmethod
+    def _create_operation(op_type, params=None):
+        """
+        Creates a dictionary with the information to an operation, similar to ns-lcm-op
+        :param op_type: can be create, edit, delete
+        :param params: operation input parameters
+        :return: new dictionary with
+        """
+        now = time()
+        return {
+            "lcmOperationType": op_type,
+            "operationState": "PROCESSING",
+            "startTime": now,
+            "statusEnteredTime": now,
+            "detailed-status": "",
+            "operationParams": params,
+        }
 
     def check_conflict_on_new(self, session, indata):
 
     def check_conflict_on_new(self, session, indata):
+        """
+        Check that the data to be inserted is valid. It is checked that name is unique
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param indata: data to be inserted
+        :return: None or raises EngineException
+        """
         self.check_unique_name(session, indata["name"], _id=None)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         self.check_unique_name(session, indata["name"], _id=None)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        """
+        Check that the data to be edited/uploaded is valid. It is checked that name is unique
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param final_content: data once modified. This method may change it.
+        :param edit_content: incremental data that contains the modifications to apply
+        :param _id: internal _id
+        :return: None or raises EngineException
+        """
         if not session["force"] and edit_content.get("name"):
             self.check_unique_name(session, edit_content["name"], _id=_id)
 
         if not session["force"] and edit_content.get("name"):
             self.check_unique_name(session, edit_content["name"], _id=_id)
 
+    def format_on_edit(self, final_content, edit_content):
+        """
+        Modifies final_content inserting admin information upon edition
+        :param final_content: final content to be stored at database
+        :param edit_content: user requested update content
+        :return: operation id
+        """
+        super().format_on_edit(final_content, edit_content)
+
         # encrypt passwords
         schema_version = final_content.get("schema_version")
         if schema_version:
         # encrypt passwords
         schema_version = final_content.get("schema_version")
         if schema_version:
-            if edit_content.get("vim_password"):
-                final_content["vim_password"] = self.db.encrypt(edit_content["vim_password"],
-                                                                schema_version=schema_version, salt=_id)
-            if edit_content.get("config"):
-                for p in self.vim_config_encrypted:
+            if edit_content.get(self.password_to_encrypt):
+                final_content[self.password_to_encrypt] = self.db.encrypt(edit_content[self.password_to_encrypt],
+                                                                          schema_version=schema_version,
+                                                                          salt=final_content["_id"])
+            config_to_encrypt_keys = self.config_to_encrypt.get(schema_version) or self.config_to_encrypt.get("default")
+            if edit_content.get("config") and config_to_encrypt_keys:
+
+                for p in config_to_encrypt_keys:
                     if edit_content["config"].get(p):
                         final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
                     if edit_content["config"].get(p):
                         final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
-                                                                     schema_version=schema_version, salt=_id)
+                                                                     schema_version=schema_version,
+                                                                     salt=final_content["_id"])
+
+        # create edit operation
+        final_content["_admin"]["operations"].append(self._create_operation("edit"))
+        return "{}:{}".format(final_content["_id"], len(final_content["_admin"]["operations"]) - 1)
 
     def format_on_new(self, content, project_id=None, make_public=False):
 
     def format_on_new(self, content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
-        content["schema_version"] = schema_version = "1.1"
+        """
+        Modifies content descriptor to include _admin and insert create operation
+        :param content: descriptor to be modified
+        :param project_id: if included, it add project read/write permissions. Can be None or a list
+        :param make_public: if included it is generated as public for reading.
+        :return: op_id: operation id on asynchronous operation, None otherwise. In addition content is modified
+        """
+        super().format_on_new(content, project_id=project_id, make_public=make_public)
+        content["schema_version"] = schema_version = "1.11"
 
         # encrypt passwords
 
         # encrypt passwords
-        if content.get("vim_password"):
-            content["vim_password"] = self.db.encrypt(content["vim_password"], schema_version=schema_version,
-                                                      salt=content["_id"])
-        if content.get("config"):
-            for p in self.vim_config_encrypted:
+        if content.get(self.password_to_encrypt):
+            content[self.password_to_encrypt] = self.db.encrypt(content[self.password_to_encrypt],
+                                                                schema_version=schema_version,
+                                                                salt=content["_id"])
+        config_to_encrypt_keys = self.config_to_encrypt.get(schema_version) or self.config_to_encrypt.get("default")
+        if content.get("config") and config_to_encrypt_keys:
+            for p in config_to_encrypt_keys:
                 if content["config"].get(p):
                 if content["config"].get(p):
-                    content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
+                    content["config"][p] = self.db.encrypt(content["config"][p],
+                                                           schema_version=schema_version,
                                                            salt=content["_id"])
 
         content["_admin"]["operationalState"] = "PROCESSING"
 
                                                            salt=content["_id"])
 
         content["_admin"]["operationalState"] = "PROCESSING"
 
-    def delete(self, session, _id, dry_run=False):
+        # create operation
+        content["_admin"]["operations"] = [self._create_operation("create")]
+        content["_admin"]["current_operation"] = None
+
+        return "{}:0".format(content["_id"])
+
+    def delete(self, session, _id, dry_run=False, not_send_msg=None):
         """
         Delete item by its internal _id
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
         """
         Delete item by its internal _id
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
-        :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
+        :param not_send_msg: To not send message (False) or store content (list) instead
+        :return: operation id if it is ordered to delete. None otherwise
         """
         """
-        # TODO add admin to filter, validate rights
-        if dry_run or session["force"]:    # delete completely
-            return BaseTopic.delete(self, session, _id, dry_run)
-        else:  # if not, sent to kafka
-            v = BaseTopic.delete(self, session, _id, 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
 
 
+        filter_q = self._get_project_filter(session)
+        filter_q["_id"] = _id
+        db_content = self.db.get_one(self.topic, filter_q)
+
+        self.check_conflict_on_del(session, _id, db_content)
+        if dry_run:
+            return None
+
+        # remove reference from project_read if there are more projects referencing it. If it last one,
+        # do not remove reference, but order via kafka to delete it
+        if session["project_id"] and session["project_id"]:
+            other_projects_referencing = next((p for p in db_content["_admin"]["projects_read"]
+                                               if p not in session["project_id"]), None)
+
+            # check if there are projects referencing it (apart from ANY, that means, public)....
+            if other_projects_referencing:
+                # remove references but not delete
+                update_dict_pull = {"_admin.projects_read.{}".format(p): None for p in session["project_id"]}
+                update_dict_pull.update({"_admin.projects_write.{}".format(p): None for p in session["project_id"]})
+                self.db.set_one(self.topic, filter_q, update_dict=None, pull=update_dict_pull)
+                return None
+            else:
+                can_write = next((p for p in db_content["_admin"]["projects_write"] if p == "ANY" or
+                                  p in session["project_id"]), None)
+                if not can_write:
+                    raise EngineException("You have not write permission to delete it",
+                                          http_code=HTTPStatus.UNAUTHORIZED)
 
 
-class WimAccountTopic(BaseTopic):
-    topic = "wim_accounts"
-    topic_msg = "wim_account"
-    schema_new = wim_account_new_schema
-    schema_edit = wim_account_edit_schema
+        # It must be deleted
+        if session["force"]:
+            self.db.del_one(self.topic, {"_id": _id})
+            op_id = None
+            self._send_msg("deleted", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg)
+        else:
+            update_dict = {"_admin.to_delete": True}
+            self.db.set_one(self.topic, {"_id": _id},
+                            update_dict=update_dict,
+                            push={"_admin.operations": self._create_operation("delete")}
+                            )
+            # the number of operations is the operation_id. db_content does not contains the new operation inserted,
+            # so the -1 is not needed
+            op_id = "{}:{}".format(db_content["_id"], len(db_content["_admin"]["operations"]))
+            self._send_msg("delete", {"_id": _id, "op_id": op_id}, not_send_msg=not_send_msg)
+        return op_id
+
+
+class VimAccountTopic(CommonVimWimSdn):
+    topic = "vim_accounts"
+    topic_msg = "vim_account"
+    schema_new = vim_account_new_schema
+    schema_edit = vim_account_edit_schema
     multiproject = True
     multiproject = True
-    wim_config_encrypted = ()
+    password_to_encrypt = "vim_password"
+    config_to_encrypt = {"1.1": ("admin_password", "nsx_password", "vcenter_password"),
+                         "default": ("admin_password", "nsx_password", "vcenter_password", "vrops_password")}
 
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
-
-    def check_conflict_on_new(self, session, indata):
-        self.check_unique_name(session, indata["name"], _id=None)
-
-    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
-        if not session["force"] and edit_content.get("name"):
-            self.check_unique_name(session, edit_content["name"], _id=_id)
-
-        # encrypt passwords
-        schema_version = final_content.get("schema_version")
-        if schema_version:
-            if edit_content.get("wim_password"):
-                final_content["wim_password"] = self.db.encrypt(edit_content["wim_password"],
-                                                                schema_version=schema_version, salt=_id)
-            if edit_content.get("config"):
-                for p in self.wim_config_encrypted:
-                    if edit_content["config"].get(p):
-                        final_content["config"][p] = self.db.encrypt(edit_content["config"][p],
-                                                                     schema_version=schema_version, salt=_id)
-
-    def format_on_new(self, content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
-        content["schema_version"] = schema_version = "1.1"
-
-        # encrypt passwords
-        if content.get("wim_password"):
-            content["wim_password"] = self.db.encrypt(content["wim_password"], schema_version=schema_version,
-                                                      salt=content["_id"])
-        if content.get("config"):
-            for p in self.wim_config_encrypted:
-                if content["config"].get(p):
-                    content["config"][p] = self.db.encrypt(content["config"][p], schema_version=schema_version,
-                                                           salt=content["_id"])
-
-        content["_admin"]["operationalState"] = "PROCESSING"
-
-    def delete(self, session, _id, dry_run=False):
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         """
-        Delete item by its internal _id
+        Check if deletion can be done because of dependencies if it is not force. To override
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param _id: server internal id
-        :param dry_run: make checking but do not delete
-        :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
+        :param _id: internal _id
+        :param db_content: The database content of this item _id
+        :return: None if ok or raises EngineException with the conflict
         """
         """
-        # TODO add admin to filter, validate rights
-        if dry_run or session["force"]:    # delete completely
-            return BaseTopic.delete(self, session, _id, dry_run)
-        else:  # if not, sent to kafka
-            v = BaseTopic.delete(self, session, _id, dry_run=True)
-            self.db.set_one("wim_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
+        if session["force"]:
+            return
+        # check if used by VNF
+        if self.db.get_list("vnfrs", {"vim-account-id": _id}):
+            raise EngineException("There is at least one VNF using this VIM account", http_code=HTTPStatus.CONFLICT)
+        super().check_conflict_on_del(session, _id, db_content)
+
+
+class WimAccountTopic(CommonVimWimSdn):
+    topic = "wim_accounts"
+    topic_msg = "wim_account"
+    schema_new = wim_account_new_schema
+    schema_edit = wim_account_edit_schema
+    multiproject = True
+    password_to_encrypt = "wim_password"
+    config_to_encrypt = {}
 
 
 
 
-class SdnTopic(BaseTopic):
+class SdnTopic(CommonVimWimSdn):
     topic = "sdns"
     topic_msg = "sdn"
     schema_new = sdn_new_schema
     schema_edit = sdn_edit_schema
     multiproject = True
     topic = "sdns"
     topic_msg = "sdn"
     schema_new = sdn_new_schema
     schema_edit = sdn_edit_schema
     multiproject = True
+    password_to_encrypt = "password"
+    config_to_encrypt = {}
+
+    def _obtain_url(self, input, create):
+        if input.get("ip") or input.get("port"):
+            if not input.get("ip") or not input.get("port") or input.get('url'):
+                raise ValidationError("You must provide both 'ip' and 'port' (deprecated); or just 'url' (prefered)")
+            input['url'] = "http://{}:{}/".format(input["ip"], input["port"])
+            del input["ip"]
+            del input["port"]
+        elif create and not input.get('url'):
+            raise ValidationError("You must provide 'url'")
+        return input
 
 
-    def __init__(self, db, fs, msg):
-        BaseTopic.__init__(self, db, fs, msg)
+    def _validate_input_new(self, input, force=False):
+        input = super()._validate_input_new(input, force)
+        return self._obtain_url(input, True)
 
 
-    def check_conflict_on_new(self, session, indata):
-        self.check_unique_name(session, indata["name"], _id=None)
+    def _validate_input_edit(self, input, force=False):
+        input = super()._validate_input_edit(input, force)
+        return self._obtain_url(input, False)
 
 
-    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
-        if not session["force"] and edit_content.get("name"):
-            self.check_unique_name(session, edit_content["name"], _id=_id)
 
 
-        # encrypt passwords
-        schema_version = final_content.get("schema_version")
-        if schema_version and edit_content.get("password"):
-            final_content["password"] = self.db.encrypt(edit_content["password"], schema_version=schema_version,
-                                                        salt=_id)
+class K8sClusterTopic(CommonVimWimSdn):
+    topic = "k8sclusters"
+    topic_msg = "k8scluster"
+    schema_new = k8scluster_new_schema
+    schema_edit = k8scluster_edit_schema
+    multiproject = True
+    password_to_encrypt = None
+    config_to_encrypt = {}
 
     def format_on_new(self, content, project_id=None, make_public=False):
 
     def format_on_new(self, content, project_id=None, make_public=False):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
-        content["schema_version"] = schema_version = "1.1"
-        # encrypt passwords
-        if content.get("password"):
-            content["password"] = self.db.encrypt(content["password"], schema_version=schema_version,
-                                                  salt=content["_id"])
-
-        content["_admin"]["operationalState"] = "PROCESSING"
+        oid = super().format_on_new(content, project_id, make_public)
+        self.db.encrypt_decrypt_fields(content["credentials"], 'encrypt', ['password', 'secret'],
+                                       schema_version=content["schema_version"], salt=content["_id"])
+        # Add Helm/Juju Repo lists
+        repos = {"helm-chart": [], "juju-bundle": []}
+        for proj in content["_admin"]["projects_read"]:
+            if proj != 'ANY':
+                for repo in self.db.get_list("k8srepos", {"_admin.projects_read": proj}):
+                    if repo["_id"] not in repos[repo["type"]]:
+                        repos[repo["type"]].append(repo["_id"])
+        for k in repos:
+            content["_admin"][k.replace('-', '_')+"_repos"] = repos[k]
+        return oid
+
+    def format_on_edit(self, final_content, edit_content):
+        if final_content.get("schema_version") and edit_content.get("credentials"):
+            self.db.encrypt_decrypt_fields(edit_content["credentials"], 'encrypt', ['password', 'secret'],
+                                           schema_version=final_content["schema_version"], salt=final_content["_id"])
+            deep_update_rfc7396(final_content["credentials"], edit_content["credentials"])
+        oid = super().format_on_edit(final_content, edit_content)
+        return oid
 
 
-    def delete(self, session, _id, dry_run=False):
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        super(CommonVimWimSdn, self).check_conflict_on_edit(session, final_content, edit_content, _id)
+        super().check_conflict_on_edit(session, final_content, edit_content, _id)
+        # Update Helm/Juju Repo lists
+        repos = {"helm-chart": [], "juju-bundle": []}
+        for proj in session.get("set_project", []):
+            if proj != 'ANY':
+                for repo in self.db.get_list("k8srepos", {"_admin.projects_read": proj}):
+                    if repo["_id"] not in repos[repo["type"]]:
+                        repos[repo["type"]].append(repo["_id"])
+        for k in repos:
+            rlist = k.replace('-', '_') + "_repos"
+            if rlist not in final_content["_admin"]:
+                final_content["_admin"][rlist] = []
+            final_content["_admin"][rlist] += repos[k]
+
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         """
-        Delete item by its internal _id
+        Check if deletion can be done because of dependencies if it is not force. To override
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param _id: server internal id
-        :param dry_run: make checking but do not delete
-        :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
+        :param _id: internal _id
+        :param db_content: The database content of this item _id
+        :return: None if ok or raises EngineException with the conflict
         """
         """
-        if dry_run or session["force"]:  # delete completely
-            return BaseTopic.delete(self, session, _id, dry_run)
-        else:  # if not sent to kafka
-            v = BaseTopic.delete(self, session, _id, 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
+        if session["force"]:
+            return
+        # check if used by VNF
+        filter_q = {"kdur.k8s-cluster.id": _id}
+        if session["project_id"]:
+            filter_q["_admin.projects_read.cont"] = session["project_id"]
+        if self.db.get_list("vnfrs", filter_q):
+            raise EngineException("There is at least one VNF using this k8scluster", http_code=HTTPStatus.CONFLICT)
+        super().check_conflict_on_del(session, _id, db_content)
+
+
+class K8sRepoTopic(CommonVimWimSdn):
+    topic = "k8srepos"
+    topic_msg = "k8srepo"
+    schema_new = k8srepo_new_schema
+    schema_edit = k8srepo_edit_schema
+    multiproject = True
+    password_to_encrypt = None
+    config_to_encrypt = {}
+
+    def format_on_new(self, content, project_id=None, make_public=False):
+        oid = super().format_on_new(content, project_id, make_public)
+        # Update Helm/Juju Repo lists
+        repo_list = content["type"].replace('-', '_')+"_repos"
+        for proj in content["_admin"]["projects_read"]:
+            if proj != 'ANY':
+                self.db.set_list("k8sclusters",
+                                 {"_admin.projects_read": proj, "_admin."+repo_list+".ne": content["_id"]}, {},
+                                 push={"_admin."+repo_list: content["_id"]})
+        return oid
+
+    def delete(self, session, _id, dry_run=False, not_send_msg=None):
+        type = self.db.get_one("k8srepos", {"_id": _id})["type"]
+        oid = super().delete(session, _id, dry_run, not_send_msg)
+        if oid:
+            # Remove from Helm/Juju Repo lists
+            repo_list = type.replace('-', '_') + "_repos"
+            self.db.set_list("k8sclusters", {"_admin."+repo_list: _id}, {}, pull={"_admin."+repo_list: _id})
+        return oid
 
 
 class UserTopicAuth(UserTopic):
     # topic = "users"
     # topic_msg = "users"
 
 
 class UserTopicAuth(UserTopic):
     # topic = "users"
     # topic_msg = "users"
-    schema_new = user_new_schema
-    schema_edit = user_edit_schema
+    schema_new = user_new_schema
+    schema_edit = user_edit_schema
 
     def __init__(self, db, fs, msg, auth):
 
     def __init__(self, db, fs, msg, auth):
-        UserTopic.__init__(self, db, fs, msg)
-        self.auth = auth
+        UserTopic.__init__(self, db, fs, msg, auth)
+        self.auth = auth
 
     def check_conflict_on_new(self, session, indata):
         """
 
     def check_conflict_on_new(self, session, indata):
         """
@@ -371,10 +539,31 @@ class UserTopicAuth(UserTopic):
         :return: None or raises EngineException
         """
         username = indata.get("username")
         :return: None or raises EngineException
         """
         username = indata.get("username")
-        user_list = list(map(lambda x: x["username"], self.auth.get_user_list()))
-
-        if username in user_list:
-            raise EngineException("username '{}' exists".format(username), HTTPStatus.CONFLICT)
+        if is_valid_uuid(username):
+            raise EngineException("username '{}' cannot have a uuid format".format(username),
+                                  HTTPStatus.UNPROCESSABLE_ENTITY)
+
+        # Check that username is not used, regardless keystone already checks this
+        if self.auth.get_user_list(filter_q={"name": username}):
+            raise EngineException("username '{}' is already used".format(username), HTTPStatus.CONFLICT)
+
+        if "projects" in indata.keys():
+            # convert to new format project_role_mappings
+            role = self.auth.get_role_list({"name": "project_admin"})
+            if not role:
+                role = self.auth.get_role_list()
+            if not role:
+                raise AuthconnNotFoundException("Can't find default role for user '{}'".format(username))
+            rid = role[0]["_id"]
+            if not indata.get("project_role_mappings"):
+                indata["project_role_mappings"] = []
+            for project in indata["projects"]:
+                pid = self.auth.get_project(project)["_id"]
+                prm = {"project": pid, "role": rid}
+                if prm not in indata["project_role_mappings"]:
+                    indata["project_role_mappings"].append(prm)
+            # raise EngineException("Format invalid: the keyword 'projects' is not allowed for keystone authentication",
+            #                       HTTPStatus.BAD_REQUEST)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
@@ -386,62 +575,55 @@ class UserTopicAuth(UserTopic):
         :param _id: internal _id
         :return: None or raises EngineException
         """
         :param _id: internal _id
         :return: None or raises EngineException
         """
-        users = self.auth.get_user_list()
-        admin_user = [user for user in users if user["name"] == "admin"][0]
 
 
-        if _id == admin_user["_id"] and edit_content["project_role_mappings"]:
-            elem = {
-                "project": "admin",
-                "role": "system_admin"
-            }
-            if elem not in edit_content:
-                raise EngineException("You cannot remove system_admin role from admin user", 
-                                      http_code=HTTPStatus.FORBIDDEN)
+        if "username" in edit_content:
+            username = edit_content.get("username")
+            if is_valid_uuid(username):
+                raise EngineException("username '{}' cannot have an uuid format".format(username),
+                                      HTTPStatus.UNPROCESSABLE_ENTITY)
 
 
-    def check_conflict_on_del(self, session, _id):
+            # Check that username is not used, regardless keystone already checks this
+            if self.auth.get_user_list(filter_q={"name": username}):
+                raise EngineException("username '{}' is already used".format(username), HTTPStatus.CONFLICT)
+
+        if final_content["username"] == "admin":
+            for mapping in edit_content.get("remove_project_role_mappings", ()):
+                if mapping["project"] == "admin" and mapping.get("role") in (None, "system_admin"):
+                    # TODO make this also available for project id and role id
+                    raise EngineException("You cannot remove system_admin role from admin user",
+                                          http_code=HTTPStatus.FORBIDDEN)
+
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         Check if deletion can be done because of dependencies if it is not force. To override
         """
         Check if deletion can be done because of dependencies if it is not force. To override
-
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
+        :param db_content: The database content of this item _id
         :return: None if ok or raises EngineException with the conflict
         """
         :return: None if ok or raises EngineException with the conflict
         """
-        if _id == session["username"]:
-            raise EngineException("You cannot delete your own user", http_code=HTTPStatus.CONFLICT)
+        if db_content["username"] == session["username"]:
+            raise EngineException("You cannot delete your own login user ", http_code=HTTPStatus.CONFLICT)
+        # TODO: Check that user is not logged in ? How? (Would require listing current tokens)
 
     @staticmethod
 
     @staticmethod
-    def format_on_new(content, project_id=None, make_public=False):
-        """
-        Modifies content descriptor to include _id.
-
-        NOTE: No password salt required because the authentication backend
-        should handle these security concerns.
-
-        :param content: descriptor to be modified
-        :param make_public: if included it is generated as public for reading.
-        :return: None, but content is modified
+    def format_on_show(content):
         """
         """
-        BaseTopic.format_on_new(content, make_public=False)
-        content["_id"] = content["username"]
-        content["password"] = content["password"]
-
-    @staticmethod
-    def format_on_edit(final_content, edit_content):
+        Modifies the content of the role information to separate the role
+        metadata from the role definition.
         """
         """
-        Modifies final_content descriptor to include the modified date.
+        project_role_mappings = []
 
 
-        NOTE: No password salt required because the authentication backend
-        should handle these security concerns.
+        if "projects" in content:
+            for project in content["projects"]:
+                for role in project["roles"]:
+                    project_role_mappings.append({"project": project["_id"],
+                                                  "project_name": project["name"],
+                                                  "role": role["_id"],
+                                                  "role_name": role["name"]})
+            del content["projects"]
+        content["project_role_mappings"] = project_role_mappings
 
 
-        :param final_content: final descriptor generated
-        :param edit_content: alterations to be include
-        :return: None, but final_content is modified
-        """
-        BaseTopic.format_on_edit(final_content, edit_content)
-        if "password" in edit_content:
-            final_content["password"] = edit_content["password"]
-        else:
-            final_content["project_role_mappings"] = edit_content["project_role_mappings"]
+        return content
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
@@ -454,7 +636,7 @@ class UserTopicAuth(UserTopic):
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
-        :return: _id: identity of the inserted data.
+        :return: _id: identity of the inserted data, operation _id (None)
         """
         try:
             content = BaseTopic._remove_envelop(indata)
         """
         try:
             content = BaseTopic._remove_envelop(indata)
@@ -463,12 +645,26 @@ class UserTopicAuth(UserTopic):
             BaseTopic._update_input_with_kwargs(content, kwargs)
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
             BaseTopic._update_input_with_kwargs(content, kwargs)
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
-            self.format_on_new(content, session["project_id"], make_public=session["public"])
-            _id = self.auth.create_user(content["username"], content["password"])
+            # self.format_on_new(content, session["project_id"], make_public=session["public"])
+            now = time()
+            content["_admin"] = {"created": now, "modified": now}
+            prms = []
+            for prm in content.get("project_role_mappings", []):
+                proj = self.auth.get_project(prm["project"], not session["force"])
+                role = self.auth.get_role(prm["role"], not session["force"])
+                pid = proj["_id"] if proj else None
+                rid = role["_id"] if role else None
+                prl = {"project": pid, "role": rid}
+                if prl not in prms:
+                    prms.append(prl)
+            content["project_role_mappings"] = prms
+            # _id = self.auth.create_user(content["username"], content["password"])["_id"]
+            _id = self.auth.create_user(content)["_id"]
+
             rollback.append({"topic": self.topic, "_id": _id})
             rollback.append({"topic": self.topic, "_id": _id})
-            del content["password"]
-            # self._send_msg("create", content)
-            return _id
+            del content["password"]
+            # self._send_msg("created", content, not_send_msg=not_send_msg)
+            return _id, None
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
@@ -477,17 +673,19 @@ class UserTopicAuth(UserTopic):
         Get complete information on an topic
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         Get complete information on an topic
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param _id: server internal id
+        :param _id: server internal id or username
         :return: dictionary, raise exception if not found.
         """
         :return: dictionary, raise exception if not found.
         """
-        users = [user for user in self.auth.get_user_list() if user["_id"] == _id]
-
+        # Allow _id to be a name or uuid
+        filter_q = {"username": _id}
+        # users = self.auth.get_user_list(filter_q)
+        users = self.list(session, filter_q)   # To allow default filtering (Bug 853)
         if len(users) == 1:
             return users[0]
         elif len(users) > 1:
         if len(users) == 1:
             return users[0]
         elif len(users) > 1:
-            raise EngineException("Too many users found", HTTPStatus.CONFLICT)
+            raise EngineException("Too many users found for '{}'".format(_id), HTTPStatus.CONFLICT)
         else:
         else:
-            raise EngineException("User not found", HTTPStatus.NOT_FOUND)
+            raise EngineException("User '{}' not found".format(_id), HTTPStatus.NOT_FOUND)
 
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         """
 
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         """
@@ -511,44 +709,96 @@ class UserTopicAuth(UserTopic):
             if not content:
                 content = self.show(session, _id)
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             if not content:
                 content = self.show(session, _id)
             self.check_conflict_on_edit(session, content, indata, _id=_id)
-            self.format_on_edit(content, indata)
-
-            if "password" in content:
-                self.auth.change_password(content["name"], content["password"])
-            else:
-                users = self.auth.get_user_list()
-                user = [user for user in users if user["_id"] == content["_id"]][0]
-                original_mapping = []
-                edit_mapping = content["project_role_mappings"]
-
-                for project in user["projects"]:
-                    for role in project["roles"]:
-                        original_mapping += {
-                            "project": project["name"],
-                            "role": role["name"]
-                        }
-                
-                mappings_to_remove = [mapping for mapping in original_mapping 
-                                      if mapping not in edit_mapping]
-                
-                mappings_to_add = [mapping for mapping in edit_mapping
-                                   if mapping not in original_mapping]
-                
-                for mapping in mappings_to_remove:
-                    self.auth.remove_role_from_user(
-                        user["name"], 
-                        mapping["project"],
-                        mapping["role"]
-                    )
-                
-                for mapping in mappings_to_add:
-                    self.auth.assign_role_to_user(
-                        user["name"], 
-                        mapping["project"],
-                        mapping["role"]
-                    )
-
-            return content["_id"]
+            # self.format_on_edit(content, indata)
+
+            if not ("password" in indata or "username" in indata or indata.get("remove_project_role_mappings") or
+                    indata.get("add_project_role_mappings") or indata.get("project_role_mappings") or
+                    indata.get("projects") or indata.get("add_projects")):
+                return _id
+            if indata.get("project_role_mappings") \
+                    and (indata.get("remove_project_role_mappings") or indata.get("add_project_role_mappings")):
+                raise EngineException("Option 'project_role_mappings' is incompatible with 'add_project_role_mappings"
+                                      "' or 'remove_project_role_mappings'", http_code=HTTPStatus.BAD_REQUEST)
+
+            if indata.get("projects") or indata.get("add_projects"):
+                role = self.auth.get_role_list({"name": "project_admin"})
+                if not role:
+                    role = self.auth.get_role_list()
+                if not role:
+                    raise AuthconnNotFoundException("Can't find a default role for user '{}'"
+                                                    .format(content["username"]))
+                rid = role[0]["_id"]
+                if "add_project_role_mappings" not in indata:
+                    indata["add_project_role_mappings"] = []
+                if "remove_project_role_mappings" not in indata:
+                    indata["remove_project_role_mappings"] = []
+                if isinstance(indata.get("projects"), dict):
+                    # backward compatible
+                    for k, v in indata["projects"].items():
+                        if k.startswith("$") and v is None:
+                            indata["remove_project_role_mappings"].append({"project": k[1:]})
+                        elif k.startswith("$+"):
+                            indata["add_project_role_mappings"].append({"project": v, "role": rid})
+                    del indata["projects"]
+                for proj in indata.get("projects", []) + indata.get("add_projects", []):
+                    indata["add_project_role_mappings"].append({"project": proj, "role": rid})
+
+            # user = self.show(session, _id)   # Already in 'content'
+            original_mapping = content["project_role_mappings"]
+
+            mappings_to_add = []
+            mappings_to_remove = []
+
+            # remove
+            for to_remove in indata.get("remove_project_role_mappings", ()):
+                for mapping in original_mapping:
+                    if to_remove["project"] in (mapping["project"], mapping["project_name"]):
+                        if not to_remove.get("role") or to_remove["role"] in (mapping["role"], mapping["role_name"]):
+                            mappings_to_remove.append(mapping)
+
+            # add
+            for to_add in indata.get("add_project_role_mappings", ()):
+                for mapping in original_mapping:
+                    if to_add["project"] in (mapping["project"], mapping["project_name"]) and \
+                            to_add["role"] in (mapping["role"], mapping["role_name"]):
+
+                        if mapping in mappings_to_remove:   # do not remove
+                            mappings_to_remove.remove(mapping)
+                        break  # do not add, it is already at user
+                else:
+                    pid = self.auth.get_project(to_add["project"])["_id"]
+                    rid = self.auth.get_role(to_add["role"])["_id"]
+                    mappings_to_add.append({"project": pid, "role": rid})
+
+            # set
+            if indata.get("project_role_mappings"):
+                for to_set in indata["project_role_mappings"]:
+                    for mapping in original_mapping:
+                        if to_set["project"] in (mapping["project"], mapping["project_name"]) and \
+                                to_set["role"] in (mapping["role"], mapping["role_name"]):
+                            if mapping in mappings_to_remove:   # do not remove
+                                mappings_to_remove.remove(mapping)
+                            break  # do not add, it is already at user
+                    else:
+                        pid = self.auth.get_project(to_set["project"])["_id"]
+                        rid = self.auth.get_role(to_set["role"])["_id"]
+                        mappings_to_add.append({"project": pid, "role": rid})
+                for mapping in original_mapping:
+                    for to_set in indata["project_role_mappings"]:
+                        if to_set["project"] in (mapping["project"], mapping["project_name"]) and \
+                                to_set["role"] in (mapping["role"], mapping["role_name"]):
+                            break
+                    else:
+                        # delete
+                        if mapping not in mappings_to_remove:   # do not remove
+                            mappings_to_remove.append(mapping)
+
+            self.auth.update_user({"_id": _id, "username": indata.get("username"), "password": indata.get("password"),
+                                   "add_project_role_mappings": mappings_to_add,
+                                   "remove_project_role_mappings": mappings_to_remove
+                                   })
+
+            # return _id
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
@@ -559,9 +809,13 @@ class UserTopicAuth(UserTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        return self.auth.get_user_list()
+        user_list = self.auth.get_user_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user_list = [usr for usr in user_list if usr["username"] == session["username"]]
+        return user_list
 
 
-    def delete(self, session, _id, dry_run=False):
+    def delete(self, session, _id, dry_run=False, not_send_msg=None):
         """
         Delete item by its internal _id
 
         """
         Delete item by its internal _id
 
@@ -569,11 +823,15 @@ class UserTopicAuth(UserTopic):
         :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
         :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
+        :param not_send_msg: To not send message (False) or store content (list) instead
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
-        self.check_conflict_on_del(session, _id)
+        # Allow _id to be a name or uuid
+        user = self.auth.get_user(_id)
+        uid = user["_id"]
+        self.check_conflict_on_del(session, uid, user)
         if not dry_run:
         if not dry_run:
-            v = self.auth.delete_user(_id)
+            v = self.auth.delete_user(uid)
             return v
         return None
 
             return v
         return None
 
@@ -581,12 +839,12 @@ class UserTopicAuth(UserTopic):
 class ProjectTopicAuth(ProjectTopic):
     # topic = "projects"
     # topic_msg = "projects"
 class ProjectTopicAuth(ProjectTopic):
     # topic = "projects"
     # topic_msg = "projects"
-    schema_new = project_new_schema
-    schema_edit = project_edit_schema
+    schema_new = project_new_schema
+    schema_edit = project_edit_schema
 
     def __init__(self, db, fs, msg, auth):
 
     def __init__(self, db, fs, msg, auth):
-        ProjectTopic.__init__(self, db, fs, msg)
-        self.auth = auth
+        ProjectTopic.__init__(self, db, fs, msg, auth)
+        self.auth = auth
 
     def check_conflict_on_new(self, session, indata):
         """
 
     def check_conflict_on_new(self, session, indata):
         """
@@ -596,27 +854,77 @@ class ProjectTopicAuth(ProjectTopic):
         :param indata: data to be inserted
         :return: None or raises EngineException
         """
         :param indata: data to be inserted
         :return: None or raises EngineException
         """
-        project = indata.get("name")
-        project_list = list(map(lambda x: x["name"], self.auth.get_project_list()))
+        project_name = indata.get("name")
+        if is_valid_uuid(project_name):
+            raise EngineException("project name '{}' cannot have an uuid format".format(project_name),
+                                  HTTPStatus.UNPROCESSABLE_ENTITY)
+
+        project_list = self.auth.get_project_list(filter_q={"name": project_name})
+
+        if project_list:
+            raise EngineException("project '{}' exists".format(project_name), HTTPStatus.CONFLICT)
 
 
-        if project in project_list:
-            raise EngineException("project '{}' exists".format(project), HTTPStatus.CONFLICT)
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        """
+        Check that the data to be edited/uploaded is valid
 
 
-    def check_conflict_on_del(self, session, _id):
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param final_content: data once modified
+        :param edit_content: incremental data that contains the modifications to apply
+        :param _id: internal _id
+        :return: None or raises EngineException
+        """
+
+        project_name = edit_content.get("name")
+        if project_name != final_content["name"]:  # It is a true renaming
+            if is_valid_uuid(project_name):
+                raise EngineException("project name '{}' cannot have an uuid format".format(project_name),
+                                      HTTPStatus.UNPROCESSABLE_ENTITY)
+
+            if final_content["name"] == "admin":
+                raise EngineException("You cannot rename project 'admin'", http_code=HTTPStatus.CONFLICT)
+
+            # Check that project name is not used, regardless keystone already checks this
+            if project_name and self.auth.get_project_list(filter_q={"name": project_name}):
+                raise EngineException("project '{}' is already used".format(project_name), HTTPStatus.CONFLICT)
+
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         Check if deletion can be done because of dependencies if it is not force. To override
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
         """
         Check if deletion can be done because of dependencies if it is not force. To override
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
+        :param db_content: The database content of this item _id
         :return: None if ok or raises EngineException with the conflict
         """
         :return: None if ok or raises EngineException with the conflict
         """
-        projects = self.auth.get_project_list()
-        current_project = [project for project in projects
-                           if project["name"] == session["project_id"]][0]
 
 
-        if _id == current_project["_id"]:
+        def check_rw_projects(topic, title, id_field):
+            for desc in self.db.get_list(topic):
+                if _id in desc["_admin"]["projects_read"] + desc["_admin"]["projects_write"]:
+                    raise EngineException("Project '{}' ({}) is being used by {} '{}'"
+                                          .format(db_content["name"], _id, title, desc[id_field]), HTTPStatus.CONFLICT)
+
+        if _id in session["project_id"]:
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
 
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
 
+        if db_content["name"] == "admin":
+            raise EngineException("You cannot delete project 'admin'", http_code=HTTPStatus.CONFLICT)
+
+        # If any user is using this project, raise CONFLICT exception
+        if not session["force"]:
+            for user in self.auth.get_user_list():
+                for prm in user.get("project_role_mappings"):
+                    if prm["project"] == _id:
+                        raise EngineException("Project '{}' ({}) is being used by user '{}'"
+                                              .format(db_content["name"], _id, user["username"]), HTTPStatus.CONFLICT)
+
+        # If any VNFD, NSD, NST, PDU, etc. is using this project, raise CONFLICT exception
+        if not session["force"]:
+            check_rw_projects("vnfds", "VNF Descriptor", "id")
+            check_rw_projects("nsds", "NS Descriptor", "id")
+            check_rw_projects("nsts", "NS Template", "id")
+            check_rw_projects("pdus", "PDU Descriptor", "name")
+
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
         Creates a new entry into the authentication backend.
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
         Creates a new entry into the authentication backend.
@@ -628,7 +936,7 @@ class ProjectTopicAuth(ProjectTopic):
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
-        :return: _id: identity of the inserted data.
+        :return: _id: identity of the inserted data, operation _id (None)
         """
         try:
             content = BaseTopic._remove_envelop(indata)
         """
         try:
             content = BaseTopic._remove_envelop(indata)
@@ -638,10 +946,10 @@ class ProjectTopicAuth(ProjectTopic):
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
             self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
             self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
-            _id = self.auth.create_project(content["name"])
+            _id = self.auth.create_project(content)
             rollback.append({"topic": self.topic, "_id": _id})
             rollback.append({"topic": self.topic, "_id": _id})
-            # self._send_msg("create", content)
-            return _id
+            # self._send_msg("created", content, not_send_msg=not_send_msg)
+            return _id, None
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
@@ -653,8 +961,10 @@ class ProjectTopicAuth(ProjectTopic):
         :param _id: server internal id
         :return: dictionary, raise exception if not found.
         """
         :param _id: server internal id
         :return: dictionary, raise exception if not found.
         """
-        projects = [project for project in self.auth.get_project_list() if project["_id"] == _id]
-
+        # Allow _id to be a name or uuid
+        filter_q = {self.id_field(self.topic, _id): _id}
+        # projects = self.auth.get_project_list(filter_q=filter_q)
+        projects = self.list(session, filter_q)   # To allow default filtering (Bug 853)
         if len(projects) == 1:
             return projects[0]
         elif len(projects) > 1:
         if len(projects) == 1:
             return projects[0]
         elif len(projects) > 1:
@@ -670,35 +980,75 @@ class ProjectTopicAuth(ProjectTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        return self.auth.get_project_list()
+        project_list = self.auth.get_project_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user = self.auth.get_user(session["username"])
+            projects = [prm["project"] for prm in user["project_role_mappings"]]
+            project_list = [proj for proj in project_list if proj["_id"] in projects]
+        return project_list
 
 
-    def delete(self, session, _id, dry_run=False):
+    def delete(self, session, _id, dry_run=False, not_send_msg=None):
         """
         Delete item by its internal _id
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
         """
         Delete item by its internal _id
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
+        :param not_send_msg: To not send message (False) or store content (list) instead
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
-        self.check_conflict_on_del(session, _id)
+        # Allow _id to be a name or uuid
+        proj = self.auth.get_project(_id)
+        pid = proj["_id"]
+        self.check_conflict_on_del(session, pid, proj)
         if not dry_run:
         if not dry_run:
-            v = self.auth.delete_project(_id)
+            v = self.auth.delete_project(pid)
             return v
         return None
 
             return v
         return None
 
+    def edit(self, session, _id, indata=None, kwargs=None, content=None):
+        """
+        Updates a project entry.
+
+        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+        :param _id:
+        :param indata: data to be inserted
+        :param kwargs: used to override the indata descriptor
+        :param content:
+        :return: _id: identity of the inserted data.
+        """
+        indata = self._remove_envelop(indata)
+
+        # Override descriptor with query string kwargs
+        if kwargs:
+            BaseTopic._update_input_with_kwargs(indata, kwargs)
+        try:
+            indata = self._validate_input_edit(indata, force=session["force"])
+
+            if not content:
+                content = self.show(session, _id)
+            self.check_conflict_on_edit(session, content, indata, _id=_id)
+            self.format_on_edit(content, indata)
+
+            deep_update_rfc7396(content, indata)
+            self.auth.update_project(content["_id"], content)
+        except ValidationError as e:
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+
 
 class RoleTopicAuth(BaseTopic):
 
 class RoleTopicAuth(BaseTopic):
-    topic = "roles_operations"
-    topic_msg = "roles"
+    topic = "roles"
+    topic_msg = None    # "roles"
     schema_new = roles_new_schema
     schema_edit = roles_edit_schema
     multiproject = False
 
     schema_new = roles_new_schema
     schema_edit = roles_edit_schema
     multiproject = False
 
-    def __init__(self, db, fs, msg, auth, ops):
-        BaseTopic.__init__(self, db, fs, msg)
-        self.auth = auth
-        self.operations = ops
+    def __init__(self, db, fs, msg, auth):
+        BaseTopic.__init__(self, db, fs, msg, auth)
+        # self.auth = auth
+        self.operations = auth.role_permissions
+        # self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
 
     @staticmethod
     def validate_role_definition(operations, role_definitions):
 
     @staticmethod
     def validate_role_definition(operations, role_definitions):
@@ -710,16 +1060,19 @@ class RoleTopicAuth(BaseTopic):
         :param role_definitions: role definition to test
         :return: None if ok, raises ValidationError exception on error
         """
         :param role_definitions: role definition to test
         :return: None if ok, raises ValidationError exception on error
         """
-        for role_def in role_definitions.keys():
-            if role_def == ".":
+        if not role_definitions.get("permissions"):
+            return
+        ignore_fields = ["admin", "default"]
+        for role_def in role_definitions["permissions"].keys():
+            if role_def in ignore_fields:
                 continue
                 continue
-            if role_def[-1] == ".":
-                raise ValidationError("Operation cannot end with \".\"")
-            
+            if role_def[-1] == ":":
+                raise ValidationError("Operation cannot end with ':'")
+
             role_def_matches = [op for op in operations if op.startswith(role_def)]
 
             if len(role_def_matches) == 0:
             role_def_matches = [op for op in operations if op.startswith(role_def)]
 
             if len(role_def_matches) == 0:
-                raise ValidationError("No matching operation found.")
+                raise ValidationError("Invalid permission '{}'".format(role_def))
 
     def _validate_input_new(self, input, force=False):
         """
 
     def _validate_input_new(self, input, force=False):
         """
@@ -731,8 +1084,8 @@ class RoleTopicAuth(BaseTopic):
         """
         if self.schema_new:
             validate_input(input, self.schema_new)
         """
         if self.schema_new:
             validate_input(input, self.schema_new)
-        if "definition" in input and input["definition"]:
-            self.validate_role_definition(self.operations, input["definition"])
+            self.validate_role_definition(self.operations, input)
+
         return input
 
     def _validate_input_edit(self, input, force=False):
         return input
 
     def _validate_input_edit(self, input, force=False):
@@ -745,8 +1098,8 @@ class RoleTopicAuth(BaseTopic):
         """
         if self.schema_edit:
             validate_input(input, self.schema_edit)
         """
         if self.schema_edit:
             validate_input(input, self.schema_edit)
-        if "definition" in input and input["definition"]:
-            self.validate_role_definition(self.operations, input["definition"])
+            self.validate_role_definition(self.operations, input)
+
         return input
 
     def check_conflict_on_new(self, session, indata):
         return input
 
     def check_conflict_on_new(self, session, indata):
@@ -757,11 +1110,16 @@ class RoleTopicAuth(BaseTopic):
         :param indata: data to be inserted
         :return: None or raises EngineException
         """
         :param indata: data to be inserted
         :return: None or raises EngineException
         """
-        role = indata.get("name")
-        role_list = list(map(lambda x: x["name"], self.auth.get_role_list()))
-
-        if role in role_list:
-            raise EngineException("role '{}' exists".format(role), HTTPStatus.CONFLICT)
+        # check name is not uuid
+        role_name = indata.get("name")
+        if is_valid_uuid(role_name):
+            raise EngineException("role name '{}' cannot have an uuid format".format(role_name),
+                                  HTTPStatus.UNPROCESSABLE_ENTITY)
+        # check name not exists
+        name = indata["name"]
+        # if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
+        if self.auth.get_role_list({"name": name}):
+            raise EngineException("role name '{}' exists".format(name), HTTPStatus.CONFLICT)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
@@ -773,30 +1131,53 @@ class RoleTopicAuth(BaseTopic):
         :param _id: internal _id
         :return: None or raises EngineException
         """
         :param _id: internal _id
         :return: None or raises EngineException
         """
-        roles = self.auth.get_role_list()
-        system_admin_role = [role for role in roles
-                             if roles["name"] == "system_admin"][0]
+        if "default" not in final_content["permissions"]:
+            final_content["permissions"]["default"] = False
+        if "admin" not in final_content["permissions"]:
+            final_content["permissions"]["admin"] = False
+
+        # check name is not uuid
+        role_name = edit_content.get("name")
+        if is_valid_uuid(role_name):
+            raise EngineException("role name '{}' cannot have an uuid format".format(role_name),
+                                  HTTPStatus.UNPROCESSABLE_ENTITY)
 
 
-        if _id == system_admin_role["_id"]:
-            raise EngineException("You cannot edit system_admin role", http_code=HTTPStatus.FORBIDDEN)
+        # Check renaming of admin roles
+        role = self.auth.get_role(_id)
+        if role["name"] in ["system_admin", "project_admin"]:
+            raise EngineException("You cannot rename role '{}'".format(role["name"]), http_code=HTTPStatus.FORBIDDEN)
+
+        # check name not exists
+        if "name" in edit_content:
+            role_name = edit_content["name"]
+            # if self.db.get_one(self.topic, {"name":role_name,"_id.ne":_id}, fail_on_empty=False, fail_on_more=False):
+            roles = self.auth.get_role_list({"name": role_name})
+            if roles and roles[0][BaseTopic.id_field("roles", _id)] != _id:
+                raise EngineException("role name '{}' exists".format(role_name), HTTPStatus.CONFLICT)
 
 
-    def check_conflict_on_del(self, session, _id):
+    def check_conflict_on_del(self, session, _id, db_content):
         """
         Check if deletion can be done because of dependencies if it is not force. To override
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
         """
         Check if deletion can be done because of dependencies if it is not force. To override
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: internal _id
+        :param db_content: The database content of this item _id
         :return: None if ok or raises EngineException with the conflict
         """
         :return: None if ok or raises EngineException with the conflict
         """
-        roles = self.auth.get_role_list()
-        system_admin_role = [role for role in roles
-                             if roles["name"] == "system_admin"][0]
+        role = self.auth.get_role(_id)
+        if role["name"] in ["system_admin", "project_admin"]:
+            raise EngineException("You cannot delete role '{}'".format(role["name"]), http_code=HTTPStatus.FORBIDDEN)
 
 
-        if _id == system_admin_role["_id"]:
-            raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
+        # If any user is using this role, raise CONFLICT exception
+        if not session["force"]:
+            for user in self.auth.get_user_list():
+                for prm in user.get("project_role_mappings"):
+                    if prm["role"] == _id:
+                        raise EngineException("Role '{}' ({}) is being used by user '{}'"
+                                              .format(role["name"], _id, user["username"]), HTTPStatus.CONFLICT)
 
     @staticmethod
 
     @staticmethod
-    def format_on_new(content, project_id=None, make_public=False):
+    def format_on_new(content, project_id=None, make_public=False):   # TO BE REMOVED ?
         """
         Modifies content descriptor to include _admin
 
         """
         Modifies content descriptor to include _admin
 
@@ -811,19 +1192,14 @@ class RoleTopicAuth(BaseTopic):
         if not content["_admin"].get("created"):
             content["_admin"]["created"] = now
         content["_admin"]["modified"] = now
         if not content["_admin"].get("created"):
             content["_admin"]["created"] = now
         content["_admin"]["modified"] = now
-        content["root"] = False
 
 
-        # Saving the role definition
-        if "definition" in content and content["definition"]:
-            for role_def, value in content["definition"].items():
-                if role_def == ".":
-                    content["root"] = value
-                else:
-                    content[role_def.replace(".", ":")] = value
+        if "permissions" not in content:
+            content["permissions"] = {}
 
 
-        # Cleaning undesired values
-        if "definition" in content:
-            del content["definition"]
+        if "default" not in content["permissions"]:
+            content["permissions"]["default"] = False
+        if "admin" not in content["permissions"]:
+            content["permissions"]["admin"] = False
 
     @staticmethod
     def format_on_edit(final_content, edit_content):
 
     @staticmethod
     def format_on_edit(final_content, edit_content):
@@ -834,49 +1210,17 @@ class RoleTopicAuth(BaseTopic):
         :param edit_content: alterations to be include
         :return: None, but final_content is modified
         """
         :param edit_content: alterations to be include
         :return: None, but final_content is modified
         """
-        final_content["_admin"]["modified"] = time()
-
-        ignore_fields = ["_id", "name", "_admin"]
-        delete_keys = [key for key in final_content.keys() if key not in ignore_fields]
+        if "_admin" in final_content:
+            final_content["_admin"]["modified"] = time()
 
 
-        for key in delete_keys:
-            del final_content[key]
+        if "permissions" not in final_content:
+            final_content["permissions"] = {}
 
 
-        # Saving the role definition
-        if "definition" in edit_content and edit_content["definition"]:
-            for role_def, value in edit_content["definition"].items():
-                if role_def == ".":
-                    final_content["root"] = value
-                else:
-                    final_content[role_def.replace(".", ":")] = value
-
-        if "root" not in final_content:
-            final_content["root"] = False
-
-    @staticmethod
-    def format_on_show(content):
-        """
-        Modifies the content of the role information to separate the role 
-        metadata from the role definition. Eases the reading process of the
-        role definition.
-
-        :param definition: role definition to be processed
-        """
-        ignore_fields = ["_admin", "_id", "name", "root"]
-        content_keys = list(content.keys())
-        definition = dict(content)
-        
-        for key in content_keys:
-            if key in ignore_fields:
-                del definition[key]
-            if ":" not in key:
-                del content[key]
-                continue
-            definition[key.replace(":", ".")] = definition[key]
-            del definition[key]
-            del content[key]
-        
-        content["definition"] = definition
+        if "default" not in final_content["permissions"]:
+            final_content["permissions"]["default"] = False
+        if "admin" not in final_content["permissions"]:
+            final_content["permissions"]["admin"] = False
+        return None
 
     def show(self, session, _id):
         """
 
     def show(self, session, _id):
         """
@@ -886,14 +1230,14 @@ class RoleTopicAuth(BaseTopic):
         :param _id: server internal id
         :return: dictionary, raise exception if not found.
         """
         :param _id: server internal id
         :return: dictionary, raise exception if not found.
         """
-        filter_db = self._get_project_filter(session, write=False, show_all=True)
-        filter_db["_id"] = _id
-
-        role = self.db.get_one(self.topic, filter_db)
-        new_role = dict(role)
-        self.format_on_show(new_role)
-
-        return new_role
+        filter_q = {BaseTopic.id_field(self.topic, _id): _id}
+        # roles = self.auth.get_role_list(filter_q)
+        roles = self.list(session, filter_q)   # To allow default filtering (Bug 853)
+        if not roles:
+            raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q))
+        elif len(roles) > 1:
+            raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q))
+        return roles[0]
 
     def list(self, session, filter_q=None):
         """
 
     def list(self, session, filter_q=None):
         """
@@ -903,18 +1247,13 @@ class RoleTopicAuth(BaseTopic):
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
         :param filter_q: filter of data to be applied
         :return: The list, it can be empty if no one match the filter.
         """
-        if not filter_q:
-            filter_q = {}
-
-        roles = self.db.get_list(self.topic, filter_q)
-        new_roles = []
-
-        for role in roles:
-            new_role = dict(role)
-            self.format_on_show(new_role)
-            new_roles.append(new_role)
-
-        return new_roles
+        role_list = self.auth.get_role_list(filter_q)
+        if not session["allow_show_user_project_role"]:
+            # Bug 853 - Default filtering
+            user = self.auth.get_user(session["username"])
+            roles = [prm["role"] for prm in user["project_role_mappings"]]
+            role_list = [role for role in role_list if role["_id"] in roles]
+        return role_list
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
@@ -925,41 +1264,49 @@ class RoleTopicAuth(BaseTopic):
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
         :param indata: data to be inserted
         :param kwargs: used to override the indata descriptor
         :param headers: http request headers
-        :return: _id: identity of the inserted data.
+        :return: _id: identity of the inserted data, operation _id (None)
         """
         try:
         """
         try:
-            content = BaseTopic._remove_envelop(indata)
+            content = self._remove_envelop(indata)
 
             # Override descriptor with query string kwargs
 
             # Override descriptor with query string kwargs
-            BaseTopic._update_input_with_kwargs(content, kwargs)
+            self._update_input_with_kwargs(content, kwargs)
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
             self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
             content = self._validate_input_new(content, session["force"])
             self.check_conflict_on_new(session, content)
             self.format_on_new(content, project_id=session["project_id"], make_public=session["public"])
-            role_name = content["name"]
-            role = self.auth.create_role(role_name)
-            content["_id"] = role["_id"]
-            _id = self.db.create(self.topic, content)
-            rollback.append({"topic": self.topic, "_id": _id})
-            # self._send_msg("create", content)
-            return _id
+            role_name = content["name"]
+            rid = self.auth.create_role(content)
+            content["_id"] = rid
+            _id = self.db.create(self.topic, content)
+            rollback.append({"topic": self.topic, "_id": rid})
+            # self._send_msg("created", content, not_send_msg=not_send_msg)
+            return rid, None
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
-    def delete(self, session, _id, dry_run=False):
+    def delete(self, session, _id, dry_run=False, not_send_msg=None):
         """
         Delete item by its internal _id
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
         """
         Delete item by its internal _id
 
         :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
         :param _id: server internal id
         :param dry_run: make checking but do not delete
+        :param not_send_msg: To not send message (False) or store content (list) instead
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
-        self.check_conflict_on_del(session, _id)
-        filter_q = self._get_project_filter(session, write=True, show_all=True)
-        filter_q["_id"] = _id
+        filter_q = {BaseTopic.id_field(self.topic, _id): _id}
+        roles = self.auth.get_role_list(filter_q)
+        if not roles:
+            raise AuthconnNotFoundException("Not found any role with filter {}".format(filter_q))
+        elif len(roles) > 1:
+            raise AuthconnConflictException("Found more than one role with filter {}".format(filter_q))
+        rid = roles[0]["_id"]
+        self.check_conflict_on_del(session, rid, None)
+        # filter_q = {"_id": _id}
+        # filter_q = {BaseTopic.id_field(self.topic, _id): _id}   # To allow role addressing by name
         if not dry_run:
         if not dry_run:
-            self.auth.delete_role(_id)
-            v = self.db.del_one(self.topic, filter_q)
+            v = self.auth.delete_role(rid)
+            v = self.db.del_one(self.topic, filter_q)
             return v
         return None
 
             return v
         return None
 
@@ -974,19 +1321,15 @@ class RoleTopicAuth(BaseTopic):
         :param content:
         :return: _id: identity of the inserted data.
         """
         :param content:
         :return: _id: identity of the inserted data.
         """
-        indata = self._remove_envelop(indata)
-
-        # Override descriptor with query string kwargs
         if kwargs:
         if kwargs:
-            BaseTopic._update_input_with_kwargs(indata, kwargs)
+            self._update_input_with_kwargs(indata, kwargs)
         try:
             indata = self._validate_input_edit(indata, force=session["force"])
         try:
             indata = self._validate_input_edit(indata, force=session["force"])
-
             if not content:
                 content = self.show(session, _id)
             if not content:
                 content = self.show(session, _id)
+            deep_update_rfc7396(content, indata)
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             self.format_on_edit(content, indata)
             self.check_conflict_on_edit(session, content, indata, _id=_id)
             self.format_on_edit(content, indata)
-            self.db.replace(self.topic, _id, content)
-            return id
+            self.auth.update_role(content)
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)