2016-11-10 02:08:22 -05:00
|
|
|
"""Unit tests for Superset"""
|
2016-08-30 00:55:31 -04:00
|
|
|
from __future__ import absolute_import
|
|
|
|
from __future__ import division
|
|
|
|
from __future__ import print_function
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2016-10-19 12:17:08 -04:00
|
|
|
import json
|
2017-11-07 23:23:40 -05:00
|
|
|
import logging
|
2016-08-30 00:55:31 -04:00
|
|
|
import os
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
from flask_appbuilder.security.sqla import models as ab_models
|
|
|
|
|
2017-11-07 23:23:40 -05:00
|
|
|
from superset import app, appbuilder, cli, db, security, sm
|
|
|
|
from superset.connectors.druid.models import DruidCluster, DruidDatasource
|
|
|
|
from superset.connectors.sqla.models import SqlaTable
|
2017-03-10 12:11:51 -05:00
|
|
|
from superset.models import core as models
|
2016-11-17 14:58:33 -05:00
|
|
|
from superset.security import sync_role_definitions
|
2016-08-30 00:55:31 -04:00
|
|
|
|
2016-11-10 02:08:22 -05:00
|
|
|
os.environ['SUPERSET_CONFIG'] = 'tests.superset_test_config'
|
2016-08-30 00:55:31 -04:00
|
|
|
|
2017-11-14 00:06:51 -05:00
|
|
|
BASE_DIR = app.config.get('BASE_DIR')
|
2016-08-30 00:55:31 -04:00
|
|
|
|
|
|
|
|
2016-11-10 02:08:22 -05:00
|
|
|
class SupersetTestCase(unittest.TestCase):
|
2016-10-13 22:21:21 -04:00
|
|
|
requires_examples = False
|
|
|
|
examples_loaded = False
|
2016-08-30 00:55:31 -04:00
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
2016-10-02 21:03:19 -04:00
|
|
|
if (
|
2017-11-10 15:06:22 -05:00
|
|
|
self.requires_examples and
|
|
|
|
not os.environ.get('SOLO_TEST') and
|
|
|
|
not os.environ.get('examples_loaded')
|
2016-11-30 17:05:09 -05:00
|
|
|
):
|
2017-11-14 00:06:51 -05:00
|
|
|
logging.info('Loading examples')
|
2016-10-02 21:03:19 -04:00
|
|
|
cli.load_examples(load_test_data=True)
|
2017-11-14 00:06:51 -05:00
|
|
|
logging.info('Done loading examples')
|
2016-11-17 14:58:33 -05:00
|
|
|
sync_role_definitions()
|
2016-10-02 21:03:19 -04:00
|
|
|
os.environ['examples_loaded'] = '1'
|
2016-11-17 14:58:33 -05:00
|
|
|
else:
|
|
|
|
sync_role_definitions()
|
2016-11-10 02:08:22 -05:00
|
|
|
super(SupersetTestCase, self).__init__(*args, **kwargs)
|
2016-08-30 00:55:31 -04:00
|
|
|
self.client = app.test_client()
|
2016-09-22 12:53:14 -04:00
|
|
|
self.maxDiff = None
|
2016-11-17 14:58:33 -05:00
|
|
|
|
2017-11-14 00:06:51 -05:00
|
|
|
gamma_sqllab_role = sm.add_role('gamma_sqllab')
|
2016-11-17 14:58:33 -05:00
|
|
|
for perm in sm.find_role('Gamma').permissions:
|
2016-12-15 08:38:34 -05:00
|
|
|
sm.add_permission_role(gamma_sqllab_role, perm)
|
2016-12-06 02:18:16 -05:00
|
|
|
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(
|
|
|
|
view_menu_name=db_perm, permission_name='database_access')
|
2016-12-15 08:38:34 -05:00
|
|
|
gamma_sqllab_role.permissions.append(db_pvm)
|
2016-11-17 14:58:33 -05:00
|
|
|
for perm in sm.find_role('sql_lab').permissions:
|
2016-12-15 08:38:34 -05:00
|
|
|
sm.add_permission_role(gamma_sqllab_role, perm)
|
2016-08-30 00:55:31 -04:00
|
|
|
|
|
|
|
admin = appbuilder.sm.find_user('admin')
|
|
|
|
if not admin:
|
|
|
|
appbuilder.sm.add_user(
|
|
|
|
'admin', 'admin', ' user', 'admin@fab.org',
|
|
|
|
appbuilder.sm.find_role('Admin'),
|
|
|
|
password='general')
|
|
|
|
|
|
|
|
gamma = appbuilder.sm.find_user('gamma')
|
|
|
|
if not gamma:
|
|
|
|
appbuilder.sm.add_user(
|
|
|
|
'gamma', 'gamma', 'user', 'gamma@fab.org',
|
|
|
|
appbuilder.sm.find_role('Gamma'),
|
|
|
|
password='general')
|
|
|
|
|
2017-01-24 21:11:51 -05:00
|
|
|
gamma2 = appbuilder.sm.find_user('gamma2')
|
|
|
|
if not gamma2:
|
|
|
|
appbuilder.sm.add_user(
|
|
|
|
'gamma2', 'gamma2', 'user', 'gamma2@fab.org',
|
|
|
|
appbuilder.sm.find_role('Gamma'),
|
|
|
|
password='general')
|
|
|
|
|
2016-12-15 08:38:34 -05:00
|
|
|
gamma_sqllab_user = appbuilder.sm.find_user('gamma_sqllab')
|
|
|
|
if not gamma_sqllab_user:
|
|
|
|
appbuilder.sm.add_user(
|
2016-11-17 14:58:33 -05:00
|
|
|
'gamma_sqllab', 'gamma_sqllab', 'user', 'gamma_sqllab@fab.org',
|
2016-12-15 08:38:34 -05:00
|
|
|
gamma_sqllab_role, password='general')
|
2016-11-17 14:58:33 -05:00
|
|
|
|
2016-08-30 00:55:31 -04:00
|
|
|
alpha = appbuilder.sm.find_user('alpha')
|
|
|
|
if not alpha:
|
|
|
|
appbuilder.sm.add_user(
|
|
|
|
'alpha', 'alpha', 'user', 'alpha@fab.org',
|
|
|
|
appbuilder.sm.find_role('Alpha'),
|
|
|
|
password='general')
|
2016-12-06 02:18:16 -05:00
|
|
|
sm.get_session.commit()
|
2016-09-22 12:53:14 -04:00
|
|
|
# create druid cluster and druid datasources
|
|
|
|
session = db.session
|
2017-03-10 12:11:51 -05:00
|
|
|
cluster = (
|
|
|
|
session.query(DruidCluster)
|
2017-11-14 00:06:51 -05:00
|
|
|
.filter_by(cluster_name='druid_test')
|
2017-03-10 12:11:51 -05:00
|
|
|
.first()
|
|
|
|
)
|
2016-09-22 12:53:14 -04:00
|
|
|
if not cluster:
|
2017-11-14 00:06:51 -05:00
|
|
|
cluster = DruidCluster(cluster_name='druid_test')
|
2016-09-22 12:53:14 -04:00
|
|
|
session.add(cluster)
|
|
|
|
session.commit()
|
|
|
|
|
2017-03-10 12:11:51 -05:00
|
|
|
druid_datasource1 = DruidDatasource(
|
2016-09-22 12:53:14 -04:00
|
|
|
datasource_name='druid_ds_1',
|
2017-11-08 00:32:45 -05:00
|
|
|
cluster_name='druid_test',
|
2016-09-22 12:53:14 -04:00
|
|
|
)
|
|
|
|
session.add(druid_datasource1)
|
2017-03-10 12:11:51 -05:00
|
|
|
druid_datasource2 = DruidDatasource(
|
2016-09-22 12:53:14 -04:00
|
|
|
datasource_name='druid_ds_2',
|
2017-11-08 00:32:45 -05:00
|
|
|
cluster_name='druid_test',
|
2016-09-22 12:53:14 -04:00
|
|
|
)
|
|
|
|
session.add(druid_datasource2)
|
|
|
|
session.commit()
|
|
|
|
|
2017-01-13 22:30:17 -05:00
|
|
|
def get_table(self, table_id):
|
2017-03-10 12:11:51 -05:00
|
|
|
return db.session.query(SqlaTable).filter_by(
|
2017-01-13 22:30:17 -05:00
|
|
|
id=table_id).first()
|
|
|
|
|
2016-10-07 19:24:39 -04:00
|
|
|
def get_or_create(self, cls, criteria, session):
|
|
|
|
obj = session.query(cls).filter_by(**criteria).first()
|
|
|
|
if not obj:
|
|
|
|
obj = cls(**criteria)
|
|
|
|
return obj
|
|
|
|
|
2016-08-30 00:55:31 -04:00
|
|
|
def login(self, username='admin', password='general'):
|
2016-11-17 14:58:33 -05:00
|
|
|
resp = self.get_resp(
|
2016-08-30 00:55:31 -04:00
|
|
|
'/login/',
|
2016-11-17 14:58:33 -05:00
|
|
|
data=dict(username=username, password=password))
|
|
|
|
self.assertIn('Welcome', resp)
|
2016-08-30 00:55:31 -04:00
|
|
|
|
2016-10-07 19:24:39 -04:00
|
|
|
def get_slice(self, slice_name, session):
|
|
|
|
slc = (
|
|
|
|
session.query(models.Slice)
|
2017-11-10 15:06:22 -05:00
|
|
|
.filter_by(slice_name=slice_name)
|
|
|
|
.one()
|
2016-10-07 19:24:39 -04:00
|
|
|
)
|
|
|
|
session.expunge_all()
|
|
|
|
return slc
|
|
|
|
|
2016-10-20 18:30:09 -04:00
|
|
|
def get_table_by_name(self, name):
|
2017-03-10 12:11:51 -05:00
|
|
|
return db.session.query(SqlaTable).filter_by(
|
2016-10-20 18:30:09 -04:00
|
|
|
table_name=name).first()
|
|
|
|
|
|
|
|
def get_druid_ds_by_name(self, name):
|
2017-03-10 12:11:51 -05:00
|
|
|
return db.session.query(DruidDatasource).filter_by(
|
2016-10-20 18:30:09 -04:00
|
|
|
datasource_name=name).first()
|
|
|
|
|
2016-12-01 18:21:18 -05:00
|
|
|
def get_resp(
|
|
|
|
self, url, data=None, follow_redirects=True, raise_on_error=True):
|
2016-10-02 21:03:19 -04:00
|
|
|
"""Shortcut to get the parsed results while following redirects"""
|
2016-11-17 14:58:33 -05:00
|
|
|
if data:
|
|
|
|
resp = self.client.post(
|
|
|
|
url, data=data, follow_redirects=follow_redirects)
|
|
|
|
else:
|
|
|
|
resp = self.client.get(url, follow_redirects=follow_redirects)
|
2016-12-01 18:21:18 -05:00
|
|
|
if raise_on_error and resp.status_code > 400:
|
|
|
|
raise Exception(
|
2017-11-14 00:06:51 -05:00
|
|
|
'http request failed with code {}'.format(resp.status_code))
|
2016-12-01 18:21:18 -05:00
|
|
|
return resp.data.decode('utf-8')
|
2016-11-17 14:58:33 -05:00
|
|
|
|
2016-12-01 18:21:18 -05:00
|
|
|
def get_json_resp(
|
|
|
|
self, url, data=None, follow_redirects=True, raise_on_error=True):
|
2016-10-19 12:17:08 -04:00
|
|
|
"""Shortcut to get the parsed results while following redirects"""
|
2016-12-01 18:21:18 -05:00
|
|
|
resp = self.get_resp(url, data, follow_redirects, raise_on_error)
|
2016-10-19 12:17:08 -04:00
|
|
|
return json.loads(resp)
|
|
|
|
|
|
|
|
def get_main_database(self, session):
|
|
|
|
return (
|
|
|
|
db.session.query(models.Database)
|
2017-11-10 15:06:22 -05:00
|
|
|
.filter_by(database_name='main')
|
|
|
|
.first()
|
2016-10-19 12:17:08 -04:00
|
|
|
)
|
|
|
|
|
2016-09-22 12:53:14 -04:00
|
|
|
def get_access_requests(self, username, ds_type, ds_id):
|
2016-11-30 17:05:09 -05:00
|
|
|
DAR = models.DatasourceAccessRequest
|
|
|
|
return (
|
|
|
|
db.session.query(DAR)
|
2017-11-10 15:06:22 -05:00
|
|
|
.filter(
|
2016-11-30 17:05:09 -05:00
|
|
|
DAR.created_by == sm.find_user(username=username),
|
|
|
|
DAR.datasource_type == ds_type,
|
|
|
|
DAR.datasource_id == ds_id,
|
2017-11-10 15:06:22 -05:00
|
|
|
)
|
|
|
|
.first()
|
2016-11-30 17:05:09 -05:00
|
|
|
)
|
2016-09-22 12:53:14 -04:00
|
|
|
|
2016-08-30 00:55:31 -04:00
|
|
|
def logout(self):
|
|
|
|
self.client.get('/logout/', follow_redirects=True)
|
|
|
|
|
2016-11-17 14:58:33 -05:00
|
|
|
def grant_public_access_to_table(self, table):
|
2016-08-30 00:55:31 -04:00
|
|
|
public_role = appbuilder.sm.find_role('Public')
|
|
|
|
perms = db.session.query(ab_models.PermissionView).all()
|
|
|
|
for perm in perms:
|
|
|
|
if (perm.permission.name == 'datasource_access' and
|
2016-11-17 14:58:33 -05:00
|
|
|
perm.view_menu and table.perm in perm.view_menu.name):
|
2016-08-30 00:55:31 -04:00
|
|
|
appbuilder.sm.add_permission_role(public_role, perm)
|
|
|
|
|
2016-11-17 14:58:33 -05:00
|
|
|
def revoke_public_access_to_table(self, table):
|
2016-08-30 00:55:31 -04:00
|
|
|
public_role = appbuilder.sm.find_role('Public')
|
|
|
|
perms = db.session.query(ab_models.PermissionView).all()
|
|
|
|
for perm in perms:
|
|
|
|
if (perm.permission.name == 'datasource_access' and
|
2016-11-17 14:58:33 -05:00
|
|
|
perm.view_menu and table.perm in perm.view_menu.name):
|
2016-08-30 00:55:31 -04:00
|
|
|
appbuilder.sm.del_permission_role(public_role, perm)
|
2016-11-01 23:48:31 -04:00
|
|
|
|
2016-12-01 22:53:23 -05:00
|
|
|
def run_sql(self, sql, client_id, user_name=None, raise_on_error=False):
|
2016-11-17 14:58:33 -05:00
|
|
|
if user_name:
|
|
|
|
self.logout()
|
|
|
|
self.login(username=(user_name if user_name else 'admin'))
|
2016-11-01 23:48:31 -04:00
|
|
|
dbid = self.get_main_database(db.session).id
|
2016-11-17 14:58:33 -05:00
|
|
|
resp = self.get_json_resp(
|
2016-11-10 02:08:22 -05:00
|
|
|
'/superset/sql_json/',
|
2016-12-01 18:21:18 -05:00
|
|
|
raise_on_error=False,
|
2016-11-01 23:48:31 -04:00
|
|
|
data=dict(database_id=dbid, sql=sql, select_as_create_as=False,
|
|
|
|
client_id=client_id),
|
|
|
|
)
|
2016-12-01 22:53:23 -05:00
|
|
|
if raise_on_error and 'error' in resp:
|
2017-11-14 00:06:51 -05:00
|
|
|
raise Exception('run_sql failed')
|
2016-11-17 14:58:33 -05:00
|
|
|
return resp
|
2016-11-30 17:05:09 -05:00
|
|
|
|
|
|
|
def test_gamma_permissions(self):
|
|
|
|
def assert_can_read(view_menu):
|
|
|
|
self.assertIn(('can_show', view_menu), gamma_perm_set)
|
|
|
|
self.assertIn(('can_list', view_menu), gamma_perm_set)
|
|
|
|
|
|
|
|
def assert_can_write(view_menu):
|
|
|
|
self.assertIn(('can_add', view_menu), gamma_perm_set)
|
|
|
|
self.assertIn(('can_download', view_menu), gamma_perm_set)
|
|
|
|
self.assertIn(('can_delete', view_menu), gamma_perm_set)
|
|
|
|
self.assertIn(('can_edit', view_menu), gamma_perm_set)
|
|
|
|
|
|
|
|
def assert_cannot_write(view_menu):
|
|
|
|
self.assertNotIn(('can_add', view_menu), gamma_perm_set)
|
|
|
|
self.assertNotIn(('can_download', view_menu), gamma_perm_set)
|
|
|
|
self.assertNotIn(('can_delete', view_menu), gamma_perm_set)
|
|
|
|
self.assertNotIn(('can_edit', view_menu), gamma_perm_set)
|
|
|
|
self.assertNotIn(('can_save', view_menu), gamma_perm_set)
|
|
|
|
|
|
|
|
def assert_can_all(view_menu):
|
|
|
|
assert_can_read(view_menu)
|
|
|
|
assert_can_write(view_menu)
|
|
|
|
|
|
|
|
gamma_perm_set = set()
|
|
|
|
for perm in sm.find_role('Gamma').permissions:
|
|
|
|
gamma_perm_set.add((perm.permission.name, perm.view_menu.name))
|
|
|
|
|
|
|
|
# check read only perms
|
|
|
|
assert_can_read('TableModelView')
|
|
|
|
assert_cannot_write('DruidColumnInlineView')
|
|
|
|
|
|
|
|
# make sure that user can create slices and dashboards
|
|
|
|
assert_can_all('SliceModelView')
|
|
|
|
assert_can_all('DashboardModelView')
|
|
|
|
|
|
|
|
self.assertIn(('can_add_slices', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_copy_dash', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_activity_per_day', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_created_dashboards', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_created_slices', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_csv', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_dashboard', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_explore', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_explore_json', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_fave_dashboards', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_fave_slices', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_save_dash', 'Superset'), gamma_perm_set)
|
|
|
|
self.assertIn(('can_slice', 'Superset'), gamma_perm_set)
|