##// END OF EJS Templates
ssh-keys: allow generation of legacy keys for older systems and windows.
marcink -
r4238:3d7adf6c stable
parent child Browse files
Show More
@@ -1,1333 +1,1336 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import datetime
22 import datetime
23 import formencode
23 import formencode
24 import formencode.htmlfill
24 import formencode.htmlfill
25
25
26 from pyramid.httpexceptions import HTTPFound
26 from pyramid.httpexceptions import HTTPFound
27 from pyramid.view import view_config
27 from pyramid.view import view_config
28 from pyramid.renderers import render
28 from pyramid.renderers import render
29 from pyramid.response import Response
29 from pyramid.response import Response
30
30
31 from rhodecode import events
31 from rhodecode import events
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
32 from rhodecode.apps._base import BaseAppView, DataGridAppView, UserAppView
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
33 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
34 from rhodecode.authentication.base import get_authn_registry, RhodeCodeExternalAuthPlugin
35 from rhodecode.authentication.plugins import auth_rhodecode
35 from rhodecode.authentication.plugins import auth_rhodecode
36 from rhodecode.events import trigger
36 from rhodecode.events import trigger
37 from rhodecode.model.db import true
37 from rhodecode.model.db import true
38
38
39 from rhodecode.lib import audit_logger, rc_cache
39 from rhodecode.lib import audit_logger, rc_cache
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
41 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
42 UserOwnsUserGroupsException, DefaultUserException)
42 UserOwnsUserGroupsException, DefaultUserException)
43 from rhodecode.lib.ext_json import json
43 from rhodecode.lib.ext_json import json
44 from rhodecode.lib.auth import (
44 from rhodecode.lib.auth import (
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
45 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
46 from rhodecode.lib import helpers as h
46 from rhodecode.lib import helpers as h
47 from rhodecode.lib.helpers import SqlPage
47 from rhodecode.lib.helpers import SqlPage
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
48 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
49 from rhodecode.model.auth_token import AuthTokenModel
49 from rhodecode.model.auth_token import AuthTokenModel
50 from rhodecode.model.forms import (
50 from rhodecode.model.forms import (
51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
51 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
52 UserExtraEmailForm, UserExtraIpForm)
52 UserExtraEmailForm, UserExtraIpForm)
53 from rhodecode.model.permission import PermissionModel
53 from rhodecode.model.permission import PermissionModel
54 from rhodecode.model.repo_group import RepoGroupModel
54 from rhodecode.model.repo_group import RepoGroupModel
55 from rhodecode.model.ssh_key import SshKeyModel
55 from rhodecode.model.ssh_key import SshKeyModel
56 from rhodecode.model.user import UserModel
56 from rhodecode.model.user import UserModel
57 from rhodecode.model.user_group import UserGroupModel
57 from rhodecode.model.user_group import UserGroupModel
58 from rhodecode.model.db import (
58 from rhodecode.model.db import (
59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
59 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
60 UserApiKeys, UserSshKeys, RepoGroup)
60 UserApiKeys, UserSshKeys, RepoGroup)
61 from rhodecode.model.meta import Session
61 from rhodecode.model.meta import Session
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 class AdminUsersView(BaseAppView, DataGridAppView):
66 class AdminUsersView(BaseAppView, DataGridAppView):
67
67
68 def load_default_context(self):
68 def load_default_context(self):
69 c = self._get_local_tmpl_context()
69 c = self._get_local_tmpl_context()
70 return c
70 return c
71
71
72 @LoginRequired()
72 @LoginRequired()
73 @HasPermissionAllDecorator('hg.admin')
73 @HasPermissionAllDecorator('hg.admin')
74 @view_config(
74 @view_config(
75 route_name='users', request_method='GET',
75 route_name='users', request_method='GET',
76 renderer='rhodecode:templates/admin/users/users.mako')
76 renderer='rhodecode:templates/admin/users/users.mako')
77 def users_list(self):
77 def users_list(self):
78 c = self.load_default_context()
78 c = self.load_default_context()
79 return self._get_template_context(c)
79 return self._get_template_context(c)
80
80
81 @LoginRequired()
81 @LoginRequired()
82 @HasPermissionAllDecorator('hg.admin')
82 @HasPermissionAllDecorator('hg.admin')
83 @view_config(
83 @view_config(
84 # renderer defined below
84 # renderer defined below
85 route_name='users_data', request_method='GET',
85 route_name='users_data', request_method='GET',
86 renderer='json_ext', xhr=True)
86 renderer='json_ext', xhr=True)
87 def users_list_data(self):
87 def users_list_data(self):
88 self.load_default_context()
88 self.load_default_context()
89 column_map = {
89 column_map = {
90 'first_name': 'name',
90 'first_name': 'name',
91 'last_name': 'lastname',
91 'last_name': 'lastname',
92 }
92 }
93 draw, start, limit = self._extract_chunk(self.request)
93 draw, start, limit = self._extract_chunk(self.request)
94 search_q, order_by, order_dir = self._extract_ordering(
94 search_q, order_by, order_dir = self._extract_ordering(
95 self.request, column_map=column_map)
95 self.request, column_map=column_map)
96 _render = self.request.get_partial_renderer(
96 _render = self.request.get_partial_renderer(
97 'rhodecode:templates/data_table/_dt_elements.mako')
97 'rhodecode:templates/data_table/_dt_elements.mako')
98
98
99 def user_actions(user_id, username):
99 def user_actions(user_id, username):
100 return _render("user_actions", user_id, username)
100 return _render("user_actions", user_id, username)
101
101
102 users_data_total_count = User.query()\
102 users_data_total_count = User.query()\
103 .filter(User.username != User.DEFAULT_USER) \
103 .filter(User.username != User.DEFAULT_USER) \
104 .count()
104 .count()
105
105
106 users_data_total_inactive_count = User.query()\
106 users_data_total_inactive_count = User.query()\
107 .filter(User.username != User.DEFAULT_USER) \
107 .filter(User.username != User.DEFAULT_USER) \
108 .filter(User.active != true())\
108 .filter(User.active != true())\
109 .count()
109 .count()
110
110
111 # json generate
111 # json generate
112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
112 base_q = User.query().filter(User.username != User.DEFAULT_USER)
113 base_inactive_q = base_q.filter(User.active != true())
113 base_inactive_q = base_q.filter(User.active != true())
114
114
115 if search_q:
115 if search_q:
116 like_expression = u'%{}%'.format(safe_unicode(search_q))
116 like_expression = u'%{}%'.format(safe_unicode(search_q))
117 base_q = base_q.filter(or_(
117 base_q = base_q.filter(or_(
118 User.username.ilike(like_expression),
118 User.username.ilike(like_expression),
119 User._email.ilike(like_expression),
119 User._email.ilike(like_expression),
120 User.name.ilike(like_expression),
120 User.name.ilike(like_expression),
121 User.lastname.ilike(like_expression),
121 User.lastname.ilike(like_expression),
122 ))
122 ))
123 base_inactive_q = base_q.filter(User.active != true())
123 base_inactive_q = base_q.filter(User.active != true())
124
124
125 users_data_total_filtered_count = base_q.count()
125 users_data_total_filtered_count = base_q.count()
126 users_data_total_filtered_inactive_count = base_inactive_q.count()
126 users_data_total_filtered_inactive_count = base_inactive_q.count()
127
127
128 sort_col = getattr(User, order_by, None)
128 sort_col = getattr(User, order_by, None)
129 if sort_col:
129 if sort_col:
130 if order_dir == 'asc':
130 if order_dir == 'asc':
131 # handle null values properly to order by NULL last
131 # handle null values properly to order by NULL last
132 if order_by in ['last_activity']:
132 if order_by in ['last_activity']:
133 sort_col = coalesce(sort_col, datetime.date.max)
133 sort_col = coalesce(sort_col, datetime.date.max)
134 sort_col = sort_col.asc()
134 sort_col = sort_col.asc()
135 else:
135 else:
136 # handle null values properly to order by NULL last
136 # handle null values properly to order by NULL last
137 if order_by in ['last_activity']:
137 if order_by in ['last_activity']:
138 sort_col = coalesce(sort_col, datetime.date.min)
138 sort_col = coalesce(sort_col, datetime.date.min)
139 sort_col = sort_col.desc()
139 sort_col = sort_col.desc()
140
140
141 base_q = base_q.order_by(sort_col)
141 base_q = base_q.order_by(sort_col)
142 base_q = base_q.offset(start).limit(limit)
142 base_q = base_q.offset(start).limit(limit)
143
143
144 users_list = base_q.all()
144 users_list = base_q.all()
145
145
146 users_data = []
146 users_data = []
147 for user in users_list:
147 for user in users_list:
148 users_data.append({
148 users_data.append({
149 "username": h.gravatar_with_user(self.request, user.username),
149 "username": h.gravatar_with_user(self.request, user.username),
150 "email": user.email,
150 "email": user.email,
151 "first_name": user.first_name,
151 "first_name": user.first_name,
152 "last_name": user.last_name,
152 "last_name": user.last_name,
153 "last_login": h.format_date(user.last_login),
153 "last_login": h.format_date(user.last_login),
154 "last_activity": h.format_date(user.last_activity),
154 "last_activity": h.format_date(user.last_activity),
155 "active": h.bool2icon(user.active),
155 "active": h.bool2icon(user.active),
156 "active_raw": user.active,
156 "active_raw": user.active,
157 "admin": h.bool2icon(user.admin),
157 "admin": h.bool2icon(user.admin),
158 "extern_type": user.extern_type,
158 "extern_type": user.extern_type,
159 "extern_name": user.extern_name,
159 "extern_name": user.extern_name,
160 "action": user_actions(user.user_id, user.username),
160 "action": user_actions(user.user_id, user.username),
161 })
161 })
162 data = ({
162 data = ({
163 'draw': draw,
163 'draw': draw,
164 'data': users_data,
164 'data': users_data,
165 'recordsTotal': users_data_total_count,
165 'recordsTotal': users_data_total_count,
166 'recordsFiltered': users_data_total_filtered_count,
166 'recordsFiltered': users_data_total_filtered_count,
167 'recordsTotalInactive': users_data_total_inactive_count,
167 'recordsTotalInactive': users_data_total_inactive_count,
168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
168 'recordsFilteredInactive': users_data_total_filtered_inactive_count
169 })
169 })
170
170
171 return data
171 return data
172
172
173 def _set_personal_repo_group_template_vars(self, c_obj):
173 def _set_personal_repo_group_template_vars(self, c_obj):
174 DummyUser = AttributeDict({
174 DummyUser = AttributeDict({
175 'username': '${username}',
175 'username': '${username}',
176 'user_id': '${user_id}',
176 'user_id': '${user_id}',
177 })
177 })
178 c_obj.default_create_repo_group = RepoGroupModel() \
178 c_obj.default_create_repo_group = RepoGroupModel() \
179 .get_default_create_personal_repo_group()
179 .get_default_create_personal_repo_group()
180 c_obj.personal_repo_group_name = RepoGroupModel() \
180 c_obj.personal_repo_group_name = RepoGroupModel() \
181 .get_personal_group_name(DummyUser)
181 .get_personal_group_name(DummyUser)
182
182
183 @LoginRequired()
183 @LoginRequired()
184 @HasPermissionAllDecorator('hg.admin')
184 @HasPermissionAllDecorator('hg.admin')
185 @view_config(
185 @view_config(
186 route_name='users_new', request_method='GET',
186 route_name='users_new', request_method='GET',
187 renderer='rhodecode:templates/admin/users/user_add.mako')
187 renderer='rhodecode:templates/admin/users/user_add.mako')
188 def users_new(self):
188 def users_new(self):
189 _ = self.request.translate
189 _ = self.request.translate
190 c = self.load_default_context()
190 c = self.load_default_context()
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
191 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
192 self._set_personal_repo_group_template_vars(c)
192 self._set_personal_repo_group_template_vars(c)
193 return self._get_template_context(c)
193 return self._get_template_context(c)
194
194
195 @LoginRequired()
195 @LoginRequired()
196 @HasPermissionAllDecorator('hg.admin')
196 @HasPermissionAllDecorator('hg.admin')
197 @CSRFRequired()
197 @CSRFRequired()
198 @view_config(
198 @view_config(
199 route_name='users_create', request_method='POST',
199 route_name='users_create', request_method='POST',
200 renderer='rhodecode:templates/admin/users/user_add.mako')
200 renderer='rhodecode:templates/admin/users/user_add.mako')
201 def users_create(self):
201 def users_create(self):
202 _ = self.request.translate
202 _ = self.request.translate
203 c = self.load_default_context()
203 c = self.load_default_context()
204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
204 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
205 user_model = UserModel()
205 user_model = UserModel()
206 user_form = UserForm(self.request.translate)()
206 user_form = UserForm(self.request.translate)()
207 try:
207 try:
208 form_result = user_form.to_python(dict(self.request.POST))
208 form_result = user_form.to_python(dict(self.request.POST))
209 user = user_model.create(form_result)
209 user = user_model.create(form_result)
210 Session().flush()
210 Session().flush()
211 creation_data = user.get_api_data()
211 creation_data = user.get_api_data()
212 username = form_result['username']
212 username = form_result['username']
213
213
214 audit_logger.store_web(
214 audit_logger.store_web(
215 'user.create', action_data={'data': creation_data},
215 'user.create', action_data={'data': creation_data},
216 user=c.rhodecode_user)
216 user=c.rhodecode_user)
217
217
218 user_link = h.link_to(
218 user_link = h.link_to(
219 h.escape(username),
219 h.escape(username),
220 h.route_path('user_edit', user_id=user.user_id))
220 h.route_path('user_edit', user_id=user.user_id))
221 h.flash(h.literal(_('Created user %(user_link)s')
221 h.flash(h.literal(_('Created user %(user_link)s')
222 % {'user_link': user_link}), category='success')
222 % {'user_link': user_link}), category='success')
223 Session().commit()
223 Session().commit()
224 except formencode.Invalid as errors:
224 except formencode.Invalid as errors:
225 self._set_personal_repo_group_template_vars(c)
225 self._set_personal_repo_group_template_vars(c)
226 data = render(
226 data = render(
227 'rhodecode:templates/admin/users/user_add.mako',
227 'rhodecode:templates/admin/users/user_add.mako',
228 self._get_template_context(c), self.request)
228 self._get_template_context(c), self.request)
229 html = formencode.htmlfill.render(
229 html = formencode.htmlfill.render(
230 data,
230 data,
231 defaults=errors.value,
231 defaults=errors.value,
232 errors=errors.error_dict or {},
232 errors=errors.error_dict or {},
233 prefix_error=False,
233 prefix_error=False,
234 encoding="UTF-8",
234 encoding="UTF-8",
235 force_defaults=False
235 force_defaults=False
236 )
236 )
237 return Response(html)
237 return Response(html)
238 except UserCreationError as e:
238 except UserCreationError as e:
239 h.flash(e, 'error')
239 h.flash(e, 'error')
240 except Exception:
240 except Exception:
241 log.exception("Exception creation of user")
241 log.exception("Exception creation of user")
242 h.flash(_('Error occurred during creation of user %s')
242 h.flash(_('Error occurred during creation of user %s')
243 % self.request.POST.get('username'), category='error')
243 % self.request.POST.get('username'), category='error')
244 raise HTTPFound(h.route_path('users'))
244 raise HTTPFound(h.route_path('users'))
245
245
246
246
247 class UsersView(UserAppView):
247 class UsersView(UserAppView):
248 ALLOW_SCOPED_TOKENS = False
248 ALLOW_SCOPED_TOKENS = False
249 """
249 """
250 This view has alternative version inside EE, if modified please take a look
250 This view has alternative version inside EE, if modified please take a look
251 in there as well.
251 in there as well.
252 """
252 """
253
253
254 def get_auth_plugins(self):
254 def get_auth_plugins(self):
255 valid_plugins = []
255 valid_plugins = []
256 authn_registry = get_authn_registry(self.request.registry)
256 authn_registry = get_authn_registry(self.request.registry)
257 for plugin in authn_registry.get_plugins_for_authentication():
257 for plugin in authn_registry.get_plugins_for_authentication():
258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
258 if isinstance(plugin, RhodeCodeExternalAuthPlugin):
259 valid_plugins.append(plugin)
259 valid_plugins.append(plugin)
260 elif plugin.name == 'rhodecode':
260 elif plugin.name == 'rhodecode':
261 valid_plugins.append(plugin)
261 valid_plugins.append(plugin)
262
262
263 # extend our choices if user has set a bound plugin which isn't enabled at the
263 # extend our choices if user has set a bound plugin which isn't enabled at the
264 # moment
264 # moment
265 extern_type = self.db_user.extern_type
265 extern_type = self.db_user.extern_type
266 if extern_type not in [x.uid for x in valid_plugins]:
266 if extern_type not in [x.uid for x in valid_plugins]:
267 try:
267 try:
268 plugin = authn_registry.get_plugin_by_uid(extern_type)
268 plugin = authn_registry.get_plugin_by_uid(extern_type)
269 if plugin:
269 if plugin:
270 valid_plugins.append(plugin)
270 valid_plugins.append(plugin)
271
271
272 except Exception:
272 except Exception:
273 log.exception(
273 log.exception(
274 'Could not extend user plugins with `{}`'.format(extern_type))
274 'Could not extend user plugins with `{}`'.format(extern_type))
275 return valid_plugins
275 return valid_plugins
276
276
277 def load_default_context(self):
277 def load_default_context(self):
278 req = self.request
278 req = self.request
279
279
280 c = self._get_local_tmpl_context()
280 c = self._get_local_tmpl_context()
281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
281 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
282 c.allowed_languages = [
282 c.allowed_languages = [
283 ('en', 'English (en)'),
283 ('en', 'English (en)'),
284 ('de', 'German (de)'),
284 ('de', 'German (de)'),
285 ('fr', 'French (fr)'),
285 ('fr', 'French (fr)'),
286 ('it', 'Italian (it)'),
286 ('it', 'Italian (it)'),
287 ('ja', 'Japanese (ja)'),
287 ('ja', 'Japanese (ja)'),
288 ('pl', 'Polish (pl)'),
288 ('pl', 'Polish (pl)'),
289 ('pt', 'Portuguese (pt)'),
289 ('pt', 'Portuguese (pt)'),
290 ('ru', 'Russian (ru)'),
290 ('ru', 'Russian (ru)'),
291 ('zh', 'Chinese (zh)'),
291 ('zh', 'Chinese (zh)'),
292 ]
292 ]
293
293
294 c.allowed_extern_types = [
294 c.allowed_extern_types = [
295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
295 (x.uid, x.get_display_name()) for x in self.get_auth_plugins()
296 ]
296 ]
297
297
298 c.available_permissions = req.registry.settings['available_permissions']
298 c.available_permissions = req.registry.settings['available_permissions']
299 PermissionModel().set_global_permission_choices(
299 PermissionModel().set_global_permission_choices(
300 c, gettext_translator=req.translate)
300 c, gettext_translator=req.translate)
301
301
302 return c
302 return c
303
303
304 @LoginRequired()
304 @LoginRequired()
305 @HasPermissionAllDecorator('hg.admin')
305 @HasPermissionAllDecorator('hg.admin')
306 @CSRFRequired()
306 @CSRFRequired()
307 @view_config(
307 @view_config(
308 route_name='user_update', request_method='POST',
308 route_name='user_update', request_method='POST',
309 renderer='rhodecode:templates/admin/users/user_edit.mako')
309 renderer='rhodecode:templates/admin/users/user_edit.mako')
310 def user_update(self):
310 def user_update(self):
311 _ = self.request.translate
311 _ = self.request.translate
312 c = self.load_default_context()
312 c = self.load_default_context()
313
313
314 user_id = self.db_user_id
314 user_id = self.db_user_id
315 c.user = self.db_user
315 c.user = self.db_user
316
316
317 c.active = 'profile'
317 c.active = 'profile'
318 c.extern_type = c.user.extern_type
318 c.extern_type = c.user.extern_type
319 c.extern_name = c.user.extern_name
319 c.extern_name = c.user.extern_name
320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
320 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
321 available_languages = [x[0] for x in c.allowed_languages]
321 available_languages = [x[0] for x in c.allowed_languages]
322 _form = UserForm(self.request.translate, edit=True,
322 _form = UserForm(self.request.translate, edit=True,
323 available_languages=available_languages,
323 available_languages=available_languages,
324 old_data={'user_id': user_id,
324 old_data={'user_id': user_id,
325 'email': c.user.email})()
325 'email': c.user.email})()
326 form_result = {}
326 form_result = {}
327 old_values = c.user.get_api_data()
327 old_values = c.user.get_api_data()
328 try:
328 try:
329 form_result = _form.to_python(dict(self.request.POST))
329 form_result = _form.to_python(dict(self.request.POST))
330 skip_attrs = ['extern_name']
330 skip_attrs = ['extern_name']
331 # TODO: plugin should define if username can be updated
331 # TODO: plugin should define if username can be updated
332 if c.extern_type != "rhodecode":
332 if c.extern_type != "rhodecode":
333 # forbid updating username for external accounts
333 # forbid updating username for external accounts
334 skip_attrs.append('username')
334 skip_attrs.append('username')
335
335
336 UserModel().update_user(
336 UserModel().update_user(
337 user_id, skip_attrs=skip_attrs, **form_result)
337 user_id, skip_attrs=skip_attrs, **form_result)
338
338
339 audit_logger.store_web(
339 audit_logger.store_web(
340 'user.edit', action_data={'old_data': old_values},
340 'user.edit', action_data={'old_data': old_values},
341 user=c.rhodecode_user)
341 user=c.rhodecode_user)
342
342
343 Session().commit()
343 Session().commit()
344 h.flash(_('User updated successfully'), category='success')
344 h.flash(_('User updated successfully'), category='success')
345 except formencode.Invalid as errors:
345 except formencode.Invalid as errors:
346 data = render(
346 data = render(
347 'rhodecode:templates/admin/users/user_edit.mako',
347 'rhodecode:templates/admin/users/user_edit.mako',
348 self._get_template_context(c), self.request)
348 self._get_template_context(c), self.request)
349 html = formencode.htmlfill.render(
349 html = formencode.htmlfill.render(
350 data,
350 data,
351 defaults=errors.value,
351 defaults=errors.value,
352 errors=errors.error_dict or {},
352 errors=errors.error_dict or {},
353 prefix_error=False,
353 prefix_error=False,
354 encoding="UTF-8",
354 encoding="UTF-8",
355 force_defaults=False
355 force_defaults=False
356 )
356 )
357 return Response(html)
357 return Response(html)
358 except UserCreationError as e:
358 except UserCreationError as e:
359 h.flash(e, 'error')
359 h.flash(e, 'error')
360 except Exception:
360 except Exception:
361 log.exception("Exception updating user")
361 log.exception("Exception updating user")
362 h.flash(_('Error occurred during update of user %s')
362 h.flash(_('Error occurred during update of user %s')
363 % form_result.get('username'), category='error')
363 % form_result.get('username'), category='error')
364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
364 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
365
365
366 @LoginRequired()
366 @LoginRequired()
367 @HasPermissionAllDecorator('hg.admin')
367 @HasPermissionAllDecorator('hg.admin')
368 @CSRFRequired()
368 @CSRFRequired()
369 @view_config(
369 @view_config(
370 route_name='user_delete', request_method='POST',
370 route_name='user_delete', request_method='POST',
371 renderer='rhodecode:templates/admin/users/user_edit.mako')
371 renderer='rhodecode:templates/admin/users/user_edit.mako')
372 def user_delete(self):
372 def user_delete(self):
373 _ = self.request.translate
373 _ = self.request.translate
374 c = self.load_default_context()
374 c = self.load_default_context()
375 c.user = self.db_user
375 c.user = self.db_user
376
376
377 _repos = c.user.repositories
377 _repos = c.user.repositories
378 _repo_groups = c.user.repository_groups
378 _repo_groups = c.user.repository_groups
379 _user_groups = c.user.user_groups
379 _user_groups = c.user.user_groups
380 _artifacts = c.user.artifacts
380 _artifacts = c.user.artifacts
381
381
382 handle_repos = None
382 handle_repos = None
383 handle_repo_groups = None
383 handle_repo_groups = None
384 handle_user_groups = None
384 handle_user_groups = None
385 handle_artifacts = None
385 handle_artifacts = None
386
386
387 # calls for flash of handle based on handle case detach or delete
387 # calls for flash of handle based on handle case detach or delete
388 def set_handle_flash_repos():
388 def set_handle_flash_repos():
389 handle = handle_repos
389 handle = handle_repos
390 if handle == 'detach':
390 if handle == 'detach':
391 h.flash(_('Detached %s repositories') % len(_repos),
391 h.flash(_('Detached %s repositories') % len(_repos),
392 category='success')
392 category='success')
393 elif handle == 'delete':
393 elif handle == 'delete':
394 h.flash(_('Deleted %s repositories') % len(_repos),
394 h.flash(_('Deleted %s repositories') % len(_repos),
395 category='success')
395 category='success')
396
396
397 def set_handle_flash_repo_groups():
397 def set_handle_flash_repo_groups():
398 handle = handle_repo_groups
398 handle = handle_repo_groups
399 if handle == 'detach':
399 if handle == 'detach':
400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
400 h.flash(_('Detached %s repository groups') % len(_repo_groups),
401 category='success')
401 category='success')
402 elif handle == 'delete':
402 elif handle == 'delete':
403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
403 h.flash(_('Deleted %s repository groups') % len(_repo_groups),
404 category='success')
404 category='success')
405
405
406 def set_handle_flash_user_groups():
406 def set_handle_flash_user_groups():
407 handle = handle_user_groups
407 handle = handle_user_groups
408 if handle == 'detach':
408 if handle == 'detach':
409 h.flash(_('Detached %s user groups') % len(_user_groups),
409 h.flash(_('Detached %s user groups') % len(_user_groups),
410 category='success')
410 category='success')
411 elif handle == 'delete':
411 elif handle == 'delete':
412 h.flash(_('Deleted %s user groups') % len(_user_groups),
412 h.flash(_('Deleted %s user groups') % len(_user_groups),
413 category='success')
413 category='success')
414
414
415 def set_handle_flash_artifacts():
415 def set_handle_flash_artifacts():
416 handle = handle_artifacts
416 handle = handle_artifacts
417 if handle == 'detach':
417 if handle == 'detach':
418 h.flash(_('Detached %s artifacts') % len(_artifacts),
418 h.flash(_('Detached %s artifacts') % len(_artifacts),
419 category='success')
419 category='success')
420 elif handle == 'delete':
420 elif handle == 'delete':
421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
421 h.flash(_('Deleted %s artifacts') % len(_artifacts),
422 category='success')
422 category='success')
423
423
424 if _repos and self.request.POST.get('user_repos'):
424 if _repos and self.request.POST.get('user_repos'):
425 handle_repos = self.request.POST['user_repos']
425 handle_repos = self.request.POST['user_repos']
426
426
427 if _repo_groups and self.request.POST.get('user_repo_groups'):
427 if _repo_groups and self.request.POST.get('user_repo_groups'):
428 handle_repo_groups = self.request.POST['user_repo_groups']
428 handle_repo_groups = self.request.POST['user_repo_groups']
429
429
430 if _user_groups and self.request.POST.get('user_user_groups'):
430 if _user_groups and self.request.POST.get('user_user_groups'):
431 handle_user_groups = self.request.POST['user_user_groups']
431 handle_user_groups = self.request.POST['user_user_groups']
432
432
433 if _artifacts and self.request.POST.get('user_artifacts'):
433 if _artifacts and self.request.POST.get('user_artifacts'):
434 handle_artifacts = self.request.POST['user_artifacts']
434 handle_artifacts = self.request.POST['user_artifacts']
435
435
436 old_values = c.user.get_api_data()
436 old_values = c.user.get_api_data()
437
437
438 try:
438 try:
439 UserModel().delete(c.user, handle_repos=handle_repos,
439 UserModel().delete(c.user, handle_repos=handle_repos,
440 handle_repo_groups=handle_repo_groups,
440 handle_repo_groups=handle_repo_groups,
441 handle_user_groups=handle_user_groups,
441 handle_user_groups=handle_user_groups,
442 handle_artifacts=handle_artifacts)
442 handle_artifacts=handle_artifacts)
443
443
444 audit_logger.store_web(
444 audit_logger.store_web(
445 'user.delete', action_data={'old_data': old_values},
445 'user.delete', action_data={'old_data': old_values},
446 user=c.rhodecode_user)
446 user=c.rhodecode_user)
447
447
448 Session().commit()
448 Session().commit()
449 set_handle_flash_repos()
449 set_handle_flash_repos()
450 set_handle_flash_repo_groups()
450 set_handle_flash_repo_groups()
451 set_handle_flash_user_groups()
451 set_handle_flash_user_groups()
452 set_handle_flash_artifacts()
452 set_handle_flash_artifacts()
453 username = h.escape(old_values['username'])
453 username = h.escape(old_values['username'])
454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
454 h.flash(_('Successfully deleted user `{}`').format(username), category='success')
455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
455 except (UserOwnsReposException, UserOwnsRepoGroupsException,
456 UserOwnsUserGroupsException, DefaultUserException) as e:
456 UserOwnsUserGroupsException, DefaultUserException) as e:
457 h.flash(e, category='warning')
457 h.flash(e, category='warning')
458 except Exception:
458 except Exception:
459 log.exception("Exception during deletion of user")
459 log.exception("Exception during deletion of user")
460 h.flash(_('An error occurred during deletion of user'),
460 h.flash(_('An error occurred during deletion of user'),
461 category='error')
461 category='error')
462 raise HTTPFound(h.route_path('users'))
462 raise HTTPFound(h.route_path('users'))
463
463
464 @LoginRequired()
464 @LoginRequired()
465 @HasPermissionAllDecorator('hg.admin')
465 @HasPermissionAllDecorator('hg.admin')
466 @view_config(
466 @view_config(
467 route_name='user_edit', request_method='GET',
467 route_name='user_edit', request_method='GET',
468 renderer='rhodecode:templates/admin/users/user_edit.mako')
468 renderer='rhodecode:templates/admin/users/user_edit.mako')
469 def user_edit(self):
469 def user_edit(self):
470 _ = self.request.translate
470 _ = self.request.translate
471 c = self.load_default_context()
471 c = self.load_default_context()
472 c.user = self.db_user
472 c.user = self.db_user
473
473
474 c.active = 'profile'
474 c.active = 'profile'
475 c.extern_type = c.user.extern_type
475 c.extern_type = c.user.extern_type
476 c.extern_name = c.user.extern_name
476 c.extern_name = c.user.extern_name
477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
477 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
478
478
479 defaults = c.user.get_dict()
479 defaults = c.user.get_dict()
480 defaults.update({'language': c.user.user_data.get('language')})
480 defaults.update({'language': c.user.user_data.get('language')})
481
481
482 data = render(
482 data = render(
483 'rhodecode:templates/admin/users/user_edit.mako',
483 'rhodecode:templates/admin/users/user_edit.mako',
484 self._get_template_context(c), self.request)
484 self._get_template_context(c), self.request)
485 html = formencode.htmlfill.render(
485 html = formencode.htmlfill.render(
486 data,
486 data,
487 defaults=defaults,
487 defaults=defaults,
488 encoding="UTF-8",
488 encoding="UTF-8",
489 force_defaults=False
489 force_defaults=False
490 )
490 )
491 return Response(html)
491 return Response(html)
492
492
493 @LoginRequired()
493 @LoginRequired()
494 @HasPermissionAllDecorator('hg.admin')
494 @HasPermissionAllDecorator('hg.admin')
495 @view_config(
495 @view_config(
496 route_name='user_edit_advanced', request_method='GET',
496 route_name='user_edit_advanced', request_method='GET',
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
497 renderer='rhodecode:templates/admin/users/user_edit.mako')
498 def user_edit_advanced(self):
498 def user_edit_advanced(self):
499 _ = self.request.translate
499 _ = self.request.translate
500 c = self.load_default_context()
500 c = self.load_default_context()
501
501
502 user_id = self.db_user_id
502 user_id = self.db_user_id
503 c.user = self.db_user
503 c.user = self.db_user
504
504
505 c.active = 'advanced'
505 c.active = 'advanced'
506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
506 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
507 c.personal_repo_group_name = RepoGroupModel()\
507 c.personal_repo_group_name = RepoGroupModel()\
508 .get_personal_group_name(c.user)
508 .get_personal_group_name(c.user)
509
509
510 c.user_to_review_rules = sorted(
510 c.user_to_review_rules = sorted(
511 (x.user for x in c.user.user_review_rules),
511 (x.user for x in c.user.user_review_rules),
512 key=lambda u: u.username.lower())
512 key=lambda u: u.username.lower())
513
513
514 c.first_admin = User.get_first_super_admin()
514 c.first_admin = User.get_first_super_admin()
515 defaults = c.user.get_dict()
515 defaults = c.user.get_dict()
516
516
517 # Interim workaround if the user participated on any pull requests as a
517 # Interim workaround if the user participated on any pull requests as a
518 # reviewer.
518 # reviewer.
519 has_review = len(c.user.reviewer_pull_requests)
519 has_review = len(c.user.reviewer_pull_requests)
520 c.can_delete_user = not has_review
520 c.can_delete_user = not has_review
521 c.can_delete_user_message = ''
521 c.can_delete_user_message = ''
522 inactive_link = h.link_to(
522 inactive_link = h.link_to(
523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
523 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
524 if has_review == 1:
524 if has_review == 1:
525 c.can_delete_user_message = h.literal(_(
525 c.can_delete_user_message = h.literal(_(
526 'The user participates as reviewer in {} pull request and '
526 'The user participates as reviewer in {} pull request and '
527 'cannot be deleted. \nYou can set the user to '
527 'cannot be deleted. \nYou can set the user to '
528 '"{}" instead of deleting it.').format(
528 '"{}" instead of deleting it.').format(
529 has_review, inactive_link))
529 has_review, inactive_link))
530 elif has_review:
530 elif has_review:
531 c.can_delete_user_message = h.literal(_(
531 c.can_delete_user_message = h.literal(_(
532 'The user participates as reviewer in {} pull requests and '
532 'The user participates as reviewer in {} pull requests and '
533 'cannot be deleted. \nYou can set the user to '
533 'cannot be deleted. \nYou can set the user to '
534 '"{}" instead of deleting it.').format(
534 '"{}" instead of deleting it.').format(
535 has_review, inactive_link))
535 has_review, inactive_link))
536
536
537 data = render(
537 data = render(
538 'rhodecode:templates/admin/users/user_edit.mako',
538 'rhodecode:templates/admin/users/user_edit.mako',
539 self._get_template_context(c), self.request)
539 self._get_template_context(c), self.request)
540 html = formencode.htmlfill.render(
540 html = formencode.htmlfill.render(
541 data,
541 data,
542 defaults=defaults,
542 defaults=defaults,
543 encoding="UTF-8",
543 encoding="UTF-8",
544 force_defaults=False
544 force_defaults=False
545 )
545 )
546 return Response(html)
546 return Response(html)
547
547
548 @LoginRequired()
548 @LoginRequired()
549 @HasPermissionAllDecorator('hg.admin')
549 @HasPermissionAllDecorator('hg.admin')
550 @view_config(
550 @view_config(
551 route_name='user_edit_global_perms', request_method='GET',
551 route_name='user_edit_global_perms', request_method='GET',
552 renderer='rhodecode:templates/admin/users/user_edit.mako')
552 renderer='rhodecode:templates/admin/users/user_edit.mako')
553 def user_edit_global_perms(self):
553 def user_edit_global_perms(self):
554 _ = self.request.translate
554 _ = self.request.translate
555 c = self.load_default_context()
555 c = self.load_default_context()
556 c.user = self.db_user
556 c.user = self.db_user
557
557
558 c.active = 'global_perms'
558 c.active = 'global_perms'
559
559
560 c.default_user = User.get_default_user()
560 c.default_user = User.get_default_user()
561 defaults = c.user.get_dict()
561 defaults = c.user.get_dict()
562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
562 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
563 defaults.update(c.default_user.get_default_perms())
563 defaults.update(c.default_user.get_default_perms())
564 defaults.update(c.user.get_default_perms())
564 defaults.update(c.user.get_default_perms())
565
565
566 data = render(
566 data = render(
567 'rhodecode:templates/admin/users/user_edit.mako',
567 'rhodecode:templates/admin/users/user_edit.mako',
568 self._get_template_context(c), self.request)
568 self._get_template_context(c), self.request)
569 html = formencode.htmlfill.render(
569 html = formencode.htmlfill.render(
570 data,
570 data,
571 defaults=defaults,
571 defaults=defaults,
572 encoding="UTF-8",
572 encoding="UTF-8",
573 force_defaults=False
573 force_defaults=False
574 )
574 )
575 return Response(html)
575 return Response(html)
576
576
577 @LoginRequired()
577 @LoginRequired()
578 @HasPermissionAllDecorator('hg.admin')
578 @HasPermissionAllDecorator('hg.admin')
579 @CSRFRequired()
579 @CSRFRequired()
580 @view_config(
580 @view_config(
581 route_name='user_edit_global_perms_update', request_method='POST',
581 route_name='user_edit_global_perms_update', request_method='POST',
582 renderer='rhodecode:templates/admin/users/user_edit.mako')
582 renderer='rhodecode:templates/admin/users/user_edit.mako')
583 def user_edit_global_perms_update(self):
583 def user_edit_global_perms_update(self):
584 _ = self.request.translate
584 _ = self.request.translate
585 c = self.load_default_context()
585 c = self.load_default_context()
586
586
587 user_id = self.db_user_id
587 user_id = self.db_user_id
588 c.user = self.db_user
588 c.user = self.db_user
589
589
590 c.active = 'global_perms'
590 c.active = 'global_perms'
591 try:
591 try:
592 # first stage that verifies the checkbox
592 # first stage that verifies the checkbox
593 _form = UserIndividualPermissionsForm(self.request.translate)
593 _form = UserIndividualPermissionsForm(self.request.translate)
594 form_result = _form.to_python(dict(self.request.POST))
594 form_result = _form.to_python(dict(self.request.POST))
595 inherit_perms = form_result['inherit_default_permissions']
595 inherit_perms = form_result['inherit_default_permissions']
596 c.user.inherit_default_permissions = inherit_perms
596 c.user.inherit_default_permissions = inherit_perms
597 Session().add(c.user)
597 Session().add(c.user)
598
598
599 if not inherit_perms:
599 if not inherit_perms:
600 # only update the individual ones if we un check the flag
600 # only update the individual ones if we un check the flag
601 _form = UserPermissionsForm(
601 _form = UserPermissionsForm(
602 self.request.translate,
602 self.request.translate,
603 [x[0] for x in c.repo_create_choices],
603 [x[0] for x in c.repo_create_choices],
604 [x[0] for x in c.repo_create_on_write_choices],
604 [x[0] for x in c.repo_create_on_write_choices],
605 [x[0] for x in c.repo_group_create_choices],
605 [x[0] for x in c.repo_group_create_choices],
606 [x[0] for x in c.user_group_create_choices],
606 [x[0] for x in c.user_group_create_choices],
607 [x[0] for x in c.fork_choices],
607 [x[0] for x in c.fork_choices],
608 [x[0] for x in c.inherit_default_permission_choices])()
608 [x[0] for x in c.inherit_default_permission_choices])()
609
609
610 form_result = _form.to_python(dict(self.request.POST))
610 form_result = _form.to_python(dict(self.request.POST))
611 form_result.update({'perm_user_id': c.user.user_id})
611 form_result.update({'perm_user_id': c.user.user_id})
612
612
613 PermissionModel().update_user_permissions(form_result)
613 PermissionModel().update_user_permissions(form_result)
614
614
615 # TODO(marcink): implement global permissions
615 # TODO(marcink): implement global permissions
616 # audit_log.store_web('user.edit.permissions')
616 # audit_log.store_web('user.edit.permissions')
617
617
618 Session().commit()
618 Session().commit()
619
619
620 h.flash(_('User global permissions updated successfully'),
620 h.flash(_('User global permissions updated successfully'),
621 category='success')
621 category='success')
622
622
623 except formencode.Invalid as errors:
623 except formencode.Invalid as errors:
624 data = render(
624 data = render(
625 'rhodecode:templates/admin/users/user_edit.mako',
625 'rhodecode:templates/admin/users/user_edit.mako',
626 self._get_template_context(c), self.request)
626 self._get_template_context(c), self.request)
627 html = formencode.htmlfill.render(
627 html = formencode.htmlfill.render(
628 data,
628 data,
629 defaults=errors.value,
629 defaults=errors.value,
630 errors=errors.error_dict or {},
630 errors=errors.error_dict or {},
631 prefix_error=False,
631 prefix_error=False,
632 encoding="UTF-8",
632 encoding="UTF-8",
633 force_defaults=False
633 force_defaults=False
634 )
634 )
635 return Response(html)
635 return Response(html)
636 except Exception:
636 except Exception:
637 log.exception("Exception during permissions saving")
637 log.exception("Exception during permissions saving")
638 h.flash(_('An error occurred during permissions saving'),
638 h.flash(_('An error occurred during permissions saving'),
639 category='error')
639 category='error')
640
640
641 affected_user_ids = [user_id]
641 affected_user_ids = [user_id]
642 PermissionModel().trigger_permission_flush(affected_user_ids)
642 PermissionModel().trigger_permission_flush(affected_user_ids)
643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
643 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
644
644
645 @LoginRequired()
645 @LoginRequired()
646 @HasPermissionAllDecorator('hg.admin')
646 @HasPermissionAllDecorator('hg.admin')
647 @CSRFRequired()
647 @CSRFRequired()
648 @view_config(
648 @view_config(
649 route_name='user_enable_force_password_reset', request_method='POST',
649 route_name='user_enable_force_password_reset', request_method='POST',
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
650 renderer='rhodecode:templates/admin/users/user_edit.mako')
651 def user_enable_force_password_reset(self):
651 def user_enable_force_password_reset(self):
652 _ = self.request.translate
652 _ = self.request.translate
653 c = self.load_default_context()
653 c = self.load_default_context()
654
654
655 user_id = self.db_user_id
655 user_id = self.db_user_id
656 c.user = self.db_user
656 c.user = self.db_user
657
657
658 try:
658 try:
659 c.user.update_userdata(force_password_change=True)
659 c.user.update_userdata(force_password_change=True)
660
660
661 msg = _('Force password change enabled for user')
661 msg = _('Force password change enabled for user')
662 audit_logger.store_web('user.edit.password_reset.enabled',
662 audit_logger.store_web('user.edit.password_reset.enabled',
663 user=c.rhodecode_user)
663 user=c.rhodecode_user)
664
664
665 Session().commit()
665 Session().commit()
666 h.flash(msg, category='success')
666 h.flash(msg, category='success')
667 except Exception:
667 except Exception:
668 log.exception("Exception during password reset for user")
668 log.exception("Exception during password reset for user")
669 h.flash(_('An error occurred during password reset for user'),
669 h.flash(_('An error occurred during password reset for user'),
670 category='error')
670 category='error')
671
671
672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
672 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
673
673
674 @LoginRequired()
674 @LoginRequired()
675 @HasPermissionAllDecorator('hg.admin')
675 @HasPermissionAllDecorator('hg.admin')
676 @CSRFRequired()
676 @CSRFRequired()
677 @view_config(
677 @view_config(
678 route_name='user_disable_force_password_reset', request_method='POST',
678 route_name='user_disable_force_password_reset', request_method='POST',
679 renderer='rhodecode:templates/admin/users/user_edit.mako')
679 renderer='rhodecode:templates/admin/users/user_edit.mako')
680 def user_disable_force_password_reset(self):
680 def user_disable_force_password_reset(self):
681 _ = self.request.translate
681 _ = self.request.translate
682 c = self.load_default_context()
682 c = self.load_default_context()
683
683
684 user_id = self.db_user_id
684 user_id = self.db_user_id
685 c.user = self.db_user
685 c.user = self.db_user
686
686
687 try:
687 try:
688 c.user.update_userdata(force_password_change=False)
688 c.user.update_userdata(force_password_change=False)
689
689
690 msg = _('Force password change disabled for user')
690 msg = _('Force password change disabled for user')
691 audit_logger.store_web(
691 audit_logger.store_web(
692 'user.edit.password_reset.disabled',
692 'user.edit.password_reset.disabled',
693 user=c.rhodecode_user)
693 user=c.rhodecode_user)
694
694
695 Session().commit()
695 Session().commit()
696 h.flash(msg, category='success')
696 h.flash(msg, category='success')
697 except Exception:
697 except Exception:
698 log.exception("Exception during password reset for user")
698 log.exception("Exception during password reset for user")
699 h.flash(_('An error occurred during password reset for user'),
699 h.flash(_('An error occurred during password reset for user'),
700 category='error')
700 category='error')
701
701
702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
702 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
703
703
704 @LoginRequired()
704 @LoginRequired()
705 @HasPermissionAllDecorator('hg.admin')
705 @HasPermissionAllDecorator('hg.admin')
706 @CSRFRequired()
706 @CSRFRequired()
707 @view_config(
707 @view_config(
708 route_name='user_create_personal_repo_group', request_method='POST',
708 route_name='user_create_personal_repo_group', request_method='POST',
709 renderer='rhodecode:templates/admin/users/user_edit.mako')
709 renderer='rhodecode:templates/admin/users/user_edit.mako')
710 def user_create_personal_repo_group(self):
710 def user_create_personal_repo_group(self):
711 """
711 """
712 Create personal repository group for this user
712 Create personal repository group for this user
713 """
713 """
714 from rhodecode.model.repo_group import RepoGroupModel
714 from rhodecode.model.repo_group import RepoGroupModel
715
715
716 _ = self.request.translate
716 _ = self.request.translate
717 c = self.load_default_context()
717 c = self.load_default_context()
718
718
719 user_id = self.db_user_id
719 user_id = self.db_user_id
720 c.user = self.db_user
720 c.user = self.db_user
721
721
722 personal_repo_group = RepoGroup.get_user_personal_repo_group(
722 personal_repo_group = RepoGroup.get_user_personal_repo_group(
723 c.user.user_id)
723 c.user.user_id)
724 if personal_repo_group:
724 if personal_repo_group:
725 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
725 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
726
726
727 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
727 personal_repo_group_name = RepoGroupModel().get_personal_group_name(c.user)
728 named_personal_group = RepoGroup.get_by_group_name(
728 named_personal_group = RepoGroup.get_by_group_name(
729 personal_repo_group_name)
729 personal_repo_group_name)
730 try:
730 try:
731
731
732 if named_personal_group and named_personal_group.user_id == c.user.user_id:
732 if named_personal_group and named_personal_group.user_id == c.user.user_id:
733 # migrate the same named group, and mark it as personal
733 # migrate the same named group, and mark it as personal
734 named_personal_group.personal = True
734 named_personal_group.personal = True
735 Session().add(named_personal_group)
735 Session().add(named_personal_group)
736 Session().commit()
736 Session().commit()
737 msg = _('Linked repository group `%s` as personal' % (
737 msg = _('Linked repository group `%s` as personal' % (
738 personal_repo_group_name,))
738 personal_repo_group_name,))
739 h.flash(msg, category='success')
739 h.flash(msg, category='success')
740 elif not named_personal_group:
740 elif not named_personal_group:
741 RepoGroupModel().create_personal_repo_group(c.user)
741 RepoGroupModel().create_personal_repo_group(c.user)
742
742
743 msg = _('Created repository group `%s`' % (
743 msg = _('Created repository group `%s`' % (
744 personal_repo_group_name,))
744 personal_repo_group_name,))
745 h.flash(msg, category='success')
745 h.flash(msg, category='success')
746 else:
746 else:
747 msg = _('Repository group `%s` is already taken' % (
747 msg = _('Repository group `%s` is already taken' % (
748 personal_repo_group_name,))
748 personal_repo_group_name,))
749 h.flash(msg, category='warning')
749 h.flash(msg, category='warning')
750 except Exception:
750 except Exception:
751 log.exception("Exception during repository group creation")
751 log.exception("Exception during repository group creation")
752 msg = _(
752 msg = _(
753 'An error occurred during repository group creation for user')
753 'An error occurred during repository group creation for user')
754 h.flash(msg, category='error')
754 h.flash(msg, category='error')
755 Session().rollback()
755 Session().rollback()
756
756
757 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
757 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
758
758
759 @LoginRequired()
759 @LoginRequired()
760 @HasPermissionAllDecorator('hg.admin')
760 @HasPermissionAllDecorator('hg.admin')
761 @view_config(
761 @view_config(
762 route_name='edit_user_auth_tokens', request_method='GET',
762 route_name='edit_user_auth_tokens', request_method='GET',
763 renderer='rhodecode:templates/admin/users/user_edit.mako')
763 renderer='rhodecode:templates/admin/users/user_edit.mako')
764 def auth_tokens(self):
764 def auth_tokens(self):
765 _ = self.request.translate
765 _ = self.request.translate
766 c = self.load_default_context()
766 c = self.load_default_context()
767 c.user = self.db_user
767 c.user = self.db_user
768
768
769 c.active = 'auth_tokens'
769 c.active = 'auth_tokens'
770
770
771 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
771 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
772 c.role_values = [
772 c.role_values = [
773 (x, AuthTokenModel.cls._get_role_name(x))
773 (x, AuthTokenModel.cls._get_role_name(x))
774 for x in AuthTokenModel.cls.ROLES]
774 for x in AuthTokenModel.cls.ROLES]
775 c.role_options = [(c.role_values, _("Role"))]
775 c.role_options = [(c.role_values, _("Role"))]
776 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
776 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
777 c.user.user_id, show_expired=True)
777 c.user.user_id, show_expired=True)
778 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
778 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
779 return self._get_template_context(c)
779 return self._get_template_context(c)
780
780
781 def maybe_attach_token_scope(self, token):
781 def maybe_attach_token_scope(self, token):
782 # implemented in EE edition
782 # implemented in EE edition
783 pass
783 pass
784
784
785 @LoginRequired()
785 @LoginRequired()
786 @HasPermissionAllDecorator('hg.admin')
786 @HasPermissionAllDecorator('hg.admin')
787 @CSRFRequired()
787 @CSRFRequired()
788 @view_config(
788 @view_config(
789 route_name='edit_user_auth_tokens_add', request_method='POST')
789 route_name='edit_user_auth_tokens_add', request_method='POST')
790 def auth_tokens_add(self):
790 def auth_tokens_add(self):
791 _ = self.request.translate
791 _ = self.request.translate
792 c = self.load_default_context()
792 c = self.load_default_context()
793
793
794 user_id = self.db_user_id
794 user_id = self.db_user_id
795 c.user = self.db_user
795 c.user = self.db_user
796
796
797 user_data = c.user.get_api_data()
797 user_data = c.user.get_api_data()
798 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
798 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
799 description = self.request.POST.get('description')
799 description = self.request.POST.get('description')
800 role = self.request.POST.get('role')
800 role = self.request.POST.get('role')
801
801
802 token = UserModel().add_auth_token(
802 token = UserModel().add_auth_token(
803 user=c.user.user_id,
803 user=c.user.user_id,
804 lifetime_minutes=lifetime, role=role, description=description,
804 lifetime_minutes=lifetime, role=role, description=description,
805 scope_callback=self.maybe_attach_token_scope)
805 scope_callback=self.maybe_attach_token_scope)
806 token_data = token.get_api_data()
806 token_data = token.get_api_data()
807
807
808 audit_logger.store_web(
808 audit_logger.store_web(
809 'user.edit.token.add', action_data={
809 'user.edit.token.add', action_data={
810 'data': {'token': token_data, 'user': user_data}},
810 'data': {'token': token_data, 'user': user_data}},
811 user=self._rhodecode_user, )
811 user=self._rhodecode_user, )
812 Session().commit()
812 Session().commit()
813
813
814 h.flash(_("Auth token successfully created"), category='success')
814 h.flash(_("Auth token successfully created"), category='success')
815 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
815 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
816
816
817 @LoginRequired()
817 @LoginRequired()
818 @HasPermissionAllDecorator('hg.admin')
818 @HasPermissionAllDecorator('hg.admin')
819 @CSRFRequired()
819 @CSRFRequired()
820 @view_config(
820 @view_config(
821 route_name='edit_user_auth_tokens_delete', request_method='POST')
821 route_name='edit_user_auth_tokens_delete', request_method='POST')
822 def auth_tokens_delete(self):
822 def auth_tokens_delete(self):
823 _ = self.request.translate
823 _ = self.request.translate
824 c = self.load_default_context()
824 c = self.load_default_context()
825
825
826 user_id = self.db_user_id
826 user_id = self.db_user_id
827 c.user = self.db_user
827 c.user = self.db_user
828
828
829 user_data = c.user.get_api_data()
829 user_data = c.user.get_api_data()
830
830
831 del_auth_token = self.request.POST.get('del_auth_token')
831 del_auth_token = self.request.POST.get('del_auth_token')
832
832
833 if del_auth_token:
833 if del_auth_token:
834 token = UserApiKeys.get_or_404(del_auth_token)
834 token = UserApiKeys.get_or_404(del_auth_token)
835 token_data = token.get_api_data()
835 token_data = token.get_api_data()
836
836
837 AuthTokenModel().delete(del_auth_token, c.user.user_id)
837 AuthTokenModel().delete(del_auth_token, c.user.user_id)
838 audit_logger.store_web(
838 audit_logger.store_web(
839 'user.edit.token.delete', action_data={
839 'user.edit.token.delete', action_data={
840 'data': {'token': token_data, 'user': user_data}},
840 'data': {'token': token_data, 'user': user_data}},
841 user=self._rhodecode_user,)
841 user=self._rhodecode_user,)
842 Session().commit()
842 Session().commit()
843 h.flash(_("Auth token successfully deleted"), category='success')
843 h.flash(_("Auth token successfully deleted"), category='success')
844
844
845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
845 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
846
846
847 @LoginRequired()
847 @LoginRequired()
848 @HasPermissionAllDecorator('hg.admin')
848 @HasPermissionAllDecorator('hg.admin')
849 @view_config(
849 @view_config(
850 route_name='edit_user_ssh_keys', request_method='GET',
850 route_name='edit_user_ssh_keys', request_method='GET',
851 renderer='rhodecode:templates/admin/users/user_edit.mako')
851 renderer='rhodecode:templates/admin/users/user_edit.mako')
852 def ssh_keys(self):
852 def ssh_keys(self):
853 _ = self.request.translate
853 _ = self.request.translate
854 c = self.load_default_context()
854 c = self.load_default_context()
855 c.user = self.db_user
855 c.user = self.db_user
856
856
857 c.active = 'ssh_keys'
857 c.active = 'ssh_keys'
858 c.default_key = self.request.GET.get('default_key')
858 c.default_key = self.request.GET.get('default_key')
859 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
859 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
860 return self._get_template_context(c)
860 return self._get_template_context(c)
861
861
862 @LoginRequired()
862 @LoginRequired()
863 @HasPermissionAllDecorator('hg.admin')
863 @HasPermissionAllDecorator('hg.admin')
864 @view_config(
864 @view_config(
865 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
865 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
866 renderer='rhodecode:templates/admin/users/user_edit.mako')
866 renderer='rhodecode:templates/admin/users/user_edit.mako')
867 def ssh_keys_generate_keypair(self):
867 def ssh_keys_generate_keypair(self):
868 _ = self.request.translate
868 _ = self.request.translate
869 c = self.load_default_context()
869 c = self.load_default_context()
870
870
871 c.user = self.db_user
871 c.user = self.db_user
872
872
873 c.active = 'ssh_keys_generate'
873 c.active = 'ssh_keys_generate'
874 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
874 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
875 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
875 private_format = self.request.GET.get('private_format') \
876 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
877 c.private, c.public = SshKeyModel().generate_keypair(
878 comment=comment, private_format=private_format)
876
879
877 return self._get_template_context(c)
880 return self._get_template_context(c)
878
881
879 @LoginRequired()
882 @LoginRequired()
880 @HasPermissionAllDecorator('hg.admin')
883 @HasPermissionAllDecorator('hg.admin')
881 @CSRFRequired()
884 @CSRFRequired()
882 @view_config(
885 @view_config(
883 route_name='edit_user_ssh_keys_add', request_method='POST')
886 route_name='edit_user_ssh_keys_add', request_method='POST')
884 def ssh_keys_add(self):
887 def ssh_keys_add(self):
885 _ = self.request.translate
888 _ = self.request.translate
886 c = self.load_default_context()
889 c = self.load_default_context()
887
890
888 user_id = self.db_user_id
891 user_id = self.db_user_id
889 c.user = self.db_user
892 c.user = self.db_user
890
893
891 user_data = c.user.get_api_data()
894 user_data = c.user.get_api_data()
892 key_data = self.request.POST.get('key_data')
895 key_data = self.request.POST.get('key_data')
893 description = self.request.POST.get('description')
896 description = self.request.POST.get('description')
894
897
895 fingerprint = 'unknown'
898 fingerprint = 'unknown'
896 try:
899 try:
897 if not key_data:
900 if not key_data:
898 raise ValueError('Please add a valid public key')
901 raise ValueError('Please add a valid public key')
899
902
900 key = SshKeyModel().parse_key(key_data.strip())
903 key = SshKeyModel().parse_key(key_data.strip())
901 fingerprint = key.hash_md5()
904 fingerprint = key.hash_md5()
902
905
903 ssh_key = SshKeyModel().create(
906 ssh_key = SshKeyModel().create(
904 c.user.user_id, fingerprint, key.keydata, description)
907 c.user.user_id, fingerprint, key.keydata, description)
905 ssh_key_data = ssh_key.get_api_data()
908 ssh_key_data = ssh_key.get_api_data()
906
909
907 audit_logger.store_web(
910 audit_logger.store_web(
908 'user.edit.ssh_key.add', action_data={
911 'user.edit.ssh_key.add', action_data={
909 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
912 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
910 user=self._rhodecode_user, )
913 user=self._rhodecode_user, )
911 Session().commit()
914 Session().commit()
912
915
913 # Trigger an event on change of keys.
916 # Trigger an event on change of keys.
914 trigger(SshKeyFileChangeEvent(), self.request.registry)
917 trigger(SshKeyFileChangeEvent(), self.request.registry)
915
918
916 h.flash(_("Ssh Key successfully created"), category='success')
919 h.flash(_("Ssh Key successfully created"), category='success')
917
920
918 except IntegrityError:
921 except IntegrityError:
919 log.exception("Exception during ssh key saving")
922 log.exception("Exception during ssh key saving")
920 err = 'Such key with fingerprint `{}` already exists, ' \
923 err = 'Such key with fingerprint `{}` already exists, ' \
921 'please use a different one'.format(fingerprint)
924 'please use a different one'.format(fingerprint)
922 h.flash(_('An error occurred during ssh key saving: {}').format(err),
925 h.flash(_('An error occurred during ssh key saving: {}').format(err),
923 category='error')
926 category='error')
924 except Exception as e:
927 except Exception as e:
925 log.exception("Exception during ssh key saving")
928 log.exception("Exception during ssh key saving")
926 h.flash(_('An error occurred during ssh key saving: {}').format(e),
929 h.flash(_('An error occurred during ssh key saving: {}').format(e),
927 category='error')
930 category='error')
928
931
929 return HTTPFound(
932 return HTTPFound(
930 h.route_path('edit_user_ssh_keys', user_id=user_id))
933 h.route_path('edit_user_ssh_keys', user_id=user_id))
931
934
932 @LoginRequired()
935 @LoginRequired()
933 @HasPermissionAllDecorator('hg.admin')
936 @HasPermissionAllDecorator('hg.admin')
934 @CSRFRequired()
937 @CSRFRequired()
935 @view_config(
938 @view_config(
936 route_name='edit_user_ssh_keys_delete', request_method='POST')
939 route_name='edit_user_ssh_keys_delete', request_method='POST')
937 def ssh_keys_delete(self):
940 def ssh_keys_delete(self):
938 _ = self.request.translate
941 _ = self.request.translate
939 c = self.load_default_context()
942 c = self.load_default_context()
940
943
941 user_id = self.db_user_id
944 user_id = self.db_user_id
942 c.user = self.db_user
945 c.user = self.db_user
943
946
944 user_data = c.user.get_api_data()
947 user_data = c.user.get_api_data()
945
948
946 del_ssh_key = self.request.POST.get('del_ssh_key')
949 del_ssh_key = self.request.POST.get('del_ssh_key')
947
950
948 if del_ssh_key:
951 if del_ssh_key:
949 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
952 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
950 ssh_key_data = ssh_key.get_api_data()
953 ssh_key_data = ssh_key.get_api_data()
951
954
952 SshKeyModel().delete(del_ssh_key, c.user.user_id)
955 SshKeyModel().delete(del_ssh_key, c.user.user_id)
953 audit_logger.store_web(
956 audit_logger.store_web(
954 'user.edit.ssh_key.delete', action_data={
957 'user.edit.ssh_key.delete', action_data={
955 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
958 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
956 user=self._rhodecode_user,)
959 user=self._rhodecode_user,)
957 Session().commit()
960 Session().commit()
958 # Trigger an event on change of keys.
961 # Trigger an event on change of keys.
959 trigger(SshKeyFileChangeEvent(), self.request.registry)
962 trigger(SshKeyFileChangeEvent(), self.request.registry)
960 h.flash(_("Ssh key successfully deleted"), category='success')
963 h.flash(_("Ssh key successfully deleted"), category='success')
961
964
962 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
965 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
963
966
964 @LoginRequired()
967 @LoginRequired()
965 @HasPermissionAllDecorator('hg.admin')
968 @HasPermissionAllDecorator('hg.admin')
966 @view_config(
969 @view_config(
967 route_name='edit_user_emails', request_method='GET',
970 route_name='edit_user_emails', request_method='GET',
968 renderer='rhodecode:templates/admin/users/user_edit.mako')
971 renderer='rhodecode:templates/admin/users/user_edit.mako')
969 def emails(self):
972 def emails(self):
970 _ = self.request.translate
973 _ = self.request.translate
971 c = self.load_default_context()
974 c = self.load_default_context()
972 c.user = self.db_user
975 c.user = self.db_user
973
976
974 c.active = 'emails'
977 c.active = 'emails'
975 c.user_email_map = UserEmailMap.query() \
978 c.user_email_map = UserEmailMap.query() \
976 .filter(UserEmailMap.user == c.user).all()
979 .filter(UserEmailMap.user == c.user).all()
977
980
978 return self._get_template_context(c)
981 return self._get_template_context(c)
979
982
980 @LoginRequired()
983 @LoginRequired()
981 @HasPermissionAllDecorator('hg.admin')
984 @HasPermissionAllDecorator('hg.admin')
982 @CSRFRequired()
985 @CSRFRequired()
983 @view_config(
986 @view_config(
984 route_name='edit_user_emails_add', request_method='POST')
987 route_name='edit_user_emails_add', request_method='POST')
985 def emails_add(self):
988 def emails_add(self):
986 _ = self.request.translate
989 _ = self.request.translate
987 c = self.load_default_context()
990 c = self.load_default_context()
988
991
989 user_id = self.db_user_id
992 user_id = self.db_user_id
990 c.user = self.db_user
993 c.user = self.db_user
991
994
992 email = self.request.POST.get('new_email')
995 email = self.request.POST.get('new_email')
993 user_data = c.user.get_api_data()
996 user_data = c.user.get_api_data()
994 try:
997 try:
995
998
996 form = UserExtraEmailForm(self.request.translate)()
999 form = UserExtraEmailForm(self.request.translate)()
997 data = form.to_python({'email': email})
1000 data = form.to_python({'email': email})
998 email = data['email']
1001 email = data['email']
999
1002
1000 UserModel().add_extra_email(c.user.user_id, email)
1003 UserModel().add_extra_email(c.user.user_id, email)
1001 audit_logger.store_web(
1004 audit_logger.store_web(
1002 'user.edit.email.add',
1005 'user.edit.email.add',
1003 action_data={'email': email, 'user': user_data},
1006 action_data={'email': email, 'user': user_data},
1004 user=self._rhodecode_user)
1007 user=self._rhodecode_user)
1005 Session().commit()
1008 Session().commit()
1006 h.flash(_("Added new email address `%s` for user account") % email,
1009 h.flash(_("Added new email address `%s` for user account") % email,
1007 category='success')
1010 category='success')
1008 except formencode.Invalid as error:
1011 except formencode.Invalid as error:
1009 h.flash(h.escape(error.error_dict['email']), category='error')
1012 h.flash(h.escape(error.error_dict['email']), category='error')
1010 except IntegrityError:
1013 except IntegrityError:
1011 log.warning("Email %s already exists", email)
1014 log.warning("Email %s already exists", email)
1012 h.flash(_('Email `{}` is already registered for another user.').format(email),
1015 h.flash(_('Email `{}` is already registered for another user.').format(email),
1013 category='error')
1016 category='error')
1014 except Exception:
1017 except Exception:
1015 log.exception("Exception during email saving")
1018 log.exception("Exception during email saving")
1016 h.flash(_('An error occurred during email saving'),
1019 h.flash(_('An error occurred during email saving'),
1017 category='error')
1020 category='error')
1018 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1021 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1019
1022
1020 @LoginRequired()
1023 @LoginRequired()
1021 @HasPermissionAllDecorator('hg.admin')
1024 @HasPermissionAllDecorator('hg.admin')
1022 @CSRFRequired()
1025 @CSRFRequired()
1023 @view_config(
1026 @view_config(
1024 route_name='edit_user_emails_delete', request_method='POST')
1027 route_name='edit_user_emails_delete', request_method='POST')
1025 def emails_delete(self):
1028 def emails_delete(self):
1026 _ = self.request.translate
1029 _ = self.request.translate
1027 c = self.load_default_context()
1030 c = self.load_default_context()
1028
1031
1029 user_id = self.db_user_id
1032 user_id = self.db_user_id
1030 c.user = self.db_user
1033 c.user = self.db_user
1031
1034
1032 email_id = self.request.POST.get('del_email_id')
1035 email_id = self.request.POST.get('del_email_id')
1033 user_model = UserModel()
1036 user_model = UserModel()
1034
1037
1035 email = UserEmailMap.query().get(email_id).email
1038 email = UserEmailMap.query().get(email_id).email
1036 user_data = c.user.get_api_data()
1039 user_data = c.user.get_api_data()
1037 user_model.delete_extra_email(c.user.user_id, email_id)
1040 user_model.delete_extra_email(c.user.user_id, email_id)
1038 audit_logger.store_web(
1041 audit_logger.store_web(
1039 'user.edit.email.delete',
1042 'user.edit.email.delete',
1040 action_data={'email': email, 'user': user_data},
1043 action_data={'email': email, 'user': user_data},
1041 user=self._rhodecode_user)
1044 user=self._rhodecode_user)
1042 Session().commit()
1045 Session().commit()
1043 h.flash(_("Removed email address from user account"),
1046 h.flash(_("Removed email address from user account"),
1044 category='success')
1047 category='success')
1045 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1048 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
1046
1049
1047 @LoginRequired()
1050 @LoginRequired()
1048 @HasPermissionAllDecorator('hg.admin')
1051 @HasPermissionAllDecorator('hg.admin')
1049 @view_config(
1052 @view_config(
1050 route_name='edit_user_ips', request_method='GET',
1053 route_name='edit_user_ips', request_method='GET',
1051 renderer='rhodecode:templates/admin/users/user_edit.mako')
1054 renderer='rhodecode:templates/admin/users/user_edit.mako')
1052 def ips(self):
1055 def ips(self):
1053 _ = self.request.translate
1056 _ = self.request.translate
1054 c = self.load_default_context()
1057 c = self.load_default_context()
1055 c.user = self.db_user
1058 c.user = self.db_user
1056
1059
1057 c.active = 'ips'
1060 c.active = 'ips'
1058 c.user_ip_map = UserIpMap.query() \
1061 c.user_ip_map = UserIpMap.query() \
1059 .filter(UserIpMap.user == c.user).all()
1062 .filter(UserIpMap.user == c.user).all()
1060
1063
1061 c.inherit_default_ips = c.user.inherit_default_permissions
1064 c.inherit_default_ips = c.user.inherit_default_permissions
1062 c.default_user_ip_map = UserIpMap.query() \
1065 c.default_user_ip_map = UserIpMap.query() \
1063 .filter(UserIpMap.user == User.get_default_user()).all()
1066 .filter(UserIpMap.user == User.get_default_user()).all()
1064
1067
1065 return self._get_template_context(c)
1068 return self._get_template_context(c)
1066
1069
1067 @LoginRequired()
1070 @LoginRequired()
1068 @HasPermissionAllDecorator('hg.admin')
1071 @HasPermissionAllDecorator('hg.admin')
1069 @CSRFRequired()
1072 @CSRFRequired()
1070 @view_config(
1073 @view_config(
1071 route_name='edit_user_ips_add', request_method='POST')
1074 route_name='edit_user_ips_add', request_method='POST')
1072 # NOTE(marcink): this view is allowed for default users, as we can
1075 # NOTE(marcink): this view is allowed for default users, as we can
1073 # edit their IP white list
1076 # edit their IP white list
1074 def ips_add(self):
1077 def ips_add(self):
1075 _ = self.request.translate
1078 _ = self.request.translate
1076 c = self.load_default_context()
1079 c = self.load_default_context()
1077
1080
1078 user_id = self.db_user_id
1081 user_id = self.db_user_id
1079 c.user = self.db_user
1082 c.user = self.db_user
1080
1083
1081 user_model = UserModel()
1084 user_model = UserModel()
1082 desc = self.request.POST.get('description')
1085 desc = self.request.POST.get('description')
1083 try:
1086 try:
1084 ip_list = user_model.parse_ip_range(
1087 ip_list = user_model.parse_ip_range(
1085 self.request.POST.get('new_ip'))
1088 self.request.POST.get('new_ip'))
1086 except Exception as e:
1089 except Exception as e:
1087 ip_list = []
1090 ip_list = []
1088 log.exception("Exception during ip saving")
1091 log.exception("Exception during ip saving")
1089 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1092 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1090 category='error')
1093 category='error')
1091 added = []
1094 added = []
1092 user_data = c.user.get_api_data()
1095 user_data = c.user.get_api_data()
1093 for ip in ip_list:
1096 for ip in ip_list:
1094 try:
1097 try:
1095 form = UserExtraIpForm(self.request.translate)()
1098 form = UserExtraIpForm(self.request.translate)()
1096 data = form.to_python({'ip': ip})
1099 data = form.to_python({'ip': ip})
1097 ip = data['ip']
1100 ip = data['ip']
1098
1101
1099 user_model.add_extra_ip(c.user.user_id, ip, desc)
1102 user_model.add_extra_ip(c.user.user_id, ip, desc)
1100 audit_logger.store_web(
1103 audit_logger.store_web(
1101 'user.edit.ip.add',
1104 'user.edit.ip.add',
1102 action_data={'ip': ip, 'user': user_data},
1105 action_data={'ip': ip, 'user': user_data},
1103 user=self._rhodecode_user)
1106 user=self._rhodecode_user)
1104 Session().commit()
1107 Session().commit()
1105 added.append(ip)
1108 added.append(ip)
1106 except formencode.Invalid as error:
1109 except formencode.Invalid as error:
1107 msg = error.error_dict['ip']
1110 msg = error.error_dict['ip']
1108 h.flash(msg, category='error')
1111 h.flash(msg, category='error')
1109 except Exception:
1112 except Exception:
1110 log.exception("Exception during ip saving")
1113 log.exception("Exception during ip saving")
1111 h.flash(_('An error occurred during ip saving'),
1114 h.flash(_('An error occurred during ip saving'),
1112 category='error')
1115 category='error')
1113 if added:
1116 if added:
1114 h.flash(
1117 h.flash(
1115 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1118 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1116 category='success')
1119 category='success')
1117 if 'default_user' in self.request.POST:
1120 if 'default_user' in self.request.POST:
1118 # case for editing global IP list we do it for 'DEFAULT' user
1121 # case for editing global IP list we do it for 'DEFAULT' user
1119 raise HTTPFound(h.route_path('admin_permissions_ips'))
1122 raise HTTPFound(h.route_path('admin_permissions_ips'))
1120 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1123 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1121
1124
1122 @LoginRequired()
1125 @LoginRequired()
1123 @HasPermissionAllDecorator('hg.admin')
1126 @HasPermissionAllDecorator('hg.admin')
1124 @CSRFRequired()
1127 @CSRFRequired()
1125 @view_config(
1128 @view_config(
1126 route_name='edit_user_ips_delete', request_method='POST')
1129 route_name='edit_user_ips_delete', request_method='POST')
1127 # NOTE(marcink): this view is allowed for default users, as we can
1130 # NOTE(marcink): this view is allowed for default users, as we can
1128 # edit their IP white list
1131 # edit their IP white list
1129 def ips_delete(self):
1132 def ips_delete(self):
1130 _ = self.request.translate
1133 _ = self.request.translate
1131 c = self.load_default_context()
1134 c = self.load_default_context()
1132
1135
1133 user_id = self.db_user_id
1136 user_id = self.db_user_id
1134 c.user = self.db_user
1137 c.user = self.db_user
1135
1138
1136 ip_id = self.request.POST.get('del_ip_id')
1139 ip_id = self.request.POST.get('del_ip_id')
1137 user_model = UserModel()
1140 user_model = UserModel()
1138 user_data = c.user.get_api_data()
1141 user_data = c.user.get_api_data()
1139 ip = UserIpMap.query().get(ip_id).ip_addr
1142 ip = UserIpMap.query().get(ip_id).ip_addr
1140 user_model.delete_extra_ip(c.user.user_id, ip_id)
1143 user_model.delete_extra_ip(c.user.user_id, ip_id)
1141 audit_logger.store_web(
1144 audit_logger.store_web(
1142 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1145 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1143 user=self._rhodecode_user)
1146 user=self._rhodecode_user)
1144 Session().commit()
1147 Session().commit()
1145 h.flash(_("Removed ip address from user whitelist"), category='success')
1148 h.flash(_("Removed ip address from user whitelist"), category='success')
1146
1149
1147 if 'default_user' in self.request.POST:
1150 if 'default_user' in self.request.POST:
1148 # case for editing global IP list we do it for 'DEFAULT' user
1151 # case for editing global IP list we do it for 'DEFAULT' user
1149 raise HTTPFound(h.route_path('admin_permissions_ips'))
1152 raise HTTPFound(h.route_path('admin_permissions_ips'))
1150 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1153 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1151
1154
1152 @LoginRequired()
1155 @LoginRequired()
1153 @HasPermissionAllDecorator('hg.admin')
1156 @HasPermissionAllDecorator('hg.admin')
1154 @view_config(
1157 @view_config(
1155 route_name='edit_user_groups_management', request_method='GET',
1158 route_name='edit_user_groups_management', request_method='GET',
1156 renderer='rhodecode:templates/admin/users/user_edit.mako')
1159 renderer='rhodecode:templates/admin/users/user_edit.mako')
1157 def groups_management(self):
1160 def groups_management(self):
1158 c = self.load_default_context()
1161 c = self.load_default_context()
1159 c.user = self.db_user
1162 c.user = self.db_user
1160 c.data = c.user.group_member
1163 c.data = c.user.group_member
1161
1164
1162 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1165 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1163 for group in c.user.group_member]
1166 for group in c.user.group_member]
1164 c.groups = json.dumps(groups)
1167 c.groups = json.dumps(groups)
1165 c.active = 'groups'
1168 c.active = 'groups'
1166
1169
1167 return self._get_template_context(c)
1170 return self._get_template_context(c)
1168
1171
1169 @LoginRequired()
1172 @LoginRequired()
1170 @HasPermissionAllDecorator('hg.admin')
1173 @HasPermissionAllDecorator('hg.admin')
1171 @CSRFRequired()
1174 @CSRFRequired()
1172 @view_config(
1175 @view_config(
1173 route_name='edit_user_groups_management_updates', request_method='POST')
1176 route_name='edit_user_groups_management_updates', request_method='POST')
1174 def groups_management_updates(self):
1177 def groups_management_updates(self):
1175 _ = self.request.translate
1178 _ = self.request.translate
1176 c = self.load_default_context()
1179 c = self.load_default_context()
1177
1180
1178 user_id = self.db_user_id
1181 user_id = self.db_user_id
1179 c.user = self.db_user
1182 c.user = self.db_user
1180
1183
1181 user_groups = set(self.request.POST.getall('users_group_id'))
1184 user_groups = set(self.request.POST.getall('users_group_id'))
1182 user_groups_objects = []
1185 user_groups_objects = []
1183
1186
1184 for ugid in user_groups:
1187 for ugid in user_groups:
1185 user_groups_objects.append(
1188 user_groups_objects.append(
1186 UserGroupModel().get_group(safe_int(ugid)))
1189 UserGroupModel().get_group(safe_int(ugid)))
1187 user_group_model = UserGroupModel()
1190 user_group_model = UserGroupModel()
1188 added_to_groups, removed_from_groups = \
1191 added_to_groups, removed_from_groups = \
1189 user_group_model.change_groups(c.user, user_groups_objects)
1192 user_group_model.change_groups(c.user, user_groups_objects)
1190
1193
1191 user_data = c.user.get_api_data()
1194 user_data = c.user.get_api_data()
1192 for user_group_id in added_to_groups:
1195 for user_group_id in added_to_groups:
1193 user_group = UserGroup.get(user_group_id)
1196 user_group = UserGroup.get(user_group_id)
1194 old_values = user_group.get_api_data()
1197 old_values = user_group.get_api_data()
1195 audit_logger.store_web(
1198 audit_logger.store_web(
1196 'user_group.edit.member.add',
1199 'user_group.edit.member.add',
1197 action_data={'user': user_data, 'old_data': old_values},
1200 action_data={'user': user_data, 'old_data': old_values},
1198 user=self._rhodecode_user)
1201 user=self._rhodecode_user)
1199
1202
1200 for user_group_id in removed_from_groups:
1203 for user_group_id in removed_from_groups:
1201 user_group = UserGroup.get(user_group_id)
1204 user_group = UserGroup.get(user_group_id)
1202 old_values = user_group.get_api_data()
1205 old_values = user_group.get_api_data()
1203 audit_logger.store_web(
1206 audit_logger.store_web(
1204 'user_group.edit.member.delete',
1207 'user_group.edit.member.delete',
1205 action_data={'user': user_data, 'old_data': old_values},
1208 action_data={'user': user_data, 'old_data': old_values},
1206 user=self._rhodecode_user)
1209 user=self._rhodecode_user)
1207
1210
1208 Session().commit()
1211 Session().commit()
1209 c.active = 'user_groups_management'
1212 c.active = 'user_groups_management'
1210 h.flash(_("Groups successfully changed"), category='success')
1213 h.flash(_("Groups successfully changed"), category='success')
1211
1214
1212 return HTTPFound(h.route_path(
1215 return HTTPFound(h.route_path(
1213 'edit_user_groups_management', user_id=user_id))
1216 'edit_user_groups_management', user_id=user_id))
1214
1217
1215 @LoginRequired()
1218 @LoginRequired()
1216 @HasPermissionAllDecorator('hg.admin')
1219 @HasPermissionAllDecorator('hg.admin')
1217 @view_config(
1220 @view_config(
1218 route_name='edit_user_audit_logs', request_method='GET',
1221 route_name='edit_user_audit_logs', request_method='GET',
1219 renderer='rhodecode:templates/admin/users/user_edit.mako')
1222 renderer='rhodecode:templates/admin/users/user_edit.mako')
1220 def user_audit_logs(self):
1223 def user_audit_logs(self):
1221 _ = self.request.translate
1224 _ = self.request.translate
1222 c = self.load_default_context()
1225 c = self.load_default_context()
1223 c.user = self.db_user
1226 c.user = self.db_user
1224
1227
1225 c.active = 'audit'
1228 c.active = 'audit'
1226
1229
1227 p = safe_int(self.request.GET.get('page', 1), 1)
1230 p = safe_int(self.request.GET.get('page', 1), 1)
1228
1231
1229 filter_term = self.request.GET.get('filter')
1232 filter_term = self.request.GET.get('filter')
1230 user_log = UserModel().get_user_log(c.user, filter_term)
1233 user_log = UserModel().get_user_log(c.user, filter_term)
1231
1234
1232 def url_generator(page_num):
1235 def url_generator(page_num):
1233 query_params = {
1236 query_params = {
1234 'page': page_num
1237 'page': page_num
1235 }
1238 }
1236 if filter_term:
1239 if filter_term:
1237 query_params['filter'] = filter_term
1240 query_params['filter'] = filter_term
1238 return self.request.current_route_path(_query=query_params)
1241 return self.request.current_route_path(_query=query_params)
1239
1242
1240 c.audit_logs = SqlPage(
1243 c.audit_logs = SqlPage(
1241 user_log, page=p, items_per_page=10, url_maker=url_generator)
1244 user_log, page=p, items_per_page=10, url_maker=url_generator)
1242 c.filter_term = filter_term
1245 c.filter_term = filter_term
1243 return self._get_template_context(c)
1246 return self._get_template_context(c)
1244
1247
1245 @LoginRequired()
1248 @LoginRequired()
1246 @HasPermissionAllDecorator('hg.admin')
1249 @HasPermissionAllDecorator('hg.admin')
1247 @view_config(
1250 @view_config(
1248 route_name='edit_user_audit_logs_download', request_method='GET',
1251 route_name='edit_user_audit_logs_download', request_method='GET',
1249 renderer='string')
1252 renderer='string')
1250 def user_audit_logs_download(self):
1253 def user_audit_logs_download(self):
1251 _ = self.request.translate
1254 _ = self.request.translate
1252 c = self.load_default_context()
1255 c = self.load_default_context()
1253 c.user = self.db_user
1256 c.user = self.db_user
1254
1257
1255 user_log = UserModel().get_user_log(c.user, filter_term=None)
1258 user_log = UserModel().get_user_log(c.user, filter_term=None)
1256
1259
1257 audit_log_data = {}
1260 audit_log_data = {}
1258 for entry in user_log:
1261 for entry in user_log:
1259 audit_log_data[entry.user_log_id] = entry.get_dict()
1262 audit_log_data[entry.user_log_id] = entry.get_dict()
1260
1263
1261 response = Response(json.dumps(audit_log_data, indent=4))
1264 response = Response(json.dumps(audit_log_data, indent=4))
1262 response.content_disposition = str(
1265 response.content_disposition = str(
1263 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1266 'attachment; filename=%s' % 'user_{}_audit_logs.json'.format(c.user.user_id))
1264 response.content_type = 'application/json'
1267 response.content_type = 'application/json'
1265
1268
1266 return response
1269 return response
1267
1270
1268 @LoginRequired()
1271 @LoginRequired()
1269 @HasPermissionAllDecorator('hg.admin')
1272 @HasPermissionAllDecorator('hg.admin')
1270 @view_config(
1273 @view_config(
1271 route_name='edit_user_perms_summary', request_method='GET',
1274 route_name='edit_user_perms_summary', request_method='GET',
1272 renderer='rhodecode:templates/admin/users/user_edit.mako')
1275 renderer='rhodecode:templates/admin/users/user_edit.mako')
1273 def user_perms_summary(self):
1276 def user_perms_summary(self):
1274 _ = self.request.translate
1277 _ = self.request.translate
1275 c = self.load_default_context()
1278 c = self.load_default_context()
1276 c.user = self.db_user
1279 c.user = self.db_user
1277
1280
1278 c.active = 'perms_summary'
1281 c.active = 'perms_summary'
1279 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1282 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1280
1283
1281 return self._get_template_context(c)
1284 return self._get_template_context(c)
1282
1285
1283 @LoginRequired()
1286 @LoginRequired()
1284 @HasPermissionAllDecorator('hg.admin')
1287 @HasPermissionAllDecorator('hg.admin')
1285 @view_config(
1288 @view_config(
1286 route_name='edit_user_perms_summary_json', request_method='GET',
1289 route_name='edit_user_perms_summary_json', request_method='GET',
1287 renderer='json_ext')
1290 renderer='json_ext')
1288 def user_perms_summary_json(self):
1291 def user_perms_summary_json(self):
1289 self.load_default_context()
1292 self.load_default_context()
1290 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1293 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1291
1294
1292 return perm_user.permissions
1295 return perm_user.permissions
1293
1296
1294 @LoginRequired()
1297 @LoginRequired()
1295 @HasPermissionAllDecorator('hg.admin')
1298 @HasPermissionAllDecorator('hg.admin')
1296 @view_config(
1299 @view_config(
1297 route_name='edit_user_caches', request_method='GET',
1300 route_name='edit_user_caches', request_method='GET',
1298 renderer='rhodecode:templates/admin/users/user_edit.mako')
1301 renderer='rhodecode:templates/admin/users/user_edit.mako')
1299 def user_caches(self):
1302 def user_caches(self):
1300 _ = self.request.translate
1303 _ = self.request.translate
1301 c = self.load_default_context()
1304 c = self.load_default_context()
1302 c.user = self.db_user
1305 c.user = self.db_user
1303
1306
1304 c.active = 'caches'
1307 c.active = 'caches'
1305 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1308 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1306
1309
1307 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1310 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1308 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1311 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1309 c.backend = c.region.backend
1312 c.backend = c.region.backend
1310 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1313 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1311
1314
1312 return self._get_template_context(c)
1315 return self._get_template_context(c)
1313
1316
1314 @LoginRequired()
1317 @LoginRequired()
1315 @HasPermissionAllDecorator('hg.admin')
1318 @HasPermissionAllDecorator('hg.admin')
1316 @CSRFRequired()
1319 @CSRFRequired()
1317 @view_config(
1320 @view_config(
1318 route_name='edit_user_caches_update', request_method='POST')
1321 route_name='edit_user_caches_update', request_method='POST')
1319 def user_caches_update(self):
1322 def user_caches_update(self):
1320 _ = self.request.translate
1323 _ = self.request.translate
1321 c = self.load_default_context()
1324 c = self.load_default_context()
1322 c.user = self.db_user
1325 c.user = self.db_user
1323
1326
1324 c.active = 'caches'
1327 c.active = 'caches'
1325 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1328 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1326
1329
1327 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1330 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1328 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1331 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1329
1332
1330 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1333 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1331
1334
1332 return HTTPFound(h.route_path(
1335 return HTTPFound(h.route_path(
1333 'edit_user_caches', user_id=c.user.user_id))
1336 'edit_user_caches', user_id=c.user.user_id))
@@ -1,156 +1,159 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22
22
23 from pyramid.httpexceptions import HTTPFound
23 from pyramid.httpexceptions import HTTPFound
24 from pyramid.view import view_config
24 from pyramid.view import view_config
25
25
26 from rhodecode.apps._base import BaseAppView, DataGridAppView
26 from rhodecode.apps._base import BaseAppView, DataGridAppView
27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
27 from rhodecode.apps.ssh_support import SshKeyFileChangeEvent
28 from rhodecode.events import trigger
28 from rhodecode.events import trigger
29 from rhodecode.lib import helpers as h
29 from rhodecode.lib import helpers as h
30 from rhodecode.lib import audit_logger
30 from rhodecode.lib import audit_logger
31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
31 from rhodecode.lib.auth import LoginRequired, NotAnonymous, CSRFRequired
32 from rhodecode.model.db import IntegrityError, UserSshKeys
32 from rhodecode.model.db import IntegrityError, UserSshKeys
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34 from rhodecode.model.ssh_key import SshKeyModel
34 from rhodecode.model.ssh_key import SshKeyModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
39 class MyAccountSshKeysView(BaseAppView, DataGridAppView):
40
40
41 def load_default_context(self):
41 def load_default_context(self):
42 c = self._get_local_tmpl_context()
42 c = self._get_local_tmpl_context()
43 c.user = c.auth_user.get_instance()
43 c.user = c.auth_user.get_instance()
44
44
45 c.ssh_enabled = self.request.registry.settings.get(
45 c.ssh_enabled = self.request.registry.settings.get(
46 'ssh.generate_authorized_keyfile')
46 'ssh.generate_authorized_keyfile')
47
47
48 return c
48 return c
49
49
50 @LoginRequired()
50 @LoginRequired()
51 @NotAnonymous()
51 @NotAnonymous()
52 @view_config(
52 @view_config(
53 route_name='my_account_ssh_keys', request_method='GET',
53 route_name='my_account_ssh_keys', request_method='GET',
54 renderer='rhodecode:templates/admin/my_account/my_account.mako')
54 renderer='rhodecode:templates/admin/my_account/my_account.mako')
55 def my_account_ssh_keys(self):
55 def my_account_ssh_keys(self):
56 _ = self.request.translate
56 _ = self.request.translate
57
57
58 c = self.load_default_context()
58 c = self.load_default_context()
59 c.active = 'ssh_keys'
59 c.active = 'ssh_keys'
60 c.default_key = self.request.GET.get('default_key')
60 c.default_key = self.request.GET.get('default_key')
61 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
61 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
62 return self._get_template_context(c)
62 return self._get_template_context(c)
63
63
64 @LoginRequired()
64 @LoginRequired()
65 @NotAnonymous()
65 @NotAnonymous()
66 @view_config(
66 @view_config(
67 route_name='my_account_ssh_keys_generate', request_method='GET',
67 route_name='my_account_ssh_keys_generate', request_method='GET',
68 renderer='rhodecode:templates/admin/my_account/my_account.mako')
68 renderer='rhodecode:templates/admin/my_account/my_account.mako')
69 def ssh_keys_generate_keypair(self):
69 def ssh_keys_generate_keypair(self):
70 _ = self.request.translate
70 _ = self.request.translate
71 c = self.load_default_context()
71 c = self.load_default_context()
72
72
73 c.active = 'ssh_keys_generate'
73 c.active = 'ssh_keys_generate'
74 if c.ssh_key_generator_enabled:
74 if c.ssh_key_generator_enabled:
75 private_format = self.request.GET.get('private_format') \
76 or SshKeyModel.DEFAULT_PRIVATE_KEY_FORMAT
75 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
77 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
76 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
78 c.private, c.public = SshKeyModel().generate_keypair(
79 comment=comment, private_format=private_format)
77 c.target_form_url = h.route_path(
80 c.target_form_url = h.route_path(
78 'my_account_ssh_keys', _query=dict(default_key=c.public))
81 'my_account_ssh_keys', _query=dict(default_key=c.public))
79 return self._get_template_context(c)
82 return self._get_template_context(c)
80
83
81 @LoginRequired()
84 @LoginRequired()
82 @NotAnonymous()
85 @NotAnonymous()
83 @CSRFRequired()
86 @CSRFRequired()
84 @view_config(
87 @view_config(
85 route_name='my_account_ssh_keys_add', request_method='POST',)
88 route_name='my_account_ssh_keys_add', request_method='POST',)
86 def my_account_ssh_keys_add(self):
89 def my_account_ssh_keys_add(self):
87 _ = self.request.translate
90 _ = self.request.translate
88 c = self.load_default_context()
91 c = self.load_default_context()
89
92
90 user_data = c.user.get_api_data()
93 user_data = c.user.get_api_data()
91 key_data = self.request.POST.get('key_data')
94 key_data = self.request.POST.get('key_data')
92 description = self.request.POST.get('description')
95 description = self.request.POST.get('description')
93 fingerprint = 'unknown'
96 fingerprint = 'unknown'
94 try:
97 try:
95 if not key_data:
98 if not key_data:
96 raise ValueError('Please add a valid public key')
99 raise ValueError('Please add a valid public key')
97
100
98 key = SshKeyModel().parse_key(key_data.strip())
101 key = SshKeyModel().parse_key(key_data.strip())
99 fingerprint = key.hash_md5()
102 fingerprint = key.hash_md5()
100
103
101 ssh_key = SshKeyModel().create(
104 ssh_key = SshKeyModel().create(
102 c.user.user_id, fingerprint, key.keydata, description)
105 c.user.user_id, fingerprint, key.keydata, description)
103 ssh_key_data = ssh_key.get_api_data()
106 ssh_key_data = ssh_key.get_api_data()
104
107
105 audit_logger.store_web(
108 audit_logger.store_web(
106 'user.edit.ssh_key.add', action_data={
109 'user.edit.ssh_key.add', action_data={
107 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
110 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
108 user=self._rhodecode_user, )
111 user=self._rhodecode_user, )
109 Session().commit()
112 Session().commit()
110
113
111 # Trigger an event on change of keys.
114 # Trigger an event on change of keys.
112 trigger(SshKeyFileChangeEvent(), self.request.registry)
115 trigger(SshKeyFileChangeEvent(), self.request.registry)
113
116
114 h.flash(_("Ssh Key successfully created"), category='success')
117 h.flash(_("Ssh Key successfully created"), category='success')
115
118
116 except IntegrityError:
119 except IntegrityError:
117 log.exception("Exception during ssh key saving")
120 log.exception("Exception during ssh key saving")
118 err = 'Such key with fingerprint `{}` already exists, ' \
121 err = 'Such key with fingerprint `{}` already exists, ' \
119 'please use a different one'.format(fingerprint)
122 'please use a different one'.format(fingerprint)
120 h.flash(_('An error occurred during ssh key saving: {}').format(err),
123 h.flash(_('An error occurred during ssh key saving: {}').format(err),
121 category='error')
124 category='error')
122 except Exception as e:
125 except Exception as e:
123 log.exception("Exception during ssh key saving")
126 log.exception("Exception during ssh key saving")
124 h.flash(_('An error occurred during ssh key saving: {}').format(e),
127 h.flash(_('An error occurred during ssh key saving: {}').format(e),
125 category='error')
128 category='error')
126
129
127 return HTTPFound(h.route_path('my_account_ssh_keys'))
130 return HTTPFound(h.route_path('my_account_ssh_keys'))
128
131
129 @LoginRequired()
132 @LoginRequired()
130 @NotAnonymous()
133 @NotAnonymous()
131 @CSRFRequired()
134 @CSRFRequired()
132 @view_config(
135 @view_config(
133 route_name='my_account_ssh_keys_delete', request_method='POST')
136 route_name='my_account_ssh_keys_delete', request_method='POST')
134 def my_account_ssh_keys_delete(self):
137 def my_account_ssh_keys_delete(self):
135 _ = self.request.translate
138 _ = self.request.translate
136 c = self.load_default_context()
139 c = self.load_default_context()
137
140
138 user_data = c.user.get_api_data()
141 user_data = c.user.get_api_data()
139
142
140 del_ssh_key = self.request.POST.get('del_ssh_key')
143 del_ssh_key = self.request.POST.get('del_ssh_key')
141
144
142 if del_ssh_key:
145 if del_ssh_key:
143 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
146 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
144 ssh_key_data = ssh_key.get_api_data()
147 ssh_key_data = ssh_key.get_api_data()
145
148
146 SshKeyModel().delete(del_ssh_key, c.user.user_id)
149 SshKeyModel().delete(del_ssh_key, c.user.user_id)
147 audit_logger.store_web(
150 audit_logger.store_web(
148 'user.edit.ssh_key.delete', action_data={
151 'user.edit.ssh_key.delete', action_data={
149 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
152 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
150 user=self._rhodecode_user,)
153 user=self._rhodecode_user,)
151 Session().commit()
154 Session().commit()
152 # Trigger an event on change of keys.
155 # Trigger an event on change of keys.
153 trigger(SshKeyFileChangeEvent(), self.request.registry)
156 trigger(SshKeyFileChangeEvent(), self.request.registry)
154 h.flash(_("Ssh key successfully deleted"), category='success')
157 h.flash(_("Ssh key successfully deleted"), category='success')
155
158
156 return HTTPFound(h.route_path('my_account_ssh_keys'))
159 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -1,136 +1,144 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2013-2019 RhodeCode GmbH
3 # Copyright (C) 2013-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
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
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
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/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import traceback
22 import traceback
23
23
24 import sshpubkeys
24 import sshpubkeys
25 import sshpubkeys.exceptions
25 import sshpubkeys.exceptions
26
26
27 from cryptography.hazmat.primitives.asymmetric import rsa
27 from cryptography.hazmat.primitives.asymmetric import rsa
28 from cryptography.hazmat.primitives import serialization as crypto_serialization
28 from cryptography.hazmat.primitives import serialization as crypto_serialization
29 from cryptography.hazmat.backends import default_backend as crypto_default_backend
29 from cryptography.hazmat.backends import default_backend as crypto_default_backend
30
30
31 from rhodecode.model import BaseModel
31 from rhodecode.model import BaseModel
32 from rhodecode.model.db import UserSshKeys
32 from rhodecode.model.db import UserSshKeys
33 from rhodecode.model.meta import Session
33 from rhodecode.model.meta import Session
34
34
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class SshKeyModel(BaseModel):
39 class SshKeyModel(BaseModel):
40 cls = UserSshKeys
40 cls = UserSshKeys
41 DEFAULT_PRIVATE_KEY_FORMAT = 'pkcs8'
41
42
42 def parse_key(self, key_data):
43 def parse_key(self, key_data):
43 """
44 """
44 print(ssh.bits) # 768
45 print(ssh.bits) # 768
45 print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86
46 print(ssh.hash_md5()) # 56:84:1e:90:08:3b:60:c7:29:70:5f:5e:25:a6:3b:86
46 print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM
47 print(ssh.hash_sha256()) # SHA256:xk3IEJIdIoR9MmSRXTP98rjDdZocmXJje/28ohMQEwM
47 print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA
48 print(ssh.hash_sha512()) # SHA512:1C3lNBhjpDVQe39hnyy+xvlZYU3IPwzqK1rVneGavy6O3/ebjEQSFvmeWoyMTplIanmUK1hmr9nA8Skmj516HA
48 print(ssh.comment) # ojar@ojar-laptop
49 print(ssh.comment) # ojar@ojar-laptop
49 print(ssh.options_raw) # None (string of optional options at the beginning of public key)
50 print(ssh.options_raw) # None (string of optional options at the beginning of public key)
50 print(ssh.options) # None (options as a dictionary, parsed and validated)
51 print(ssh.options) # None (options as a dictionary, parsed and validated)
51
52
52 :param key_data:
53 :param key_data:
53 :return:
54 :return:
54 """
55 """
55 ssh = sshpubkeys.SSHKey(strict_mode=True)
56 ssh = sshpubkeys.SSHKey(strict_mode=True)
56 try:
57 try:
57 ssh.parse(key_data)
58 ssh.parse(key_data)
58 return ssh
59 return ssh
59 except sshpubkeys.exceptions.InvalidKeyException as err:
60 except sshpubkeys.exceptions.InvalidKeyException as err:
60 log.error("Invalid key: %s", err)
61 log.error("Invalid key: %s", err)
61 raise
62 raise
62 except NotImplementedError as err:
63 except NotImplementedError as err:
63 log.error("Invalid key type: %s", err)
64 log.error("Invalid key type: %s", err)
64 raise
65 raise
65 except Exception as err:
66 except Exception as err:
66 log.error("Key Parse error: %s", err)
67 log.error("Key Parse error: %s", err)
67 raise
68 raise
68
69
69 def generate_keypair(self, comment=None):
70 def generate_keypair(self, comment=None, private_format=DEFAULT_PRIVATE_KEY_FORMAT):
70
71
71 key = rsa.generate_private_key(
72 key = rsa.generate_private_key(
72 backend=crypto_default_backend(),
73 backend=crypto_default_backend(),
73 public_exponent=65537,
74 public_exponent=65537,
74 key_size=2048
75 key_size=2048
75 )
76 )
77 if private_format == self.DEFAULT_PRIVATE_KEY_FORMAT:
78 private_format = crypto_serialization.PrivateFormat.PKCS8
79 else:
80 # legacy format that can be used by older systems, use if pkcs8 have
81 # problems
82 private_format = crypto_serialization.PrivateFormat.TraditionalOpenSSL
83
76 private_key = key.private_bytes(
84 private_key = key.private_bytes(
77 crypto_serialization.Encoding.PEM,
85 crypto_serialization.Encoding.PEM,
78 crypto_serialization.PrivateFormat.PKCS8,
86 private_format,
79 crypto_serialization.NoEncryption())
87 crypto_serialization.NoEncryption())
80 public_key = key.public_key().public_bytes(
88 public_key = key.public_key().public_bytes(
81 crypto_serialization.Encoding.OpenSSH,
89 crypto_serialization.Encoding.OpenSSH,
82 crypto_serialization.PublicFormat.OpenSSH
90 crypto_serialization.PublicFormat.OpenSSH
83 )
91 )
84
92
85 if comment:
93 if comment:
86 public_key = public_key + " " + comment
94 public_key = public_key + " " + comment
87 return private_key, public_key
95 return private_key, public_key
88
96
89 def create(self, user, fingerprint, key_data, description):
97 def create(self, user, fingerprint, key_data, description):
90 """
98 """
91 """
99 """
92 user = self._get_user(user)
100 user = self._get_user(user)
93
101
94 new_ssh_key = UserSshKeys()
102 new_ssh_key = UserSshKeys()
95 new_ssh_key.ssh_key_fingerprint = fingerprint
103 new_ssh_key.ssh_key_fingerprint = fingerprint
96 new_ssh_key.ssh_key_data = key_data
104 new_ssh_key.ssh_key_data = key_data
97 new_ssh_key.user_id = user.user_id
105 new_ssh_key.user_id = user.user_id
98 new_ssh_key.description = description
106 new_ssh_key.description = description
99
107
100 Session().add(new_ssh_key)
108 Session().add(new_ssh_key)
101
109
102 return new_ssh_key
110 return new_ssh_key
103
111
104 def delete(self, ssh_key_id, user=None):
112 def delete(self, ssh_key_id, user=None):
105 """
113 """
106 Deletes given api_key, if user is set it also filters the object for
114 Deletes given api_key, if user is set it also filters the object for
107 deletion by given user.
115 deletion by given user.
108 """
116 """
109 ssh_key = UserSshKeys.query().filter(
117 ssh_key = UserSshKeys.query().filter(
110 UserSshKeys.ssh_key_id == ssh_key_id)
118 UserSshKeys.ssh_key_id == ssh_key_id)
111
119
112 if user:
120 if user:
113 user = self._get_user(user)
121 user = self._get_user(user)
114 ssh_key = ssh_key.filter(UserSshKeys.user_id == user.user_id)
122 ssh_key = ssh_key.filter(UserSshKeys.user_id == user.user_id)
115 ssh_key = ssh_key.scalar()
123 ssh_key = ssh_key.scalar()
116
124
117 if ssh_key:
125 if ssh_key:
118 try:
126 try:
119 Session().delete(ssh_key)
127 Session().delete(ssh_key)
120 except Exception:
128 except Exception:
121 log.error(traceback.format_exc())
129 log.error(traceback.format_exc())
122 raise
130 raise
123
131
124 def get_ssh_keys(self, user):
132 def get_ssh_keys(self, user):
125 user = self._get_user(user)
133 user = self._get_user(user)
126 user_ssh_keys = UserSshKeys.query()\
134 user_ssh_keys = UserSshKeys.query()\
127 .filter(UserSshKeys.user_id == user.user_id)
135 .filter(UserSshKeys.user_id == user.user_id)
128 user_ssh_keys = user_ssh_keys.order_by(UserSshKeys.ssh_key_id)
136 user_ssh_keys = user_ssh_keys.order_by(UserSshKeys.ssh_key_id)
129 return user_ssh_keys
137 return user_ssh_keys
130
138
131 def get_ssh_key_by_fingerprint(self, ssh_key_fingerprint):
139 def get_ssh_key_by_fingerprint(self, ssh_key_fingerprint):
132 user_ssh_key = UserSshKeys.query()\
140 user_ssh_key = UserSshKeys.query()\
133 .filter(UserSshKeys.ssh_key_fingerprint == ssh_key_fingerprint)\
141 .filter(UserSshKeys.ssh_key_fingerprint == ssh_key_fingerprint)\
134 .first()
142 .first()
135
143
136 return user_ssh_key
144 return user_ssh_key
@@ -1,63 +1,63 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 <div class="panel-heading">
4 <div class="panel-heading">
5 <h3 class="panel-title">
5 <h3 class="panel-title">
6 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
6 ${base.gravatar_with_user(c.user.username, 16, tooltip=False, _class='pull-left')}
7 &nbsp;- ${_('New SSH Key generation')}
7 &nbsp;- ${_('New SSH Key generation')}
8 </h3>
8 </h3>
9 </div>
9 </div>
10 <div class="panel-body">
10 <div class="panel-body">
11 %if c.ssh_enabled and c.ssh_key_generator_enabled:
11 %if c.ssh_enabled and c.ssh_key_generator_enabled:
12 <p>
12 <p>
13 ${_('Below is a 2048 bit generated SSH RSA key.')}<br/>
13 ${_('Below is a 2048 bit generated SSH RSA key.')}<br/>${_('If you use older systems please try to generate a')} <a href="${h.current_route_path(request, private_format='legacy')}">${_('legacy format')}</a> ssh key.<br/>
14 ${_('If You wish to use it to access RhodeCode via the SSH please save the private key and click `Use this generated key` at the bottom.')}
14 ${_('If You wish to use it to access RhodeCode via the SSH please save the private key and click `Use this generated key` at the bottom.')}
15 </p>
15 </p>
16 <h4>${_('Private key')}</h4>
16 <h4>${_('Private key')}</h4>
17 <pre>
17 <pre>
18 # Save the below content as
18 # Save the below content as
19 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_priv.key
19 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_priv.key
20 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_priv.key
20 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_priv.key
21 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
21 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
22
22
23 # Change permissions to 0600 to make it secure, and usable.
23 # Change permissions to 0600 to make it secure, and usable.
24 e.g chmod 0600 /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
24 e.g chmod 0600 /home/{username}/.ssh/id_rsa_rhodecode_access_priv.key
25 </pre>
25 </pre>
26
26
27 <div>
27 <div>
28 <textarea style="height: 300px">${c.private}</textarea>
28 <textarea style="height: 300px">${c.private}</textarea>
29 </div>
29 </div>
30 <br/>
30 <br/>
31
31
32 <h4>${_('Public key')}</h4>
32 <h4>${_('Public key')}</h4>
33 <pre>
33 <pre>
34 # Save the below content as
34 # Save the below content as
35 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_pub.key
35 # Windows: /Users/{username}/.ssh/id_rsa_rhodecode_access_pub.key
36 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_pub.key
36 # macOS: /Users/{yourname}/.ssh/id_rsa_rhodecode_access_pub.key
37 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_pub.key
37 # Linux: /home/{username}/.ssh/id_rsa_rhodecode_access_pub.key
38 </pre>
38 </pre>
39
39
40 <input type="text" value="${c.public}" class="large text" size="100"/>
40 <input type="text" value="${c.public}" class="large text" size="100"/>
41 <p>
41 <p>
42 % if hasattr(c, 'target_form_url'):
42 % if hasattr(c, 'target_form_url'):
43 <a href="${c.target_form_url}">${_('Use this generated key')}.</a>
43 <a href="${c.target_form_url}">${_('Use this generated key')}.</a>
44 % else:
44 % else:
45 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Use this generated key')}.</a>
45 <a href="${h.route_path('edit_user_ssh_keys', user_id=c.user.user_id, _query=dict(default_key=c.public))}">${_('Use this generated key')}.</a>
46 % endif
46 % endif
47 ${_('Confirmation required on the next screen')}.
47 ${_('Confirmation required on the next screen')}.
48 </p>
48 </p>
49 % else:
49 % else:
50 <h2>
50 <h2>
51 ${_('SSH key generator has been disabled.')}
51 ${_('SSH key generator has been disabled.')}
52 </h2>
52 </h2>
53 % endif
53 % endif
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <script>
57 <script>
58
58
59 $(document).ready(function(){
59 $(document).ready(function(){
60
60
61
61
62 });
62 });
63 </script>
63 </script>
General Comments 0
You need to be logged in to leave comments. Login now