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