# HG changeset patch # User Marcin Kuzminski # Date 2020-04-20 13:59:06 # Node ID 674186c0c1176b0b7cbba9b35d5bd0874fb93a3a # Parent 2cac6d75f5d55c4a1dac57459db4704547d32ef3 users: added more secure way for fetching authentication tokens. - we don't expose ALL on single page - request needs a validation before viewing of each token - will later add 2nd layer of auth. diff --git a/rhodecode/apps/admin/__init__.py b/rhodecode/apps/admin/__init__.py --- a/rhodecode/apps/admin/__init__.py +++ b/rhodecode/apps/admin/__init__.py @@ -302,6 +302,10 @@ def admin_routes(config): pattern='/users/{user_id:\d+}/edit/auth_tokens', user_route=True) config.add_route( + name='edit_user_auth_tokens_view', + pattern='/users/{user_id:\d+}/edit/auth_tokens/view', + user_route=True) + config.add_route( name='edit_user_auth_tokens_add', pattern='/users/{user_id:\d+}/edit/auth_tokens/new', user_route=True) diff --git a/rhodecode/apps/admin/tests/test_admin_users.py b/rhodecode/apps/admin/tests/test_admin_users.py --- a/rhodecode/apps/admin/tests/test_admin_users.py +++ b/rhodecode/apps/admin/tests/test_admin_users.py @@ -143,7 +143,7 @@ class TestAdminUsersView(TestController) response = self.app.get( route_path('edit_user_auth_tokens', user_id=user_id)) for token in auth_tokens: - response.mustcontain(token) + response.mustcontain(token[:4]) response.mustcontain('never') @pytest.mark.parametrize("desc, lifetime", [ @@ -165,7 +165,7 @@ class TestAdminUsersView(TestController) response = response.follow() user = User.get(user_id) for auth_token in user.auth_tokens: - response.mustcontain(auth_token) + response.mustcontain(auth_token[:4]) def test_delete_auth_token(self, user_util): self.log_user() diff --git a/rhodecode/apps/admin/views/users.py b/rhodecode/apps/admin/views/users.py --- a/rhodecode/apps/admin/views/users.py +++ b/rhodecode/apps/admin/views/users.py @@ -804,6 +804,25 @@ class UsersView(UserAppView): c.role_vcs = AuthTokenModel.cls.ROLE_VCS return self._get_template_context(c) + @LoginRequired() + @HasPermissionAllDecorator('hg.admin') + @view_config( + route_name='edit_user_auth_tokens_view', request_method='POST', + renderer='json_ext', xhr=True) + def auth_tokens_view(self): + _ = self.request.translate + c = self.load_default_context() + c.user = self.db_user + + auth_token_id = self.request.POST.get('auth_token_id') + + if auth_token_id: + token = UserApiKeys.get_or_404(auth_token_id) + + return { + 'auth_token': token.api_key + } + def maybe_attach_token_scope(self, token): # implemented in EE edition pass diff --git a/rhodecode/apps/my_account/__init__.py b/rhodecode/apps/my_account/__init__.py --- a/rhodecode/apps/my_account/__init__.py +++ b/rhodecode/apps/my_account/__init__.py @@ -50,6 +50,9 @@ def includeme(config): name='my_account_auth_tokens', pattern=ADMIN_PREFIX + '/my_account/auth_tokens') config.add_route( + name='my_account_auth_tokens_view', + pattern=ADMIN_PREFIX + '/my_account/auth_tokens/view') + config.add_route( name='my_account_auth_tokens_add', pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new') config.add_route( diff --git a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py --- a/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py +++ b/rhodecode/apps/my_account/tests/test_my_account_auth_tokens.py @@ -49,7 +49,7 @@ class TestMyAccountAuthTokens(TestContro user = User.get(usr['user_id']) response = self.app.get(route_path('my_account_auth_tokens')) for token in user.auth_tokens: - response.mustcontain(token) + response.mustcontain(token[:4]) response.mustcontain('never') def test_my_account_add_auth_tokens_wrong_csrf(self, user_util): @@ -79,7 +79,7 @@ class TestMyAccountAuthTokens(TestContro response = response.follow() user = User.get(user_id) for auth_token in user.auth_tokens: - response.mustcontain(auth_token) + response.mustcontain(auth_token[:4]) def test_my_account_delete_auth_token(self, user_util): user = user_util.create_user(password='qweqwe') diff --git a/rhodecode/apps/my_account/views/my_account.py b/rhodecode/apps/my_account/views/my_account.py --- a/rhodecode/apps/my_account/views/my_account.py +++ b/rhodecode/apps/my_account/views/my_account.py @@ -25,7 +25,7 @@ import string import formencode import formencode.htmlfill import peppercorn -from pyramid.httpexceptions import HTTPFound +from pyramid.httpexceptions import HTTPFound, HTTPNotFound from pyramid.view import view_config from rhodecode.apps._base import BaseAppView, DataGridAppView @@ -164,6 +164,27 @@ class MyAccountView(BaseAppView, DataGri c.role_vcs = AuthTokenModel.cls.ROLE_VCS return self._get_template_context(c) + @LoginRequired() + @NotAnonymous() + @CSRFRequired() + @view_config( + route_name='my_account_auth_tokens_view', request_method='POST', xhr=True, + renderer='json_ext') + def my_account_auth_tokens_view(self): + _ = self.request.translate + c = self.load_default_context() + + auth_token_id = self.request.POST.get('auth_token_id') + + if auth_token_id: + token = UserApiKeys.get_or_404(auth_token_id) + if token.user.user_id != c.user.user_id: + raise HTTPNotFound() + + return { + 'auth_token': token.api_key + } + def maybe_attach_token_scope(self, token): # implemented in EE edition pass diff --git a/rhodecode/public/css/tables.less b/rhodecode/public/css/tables.less --- a/rhodecode/public/css/tables.less +++ b/rhodecode/public/css/tables.less @@ -352,7 +352,7 @@ table.dataTable { margin-bottom: @padding; table.rctable td:first-child { - width: 340px; + width: 120px; } } diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -111,6 +111,7 @@ function registerRCRoutes() { pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']); pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']); pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']); + pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']); pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']); pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']); pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']); @@ -312,6 +313,7 @@ function registerRCRoutes() { pyroutes.register('my_account_update', '/_admin/my_account/update', []); pyroutes.register('my_account_password', '/_admin/my_account/password', []); pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []); + pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []); pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []); pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []); pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []); diff --git a/rhodecode/public/js/src/rhodecode/users.js b/rhodecode/public/js/src/rhodecode/users.js --- a/rhodecode/public/js/src/rhodecode/users.js +++ b/rhodecode/public/js/src/rhodecode/users.js @@ -47,3 +47,72 @@ var UsersAutoComplete = function(input_i lookupFilter: autocompleteFilterResult }); }; + +var _showAuthToken = function (authTokenId, showUrl) { + + Swal.fire({ + title: _gettext('Show this authentication token?'), + showCancelButton: true, + confirmButtonColor: '#84a5d2', + cancelButtonColor: '#e85e4d', + showClass: { + popup: 'swal2-noanimation', + backdrop: 'swal2-noanimation' + }, + hideClass: { + popup: '', + backdrop: '' + }, + confirmButtonText: _gettext('Show'), + showLoaderOnConfirm: true, + allowOutsideClick: function () { + !Swal.isLoading() + }, + preConfirm: function () { + + var postData = { + 'auth_token_id': authTokenId, + 'csrf_token': CSRF_TOKEN + }; + return new Promise(function (resolve, reject) { + $.ajax({ + type: 'POST', + data: postData, + url: showUrl, + headers: {'X-PARTIAL-XHR': true} + }) + .done(function (data) { + resolve(data); + }) + .fail(function (jqXHR, textStatus, errorThrown) { + //reject("Failed to fetch Authentication Token") + var message = formatErrorMessage(jqXHR, textStatus, errorThrown) + Swal.showValidationMessage('Request failed: {0}'.format(message) + ) + }); + }) + } + + }) + .then(function (result) { + if (result.value) { + var tmpl = ('{0}' + + ''); + + Swal.fire({ + title: _gettext('Authentication Token'), + html: tmpl.format(result.value.auth_token, result.value.auth_token), + confirmButtonColor: '#84a5d2', + cancelButtonColor: '#e85e4d', + showClass: { + popup: 'swal2-noanimation', + backdrop: 'swal2-noanimation' + }, + hideClass: { + popup: '', + backdrop: '' + }, + }) + } + }) +} \ No newline at end of file diff --git a/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako b/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako --- a/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako +++ b/rhodecode/templates/admin/my_account/my_account_auth_tokens.mako @@ -1,4 +1,10 @@
+ +

${_('Authentication Tokens')}

@@ -21,9 +27,11 @@ %if c.user_auth_tokens: %for auth_token in c.user_auth_tokens: - -
- ${auth_token.api_key} + +
+ + ${auth_token.token_obfuscated} +
${auth_token.description} @@ -44,7 +52,7 @@ ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)} - ${h.hidden('del_auth_token', auth_token.user_api_key_id)} +