factor out datasource_access_request model (#8809)

This commit is contained in:
David Aaron Suddjian 2019-12-17 16:17:49 -08:00 committed by Maxime Beauchemin
parent 9ed4b24533
commit 7a68cb7ca0
7 changed files with 110 additions and 73 deletions

View File

@ -14,4 +14,4 @@
# KIND, either express or implied. See the License for the # KIND, either express or implied. See the License for the
# specific language governing permissions and limitations # specific language governing permissions and limitations
# under the License. # under the License.
from . import core, schedules, sql_lab, user_attributes from . import core, datasource_access_request, schedules, sql_lab, user_attributes

View File

@ -1322,74 +1322,6 @@ class FavStar(Model): # pylint: disable=too-few-public-methods
dttm = Column(DateTime, default=datetime.utcnow) 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 = '<a href="{}">Grant {} Role</a>'.format(href, role.name)
action_list = action_list + "<li>" + link + "</li>"
return "<ul>" + action_list + "</ul>"
@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 = '<a href="{}">Extend {} Role</a>'.format(href, role.name)
if role.name in self.ROLES_BLACKLIST:
link = "{} Role".format(role.name)
action_list = action_list + "<li>" + link + "</li>"
return "<ul>" + action_list + "</ul>"
# events for updating tags # events for updating tags
if is_feature_enabled("TAGGING_SYSTEM"): if is_feature_enabled("TAGGING_SYSTEM"):
sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert) sqla.event.listen(Slice, "after_insert", ChartUpdater.after_insert)

View File

@ -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 = '<a href="{}">Grant {} Role</a>'.format(href, role.name)
action_list = action_list + "<li>" + link + "</li>"
return "<ul>" + action_list + "</ul>"
@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 = '<a href="{}">Extend {} Role</a>'.format(href, role.name)
if role.name in self.ROLES_BLACKLIST:
link = "{} Role".format(role.name)
action_list = action_list + "<li>" + link + "</li>"
return "<ul>" + action_list + "</ul>"

View File

@ -77,6 +77,7 @@ from superset.exceptions import (
SupersetTimeoutException, SupersetTimeoutException,
) )
from superset.jinja_context import get_template_processor 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.sql_lab import Query, TabState
from superset.models.user_attributes import UserAttribute from superset.models.user_attributes import UserAttribute
from superset.sql_parse import ParsedQuery from superset.sql_parse import ParsedQuery
@ -117,7 +118,7 @@ config = app.config
CACHE_DEFAULT_TIMEOUT = config["CACHE_DEFAULT_TIMEOUT"] CACHE_DEFAULT_TIMEOUT = config["CACHE_DEFAULT_TIMEOUT"]
SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT = config["SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT"] SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT = config["SQLLAB_QUERY_COST_ESTIMATE_TIMEOUT"]
stats_logger = config["STATS_LOGGER"] stats_logger = config["STATS_LOGGER"]
DAR = models.DatasourceAccessRequest DAR = DatasourceAccessRequest
QueryStatus = utils.QueryStatus QueryStatus = utils.QueryStatus
DATABASE_KEYS = [ DATABASE_KEYS = [

View File

@ -26,6 +26,7 @@ from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.druid.models import DruidDatasource from superset.connectors.druid.models import DruidDatasource
from superset.connectors.sqla.models import SqlaTable from superset.connectors.sqla.models import SqlaTable
from superset.models import core as models from superset.models import core as models
from superset.models.datasource_access_request import DatasourceAccessRequest
from .base_tests import SupersetTestCase 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.add_permission_role(
security_manager.find_role(role_name), ds_perm_view security_manager.find_role(role_name), ds_perm_view
) )
access_request = models.DatasourceAccessRequest( access_request = DatasourceAccessRequest(
datasource_id=ds.id, datasource_id=ds.id,
datasource_type=ds_type, datasource_type=ds_type,
created_by_fk=security_manager.find_user(username=user_name).id, created_by_fk=security_manager.find_user(username=user_name).id,

View File

@ -31,6 +31,7 @@ from superset.connectors.druid.models import DruidCluster, DruidDatasource
from superset.connectors.sqla.models import SqlaTable from superset.connectors.sqla.models import SqlaTable
from superset.models import core as models from superset.models import core as models
from superset.models.core import Database from superset.models.core import Database
from superset.models.datasource_access_request import DatasourceAccessRequest
from superset.utils.core import get_example_database from superset.utils.core import get_example_database
FAKE_DB_NAME = "fake_db_100" FAKE_DB_NAME = "fake_db_100"
@ -158,7 +159,7 @@ class SupersetTestCase(TestCase):
return json.loads(resp) return json.loads(resp)
def get_access_requests(self, username, ds_type, ds_id): def get_access_requests(self, username, ds_type, ds_id):
DAR = models.DatasourceAccessRequest DAR = DatasourceAccessRequest
return ( return (
db.session.query(DAR) db.session.query(DAR)
.filter( .filter(

View File

@ -40,6 +40,7 @@ from superset.connectors.sqla.models import SqlaTable
from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.base import BaseEngineSpec
from superset.db_engine_specs.mssql import MssqlEngineSpec from superset.db_engine_specs.mssql import MssqlEngineSpec
from superset.models import core as models from superset.models import core as models
from superset.models.datasource_access_request import DatasourceAccessRequest
from superset.models.sql_lab import Query from superset.models.sql_lab import Query
from superset.utils import core as utils from superset.utils import core as utils
from superset.views import core as views from superset.views import core as views
@ -55,7 +56,7 @@ class CoreTests(SupersetTestCase):
def setUp(self): def setUp(self):
db.session.query(Query).delete() db.session.query(Query).delete()
db.session.query(models.DatasourceAccessRequest).delete() db.session.query(DatasourceAccessRequest).delete()
db.session.query(models.Log).delete() db.session.query(models.Log).delete()
self.table_ids = { self.table_ids = {
tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all()) tbl.table_name: tbl.id for tbl in (db.session.query(SqlaTable).all())