diff --git a/UPDATING.MD b/UPDATING.MD new file mode 100644 index 0000000000000..7fb3e852ec4b1 --- /dev/null +++ b/UPDATING.MD @@ -0,0 +1,8 @@ +# Updating Superset + +This file documents any backwards-incompatible changes in Superset and +assists people when migrating to a new version. + +## Superset 0.23.0 + +* [4565](https://github.com/apache/incubator-superset/pull/4565) \ No newline at end of file diff --git a/superset/__init__.py b/superset/__init__.py index d420b764f3f7d..83154cabc55a7 100644 --- a/superset/__init__.py +++ b/superset/__init__.py @@ -18,8 +18,9 @@ from flask_wtf.csrf import CSRFProtect from werkzeug.contrib.fixers import ProxyFix +from superset import config, utils from superset.connectors.connector_registry import ConnectorRegistry -from superset import utils, config # noqa +from superset.security import SupersetSecurityManager APP_DIR = os.path.dirname(__file__) CONFIG_MODULE = os.environ.get('SUPERSET_CONFIG', 'superset.config') @@ -149,16 +150,23 @@ def index(self): return redirect('/superset/welcome') +custom_sm = app.config.get('CUSTOM_SECURITY_MANAGER') or SupersetSecurityManager +if not issubclass(custom_sm, SupersetSecurityManager): + raise Exception( + """Your CUSTOM_SECURITY_MANAGER must now extend SupersetSecurityManager, + not FAB's security manager. + See [4565] in UPDATING.md""") + appbuilder = AppBuilder( app, db.session, base_template='superset/base.html', indexview=MyIndexView, - security_manager_class=app.config.get('CUSTOM_SECURITY_MANAGER'), + security_manager_class=custom_sm, update_perms=utils.get_update_perms_flag(), ) -sm = appbuilder.sm +security_manager = appbuilder.sm results_backend = app.config.get('RESULTS_BACKEND') diff --git a/superset/cli.py b/superset/cli.py index 95f6df7b9522d..b146877f22c17 100755 --- a/superset/cli.py +++ b/superset/cli.py @@ -16,7 +16,7 @@ from pathlib2 import Path import yaml -from superset import app, db, dict_import_export_util, security, utils +from superset import app, data, db, dict_import_export_util, security_manager, utils config = app.config celery_app = utils.get_celery_app(config) @@ -28,7 +28,8 @@ @manager.command def init(): """Inits the Superset application""" - security.sync_role_definitions() + utils.get_or_create_main_db() + security_manager.sync_role_definitions() @manager.option( @@ -108,7 +109,6 @@ def version(verbose): help='Load additional test data') def load_examples(load_test_data): """Loads a set of Slices and Dashboards and a supporting dataset """ - from superset import data print('Loading examples into {}'.format(db)) data.load_css_templates() diff --git a/superset/connectors/druid/models.py b/superset/connectors/druid/models.py index f4f137ef2f7e8..107e3c84ff9df 100644 --- a/superset/connectors/druid/models.py +++ b/superset/connectors/druid/models.py @@ -33,7 +33,7 @@ ) from sqlalchemy.orm import backref, relationship -from superset import conf, db, import_util, sm, utils +from superset import conf, db, import_util, security_manager, utils from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric from superset.exceptions import MetricPermException from superset.models.helpers import ( @@ -465,7 +465,7 @@ class DruidDatasource(Model, BaseDatasource): 'DruidCluster', backref='datasources', foreign_keys=[cluster_name]) user_id = Column(Integer, ForeignKey('ab_user.id')) owner = relationship( - sm.user_model, + security_manager.user_model, backref=backref('datasources', cascade='all, delete-orphan'), foreign_keys=[user_id]) UniqueConstraint('cluster_name', 'datasource_name') @@ -506,7 +506,7 @@ def schema(self): @property def schema_perm(self): """Returns schema permission if present, cluster one otherwise.""" - return utils.get_schema_perm(self.cluster, self.schema) + return security_manager.get_schema_perm(self.cluster, self.schema) def get_perm(self): return ( @@ -980,7 +980,7 @@ def check_restricted_metrics(self, aggregations): m.metric_name for m in self.metrics if m.is_restricted and m.metric_name in aggregations.keys() and - not sm.has_access('metric_access', m.perm) + not security_manager.has_access('metric_access', m.perm) ] if rejected_metrics: raise MetricPermException( diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py index f4a20891c7cd4..02b8cb4891ef6 100644 --- a/superset/connectors/druid/views.py +++ b/superset/connectors/druid/views.py @@ -14,7 +14,7 @@ from flask_babel import gettext as __ from flask_babel import lazy_gettext as _ -from superset import appbuilder, db, security, sm, utils +from superset import appbuilder, db, security_manager, utils from superset.connectors.base.views import DatasourceModelView from superset.connectors.connector_registry import ConnectorRegistry from superset.utils import has_access @@ -140,11 +140,11 @@ class DruidMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa def post_add(self, metric): if metric.is_restricted: - security.merge_perm(sm, 'metric_access', metric.get_perm()) + security_manager.merge_perm('metric_access', metric.get_perm()) def post_update(self, metric): if metric.is_restricted: - security.merge_perm(sm, 'metric_access', metric.get_perm()) + security_manager.merge_perm('metric_access', metric.get_perm()) appbuilder.add_view_no_menu(DruidMetricInlineView) @@ -177,7 +177,7 @@ class DruidClusterModelView(SupersetModelView, DeleteMixin, YamlExportMixin): # } def pre_add(self, cluster): - security.merge_perm(sm, 'database_access', cluster.perm) + security_manager.merge_perm('database_access', cluster.perm) def pre_update(self, cluster): self.pre_add(cluster) @@ -278,9 +278,9 @@ def pre_add(self, datasource): def post_add(self, datasource): datasource.refresh_metrics() - security.merge_perm(sm, 'datasource_access', datasource.get_perm()) + security_manager.merge_perm('datasource_access', datasource.get_perm()) if datasource.schema: - security.merge_perm(sm, 'schema_access', datasource.schema_perm) + security_manager.merge_perm('schema_access', datasource.schema_perm) def post_update(self, datasource): self.post_add(datasource) diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py index ef8c68ebb33e6..e2dca32137e45 100644 --- a/superset/connectors/sqla/models.py +++ b/superset/connectors/sqla/models.py @@ -24,7 +24,7 @@ from sqlalchemy.sql.expression import TextAsFrom import sqlparse -from superset import db, import_util, sm, utils +from superset import db, import_util, security_manager, utils from superset.connectors.base.models import BaseColumn, BaseDatasource, BaseMetric from superset.jinja_context import get_template_processor from superset.models.annotations import Annotation @@ -259,7 +259,7 @@ class SqlaTable(Model, BaseDatasource): fetch_values_predicate = Column(String(1000)) user_id = Column(Integer, ForeignKey('ab_user.id')) owner = relationship( - sm.user_model, + security_manager.user_model, backref='tables', foreign_keys=[user_id]) database = relationship( @@ -298,7 +298,7 @@ def link(self): @property def schema_perm(self): """Returns schema permission if present, database one otherwise.""" - return utils.get_schema_perm(self.database, self.schema) + return security_manager.get_schema_perm(self.database, self.schema) def get_perm(self): return ( diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py index b6674750339ca..16a218fd39e7b 100644 --- a/superset/connectors/sqla/views.py +++ b/superset/connectors/sqla/views.py @@ -13,7 +13,7 @@ from flask_babel import lazy_gettext as _ from past.builtins import basestring -from superset import appbuilder, db, security, sm, utils +from superset import appbuilder, db, security_manager, utils from superset.connectors.base.views import DatasourceModelView from superset.utils import has_access from superset.views.base import ( @@ -144,11 +144,11 @@ class SqlMetricInlineView(CompactCRUDMixin, SupersetModelView): # noqa def post_add(self, metric): if metric.is_restricted: - security.merge_perm(sm, 'metric_access', metric.get_perm()) + security_manager.merge_perm('metric_access', metric.get_perm()) def post_update(self, metric): if metric.is_restricted: - security.merge_perm(sm, 'metric_access', metric.get_perm()) + security_manager.merge_perm('metric_access', metric.get_perm()) appbuilder.add_view_no_menu(SqlMetricInlineView) @@ -253,9 +253,9 @@ def pre_add(self, table): def post_add(self, table, flash_message=True): table.fetch_metadata() - security.merge_perm(sm, 'datasource_access', table.get_perm()) + security_manager.merge_perm('datasource_access', table.get_perm()) if table.schema: - security.merge_perm(sm, 'schema_access', table.schema_perm) + security_manager.merge_perm('schema_access', table.schema_perm) if flash_message: flash(_( diff --git a/superset/data/__init__.py b/superset/data/__init__.py index ec87994648b28..9f505c12e9cf0 100644 --- a/superset/data/__init__.py +++ b/superset/data/__init__.py @@ -16,10 +16,9 @@ import geohash import polyline -from superset import app, db, utils +from superset import app, db, security_manager, utils from superset.connectors.connector_registry import ConnectorRegistry from superset.models import core as models -from superset.security import get_or_create_main_db # Shortcuts DB = models.Database @@ -71,7 +70,7 @@ def load_energy(): if not tbl: tbl = TBL(table_name=tbl_name) tbl.description = "Energy consumption" - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() db.session.merge(tbl) db.session.commit() tbl.fetch_metadata() @@ -179,7 +178,7 @@ def load_world_bank_health_n_pop(): tbl = TBL(table_name=tbl_name) tbl.description = utils.readfile(os.path.join(DATA_FOLDER, 'countries.md')) tbl.main_dttm_col = 'year' - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() tbl.filter_select_enabled = True db.session.merge(tbl) db.session.commit() @@ -583,7 +582,7 @@ def load_birth_names(): if not obj: obj = TBL(table_name='birth_names') obj.main_dttm_col = 'ds' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() obj.filter_select_enabled = True db.session.merge(obj) db.session.commit() @@ -870,7 +869,7 @@ def load_unicode_test_data(): if not obj: obj = TBL(table_name='unicode_test') obj.main_dttm_col = 'dttm' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() db.session.merge(obj) db.session.commit() obj.fetch_metadata() @@ -948,7 +947,7 @@ def load_random_time_series_data(): if not obj: obj = TBL(table_name='random_time_series') obj.main_dttm_col = 'ds' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() db.session.merge(obj) db.session.commit() obj.fetch_metadata() @@ -1011,7 +1010,7 @@ def load_country_map_data(): if not obj: obj = TBL(table_name='birth_france_by_region') obj.main_dttm_col = 'dttm' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() db.session.merge(obj) db.session.commit() obj.fetch_metadata() @@ -1086,7 +1085,7 @@ def load_long_lat_data(): if not obj: obj = TBL(table_name='long_lat') obj.main_dttm_col = 'datetime' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() db.session.merge(obj) db.session.commit() obj.fetch_metadata() @@ -1147,7 +1146,7 @@ def load_multiformat_time_series_data(): if not obj: obj = TBL(table_name='multiformat_time_series') obj.main_dttm_col = 'ds' - obj.database = get_or_create_main_db() + obj.database = utils.get_or_create_main_db() dttm_and_expr_dict = { 'ds': [None, None], 'ds2': [None, None], @@ -1769,7 +1768,7 @@ def load_flights(): if not tbl: tbl = TBL(table_name=tbl_name) tbl.description = "Random set of flights in the US" - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() db.session.merge(tbl) db.session.commit() tbl.fetch_metadata() @@ -1800,7 +1799,7 @@ def load_paris_iris_geojson(): if not tbl: tbl = TBL(table_name=tbl_name) tbl.description = "Map of Paris" - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() db.session.merge(tbl) db.session.commit() tbl.fetch_metadata() @@ -1830,7 +1829,7 @@ def load_sf_population_polygons(): if not tbl: tbl = TBL(table_name=tbl_name) tbl.description = "Population density of San Francisco" - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() db.session.merge(tbl) db.session.commit() tbl.fetch_metadata() @@ -1860,7 +1859,7 @@ def load_bart_lines(): if not tbl: tbl = TBL(table_name=tbl_name) tbl.description = "BART lines" - tbl.database = get_or_create_main_db() + tbl.database = utils.get_or_create_main_db() db.session.merge(tbl) db.session.commit() tbl.fetch_metadata() diff --git a/superset/models/core.py b/superset/models/core.py index cd7cc44b9eb45..c32ac5c229635 100644 --- a/superset/models/core.py +++ b/superset/models/core.py @@ -33,7 +33,7 @@ from sqlalchemy.sql.expression import TextAsFrom from sqlalchemy_utils import EncryptedType -from superset import app, db, db_engine_specs, sm, utils +from superset import app, db, db_engine_specs, security_manager, utils from superset.connectors.connector_registry import ConnectorRegistry from superset.models.helpers import AuditMixinNullable, ImportMixin, set_perm from superset.viz import viz_types @@ -104,7 +104,7 @@ class Slice(Model, AuditMixinNullable, ImportMixin): description = Column(Text) cache_timeout = Column(Integer) perm = Column(String(1000)) - owners = relationship(sm.user_model, secondary=slice_user) + owners = relationship(security_manager.user_model, secondary=slice_user) export_fields = ('slice_name', 'datasource_type', 'datasource_name', 'viz_type', 'params', 'cache_timeout') @@ -322,7 +322,7 @@ class Dashboard(Model, AuditMixinNullable, ImportMixin): slug = Column(String(255), unique=True) slices = relationship( 'Slice', secondary=dashboard_slices, backref='dashboards') - owners = relationship(sm.user_model, secondary=dashboard_user) + owners = relationship(security_manager.user_model, secondary=dashboard_user) export_fields = ('dashboard_title', 'position_json', 'json_metadata', 'description', 'css', 'slug') @@ -681,7 +681,7 @@ def get_sqla_engine(self, schema=None, nullpool=True, user_name=None): DB_CONNECTION_MUTATOR = config.get('DB_CONNECTION_MUTATOR') if DB_CONNECTION_MUTATOR: url, params = DB_CONNECTION_MUTATOR( - url, params, effective_username, sm) + url, params, effective_username, security_manager) return create_engine(url, **params) def get_reserved_words(self): @@ -862,7 +862,8 @@ class Log(Model): dashboard_id = Column(Integer) slice_id = Column(Integer) json = Column(Text) - user = relationship(sm.user_model, backref='logs', foreign_keys=[user_id]) + user = relationship( + security_manager.user_model, backref='logs', foreign_keys=[user_id]) dttm = Column(DateTime, default=datetime.utcnow) dt = Column(Date, default=date.today()) duration_ms = Column(Integer) @@ -961,7 +962,7 @@ def datasource_link(self): def roles_with_datasource(self): action_list = '' perm = self.datasource.perm # pylint: disable=no-member - pv = sm.find_permission_view_menu('datasource_access', perm) + pv = security_manager.find_permission_view_menu('datasource_access', perm) for r in pv.role: if r.name in self.ROLES_BLACKLIST: continue diff --git a/superset/models/helpers.py b/superset/models/helpers.py index db395c11074d4..718550bcad811 100644 --- a/superset/models/helpers.py +++ b/superset/models/helpers.py @@ -20,7 +20,7 @@ from sqlalchemy.orm.exc import MultipleResultsFound import yaml -from superset import sm +from superset import security_manager from superset.utils import QueryStatus @@ -353,4 +353,4 @@ def set_perm(mapper, connection, target): # noqa ) # add to view menu if not already exists - merge_perm(sm, 'datasource_access', target.get_perm(), connection) + merge_perm(security_manager, 'datasource_access', target.get_perm(), connection) diff --git a/superset/models/sql_lab.py b/superset/models/sql_lab.py index 81ee41a1608c2..5dfd8a6631f43 100644 --- a/superset/models/sql_lab.py +++ b/superset/models/sql_lab.py @@ -17,7 +17,7 @@ ) from sqlalchemy.orm import backref, relationship -from superset import sm +from superset import security_manager from superset.models.helpers import AuditMixinNullable from superset.utils import QueryStatus, user_label @@ -76,7 +76,7 @@ class Query(Model): 'Database', foreign_keys=[database_id], backref=backref('queries', cascade='all, delete-orphan')) - user = relationship(sm.user_model, foreign_keys=[user_id]) + user = relationship(security_manager.user_model, foreign_keys=[user_id]) __table_args__ = ( sqla.Index('ti_user_id_changed_on', user_id, changed_on), @@ -138,7 +138,7 @@ class SavedQuery(Model, AuditMixinNullable): description = Column(Text) sql = Column(Text) user = relationship( - sm.user_model, + security_manager.user_model, backref=backref('saved_queries', cascade='all, delete-orphan'), foreign_keys=[user_id]) database = relationship( diff --git a/superset/security.py b/superset/security.py index 4d5b9f42b350c..8ea91b941561e 100644 --- a/superset/security.py +++ b/superset/security.py @@ -7,12 +7,13 @@ import logging +from flask import g from flask_appbuilder.security.sqla import models as ab_models +from flask_appbuilder.security.sqla.manager import SecurityManager from sqlalchemy import or_ -from superset import conf, db, sm +from superset import sql_parse from superset.connectors.connector_registry import ConnectorRegistry -from superset.models import core as models READ_ONLY_MODEL_VIEWS = { 'DatabaseAsync', @@ -77,177 +78,282 @@ ]) -def merge_perm(sm, permission_name, view_menu_name): - # Implementation copied from sm.find_permission_view_menu. - # TODO: use sm.find_permission_view_menu once issue - # https://github.com/airbnb/superset/issues/1944 is resolved. - permission = sm.find_permission(permission_name) - view_menu = sm.find_view_menu(view_menu_name) - pv = None - if permission and view_menu: - pv = sm.get_session.query(sm.permissionview_model).filter_by( - permission=permission, view_menu=view_menu).first() - if not pv and permission_name and view_menu_name: - sm.add_permission_view_menu(permission_name, view_menu_name) - - -def is_user_defined_permission(perm): - return perm.permission.name in OBJECT_SPEC_PERMISSIONS - - -def get_or_create_main_db(): - logging.info('Creating database reference') - dbobj = ( - db.session.query(models.Database) - .filter_by(database_name='main') - .first() - ) - if not dbobj: - dbobj = models.Database(database_name='main') - dbobj.set_sqlalchemy_uri(conf.get('SQLALCHEMY_DATABASE_URI')) - dbobj.expose_in_sqllab = True - dbobj.allow_run_sync = True - db.session.add(dbobj) - db.session.commit() - return dbobj - - -def is_admin_only(pvm): - # not readonly operations on read only model views allowed only for admins - if (pvm.view_menu.name in READ_ONLY_MODEL_VIEWS and - pvm.permission.name not in READ_ONLY_PERMISSION): - return True - return ( - pvm.view_menu.name in ADMIN_ONLY_VIEW_MENUS or - pvm.permission.name in ADMIN_ONLY_PERMISSIONS - ) - - -def is_alpha_only(pvm): - if (pvm.view_menu.name in GAMMA_READ_ONLY_MODEL_VIEWS and - pvm.permission.name not in READ_ONLY_PERMISSION): - return True - return ( - pvm.view_menu.name in ALPHA_ONLY_VIEW_MENUS or - pvm.permission.name in ALPHA_ONLY_PERMISSIONS - ) - - -def is_admin_pvm(pvm): - return not is_user_defined_permission(pvm) - - -def is_alpha_pvm(pvm): - return not (is_user_defined_permission(pvm) or is_admin_only(pvm)) - - -def is_gamma_pvm(pvm): - return not (is_user_defined_permission(pvm) or is_admin_only(pvm) or - is_alpha_only(pvm)) - - -def is_sql_lab_pvm(pvm): - return pvm.view_menu.name in {'SQL Lab'} or pvm.permission.name in { - 'can_sql_json', 'can_csv', 'can_search_queries', - } - - -def is_granter_pvm(pvm): - return pvm.permission.name in { - 'can_override_role_permissions', 'can_approve', - } - - -def set_role(role_name, pvm_check): - logging.info('Syncing {} perms'.format(role_name)) - sesh = sm.get_session() - pvms = sesh.query(ab_models.PermissionView).all() - pvms = [p for p in pvms if p.permission and p.view_menu] - role = sm.add_role(role_name) - role_pvms = [p for p in pvms if pvm_check(p)] - role.permissions = role_pvms - sesh.merge(role) - sesh.commit() - - -def create_custom_permissions(): - # Global perms - merge_perm(sm, 'all_datasource_access', 'all_datasource_access') - merge_perm(sm, 'all_database_access', 'all_database_access') - - -def create_missing_perms(): - """Creates missing perms for datasources, schemas and metrics""" - - logging.info( - 'Fetching a set of all perms to lookup which ones are missing') - all_pvs = set() - for pv in sm.get_session.query(sm.permissionview_model).all(): - if pv.permission and pv.view_menu: - all_pvs.add((pv.permission.name, pv.view_menu.name)) - - def merge_pv(view_menu, perm): - """Create permission view menu only if it doesn't exist""" - if view_menu and perm and (view_menu, perm) not in all_pvs: - merge_perm(sm, view_menu, perm) - - logging.info('Creating missing datasource permissions.') - datasources = ConnectorRegistry.get_all_datasources(db.session) - for datasource in datasources: - merge_pv('datasource_access', datasource.get_perm()) - merge_pv('schema_access', datasource.schema_perm) - - logging.info('Creating missing database permissions.') - databases = db.session.query(models.Database).all() - for database in databases: - merge_pv('database_access', database.perm) - - logging.info('Creating missing metrics permissions') - metrics = [] - for datasource_class in ConnectorRegistry.sources.values(): - metrics += list(db.session.query(datasource_class.metric_class).all()) - - for metric in metrics: - if metric.is_restricted: - merge_pv('metric_access', metric.perm) - - -def clean_perms(): - """FAB leaves faulty permissions that need to be cleaned up""" - logging.info('Cleaning faulty perms') - sesh = sm.get_session() - pvms = ( - sesh.query(ab_models.PermissionView) - .filter(or_( - ab_models.PermissionView.permission == None, # NOQA - ab_models.PermissionView.view_menu == None, # NOQA - )) - ) - deleted_count = pvms.delete() - sesh.commit() - if deleted_count: - logging.info('Deleted {} faulty permissions'.format(deleted_count)) - - -def sync_role_definitions(): - """Inits the Superset application with security roles and such""" - logging.info('Syncing role definition') - - get_or_create_main_db() - create_custom_permissions() - - # Creating default roles - set_role('Admin', is_admin_pvm) - set_role('Alpha', is_alpha_pvm) - set_role('Gamma', is_gamma_pvm) - set_role('granter', is_granter_pvm) - set_role('sql_lab', is_sql_lab_pvm) - - if conf.get('PUBLIC_ROLE_LIKE_GAMMA', False): - set_role('Public', is_gamma_pvm) - - create_missing_perms() - - # commit role and view menu updates - sm.get_session.commit() - clean_perms() +class SupersetSecurityManager(SecurityManager): + + def get_schema_perm(self, database, schema): + if schema: + return '[{}].[{}]'.format(database, schema) + + def can_access(self, permission_name, view_name, user=None): + """Protecting from has_access failing from missing perms/view""" + if not user: + user = g.user + if user.is_anonymous(): + return self.is_item_public(permission_name, view_name) + return self._has_view_access(user, permission_name, view_name) + + def all_datasource_access(self, user=None): + return self.can_access( + 'all_datasource_access', 'all_datasource_access', user=user) + + def database_access(self, database, user=None): + return ( + self.can_access( + 'all_database_access', 'all_database_access', user=user) or + self.can_access('database_access', database.perm, user=user) + ) + + def schema_access(self, datasource, user=None): + return ( + self.database_access(datasource.database, user=user) or + self.all_datasource_access(user=user) or + self.can_access('schema_access', datasource.schema_perm, user=user) + ) + + def datasource_access(self, datasource, user=None): + return ( + self.schema_access(datasource, user=user) or + self.can_access('datasource_access', datasource.perm, user=user) + ) + + def datasource_access_by_name( + self, database, datasource_name, schema=None): + from superset import db + + if self.database_access(database) or self.all_datasource_access(): + return True + + schema_perm = self.get_schema_perm(database, schema) + if schema and self.can_access('schema_access', schema_perm): + return True + + datasources = ConnectorRegistry.query_datasources_by_name( + db.session, database, datasource_name, schema=schema) + for datasource in datasources: + if self.can_access('datasource_access', datasource.perm): + return True + return False + + def datasource_access_by_fullname( + self, database, full_table_name, schema): + table_name_pieces = full_table_name.split('.') + if len(table_name_pieces) == 2: + table_schema = table_name_pieces[0] + table_name = table_name_pieces[1] + else: + table_schema = schema + table_name = table_name_pieces[0] + return self.datasource_access_by_name( + database, table_name, schema=table_schema) + + def rejected_datasources(self, sql, database, schema): + superset_query = sql_parse.SupersetQuery(sql) + return [ + t for t in superset_query.tables if not + self.datasource_access_by_fullname(database, t, schema)] + + def user_datasource_perms(self): + datasource_perms = set() + for r in g.user.roles: + for perm in r.permissions: + if ( + perm.permission and + 'datasource_access' == perm.permission.name): + datasource_perms.add(perm.view_menu.name) + return datasource_perms + + def schemas_accessible_by_user(self, database, schemas): + from superset import db + from superset.connectors.sqla.models import SqlaTable + if self.database_access(database) or self.all_datasource_access(): + return schemas + + subset = set() + for schema in schemas: + schema_perm = self.get_schema_perm(database, schema) + if self.can_access('schema_access', schema_perm): + subset.add(schema) + + perms = self.user_datasource_perms() + if perms: + tables = ( + db.session.query(SqlaTable) + .filter( + SqlaTable.perm.in_(perms), + SqlaTable.database_id == database.id, + ) + .all() + ) + for t in tables: + if t.schema: + subset.add(t.schema) + return sorted(list(subset)) + + def accessible_by_user(self, database, datasource_names, schema=None): + from superset import db + if self.database_access(database) or self.all_datasource_access(): + return datasource_names + + if schema: + schema_perm = self.get_schema_perm(database, schema) + if self.can_access('schema_access', schema_perm): + return datasource_names + + user_perms = self.user_datasource_perms() + user_datasources = ConnectorRegistry.query_datasources_by_permissions( + db.session, database, user_perms) + if schema: + names = { + d.table_name + for d in user_datasources if d.schema == schema} + return [d for d in datasource_names if d in names] + else: + full_names = {d.full_name for d in user_datasources} + return [d for d in datasource_names if d in full_names] + + def merge_perm(self, permission_name, view_menu_name): + # Implementation copied from sm.find_permission_view_menu. + # TODO: use sm.find_permission_view_menu once issue + # https://github.com/airbnb/superset/issues/1944 is resolved. + permission = self.find_permission(permission_name) + view_menu = self.find_view_menu(view_menu_name) + pv = None + if permission and view_menu: + pv = self.get_session.query(self.permissionview_model).filter_by( + permission=permission, view_menu=view_menu).first() + if not pv and permission_name and view_menu_name: + self.add_permission_view_menu(permission_name, view_menu_name) + + def is_user_defined_permission(self, perm): + return perm.permission.name in OBJECT_SPEC_PERMISSIONS + + def create_custom_permissions(self): + # Global perms + self.merge_perm('all_datasource_access', 'all_datasource_access') + self.merge_perm('all_database_access', 'all_database_access') + + def create_missing_perms(self): + """Creates missing perms for datasources, schemas and metrics""" + from superset import db + from superset.models import core as models + + logging.info( + 'Fetching a set of all perms to lookup which ones are missing') + all_pvs = set() + for pv in self.get_session.query(self.permissionview_model).all(): + if pv.permission and pv.view_menu: + all_pvs.add((pv.permission.name, pv.view_menu.name)) + + def merge_pv(view_menu, perm): + """Create permission view menu only if it doesn't exist""" + if view_menu and perm and (view_menu, perm) not in all_pvs: + self.merge_perm(view_menu, perm) + + logging.info('Creating missing datasource permissions.') + datasources = ConnectorRegistry.get_all_datasources(db.session) + for datasource in datasources: + merge_pv('datasource_access', datasource.get_perm()) + merge_pv('schema_access', datasource.schema_perm) + + logging.info('Creating missing database permissions.') + databases = db.session.query(models.Database).all() + for database in databases: + merge_pv('database_access', database.perm) + + logging.info('Creating missing metrics permissions') + metrics = [] + for datasource_class in ConnectorRegistry.sources.values(): + metrics += list(db.session.query(datasource_class.metric_class).all()) + + for metric in metrics: + if metric.is_restricted: + merge_pv('metric_access', metric.perm) + + def clean_perms(self): + """FAB leaves faulty permissions that need to be cleaned up""" + logging.info('Cleaning faulty perms') + sesh = self.get_session + pvms = ( + sesh.query(ab_models.PermissionView) + .filter(or_( + ab_models.PermissionView.permission == None, # NOQA + ab_models.PermissionView.view_menu == None, # NOQA + )) + ) + deleted_count = pvms.delete() + sesh.commit() + if deleted_count: + logging.info('Deleted {} faulty permissions'.format(deleted_count)) + + def sync_role_definitions(self): + """Inits the Superset application with security roles and such""" + from superset import conf + logging.info('Syncing role definition') + + self.create_custom_permissions() + + # Creating default roles + self.set_role('Admin', self.is_admin_pvm) + self.set_role('Alpha', self.is_alpha_pvm) + self.set_role('Gamma', self.is_gamma_pvm) + self.set_role('granter', self.is_granter_pvm) + self.set_role('sql_lab', self.is_sql_lab_pvm) + + if conf.get('PUBLIC_ROLE_LIKE_GAMMA', False): + self.set_role('Public', self.is_gamma_pvm) + + self.create_missing_perms() + + # commit role and view menu updates + self.get_session.commit() + self.clean_perms() + + def set_role(self, role_name, pvm_check): + logging.info('Syncing {} perms'.format(role_name)) + sesh = self.get_session + pvms = sesh.query(ab_models.PermissionView).all() + pvms = [p for p in pvms if p.permission and p.view_menu] + role = self.add_role(role_name) + role_pvms = [p for p in pvms if pvm_check(p)] + role.permissions = role_pvms + sesh.merge(role) + sesh.commit() + + def is_admin_only(self, pvm): + # not readonly operations on read only model views allowed only for admins + if (pvm.view_menu.name in READ_ONLY_MODEL_VIEWS and + pvm.permission.name not in READ_ONLY_PERMISSION): + return True + return ( + pvm.view_menu.name in ADMIN_ONLY_VIEW_MENUS or + pvm.permission.name in ADMIN_ONLY_PERMISSIONS + ) + + def is_alpha_only(self, pvm): + if (pvm.view_menu.name in GAMMA_READ_ONLY_MODEL_VIEWS and + pvm.permission.name not in READ_ONLY_PERMISSION): + return True + return ( + pvm.view_menu.name in ALPHA_ONLY_VIEW_MENUS or + pvm.permission.name in ALPHA_ONLY_PERMISSIONS + ) + + def is_admin_pvm(self, pvm): + return not self.is_user_defined_permission(pvm) + + def is_alpha_pvm(self, pvm): + return not (self.is_user_defined_permission(pvm) or self.is_admin_only(pvm)) + + def is_gamma_pvm(self, pvm): + return not (self.is_user_defined_permission(pvm) or self.is_admin_only(pvm) or + self.is_alpha_only(pvm)) + + def is_sql_lab_pvm(self, pvm): + return pvm.view_menu.name in {'SQL Lab'} or pvm.permission.name in { + 'can_sql_json', 'can_csv', 'can_search_queries', + } + + def is_granter_pvm(self, pvm): + return pvm.permission.name in { + 'can_override_role_permissions', 'can_approve', + } diff --git a/superset/sql_lab.py b/superset/sql_lab.py index d28de7c354666..c13addf1f2837 100644 --- a/superset/sql_lab.py +++ b/superset/sql_lab.py @@ -17,7 +17,7 @@ from sqlalchemy.orm import sessionmaker from sqlalchemy.pool import NullPool -from superset import app, dataframe, db, results_backend, sm, utils +from superset import app, dataframe, db, results_backend, security_manager, utils from superset.db_engine_specs import LimitMethod from superset.jinja_context import get_template_processor from superset.models.sql_lab import Query @@ -196,7 +196,8 @@ def handle_error(msg): # Hook to allow environment-specific mutation (usually comments) to the SQL SQL_QUERY_MUTATOR = config.get('SQL_QUERY_MUTATOR') if SQL_QUERY_MUTATOR: - executed_sql = SQL_QUERY_MUTATOR(executed_sql, user_name, sm, database) + executed_sql = SQL_QUERY_MUTATOR( + executed_sql, user_name, security_manager, database) query.executed_sql = executed_sql query.status = QueryStatus.RUNNING diff --git a/superset/utils.py b/superset/utils.py index 78fb12b669dbc..8e91e8dc80406 100644 --- a/superset/utils.py +++ b/superset/utils.py @@ -55,13 +55,6 @@ DTTM_ALIAS = '__timestamp' -def can_access(sm, permission_name, view_name, user): - """Protecting from has_access failing from missing perms/view""" - if user.is_anonymous(): - return sm.is_item_public(permission_name, view_name) - return sm._has_view_access(user, permission_name, view_name) - - def flasher(msg, severity=None): """Flask's flash if available, logging call if not""" try: @@ -477,11 +470,6 @@ def get_datasource_full_name(database_name, datasource_name, schema=None): return '[{}].[{}].[{}]'.format(database_name, schema, datasource_name) -def get_schema_perm(database, schema): - if schema: - return '[{}].[{}]'.format(database, schema) - - def validate_json(obj): if obj: try: @@ -834,3 +822,23 @@ def user_label(user): return user.first_name + ' ' + user.last_name else: return user.username + + +def get_or_create_main_db(): + from superset import conf, db + from superset.models import core as models + + logging.info('Creating database reference') + dbobj = ( + db.session.query(models.Database) + .filter_by(database_name='main') + .first() + ) + if not dbobj: + dbobj = models.Database(database_name='main') + dbobj.set_sqlalchemy_uri(conf.get('SQLALCHEMY_DATABASE_URI')) + dbobj.expose_in_sqllab = True + dbobj.allow_run_sync = True + db.session.add(dbobj) + db.session.commit() + return dbobj diff --git a/superset/views/base.py b/superset/views/base.py index 54a98c2c4e210..328789f0dbb66 100644 --- a/superset/views/base.py +++ b/superset/views/base.py @@ -20,9 +20,7 @@ from flask_babel import lazy_gettext as _ import yaml -from superset import appbuilder, conf, db, sm, sql_parse, utils -from superset.connectors.connector_registry import ConnectorRegistry -from superset.connectors.sqla.models import SqlaTable +from superset import conf, security_manager, utils from superset.translations.utils import get_language_pack FRONTEND_CONF_KEYS = ( @@ -83,131 +81,11 @@ def get_datasource_exist_error_mgs(full_name): def get_user_roles(): if g.user.is_anonymous(): public_role = conf.get('AUTH_ROLE_PUBLIC') - return [appbuilder.sm.find_role(public_role)] if public_role else [] + return [security_manager.find_role(public_role)] if public_role else [] return g.user.roles class BaseSupersetView(BaseView): - def can_access(self, permission_name, view_name, user=None): - if not user: - user = g.user - return utils.can_access( - appbuilder.sm, permission_name, view_name, user) - - def all_datasource_access(self, user=None): - return self.can_access( - 'all_datasource_access', 'all_datasource_access', user=user) - - def database_access(self, database, user=None): - return ( - self.can_access( - 'all_database_access', 'all_database_access', user=user) or - self.can_access('database_access', database.perm, user=user) - ) - - def schema_access(self, datasource, user=None): - return ( - self.database_access(datasource.database, user=user) or - self.all_datasource_access(user=user) or - self.can_access('schema_access', datasource.schema_perm, user=user) - ) - - def datasource_access(self, datasource, user=None): - return ( - self.schema_access(datasource, user=user) or - self.can_access('datasource_access', datasource.perm, user=user) - ) - - def datasource_access_by_name( - self, database, datasource_name, schema=None): - if self.database_access(database) or self.all_datasource_access(): - return True - - schema_perm = utils.get_schema_perm(database, schema) - if schema and self.can_access('schema_access', schema_perm): - return True - - datasources = ConnectorRegistry.query_datasources_by_name( - db.session, database, datasource_name, schema=schema) - for datasource in datasources: - if self.can_access('datasource_access', datasource.perm): - return True - return False - - def datasource_access_by_fullname( - self, database, full_table_name, schema): - table_name_pieces = full_table_name.split('.') - if len(table_name_pieces) == 2: - table_schema = table_name_pieces[0] - table_name = table_name_pieces[1] - else: - table_schema = schema - table_name = table_name_pieces[0] - return self.datasource_access_by_name( - database, table_name, schema=table_schema) - - def rejected_datasources(self, sql, database, schema): - superset_query = sql_parse.SupersetQuery(sql) - return [ - t for t in superset_query.tables if not - self.datasource_access_by_fullname(database, t, schema)] - - def user_datasource_perms(self): - datasource_perms = set() - for r in g.user.roles: - for perm in r.permissions: - if ( - perm.permission and - 'datasource_access' == perm.permission.name): - datasource_perms.add(perm.view_menu.name) - return datasource_perms - - def schemas_accessible_by_user(self, database, schemas): - if self.database_access(database) or self.all_datasource_access(): - return schemas - - subset = set() - for schema in schemas: - schema_perm = utils.get_schema_perm(database, schema) - if self.can_access('schema_access', schema_perm): - subset.add(schema) - - perms = self.user_datasource_perms() - if perms: - tables = ( - db.session.query(SqlaTable) - .filter( - SqlaTable.perm.in_(perms), - SqlaTable.database_id == database.id, - ) - .all() - ) - for t in tables: - if t.schema: - subset.add(t.schema) - return sorted(list(subset)) - - def accessible_by_user(self, database, datasource_names, schema=None): - if self.database_access(database) or self.all_datasource_access(): - return datasource_names - - if schema: - schema_perm = utils.get_schema_perm(database, schema) - if self.can_access('schema_access', schema_perm): - return datasource_names - - user_perms = self.user_datasource_perms() - user_datasources = ConnectorRegistry.query_datasources_by_permissions( - db.session, database, user_perms) - if schema: - names = { - d.table_name - for d in user_datasources if d.schema == schema} - return [d for d in datasource_names if d in names] - else: - full_names = {d.full_name for d in user_datasources} - return [d for d in datasource_names if d in full_names] - def common_bootsrap_payload(self): """Common data always sent to the client""" messages = get_flashed_messages(with_categories=True) @@ -274,31 +152,32 @@ def _delete(self, pk): except Exception as e: flash(str(e), 'danger') else: - view_menu = sm.find_view_menu(item.get_perm()) - pvs = sm.get_session.query(sm.permissionview_model).filter_by( + view_menu = security_manager.find_view_menu(item.get_perm()) + pvs = security_manager.get_session.query( + security_manager.permissionview_model).filter_by( view_menu=view_menu).all() schema_view_menu = None if hasattr(item, 'schema_perm'): - schema_view_menu = sm.find_view_menu(item.schema_perm) + schema_view_menu = security_manager.find_view_menu(item.schema_perm) - pvs.extend(sm.get_session.query( - sm.permissionview_model).filter_by( + pvs.extend(security_manager.get_session.query( + security_manager.permissionview_model).filter_by( view_menu=schema_view_menu).all()) if self.datamodel.delete(item): self.post_delete(item) for pv in pvs: - sm.get_session.delete(pv) + security_manager.get_session.delete(pv) if view_menu: - sm.get_session.delete(view_menu) + security_manager.get_session.delete(view_menu) if schema_view_menu: - sm.get_session.delete(schema_view_menu) + security_manager.get_session.delete(schema_view_menu) - sm.get_session.commit() + security_manager.get_session.commit() flash(*self.datamodel.message) self.update_redirect() diff --git a/superset/views/core.py b/superset/views/core.py index a080a4b8ebe3a..f0956e359e68f 100755 --- a/superset/views/core.py +++ b/superset/views/core.py @@ -33,7 +33,7 @@ from werkzeug.utils import secure_filename from superset import ( - app, appbuilder, cache, db, results_backend, security, sm, sql_lab, utils, + app, appbuilder, cache, db, results_backend, security_manager, sql_lab, utils, viz, ) from superset.connectors.connector_registry import ConnectorRegistry @@ -57,7 +57,6 @@ config = app.config stats_logger = config.get('STATS_LOGGER') log_this = models.Log.log_this -can_access = utils.can_access DAR = models.DatasourceAccessRequest @@ -284,10 +283,10 @@ class DatabaseView(SupersetModelView, DeleteMixin, YamlExportMixin): # noqa def pre_add(self, db): db.set_sqlalchemy_uri(db.sqlalchemy_uri) - security.merge_perm(sm, 'database_access', db.perm) + security_manager.merge_perm('database_access', db.perm) for schema in db.all_schema_names(): - security.merge_perm( - sm, 'schema_access', utils.get_schema_perm(db, schema)) + security_manager.merge_perm( + 'schema_access', security_manager.get_schema_perm(db, schema)) def pre_update(self, db): self.pre_add(db) @@ -809,13 +808,13 @@ def override_role_permissions(self): existing_datasources = ConnectorRegistry.get_all_datasources(db.session) datasources = [ d for d in existing_datasources if d.full_name in db_ds_names] - role = sm.find_role(role_name) + role = security_manager.find_role(role_name) # remove all permissions role.permissions = [] # grant permissions to the list of datasources granted_perms = [] for datasource in datasources: - view_menu_perm = sm.find_permission_view_menu( + view_menu_perm = security_manager.find_permission_view_menu( view_menu_name=datasource.perm, permission_name='datasource_access') # prevent creating empty permissions @@ -854,7 +853,7 @@ def request_access(self): has_access = all( ( - datasource and self.datasource_access(datasource) + datasource and security_manager.datasource_access(datasource) for datasource in datasources )) if has_access: @@ -884,9 +883,9 @@ def clean_fulfilled_requests(session): for r in session.query(DAR).all(): datasource = ConnectorRegistry.get_datasource( r.datasource_type, r.datasource_id, session) - user = sm.get_user_by_id(r.created_by_fk) + user = security_manager.get_user_by_id(r.created_by_fk) if not datasource or \ - self.datasource_access(datasource, user): + security_manager.datasource_access(datasource, user): # datasource does not exist anymore session.delete(r) session.commit() @@ -904,7 +903,7 @@ def clean_fulfilled_requests(session): flash(DATASOURCE_MISSING_ERR, 'alert') return json_error_response(DATASOURCE_MISSING_ERR) - requested_by = sm.find_user(username=created_by_username) + requested_by = security_manager.find_user(username=created_by_username) if not requested_by: flash(USER_MISSING_ERR, 'alert') return json_error_response(USER_MISSING_ERR) @@ -923,10 +922,10 @@ def clean_fulfilled_requests(session): return json_error_response(ACCESS_REQUEST_MISSING_ERR) # check if you can approve - if self.all_datasource_access() or g.user.id == datasource.owner_id: + if security_manager.all_datasource_access() or g.user.id == datasource.owner_id: # can by done by admin only if role_to_grant: - role = sm.find_role(role_to_grant) + role = security_manager.find_role(role_to_grant) requested_by.roles.append(role) msg = __( '%(user)s was granted the role %(role)s that gives access ' @@ -940,10 +939,10 @@ def clean_fulfilled_requests(session): flash(msg, 'info') if role_to_extend: - perm_view = sm.find_permission_view_menu( + perm_view = security_manager.find_permission_view_menu( 'email/datasource_access', datasource.perm) - role = sm.find_role(role_to_extend) - sm.add_permission_role(role, perm_view) + role = security_manager.find_role(role_to_extend) + security_manager.add_permission_role(role, perm_view) msg = __('Role %(r)s was extended to provide the access to ' 'the datasource %(ds)s', r=role_to_extend, ds=datasource.full_name) @@ -1085,7 +1084,7 @@ def generate_json(self, datasource_type, datasource_id, form_data, utils.error_msg_from_exception(e), stacktrace=traceback.format_exc()) - if not self.datasource_access(viz_obj.datasource): + if not security_manager.datasource_access(viz_obj.datasource, g.user): return json_error_response(DATASOURCE_ACCESS_ERR, status=404) if csv: @@ -1245,7 +1244,7 @@ def explore(self, datasource_type=None, datasource_id=None): flash(DATASOURCE_MISSING_ERR, 'danger') return redirect(error_redirect) - if not self.datasource_access(datasource): + if not security_manager.datasource_access(datasource): flash( __(get_datasource_access_error_msg(datasource.name)), 'danger') @@ -1260,9 +1259,10 @@ def explore(self, datasource_type=None, datasource_id=None): return redirect(datasource.default_endpoint) # slc perms - slice_add_perm = self.can_access('can_add', 'SliceModelView') + slice_add_perm = security_manager.can_access('can_add', 'SliceModelView') slice_overwrite_perm = is_owner(slc, g.user) - slice_download_perm = self.can_access('can_download', 'SliceModelView') + slice_download_perm = security_manager.can_access( + 'can_download', 'SliceModelView') form_data['datasource'] = str(datasource_id) + '__' + datasource_type @@ -1342,7 +1342,7 @@ def filter(self, datasource_type, datasource_id, column): datasource_type, datasource_id, db.session) if not datasource: return json_error_response(DATASOURCE_MISSING_ERR) - if not self.datasource_access(datasource): + if not security_manager.datasource_access(datasource): return json_error_response(DATASOURCE_ACCESS_ERR) payload = json.dumps( @@ -1402,7 +1402,7 @@ def save_or_overwrite_slice( 'info') elif request.args.get('add_to_dash') == 'new': # check create dashboard permissions - dash_add_perm = self.can_access('can_add', 'DashboardModelView') + dash_add_perm = security_manager.can_access('can_add', 'DashboardModelView') if not dash_add_perm: return json_error_response( _('You don\'t have the rights to ') + _('create a ') + _('dashboard'), @@ -1501,7 +1501,7 @@ def schemas(self, db_id): .one() ) schemas = database.all_schema_names() - schemas = self.schemas_accessible_by_user(database, schemas) + schemas = security_manager.schemas_accessible_by_user(database, schemas) return Response( json.dumps({'schemas': schemas}), mimetype='application/json') @@ -1515,9 +1515,9 @@ def tables(self, db_id, schema, substr): schema = utils.js_string_to_python(schema) substr = utils.js_string_to_python(substr) database = db.session.query(models.Database).filter_by(id=db_id).one() - table_names = self.accessible_by_user( + table_names = security_manager.accessible_by_user( database, database.all_table_names(schema), schema) - view_names = self.accessible_by_user( + view_names = security_manager.accessible_by_user( database, database.all_view_names(schema), schema) if substr: @@ -1761,7 +1761,7 @@ def csrf_token(self): @expose('/fave_dashboards_by_username//', methods=['GET']) def fave_dashboards_by_username(self, username): """This lets us use a user's username to pull favourite dashboards""" - user = sm.find_user(username=username) + user = security_manager.find_user(username=username) return self.fave_dashboards(user.get_id()) @api @@ -2041,7 +2041,7 @@ def dashboard(self, dashboard_id): if config.get('ENABLE_ACCESS_REQUEST'): for datasource in datasources: - if datasource and not self.datasource_access(datasource): + if datasource and not security_manager.datasource_access(datasource): flash( __(get_datasource_access_error_msg(datasource.name)), 'danger') @@ -2057,7 +2057,7 @@ def dashboard(**kwargs): # noqa dash_edit_perm = check_ownership(dash, raise_if_false=False) dash_save_perm = \ - dash_edit_perm and self.can_access('can_save_dash', 'Superset') + dash_edit_perm and security_manager.can_access('can_save_dash', 'Superset') standalone_mode = request.args.get('standalone') == 'true' @@ -2108,7 +2108,7 @@ def sync_druid_source(self): metrics_spec: list of metrics (dictionary). Metric consists of 2 attributes: type and name. Type can be count, etc. `count` type is stored internally as longSum - other fields will be ignored. + other fields will be ignored. Example: { 'name': 'test_click', @@ -2121,7 +2121,7 @@ def sync_druid_source(self): user_name = payload['user'] cluster_name = payload['cluster'] - user = sm.find_user(username=user_name) + user = security_manager.find_user(username=user_name) DruidDatasource = ConnectorRegistry.sources['druid'] DruidCluster = DruidDatasource.cluster_class if not user: @@ -2324,7 +2324,7 @@ def results(self, key): ) query = db.session.query(Query).filter_by(results_key=key).one() - rejected_tables = self.rejected_datasources( + rejected_tables = security_manager.rejected_datasources( query.sql, query.database, query.schema) if rejected_tables: return json_error_response(get_datasource_access_error_msg( @@ -2373,7 +2373,7 @@ def sql_json(self): json_error_response( 'Database with id {} is missing.'.format(database_id)) - rejected_tables = self.rejected_datasources(sql, mydb, schema) + rejected_tables = security_manager.rejected_datasources(sql, mydb, schema) if rejected_tables: return json_error_response(get_datasource_access_error_msg( '{}'.format(rejected_tables))) @@ -2470,7 +2470,7 @@ def csv(self, client_id): .one() ) - rejected_tables = self.rejected_datasources( + rejected_tables = security_manager.rejected_datasources( query.sql, query.database, query.schema) if rejected_tables: flash(get_datasource_access_error_msg('{}'.format(rejected_tables))) @@ -2514,7 +2514,7 @@ def fetch_datasource_metadata(self): return json_error_response(DATASOURCE_MISSING_ERR) # Check permission for datasource - if not self.datasource_access(datasource): + if not security_manager.datasource_access(datasource): return json_error_response(DATASOURCE_ACCESS_ERR) return json_success(json.dumps(datasource.data)) @@ -2662,7 +2662,7 @@ def sliceQuery(self, slice_id): get the database query string for this slice """ viz_obj = self.get_viz(slice_id) - if not self.datasource_access(viz_obj.datasource): + if not security_manager.datasource_access(viz_obj.datasource): return json_error_response(DATASOURCE_ACCESS_ERR, status=401) return self.get_query_string_response(viz_obj) diff --git a/tests/access_tests.py b/tests/access_tests.py index 39938c9b5534b..a669f5dec5937 100644 --- a/tests/access_tests.py +++ b/tests/access_tests.py @@ -10,7 +10,7 @@ import mock -from superset import app, db, security, sm +from superset import app, db, security_manager from superset.connectors.connector_registry import ConnectorRegistry from superset.connectors.druid.models import DruidDatasource from superset.connectors.sqla.models import SqlaTable @@ -70,13 +70,14 @@ def create_access_request(session, ds_type, ds_name, role_name, user_name): else: ds = session.query(ds_class).filter( ds_class.datasource_name == ds_name).first() - ds_perm_view = sm.find_permission_view_menu( + ds_perm_view = security_manager.find_permission_view_menu( 'datasource_access', ds.perm) - sm.add_permission_role(sm.find_role(role_name), ds_perm_view) + security_manager.add_permission_role( + security_manager.find_role(role_name), ds_perm_view) access_request = models.DatasourceAccessRequest( datasource_id=ds.id, datasource_type=ds_type, - created_by_fk=sm.find_user(username=user_name).id, + created_by_fk=security_manager.find_user(username=user_name).id, ) session.add(access_request) session.commit() @@ -89,21 +90,21 @@ class RequestAccessTests(SupersetTestCase): @classmethod def setUpClass(cls): - sm.add_role('override_me') - sm.add_role(TEST_ROLE_1) - sm.add_role(TEST_ROLE_2) - sm.add_role(DB_ACCESS_ROLE) - sm.add_role(SCHEMA_ACCESS_ROLE) + security_manager.add_role('override_me') + security_manager.add_role(TEST_ROLE_1) + security_manager.add_role(TEST_ROLE_2) + security_manager.add_role(DB_ACCESS_ROLE) + security_manager.add_role(SCHEMA_ACCESS_ROLE) db.session.commit() @classmethod def tearDownClass(cls): - override_me = sm.find_role('override_me') + override_me = security_manager.find_role('override_me') db.session.delete(override_me) - db.session.delete(sm.find_role(TEST_ROLE_1)) - db.session.delete(sm.find_role(TEST_ROLE_2)) - db.session.delete(sm.find_role(DB_ACCESS_ROLE)) - db.session.delete(sm.find_role(SCHEMA_ACCESS_ROLE)) + db.session.delete(security_manager.find_role(TEST_ROLE_1)) + db.session.delete(security_manager.find_role(TEST_ROLE_2)) + db.session.delete(security_manager.find_role(DB_ACCESS_ROLE)) + db.session.delete(security_manager.find_role(SCHEMA_ACCESS_ROLE)) db.session.commit() def setUp(self): @@ -111,7 +112,7 @@ def setUp(self): def tearDown(self): self.logout() - override_me = sm.find_role('override_me') + override_me = security_manager.find_role('override_me') override_me.permissions = [] db.session.commit() db.session.close() @@ -133,7 +134,7 @@ def test_override_role_permissions_1_table(self): content_type='application/json') self.assertEquals(201, response.status_code) - updated_override_me = sm.find_role('override_me') + updated_override_me = security_manager.find_role('override_me') self.assertEquals(1, len(updated_override_me.permissions)) birth_names = self.get_table_by_name('birth_names') self.assertEquals( @@ -150,7 +151,7 @@ def test_override_role_permissions_druid_and_table(self): content_type='application/json') self.assertEquals(201, response.status_code) - updated_role = sm.find_role('override_me') + updated_role = security_manager.find_role('override_me') perms = sorted( updated_role.permissions, key=lambda p: p.view_menu.name) druid_ds_1 = self.get_druid_ds_by_name('druid_ds_1') @@ -169,9 +170,9 @@ def test_override_role_permissions_druid_and_table(self): self.assertEquals(3, len(perms)) def test_override_role_permissions_drops_absent_perms(self): - override_me = sm.find_role('override_me') + override_me = security_manager.find_role('override_me') override_me.permissions.append( - sm.find_permission_view_menu( + security_manager.find_permission_view_menu( view_menu_name=self.get_table_by_name('long_lat').perm, permission_name='datasource_access'), ) @@ -182,7 +183,7 @@ def test_override_role_permissions_drops_absent_perms(self): data=json.dumps(ROLE_TABLES_PERM_DATA), content_type='application/json') self.assertEquals(201, response.status_code) - updated_override_me = sm.find_role('override_me') + updated_override_me = security_manager.find_role('override_me') self.assertEquals(1, len(updated_override_me.permissions)) birth_names = self.get_table_by_name('birth_names') self.assertEquals( @@ -218,8 +219,8 @@ def test_clean_requests_after_role_extend(self): access_requests = self.get_access_requests('gamma', 'table', ds_1_id) self.assertFalse(access_requests) - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role('test_role1')) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role('test_role1')) def test_clean_requests_after_alpha_grant(self): session = db.session @@ -234,8 +235,8 @@ def test_clean_requests_after_alpha_grant(self): session, 'table', 'birth_names', TEST_ROLE_2, 'gamma2') ds_1_id = access_request1.datasource_id # gamma becomes alpha - alpha_role = sm.find_role('Alpha') - gamma_user = sm.find_user(username='gamma') + alpha_role = security_manager.find_role('Alpha') + gamma_user = security_manager.find_user(username='gamma') gamma_user.roles.append(alpha_role) session.commit() access_requests = self.get_access_requests('gamma', 'table', ds_1_id) @@ -245,8 +246,8 @@ def test_clean_requests_after_alpha_grant(self): access_requests = self.get_access_requests('gamma', 'table', ds_1_id) self.assertFalse(access_requests) - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role('Alpha')) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role('Alpha')) session.commit() def test_clean_requests_after_db_grant(self): @@ -256,7 +257,7 @@ def test_clean_requests_after_db_grant(self): # Gamma gets database access, gamma2 access request granted # Check if request by gamma has been deleted - gamma_user = sm.find_user(username='gamma') + gamma_user = security_manager.find_user(username='gamma') access_request1 = create_access_request( session, 'table', 'long_lat', TEST_ROLE_1, 'gamma') create_access_request( @@ -265,13 +266,12 @@ def test_clean_requests_after_db_grant(self): # gamma gets granted database access database = session.query(models.Database).first() - security.merge_perm( - sm, 'database_access', database.perm) - ds_perm_view = sm.find_permission_view_menu( + security_manager.merge_perm('database_access', database.perm) + ds_perm_view = security_manager.find_permission_view_menu( 'database_access', database.perm) - sm.add_permission_role( - sm.find_role(DB_ACCESS_ROLE), ds_perm_view) - gamma_user.roles.append(sm.find_role(DB_ACCESS_ROLE)) + security_manager.add_permission_role( + security_manager.find_role(DB_ACCESS_ROLE), ds_perm_view) + gamma_user.roles.append(security_manager.find_role(DB_ACCESS_ROLE)) session.commit() access_requests = self.get_access_requests('gamma', 'table', ds_1_id) self.assertTrue(access_requests) @@ -281,8 +281,8 @@ def test_clean_requests_after_db_grant(self): access_requests = self.get_access_requests('gamma', 'table', ds_1_id) self.assertFalse(access_requests) - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role(DB_ACCESS_ROLE)) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role(DB_ACCESS_ROLE)) session.commit() def test_clean_requests_after_schema_grant(self): @@ -292,7 +292,7 @@ def test_clean_requests_after_schema_grant(self): # Gamma gets schema access, gamma2 access request granted # Check if request by gamma has been deleted - gamma_user = sm.find_user(username='gamma') + gamma_user = security_manager.find_user(username='gamma') access_request1 = create_access_request( session, 'table', 'wb_health_population', TEST_ROLE_1, 'gamma') create_access_request( @@ -302,21 +302,20 @@ def test_clean_requests_after_schema_grant(self): table_name='wb_health_population').first() ds.schema = 'temp_schema' - security.merge_perm( - sm, 'schema_access', ds.schema_perm) - schema_perm_view = sm.find_permission_view_menu( + security_manager.merge_perm('schema_access', ds.schema_perm) + schema_perm_view = security_manager.find_permission_view_menu( 'schema_access', ds.schema_perm) - sm.add_permission_role( - sm.find_role(SCHEMA_ACCESS_ROLE), schema_perm_view) - gamma_user.roles.append(sm.find_role(SCHEMA_ACCESS_ROLE)) + security_manager.add_permission_role( + security_manager.find_role(SCHEMA_ACCESS_ROLE), schema_perm_view) + gamma_user.roles.append(security_manager.find_role(SCHEMA_ACCESS_ROLE)) session.commit() # gamma2 request gets fulfilled self.client.get(EXTEND_ROLE_REQUEST.format( 'table', ds_1_id, 'gamma2', TEST_ROLE_2)) access_requests = self.get_access_requests('gamma', 'table', ds_1_id) self.assertFalse(access_requests) - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role(SCHEMA_ACCESS_ROLE)) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role(SCHEMA_ACCESS_ROLE)) ds = session.query(SqlaTable).filter_by( table_name='wb_health_population').first() @@ -329,7 +328,7 @@ def test_approve(self, mock_send_mime): if app.config.get('ENABLE_ACCESS_REQUEST'): session = db.session TEST_ROLE_NAME = 'table_role' - sm.add_role(TEST_ROLE_NAME) + security_manager.add_role(TEST_ROLE_NAME) # Case 1. Grant new role to the user. @@ -341,8 +340,8 @@ def test_approve(self, mock_send_mime): # Test email content. self.assertTrue(mock_send_mime.called) call_args = mock_send_mime.call_args[0] - self.assertEqual([sm.find_user(username='gamma').email, - sm.find_user(username='admin').email], + self.assertEqual([security_manager.find_user(username='gamma').email, + security_manager.find_user(username='admin').email], call_args[1]) self.assertEqual( '[Superset] Access to the datasource {} was granted'.format( @@ -354,7 +353,7 @@ def test_approve(self, mock_send_mime): # request was removed self.assertFalse(access_requests) # user was granted table_role - user_roles = [r.name for r in sm.find_user('gamma').roles] + user_roles = [r.name for r in security_manager.find_user('gamma').roles] self.assertIn(TEST_ROLE_NAME, user_roles) # Case 2. Extend the role to have access to the table @@ -371,8 +370,8 @@ def test_approve(self, mock_send_mime): # Test email content. self.assertTrue(mock_send_mime.called) call_args = mock_send_mime.call_args[0] - self.assertEqual([sm.find_user(username='gamma').email, - sm.find_user(username='admin').email], + self.assertEqual([security_manager.find_user(username='gamma').email, + security_manager.find_user(username='admin').email], call_args[1]) self.assertEqual( '[Superset] Access to the datasource {} was granted'.format( @@ -383,21 +382,21 @@ def test_approve(self, mock_send_mime): # request was removed self.assertFalse(access_requests) # table_role was extended to grant access to the long_lat table/ - perm_view = sm.find_permission_view_menu( + perm_view = security_manager.find_permission_view_menu( 'datasource_access', long_lat_perm) - TEST_ROLE = sm.find_role(TEST_ROLE_NAME) + TEST_ROLE = security_manager.find_role(TEST_ROLE_NAME) self.assertIn(perm_view, TEST_ROLE.permissions) # Case 3. Grant new role to the user to access the druid datasource. - sm.add_role('druid_role') + security_manager.add_role('druid_role') access_request3 = create_access_request( session, 'druid', 'druid_ds_1', 'druid_role', 'gamma') self.get_resp(GRANT_ROLE_REQUEST.format( 'druid', access_request3.datasource_id, 'gamma', 'druid_role')) # user was granted table_role - user_roles = [r.name for r in sm.find_user('gamma').roles] + user_roles = [r.name for r in security_manager.find_user('gamma').roles] self.assertIn('druid_role', user_roles) # Case 4. Extend the role to have access to the druid datasource @@ -409,17 +408,17 @@ def test_approve(self, mock_send_mime): self.client.get(EXTEND_ROLE_REQUEST.format( 'druid', access_request4.datasource_id, 'gamma', 'druid_role')) # druid_role was extended to grant access to the druid_access_ds_2 - druid_role = sm.find_role('druid_role') - perm_view = sm.find_permission_view_menu( + druid_role = security_manager.find_role('druid_role') + perm_view = security_manager.find_permission_view_menu( 'datasource_access', druid_ds_2_perm) self.assertIn(perm_view, druid_role.permissions) # cleanup - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role('druid_role')) - gamma_user.roles.remove(sm.find_role(TEST_ROLE_NAME)) - session.delete(sm.find_role('druid_role')) - session.delete(sm.find_role(TEST_ROLE_NAME)) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role('druid_role')) + gamma_user.roles.remove(security_manager.find_role(TEST_ROLE_NAME)) + session.delete(security_manager.find_role('druid_role')) + session.delete(security_manager.find_role(TEST_ROLE_NAME)) session.commit() def test_request_access(self): @@ -427,9 +426,9 @@ def test_request_access(self): session = db.session self.logout() self.login(username='gamma') - gamma_user = sm.find_user(username='gamma') - sm.add_role('dummy_role') - gamma_user.roles.append(sm.find_role('dummy_role')) + gamma_user = security_manager.find_user(username='gamma') + security_manager.add_role('dummy_role') + gamma_user.roles.append(security_manager.find_role('dummy_role')) session.commit() ACCESS_REQUEST = ( @@ -461,14 +460,16 @@ def test_request_access(self): table_3_id = table3.id table3_perm = table3.perm - sm.add_role('energy_usage_role') - alpha_role = sm.find_role('Alpha') - sm.add_permission_role( + security_manager.add_role('energy_usage_role') + alpha_role = security_manager.find_role('Alpha') + security_manager.add_permission_role( alpha_role, - sm.find_permission_view_menu('datasource_access', table3_perm)) - sm.add_permission_role( - sm.find_role('energy_usage_role'), - sm.find_permission_view_menu('datasource_access', table3_perm)) + security_manager.find_permission_view_menu( + 'datasource_access', table3_perm)) + security_manager.add_permission_role( + security_manager.find_role('energy_usage_role'), + security_manager.find_permission_view_menu( + 'datasource_access', table3_perm)) session.commit() self.get_resp( @@ -500,14 +501,16 @@ def test_request_access(self): druid_ds_5_id = druid_ds_5.id druid_ds_5_perm = druid_ds_5.perm - druid_ds_2_role = sm.add_role('druid_ds_2_role') - admin_role = sm.find_role('Admin') - sm.add_permission_role( + druid_ds_2_role = security_manager.add_role('druid_ds_2_role') + admin_role = security_manager.find_role('Admin') + security_manager.add_permission_role( admin_role, - sm.find_permission_view_menu('datasource_access', druid_ds_5_perm)) - sm.add_permission_role( + security_manager.find_permission_view_menu( + 'datasource_access', druid_ds_5_perm)) + security_manager.add_permission_role( druid_ds_2_role, - sm.find_permission_view_menu('datasource_access', druid_ds_5_perm)) + security_manager.find_permission_view_menu( + 'datasource_access', druid_ds_5_perm)) session.commit() self.get_resp(ACCESS_REQUEST.format('druid', druid_ds_5_id, 'go')) @@ -520,8 +523,8 @@ def test_request_access(self): ''.format(approve_link_5)) # cleanup - gamma_user = sm.find_user(username='gamma') - gamma_user.roles.remove(sm.find_role('dummy_role')) + gamma_user = security_manager.find_user(username='gamma') + gamma_user.roles.remove(security_manager.find_role('dummy_role')) session.commit() diff --git a/tests/base_tests.py b/tests/base_tests.py index dcc67988c3172..c491637f22a58 100644 --- a/tests/base_tests.py +++ b/tests/base_tests.py @@ -12,11 +12,10 @@ from flask_appbuilder.security.sqla import models as ab_models -from superset import app, appbuilder, cli, db, security, sm +from superset import app, cli, db, security_manager, utils from superset.connectors.druid.models import DruidCluster, DruidDatasource from superset.connectors.sqla.models import SqlaTable from superset.models import core as models -from superset.security import sync_role_definitions os.environ['SUPERSET_CONFIG'] = 'tests.superset_test_config' @@ -36,59 +35,60 @@ def __init__(self, *args, **kwargs): logging.info('Loading examples') cli.load_examples(load_test_data=True) logging.info('Done loading examples') - sync_role_definitions() + security_manager.sync_role_definitions() os.environ['examples_loaded'] = '1' else: - sync_role_definitions() + security_manager.sync_role_definitions() super(SupersetTestCase, self).__init__(*args, **kwargs) self.client = app.test_client() self.maxDiff = None - gamma_sqllab_role = sm.add_role('gamma_sqllab') - for perm in sm.find_role('Gamma').permissions: - sm.add_permission_role(gamma_sqllab_role, perm) - db_perm = self.get_main_database(sm.get_session).perm - security.merge_perm(sm, 'database_access', db_perm) - db_pvm = sm.find_permission_view_menu( + gamma_sqllab_role = security_manager.add_role('gamma_sqllab') + for perm in security_manager.find_role('Gamma').permissions: + security_manager.add_permission_role(gamma_sqllab_role, perm) + utils.get_or_create_main_db() + db_perm = self.get_main_database(security_manager.get_session).perm + security_manager.merge_perm('database_access', db_perm) + db_pvm = security_manager.find_permission_view_menu( view_menu_name=db_perm, permission_name='database_access') gamma_sqllab_role.permissions.append(db_pvm) - for perm in sm.find_role('sql_lab').permissions: - sm.add_permission_role(gamma_sqllab_role, perm) + for perm in security_manager.find_role('sql_lab').permissions: + security_manager.add_permission_role(gamma_sqllab_role, perm) - admin = appbuilder.sm.find_user('admin') + admin = security_manager.find_user('admin') if not admin: - appbuilder.sm.add_user( + security_manager.add_user( 'admin', 'admin', ' user', 'admin@fab.org', - appbuilder.sm.find_role('Admin'), + security_manager.find_role('Admin'), password='general') - gamma = appbuilder.sm.find_user('gamma') + gamma = security_manager.find_user('gamma') if not gamma: - appbuilder.sm.add_user( + security_manager.add_user( 'gamma', 'gamma', 'user', 'gamma@fab.org', - appbuilder.sm.find_role('Gamma'), + security_manager.find_role('Gamma'), password='general') - gamma2 = appbuilder.sm.find_user('gamma2') + gamma2 = security_manager.find_user('gamma2') if not gamma2: - appbuilder.sm.add_user( + security_manager.add_user( 'gamma2', 'gamma2', 'user', 'gamma2@fab.org', - appbuilder.sm.find_role('Gamma'), + security_manager.find_role('Gamma'), password='general') - gamma_sqllab_user = appbuilder.sm.find_user('gamma_sqllab') + gamma_sqllab_user = security_manager.find_user('gamma_sqllab') if not gamma_sqllab_user: - appbuilder.sm.add_user( + security_manager.add_user( 'gamma_sqllab', 'gamma_sqllab', 'user', 'gamma_sqllab@fab.org', gamma_sqllab_role, password='general') - alpha = appbuilder.sm.find_user('alpha') + alpha = security_manager.find_user('alpha') if not alpha: - appbuilder.sm.add_user( + security_manager.add_user( 'alpha', 'alpha', 'user', 'alpha@fab.org', - appbuilder.sm.find_role('Alpha'), + security_manager.find_role('Alpha'), password='general') - sm.get_session.commit() + security_manager.get_session.commit() # create druid cluster and druid datasources session = db.session cluster = ( @@ -177,7 +177,7 @@ def get_access_requests(self, username, ds_type, ds_id): return ( db.session.query(DAR) .filter( - DAR.created_by == sm.find_user(username=username), + DAR.created_by == security_manager.find_user(username=username), DAR.datasource_type == ds_type, DAR.datasource_id == ds_id, ) @@ -188,20 +188,20 @@ def logout(self): self.client.get('/logout/', follow_redirects=True) def grant_public_access_to_table(self, table): - public_role = appbuilder.sm.find_role('Public') + public_role = security_manager.find_role('Public') perms = db.session.query(ab_models.PermissionView).all() for perm in perms: if (perm.permission.name == 'datasource_access' and perm.view_menu and table.perm in perm.view_menu.name): - appbuilder.sm.add_permission_role(public_role, perm) + security_manager.add_permission_role(public_role, perm) def revoke_public_access_to_table(self, table): - public_role = appbuilder.sm.find_role('Public') + public_role = security_manager.find_role('Public') perms = db.session.query(ab_models.PermissionView).all() for perm in perms: if (perm.permission.name == 'datasource_access' and perm.view_menu and table.perm in perm.view_menu.name): - appbuilder.sm.del_permission_role(public_role, perm) + security_manager.del_permission_role(public_role, perm) def run_sql(self, sql, client_id, user_name=None, raise_on_error=False): if user_name: @@ -241,7 +241,7 @@ def assert_can_all(view_menu): assert_can_write(view_menu) gamma_perm_set = set() - for perm in sm.find_role('Gamma').permissions: + for perm in security_manager.find_role('Gamma').permissions: gamma_perm_set.add((perm.permission.name, perm.view_menu.name)) # check read only perms diff --git a/tests/celery_tests.py b/tests/celery_tests.py index 172176ebb53b2..79b71e986e2aa 100644 --- a/tests/celery_tests.py +++ b/tests/celery_tests.py @@ -14,10 +14,9 @@ import pandas as pd from past.builtins import basestring -from superset import app, appbuilder, cli, dataframe, db +from superset import app, cli, dataframe, db, security_manager from superset.models.helpers import QueryStatus from superset.models.sql_lab import Query -from superset.security import sync_role_definitions from superset.sql_parse import SupersetQuery from .base_tests import SupersetTestCase @@ -98,17 +97,17 @@ def setUpClass(cls): except OSError as e: app.logger.warn(str(e)) - sync_role_definitions() + security_manager.sync_role_definitions() worker_command = BASE_DIR + '/bin/superset worker' subprocess.Popen( worker_command, shell=True, stdout=subprocess.PIPE) - admin = appbuilder.sm.find_user('admin') + admin = security_manager.find_user('admin') if not admin: - appbuilder.sm.add_user( + security_manager.add_user( 'admin', 'admin', ' user', 'admin@fab.org', - appbuilder.sm.find_role('Admin'), + security_manager.find_role('Admin'), password='general') cli.load_examples(load_test_data=True) diff --git a/tests/core_tests.py b/tests/core_tests.py index e4df8bf05c479..6f82af883859e 100644 --- a/tests/core_tests.py +++ b/tests/core_tests.py @@ -22,7 +22,7 @@ from six import text_type import sqlalchemy as sqla -from superset import appbuilder, dataframe, db, jinja_context, sm, sql_lab, utils +from superset import dataframe, db, jinja_context, security_manager, sql_lab, utils from superset.connectors.sqla.models import SqlaTable from superset.models import core as models from superset.models.sql_lab import Query @@ -136,7 +136,7 @@ def test_slice_csv_endpoint(self): def test_admin_only_permissions(self): def assert_admin_permission_in(role_name, assert_func): - role = sm.find_role(role_name) + role = security_manager.find_role(role_name) permissions = [p.permission.name for p in role.permissions] assert_func('can_sync_druid_source', permissions) assert_func('can_approve', permissions) @@ -147,7 +147,7 @@ def assert_admin_permission_in(role_name, assert_func): def test_admin_only_menu_views(self): def assert_admin_view_menus_in(role_name, assert_func): - role = sm.find_role(role_name) + role = security_manager.find_role(role_name) view_menus = [p.view_menu.name for p in role.permissions] assert_func('ResetPasswordView', view_menus) assert_func('RoleModelView', view_menus) @@ -267,7 +267,7 @@ def test_add_slice(self): def test_get_user_slices(self): self.login(username='admin') - userid = appbuilder.sm.find_user('admin').id + userid = security_manager.find_user('admin').id url = '/sliceaddview/api/read?_flt_0_created_by={}'.format(userid) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) @@ -275,11 +275,11 @@ def test_get_user_slices(self): def test_slices_V2(self): # Add explore-v2-beta role to admin user # Test all slice urls as user with with explore-v2-beta role - sm.add_role('explore-v2-beta') + security_manager.add_role('explore-v2-beta') - appbuilder.sm.add_user( + security_manager.add_user( 'explore_beta', 'explore_beta', ' user', 'explore_beta@airbnb.com', - appbuilder.sm.find_role('explore-v2-beta'), + security_manager.find_role('explore-v2-beta'), password='general') self.login(username='explore_beta', password='general') @@ -651,8 +651,8 @@ def test_dashboard_with_created_by_can_be_accessed_by_public_users(self): dash = db.session.query(models.Dashboard).filter_by( slug='births').first() - dash.owners = [appbuilder.sm.find_user('admin')] - dash.created_by = appbuilder.sm.find_user('admin') + dash.owners = [security_manager.find_user('admin')] + dash.created_by = security_manager.find_user('admin') db.session.merge(dash) db.session.commit() @@ -674,7 +674,7 @@ def test_only_owners_can_save(self): self.assertRaises( Exception, self.test_save_dash, 'alpha') - alpha = appbuilder.sm.find_user('alpha') + alpha = security_manager.find_user('alpha') dash = ( db.session @@ -775,7 +775,7 @@ def test_user_profile(self, username='admin'): resp = self.get_json_resp(url) self.assertEqual(resp['count'], 1) - userid = appbuilder.sm.find_user('admin').id + userid = security_manager.find_user('admin').id resp = self.get_resp('/superset/profile/admin/') self.assertIn('"app"', resp) data = self.get_json_resp('/superset/recent_activity/{}/'.format(userid)) diff --git a/tests/druid_tests.py b/tests/druid_tests.py index e72eb25ee94de..7406dacbc31e8 100644 --- a/tests/druid_tests.py +++ b/tests/druid_tests.py @@ -11,7 +11,7 @@ from mock import Mock, patch -from superset import db, security, sm +from superset import db, security_manager from superset.connectors.druid.models import ( DruidCluster, DruidColumn, DruidDatasource, DruidMetric, ) @@ -278,13 +278,13 @@ def test_filter_druid_datasource(self): db.session.merge(no_gamma_ds) db.session.commit() - security.merge_perm(sm, 'datasource_access', gamma_ds.perm) - security.merge_perm(sm, 'datasource_access', no_gamma_ds.perm) + security_manager.merge_perm('datasource_access', gamma_ds.perm) + security_manager.merge_perm('datasource_access', no_gamma_ds.perm) - perm = sm.find_permission_view_menu( + perm = security_manager.find_permission_view_menu( 'datasource_access', gamma_ds.get_perm()) - sm.add_permission_role(sm.find_role('Gamma'), perm) - sm.get_session.commit() + security_manager.add_permission_role(security_manager.find_role('Gamma'), perm) + security_manager.get_session.commit() self.login(username='gamma') url = '/druiddatasourcemodelview/list/' @@ -331,10 +331,11 @@ def test_sync_druid_perm(self, PyDruid): db.session.commit() view_menu_name = cluster.datasources[0].get_perm() - view_menu = sm.find_view_menu(view_menu_name) - permission = sm.find_permission('datasource_access') + view_menu = security_manager.find_view_menu(view_menu_name) + permission = security_manager.find_permission('datasource_access') - pv = sm.get_session.query(sm.permissionview_model).filter_by( + pv = security_manager.get_session.query( + security_manager.permissionview_model).filter_by( permission=permission, view_menu=view_menu).first() assert pv is not None diff --git a/tests/security_tests.py b/tests/security_tests.py index e117394a366ce..58ec0c77ea1e2 100644 --- a/tests/security_tests.py +++ b/tests/security_tests.py @@ -4,13 +4,13 @@ from __future__ import print_function from __future__ import unicode_literals -from superset import app, security, sm +from superset import app, security_manager from .base_tests import SupersetTestCase def get_perm_tuples(role_name): perm_set = set() - for perm in sm.find_role(role_name).permissions: + for perm in security_manager.find_role(role_name).permissions: perm_set.add((perm.permission.name, perm.view_menu.name)) return perm_set @@ -103,46 +103,47 @@ def assert_can_admin(self, perm_set): self.assertIn(('can_approve', 'Superset'), perm_set) def test_is_admin_only(self): - self.assertFalse(security.is_admin_only( - sm.find_permission_view_menu('can_show', 'TableModelView'))) - self.assertFalse(security.is_admin_only( - sm.find_permission_view_menu( + self.assertFalse(security_manager.is_admin_only( + security_manager.find_permission_view_menu('can_show', 'TableModelView'))) + self.assertFalse(security_manager.is_admin_only( + security_manager.find_permission_view_menu( 'all_datasource_access', 'all_datasource_access'))) - self.assertTrue(security.is_admin_only( - sm.find_permission_view_menu('can_delete', 'DatabaseView'))) + self.assertTrue(security_manager.is_admin_only( + security_manager.find_permission_view_menu('can_delete', 'DatabaseView'))) if app.config.get('ENABLE_ACCESS_REQUEST'): - self.assertTrue(security.is_admin_only( - sm.find_permission_view_menu( + self.assertTrue(security_manager.is_admin_only( + security_manager.find_permission_view_menu( 'can_show', 'AccessRequestsModelView'))) - self.assertTrue(security.is_admin_only( - sm.find_permission_view_menu( + self.assertTrue(security_manager.is_admin_only( + security_manager.find_permission_view_menu( 'can_edit', 'UserDBModelView'))) - self.assertTrue(security.is_admin_only( - sm.find_permission_view_menu( + self.assertTrue(security_manager.is_admin_only( + security_manager.find_permission_view_menu( 'can_approve', 'Superset'))) - self.assertTrue(security.is_admin_only( - sm.find_permission_view_menu( + self.assertTrue(security_manager.is_admin_only( + security_manager.find_permission_view_menu( 'all_database_access', 'all_database_access'))) def test_is_alpha_only(self): - self.assertFalse(security.is_alpha_only( - sm.find_permission_view_menu('can_show', 'TableModelView'))) + self.assertFalse(security_manager.is_alpha_only( + security_manager.find_permission_view_menu('can_show', 'TableModelView'))) - self.assertTrue(security.is_alpha_only( - sm.find_permission_view_menu('muldelete', 'TableModelView'))) - self.assertTrue(security.is_alpha_only( - sm.find_permission_view_menu( + self.assertTrue(security_manager.is_alpha_only( + security_manager.find_permission_view_menu('muldelete', 'TableModelView'))) + self.assertTrue(security_manager.is_alpha_only( + security_manager.find_permission_view_menu( 'all_datasource_access', 'all_datasource_access'))) - self.assertTrue(security.is_alpha_only( - sm.find_permission_view_menu('can_edit', 'SqlMetricInlineView'))) - self.assertTrue(security.is_alpha_only( - sm.find_permission_view_menu( + 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( 'can_delete', 'DruidMetricInlineView'))) def test_is_gamma_pvm(self): - self.assertTrue(security.is_gamma_pvm( - sm.find_permission_view_menu('can_show', 'TableModelView'))) + self.assertTrue(security_manager.is_gamma_pvm( + security_manager.find_permission_view_menu('can_show', 'TableModelView'))) def test_gamma_permissions(self): self.assert_can_gamma(get_perm_tuples('Gamma')) diff --git a/tests/sqllab_tests.py b/tests/sqllab_tests.py index 977bbe0d35554..afab1403f0a20 100644 --- a/tests/sqllab_tests.py +++ b/tests/sqllab_tests.py @@ -11,7 +11,7 @@ from flask_appbuilder.security.sqla import models as ab_models -from superset import appbuilder, db, sm, utils +from superset import db, security_manager, utils from superset.models.sql_lab import Query from superset.sql_lab import convert_results_to_df from .base_tests import SupersetTestCase @@ -56,7 +56,7 @@ def test_sql_json(self): def test_sql_json_has_access(self): main_db = self.get_main_database(db.session) - sm.add_permission_view_menu('database_access', main_db.perm) + security_manager.add_permission_view_menu('database_access', main_db.perm) db.session.commit() main_db_permission_view = ( db.session.query(ab_models.PermissionView) @@ -66,17 +66,17 @@ def test_sql_json_has_access(self): .filter(ab_models.Permission.name == 'database_access') .first() ) - astronaut = sm.add_role('Astronaut') - sm.add_permission_role(astronaut, main_db_permission_view) + astronaut = security_manager.add_role('Astronaut') + security_manager.add_permission_role(astronaut, main_db_permission_view) # Astronaut role is Gamma + sqllab + main db permissions - for perm in sm.find_role('Gamma').permissions: - sm.add_permission_role(astronaut, perm) - for perm in sm.find_role('sql_lab').permissions: - sm.add_permission_role(astronaut, perm) + for perm in security_manager.find_role('Gamma').permissions: + security_manager.add_permission_role(astronaut, perm) + for perm in security_manager.find_role('sql_lab').permissions: + security_manager.add_permission_role(astronaut, perm) - gagarin = appbuilder.sm.find_user('gagarin') + gagarin = security_manager.find_user('gagarin') if not gagarin: - appbuilder.sm.add_user( + security_manager.add_user( 'gagarin', 'Iurii', 'Gagarin', 'gagarin@cosmos.ussr', astronaut, password='general') @@ -139,14 +139,14 @@ def test_search_query_on_user(self): self.login('admin') # Test search queries on user Id - user_id = appbuilder.sm.find_user('admin').id + user_id = security_manager.find_user('admin').id data = self.get_json_resp( '/superset/search_queries?user_id={}'.format(user_id)) self.assertEquals(2, len(data)) user_ids = {k['userId'] for k in data} self.assertEquals(set([user_id]), user_ids) - user_id = appbuilder.sm.find_user('gamma_sqllab').id + user_id = security_manager.find_user('gamma_sqllab').id resp = self.get_resp( '/superset/search_queries?user_id={}'.format(user_id)) data = json.loads(resp)