"""A set of constants and methods to manage permissions and security""" from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import logging from flask_appbuilder.security.sqla import models as ab_models from superset import conf, db, sm from superset.connectors.connector_registry import ConnectorRegistry from superset.models import core as models READ_ONLY_MODEL_VIEWS = { 'DatabaseAsync', 'DatabaseView', 'DruidClusterModelView', } GAMMA_READ_ONLY_MODEL_VIEWS = { 'SqlMetricInlineView', 'TableColumnInlineView', 'TableModelView', 'DruidColumnInlineView', 'DruidDatasourceModelView', 'DruidMetricInlineView', } | READ_ONLY_MODEL_VIEWS ADMIN_ONLY_VIEW_MENUS = { 'AccessRequestsModelView', 'Manage', 'SQL Lab', 'Queries', 'Refresh Druid Metadata', 'ResetPasswordView', 'RoleModelView', 'Security', 'UserDBModelView', } ADMIN_ONLY_PERMISSIONS = { 'all_database_access', 'can_sql_json', # TODO: move can_sql_json to sql_lab role 'can_override_role_permissions', 'can_sync_druid_source', 'can_override_role_permissions', 'can_approve', 'can_update_role', } READ_ONLY_PERMISSION = { 'can_show', 'can_list', } ALPHA_ONLY_PERMISSIONS = set([ 'muldelete', 'all_datasource_access', ]) OBJECT_SPEC_PERMISSIONS = set([ 'database_access', 'schema_access', 'datasource_access', 'metric_access', ]) 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.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 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()