adding user, project and ids to logs for traceability
[osm/NBI.git] / osm_nbi / admin_topics.py
index cbe2f4c..187ca82 100644 (file)
@@ -25,6 +25,7 @@ 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 authconn_keystone import AuthconnKeystone
 
 __author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
 
@@ -58,7 +59,7 @@ class UserTopic(BaseTopic):
             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):
@@ -100,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()
+        return None
 
     def edit(self, session, _id, indata=None, kwargs=None, content=None):
         if not session["admin"]:
@@ -195,182 +197,191 @@ class ProjectTopic(BaseTopic):
         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):
+        """
+        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):
+        """
+        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)
 
+    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
+        """
+
         # 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"])
+            if edit_content.get("config") and self.config_to_encrypt:
+                for p in self.config_to_encrypt:
                     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):
-        BaseTopic.format_on_new(content, project_id=project_id, make_public=make_public)
+        """
+        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.1"
 
         # 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"])
+        if content.get("config") and self.config_to_encrypt:
+            for p in self.config_to_encrypt:
                 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"
 
+        # 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):
         """
         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, ...
+        :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)
 
-class WimAccountTopic(BaseTopic):
-    topic = "wim_accounts"
-    topic_msg = "wim_account"
-    schema_new = wim_account_new_schema
-    schema_edit = wim_account_edit_schema
-    multiproject = True
-    wim_config_encrypted = ()
+        self.check_conflict_on_del(session, _id, db_content)
+        if dry_run:
+            return None
 
-    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)
+        # remove reference from project_read. If not last delete
+        if session["project_id"]:
+            for project_id in session["project_id"]:
+                if project_id in db_content["_admin"]["projects_read"]:
+                    db_content["_admin"]["projects_read"].remove(project_id)
+                if project_id in db_content["_admin"]["projects_write"]:
+                    db_content["_admin"]["projects_write"].remove(project_id)
+        else:
+            db_content["_admin"]["projects_read"].clear()
+            db_content["_admin"]["projects_write"].clear()
 
-        # 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)
+        update_dict = {"_admin.projects_read": db_content["_admin"]["projects_read"],
+                       "_admin.projects_write": db_content["_admin"]["projects_write"]
+                       }
 
-    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"
+        # check if there are projects referencing it (apart from ANY that means public)....
+        if db_content["_admin"]["projects_read"] and (len(db_content["_admin"]["projects_read"]) > 1 or
+                                                      db_content["_admin"]["projects_read"][0] != "ANY"):
+            self.db.set_one(self.topic, filter_q, update_dict=update_dict)  # remove references but not delete
+            return None
 
-        # 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"])
+        # 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})
+        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})
+        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
+    password_to_encrypt = "vim_password"
+    config_to_encrypt = ("admin_password", "nsx_password", "vcenter_password")
 
-        content["_admin"]["operationalState"] = "PROCESSING"
 
-    def delete(self, session, _id, dry_run=False):
-        """
-        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, ...
-        """
-        # 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
+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
-
-    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 and edit_content.get("password"):
-            final_content["password"] = self.db.encrypt(edit_content["password"], 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("password"):
-            content["password"] = self.db.encrypt(content["password"], schema_version=schema_version,
-                                                  salt=content["_id"])
-
-        content["_admin"]["operationalState"] = "PROCESSING"
-
-    def delete(self, session, _id, dry_run=False):
-        """
-        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, ...
-        """
-        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
+    password_to_encrypt = "password"
+    config_to_encrypt = ()
 
 
 class UserTopicAuth(UserTopic):
@@ -393,7 +404,7 @@ class UserTopicAuth(UserTopic):
         """
         username = indata.get("username")
         if is_valid_uuid(username):
