##// END OF EJS Templates
users: added more secure way for fetching authentication tokens....
marcink -
r4316:674186c0 default
parent child Browse files
Show More
@@ -302,6 +302,10 b' def admin_routes(config):'
302 302 pattern='/users/{user_id:\d+}/edit/auth_tokens',
303 303 user_route=True)
304 304 config.add_route(
305 name='edit_user_auth_tokens_view',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/view',
307 user_route=True)
308 config.add_route(
305 309 name='edit_user_auth_tokens_add',
306 310 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
307 311 user_route=True)
@@ -143,7 +143,7 b' class TestAdminUsersView(TestController)'
143 143 response = self.app.get(
144 144 route_path('edit_user_auth_tokens', user_id=user_id))
145 145 for token in auth_tokens:
146 response.mustcontain(token)
146 response.mustcontain(token[:4])
147 147 response.mustcontain('never')
148 148
149 149 @pytest.mark.parametrize("desc, lifetime", [
@@ -165,7 +165,7 b' class TestAdminUsersView(TestController)'
165 165 response = response.follow()
166 166 user = User.get(user_id)
167 167 for auth_token in user.auth_tokens:
168 response.mustcontain(auth_token)
168 response.mustcontain(auth_token[:4])
169 169
170 170 def test_delete_auth_token(self, user_util):
171 171 self.log_user()
@@ -804,6 +804,25 b' class UsersView(UserAppView):'
804 804 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
805 805 return self._get_template_context(c)
806 806
807 @LoginRequired()
808 @HasPermissionAllDecorator('hg.admin')
809 @view_config(
810 route_name='edit_user_auth_tokens_view', request_method='POST',
811 renderer='json_ext', xhr=True)
812 def auth_tokens_view(self):
813 _ = self.request.translate
814 c = self.load_default_context()
815 c.user = self.db_user
816
817 auth_token_id = self.request.POST.get('auth_token_id')
818
819 if auth_token_id:
820 token = UserApiKeys.get_or_404(auth_token_id)
821
822 return {
823 'auth_token': token.api_key
824 }
825
807 826 def maybe_attach_token_scope(self, token):
808 827 # implemented in EE edition
809 828 pass
@@ -50,6 +50,9 b' def includeme(config):'
50 50 name='my_account_auth_tokens',
51 51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 52 config.add_route(
53 name='my_account_auth_tokens_view',
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/view')
55 config.add_route(
53 56 name='my_account_auth_tokens_add',
54 57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 58 config.add_route(
@@ -49,7 +49,7 b' class TestMyAccountAuthTokens(TestContro'
49 49 user = User.get(usr['user_id'])
50 50 response = self.app.get(route_path('my_account_auth_tokens'))
51 51 for token in user.auth_tokens:
52 response.mustcontain(token)
52 response.mustcontain(token[:4])
53 53 response.mustcontain('never')
54 54
55 55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
@@ -79,7 +79,7 b' class TestMyAccountAuthTokens(TestContro'
79 79 response = response.follow()
80 80 user = User.get(user_id)
81 81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
82 response.mustcontain(auth_token[:4])
83 83
84 84 def test_my_account_delete_auth_token(self, user_util):
85 85 user = user_util.create_user(password='qweqwe')
@@ -25,7 +25,7 b' import string'
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 29 from pyramid.view import view_config
30 30
31 31 from rhodecode.apps._base import BaseAppView, DataGridAppView
@@ -164,6 +164,27 b' class MyAccountView(BaseAppView, DataGri'
164 164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 165 return self._get_template_context(c)
166 166
167 @LoginRequired()
168 @NotAnonymous()
169 @CSRFRequired()
170 @view_config(
171 route_name='my_account_auth_tokens_view', request_method='POST', xhr=True,
172 renderer='json_ext')
173 def my_account_auth_tokens_view(self):
174 _ = self.request.translate
175 c = self.load_default_context()
176
177 auth_token_id = self.request.POST.get('auth_token_id')
178
179 if auth_token_id:
180 token = UserApiKeys.get_or_404(auth_token_id)
181 if token.user.user_id != c.user.user_id:
182 raise HTTPNotFound()
183
184 return {
185 'auth_token': token.api_key
186 }
187
167 188 def maybe_attach_token_scope(self, token):
168 189 # implemented in EE edition
169 190 pass
@@ -352,7 +352,7 b' table.dataTable {'
352 352 margin-bottom: @padding;
353 353
354 354 table.rctable td:first-child {
355 width: 340px;
355 width: 120px;
356 356 }
357 357 }
358 358
@@ -111,6 +111,7 b' function registerRCRoutes() {'
111 111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
114 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
114 115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
115 116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
116 117 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
@@ -312,6 +313,7 b' function registerRCRoutes() {'
312 313 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
313 314 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
314 315 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
316 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
315 317 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
316 318 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
317 319 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
@@ -47,3 +47,72 b' var UsersAutoComplete = function(input_i'
47 47 lookupFilter: autocompleteFilterResult
48 48 });
49 49 };
50
51 var _showAuthToken = function (authTokenId, showUrl) {
52
53 Swal.fire({
54 title: _gettext('Show this authentication token?'),
55 showCancelButton: true,
56 confirmButtonColor: '#84a5d2',
57 cancelButtonColor: '#e85e4d',
58 showClass: {
59 popup: 'swal2-noanimation',
60 backdrop: 'swal2-noanimation'
61 },
62 hideClass: {
63 popup: '',
64 backdrop: ''
65 },
66 confirmButtonText: _gettext('Show'),
67 showLoaderOnConfirm: true,
68 allowOutsideClick: function () {
69 !Swal.isLoading()
70 },
71 preConfirm: function () {
72
73 var postData = {
74 'auth_token_id': authTokenId,
75 'csrf_token': CSRF_TOKEN
76 };
77 return new Promise(function (resolve, reject) {
78 $.ajax({
79 type: 'POST',
80 data: postData,
81 url: showUrl,
82 headers: {'X-PARTIAL-XHR': true}
83 })
84 .done(function (data) {
85 resolve(data);
86 })
87 .fail(function (jqXHR, textStatus, errorThrown) {
88 //reject("Failed to fetch Authentication Token")
89 var message = formatErrorMessage(jqXHR, textStatus, errorThrown)
90 Swal.showValidationMessage('Request failed: {0}'.format(message)
91 )
92 });
93 })
94 }
95
96 })
97 .then(function (result) {
98 if (result.value) {
99 var tmpl = ('<code>{0}</code>' +
100 '<i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="{1}" title="Copy Token"></i>');
101
102 Swal.fire({
103 title: _gettext('Authentication Token'),
104 html: tmpl.format(result.value.auth_token, result.value.auth_token),
105 confirmButtonColor: '#84a5d2',
106 cancelButtonColor: '#e85e4d',
107 showClass: {
108 popup: 'swal2-noanimation',
109 backdrop: 'swal2-noanimation'
110 },
111 hideClass: {
112 popup: '',
113 backdrop: ''
114 },
115 })
116 }
117 })
118 } No newline at end of file
@@ -1,4 +1,10 b''
1 1 <div class="panel panel-default">
2 <script>
3 var showAuthToken = function(authTokenId) {
4 return _showAuthToken(authTokenId, pyroutes.url('my_account_auth_tokens_view'))
5 }
6 </script>
7
2 8 <div class="panel-heading">
3 9 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 10 </div>
@@ -21,9 +27,11 b''
21 27 %if c.user_auth_tokens:
22 28 %for auth_token in c.user_auth_tokens:
23 29 <tr class="${('expired' if auth_token.expired else '')}">
24 <td class="truncate-wrap td-authtoken">
25 <div class="user_auth_tokens truncate autoexpand">
26 <code>${auth_token.api_key}</code>
30 <td class="td-authtoken">
31 <div class="user_auth_tokens">
32 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
33 ${auth_token.token_obfuscated}
34 </code>
27 35 </div>
28 36 </td>
29 37 <td class="td-wrap">${auth_token.description}</td>
@@ -44,7 +52,7 b''
44 52 </td>
45 53 <td class="td-action">
46 54 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
47 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
55 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
48 56 <button class="btn btn-link btn-danger" type="submit"
49 57 onclick="submitConfirm(event, this, _gettext('Confirm to delete this auth token'), _gettext('Delete'), '${auth_token.token_obfuscated}')"
50 58 >
@@ -1,6 +1,12 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 <script>
5 var showAuthToken = function(authTokenId) {
6 return _showAuthToken(authTokenId, pyroutes.url('edit_user_auth_tokens_view', {'user_id': '${c.user.user_id}'}))
7 }
8 </script>
9
4 10 <div class="panel-heading">
5 11 <h3 class="panel-title">
6 12 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
@@ -26,9 +32,11 b''
26 32 %if c.user_auth_tokens:
27 33 %for auth_token in c.user_auth_tokens:
28 34 <tr class="${('expired' if auth_token.expired else '')}">
29 <td class="truncate-wrap td-authtoken">
30 <div class="user_auth_tokens truncate autoexpand">
31 <code>${auth_token.api_key}</code>
35 <td class="td-authtoken">
36 <div class="user_auth_tokens">
37 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
38 ${auth_token.token_obfuscated}
39 </code>
32 40 </div>
33 41 </td>
34 42 <td class="td-wrap">${auth_token.description}</td>
@@ -49,7 +57,7 b''
49 57 </td>
50 58 <td class="td-action">
51 59 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
52 ${h.hidden('del_auth_token', auth_token.user_api_key_id)}
60 <input name="del_auth_token" type="hidden" value="${auth_token.user_api_key_id}">
53 61 <button class="btn btn-link btn-danger" type="submit"
54 62 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
55 63 ${_('Delete')}
General Comments 0
You need to be logged in to leave comments. Login now