From 7a68cb7ca0d00294e10b86a2a4f29268ea5cb965 Mon Sep 17 00:00:00 2001 From: David Aaron Suddjian <1858430+suddjian@users.noreply.github.com> Date: Tue, 17 Dec 2019 16:17:49 -0800 Subject: [PATCH] factor out datasource_access_request model (#8809) --- superset/models/__init__.py | 2 +- superset/models/core.py | 68 ------------- superset/models/datasource_access_request.py | 101 +++++++++++++++++++ superset/views/core.py | 3 +- tests/access_tests.py | 3 +- tests/base_tests.py | 3 +- tests/core_tests.py | 3 +- 7 files changed, 110 insertions(+), 73 deletions(-) create mode 100644 superset/models/datasource_access_request.py diff --git a/superset/models/__init__.py b/superset/models/__init__.py index 17e7d188dc..c7eed13b4a 100644 --- a/superset/models/__init__.py +++ b/superset/models/__init__.py @@ -14,4 +14,4 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -from . import core, schedules, sql_lab, user_attributes +from . import core, datasource_access_request, schedules, sql_lab, user_attributes diff --git a/superset/models/core.py b/superset/models/core.py index 79ff3b0483..b65547ab93 100755 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -1322,74 +1322,6 @@ class FavStar(Model): # pylint: disable=too-few-public-methods dttm = Column(DateTime, default=datetime.utcnow) -class DatasourceAccessRequest(Model, AuditMixinNullable): - """ORM model for the access requests for datasources and dbs.""" - - __tablename__ = "access_request" - id = Column(Integer, primary_key=True) # pylint: disable=invalid-name - - datasource_id = Column(Integer) - datasource_type = Column(String(200)) - - ROLES_BLACKLIST = set(config["ROBOT_PERMISSION_ROLES"]) - - @property - def cls_model(self) -> Type["BaseDatasource"]: - return ConnectorRegistry.sources[self.datasource_type] - - @property - def username(self) -> Markup: - return self.creator() - - @property - def datasource(self) -> "BaseDatasource": - return self.get_datasource - - @datasource.getter # type: ignore - @utils.memoized - def get_datasource(self) -> "BaseDatasource": - ds = db.session.query(self.cls_model).filter_by(id=self.datasource_id).first() - return ds - - @property - def datasource_link(self) -> Optional[Markup]: - return self.datasource.link # pylint: disable=no-member - - @property - def roles_with_datasource(self) -> str: - action_list = "" - perm = self.datasource.perm # pylint: disable=no-member - pv = security_manager.find_permission_view_menu("datasource_access", perm) - for role in pv.role: - if role.name in self.ROLES_BLACKLIST: - continue - # pylint: disable=no-member - href = ( - f"/superset/approve?datasource_type={self.datasource_type}&" - f"datasource_id={self.datasource_id}&" - f"created_by={self.created_by.username}&role_to_grant={role.name}" - ) - link = 'Grant {} Role'.format(href, role.name) - action_list = action_list + "
  • " + link + "
  • " - return "" - - @property - def user_roles(self) -> str: - action_list = "" - for role in self.created_by.roles: # pylint: disable=no-member - # pylint: disable=no-member - href = ( - f"/superset/approve?datasource_type={self.datasource_type}&" - f"datasource_id={self.datasource_id}&" - f"created_by={self.created_by.username}&role_to_extend={role.name}" - ) - link = 'Extend {} Role'.format(href, role.name) - if role.name in self.ROLES_BLACKLIST: - link = "{} Role".format(role.name) - action_list = action_list + "
  • " + link + "
  • " - return "" - - # events for updating tags if is_feature_enabled("TAGGING_SYSTEM"): sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert) diff --git a/superset/models/datasource_access_request.py b/superset/models/datasource_access_request.py new file mode 100644 index 0000000000..803a91115f --- /dev/null +++ b/superset/models/datasource_access_request.py @@ -0,0 +1,101 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from typing import Optional, Type, TYPE_CHECKING + +from flask import Markup +from flask_appbuilder import Model +from sqlalchemy import Column, Integer, String + +from superset import app, db, security_manager +from superset.connectors.connector_registry import ConnectorRegistry +from superset.models.helpers import AuditMixinNullable +from superset.utils import core as utils + +if TYPE_CHECKING: + from superset.connectors.base.models import ( # pylint: disable=unused-import + BaseDatasource, + ) + +config = app.config + + +class DatasourceAccessRequest(Model, AuditMixinNullable): + """ORM model for the access requests for datasources and dbs.""" + + __tablename__ = "access_request" + id = Column(Integer, primary_key=True) # pylint: disable=invalid-name + + datasource_id = Column(Integer) + datasource_type = Column(String(200)) + + ROLES_BLACKLIST = set(config["ROBOT_PERMISSION_ROLES"]) + + @property + def cls_model(self) -> Type["BaseDatasource"]: + return ConnectorRegistry.sources[self.datasource_type] + + @property + def username(self) -> Markup: + return self.creator() + + @property + def datasource(self) -> "BaseDatasource": + return self.get_datasource + + @datasource.getter # type: ignore + @utils.memoized + def get_datasource(self) -> "BaseDatasource": + ds = db.session.query(self.cls_model).filter_by(id=self.datasource_id).first() + return ds + + @property + def datasource_link(self) -> Optional[Markup]: + return self.datasource.link # pylint: disable=no-member + + @property + def roles_with_datasource(self) -> str: + action_list = "" + perm = self.datasource.perm # pylint: disable=no-member + pv = security_manager.find_permission_view_menu("datasource_access", perm) + for role in pv.role: + if role.name in self.ROLES_BLACKLIST: + continue + # pylint: disable=no-member + href = ( + f"/superset/approve?datasource_type={self.datasource_type}&" + f"datasource_id={self.datasource_id}&" + f"created_by={self.created_by.username}&role_to_grant={role.name}" + ) + link = 'Grant {} Role'.format(href, role.name) + action_list = action_list + "
  • " + link + "
  • " + return "" + + @property + def user_roles(self) -> str: + action_list = "" + for role in self.created_by.roles: # pylint: disable=no-member + # pylint: disable=no-member + href = ( + f"/superset/approve?datasource_type={self.datasource_type}&" + f"datasource_id={self.datasource_id}&" + f"created_by={self.created_by.username}&role_to_extend={role.name}" + ) + link = 'Extend {} Role'.format(href, role.name) + if role.name in self.ROLES_BLACKLIST: + link = "{} Role".format(role.name) + action_list = action_list + "
  • " + link + "
  • " + return "" diff --git a/superset/views/core.py b/superset/views/core.py index fe33bf9c3c..0869c611cb 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -77,6 +77,7 @@ from superset.exceptions import ( SupersetTimeoutException, ) from superset.jinja_context import get_template_processor +from superset.models.datasource_access_request import DatasourceAccessRequest from superset.models.sql_lab import Query, TabState from superset.models.user_attributes import UserAttribute from superset.sql_parse import ParsedQuery @@ -117,7 +118,7 @@ config = app.config CACHE_DEFAULT_TIMEOUT = config["CACHE_DEFAULT_TIMEOUT"] SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT = config["SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT"] stats_logger = config["STATS_LOGGER"] -DAR = models.DatasourceAccessRequest +DAR = DatasourceAccessRequest QueryStatus = utils.QueryStatus DATABASE_KEYS = [ diff --git a/tests/access_tests.py b/tests/access_tests.py index 7b0be43c04..63cbfb5c70 100644 --- a/tests/access_tests.py +++ b/tests/access_tests.py @@ -26,6 +26,7 @@ from superset.connectors.connector_registry import ConnectorRegistry from superset.connectors.druid.models import DruidDatasource from superset.connectors.sqla.models import SqlaTable from superset.models import core as models +from superset.models.datasource_access_request import DatasourceAccessRequest from .base_tests import SupersetTestCase @@ -83,7 +84,7 @@ def create_access_request(session, ds_type, ds_name, role_name, user_name): security_manager.add_permission_role( security_manager.find_role(role_name), ds_perm_view ) - access_request = models.DatasourceAccessRequest( + access_request = DatasourceAccessRequest( datasource_id=ds.id, datasource_type=ds_type, created_by_fk=security_manager.find_user(username=user_name).id, diff --git a/tests/base_tests.py b/tests/base_tests.py index 666102c43c..4f82e2ee0e 100644 --- a/tests/base_tests.py +++ b/tests/base_tests.py @@ -31,6 +31,7 @@ from superset.connectors.druid.models import DruidCluster, DruidDatasource from superset.connectors.sqla.models import SqlaTable from superset.models import core as models from superset.models.core import Database +from superset.models.datasource_access_request import DatasourceAccessRequest from superset.utils.core import get_example_database FAKE_DB_NAME = "fake_db_100" @@ -158,7 +159,7 @@ class SupersetTestCase(TestCase): return json.loads(resp) def get_access_requests(self, username, ds_type, ds_id): - DAR = models.DatasourceAccessRequest + DAR = DatasourceAccessRequest return ( db.session.query(DAR) .filter( diff --git a/tests/core_tests.py b/tests/core_tests.py index ae7cf633e3..59092bd0a4 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -40,6 +40,7 @@ from superset.connectors.sqla.models import SqlaTable from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.mssql import MssqlEngineSpec from superset.models import core as models +from superset.models.datasource_access_request import DatasourceAccessRequest from superset.models.sql_lab import Query from superset.utils import core as utils from superset.views import core as views @@ -55,7 +56,7 @@ class CoreTests(SupersetTestCase): def setUp(self): db.session.query(Query).delete() - db.session.query(models.DatasourceAccessRequest).delete() + db.session.query(DatasourceAccessRequest).delete() db.session.query(models.Log).delete() self.table_ids = { tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())