##// END OF EJS Templates
ssh-keys: further sanitize data of inputted SSH keys.
marcink -
r2751:ea9069b3 stable
parent child Browse files
Show More
@@ -1,1191 +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 820 fingerprint = 'unknown'
821 821 try:
822 822 if not key_data:
823 823 raise ValueError('Please add a valid public key')
824 824
825 825 key = SshKeyModel().parse_key(key_data.strip())
826 826 fingerprint = key.hash_md5()
827 827
828 828 ssh_key = SshKeyModel().create(
829 c.user.user_id, fingerprint, key_data, description)
829 c.user.user_id, fingerprint, key.keydata, description)
830 830 ssh_key_data = ssh_key.get_api_data()
831 831
832 832 audit_logger.store_web(
833 833 'user.edit.ssh_key.add', action_data={
834 834 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
835 835 user=self._rhodecode_user, )
836 836 Session().commit()
837 837
838 838 # Trigger an event on change of keys.
839 839 trigger(SshKeyFileChangeEvent(), self.request.registry)
840 840
841 841 h.flash(_("Ssh Key successfully created"), category='success')
842 842
843 843 except IntegrityError:
844 844 log.exception("Exception during ssh key saving")
845 845 err = 'Such key with fingerprint `{}` already exists, ' \
846 846 'please use a different one'.format(fingerprint)
847 847 h.flash(_('An error occurred during ssh key saving: {}').format(err),
848 848 category='error')
849 849 except Exception as e:
850 850 log.exception("Exception during ssh key saving")
851 851 h.flash(_('An error occurred during ssh key saving: {}').format(e),
852 852 category='error')
853 853
854 854 return HTTPFound(
855 855 h.route_path('edit_user_ssh_keys', user_id=user_id))
856 856
857 857 @LoginRequired()
858 858 @HasPermissionAllDecorator('hg.admin')
859 859 @CSRFRequired()
860 860 @view_config(
861 861 route_name='edit_user_ssh_keys_delete', request_method='POST')
862 862 def ssh_keys_delete(self):
863 863 _ = self.request.translate
864 864 c = self.load_default_context()
865 865
866 866 user_id = self.db_user_id
867 867 c.user = self.db_user
868 868
869 869 user_data = c.user.get_api_data()
870 870
871 871 del_ssh_key = self.request.POST.get('del_ssh_key')
872 872
873 873 if del_ssh_key:
874 874 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
875 875 ssh_key_data = ssh_key.get_api_data()
876 876
877 877 SshKeyModel().delete(del_ssh_key, c.user.user_id)
878 878 audit_logger.store_web(
879 879 'user.edit.ssh_key.delete', action_data={
880 880 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
881 881 user=self._rhodecode_user,)
882 882 Session().commit()
883 883 # Trigger an event on change of keys.
884 884 trigger(SshKeyFileChangeEvent(), self.request.registry)
885 885 h.flash(_("Ssh key successfully deleted"), category='success')
886 886
887 887 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
888 888
889 889 @LoginRequired()
890 890 @HasPermissionAllDecorator('hg.admin')
891 891 @view_config(
892 892 route_name='edit_user_emails', request_method='GET',
893 893 renderer='rhodecode:templates/admin/users/user_edit.mako')
894 894 def emails(self):
895 895 _ = self.request.translate
896 896 c = self.load_default_context()
897 897 c.user = self.db_user
898 898
899 899 c.active = 'emails'
900 900 c.user_email_map = UserEmailMap.query() \
901 901 .filter(UserEmailMap.user == c.user).all()
902 902
903 903 return self._get_template_context(c)
904 904
905 905 @LoginRequired()
906 906 @HasPermissionAllDecorator('hg.admin')
907 907 @CSRFRequired()
908 908 @view_config(
909 909 route_name='edit_user_emails_add', request_method='POST')
910 910 def emails_add(self):
911 911 _ = self.request.translate
912 912 c = self.load_default_context()
913 913
914 914 user_id = self.db_user_id
915 915 c.user = self.db_user
916 916
917 917 email = self.request.POST.get('new_email')
918 918 user_data = c.user.get_api_data()
919 919 try:
920 920
921 921 form = UserExtraEmailForm(self.request.translate)()
922 922 data = form.to_python({'email': email})
923 923 email = data['email']
924 924
925 925 UserModel().add_extra_email(c.user.user_id, email)
926 926 audit_logger.store_web(
927 927 'user.edit.email.add',
928 928 action_data={'email': email, 'user': user_data},
929 929 user=self._rhodecode_user)
930 930 Session().commit()
931 931 h.flash(_("Added new email address `%s` for user account") % email,
932 932 category='success')
933 933 except formencode.Invalid as error:
934 934 h.flash(h.escape(error.error_dict['email']), category='error')
935 935 except IntegrityError:
936 936 log.warning("Email %s already exists", email)
937 937 h.flash(_('Email `{}` is already registered for another user.').format(email),
938 938 category='error')
939 939 except Exception:
940 940 log.exception("Exception during email saving")
941 941 h.flash(_('An error occurred during email saving'),
942 942 category='error')
943 943 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
944 944
945 945 @LoginRequired()
946 946 @HasPermissionAllDecorator('hg.admin')
947 947 @CSRFRequired()
948 948 @view_config(
949 949 route_name='edit_user_emails_delete', request_method='POST')
950 950 def emails_delete(self):
951 951 _ = self.request.translate
952 952 c = self.load_default_context()
953 953
954 954 user_id = self.db_user_id
955 955 c.user = self.db_user
956 956
957 957 email_id = self.request.POST.get('del_email_id')
958 958 user_model = UserModel()
959 959
960 960 email = UserEmailMap.query().get(email_id).email
961 961 user_data = c.user.get_api_data()
962 962 user_model.delete_extra_email(c.user.user_id, email_id)
963 963 audit_logger.store_web(
964 964 'user.edit.email.delete',
965 965 action_data={'email': email, 'user': user_data},
966 966 user=self._rhodecode_user)
967 967 Session().commit()
968 968 h.flash(_("Removed email address from user account"),
969 969 category='success')
970 970 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
971 971
972 972 @LoginRequired()
973 973 @HasPermissionAllDecorator('hg.admin')
974 974 @view_config(
975 975 route_name='edit_user_ips', request_method='GET',
976 976 renderer='rhodecode:templates/admin/users/user_edit.mako')
977 977 def ips(self):
978 978 _ = self.request.translate
979 979 c = self.load_default_context()
980 980 c.user = self.db_user
981 981
982 982 c.active = 'ips'
983 983 c.user_ip_map = UserIpMap.query() \
984 984 .filter(UserIpMap.user == c.user).all()
985 985
986 986 c.inherit_default_ips = c.user.inherit_default_permissions
987 987 c.default_user_ip_map = UserIpMap.query() \
988 988 .filter(UserIpMap.user == User.get_default_user()).all()
989 989
990 990 return self._get_template_context(c)
991 991
992 992 @LoginRequired()
993 993 @HasPermissionAllDecorator('hg.admin')
994 994 @CSRFRequired()
995 995 @view_config(
996 996 route_name='edit_user_ips_add', request_method='POST')
997 997 # NOTE(marcink): this view is allowed for default users, as we can
998 998 # edit their IP white list
999 999 def ips_add(self):
1000 1000 _ = self.request.translate
1001 1001 c = self.load_default_context()
1002 1002
1003 1003 user_id = self.db_user_id
1004 1004 c.user = self.db_user
1005 1005
1006 1006 user_model = UserModel()
1007 1007 desc = self.request.POST.get('description')
1008 1008 try:
1009 1009 ip_list = user_model.parse_ip_range(
1010 1010 self.request.POST.get('new_ip'))
1011 1011 except Exception as e:
1012 1012 ip_list = []
1013 1013 log.exception("Exception during ip saving")
1014 1014 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1015 1015 category='error')
1016 1016 added = []
1017 1017 user_data = c.user.get_api_data()
1018 1018 for ip in ip_list:
1019 1019 try:
1020 1020 form = UserExtraIpForm(self.request.translate)()
1021 1021 data = form.to_python({'ip': ip})
1022 1022 ip = data['ip']
1023 1023
1024 1024 user_model.add_extra_ip(c.user.user_id, ip, desc)
1025 1025 audit_logger.store_web(
1026 1026 'user.edit.ip.add',
1027 1027 action_data={'ip': ip, 'user': user_data},
1028 1028 user=self._rhodecode_user)
1029 1029 Session().commit()
1030 1030 added.append(ip)
1031 1031 except formencode.Invalid as error:
1032 1032 msg = error.error_dict['ip']
1033 1033 h.flash(msg, category='error')
1034 1034 except Exception:
1035 1035 log.exception("Exception during ip saving")
1036 1036 h.flash(_('An error occurred during ip saving'),
1037 1037 category='error')
1038 1038 if added:
1039 1039 h.flash(
1040 1040 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1041 1041 category='success')
1042 1042 if 'default_user' in self.request.POST:
1043 1043 # case for editing global IP list we do it for 'DEFAULT' user
1044 1044 raise HTTPFound(h.route_path('admin_permissions_ips'))
1045 1045 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1046 1046
1047 1047 @LoginRequired()
1048 1048 @HasPermissionAllDecorator('hg.admin')
1049 1049 @CSRFRequired()
1050 1050 @view_config(
1051 1051 route_name='edit_user_ips_delete', request_method='POST')
1052 1052 # NOTE(marcink): this view is allowed for default users, as we can
1053 1053 # edit their IP white list
1054 1054 def ips_delete(self):
1055 1055 _ = self.request.translate
1056 1056 c = self.load_default_context()
1057 1057
1058 1058 user_id = self.db_user_id
1059 1059 c.user = self.db_user
1060 1060
1061 1061 ip_id = self.request.POST.get('del_ip_id')
1062 1062 user_model = UserModel()
1063 1063 user_data = c.user.get_api_data()
1064 1064 ip = UserIpMap.query().get(ip_id).ip_addr
1065 1065 user_model.delete_extra_ip(c.user.user_id, ip_id)
1066 1066 audit_logger.store_web(
1067 1067 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1068 1068 user=self._rhodecode_user)
1069 1069 Session().commit()
1070 1070 h.flash(_("Removed ip address from user whitelist"), category='success')
1071 1071
1072 1072 if 'default_user' in self.request.POST:
1073 1073 # case for editing global IP list we do it for 'DEFAULT' user
1074 1074 raise HTTPFound(h.route_path('admin_permissions_ips'))
1075 1075 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1076 1076
1077 1077 @LoginRequired()
1078 1078 @HasPermissionAllDecorator('hg.admin')
1079 1079 @view_config(
1080 1080 route_name='edit_user_groups_management', request_method='GET',
1081 1081 renderer='rhodecode:templates/admin/users/user_edit.mako')
1082 1082 def groups_management(self):
1083 1083 c = self.load_default_context()
1084 1084 c.user = self.db_user
1085 1085 c.data = c.user.group_member
1086 1086
1087 1087 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1088 1088 for group in c.user.group_member]
1089 1089 c.groups = json.dumps(groups)
1090 1090 c.active = 'groups'
1091 1091
1092 1092 return self._get_template_context(c)
1093 1093
1094 1094 @LoginRequired()
1095 1095 @HasPermissionAllDecorator('hg.admin')
1096 1096 @CSRFRequired()
1097 1097 @view_config(
1098 1098 route_name='edit_user_groups_management_updates', request_method='POST')
1099 1099 def groups_management_updates(self):
1100 1100 _ = self.request.translate
1101 1101 c = self.load_default_context()
1102 1102
1103 1103 user_id = self.db_user_id
1104 1104 c.user = self.db_user
1105 1105
1106 1106 user_groups = set(self.request.POST.getall('users_group_id'))
1107 1107 user_groups_objects = []
1108 1108
1109 1109 for ugid in user_groups:
1110 1110 user_groups_objects.append(
1111 1111 UserGroupModel().get_group(safe_int(ugid)))
1112 1112 user_group_model = UserGroupModel()
1113 1113 added_to_groups, removed_from_groups = \
1114 1114 user_group_model.change_groups(c.user, user_groups_objects)
1115 1115
1116 1116 user_data = c.user.get_api_data()
1117 1117 for user_group_id in added_to_groups:
1118 1118 user_group = UserGroup.get(user_group_id)
1119 1119 old_values = user_group.get_api_data()
1120 1120 audit_logger.store_web(
1121 1121 'user_group.edit.member.add',
1122 1122 action_data={'user': user_data, 'old_data': old_values},
1123 1123 user=self._rhodecode_user)
1124 1124
1125 1125 for user_group_id in removed_from_groups:
1126 1126 user_group = UserGroup.get(user_group_id)
1127 1127 old_values = user_group.get_api_data()
1128 1128 audit_logger.store_web(
1129 1129 'user_group.edit.member.delete',
1130 1130 action_data={'user': user_data, 'old_data': old_values},
1131 1131 user=self._rhodecode_user)
1132 1132
1133 1133 Session().commit()
1134 1134 c.active = 'user_groups_management'
1135 1135 h.flash(_("Groups successfully changed"), category='success')
1136 1136
1137 1137 return HTTPFound(h.route_path(
1138 1138 'edit_user_groups_management', user_id=user_id))
1139 1139
1140 1140 @LoginRequired()
1141 1141 @HasPermissionAllDecorator('hg.admin')
1142 1142 @view_config(
1143 1143 route_name='edit_user_audit_logs', request_method='GET',
1144 1144 renderer='rhodecode:templates/admin/users/user_edit.mako')
1145 1145 def user_audit_logs(self):
1146 1146 _ = self.request.translate
1147 1147 c = self.load_default_context()
1148 1148 c.user = self.db_user
1149 1149
1150 1150 c.active = 'audit'
1151 1151
1152 1152 p = safe_int(self.request.GET.get('page', 1), 1)
1153 1153
1154 1154 filter_term = self.request.GET.get('filter')
1155 1155 user_log = UserModel().get_user_log(c.user, filter_term)
1156 1156
1157 1157 def url_generator(**kw):
1158 1158 if filter_term:
1159 1159 kw['filter'] = filter_term
1160 1160 return self.request.current_route_path(_query=kw)
1161 1161
1162 1162 c.audit_logs = h.Page(
1163 1163 user_log, page=p, items_per_page=10, url=url_generator)
1164 1164 c.filter_term = filter_term
1165 1165 return self._get_template_context(c)
1166 1166
1167 1167 @LoginRequired()
1168 1168 @HasPermissionAllDecorator('hg.admin')
1169 1169 @view_config(
1170 1170 route_name='edit_user_perms_summary', request_method='GET',
1171 1171 renderer='rhodecode:templates/admin/users/user_edit.mako')
1172 1172 def user_perms_summary(self):
1173 1173 _ = self.request.translate
1174 1174 c = self.load_default_context()
1175 1175 c.user = self.db_user
1176 1176
1177 1177 c.active = 'perms_summary'
1178 1178 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1179 1179
1180 1180 return self._get_template_context(c)
1181 1181
1182 1182 @LoginRequired()
1183 1183 @HasPermissionAllDecorator('hg.admin')
1184 1184 @view_config(
1185 1185 route_name='edit_user_perms_summary_json', request_method='GET',
1186 1186 renderer='json_ext')
1187 1187 def user_perms_summary_json(self):
1188 1188 self.load_default_context()
1189 1189 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1190 1190
1191 1191 return perm_user.permissions
@@ -1,155 +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 c.user.user_id, fingerprint, key_data, description)
101 c.user.user_id, fingerprint, key.keydata, 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 117 err = 'Such key with fingerprint `{}` already exists, ' \
118 118 'please use a different one'.format(fingerprint)
119 119 h.flash(_('An error occurred during ssh key saving: {}').format(err),
120 120 category='error')
121 121 except Exception as e:
122 122 log.exception("Exception during ssh key saving")
123 123 h.flash(_('An error occurred during ssh key saving: {}').format(e),
124 124 category='error')
125 125
126 126 return HTTPFound(h.route_path('my_account_ssh_keys'))
127 127
128 128 @LoginRequired()
129 129 @NotAnonymous()
130 130 @CSRFRequired()
131 131 @view_config(
132 132 route_name='my_account_ssh_keys_delete', request_method='POST')
133 133 def my_account_ssh_keys_delete(self):
134 134 _ = self.request.translate
135 135 c = self.load_default_context()
136 136
137 137 user_data = c.user.get_api_data()
138 138
139 139 del_ssh_key = self.request.POST.get('del_ssh_key')
140 140
141 141 if del_ssh_key:
142 142 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
143 143 ssh_key_data = ssh_key.get_api_data()
144 144
145 145 SshKeyModel().delete(del_ssh_key, c.user.user_id)
146 146 audit_logger.store_web(
147 147 'user.edit.ssh_key.delete', action_data={
148 148 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
149 149 user=self._rhodecode_user,)
150 150 Session().commit()
151 151 # Trigger an event on change of keys.
152 152 trigger(SshKeyFileChangeEvent(), self.request.registry)
153 153 h.flash(_("Ssh key successfully deleted"), category='success')
154 154
155 155 return HTTPFound(h.route_path('my_account_ssh_keys'))
@@ -1,131 +1,132 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 os
22 22 import stat
23 23 import logging
24 24 import tempfile
25 25 import datetime
26 26
27 27 from . import config_keys
28 28 from rhodecode.model.db import true, joinedload, User, UserSshKeys
29 29
30 30
31 31 log = logging.getLogger(__name__)
32 32
33 33 HEADER = \
34 34 "# This file is managed by RhodeCode, please do not edit it manually. # \n" \
35 35 "# Current entries: {}, create date: UTC:{}.\n"
36 36
37 37 # Default SSH options for authorized_keys file, can be override via .ini
38 38 SSH_OPTS = 'no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding'
39 39
40 40
41 41 def get_all_active_keys():
42 42 result = UserSshKeys.query() \
43 43 .options(joinedload(UserSshKeys.user)) \
44 44 .filter(UserSshKeys.user != User.get_default_user()) \
45 45 .filter(User.active == true()) \
46 46 .all()
47 47 return result
48 48
49 49
50 50 def _generate_ssh_authorized_keys_file(
51 51 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts, debug):
52 52
53 53 authorized_keys_file_path = os.path.abspath(
54 54 os.path.expanduser(authorized_keys_file_path))
55 55
56 56 import rhodecode
57 57 all_active_keys = get_all_active_keys()
58 58
59 59 if allow_shell:
60 60 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --shell'
61 61 if debug:
62 62 ssh_wrapper_cmd = ssh_wrapper_cmd + ' --debug'
63 63
64 64 if not os.path.isfile(authorized_keys_file_path):
65 65 log.debug('Creating file at %s', authorized_keys_file_path)
66 66 with open(authorized_keys_file_path, 'w'):
67 67 pass
68 68
69 69 if not os.access(authorized_keys_file_path, os.R_OK):
70 70 raise OSError('Access to file {} is without read access'.format(
71 71 authorized_keys_file_path))
72 72
73 73 line_tmpl = '{ssh_opts},command="{wrapper_command} {ini_path} --user-id={user_id} --user={user} --key-id={user_key_id}" {key}\n'
74 74
75 75 fd, tmp_authorized_keys = tempfile.mkstemp(
76 76 '.authorized_keys_write',
77 77 dir=os.path.dirname(authorized_keys_file_path))
78 78
79 79 now = datetime.datetime.utcnow().isoformat()
80 80 keys_file = os.fdopen(fd, 'wb')
81 81 keys_file.write(HEADER.format(len(all_active_keys), now))
82 82 ini_path = rhodecode.CONFIG['__file__']
83 83
84 84 for user_key in all_active_keys:
85 85 username = user_key.user.username
86 86 user_id = user_key.user.user_id
87 87 # replace all newline from ends and inside
88 88 safe_key_data = user_key.ssh_key_data\
89 89 .strip()\
90 .replace('\n', ' ')\
90 .replace('\n', ' ') \
91 .replace('\t', ' ') \
91 92 .replace('\r', ' ')
92 93
93 94 line = line_tmpl.format(
94 95 ssh_opts=ssh_opts or SSH_OPTS,
95 96 wrapper_command=ssh_wrapper_cmd,
96 97 ini_path=ini_path,
97 98 user_id=user_id,
98 99 user=username,
99 100 user_key_id=user_key.ssh_key_id,
100 101 key=safe_key_data)
101 102
102 103 keys_file.write(line)
103 104 log.debug('addkey: Key added for user: `%s`', username)
104 105 keys_file.close()
105 106
106 107 # Explicitly setting read-only permissions to authorized_keys
107 108 os.chmod(tmp_authorized_keys, stat.S_IRUSR | stat.S_IWUSR)
108 109 # Rename is atomic operation
109 110 os.rename(tmp_authorized_keys, authorized_keys_file_path)
110 111
111 112
112 113 def generate_ssh_authorized_keys_file(registry):
113 114 log.info('Generating new authorized key file')
114 115
115 116 authorized_keys_file_path = registry.settings.get(
116 117 config_keys.authorized_keys_file_path)
117 118
118 119 ssh_wrapper_cmd = registry.settings.get(
119 120 config_keys.wrapper_cmd)
120 121 allow_shell = registry.settings.get(
121 122 config_keys.wrapper_allow_shell)
122 123 ssh_opts = registry.settings.get(
123 124 config_keys.authorized_keys_line_ssh_opts)
124 125 debug = registry.settings.get(
125 126 config_keys.enable_debug_logging)
126 127
127 128 _generate_ssh_authorized_keys_file(
128 129 authorized_keys_file_path, ssh_wrapper_cmd, allow_shell, ssh_opts,
129 130 debug)
130 131
131 132 return 0
General Comments 0
You need to be logged in to leave comments. Login now