You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ma...@apache.org on 2020/01/23 16:25:42 UTC

[incubator-superset] branch master updated: fix: shut off unneeded endpoints (#8960)

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 <ma...@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 <da...@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)