Skip to content

Commit

Permalink
Updating permission when refreshing druid datasource (#2655)
Browse files Browse the repository at this point in the history
* Updating permission when refreshing druid datasource

* Adding test

* Fix style

* Deletion view_menu after db, table, cluster, ds deletion

* Update table model

* Linting

* Override _delete instead of post_delete

* fix

* lint

* fix multi delete

* fix

* Refactoring

* Amending
  • Loading branch information
ShengyaoQian authored and mistercrunch committed May 22, 2017
1 parent ce506bd commit b0e2904
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 5 deletions.
3 changes: 3 additions & 0 deletions superset/connectors/druid/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ def refresh_datasources(self, datasource_name=None, merge_flag=False):
def perm(self):
return "[{obj.cluster_name}].(id:{obj.id})".format(obj=self)

def get_perm(self):
return self.perm

@property
def name(self):
return self.verbose_name if self.verbose_name else self.cluster_name
Expand Down
7 changes: 6 additions & 1 deletion superset/connectors/druid/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import sqlalchemy as sqla

from flask import Markup, flash, redirect
from flask import Markup, flash, redirect, abort
from flask_appbuilder import CompactCRUDMixin, expose
from flask_appbuilder.models.sqla.interface import SQLAInterface

Expand Down Expand Up @@ -136,6 +136,8 @@ def pre_add(self, cluster):
def pre_update(self, cluster):
self.pre_add(cluster)

def _delete(self, pk):
DeleteMixin._delete(self, pk)

appbuilder.add_view(
DruidClusterModelView,
Expand Down Expand Up @@ -231,6 +233,9 @@ def post_add(self, datasource):
def post_update(self, datasource):
self.post_add(datasource)

def _delete(self, pk):
DeleteMixin._delete(self, pk)

appbuilder.add_view(
DruidDatasourceModelView,
"Druid Datasources",
Expand Down
5 changes: 4 additions & 1 deletion superset/connectors/sqla/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from past.builtins import basestring

from flask import Markup, flash, redirect
from flask import Markup, flash, redirect, abort
from flask_appbuilder import CompactCRUDMixin, expose
from flask_appbuilder.models.sqla.interface import SQLAInterface
import sqlalchemy as sa
Expand Down Expand Up @@ -242,6 +242,9 @@ def post_add(self, table, flash_message=True):
def post_update(self, table):
self.post_add(table, flash_message=False)

def _delete(self, pk):
DeleteMixin._delete(self, pk)

@expose('/edit/<pk>', methods=['GET', 'POST'])
@has_access
def edit(self, pk):
Expand Down
41 changes: 41 additions & 0 deletions superset/models/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from flask_appbuilder.models.mixins import AuditMixin
from flask_appbuilder.models.decorators import renders
from superset.utils import QueryStatus
from superset import sm


class ImportMixin(object):
Expand Down Expand Up @@ -117,11 +118,51 @@ def __init__( # noqa
self.error_message = error_message


def merge_perm(sm, permission_name, view_menu_name, connection):

permission = sm.find_permission(permission_name)
view_menu = sm.find_view_menu(view_menu_name)
pv = None

if not permission:
permission_table = sm.permission_model.__table__
connection.execute(
permission_table.insert()
.values(name=permission_name)
)
if not view_menu:
view_menu_table = sm.viewmenu_model.__table__
connection.execute(
view_menu_table.insert()
.values(name=view_menu_name)
)

permission = sm.find_permission(permission_name)
view_menu = sm.find_view_menu(view_menu_name)

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 and view_menu:
permission_view_table = sm.permissionview_model.__table__
connection.execute(
permission_view_table.insert()
.values(
permission_id=permission.id,
view_menu_id=view_menu.id
)
)


def set_perm(mapper, connection, target): # noqa

if target.perm != target.get_perm():
link_table = target.__table__
connection.execute(
link_table.update()
.where(link_table.c.id == target.id)
.values(perm=target.get_perm())
)

# add to view menu if not already exists
merge_perm(sm, 'datasource_access', target.get_perm(), connection)
57 changes: 55 additions & 2 deletions superset/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import traceback

from flask import g, redirect, Response
from flask import g, redirect, Response, flash, abort
from flask_babel import gettext as __

from flask_appbuilder import BaseView
Expand Down Expand Up @@ -207,6 +207,51 @@ def validate_json(form, field): # noqa


class DeleteMixin(object):
def _delete(self, pk):
"""
Delete function logic, override to implement diferent logic
deletes the record with primary_key = pk
:param pk:
record primary key to delete
"""
item = self.datamodel.get(pk, self._base_filters)
if not item:
abort(404)
try:
self.pre_delete(item)
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=view_menu).all()

schema_view_menu = None
if hasattr(item, 'schema_perm'):
schema_view_menu = sm.find_view_menu(item.schema_perm)

pvs.extend(sm.get_session.query(
sm.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)

if view_menu:
sm.get_session.delete(view_menu)

if schema_view_menu:
sm.get_session.delete(schema_view_menu)

sm.get_session.commit()

flash(*self.datamodel.message)
self.update_redirect()

@action(
"muldelete",
__("Delete"),
Expand All @@ -215,7 +260,15 @@ class DeleteMixin(object):
single=False
)
def muldelete(self, items):
self.datamodel.delete_all(items)
if not items:
abort(404)
for item in items:
try:
self.pre_delete(item)
except Exception as e:
flash(str(e), "danger")
else:
self._delete(item.id)
self.update_redirect()
return redirect(self.get_redirect())

Expand Down
4 changes: 3 additions & 1 deletion superset/views/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import sqlalchemy as sqla

from flask import (
g, request, redirect, flash, Response, render_template, Markup)
g, request, redirect, flash, Response, render_template, Markup, abort)
from flask_appbuilder import expose
from flask_appbuilder.actions import action
from flask_appbuilder.models.sqla.interface import SQLAInterface
Expand Down Expand Up @@ -252,6 +252,8 @@ def pre_add(self, db):
def pre_update(self, db):
self.pre_add(db)

def _delete(self, pk):
DeleteMixin._delete(self, pk)

appbuilder.add_link(
'Import Dashboards',
Expand Down
43 changes: 43 additions & 0 deletions tests/druid_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,49 @@ def test_filter_druid_datasource(self):
self.assertIn('datasource_for_gamma', resp)
self.assertNotIn('datasource_not_for_gamma', resp)

@patch('superset.connectors.druid.models.PyDruid')
def test_sync_druid_perm(self, PyDruid):
self.login(username='admin')
instance = PyDruid.return_value
instance.time_boundary.return_value = [
{'result': {'maxTime': '2016-01-01'}}]
instance.segment_metadata.return_value = SEGMENT_METADATA

cluster = (
db.session
.query(DruidCluster)
.filter_by(cluster_name='test_cluster')
.first()
)
if cluster:
db.session.delete(cluster)
db.session.commit()

cluster = DruidCluster(
cluster_name='test_cluster',
coordinator_host='localhost',
coordinator_port=7979,
broker_host='localhost',
broker_port=7980,
metadata_last_refreshed=datetime.now())

db.session.add(cluster)
cluster.get_datasources = Mock(return_value=['test_datasource'])
cluster.get_druid_version = Mock(return_value='0.9.1')

cluster.refresh_datasources()
datasource_id = cluster.datasources[0].id
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")

pv = sm.get_session.query(sm.permissionview_model).filter_by(
permission=permission, view_menu=view_menu).first()
assert pv is not None



if __name__ == '__main__':
unittest.main()
2 changes: 2 additions & 0 deletions tests/sqllab_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ def test_sql_json_has_access(self):
main_db_permission_view = (
db.session.query(ab_models.PermissionView)
.join(ab_models.ViewMenu)
.join(ab_models.Permission)
.filter(ab_models.ViewMenu.name == '[main].(id:1)')
.filter(ab_models.Permission.name == 'database_access')
.first()
)
astronaut = sm.add_role("Astronaut")
Expand Down

0 comments on commit b0e2904

Please sign in to comment.