Add regenerate function of user's API key (#3224)

* Add regenerate function of user's API Key

* Update client/app/pages/users/show.js

Co-Authored-By: kyoshidajp <claddvd@gmail.com>

* Remove unused error message

* Refactoring: Inline temp

* Update client/app/pages/users/show.js

Co-Authored-By: kyoshidajp <claddvd@gmail.com>

* Change action event of regenerate user API key
This commit is contained in:
YOSHIDA Katsuhiko
2019-01-20 20:38:20 +09:00
committed by Arik Fraimovich
parent b91d4bdcaf
commit b3643ffbb7
7 changed files with 115 additions and 3 deletions

View File

@@ -43,6 +43,9 @@
<label class="control-label">API Key</label>
<input type="text" class="form-control" value="{{user.api_key}}" size="44" readonly/>
</div>
<div class="form-group">
<button class="btn btn-default" ng-click="regenerateUserApiKey(user)" ng-disabled="disableRegenerateApiKeyButton">Regenerate</button>
</div>
<hr>

View File

@@ -6,7 +6,7 @@ import './settings.less';
function UserCtrl(
$scope, $routeParams, $http, $location, toastr,
clientConfig, currentUser, User,
clientConfig, currentUser, User, AlertDialog,
) {
$scope.userId = $routeParams.userId;
$scope.currentUser = currentUser;
@@ -122,6 +122,34 @@ function UserCtrl(
$scope.disableUser = (user) => {
User.disableUser(user);
};
$scope.regenerateUserApiKey = (user) => {
const doRegenerate = () => {
$scope.disableRegenerateApiKeyButton = true;
$http
.post(`api/users/${$scope.user.id}/regenerate_api_key`)
.success((data) => {
toastr.success('The API Key has been updated.');
user.api_key = data.api_key;
$scope.disableRegenerateApiKeyButton = false;
})
.error((response) => {
const message =
response.message
? response.message
: `Failed regenerating API Key: ${response.statusText}`;
toastr.error(message);
$scope.disableRegenerateApiKeyButton = false;
});
};
const title = 'Regenerate API Key';
const message = 'Are you sure you want to regenerate?';
AlertDialog.open(title, message, { class: 'btn-warning', title: 'Regenerate' })
.then(doRegenerate);
};
}
export default function init(ngModule) {

View File

@@ -11,7 +11,7 @@ from redash.handlers.data_sources import DataSourceTypeListResource, DataSourceL
from redash.handlers.events import EventsResource
from redash.handlers.queries import QueryForkResource, QueryRefreshResource, QueryListResource, QueryRecentResource, QuerySearchResource, QueryResource, MyQueriesResource
from redash.handlers.query_results import QueryResultListResource, QueryResultResource, JobResource
from redash.handlers.users import UserResource, UserListResource, UserInviteResource, UserResetPasswordResource, UserDisableResource
from redash.handlers.users import UserResource, UserListResource, UserInviteResource, UserResetPasswordResource, UserDisableResource, UserRegenerateApiKeyResource
from redash.handlers.visualizations import VisualizationListResource
from redash.handlers.visualizations import VisualizationResource
from redash.handlers.widgets import WidgetResource, WidgetListResource
@@ -101,6 +101,9 @@ api.add_org_resource(UserListResource, '/api/users', endpoint='users')
api.add_org_resource(UserResource, '/api/users/<user_id>', endpoint='user')
api.add_org_resource(UserInviteResource, '/api/users/<user_id>/invite', endpoint='user_invite')
api.add_org_resource(UserResetPasswordResource, '/api/users/<user_id>/reset_password', endpoint='user_reset_password')
api.add_org_resource(UserRegenerateApiKeyResource,
'/api/users/<user_id>/regenerate_api_key',
endpoint='user_regenerate_api_key')
api.add_org_resource(UserDisableResource, '/api/users/<user_id>/disable', endpoint='user_disable')
api.add_org_resource(VisualizationListResource, '/api/visualizations', endpoint='visualizations')

View File

@@ -149,6 +149,26 @@ class UserResetPasswordResource(BaseResource):
}
class UserRegenerateApiKeyResource(BaseResource):
def post(self, user_id):
user = models.User.get_by_id_and_org(user_id, self.current_org)
if user.is_disabled:
abort(404, message='Not found')
if not is_admin_or_owner(user_id):
abort(403)
user.regenerate_api_key()
models.db.session.commit()
self.record_event({
'action': 'regnerate_api_key',
'object_id': user.id,
'object_type': 'user'
})
return user.to_dict(with_api_key=True)
class UserResource(BaseResource):
def get(self, user_id):
require_permission_or_owner('list_users', user_id)

View File

@@ -122,6 +122,9 @@ class User(TimestampMixin, db.Model, BelongsToOrgMixin, UserMixin, PermissionsCh
def enable(self):
self.disabled_at = None
def regenerate_api_key(self):
self.api_key = generate_token(40)
def to_dict(self, with_api_key=False):
profile_image_url = self.profile_image_url
if self.is_disabled:

View File

@@ -328,3 +328,47 @@ class TestUserDisable(BaseTestCase):
rv = self.make_request('post', '/api/users/{}/reset_password'.format(user.id), user=admin_user)
self.assertEqual(rv.status_code, 404)
send_password_reset_email_mock.assert_not_called()
class TestUserRegenerateApiKey(BaseTestCase):
def test_non_admin_cannot_regenerate_other_user_api_key(self):
admin_user = self.factory.create_admin()
other_user = self.factory.create_user()
orig_api_key = other_user.api_key
rv = self.make_request('post', "/api/users/{}/regenerate_api_key".format(other_user.id), user=admin_user)
self.assertEqual(rv.status_code, 200)
other_user = models.User.query.get(other_user.id)
self.assertNotEquals(orig_api_key, other_user.api_key)
def test_admin_can_regenerate_other_user_api_key(self):
user1 = self.factory.create_user()
user2 = self.factory.create_user()
orig_user2_api_key = user2.api_key
rv = self.make_request('post', "/api/users/{}/regenerate_api_key".format(user2.id), user=user1)
self.assertEqual(rv.status_code, 403)
user = models.User.query.get(user2.id)
self.assertEquals(orig_user2_api_key, user.api_key)
def test_admin_can_regenerate_api_key_myself(self):
admin_user = self.factory.create_admin()
orig_api_key = admin_user.api_key
rv = self.make_request('post', "/api/users/{}/regenerate_api_key".format(admin_user.id), user=admin_user)
self.assertEqual(rv.status_code, 200)
user = models.User.query.get(admin_user.id)
self.assertNotEquals(orig_api_key, user.api_key)
def test_user_can_regenerate_api_key_myself(self):
user = self.factory.create_user()
orig_api_key = user.api_key
rv = self.make_request('post', "/api/users/{}/regenerate_api_key".format(user.id), user=user)
self.assertEqual(rv.status_code, 200)
user = models.User.query.get(user.id)
self.assertNotEquals(orig_api_key, user.api_key)

View File

@@ -62,6 +62,17 @@ class TestUserSearch(BaseTestCase):
assert user in User.search(User.all(user.org), term=u'א')
class TestUserRegenerateApiKey(BaseTestCase):
def test_regenerate_api_key(self):
user = self.factory.user
before_api_key = user.api_key
user.regenerate_api_key()
# check committed by research
user = User.query.get(user.id)
self.assertNotEquals(user.api_key, before_api_key)
class TestUserDetail(BaseTestCase):
# def setUp(self):
# super(TestUserDetail, self).setUp()
@@ -94,4 +105,4 @@ class TestUserDetail(BaseTestCase):
user_reloaded = User.query.filter(User.id==user.id).first()
self.assertIn('active_at', user_reloaded.details)
self.assertEqual(user_reloaded.active_at, timestamp)
self.assertEqual(user_reloaded.active_at, timestamp)