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