mirror of https://github.com/apache/superset.git
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:
parent
22699a204d
commit
315a11dfe2
|
@ -20,6 +20,7 @@
|
|||
*.pyc
|
||||
*.sqllite
|
||||
*.swp
|
||||
.bento*
|
||||
.cache-loader
|
||||
.coverage
|
||||
.DS_Store
|
||||
|
|
|
@ -29,7 +29,9 @@ apache_superset.egg-info
|
|||
.*json
|
||||
.*csv
|
||||
# Generated doc files
|
||||
env/*
|
||||
docs/_build/*
|
||||
docs/_modules/*
|
||||
_build/*
|
||||
_static/*
|
||||
.buildinfo
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 = {};
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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]] = {}
|
||||
"""
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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")}
|
||||
|
|
|
@ -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"))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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: []]]
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue