Skip to content

Commit

Permalink
Add regenerate function of user's API key (#3224)
Browse files Browse the repository at this point in the history
* Add regenerate function of user's API Key

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

Co-Authored-By: kyoshidajp <[email protected]>

* Remove unused error message

* Refactoring: Inline temp

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

Co-Authored-By: kyoshidajp <[email protected]>

* Change action event of regenerate user API key
  • Loading branch information
kyoshidajp authored and arikfr committed Jan 20, 2019
1 parent b91d4bd commit b3643ff
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 3 deletions.
3 changes: 3 additions & 0 deletions client/app/pages/users/show.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ <h3 class="profile__h3">{{user.name}}</h3>
<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>

Expand Down
30 changes: 29 additions & 1 deletion client/app/pages/users/show.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand Down
5 changes: 4 additions & 1 deletion redash/handlers/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
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
Expand Down Expand Up @@ -101,6 +101,9 @@ def json_representation(data, code, headers=None):
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')
Expand Down
20 changes: 20 additions & 0 deletions redash/handlers/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,26 @@ def post(self, user_id):
}


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)
Expand Down
3 changes: 3 additions & 0 deletions redash/models/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ def disable(self):
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:
Expand Down
44 changes: 44 additions & 0 deletions tests/handlers/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,47 @@ def test_disabled_user_should_not_receive_restore_password_email(self):
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)
13 changes: 12 additions & 1 deletion tests/models/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,17 @@ def test_non_unicode_search_string(self):
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()
Expand Down Expand Up @@ -94,4 +105,4 @@ def test_sync(self):

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)

0 comments on commit b3643ff

Please sign in to comment.