-            raise EngineException("username '{}' cannot be a uuid format".format(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
@@ -401,8 +412,13 @@ class UserTopicAuth(UserTopic):
             raise EngineException("username '{}' is already used".format(username), HTTPStatus.CONFLICT)
 
         if "projects" in indata.keys():
-            raise EngineException("Format invalid: the keyword \"projects\" is not allowed for keystone authentication",
-                                  HTTPStatus.BAD_REQUEST)
+            # convert to new format project_role_mappings
+            if not indata.get("project_role_mappings"):
+                indata["project_role_mappings"] = []
+            for project in indata["projects"]:
+                indata["project_role_mappings"].append({"project": project, "role": "project_user"})
+            # 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):
         """
@@ -418,7 +434,7 @@ class UserTopicAuth(UserTopic):
         if "username" in edit_content:
             username = edit_content.get("username")
             if is_valid_uuid(username):
-                raise EngineException("username '{}' cannot be an uuid format".format(username),
+                raise EngineException("username '{}' cannot have an uuid format".format(username),
                                       HTTPStatus.UNPROCESSABLE_ENTITY)
 
             # Check that username is not used, regardless keystone already checks this
@@ -703,7 +719,7 @@ class ProjectTopicAuth(ProjectTopic):
         """
         project_name = indata.get("name")
         if is_valid_uuid(project_name):
-            raise EngineException("project name '{}' cannot be an uuid format".format(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})
@@ -857,8 +873,8 @@ class ProjectTopicAuth(ProjectTopic):
 
 
 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
@@ -867,6 +883,7 @@ class RoleTopicAuth(BaseTopic):
         BaseTopic.__init__(self, db, fs, msg)
         self.auth = auth
         self.operations = ops
+        self.topic = "roles_operations" if isinstance(auth, AuthconnKeystone) else "roles"
 
     @staticmethod
     def validate_role_definition(operations, role_definitions):
@@ -878,25 +895,19 @@ class RoleTopicAuth(BaseTopic):
         :param role_definitions: role definition to test
         :return: None if ok, raises ValidationError exception on error
         """
-        ignore_fields = ["_id", "_admin", "name"]
-        for role_def in role_definitions.keys():
+        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
-            if role_def == "root":
-                if isinstance(role_definitions[role_def], bool):
-                    continue
-                else:
-                    raise ValidationError("Operation authorization \".\" should be True/False.")
             if role_def[-1] == ":":
-                raise ValidationError("Operation cannot end with \".\"")
+                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:
-                raise ValidationError("No matching operation found.")
-
-            if not isinstance(role_definitions[role_def], bool):
-                raise ValidationError("Operation authorization {} should be True/False.".format(role_def))
+                raise ValidationError("Invalid permission '{}'".format(role_def))
 
     def _validate_input_new(self, input, force=False):
         """
@@ -934,11 +945,9 @@ class RoleTopicAuth(BaseTopic):
         :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 not exists
+        if self.db.get_one(self.topic, {"name": indata.get("name")}, fail_on_empty=False, fail_on_more=False):
+            raise EngineException("role name '{}' exists".format(indata["name"]), HTTPStatus.CONFLICT)
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
@@ -950,12 +959,16 @@ class RoleTopicAuth(BaseTopic):
         :param _id: internal _id
         :return: None or raises EngineException
         """
-        roles = self.auth.get_role_list()
-        system_admin_role = [role for role in roles
-                             if role["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
 
-        if _id == system_admin_role["_id"]:
-            raise EngineException("You cannot edit system_admin role", 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):
+                raise EngineException("role name '{}' exists".format(role_name), HTTPStatus.CONFLICT)
 
     def check_conflict_on_del(self, session, _id, db_content):
         """
@@ -967,10 +980,9 @@ class RoleTopicAuth(BaseTopic):
         :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 role["name"] == "system_admin"][0]
+        system_admin_roles = [role for role in roles if role["name"] == "system_admin"]
 
-        if _id == system_admin_role["_id"]:
+        if system_admin_roles and _id == system_admin_roles[0]["_id"]:
             raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
 
     @staticmethod
@@ -990,12 +1002,13 @@ class RoleTopicAuth(BaseTopic):
             content["_admin"]["created"] = now
         content["_admin"]["modified"] = now
 
-        if ":" in content.keys():
-            content["root"] = content[":"]
-            del content[":"]
+        if "permissions" not in content:
+            content["permissions"] = {}
 
-        if "root" not in content.keys():
-            content["root"] = False
+        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):
@@ -1008,78 +1021,71 @@ class RoleTopicAuth(BaseTopic):
         """
         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]
-
-        for key in delete_keys:
-            del final_content[key]
-
-        # Saving the role definition
-        for role_def, value in edit_content.items():
-            final_content[role_def] = value
-
-        if ":" in final_content.keys():
-            final_content["root"] = final_content[":"]
-            del final_content[":"]
-
-        if "root" not in final_content.keys():
-            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
-        """
-        content["_id"] = str(content["_id"])
-
-    def show(self, session, _id):
-        """
-        Get complete information on an topic
-
-        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :param _id: server internal id
-        :return: dictionary, raise exception if not found.
-        """
-        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
+        if "permissions" not in final_content:
+            final_content["permissions"] = {}
 
