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