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: |
|
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="t |
|
30 | <td class="td-authtoken"> | |
25 |
<div class="user_auth_tokens |
|
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 |
${ |
|
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="t |
|
35 | <td class="td-authtoken"> | |
30 |
<div class="user_auth_tokens |
|
36 | <div class="user_auth_tokens"> | |
31 |
<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 |
${ |
|
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