-    def list(self, session, filter_q=None):
-        """
-        Get a list of the topic that matches a filter
-
-        :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
-        :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 = {}
-
-        if ":" in filter_q:
-            filter_q["root"] = filter_q[":"]
-
-        for key in filter_q.keys():
-            if key == "name":
-                continue
-            filter_q[key] = filter_q[key] in ["True", "true"]
-
-        roles = self.db.get_list(self.topic, filter_q)
-        new_roles = []
+        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
 
-        for role in roles:
-            new_role = dict(role)
-            self.format_on_show(new_role)
-            new_roles.append(new_role)
+    # @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
+    #     """
+    #     content["_id"] = str(content["_id"])
+    #
+    # def show(self, session, _id):
+    #     """
+    #     Get complete information on an topic
+    #
+    #     :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+    #     :param _id: server internal id
+    #     :return: dictionary, raise exception if not found.
+    #     """
+    #     filter_db = {"_id": _id}
+    #     filter_db = { BaseTopic.id_field(self.topic, _id): _id }   # To allow role addressing by name
+    #
+    #     role = self.db.get_one(self.topic, filter_db)
+    #     new_role = dict(role)
+    #     self.format_on_show(new_role)
+    #
+    #     return new_role
 
-        return new_roles
+    # def list(self, session, filter_q=None):
+    #     """
+    #     Get a list of the topic that matches a filter
+    #
+    #     :param session: contains "username", "admin", "force", "public", "project_id", "set_project"
+    #     :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 = {}
+    #
+    #     if ":" in filter_q:
+    #         filter_q["root"] = filter_q[":"]
+    #
+    #     for key in filter_q.keys():
+    #         if key == "name":
+    #             continue
+    #         filter_q[key] = filter_q[key] in ["True", "true"]
+    #
+    #     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
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
@@ -1093,16 +1099,16 @@ class RoleTopicAuth(BaseTopic):
         :return: _id: identity of the inserted data.
         """
         try:
-            content = BaseTopic._remove_envelop(indata)
+            content = self._remove_envelop(indata)
 
             # 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"])
             role_name = content["name"]
-            role = self.auth.create_role(role_name)
-            content["_id"] = role["_id"]
+            role_id = 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)
@@ -1120,7 +1126,8 @@ class RoleTopicAuth(BaseTopic):
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         self.check_conflict_on_del(session, _id, None)
-        filter_q = {"_id": _id}
+        # filter_q = {"_id": _id}
+        filter_q = {BaseTopic.id_field(self.topic, _id): _id}   # To allow role addressing by name
         if not dry_run:
             self.auth.delete_role(_id)
             v = self.db.del_one(self.topic, filter_q)
@@ -1138,19 +1145,6 @@ class RoleTopicAuth(BaseTopic):
         :param content:
         :return: _id: identity of the inserted data.
         """
-        indata = self._remove_envelop(indata)
-
-        # Override descriptor with query string kwargs
-        if kwargs:
-            self._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)
-            self.db.replace(self.topic, _id, content)
-            return id
-        except ValidationError as e:
-            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+        _id = super().edit(session, _id, indata, kwargs, content)
+        if indata.get("name"):
+            self.auth.update_role(_id, name=indata.get("name"))