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 <danielvazgaspar@gmail.com>
This commit is contained in:
Maxime Beauchemin 2020-01-23 11:25:15 -05:00 committed by GitHub
parent 22699a204d
commit 315a11dfe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 176 additions and 190 deletions

1
.gitignore vendored
View File

@ -20,6 +20,7 @@
*.pyc
*.sqllite
*.swp
.bento*
.cache-loader
.coverage
.DS_Store

View File

@ -29,7 +29,9 @@ apache_superset.egg-info
.*json
.*csv
# Generated doc files
env/*
docs/_build/*
docs/_modules/*
_build/*
_static/*
.buildinfo

View File

@ -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

View File

@ -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)
@ -282,12 +282,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"),
@ -306,6 +300,12 @@ class SupersetAppInitializer:
category="SQL Lab",
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"),
@ -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,

View File

@ -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 = {};

View File

@ -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")

View File

@ -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")

View File

@ -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}

View File

@ -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 = {

View File

@ -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")

View File

@ -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]] = {}
"""

View File

@ -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",

View File

@ -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"
list_columns = [
"id",
"slice_link",
"viz_type",
"slice_name",
"creator",
"modified",
"icons",
"changed_on_humanized",
]
label_columns = {"icons": " ", "slice_link": _("Chart")}
include_route_methods = {RouteMethod.API_READ}
class SliceAddView(SliceModelView): # pylint: disable=too-many-ancestors
route_base = "/sliceaddview"
list_columns = [
"id",
"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",
"creator",
"datasource_id",
"datasource_link",
"datasource_name_text",
"datasource_type",
"description",
"description_markeddown",
"edit_url",
"icons",
"id",
"modified",
"owners",
"params",
"slice_link",
"slice_name",
"slice_url",
"viz_type",
]
label_columns = {"icons": " ", "slice_link": _("Chart")}

View File

@ -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"))
# ---------------------------------------------------------------------

View File

@ -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",

View File

@ -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))

View File

@ -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: []]]

View File

@ -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",
]

View File

@ -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")

View File

@ -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}

View File

@ -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

View File

@ -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",

View File

@ -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)

View File

@ -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"],

View File

@ -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")
)
)
@ -644,13 +629,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(
@ -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)