From 029405dc71e09d30e7660fa68f799737d017acbf Mon Sep 17 00:00:00 2001 From: delacruzramo Date: Thu, 26 Sep 2019 10:52:56 +0200 Subject: [PATCH 1/1] Bug 853 - Fix permissions Change-Id: Ie95624fe6cbea3cd8ee45709eb76cac51cf981f6 Signed-off-by: delacruzramo --- osm_nbi/admin_topics.py | 35 +++++++++++++++++++++++++---------- osm_nbi/auth.py | 32 +++++++++++++++++++++++--------- osm_nbi/nbi.py | 6 +++--- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/osm_nbi/admin_topics.py b/osm_nbi/admin_topics.py index c198733..71dfa3c 100644 --- a/osm_nbi/admin_topics.py +++ b/osm_nbi/admin_topics.py @@ -545,8 +545,8 @@ class UserTopicAuth(UserTopic): """ # 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) - + # 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: @@ -676,9 +676,11 @@ 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. """ - users = self.auth.get_user_list(filter_q) - - return users + 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): """ @@ -827,8 +829,8 @@ class ProjectTopicAuth(ProjectTopic): """ # 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.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: @@ -844,7 +846,13 @@ 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. """ - return self.auth.get_project_list(filter_q) + 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): """ @@ -1071,7 +1079,8 @@ class RoleTopicAuth(BaseTopic): :return: dictionary, raise exception if not found. """ filter_q = {BaseTopic.id_field(self.topic, _id): _id} - roles = self.auth.get_role_list(filter_q) + # 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: @@ -1086,7 +1095,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. """ - return self.auth.get_role_list(filter_q) + 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): """ diff --git a/osm_nbi/auth.py b/osm_nbi/auth.py index 36b6dc5..8bb479d 100644 --- a/osm_nbi/auth.py +++ b/osm_nbi/auth.py @@ -45,8 +45,8 @@ from osm_nbi.authconn_internal import AuthconnInternal # Comment out for testi from osm_common import dbmongo from osm_common import dbmemory from osm_common.dbbase import DbException +from osm_nbi.validation import is_valid_uuid from itertools import chain - from uuid import uuid4 @@ -320,7 +320,7 @@ class Authenticator: self.operation_to_allowed_roles = permissions - def authorize(self, role_permission=None, query_string_operations=None): + def authorize(self, role_permission=None, query_string_operations=None, item_id=None): token = None user_passwd64 = None try: @@ -358,8 +358,10 @@ class Authenticator: # TODO add to token info remote host, port if role_permission: - self.check_permissions(token_info, cherrypy.request.method, role_permission, - query_string_operations) + RBAC_auth = self.check_permissions(token_info, cherrypy.request.method, role_permission, + query_string_operations, item_id) + token_info["allow_show_user_project_role"] = RBAC_auth + return token_info except AuthException as e: if not isinstance(e, AuthExceptionUnauthorized): @@ -427,7 +429,7 @@ class Authenticator: except KeyError: raise AuthException("Token '{}' not found".format(token), http_code=HTTPStatus.NOT_FOUND) - def check_permissions(self, token_info, method, role_permission=None, query_string_operations=None): + def check_permissions(self, token_info, method, role_permission=None, query_string_operations=None, item_id=None): """ Checks that operation has permissions to be done, base on the assigned roles to this user project :param token_info: Dictionary that contains "roles" with a list of assigned roles. @@ -437,7 +439,9 @@ class Authenticator: :param role_permission: role permission name of the operation required :param query_string_operations: list of possible admin query strings provided by user. It is checked that the assigned role allows this query string for this method - :return: None if granted, exception if not allowed + :param item_id: item identifier if included in the URL, None otherwise + :return: True if access granted by permission rules, False if access granted by default rules (Bug 853) + :raises: AuthExceptionUnauthorized if access denied """ roles_required = self.operation_to_allowed_roles[role_permission] @@ -451,19 +455,29 @@ class Authenticator: break if "anonymous" in roles_required: - return + return True operation_allowed = False for role in roles_allowed: if role in roles_required: operation_allowed = True # if query_string operations, check if this role allows it if not query_string_operations: - return + return True for query_string_operation in query_string_operations: if role not in self.operation_to_allowed_roles[query_string_operation]: break else: - return + return True + + # Bug 853 - Final Solution + # User/Project/Role whole listings are filtered elsewhere + # uid, pid, rid = ("user_id", "project_id", "id") if is_valid_uuid(id) else ("username", "project_name", "name") + uid = "user_id" if is_valid_uuid(item_id) else "username" + if (role_permission in ["projects:get", "projects:id:get", "roles:get", "roles:id:get", "users:get"]) \ + or (role_permission == "users:id:get" and item_id == token_info[uid]): + # or (role_permission == "projects:id:get" and item_id == token_info[pid]) \ + # or (role_permission == "roles:id:get" and item_id in [role[rid] for role in token_info["roles"]]): + return False if not operation_allowed: raise AuthExceptionUnauthorized("Access denied: lack of permissions.") diff --git a/osm_nbi/nbi.py b/osm_nbi/nbi.py index 03e8727..665df75 100644 --- a/osm_nbi/nbi.py +++ b/osm_nbi/nbi.py @@ -861,7 +861,8 @@ class Server(object): method: show, list, delete, write """ admin_query = {"force": False, "project_id": (token_info["project_id"], ), "username": token_info["username"], - "admin": token_info["admin"], "public": None} + "admin": token_info["admin"], "public": None, + "allow_show_user_project_role": token_info["allow_show_user_project_role"]} if kwargs: # FORCE if "FORCE" in kwargs: @@ -944,8 +945,7 @@ class Server(object): query_string_operations = self._extract_query_string_operations(kwargs, method) if main_topic == "admin" and topic == "tokens": return self.token(method, _id, kwargs) - - token_info = self.authenticator.authorize(role_permission, query_string_operations) + token_info = self.authenticator.authorize(role_permission, query_string_operations, _id) engine_session = self._manage_admin_query(token_info, kwargs, method, _id) indata = self._format_in(kwargs) engine_topic = topic -- 2.17.1