diff --git a/UPDATING.md b/UPDATING.md index 054afafe2b890..47a5dd396e1ec 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -23,6 +23,8 @@ assists people when migrating to a new version. ## Next +* [10674](https://github.com/apache/incubator-superset/pull/10674): Breaking change: PUBLIC_ROLE_LIKE_GAMMA was removed is favour of the new PUBLIC_ROLE_LIKE so it can be set it whatever role you want. + * [10590](https://github.com/apache/incubator-superset/pull/10590): Breaking change: this PR will convert iframe chart into dashboard markdown component, and remove all `iframe`, `separator`, and `markup` slices (and support) from Superset. If you have important data in those slices, please backup manually. * [10562](https://github.com/apache/incubator-superset/pull/10562): EMAIL_REPORTS_WEBDRIVER is deprecated use WEBDRIVER_TYPE instead. diff --git a/superset/config.py b/superset/config.py index 0dfde99054ed4..ff136e462241a 100644 --- a/superset/config.py +++ b/superset/config.py @@ -260,10 +260,10 @@ def _try_json_readsha( # pylint: disable=unused-argument # --------------------------------------------------- # Roles config # --------------------------------------------------- -# Grant public role the same set of permissions as for the GAMMA role. +# Grant public role the same set of permissions as for a selected builtin role. # This is useful if one wants to enable anonymous users to view # dashboards. Explicit grant on specific datasets is still required. -PUBLIC_ROLE_LIKE_GAMMA = False +PUBLIC_ROLE_LIKE: Optional[str] = None # --------------------------------------------------- # Babel config for translations diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index e4065aeeea15d..601a69ddb63dd 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -585,7 +585,7 @@ def get_schema_perm(self) -> Optional[str]: return security_manager.get_schema_perm(self.database, self.schema) def get_perm(self) -> str: - return ("[{obj.database}].[{obj.table_name}]" "(id:{obj.id})").format(obj=self) + return f"[{self.database}].[{self.table_name}](id:{self.id})" @property def name(self) -> str: diff --git a/superset/security/manager.py b/superset/security/manager.py index f939d6d258eb8..f5376d8f6d215 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -17,6 +17,7 @@ # pylint: disable=too-few-public-methods """A set of constants and methods to manage permissions and security""" import logging +import re from typing import Any, Callable, cast, List, Optional, Set, Tuple, TYPE_CHECKING, Union from flask import current_app, g @@ -172,6 +173,15 @@ class SupersetSecurityManager( # pylint: disable=too-many-public-methods ACCESSIBLE_PERMS = {"can_userinfo"} + data_access_permissions = ( + "database_access", + "schema_access", + "datasource_access", + "all_datasource_access", + "all_database_access", + "all_query_access", + ) + def get_schema_perm( # pylint: disable=no-self-use self, database: Union["Database", str], schema: Optional[str] = None ) -> Optional[str]: @@ -609,8 +619,15 @@ def sync_role_definitions(self) -> None: self.set_role("granter", self._is_granter_pvm) self.set_role("sql_lab", self._is_sql_lab_pvm) + # Configure public role + if conf["PUBLIC_ROLE_LIKE"]: + self.copy_role(conf["PUBLIC_ROLE_LIKE"], self.auth_role_public, merge=True) if conf.get("PUBLIC_ROLE_LIKE_GAMMA", False): - self.set_role("Public", self._is_gamma_pvm) + logger.warning( + "The config `PUBLIC_ROLE_LIKE_GAMMA` is deprecated and will be removed " + "in Superset 1.0. Please use `PUBLIC_ROLE_LIKE ` instead." + ) + self.copy_role("Gamma", self.auth_role_public, merge=True) self.create_missing_perms() @@ -618,6 +635,58 @@ def sync_role_definitions(self) -> None: self.get_session.commit() self.clean_perms() + def _get_pvms_from_builtin_role(self, role_name: str) -> List[PermissionView]: + """ + Gets a list of model PermissionView permissions infered from a builtin role + definition + """ + role_from_permissions_names = self.builtin_roles.get(role_name, []) + all_pvms = self.get_session.query(PermissionView).all() + role_from_permissions = [] + for pvm_regex in role_from_permissions_names: + view_name_regex = pvm_regex[0] + permission_name_regex = pvm_regex[1] + for pvm in all_pvms: + if re.match(view_name_regex, pvm.view_menu.name) and re.match( + permission_name_regex, pvm.permission.name + ): + if pvm not in role_from_permissions: + role_from_permissions.append(pvm) + return role_from_permissions + + def copy_role( + self, role_from_name: str, role_to_name: str, merge: bool = True + ) -> None: + """ + Copies permissions from a role to another. + + Note: Supports regex defined builtin roles + + :param role_from_name: The FAB role name from where the permissions are taken + :param role_to_name: The FAB role name from where the permissions are copied to + :param merge: If merge is true, keep data access permissions + if they already exist on the target role + """ + + logger.info("Copy/Merge %s to %s", role_from_name, role_to_name) + # If it's a builtin role extract permissions from it + if role_from_name in self.builtin_roles: + role_from_permissions = self._get_pvms_from_builtin_role(role_from_name) + else: + role_from_permissions = list(self.find_role(role_from_name).permissions) + role_to = self.add_role(role_to_name) + # If merge, recover existing data access permissions + if merge: + for permission_view in role_to.permissions: + if ( + permission_view not in role_from_permissions + and permission_view.permission.name in self.data_access_permissions + ): + role_from_permissions.append(permission_view) + role_to.permissions = role_from_permissions + self.get_session.merge(role_to) + self.get_session.commit() + def set_role( self, role_name: str, pvm_check: Callable[[PermissionView], bool] ) -> None: @@ -629,14 +698,15 @@ def set_role( """ logger.info("Syncing %s perms", role_name) - sesh = self.get_session - pvms = sesh.query(PermissionView).all() + pvms = self.get_session.query(PermissionView).all() pvms = [p for p in pvms if p.permission and p.view_menu] role = self.add_role(role_name) - role_pvms = [p for p in pvms if pvm_check(p)] + role_pvms = [ + permission_view for permission_view in pvms if pvm_check(permission_view) + ] role.permissions = role_pvms - sesh.merge(role) - sesh.commit() + self.get_session.merge(role) + self.get_session.commit() def _is_admin_only(self, pvm: Model) -> bool: """ diff --git a/superset/templates/superset/public_welcome.html b/superset/templates/superset/public_welcome.html new file mode 100644 index 0000000000000..0d821512eb782 --- /dev/null +++ b/superset/templates/superset/public_welcome.html @@ -0,0 +1,23 @@ +{# +Licensed to the Apache Software Foundation (ASF) under one +or more contributor license agreements. See the NOTICE file +distributed with this work for additional information +regarding copyright ownership. The ASF licenses this file +to you under the Apache License, Version 2.0 (the +"License"); you may not use this file except in compliance +with the License. You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on an +"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. +#} +{% extends "appbuilder/base.html" %} + +{% block content %} +