mirror of
https://github.com/valitydev/redash.git
synced 2024-11-07 01:25:16 +00:00
Allow collaborators to create, delete and edit visualizations.
This commit is contained in:
parent
5b54a777d9
commit
47fc6612bf
@ -1,9 +1,12 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import request
|
from flask import request
|
||||||
|
|
||||||
from redash import models
|
from redash import models
|
||||||
from redash.permissions import require_permission, require_admin_or_owner
|
|
||||||
from redash.handlers.base import BaseResource, get_object_or_404
|
from redash.handlers.base import BaseResource, get_object_or_404
|
||||||
|
from redash.permissions import (require_admin_or_owner,
|
||||||
|
require_object_modify_permission,
|
||||||
|
require_permission)
|
||||||
|
|
||||||
|
|
||||||
class VisualizationListResource(BaseResource):
|
class VisualizationListResource(BaseResource):
|
||||||
@ -12,7 +15,7 @@ class VisualizationListResource(BaseResource):
|
|||||||
kwargs = request.get_json(force=True)
|
kwargs = request.get_json(force=True)
|
||||||
|
|
||||||
query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)
|
query = get_object_or_404(models.Query.get_by_id_and_org, kwargs.pop('query_id'), self.current_org)
|
||||||
require_admin_or_owner(query.user_id)
|
require_object_modify_permission(query, self.current_user)
|
||||||
|
|
||||||
kwargs['options'] = json.dumps(kwargs['options'])
|
kwargs['options'] = json.dumps(kwargs['options'])
|
||||||
kwargs['query_rel'] = query
|
kwargs['query_rel'] = query
|
||||||
@ -28,7 +31,7 @@ class VisualizationResource(BaseResource):
|
|||||||
@require_permission('edit_query')
|
@require_permission('edit_query')
|
||||||
def post(self, visualization_id):
|
def post(self, visualization_id):
|
||||||
vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
|
vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
|
||||||
require_admin_or_owner(vis.query_rel.user_id)
|
require_object_modify_permission(vis.query_rel, self.current_user)
|
||||||
|
|
||||||
kwargs = request.get_json(force=True)
|
kwargs = request.get_json(force=True)
|
||||||
if 'options' in kwargs:
|
if 'options' in kwargs:
|
||||||
@ -45,6 +48,6 @@ class VisualizationResource(BaseResource):
|
|||||||
@require_permission('edit_query')
|
@require_permission('edit_query')
|
||||||
def delete(self, visualization_id):
|
def delete(self, visualization_id):
|
||||||
vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
|
vis = get_object_or_404(models.Visualization.get_by_id_and_org, visualization_id, self.current_org)
|
||||||
require_admin_or_owner(vis.query_rel.user_id)
|
require_object_modify_permission(vis.query_rel, self.current_user)
|
||||||
models.db.session.delete(vis)
|
models.db.session.delete(vis)
|
||||||
models.db.session.commit()
|
models.db.session.commit()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
import functools
|
||||||
|
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
from flask_restful import abort
|
from flask_restful import abort
|
||||||
import functools
|
|
||||||
from funcy import flatten
|
from funcy import flatten
|
||||||
|
|
||||||
view_only = True
|
view_only = True
|
||||||
|
130
tests/handlers/test_visualizations.py
Normal file
130
tests/handlers/test_visualizations.py
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
from tests import BaseTestCase
|
||||||
|
|
||||||
|
from redash import models
|
||||||
|
|
||||||
|
|
||||||
|
class VisualizationResourceTest(BaseTestCase):
|
||||||
|
def test_create_visualization(self):
|
||||||
|
query = self.factory.create_query()
|
||||||
|
models.db.session.commit()
|
||||||
|
data = {
|
||||||
|
'query_id': query.id,
|
||||||
|
'name': 'Chart',
|
||||||
|
'description': '',
|
||||||
|
'options': {},
|
||||||
|
'type': 'CHART'
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = self.make_request('post', '/api/visualizations', data=data)
|
||||||
|
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
data.pop('query_id')
|
||||||
|
self.assertDictContainsSubset(data, rv.json)
|
||||||
|
|
||||||
|
def test_delete_visualization(self):
|
||||||
|
visualization = self.factory.create_visualization()
|
||||||
|
models.db.session.commit()
|
||||||
|
rv = self.make_request('delete', '/api/visualizations/{}'.format(visualization.id))
|
||||||
|
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
self.assertEquals(models.db.session.query(models.Visualization).count(), 0)
|
||||||
|
|
||||||
|
def test_update_visualization(self):
|
||||||
|
visualization = self.factory.create_visualization()
|
||||||
|
models.db.session.commit()
|
||||||
|
rv = self.make_request('post', '/api/visualizations/{0}'.format(visualization.id), data={'name': 'After Update'})
|
||||||
|
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
self.assertEquals(rv.json['name'], 'After Update')
|
||||||
|
|
||||||
|
def test_only_owner_collaborator_or_admin_can_create_visualization(self):
|
||||||
|
query = self.factory.create_query()
|
||||||
|
other_user = self.factory.create_user()
|
||||||
|
admin = self.factory.create_admin()
|
||||||
|
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
||||||
|
models.db.session.commit()
|
||||||
|
models.db.session.refresh(admin)
|
||||||
|
models.db.session.refresh(other_user)
|
||||||
|
models.db.session.refresh(admin_from_diff_org)
|
||||||
|
data = {
|
||||||
|
'query_id': query.id,
|
||||||
|
'name': 'Chart',
|
||||||
|
'description': '',
|
||||||
|
'options': {},
|
||||||
|
'type': 'CHART'
|
||||||
|
}
|
||||||
|
|
||||||
|
rv = self.make_request('post', '/api/visualizations', data=data, user=admin)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
rv = self.make_request('post', '/api/visualizations', data=data, user=other_user)
|
||||||
|
self.assertEquals(rv.status_code, 403)
|
||||||
|
|
||||||
|
self.make_request('post', '/api/queries/{}/acl'.format(query.id), data={'access_type': 'modify', 'user_id': other_user.id})
|
||||||
|
rv = self.make_request('post', '/api/visualizations', data=data, user=other_user)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
rv = self.make_request('post', '/api/visualizations', data=data, user=admin_from_diff_org)
|
||||||
|
self.assertEquals(rv.status_code, 404)
|
||||||
|
|
||||||
|
def test_only_owner_collaborator_or_admin_can_edit_visualization(self):
|
||||||
|
vis = self.factory.create_visualization()
|
||||||
|
models.db.session.flush()
|
||||||
|
path = '/api/visualizations/{}'.format(vis.id)
|
||||||
|
data = {'name': 'After Update'}
|
||||||
|
|
||||||
|
other_user = self.factory.create_user()
|
||||||
|
admin = self.factory.create_admin()
|
||||||
|
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
||||||
|
models.db.session.commit()
|
||||||
|
models.db.session.refresh(admin)
|
||||||
|
models.db.session.refresh(other_user)
|
||||||
|
models.db.session.refresh(admin_from_diff_org)
|
||||||
|
|
||||||
|
rv = self.make_request('post', path, user=admin, data=data)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
rv = self.make_request('post', path, user=other_user, data=data)
|
||||||
|
self.assertEquals(rv.status_code, 403)
|
||||||
|
|
||||||
|
self.make_request('post', '/api/queries/{}/acl'.format(vis.query_id), data={'access_type': 'modify', 'user_id': other_user.id})
|
||||||
|
rv = self.make_request('post', path, user=other_user, data=data)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
rv = self.make_request('post', path, user=admin_from_diff_org, data=data)
|
||||||
|
self.assertEquals(rv.status_code, 404)
|
||||||
|
|
||||||
|
def test_only_owner_collaborator_or_admin_can_delete_visualization(self):
|
||||||
|
vis = self.factory.create_visualization()
|
||||||
|
models.db.session.flush()
|
||||||
|
path = '/api/visualizations/{}'.format(vis.id)
|
||||||
|
|
||||||
|
other_user = self.factory.create_user()
|
||||||
|
admin = self.factory.create_admin()
|
||||||
|
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
||||||
|
|
||||||
|
models.db.session.commit()
|
||||||
|
models.db.session.refresh(admin)
|
||||||
|
models.db.session.refresh(other_user)
|
||||||
|
models.db.session.refresh(admin_from_diff_org)
|
||||||
|
rv = self.make_request('delete', path, user=admin)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
vis = self.factory.create_visualization()
|
||||||
|
models.db.session.commit()
|
||||||
|
path = '/api/visualizations/{}'.format(vis.id)
|
||||||
|
|
||||||
|
rv = self.make_request('delete', path, user=other_user)
|
||||||
|
self.assertEquals(rv.status_code, 403)
|
||||||
|
|
||||||
|
self.make_request('post', '/api/queries/{}/acl'.format(vis.query_id), data={'access_type': 'modify', 'user_id': other_user.id})
|
||||||
|
|
||||||
|
rv = self.make_request('delete', path, user=other_user)
|
||||||
|
self.assertEquals(rv.status_code, 200)
|
||||||
|
|
||||||
|
vis = self.factory.create_visualization()
|
||||||
|
models.db.session.commit()
|
||||||
|
path = '/api/visualizations/{}'.format(vis.id)
|
||||||
|
|
||||||
|
rv = self.make_request('delete', path, user=admin_from_diff_org)
|
||||||
|
self.assertEquals(rv.status_code, 404)
|
@ -1,11 +1,10 @@
|
|||||||
from funcy import project
|
|
||||||
|
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
from funcy import project
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
from tests import BaseTestCase, authenticated_user
|
||||||
|
|
||||||
from redash import models, settings
|
from redash import models, settings
|
||||||
from tests import BaseTestCase
|
|
||||||
from tests import authenticated_user
|
|
||||||
|
|
||||||
|
|
||||||
class AuthenticationTestMixin(object):
|
class AuthenticationTestMixin(object):
|
||||||
@ -65,121 +64,6 @@ class StatusTest(BaseTestCase):
|
|||||||
self.assertEqual(rv.status_code, 302)
|
self.assertEqual(rv.status_code, 302)
|
||||||
|
|
||||||
|
|
||||||
class VisualizationResourceTest(BaseTestCase):
|
|
||||||
def test_create_visualization(self):
|
|
||||||
query = self.factory.create_query()
|
|
||||||
models.db.session.commit()
|
|
||||||
data = {
|
|
||||||
'query_id': query.id,
|
|
||||||
'name': 'Chart',
|
|
||||||
'description': '',
|
|
||||||
'options': {},
|
|
||||||
'type': 'CHART'
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = self.make_request('post', '/api/visualizations', data=data)
|
|
||||||
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
data.pop('query_id')
|
|
||||||
self.assertDictContainsSubset(data, rv.json)
|
|
||||||
|
|
||||||
def test_delete_visualization(self):
|
|
||||||
visualization = self.factory.create_visualization()
|
|
||||||
models.db.session.commit()
|
|
||||||
rv = self.make_request('delete', '/api/visualizations/{}'.format(visualization.id))
|
|
||||||
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
self.assertEquals(models.db.session.query(models.Visualization).count(), 0)
|
|
||||||
|
|
||||||
def test_update_visualization(self):
|
|
||||||
visualization = self.factory.create_visualization()
|
|
||||||
models.db.session.commit()
|
|
||||||
rv = self.make_request('post', '/api/visualizations/{0}'.format(visualization.id), data={'name': 'After Update'})
|
|
||||||
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
self.assertEquals(rv.json['name'], 'After Update')
|
|
||||||
|
|
||||||
def test_only_owner_or_admin_can_create_visualization(self):
|
|
||||||
query = self.factory.create_query()
|
|
||||||
other_user = self.factory.create_user()
|
|
||||||
admin = self.factory.create_admin()
|
|
||||||
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
|
||||||
models.db.session.commit()
|
|
||||||
models.db.session.refresh(admin)
|
|
||||||
models.db.session.refresh(other_user)
|
|
||||||
models.db.session.refresh(admin_from_diff_org)
|
|
||||||
data = {
|
|
||||||
'query_id': query.id,
|
|
||||||
'name': 'Chart',
|
|
||||||
'description': '',
|
|
||||||
'options': {},
|
|
||||||
'type': 'CHART'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
rv = self.make_request('post', '/api/visualizations', data=data, user=admin)
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
|
|
||||||
rv = self.make_request('post', '/api/visualizations', data=data, user=other_user)
|
|
||||||
self.assertEquals(rv.status_code, 403)
|
|
||||||
|
|
||||||
rv = self.make_request('post', '/api/visualizations', data=data, user=admin_from_diff_org)
|
|
||||||
self.assertEquals(rv.status_code, 404)
|
|
||||||
|
|
||||||
def test_only_owner_or_admin_can_edit_visualization(self):
|
|
||||||
vis = self.factory.create_visualization()
|
|
||||||
models.db.session.flush()
|
|
||||||
path = '/api/visualizations/{}'.format(vis.id)
|
|
||||||
data = {'name': 'After Update'}
|
|
||||||
|
|
||||||
other_user = self.factory.create_user()
|
|
||||||
admin = self.factory.create_admin()
|
|
||||||
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
|
||||||
models.db.session.commit()
|
|
||||||
models.db.session.refresh(admin)
|
|
||||||
models.db.session.refresh(other_user)
|
|
||||||
models.db.session.refresh(admin_from_diff_org)
|
|
||||||
|
|
||||||
rv = self.make_request('post', path, user=admin, data=data)
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
|
|
||||||
rv = self.make_request('post', path, user=other_user, data=data)
|
|
||||||
self.assertEquals(rv.status_code, 403)
|
|
||||||
|
|
||||||
rv = self.make_request('post', path, user=admin_from_diff_org, data=data)
|
|
||||||
self.assertEquals(rv.status_code, 404)
|
|
||||||
|
|
||||||
def test_only_owner_or_admin_can_delete_visualization(self):
|
|
||||||
vis = self.factory.create_visualization()
|
|
||||||
models.db.session.flush()
|
|
||||||
path = '/api/visualizations/{}'.format(vis.id)
|
|
||||||
|
|
||||||
other_user = self.factory.create_user()
|
|
||||||
admin = self.factory.create_admin()
|
|
||||||
admin_from_diff_org = self.factory.create_admin(org=self.factory.create_org())
|
|
||||||
|
|
||||||
models.db.session.commit()
|
|
||||||
models.db.session.refresh(admin)
|
|
||||||
models.db.session.refresh(other_user)
|
|
||||||
models.db.session.refresh(admin_from_diff_org)
|
|
||||||
rv = self.make_request('delete', path, user=admin)
|
|
||||||
self.assertEquals(rv.status_code, 200)
|
|
||||||
|
|
||||||
vis = self.factory.create_visualization()
|
|
||||||
models.db.session.commit()
|
|
||||||
path = '/api/visualizations/{}'.format(vis.id)
|
|
||||||
|
|
||||||
rv = self.make_request('delete', path, user=other_user)
|
|
||||||
self.assertEquals(rv.status_code, 403)
|
|
||||||
|
|
||||||
vis = self.factory.create_visualization()
|
|
||||||
models.db.session.commit()
|
|
||||||
path = '/api/visualizations/{}'.format(vis.id)
|
|
||||||
|
|
||||||
rv = self.make_request('delete', path, user=admin_from_diff_org)
|
|
||||||
self.assertEquals(rv.status_code, 404)
|
|
||||||
|
|
||||||
|
|
||||||
class JobAPITest(BaseTestCase, AuthenticationTestMixin):
|
class JobAPITest(BaseTestCase, AuthenticationTestMixin):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.paths = []
|
self.paths = []
|
||||||
|
Loading…
Reference in New Issue
Block a user