##// 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 pattern='/users/{user_id:\d+}/edit/auth_tokens',
302 pattern='/users/{user_id:\d+}/edit/auth_tokens',
303 user_route=True)
303 user_route=True)
304 config.add_route(
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 name='edit_user_auth_tokens_add',
309 name='edit_user_auth_tokens_add',
306 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
310 pattern='/users/{user_id:\d+}/edit/auth_tokens/new',
307 user_route=True)
311 user_route=True)
@@ -143,7 +143,7 b' class TestAdminUsersView(TestController)'
143 response = self.app.get(
143 response = self.app.get(
144 route_path('edit_user_auth_tokens', user_id=user_id))
144 route_path('edit_user_auth_tokens', user_id=user_id))
145 for token in auth_tokens:
145 for token in auth_tokens:
146 response.mustcontain(token)
146 response.mustcontain(token[:4])
147 response.mustcontain('never')
147 response.mustcontain('never')
148
148
149 @pytest.mark.parametrize("desc, lifetime", [
149 @pytest.mark.parametrize("desc, lifetime", [
@@ -165,7 +165,7 b' class TestAdminUsersView(TestController)'
165 response = response.follow()
165 response = response.follow()
166 user = User.get(user_id)
166 user = User.get(user_id)
167 for auth_token in user.auth_tokens:
167 for auth_token in user.auth_tokens:
168 response.mustcontain(auth_token)
168 response.mustcontain(auth_token[:4])
169
169
170 def test_delete_auth_token(self, user_util):
170 def test_delete_auth_token(self, user_util):
171 self.log_user()
171 self.log_user()
@@ -804,6 +804,25 b' class UsersView(UserAppView):'
804 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
804 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
805 return self._get_template_context(c)
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 def maybe_attach_token_scope(self, token):
826 def maybe_attach_token_scope(self, token):
808 # implemented in EE edition
827 # implemented in EE edition
809 pass
828 pass
@@ -50,6 +50,9 b' def includeme(config):'
50 name='my_account_auth_tokens',
50 name='my_account_auth_tokens',
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
51 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
52 config.add_route(
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 name='my_account_auth_tokens_add',
56 name='my_account_auth_tokens_add',
54 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
57 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
55 config.add_route(
58 config.add_route(
@@ -49,7 +49,7 b' class TestMyAccountAuthTokens(TestContro'
49 user = User.get(usr['user_id'])
49 user = User.get(usr['user_id'])
50 response = self.app.get(route_path('my_account_auth_tokens'))
50 response = self.app.get(route_path('my_account_auth_tokens'))
51 for token in user.auth_tokens:
51 for token in user.auth_tokens:
52 response.mustcontain(token)
52 response.mustcontain(token[:4])
53 response.mustcontain('never')
53 response.mustcontain('never')
54
54
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
55 def test_my_account_add_auth_tokens_wrong_csrf(self, user_util):
@@ -79,7 +79,7 b' class TestMyAccountAuthTokens(TestContro'
79 response = response.follow()
79 response = response.follow()
80 user = User.get(user_id)
80 user = User.get(user_id)
81 for auth_token in user.auth_tokens:
81 for auth_token in user.auth_tokens:
82 response.mustcontain(auth_token)
82 response.mustcontain(auth_token[:4])
83
83
84 def test_my_account_delete_auth_token(self, user_util):
84 def test_my_account_delete_auth_token(self, user_util):
85 user = user_util.create_user(password='qweqwe')
85 user = user_util.create_user(password='qweqwe')
@@ -25,7 +25,7 b' import string'
25 import formencode
25 import formencode
26 import formencode.htmlfill
26 import formencode.htmlfill
27 import peppercorn
27 import peppercorn
28 from pyramid.httpexceptions import HTTPFound
28 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30
30
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
31 from rhodecode.apps._base import BaseAppView, DataGridAppView
@@ -164,6 +164,27 b' class MyAccountView(BaseAppView, DataGri'
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
164 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
165 return self._get_template_context(c)
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 def maybe_attach_token_scope(self, token):
188 def maybe_attach_token_scope(self, token):
168 # implemented in EE edition
189 # implemented in EE edition
169 pass
190 pass
@@ -352,7 +352,7 b' table.dataTable {'
352 margin-bottom: @padding;
352 margin-bottom: @padding;
353
353
354 table.rctable td:first-child {
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 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
111 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
112 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
113 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
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 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
115 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
115 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
116 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
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 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
313 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
313 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
314 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
314 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
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 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
317 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
316 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
318 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
317 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
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 lookupFilter: autocompleteFilterResult
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 <div class="panel panel-default">
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 <div class="panel-heading">
8 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
9 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
10 </div>
@@ -21,9 +27,11 b''
21 %if c.user_auth_tokens:
27 %if c.user_auth_tokens:
22 %for auth_token in c.user_auth_tokens:
28 %for auth_token in c.user_auth_tokens:
23 <tr class="${('expired' if auth_token.expired else '')}">
29 <tr class="${('expired' if auth_token.expired else '')}">
24 <td class="truncate-wrap td-authtoken">
30 <td class="td-authtoken">
25 <div class="user_auth_tokens truncate autoexpand">
31 <div class="user_auth_tokens">
26 <code>${auth_token.api_key}</code>
32 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
33 ${auth_token.token_obfuscated}
34 </code>
27 </div>
35 </div>
28 </td>
36 </td>
29 <td class="td-wrap">${auth_token.description}</td>
37 <td class="td-wrap">${auth_token.description}</td>
@@ -44,7 +52,7 b''
44 </td>
52 </td>
45 <td class="td-action">
53 <td class="td-action">
46 ${h.secure_form(h.route_path('my_account_auth_tokens_delete'), request=request)}
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 <button class="btn btn-link btn-danger" type="submit"
56 <button class="btn btn-link btn-danger" type="submit"
49 onclick="submitConfirm(event, this, _gettext('Confirm to delete this auth token'), _gettext('Delete'), '${auth_token.token_obfuscated}')"
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 <%namespace name="base" file="/base/base.mako"/>
1 <%namespace name="base" file="/base/base.mako"/>
2
2
3 <div class="panel panel-default">
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 <div class="panel-heading">
10 <div class="panel-heading">
5 <h3 class="panel-title">
11 <h3 class="panel-title">
6 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
12 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
@@ -26,9 +32,11 b''
26 %if c.user_auth_tokens:
32 %if c.user_auth_tokens:
27 %for auth_token in c.user_auth_tokens:
33 %for auth_token in c.user_auth_tokens:
28 <tr class="${('expired' if auth_token.expired else '')}">
34 <tr class="${('expired' if auth_token.expired else '')}">
29 <td class="truncate-wrap td-authtoken">
35 <td class="td-authtoken">
30 <div class="user_auth_tokens truncate autoexpand">
36 <div class="user_auth_tokens">
31 <code>${auth_token.api_key}</code>
37 <code class="cursor-pointer" onclick="showAuthToken(${auth_token.user_api_key_id})">
38 ${auth_token.token_obfuscated}
39 </code>
32 </div>
40 </div>
33 </td>
41 </td>
34 <td class="td-wrap">${auth_token.description}</td>
42 <td class="td-wrap">${auth_token.description}</td>
@@ -49,7 +57,7 b''
49 </td>
57 </td>
50 <td class="td-action">
58 <td class="td-action">
51 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), request=request)}
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 <button class="btn btn-link btn-danger" type="submit"
61 <button class="btn btn-link btn-danger" type="submit"
54 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
62 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.token_obfuscated}');">
55 ${_('Delete')}
63 ${_('Delete')}
General Comments 0
You need to be logged in to leave comments. Login now