bug 767: uniform role format
[osm/NBI.git] / osm_nbi / admin_topics.py
index 2bca416..071ed3b 100644 (file)
@@ -64,7 +64,14 @@ class UserTopic(BaseTopic):
                                        fail_on_more=False):
                     raise EngineException("project '{}' does not exist".format(p), HTTPStatus.CONFLICT)
 
                                        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 +84,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[0] 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):
@@ -144,7 +158,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"]:
@@ -355,8 +376,8 @@ class SdnTopic(BaseTopic):
 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):
         UserTopic.__init__(self, db, fs, msg)
 
     def __init__(self, db, fs, msg, auth):
         UserTopic.__init__(self, db, fs, msg)
@@ -371,10 +392,17 @@ 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 is_valid_uuid(username):
+            raise EngineException("username '{}' cannot be 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 username in user_list:
-            raise EngineException("username '{}' exists".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)
 
     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 +414,88 @@ 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 be an 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 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):
+    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)
+
+    # @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
+    #     """
+    #     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 final_content descriptor to include the modified date.
+    #
+    #     NOTE: No password salt required because the authentication backend
+    #     should handle these security concerns.
+    #
+    #     :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"]
 
     @staticmethod
 
     @staticmethod
-    def format_on_new(content, project_id=None, make_public=False):
+    def format_on_show(content):
         """
         """
-        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
+        Modifies the content of the role information to separate the role
+        metadata from the role definition.
         """
         """
-        BaseTopic.format_on_new(content, make_public=False)
-        content["_id"] = content["username"]
-        content["password"] = content["password"]
+        project_role_mappings = []
 
 
-    @staticmethod
-    def format_on_edit(final_content, edit_content):
-        """
-        Modifies final_content descriptor to include the modified date.
+        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"]})
 
 
-        NOTE: No password salt required because the authentication backend
-        should handle these security concerns.
+        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):
         """
@@ -463,10 +517,15 @@ 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"])
+            _id = self.auth.create_user(content["username"], content["password"])["_id"]
+
+            if "project_role_mappings" in content.keys():
+                for mapping in content["project_role_mappings"]:
+                    self.auth.assign_role_to_user(_id, mapping["project"], mapping["role"])
+
             rollback.append({"topic": self.topic, "_id": _id})
             rollback.append({"topic": self.topic, "_id": _id})
-            del content["password"]
+            del content["password"]
             # self._send_msg("create", content)
             return _id
         except ValidationError as e:
             # self._send_msg("create", content)
             return _id
         except ValidationError as e:
@@ -480,10 +539,12 @@ class UserTopicAuth(UserTopic):
         :param _id: server internal id
         :return: dictionary, raise exception if not found.
         """
         :param _id: server internal id
         :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 = {self.id_field(self.topic, _id): _id}
+        users = self.auth.get_user_list(filter_q)
 
         if len(users) == 1:
 
         if len(users) == 1:
-            return users[0]
+            return self.format_on_show(users[0])
         elif len(users) > 1:
             raise EngineException("Too many users found", HTTPStatus.CONFLICT)
         else:
         elif len(users) > 1:
             raise EngineException("Too many users found", HTTPStatus.CONFLICT)
         else:
@@ -511,44 +572,80 @@ 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 "password" in indata or "username" in indata:
+                self.auth.update_user(_id, new_name=indata.get("username"), new_password=indata.get("password"))
+            if not indata.get("remove_project_role_mappings") and not indata.get("add_project_role_mappings") and \
+                    not indata.get("project_role_mappings"):
+                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)
+
+            user = self.show(session, _id)
+            original_mapping = user["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:
+                    mappings_to_add.append(to_add)
+
+            # 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:
+                        mappings_to_add.append(to_set)
+                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)
+
+            for mapping in mappings_to_remove:
+                self.auth.remove_role_from_user(
+                    _id,
+                    mapping["project"],
+                    mapping["role"]
+                )
+
+            for mapping in mappings_to_add:
+                self.auth.assign_role_to_user(
+                    _id,
+                    mapping["project"],
+                    mapping["role"]
+                )
+
+            return "_id"
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
         except ValidationError as e:
             raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
 
@@ -559,10 +656,9 @@ 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.
         """
-        if not filter_q:
-            filter_q = {}
+        users = [self.format_on_show(user) for user in self.auth.get_user_list(filter_q)]
 
 
-        return self.auth.get_user_list(filter_q)
+        return users
 
     def delete(self, session, _id, dry_run=False):
         """
 
     def delete(self, session, _id, dry_run=False):
         """
@@ -574,7 +670,13 @@ class UserTopicAuth(UserTopic):
         :param dry_run: make checking but do not delete
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :param dry_run: make checking but do not delete
         :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
+        filter_q = {self.id_field(self.topic, _id): _id}
+        user_list = self.auth.get_user_list(filter_q)
+        if not user_list:
+            raise EngineException("User '{}' not found".format(_id), http_code=HTTPStatus.NOT_FOUND)
+        _id = user_list[0]["_id"]
+        self.check_conflict_on_del(session, _id, user_list[0])
         if not dry_run:
             v = self.auth.delete_user(_id)
             return v
         if not dry_run:
             v = self.auth.delete_user(_id)
             return v
@@ -584,8 +686,8 @@ 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):
         ProjectTopic.__init__(self, db, fs, msg)
 
     def __init__(self, db, fs, msg, auth):
         ProjectTopic.__init__(self, db, fs, msg)
