diff --git a/client/app/components/queries/api-key-dialog.js b/client/app/components/queries/api-key-dialog.js index 796f186219..6f4451b3b0 100644 --- a/client/app/components/queries/api-key-dialog.js +++ b/client/app/components/queries/api-key-dialog.js @@ -4,26 +4,46 @@ const ApiKeyDialog = { `, - controller(clientConfig) { + controller($http, clientConfig, currentUser) { 'ngInject'; - this.apiKey = this.resolve.query.api_key; - this.csvUrl = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.csv?api_key=${this.apiKey}`; - this.jsonUrl = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.json?api_key=${this.apiKey}`; + this.canEdit = currentUser.id === this.resolve.query.user.id || currentUser.hasPermission('admin'); + this.disableRegenerateApiKeyButton = false; + this.query = this.resolve.query; + this.csvUrlBase = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.csv?api_key=`; + this.jsonUrlBase = `${clientConfig.basePath}api/queries/${this.resolve.query.id}/results.json?api_key=`; + + this.regenerateQueryApiKey = () => { + this.disableRegenerateApiKeyButton = true; + $http + .post(`api/queries/${this.resolve.query.id}/regenerate_api_key`) + .success((data) => { + this.query.api_key = data.api_key; + this.disableRegenerateApiKeyButton = false; + }) + .error(() => { + this.disableRegenerateApiKeyButton = false; + }); + }; }, bindings: { resolve: '<', diff --git a/redash/handlers/api.py b/redash/handlers/api.py index 012c121f63..4479ca12ea 100644 --- a/redash/handlers/api.py +++ b/redash/handlers/api.py @@ -35,7 +35,8 @@ QueryForkResource, QueryListResource, QueryRecentResource, QueryRefreshResource, QueryResource, QuerySearchResource, - QueryTagsResource) + QueryTagsResource, + QueryRegenerateApiKeyResource) from redash.handlers.query_results import (JobResource, QueryResultDropdownResource, QueryDropdownsResource, @@ -115,6 +116,9 @@ def json_representation(data, code, headers=None): api.add_org_resource(QueryRefreshResource, '/api/queries//refresh', endpoint='query_refresh') api.add_org_resource(QueryResource, '/api/queries/', endpoint='query') api.add_org_resource(QueryForkResource, '/api/queries//fork', endpoint='query_fork') +api.add_org_resource(QueryRegenerateApiKeyResource, + '/api/queries//regenerate_api_key', + endpoint='query_regenerate_api_key') api.add_org_resource(ObjectPermissionsListResource, '/api///acl', endpoint='object_permissions') api.add_org_resource(CheckPermissionResource, '/api///acl/', endpoint='check_permissions') diff --git a/redash/handlers/queries.py b/redash/handlers/queries.py index 79445c8ea0..8a760040c2 100644 --- a/redash/handlers/queries.py +++ b/redash/handlers/queries.py @@ -387,6 +387,24 @@ def delete(self, query_id): models.db.session.commit() +class QueryRegenerateApiKeyResource(BaseResource): + @require_permission('edit_query') + def post(self, query_id): + query = get_object_or_404(models.Query.get_by_id_and_org, query_id, self.current_org) + require_admin_or_owner(query.user_id) + query.regenerate_api_key() + models.db.session.commit() + + self.record_event({ + 'action': 'regnerate_api_key', + 'object_id': query_id, + 'object_type': 'query', + }) + + result = QuerySerializer(query).serialize() + return result + + class QueryForkResource(BaseResource): @require_permission('edit_query') def post(self, query_id): diff --git a/redash/models/__init__.py b/redash/models/__init__.py index 93d2515003..5abe93e9b6 100644 --- a/redash/models/__init__.py +++ b/redash/models/__init__.py @@ -421,6 +421,9 @@ def archive(self, user=None): if user: self.record_changes(user) + def regenerate_api_key(self): + self.api_key = generate_token(40) + @classmethod def create(cls, **kwargs): query = cls(**kwargs) diff --git a/tests/handlers/test_queries.py b/tests/handlers/test_queries.py index 3a898b1fa3..cfd51e3907 100644 --- a/tests/handlers/test_queries.py +++ b/tests/handlers/test_queries.py @@ -373,6 +373,55 @@ def test_refresh_forbiden_with_query_api_key(self): self.assertEqual(200, response.status_code) +class TestQueryRegenerateApiKey(BaseTestCase): + def test_non_admin_cannot_regenerate_api_key_of_other_user(self): + query_creator = self.factory.create_user() + query = self.factory.create_query(user=query_creator) + other_user = self.factory.create_user() + orig_api_key = query.api_key + + rv = self.make_request('post', "/api/queries/{}/regenerate_api_key".format(query.id), user=other_user) + self.assertEqual(rv.status_code, 403) + + reloaded_query = models.Query.query.get(query.id) + self.assertEquals(orig_api_key, reloaded_query.api_key) + + def test_admin_can_regenerate_api_key_of_other_user(self): + query_creator = self.factory.create_user() + query = self.factory.create_query(user=query_creator) + admin_user = self.factory.create_admin() + orig_api_key = query.api_key + + rv = self.make_request('post', "/api/queries/{}/regenerate_api_key".format(query.id), user=admin_user) + self.assertEqual(rv.status_code, 200) + + reloaded_query = models.Query.query.get(query.id) + self.assertNotEquals(orig_api_key, reloaded_query.api_key) + + def test_admin_can_regenerate_api_key_of_myself(self): + query_creator = self.factory.create_user() + admin_user = self.factory.create_admin() + query = self.factory.create_query(user=query_creator) + orig_api_key = query.api_key + + rv = self.make_request('post', "/api/queries/{}/regenerate_api_key".format(query.id), user=admin_user) + self.assertEqual(rv.status_code, 200) + + updated_query = models.Query.query.get(query.id) + self.assertNotEquals(orig_api_key, updated_query.api_key) + + def test_user_can_regenerate_api_key_of_myself(self): + user = self.factory.create_user() + query = self.factory.create_query(user=user) + orig_api_key = query.api_key + + rv = self.make_request('post', "/api/queries/{}/regenerate_api_key".format(query.id), user=user) + self.assertEqual(rv.status_code, 200) + + updated_query = models.Query.query.get(query.id) + self.assertNotEquals(orig_api_key, updated_query.api_key) + + class TestQueryForkResourcePost(BaseTestCase): def test_forks_a_query(self): ds = self.factory.create_data_source(group=self.factory.org.default_group, view_only=False)