This is an automated email from the ASF dual-hosted git repository. maximebeauchemin pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/incubator-superset.git
The following commit(s) were added to refs/heads/master by this push: new 315a11d fix: shut off unneeded endpoints (#8960) 315a11d is described below commit 315a11dfe265c6a20ed014610eb7351966292c38 Author: Maxime Beauchemin <maximebeauche...@gmail.com> AuthorDate: Thu Jan 23 11:25:15 2020 -0500 fix: shut off unneeded endpoints (#8960) * fix: shut off all uneeded endpoints We recently added a new feature to FAB allowing to whitelist the needed endpoints in ModelView and ModelRestApi. First, we set our base wrapper class to an empty set, forcing each class inheriting from it to explicitely turn on the endpoints that Superset intends to use. Second, we go ModelView by ModelView to whitelist the actual endpoints used in the app. Notes: * as a result a large set of [unneeded] permissions should be cleaned up * outside of the "private" use of endpoints in the app, people that have been using endpoints in their environment for other purposes may experience loss of functionality * Tweaking * Reduce the amount of endpoints using white lists * Fix, included needed endpoints for dashboard and druid * Drying things up * fixes * limiting more endpoints * Read only on some FAB model views * fixing some tests * fixes * Fixing more tests * Addressing comments * Drying up route_methods * further drying Co-authored-by: Daniel Vaz Gaspar <danielvazgas...@gmail.com> --- .gitignore | 1 + .rat-excludes | 2 + requirements.txt | 2 +- superset/app.py | 50 ++++++++-------------- .../assets/src/dashboard/actions/sliceEntities.js | 2 +- superset/connectors/druid/views.py | 7 ++- superset/connectors/sqla/views.py | 5 +++ superset/constants.py | 40 +++++++++++++++++ superset/security/manager.py | 10 +++++ superset/views/annotations.py | 3 ++ superset/views/base_api.py | 11 +++++ superset/views/chart/api.py | 9 ---- superset/views/chart/views.py | 47 ++++++++++---------- superset/views/core.py | 17 ++------ superset/views/dashboard/api.py | 18 +++----- superset/views/dashboard/views.py | 25 +++++------ superset/views/database/api.py | 15 ++----- superset/views/database/views.py | 22 +--------- superset/views/log/api.py | 12 +----- superset/views/log/views.py | 2 + superset/views/schedules.py | 2 + superset/views/sql_lab.py | 9 ++++ tests/core_tests.py | 2 +- tests/import_export_tests.py | 7 ++- tests/security_tests.py | 40 ++++------------- 25 files changed, 173 insertions(+), 187 deletions(-) diff --git a/.gitignore b/.gitignore index b228b98..5dc500e 100644 --- a/.gitignore +++ b/.gitignore @@ -20,6 +20,7 @@ *.pyc *.sqllite *.swp +.bento* .cache-loader .coverage .DS_Store diff --git a/.rat-excludes b/.rat-excludes index 61d68b5..79ed787 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -29,7 +29,9 @@ apache_superset.egg-info .*json .*csv # Generated doc files +env/* docs/_build/* +docs/_modules/* _build/* _static/* .buildinfo diff --git a/requirements.txt b/requirements.txt index a89d859..fb84146 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ croniter==0.3.31 cryptography==2.8 decorator==4.4.1 # via retry defusedxml==0.6.0 # via python3-openid -flask-appbuilder==2.2.1 +flask-appbuilder==2.2.2rc3 flask-babel==0.12.2 # via flask-appbuilder flask-caching==1.8.0 flask-compress==1.4.0 diff --git a/superset/app.py b/superset/app.py index 9838197..4a904ee 100644 --- a/superset/app.py +++ b/superset/app.py @@ -149,21 +149,15 @@ class SupersetAppInitializer: CssTemplateAsyncModelView, ) from superset.views.chart.api import ChartRestApi - from superset.views.chart.views import SliceModelView, SliceAsync, SliceAddView + from superset.views.chart.views import SliceModelView, SliceAsync from superset.views.dashboard.api import DashboardRestApi from superset.views.dashboard.views import ( DashboardModelView, Dashboard, - DashboardAddView, DashboardModelViewAsync, ) from superset.views.database.api import DatabaseRestApi - from superset.views.database.views import ( - DatabaseView, - DatabaseTablesAsync, - CsvToDatabaseView, - DatabaseAsync, - ) + from superset.views.database.views import DatabaseView, CsvToDatabaseView from superset.views.datasource import Datasource from superset.views.log.api import LogRestApi from superset.views.log.views import LogModelView @@ -218,6 +212,16 @@ class SupersetAppInitializer: category_label=__("Sources"), category_icon="fa-database", ) + appbuilder.add_link( + "Tables", + label=__("Tables"), + href="/tablemodelview/list/?_flt_1_is_sqllab_view=y", + icon="fa-table", + category="Sources", + category_label=__("Sources"), + category_icon="fa-table", + ) + appbuilder.add_separator("Sources") appbuilder.add_view( SliceModelView, "Charts", @@ -259,16 +263,12 @@ class SupersetAppInitializer: appbuilder.add_view_no_menu(CssTemplateAsyncModelView) appbuilder.add_view_no_menu(CsvToDatabaseView) appbuilder.add_view_no_menu(Dashboard) - appbuilder.add_view_no_menu(DashboardAddView) appbuilder.add_view_no_menu(DashboardModelViewAsync) - appbuilder.add_view_no_menu(DatabaseAsync) - appbuilder.add_view_no_menu(DatabaseTablesAsync) appbuilder.add_view_no_menu(Datasource) appbuilder.add_view_no_menu(KV) appbuilder.add_view_no_menu(R) appbuilder.add_view_no_menu(SavedQueryView) appbuilder.add_view_no_menu(SavedQueryViewApi) - appbuilder.add_view_no_menu(SliceAddView) appbuilder.add_view_no_menu(SliceAsync) appbuilder.add_view_no_menu(SqlLab) appbuilder.add_view_no_menu(SqlMetricInlineView) @@ -283,12 +283,6 @@ class SupersetAppInitializer: # Add links # appbuilder.add_link( - __("Saved Queries"), - href="/sqllab/my_queries/", - icon="fa-save", - category="SQL Lab", - ) - appbuilder.add_link( "Import Dashboards", label=__("Import Dashboards"), href="/superset/import_dashboards", @@ -307,6 +301,12 @@ class SupersetAppInitializer: category_label=__("SQL Lab"), ) appbuilder.add_link( + __("Saved Queries"), + href="/sqllab/my_queries/", + icon="fa-save", + category="SQL Lab", + ) + appbuilder.add_link( "Query Search", label=_("Query Search"), href="/superset/sqllab#search", @@ -324,23 +324,11 @@ class SupersetAppInitializer: category_label=__("Sources"), category_icon="fa-wrench", ) - appbuilder.add_link( - "Tables", - label=__("Tables"), - href="/tablemodelview/list/?_flt_1_is_sqllab_view=y", - icon="fa-table", - category="Sources", - category_label=__("Sources"), - category_icon="fa-table", - ) # # Conditionally setup log views # - if ( - not self.config["FAB_ADD_SECURITY_VIEWS"] is False - or self.config["SUPERSET_LOG_VIEW"] is False - ): + if self.config["FAB_ADD_SECURITY_VIEWS"] and self.config["SUPERSET_LOG_VIEW"]: appbuilder.add_api(LogRestApi) appbuilder.add_view( LogModelView, diff --git a/superset/assets/src/dashboard/actions/sliceEntities.js b/superset/assets/src/dashboard/actions/sliceEntities.js index efee23e..b0db626 100644 --- a/superset/assets/src/dashboard/actions/sliceEntities.js +++ b/superset/assets/src/dashboard/actions/sliceEntities.js @@ -46,7 +46,7 @@ export function fetchAllSlices(userId) { dispatch(fetchAllSlicesStarted()); return SupersetClient.get({ - endpoint: `/sliceaddview/api/read?_flt_0_created_by=${userId}`, + endpoint: `/sliceasync/api/read?_flt_0_created_by=${userId}`, }) .then(({ json }) => { const slices = {}; diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py index 523bfcd..5a2cea0 100644 --- a/superset/connectors/druid/views.py +++ b/superset/connectors/druid/views.py @@ -30,6 +30,7 @@ from wtforms.ext.sqlalchemy.fields import QuerySelectField from superset import app, appbuilder, db, security_manager from superset.connectors.base.views import DatasourceModelView from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import ( BaseSupersetView, @@ -47,6 +48,7 @@ from . import models class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.DruidColumn) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Columns") show_title = _("Show Druid Column") @@ -133,6 +135,7 @@ class DruidColumnInlineView(CompactCRUDMixin, SupersetModelView): class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.DruidMetric) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Metrics") show_title = _("Show Druid Metric") @@ -185,7 +188,7 @@ class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.DruidCluster) - + include_route_methods = RouteMethod.CRUD_SET list_title = _("Druid Clusters") show_title = _("Show Druid Cluster") add_title = _("Add Druid Cluster") @@ -247,7 +250,7 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): class DruidDatasourceModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.DruidDatasource) - + include_route_methods = RouteMethod.CRUD_SET list_title = _("Druid Datasources") show_title = _("Show Druid Datasource") add_title = _("Add Druid Datasource") diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index c51057a..c63c4f8 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -31,6 +31,7 @@ from wtforms.validators import Regexp from superset import appbuilder, db, security_manager from superset.connectors.base.views import DatasourceModelView +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import ( DatasourceFilter, @@ -48,6 +49,8 @@ logger = logging.getLogger(__name__) class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.TableColumn) + # TODO TODO, review need for this on related_views + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Columns") show_title = _("Show Column") @@ -164,6 +167,7 @@ class TableColumnInlineView(CompactCRUDMixin, SupersetModelView): class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): datamodel = SQLAInterface(models.SqlMetric) + include_route_methods = RouteMethod.RELATED_VIEW_SET list_title = _("Metrics") show_title = _("Show Metric") @@ -223,6 +227,7 @@ class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): class TableModelView(DatasourceModelView, DeleteMixin, YamlExportMixin): datamodel = SQLAInterface(models.SqlaTable) + include_route_methods = RouteMethod.CRUD_SET list_title = _("Tables") show_title = _("Show Table") diff --git a/superset/constants.py b/superset/constants.py index f8472c5..78b47e5 100644 --- a/superset/constants.py +++ b/superset/constants.py @@ -19,3 +19,43 @@ # string to use when None values *need* to be converted to/from strings NULL_STRING = "<NULL>" + + +class RouteMethod: # pylint: disable=too-few-public-methods + """ + Route methods are a FAB concept around ModelView and RestModelView + classes in FAB. Derivatives can define `include_route_method` and + `exclude_route_methods` class attribute as a set of methods that + will or won't get exposed. + + This class is a collection of static constants to reference common + route methods, namely the ones defined in the base classes in FAB + """ + + # ModelView specific + ACTION = "action" + ACTION_POST = "action_post" + ADD = "add" + API_CREATE = "api_create" + API_DELETE = "api_delete" + API_GET = "api_get" + API_READ = "api_read" + API_UPDATE = "api_update" + DELETE = "delete" + DOWNLOAD = "download" + EDIT = "edit" + LIST = "list" + SHOW = "show" + + # RestModelView specific + EXPORT = "export" + GET = "get" + GET_LIST = "get_list" + POST = "post" + PUT = "put" + RELATED = "related" + + # Commonly used sets + CRUD_SET = {ADD, LIST, EDIT, DELETE, ACTION_POST} + RELATED_VIEW_SET = {ADD, LIST, EDIT, DELETE} + REST_MODEL_VIEW_CRUD_SET = {DELETE, GET, GET_LIST, POST, PUT} diff --git a/superset/security/manager.py b/superset/security/manager.py index 8b43620..f1b69d1 100644 --- a/superset/security/manager.py +++ b/superset/security/manager.py @@ -32,6 +32,7 @@ from flask_appbuilder.security.views import ( PermissionViewModelView, RoleModelView, UserModelView, + ViewMenuModelView, ) from flask_appbuilder.widgets import ListWidget from sqlalchemy import or_ @@ -40,6 +41,7 @@ from sqlalchemy.orm.mapper import Mapper from superset import sql_parse from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.exceptions import SupersetSecurityException from superset.utils.core import DatasourceName @@ -76,8 +78,16 @@ RoleModelView.list_widget = SupersetRoleListWidget PermissionViewModelView.list_widget = SupersetSecurityListWidget PermissionModelView.list_widget = SupersetSecurityListWidget +# Limiting routes on FAB model views +UserModelView.include_route_methods = RouteMethod.CRUD_SET | {"userinfo"} +RoleModelView.include_route_methods = RouteMethod.CRUD_SET +PermissionViewModelView.include_route_methods = {RouteMethod.LIST} +PermissionModelView.include_route_methods = {RouteMethod.LIST} +ViewMenuModelView.include_route_methods = {RouteMethod.LIST} + class SupersetSecurityManager(SecurityManager): + userstatschartview = None READ_ONLY_MODEL_VIEWS = {"DatabaseAsync", "DatabaseView", "DruidClusterModelView"} USER_MODEL_VIEWS = { diff --git a/superset/views/annotations.py b/superset/views/annotations.py index 33fdb38..5ba9750 100644 --- a/superset/views/annotations.py +++ b/superset/views/annotations.py @@ -18,6 +18,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface from flask_babel import lazy_gettext as _ from wtforms.validators import StopValidation +from superset.constants import RouteMethod from superset.models.annotations import Annotation, AnnotationLayer from .base import DeleteMixin, SupersetModelView @@ -45,6 +46,7 @@ class AnnotationModelView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(Annotation) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Annotation") show_title = _("Show Annotation") @@ -93,6 +95,7 @@ class AnnotationLayerModelView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(AnnotationLayer) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Annotation Layer") show_title = _("Show Annotation Layer") diff --git a/superset/views/base_api.py b/superset/views/base_api.py index a2ee1d4..976f5b4 100644 --- a/superset/views/base_api.py +++ b/superset/views/base_api.py @@ -63,6 +63,17 @@ class BaseSupersetModelRestApi(ModelRestApi): """ logger = logging.getLogger(__name__) + method_permission_name = { + "get_list": "list", + "get": "show", + "export": "mulexport", + "post": "add", + "put": "edit", + "delete": "delete", + "bulk_delete": "delete", + "info": "list", + "related": "list", + } order_rel_fields: Dict[str, Tuple[str, str]] = {} """ diff --git a/superset/views/chart/api.py b/superset/views/chart/api.py index 511b62a..47c7d7f 100644 --- a/superset/views/chart/api.py +++ b/superset/views/chart/api.py @@ -134,15 +134,6 @@ class ChartRestApi(SliceMixin, BaseOwnedModelRestApi): allow_browser_login = True class_permission_name = "SliceModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - "related": "list", - } show_columns = [ "slice_name", "description", diff --git a/superset/views/chart/views.py b/superset/views/chart/views.py index d6a0c7e..a493600 100644 --- a/superset/views/chart/views.py +++ b/superset/views/chart/views.py @@ -22,6 +22,7 @@ from flask_babel import lazy_gettext as _ from superset import db from superset.connectors.connector_registry import ConnectorRegistry +from superset.constants import RouteMethod from superset.models.slice import Slice from superset.utils import core as utils from superset.views.base import check_ownership, DeleteMixin, SupersetModelView @@ -33,6 +34,11 @@ class SliceModelView( ): # pylint: disable=too-many-ancestors route_base = "/chart" datamodel = SQLAInterface(Slice) + include_route_methods = RouteMethod.CRUD_SET | { + RouteMethod.DOWNLOAD, + RouteMethod.API_READ, + RouteMethod.API_DELETE, + } def pre_add(self, item): utils.validate_json(item.params) @@ -61,36 +67,27 @@ class SliceModelView( class SliceAsync(SliceModelView): # pylint: disable=too-many-ancestors route_base = "/sliceasync" + include_route_methods = {RouteMethod.API_READ} + list_columns = [ - "id", - "slice_link", - "viz_type", - "slice_name", + "changed_on", + "changed_on_humanized", "creator", - "modified", + "datasource_id", + "datasource_link", + "datasource_name_text", + "datasource_type", + "description", + "description_markeddown", + "edit_url", "icons", - "changed_on_humanized", - ] - label_columns = {"icons": " ", "slice_link": _("Chart")} - - -class SliceAddView(SliceModelView): # pylint: disable=too-many-ancestors - route_base = "/sliceaddview" - list_columns = [ "id", + "modified", + "owners", + "params", + "slice_link", "slice_name", "slice_url", - "edit_url", "viz_type", - "params", - "description", - "description_markeddown", - "datasource_id", - "datasource_type", - "datasource_name_text", - "datasource_link", - "owners", - "modified", - "changed_on", - "changed_on_humanized", ] + label_columns = {"icons": " ", "slice_link": _("Chart")} diff --git a/superset/views/core.py b/superset/views/core.py index d96d67c..7957572 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -70,6 +70,7 @@ from superset import ( ) from superset.connectors.connector_registry import ConnectorRegistry from superset.connectors.sqla.models import AnnotationDatasource +from superset.constants import RouteMethod from superset.exceptions import ( DatabaseNotFound, SupersetException, @@ -250,6 +251,7 @@ def _deserialize_results_payload( class AccessRequestsModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(DAR) + include_route_methods = RouteMethod.CRUD_SET list_columns = [ "username", "user_roles", @@ -2816,6 +2818,7 @@ class Superset(BaseSupersetView): class CssTemplateModelView(SupersetModelView, DeleteMixin): datamodel = SQLAInterface(models.CssTemplate) + include_route_methods = RouteMethod.CRUD_SET list_title = _("CSS Templates") show_title = _("Show CSS Template") @@ -2829,6 +2832,7 @@ class CssTemplateModelView(SupersetModelView, DeleteMixin): class CssTemplateAsyncModelView(CssTemplateModelView): + include_route_methods = {RouteMethod.API_READ} list_columns = ["template_name", "css"] @@ -2845,16 +2849,3 @@ def apply_http_headers(response: Response): if k not in response.headers: response.headers[k] = v return response - - -@app.route('/<regex("panoramix\/.*"):url>') -def panoramix(url): - return redirect(request.full_path.replace("panoramix", "superset")) - - -@app.route('/<regex("caravel\/.*"):url>') -def caravel(url): - return redirect(request.full_path.replace("caravel", "superset")) - - -# --------------------------------------------------------------------- diff --git a/superset/views/dashboard/api.py b/superset/views/dashboard/api.py index 857f99e..80cac55 100644 --- a/superset/views/dashboard/api.py +++ b/superset/views/dashboard/api.py @@ -27,6 +27,7 @@ from marshmallow import fields, post_load, pre_load, Schema, ValidationError from marshmallow.validate import Length from sqlalchemy.exc import SQLAlchemyError +from superset.constants import RouteMethod from superset.exceptions import SupersetException, SupersetSecurityException from superset.models.dashboard import Dashboard from superset.utils import core as utils @@ -130,22 +131,15 @@ get_export_ids_schema = {"type": "array", "items": {"type": "integer"}} class DashboardRestApi(DashboardMixin, BaseOwnedModelRestApi): datamodel = SQLAInterface(Dashboard) - + include_route_methods = RouteMethod.REST_MODEL_VIEW_CRUD_SET | { + RouteMethod.EXPORT, + RouteMethod.RELATED, + "bulk_delete", # not using RouteMethod since locally defined + } resource_name = "dashboard" allow_browser_login = True class_permission_name = "DashboardModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "export": "mulexport", - "post": "add", - "put": "edit", - "delete": "delete", - "bulk_delete": "delete", - "info": "list", - "related": "list", - } show_columns = [ "dashboard_title", "slug", diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py index 236f426..69c37e5 100644 --- a/superset/views/dashboard/views.py +++ b/superset/views/dashboard/views.py @@ -26,6 +26,7 @@ from flask_babel import gettext as __, lazy_gettext as _ import superset.models.core as models from superset import db, event_logger +from superset.constants import RouteMethod from superset.utils import core as utils from ..base import ( @@ -45,6 +46,13 @@ class DashboardModelView( ): # pylint: disable=too-many-ancestors route_base = "/dashboard" datamodel = SQLAInterface(models.Dashboard) + # TODO disable api_read and api_delete (used by cypress) + # once we move to ChartRestModelApi + include_route_methods = RouteMethod.CRUD_SET | { + RouteMethod.API_READ, + RouteMethod.API_DELETE, + "download_dashboards", + } @has_access @expose("/list/") @@ -119,6 +127,8 @@ class Dashboard(BaseSupersetView): class DashboardModelViewAsync(DashboardModelView): # pylint: disable=too-many-ancestors route_base = "/dashboardasync" + include_route_methods = {RouteMethod.API_READ} + list_columns = [ "id", "dashboard_link", @@ -135,18 +145,3 @@ class DashboardModelViewAsync(DashboardModelView): # pylint: disable=too-many-a "creator": _("Creator"), "modified": _("Modified"), } - - -class DashboardAddView(DashboardModelView): # pylint: disable=too-many-ancestors - route_base = "/dashboardaddview" - list_columns = [ - "id", - "dashboard_link", - "creator", - "modified", - "dashboard_title", - "changed_on", - "url", - "changed_by_name", - ] - show_columns = list(set(DashboardModelView.edit_columns + list_columns)) diff --git a/superset/views/database/api.py b/superset/views/database/api.py index 0c1b688..72d2d09 100644 --- a/superset/views/database/api.py +++ b/superset/views/database/api.py @@ -14,27 +14,20 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from flask_appbuilder import ModelRestApi from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.views.base_api import BaseSupersetModelRestApi from .mixins import DatabaseFilter, DatabaseMixin from .validators import sqlalchemy_uri_validator -class DatabaseRestApi(DatabaseMixin, ModelRestApi): +class DatabaseRestApi(DatabaseMixin, BaseSupersetModelRestApi): datamodel = SQLAInterface(models.Database) + include_route_methods = {"get_list"} - class_permission_name = "DatabaseAsync" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - } + class_permission_name = "DatabaseView" resource_name = "database" allow_browser_login = True base_filters = [["id", DatabaseFilter, lambda: []]] diff --git a/superset/views/database/views.py b/superset/views/database/views.py index 19620e1..9a28715 100644 --- a/superset/views/database/views.py +++ b/superset/views/database/views.py @@ -27,6 +27,7 @@ from wtforms.validators import ValidationError import superset.models.core as models from superset import app, db from superset.connectors.sqla.models import SqlaTable +from superset.constants import RouteMethod from superset.utils import core as utils from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin @@ -49,6 +50,7 @@ class DatabaseView( DatabaseMixin, SupersetModelView, DeleteMixin, YamlExportMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(models.Database) + include_route_methods = RouteMethod.CRUD_SET add_template = "superset/models/database/add.html" edit_template = "superset/models/database/edit.html" @@ -157,23 +159,3 @@ class CsvToDatabaseView(SimpleFormView): flash(message, "info") stats_logger.incr("successful_csv_upload") return redirect("/tablemodelview/list/") - - -class DatabaseTablesAsync(DatabaseView): # pylint: disable=too-many-ancestors - list_columns = ["id", "all_table_names_in_database", "all_schema_names"] - - -class DatabaseAsync(DatabaseView): # pylint: disable=too-many-ancestors - list_columns = [ - "id", - "database_name", - "expose_in_sqllab", - "allow_ctas", - "force_ctas_schema", - "allow_run_async", - "allow_dml", - "allow_multi_schema_metadata_fetch", - "allow_csv_upload", - "allows_subquery", - "backend", - ] diff --git a/superset/views/log/api.py b/superset/views/log/api.py index c26e091..cc9424e 100644 --- a/superset/views/log/api.py +++ b/superset/views/log/api.py @@ -14,26 +14,18 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from flask_appbuilder import ModelRestApi from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.views.base_api import BaseSupersetModelRestApi from . import LogMixin -class LogRestApi(LogMixin, ModelRestApi): +class LogRestApi(LogMixin, BaseSupersetModelRestApi): datamodel = SQLAInterface(models.Log) class_permission_name = "LogModelView" - method_permission_name = { - "get_list": "list", - "get": "show", - "post": "add", - "put": "edit", - "delete": "delete", - "info": "list", - } resource_name = "log" allow_browser_login = True list_columns = ("user.username", "action", "dttm") diff --git a/superset/views/log/views.py b/superset/views/log/views.py index a9f3bd0..7139d34 100644 --- a/superset/views/log/views.py +++ b/superset/views/log/views.py @@ -17,6 +17,7 @@ from flask_appbuilder.models.sqla.interface import SQLAInterface import superset.models.core as models +from superset.constants import RouteMethod from superset.views.base import SupersetModelView from . import LogMixin @@ -24,3 +25,4 @@ from . import LogMixin class LogModelView(LogMixin, SupersetModelView): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(models.Log) + include_route_methods = {RouteMethod.LIST, RouteMethod.SHOW} diff --git a/superset/views/schedules.py b/superset/views/schedules.py index c1f690d..ad7861e 100644 --- a/superset/views/schedules.py +++ b/superset/views/schedules.py @@ -27,6 +27,7 @@ from flask_babel import lazy_gettext as _ from wtforms import BooleanField, StringField from superset import db, security_manager +from superset.constants import RouteMethod from superset.exceptions import SupersetException from superset.models.dashboard import Dashboard from superset.models.schedules import ( @@ -45,6 +46,7 @@ from .base import DeleteMixin, SupersetModelView class EmailScheduleView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors + include_route_methods = RouteMethod.CRUD_SET _extra_data = {"test_email": False, "test_email_recipients": None} schedule_type: Optional[Type] = None schedule_type_model: Optional[Type] = None diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py index e531418..5414c15 100644 --- a/superset/views/sql_lab.py +++ b/superset/views/sql_lab.py @@ -25,6 +25,7 @@ from flask_babel import lazy_gettext as _ from flask_sqlalchemy import BaseQuery from superset import db, get_feature_flags, security_manager +from superset.constants import RouteMethod from superset.models.sql_lab import Query, SavedQuery, TableSchema, TabState from superset.utils import core as utils @@ -52,6 +53,7 @@ class QueryFilter(BaseFilter): # pylint: disable=too-few-public-methods class QueryView(SupersetModelView): datamodel = SQLAInterface(Query) + include_route_methods = {RouteMethod.SHOW, RouteMethod.LIST, RouteMethod.API_READ} list_title = _("List Query") show_title = _("Show Query") @@ -75,6 +77,7 @@ class SavedQueryView( SupersetModelView, DeleteMixin ): # pylint: disable=too-many-ancestors datamodel = SQLAInterface(SavedQuery) + include_route_methods = RouteMethod.CRUD_SET list_title = _("List Saved Query") show_title = _("Show Saved Query") @@ -143,6 +146,12 @@ class SavedQueryView( class SavedQueryViewApi(SavedQueryView): # pylint: disable=too-many-ancestors + include_route_methods = { + RouteMethod.API_READ, + RouteMethod.API_CREATE, + RouteMethod.API_UPDATE, + RouteMethod.API_GET, + } list_columns = [ "id", "label", diff --git a/tests/core_tests.py b/tests/core_tests.py index 060b292..cc53141 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -345,7 +345,7 @@ class CoreTests(SupersetTestCase): def test_get_user_slices(self): self.login(username="admin") userid = security_manager.find_user("admin").id - url = "/sliceaddview/api/read?_flt_0_created_by={}".format(userid) + url = f"/sliceasync/api/read?_flt_0_created_by={userid}" resp = self.client.get(url) self.assertEqual(resp.status_code, 200) diff --git a/tests/import_export_tests.py b/tests/import_export_tests.py index b932bf0..bf5f6dd 100644 --- a/tests/import_export_tests.py +++ b/tests/import_export_tests.py @@ -235,9 +235,8 @@ class ImportExportTests(SupersetTestCase): def test_export_1_dashboard(self): self.login("admin") birth_dash = self.get_dash_by_slug("births") - export_dash_url = "/dashboard/export_dashboards_form?id={}&action=go".format( - birth_dash.id - ) + id_ = birth_dash.id + export_dash_url = f"/dashboard/export_dashboards_form?id={id_}&action=go" resp = self.client.get(export_dash_url) exported_dashboards = json.loads( resp.data.decode("utf-8"), object_hook=decode_dashboards @@ -247,7 +246,7 @@ class ImportExportTests(SupersetTestCase): self.assert_only_exported_slc_fields(birth_dash, exported_dashboards[0]) self.assert_dash_equals(birth_dash, exported_dashboards[0]) self.assertEqual( - birth_dash.id, + id_, json.loads( exported_dashboards[0].json_metadata, object_hook=decode_dashboards )["remote_id"], diff --git a/tests/security_tests.py b/tests/security_tests.py index afaad38..f16f411 100644 --- a/tests/security_tests.py +++ b/tests/security_tests.py @@ -486,14 +486,6 @@ class RolePermissionTests(SupersetTestCase): example_db.expose_in_sqllab = True session.commit() - OLD_FLASK_GET_SQL_DBS_REQUEST = ( - "databaseasync/api/read?_flt_0_expose_in_sqllab=1&" - "_oc_DatabaseAsync=database_name&_od_DatabaseAsync=asc" - ) - self.login(username="gamma") - databases_json = self.client.get(OLD_FLASK_GET_SQL_DBS_REQUEST).json - self.assertEquals(databases_json["count"], 1) - arguments = { "keys": ["none"], "filters": [{"col": "expose_in_sqllab", "opr": "eq", "value": True}], @@ -509,18 +501,15 @@ class RolePermissionTests(SupersetTestCase): self.logout() def assert_can_read(self, view_menu, permissions_set): - self.assertIn(("can_show", view_menu), permissions_set) self.assertIn(("can_list", view_menu), permissions_set) def assert_can_write(self, view_menu, permissions_set): self.assertIn(("can_add", view_menu), permissions_set) - self.assertIn(("can_download", view_menu), permissions_set) self.assertIn(("can_delete", view_menu), permissions_set) self.assertIn(("can_edit", view_menu), permissions_set) def assert_cannot_write(self, view_menu, permissions_set): self.assertNotIn(("can_add", view_menu), permissions_set) - self.assertNotIn(("can_download", view_menu), permissions_set) self.assertNotIn(("can_delete", view_menu), permissions_set) self.assertNotIn(("can_edit", view_menu), permissions_set) self.assertNotIn(("can_save", view_menu), permissions_set) @@ -530,7 +519,6 @@ class RolePermissionTests(SupersetTestCase): self.assert_can_write(view_menu, permissions_set) def assert_can_gamma(self, perm_set): - self.assert_can_read("DatabaseAsync", perm_set) self.assert_can_read("TableModelView", perm_set) # make sure that user can create slices and dashboards @@ -554,8 +542,6 @@ class RolePermissionTests(SupersetTestCase): self.assertIn(("can_userinfo", "UserDBModelView"), perm_set) def assert_can_alpha(self, perm_set): - self.assert_can_all("SqlMetricInlineView", perm_set) - self.assert_can_all("TableColumnInlineView", perm_set) self.assert_can_all("TableModelView", perm_set) self.assertIn(("all_datasource_access", "all_datasource_access"), perm_set) @@ -569,7 +555,6 @@ class RolePermissionTests(SupersetTestCase): self.assert_cannot_write("UserDBModelView", perm_set) def assert_can_admin(self, perm_set): - self.assert_can_read("DatabaseAsync", perm_set) self.assert_can_all("DatabaseView", perm_set) self.assert_can_all("RoleModelView", perm_set) self.assert_can_all("UserDBModelView", perm_set) @@ -583,7 +568,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_admin_only(self): self.assertFalse( security_manager._is_admin_only( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) self.assertFalse( @@ -603,7 +588,7 @@ class RolePermissionTests(SupersetTestCase): self.assertTrue( security_manager._is_admin_only( security_manager.find_permission_view_menu( - "can_show", "AccessRequestsModelView" + "can_list", "AccessRequestsModelView" ) ) ) @@ -626,7 +611,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_alpha_only(self): self.assertFalse( security_manager._is_alpha_only( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) @@ -647,13 +632,6 @@ class RolePermissionTests(SupersetTestCase): self.assertTrue( security_manager._is_alpha_only( security_manager.find_permission_view_menu( - "can_edit", "SqlMetricInlineView" - ) - ) - ) - self.assertTrue( - security_manager._is_alpha_only( - security_manager.find_permission_view_menu( "all_database_access", "all_database_access" ) ) @@ -662,7 +640,7 @@ class RolePermissionTests(SupersetTestCase): def test_is_gamma_pvm(self): self.assertTrue( security_manager._is_gamma_pvm( - security_manager.find_permission_view_menu("can_show", "TableModelView") + security_manager.find_permission_view_menu("can_list", "TableModelView") ) ) @@ -674,9 +652,10 @@ class RolePermissionTests(SupersetTestCase): SupersetTestCase.is_module_installed("pydruid"), "pydruid not installed" ) def test_alpha_permissions(self): - self.assert_can_gamma(get_perm_tuples("Alpha")) - self.assert_can_alpha(get_perm_tuples("Alpha")) - self.assert_cannot_alpha(get_perm_tuples("Alpha")) + alpha_perm_tuples = get_perm_tuples("Alpha") + self.assert_can_gamma(alpha_perm_tuples) + self.assert_can_alpha(alpha_perm_tuples) + self.assert_cannot_alpha(alpha_perm_tuples) @unittest.skipUnless( SupersetTestCase.is_module_installed("pydruid"), "pydruid not installed" @@ -703,18 +682,15 @@ class RolePermissionTests(SupersetTestCase): def test_gamma_permissions(self): def assert_can_read(view_menu): - self.assertIn(("can_show", view_menu), gamma_perm_set) self.assertIn(("can_list", view_menu), gamma_perm_set) def assert_can_write(view_menu): self.assertIn(("can_add", view_menu), gamma_perm_set) - self.assertIn(("can_download", view_menu), gamma_perm_set) self.assertIn(("can_delete", view_menu), gamma_perm_set) self.assertIn(("can_edit", view_menu), gamma_perm_set) def assert_cannot_write(view_menu): self.assertNotIn(("can_add", view_menu), gamma_perm_set) - self.assertNotIn(("can_download", view_menu), gamma_perm_set) self.assertNotIn(("can_delete", view_menu), gamma_perm_set) self.assertNotIn(("can_edit", view_menu), gamma_perm_set) self.assertNotIn(("can_save", view_menu), gamma_perm_set)