@@ -599,25 +701,51 @@ 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 be an uuid format".format(project_name),
+                                  HTTPStatus.UNPROCESSABLE_ENTITY)
 
 
-        if project in project_list:
-            raise EngineException("project '{}' exists".format(project), HTTPStatus.CONFLICT)
+        project_list = self.auth.get_project_list(filter_q={"name": project_name})
 
 
-    def check_conflict_on_del(self, session, _id):
+        if project_list:
+            raise EngineException("project '{}' exists".format(project_name), HTTPStatus.CONFLICT)
+
+    def check_conflict_on_edit(self, session, final_content, edit_content, _id):
+        """
+        Check that the data to be edited/uploaded is valid
+
+        :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:
+            if is_valid_uuid(project_name):
+                raise EngineException("project name  '{}' cannot be an uuid format".format(project_name),
+                                      HTTPStatus.UNPROCESSABLE_ENTITY)
+
+            # Check that project name is not used, regardless keystone already checks this
+            if 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"]:
+        projects = self.auth.get_project_list()
+        current_project = [project for project in projects
+        #                    if project["name"] in session["project_id"]][0]
+        # TODO check that any user is using this project, raise CONFLICT exception
+        if _id == session["project_id"]:
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
             raise EngineException("You cannot delete your own project", http_code=HTTPStatus.CONFLICT)
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
@@ -656,7 +784,9 @@ 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)
 
         if len(projects) == 1:
             return projects[0]
 
         if len(projects) == 1:
             return projects[0]
@@ -673,9 +803,6 @@ 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.
         """
-        if not filter_q:
-            filter_q = {}
-
         return self.auth.get_project_list(filter_q)
 
     def delete(self, session, _id, dry_run=False):
         return self.auth.get_project_list(filter_q)
 
     def delete(self, session, _id, dry_run=False):
@@ -687,16 +814,51 @@ class ProjectTopicAuth(ProjectTopic):
         :param dry_run: make checking but do not delete
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :param dry_run: make checking but do not delete
         :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
+        filter_q = {self.id_field(self.topic, _id): _id}
+        project_list = self.auth.get_project_list(filter_q)
+        if not project_list:
+            raise EngineException("Project '{}' not found".format(_id), http_code=HTTPStatus.NOT_FOUND)
+        _id = project_list[0]["_id"]
+        self.check_conflict_on_del(session, _id, project_list[0])
         if not dry_run:
             v = self.auth.delete_project(_id)
             return v
         return None
 
         if not dry_run:
             v = self.auth.delete_project(_id)
             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)
+
+            if "name" in indata:
+                self.auth.update_project(content["_id"], indata["name"])
+        except ValidationError as e:
+            raise EngineException(e, HTTPStatus.UNPROCESSABLE_ENTITY)
+
 
 class RoleTopicAuth(BaseTopic):
     topic = "roles_operations"
 
 class RoleTopicAuth(BaseTopic):
     topic = "roles_operations"
-    topic_msg = "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
@@ -716,16 +878,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):
         """
@@ -737,8 +902,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):
@@ -751,8 +916,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):
@@ -763,11 +928,9 @@ 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 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):
         """
 
     def check_conflict_on_edit(self, session, final_content, edit_content, _id):
         """
@@ -779,24 +942,29 @@ 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
 
 
-        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):
+    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
         """
         roles = self.auth.get_role_list()
         system_admin_role = [role for role in roles
         :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]
+                             if role["name"] == "system_admin"][0]
 
         if _id == system_admin_role["_id"]:
             raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
 
         if _id == system_admin_role["_id"]:
             raise EngineException("You cannot delete system_admin role", http_code=HTTPStatus.FORBIDDEN)
@@ -817,19 +985,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):
@@ -842,85 +1005,69 @@ class RoleTopicAuth(BaseTopic):
         """
         final_content["_admin"]["modified"] = time()
 
         """
         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
-        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
-
-    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 = 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
-
-    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 = {}
-
-        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
+        if "permissions" not in final_content:
+            final_content["permissions"] = {}
+
+        if "default" not in final_content["permissions"]:
+            final_content["permissions"]["default"] = False
+        if "admin" not in final_content["permissions"]:
+            final_content["permissions"]["admin"] = 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
+
+    # 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):
         """
 
     def new(self, rollback, session, indata=None, kwargs=None, headers=None):
         """
@@ -934,16 +1081,16 @@ class RoleTopicAuth(BaseTopic):
         :return: _id: identity of the inserted data.
         """
         try:
         :return: _id: identity of the inserted data.
         """
         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"])
             role_name = content["name"]
             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)
             _id = self.db.create(self.topic, content)
             rollback.append({"topic": self.topic, "_id": _id})
             # self._send_msg("create", content)
@@ -960,9 +1107,8 @@ class RoleTopicAuth(BaseTopic):
         :param dry_run: make checking but do not delete
         :return: dictionary with deleted item _id. It raises EngineException on error: not found, conflict, ...
         """
         :param dry_run: make checking but do not delete
         :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
+        self.check_conflict_on_del(session, _id, None)
+        filter_q = {"_id": _id}
         if not dry_run:
             self.auth.delete_role(_id)
             v = self.db.del_one(self.topic, filter_q)
         if not dry_run:
             self.auth.delete_role(_id)
             v = self.db.del_one(self.topic, filter_q)
@@ -980,19 +1126,6 @@ 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:
-            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)
-            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"))