##// END OF EJS Templates
admin: moved auth tokens into pyramid view....
marcink -
r1518:6474fb97 default
parent child Browse files
Show More
@@ -0,0 +1,141 b''
1 # -*- coding: utf-8 -*-
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
21 import logging
22
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
25
26 from rhodecode.apps._base import BaseAppView
27 from rhodecode.lib.auth import (
28 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib.utils import PartialRenderer
31 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.model.auth_token import AuthTokenModel
33 from rhodecode.model.db import User
34 from rhodecode.model.meta import Session
35
36 log = logging.getLogger(__name__)
37
38
39 class AdminUsersView(BaseAppView):
40 ALLOW_SCOPED_TOKENS = False
41 """
42 This view has alternative version inside EE, if modified please take a look
43 in there as well.
44 """
45
46 def load_default_context(self):
47 c = self._get_local_tmpl_context()
48 c.auth_user = self.request.user
49 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
50 self._register_global_c(c)
51 return c
52
53 def _redirect_for_default_user(self, username):
54 _ = self.request.translate
55 if username == User.DEFAULT_USER:
56 h.flash(_("You can't edit this user"), category='warning')
57 # TODO(marcink): redirect to 'users' admin panel once this
58 # is a pyramid view
59 raise HTTPFound('/')
60
61 @LoginRequired()
62 @HasPermissionAllDecorator('hg.admin')
63 @view_config(
64 route_name='edit_user_auth_tokens', request_method='GET',
65 renderer='rhodecode:templates/admin/users/user_edit.mako')
66 def auth_tokens(self):
67 _ = self.request.translate
68 c = self.load_default_context()
69
70 user_id = self.request.matchdict.get('user_id')
71 c.user = User.get_or_404(user_id, pyramid_exc=True)
72 self._redirect_for_default_user(c.user.username)
73
74 c.active = 'auth_tokens'
75
76 c.lifetime_values = [
77 (str(-1), _('forever')),
78 (str(5), _('5 minutes')),
79 (str(60), _('1 hour')),
80 (str(60 * 24), _('1 day')),
81 (str(60 * 24 * 30), _('1 month')),
82 ]
83 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
84 c.role_values = [
85 (x, AuthTokenModel.cls._get_role_name(x))
86 for x in AuthTokenModel.cls.ROLES]
87 c.role_options = [(c.role_values, _("Role"))]
88 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
89 c.user.user_id, show_expired=True)
90 return self._get_template_context(c)
91
92 def maybe_attach_token_scope(self, token):
93 # implemented in EE edition
94 pass
95
96 @LoginRequired()
97 @HasPermissionAllDecorator('hg.admin')
98 @CSRFRequired()
99 @view_config(
100 route_name='edit_user_auth_tokens_add', request_method='POST')
101 def auth_tokens_add(self):
102 _ = self.request.translate
103 c = self.load_default_context()
104
105 user_id = self.request.matchdict.get('user_id')
106 c.user = User.get_or_404(user_id, pyramid_exc=True)
107 self._redirect_for_default_user(c.user.username)
108
109 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
110 description = self.request.POST.get('description')
111 role = self.request.POST.get('role')
112
113 token = AuthTokenModel().create(
114 c.user.user_id, description, lifetime, role)
115 self.maybe_attach_token_scope(token)
116 Session().commit()
117
118 h.flash(_("Auth token successfully created"), category='success')
119 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
120
121 @LoginRequired()
122 @HasPermissionAllDecorator('hg.admin')
123 @CSRFRequired()
124 @view_config(
125 route_name='edit_user_auth_tokens_delete', request_method='POST')
126 def auth_tokens_delete(self):
127 _ = self.request.translate
128 c = self.load_default_context()
129
130 user_id = self.request.matchdict.get('user_id')
131 c.user = User.get_or_404(user_id, pyramid_exc=True)
132 self._redirect_for_default_user(c.user.username)
133
134 del_auth_token = self.request.POST.get('del_auth_token')
135
136 if del_auth_token:
137 AuthTokenModel().delete(del_auth_token, c.user.user_id)
138 Session().commit()
139 h.flash(_("Auth token successfully deleted"), category='success')
140
141 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
@@ -53,5 +53,16 b' def includeme(config):'
53 name='admin_settings_sessions_cleanup',
53 name='admin_settings_sessions_cleanup',
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
54 pattern=ADMIN_PREFIX + '/settings/sessions/cleanup')
55
55
56 # user auth tokens
57 config.add_route(
58 name='edit_user_auth_tokens',
59 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens')
60 config.add_route(
61 name='edit_user_auth_tokens_add',
62 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/new')
63 config.add_route(
64 name='edit_user_auth_tokens_delete',
65 pattern=ADMIN_PREFIX + '/users/{user_id:\d+}/edit/auth_tokens/delete')
66
56 # Scan module for configuration decorators.
67 # Scan module for configuration decorators.
57 config.scan()
68 config.scan()
@@ -315,13 +315,6 b' def make_map(config):'
315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
315 m.connect('edit_user_advanced', '/users/{user_id}/edit/advanced',
316 action='update_advanced', conditions={'method': ['PUT']})
316 action='update_advanced', conditions={'method': ['PUT']})
317
317
318 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
319 action='edit_auth_tokens', conditions={'method': ['GET']})
320 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
321 action='add_auth_token', conditions={'method': ['PUT']})
322 m.connect('edit_user_auth_tokens', '/users/{user_id}/edit/auth_tokens',
323 action='delete_auth_token', conditions={'method': ['DELETE']})
324
325 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
318 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
326 action='edit_global_perms', conditions={'method': ['GET']})
319 action='edit_global_perms', conditions={'method': ['GET']})
327 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
320 m.connect('edit_user_global_perms', '/users/{user_id}/edit/global_permissions',
@@ -452,70 +452,6 b' class UsersController(BaseController):'
452 force_defaults=False)
452 force_defaults=False)
453
453
454 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
455 def edit_auth_tokens(self, user_id):
456 user_id = safe_int(user_id)
457 c.user = User.get_or_404(user_id)
458 if c.user.username == User.DEFAULT_USER:
459 h.flash(_("You can't edit this user"), category='warning')
460 return redirect(url('users'))
461
462 c.active = 'auth_tokens'
463 show_expired = True
464 c.lifetime_values = [
465 (str(-1), _('forever')),
466 (str(5), _('5 minutes')),
467 (str(60), _('1 hour')),
468 (str(60 * 24), _('1 day')),
469 (str(60 * 24 * 30), _('1 month')),
470 ]
471 c.lifetime_options = [(c.lifetime_values, _("Lifetime"))]
472 c.role_values = [(x, AuthTokenModel.cls._get_role_name(x))
473 for x in AuthTokenModel.cls.ROLES]
474 c.role_options = [(c.role_values, _("Role"))]
475 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
476 c.user.user_id, show_expired=show_expired)
477 defaults = c.user.get_dict()
478 return htmlfill.render(
479 render('admin/users/user_edit.mako'),
480 defaults=defaults,
481 encoding="UTF-8",
482 force_defaults=False)
483
484 @HasPermissionAllDecorator('hg.admin')
485 @auth.CSRFRequired()
486 def add_auth_token(self, user_id):
487 user_id = safe_int(user_id)
488 c.user = User.get_or_404(user_id)
489 if c.user.username == User.DEFAULT_USER:
490 h.flash(_("You can't edit this user"), category='warning')
491 return redirect(url('users'))
492
493 lifetime = safe_int(request.POST.get('lifetime'), -1)
494 description = request.POST.get('description')
495 role = request.POST.get('role')
496 AuthTokenModel().create(c.user.user_id, description, lifetime, role)
497 Session().commit()
498 h.flash(_("Auth token successfully created"), category='success')
499 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
500
501 @HasPermissionAllDecorator('hg.admin')
502 @auth.CSRFRequired()
503 def delete_auth_token(self, user_id):
504 user_id = safe_int(user_id)
505 c.user = User.get_or_404(user_id)
506 if c.user.username == User.DEFAULT_USER:
507 h.flash(_("You can't edit this user"), category='warning')
508 return redirect(url('users'))
509
510 del_auth_token = request.POST.get('del_auth_token')
511 if del_auth_token:
512 AuthTokenModel().delete(del_auth_token, c.user.user_id)
513 Session().commit()
514 h.flash(_("Auth token successfully deleted"), category='success')
515
516 return redirect(url('edit_user_auth_tokens', user_id=c.user.user_id))
517
518 @HasPermissionAllDecorator('hg.admin')
519 def edit_global_perms(self, user_id):
455 def edit_global_perms(self, user_id):
520 user_id = safe_int(user_id)
456 user_id = safe_int(user_id)
521 c.user = User.get_or_404(user_id)
457 c.user = User.get_or_404(user_id)
@@ -3,20 +3,21 b''
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <p>
7 <p>
7 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 </p>
10 </p>
10 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
12 <tr>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
19 </tr>
11 %if c.user_auth_tokens:
20 %if c.user_auth_tokens:
12 <tr>
13 <th>${_('Token')}</th>
14 <th>${_('Scope')}</th>
15 <th>${_('Description')}</th>
16 <th>${_('Role')}</th>
17 <th>${_('Expiration')}</th>
18 <th>${_('Action')}</th>
19 </tr>
20 %for auth_token in c.user_auth_tokens:
21 %for auth_token in c.user_auth_tokens:
21 <tr class="${'expired' if auth_token.expired else ''}">
22 <tr class="${'expired' if auth_token.expired else ''}">
22 <td class="truncate-wrap td-authtoken">
23 <td class="truncate-wrap td-authtoken">
@@ -52,9 +53,10 b''
52 </tr>
53 </tr>
53 %endfor
54 %endfor
54 %else:
55 %else:
55 <tr><td><div class="ip">${_('No additional auth token specified')}</div></td></tr>
56 <tr><td><div class="ip">${_('No additional auth tokens specified')}</div></td></tr>
56 %endif
57 %endif
57 </table>
58 </table>
59 </div>
58
60
59 <div class="user_auth_tokens">
61 <div class="user_auth_tokens">
60 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
62 ${h.secure_form(h.route_path('my_account_auth_tokens_add'), method='post')}
@@ -66,7 +68,7 b''
66 <label for="new_email">${_('New authentication token')}:</label>
68 <label for="new_email">${_('New authentication token')}:</label>
67 </div>
69 </div>
68 <div class="input">
70 <div class="input">
69 ${h.text('description', placeholder=_('Description'))}
71 ${h.text('description', class_='medium', placeholder=_('Description'))}
70 ${h.select('lifetime', '', c.lifetime_options)}
72 ${h.select('lifetime', '', c.lifetime_options)}
71 ${h.select('role', '', c.role_options)}
73 ${h.select('role', '', c.role_options)}
72
74
@@ -76,9 +78,9 b''
76 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
78 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
77 % endif
79 % endif
78 </div>
80 </div>
79 <p class="help-block">
81 <p class="help-block">
80 ${_('Repository scope works only with tokens with VCS type.')}
82 ${_('Repository scope works only with tokens with VCS type.')}
81 </p>
83 </p>
82 </div>
84 </div>
83 <div class="buttons">
85 <div class="buttons">
84 ${h.submit('save',_('Add'),class_="btn")}
86 ${h.submit('save',_('Add'),class_="btn")}
@@ -31,7 +31,7 b''
31 <div class="sidebar">
31 <div class="sidebar">
32 <ul class="nav nav-pills nav-stacked">
32 <ul class="nav nav-pills nav-stacked">
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
33 <li class="${'active' if c.active=='profile' else ''}"><a href="${h.url('edit_user', user_id=c.user.user_id)}">${_('User Profile')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.url('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
34 <li class="${'active' if c.active=='auth_tokens' else ''}"><a href="${h.route_path('edit_user_auth_tokens', user_id=c.user.user_id)}">${_('Auth tokens')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
35 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.url('edit_user_advanced', user_id=c.user.user_id)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
36 <li class="${'active' if c.active=='global_perms' else ''}"><a href="${h.url('edit_user_global_perms', user_id=c.user.user_id)}">${_('Global permissions')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
37 <li class="${'active' if c.active=='perms_summary' else ''}"><a href="${h.url('edit_user_perms_summary', user_id=c.user.user_id)}">${_('Permissions summary')}</a></li>
@@ -1,13 +1,12 b''
1 <div class="panel panel-default">
1 <div class="panel panel-default">
2 <div class="panel-heading">
2 <div class="panel-heading">
3 <h3 class="panel-title">${_('Authentication Access Tokens')}</h3>
3 <h3 class="panel-title">${_('Authentication Tokens')}</h3>
4 </div>
4 </div>
5 <div class="panel-body">
5 <div class="panel-body">
6 <div class="apikeys_wrap">
6 <div class="apikeys_wrap">
7 <p>
7 <p>
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
8 ${_('Each token can have a role. Token with a role can be used only in given context, '
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
9 'e.g. VCS tokens can be used together with the authtoken auth plugin for git/hg/svn operations only.')}
10 ${_('Additionally scope for VCS type token can narrow the use to chosen repository.')}
11 </p>
10 </p>
12 <table class="rctable auth_tokens">
11 <table class="rctable auth_tokens">
13 <tr>
12 <tr>
@@ -25,7 +24,7 b''
25 <td class="td">${auth_token.scope_humanized}</td>
24 <td class="td">${auth_token.scope_humanized}</td>
26 <td class="td-wrap">${auth_token.description}</td>
25 <td class="td-wrap">${auth_token.description}</td>
27 <td class="td-tags">
26 <td class="td-tags">
28 <span class="tag">${auth_token.role_humanized}</span>
27 <span class="tag disabled">${auth_token.role_humanized}</span>
29 </td>
28 </td>
30 <td class="td-exp">
29 <td class="td-exp">
31 %if auth_token.expires == -1:
30 %if auth_token.expires == -1:
@@ -38,8 +37,8 b''
38 %endif
37 %endif
39 %endif
38 %endif
40 </td>
39 </td>
41 <td>
40 <td class="td-action">
42 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id),method='delete')}
41 ${h.secure_form(h.route_path('edit_user_auth_tokens_delete', user_id=c.user.user_id), method='post')}
43 ${h.hidden('del_auth_token',auth_token.api_key)}
42 ${h.hidden('del_auth_token',auth_token.api_key)}
44 <button class="btn btn-link btn-danger" type="submit"
43 <button class="btn btn-link btn-danger" type="submit"
45 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
44 onclick="return confirm('${_('Confirm to remove this auth token: %s') % auth_token.api_key}');">
@@ -56,7 +55,7 b''
56 </div>
55 </div>
57
56
58 <div class="user_auth_tokens">
57 <div class="user_auth_tokens">
59 ${h.secure_form(url('edit_user_auth_tokens', user_id=c.user.user_id), method='put')}
58 ${h.secure_form(h.route_path('edit_user_auth_tokens_add', user_id=c.user.user_id), method='post')}
60 <div class="form form-vertical">
59 <div class="form form-vertical">
61 <!-- fields -->
60 <!-- fields -->
62 <div class="fields">
61 <div class="fields">
@@ -68,11 +67,20 b''
68 ${h.text('description', class_='medium', placeholder=_('Description'))}
67 ${h.text('description', class_='medium', placeholder=_('Description'))}
69 ${h.select('lifetime', '', c.lifetime_options)}
68 ${h.select('lifetime', '', c.lifetime_options)}
70 ${h.select('role', '', c.role_options)}
69 ${h.select('role', '', c.role_options)}
70
71 % if c.allow_scoped_tokens:
72 ${h.hidden('scope_repo_id')}
73 % else:
74 ${h.select('scope_repo_id_disabled', '', ['Scopes available in EE edition'], disabled='disabled')}
75 % endif
71 </div>
76 </div>
77 <p class="help-block">
78 ${_('Repository scope works only with tokens with VCS type.')}
79 </p>
72 </div>
80 </div>
73 <div class="buttons">
81 <div class="buttons">
74 ${h.submit('save',_('Add'),class_="btn btn-small")}
82 ${h.submit('save',_('Add'),class_="btn")}
75 ${h.reset('reset',_('Reset'),class_="btn btn-small")}
83 ${h.reset('reset',_('Reset'),class_="btn")}
76 </div>
84 </div>
77 </div>
85 </div>
78 </div>
86 </div>
@@ -82,16 +90,68 b''
82 </div>
90 </div>
83
91
84 <script>
92 <script>
85 $(document).ready(function(){
93
86 $("#lifetime").select2({
94 $(document).ready(function(){
87 'containerCssClass': "drop-menu",
95 var select2Options = {
88 'dropdownCssClass': "drop-menu-dropdown",
96 'containerCssClass': "drop-menu",
89 'dropdownAutoWidth': true
97 'dropdownCssClass': "drop-menu-dropdown",
90 });
98 'dropdownAutoWidth': true
91 $("#role").select2({
99 };
92 'containerCssClass': "drop-menu",
100 $("#lifetime").select2(select2Options);
93 'dropdownCssClass': "drop-menu-dropdown",
101 $("#role").select2(select2Options);
94 'dropdownAutoWidth': true
102
95 });
103 var repoFilter = function(data) {
104 var results = [];
105
106 if (!data.results[0]) {
107 return data
108 }
109
110 $.each(data.results[0].children, function() {
111 // replace name to ID for submision
112 this.id = this.obj.repo_id;
113 results.push(this);
114 });
115
116 data.results[0].children = results;
117 return data;
118 };
119
120 $("#scope_repo_id_disabled").select2(select2Options);
121
122 $("#scope_repo_id").select2({
123 cachedDataSource: {},
124 minimumInputLength: 2,
125 placeholder: "${_('repository scope')}",
126 dropdownAutoWidth: true,
127 containerCssClass: "drop-menu",
128 dropdownCssClass: "drop-menu-dropdown",
129 formatResult: formatResult,
130 query: $.debounce(250, function(query){
131 self = this;
132 var cacheKey = query.term;
133 var cachedData = self.cachedDataSource[cacheKey];
134
135 if (cachedData) {
136 query.callback({results: cachedData.results});
137 } else {
138 $.ajax({
139 url: "${h.url('repo_list_data')}",
140 data: {'query': query.term},
141 dataType: 'json',
142 type: 'GET',
143 success: function(data) {
144 data = repoFilter(data);
145 self.cachedDataSource[cacheKey] = data;
146 query.callback({results: data.results});
147 },
148 error: function(data, textStatus, errorThrown) {
149 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
150 }
151 })
152 }
96 })
153 })
154 });
155
156 });
97 </script>
157 </script>
General Comments 0
You need to be logged in to leave comments. Login now