Return unsafe sharing error from backend (#3990)

* return message explaining unsafe sharing

* use backend-generated message for public dashboards

* use backend-generated message for embeds

* Update redash/handlers/query_results.py

Co-Authored-By: Arik Fraimovich <arik@arikfr.com>

* refactor simple (non-interpolated) query result handler error messages to a single location

* use error_messages to test out unsafe error messages (along with a couple of others)

* Update redash/handlers/query_results.py

Co-Authored-By: Ran Byron <ranbena@gmail.com>

* Update redash/handlers/query_results.py

Co-Authored-By: Arik Fraimovich <arik@arikfr.com>
This commit is contained in:
Omer Lachish 2019-07-21 09:21:45 +03:00 committed by GitHub
parent 9bdb3412a5
commit ea0e411053
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 22 deletions

View File

@ -39,11 +39,7 @@ const VisualizationEmbed = {
document.querySelector('body').classList.add('headless');
if (this.query.is_safe) {
this.refreshQueryResults();
} else {
this.error = "Can't embed queries with text parameters.";
}
this.refreshQueryResults();
},
};

View File

@ -38,16 +38,7 @@ const PublicDashboardPage = {
loadDashboard($http, $route).then((data) => {
this.dashboard = new Dashboard(data);
this.dashboard.widgets = Dashboard.prepareDashboardWidgets(this.dashboard.widgets);
this.dashboard.widgets.forEach((widget) => {
widget.load(!!refreshRate).catch((error) => {
const isSafe = widget.getQuery() ? widget.getQuery().is_safe : true;
if (!isSafe) {
error.errorMessage = 'This query contains potentially unsafe parameters and cannot be executed on a publicly shared dashboard.';
}
throw error;
});
});
this.dashboard.widgets.forEach(widget => widget.load(!!refreshRate));
this.filters = []; // TODO: implement (@/services/dashboard.js:collectDashboardFilters)
this.filtersOnChange = (allFilters) => {
this.filters = allFilters;

View File

@ -15,8 +15,16 @@ from redash.models.parameterized_query import ParameterizedQuery, InvalidParamet
from redash.serializers import serialize_query_result, serialize_query_result_to_csv, serialize_query_result_to_xlsx
def error_response(message):
return {'job': {'status': 4, 'error': message}}, 400
def error_response(message, http_status=400):
return {'job': {'status': 4, 'error': message}}, http_status
error_messages = {
'unsafe_when_shared': error_response('This query contains potentially unsafe parameters and cannot be executed on a shared dashboard or an embedded visualization.', 403),
'unsafe_on_view_only': error_response('This query contains potentially unsafe parameters and cannot be executed with read-only access to this data source.', 403),
'no_permission': error_response('You do not have permission to run queries with this data source.', 403),
'select_data_source': error_response('Please select data source to run this query.', 401)
}
def run_query(query, parameters, data_source, query_id, max_age=0):
@ -92,10 +100,10 @@ class QueryResultListResource(BaseResource):
if data_source_id:
data_source = models.DataSource.get_by_id_and_org(params.get('data_source_id'), self.current_org)
else:
return {'job': {'status': 4, 'error': 'Please select data source to run this query.'}}, 401
return error_messages['select_data_source']
if not has_access(data_source, self.current_user, not_view_only):
return {'job': {'status': 4, 'error': 'You do not have permission to run queries with this data source.'}}, 403
return error_messages['no_permission']
self.record_event({
'action': 'execute_query',
@ -182,7 +190,13 @@ class QueryResultResource(BaseResource):
if has_access(query, self.current_user, allow_executing_with_view_only_permissions):
return run_query(query.parameterized, parameter_values, query.data_source, query_id, max_age)
else:
return {'job': {'status': 4, 'error': 'You do not have permission to run queries with this data source.'}}, 403
if not query.parameterized.is_safe:
if current_user.is_api_user():
return error_messages['unsafe_when_shared']
else:
return error_messages['unsafe_on_view_only']
else:
return error_messages['no_permission']
@require_permission('view_query')
def get(self, query_id=None, query_result_id=None, filetype='json'):

View File

@ -2,6 +2,7 @@ from tests import BaseTestCase
from redash.models import db
from redash.utils import json_dumps
from redash.handlers.query_results import error_messages
class TestQueryResultsCacheHeaders(BaseTestCase):
@ -111,8 +112,7 @@ class TestQueryResultListAPI(BaseTestCase):
'max_age': 0})
self.assertEquals(rv.status_code, 401)
self.assertNotIn('query_result', rv.json)
self.assertIn('job', rv.json)
self.assertDictEqual(rv.json, error_messages['select_data_source'][0])
class TestQueryResultAPI(BaseTestCase):
@ -145,6 +145,14 @@ class TestQueryResultAPI(BaseTestCase):
self.assertEquals(rv.status_code, 200)
self.assertIn('job', rv.json)
def test_execute_but_has_no_access_to_data_source(self):
ds = self.factory.create_data_source(group=self.factory.create_group())
query = self.factory.create_query(data_source=ds)
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id))
self.assertEquals(rv.status_code, 403)
self.assertDictEqual(rv.json, error_messages['no_permission'][0])
def test_execute_with_no_parameter_values(self):
query = self.factory.create_query()
@ -159,6 +167,7 @@ class TestQueryResultAPI(BaseTestCase):
rv = self.make_request('post', '/api/queries/{}/results'.format(query.id), data={"parameters": {}})
self.assertEquals(rv.status_code, 403)
self.assertDictEqual(rv.json, error_messages['unsafe_on_view_only'][0])
def test_allows_execution_of_safe_queries_on_view_only_data_sources(self):
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=True)
@ -174,6 +183,7 @@ class TestQueryResultAPI(BaseTestCase):
data = {'parameters': {'foo': 'bar'}}
rv = self.make_request('post', '/api/queries/{}/results?api_key={}'.format(query.id, query.api_key), data=data)
self.assertEquals(rv.status_code, 403)
self.assertDictEqual(rv.json, error_messages['unsafe_when_shared'][0])
def test_access_with_query_api_key(self):
ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)