##// END OF EJS Templates
auth/security: enforce that external users cannot reset their password.
marcink -
r3258:e5497c9f default
parent child Browse files
Show More
@@ -1,1242 +1,1242 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 from rhodecode.model.db import true
36 36
37 37 from rhodecode.lib import audit_logger, rc_cache
38 38 from rhodecode.lib.exceptions import (
39 39 UserCreationError, UserOwnsReposException, UserOwnsRepoGroupsException,
40 40 UserOwnsUserGroupsException, DefaultUserException)
41 41 from rhodecode.lib.ext_json import json
42 42 from rhodecode.lib.auth import (
43 43 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
44 44 from rhodecode.lib import helpers as h
45 45 from rhodecode.lib.utils2 import safe_int, safe_unicode, AttributeDict
46 46 from rhodecode.model.auth_token import AuthTokenModel
47 47 from rhodecode.model.forms import (
48 48 UserForm, UserIndividualPermissionsForm, UserPermissionsForm,
49 49 UserExtraEmailForm, UserExtraIpForm)
50 50 from rhodecode.model.permission import PermissionModel
51 51 from rhodecode.model.repo_group import RepoGroupModel
52 52 from rhodecode.model.ssh_key import SshKeyModel
53 53 from rhodecode.model.user import UserModel
54 54 from rhodecode.model.user_group import UserGroupModel
55 55 from rhodecode.model.db import (
56 56 or_, coalesce,IntegrityError, User, UserGroup, UserIpMap, UserEmailMap,
57 57 UserApiKeys, UserSshKeys, RepoGroup)
58 58 from rhodecode.model.meta import Session
59 59
60 60 log = logging.getLogger(__name__)
61 61
62 62
63 63 class AdminUsersView(BaseAppView, DataGridAppView):
64 64
65 65 def load_default_context(self):
66 66 c = self._get_local_tmpl_context()
67 67 return c
68 68
69 69 @LoginRequired()
70 70 @HasPermissionAllDecorator('hg.admin')
71 71 @view_config(
72 72 route_name='users', request_method='GET',
73 73 renderer='rhodecode:templates/admin/users/users.mako')
74 74 def users_list(self):
75 75 c = self.load_default_context()
76 76 return self._get_template_context(c)
77 77
78 78 @LoginRequired()
79 79 @HasPermissionAllDecorator('hg.admin')
80 80 @view_config(
81 81 # renderer defined below
82 82 route_name='users_data', request_method='GET',
83 83 renderer='json_ext', xhr=True)
84 84 def users_list_data(self):
85 85 self.load_default_context()
86 86 column_map = {
87 87 'first_name': 'name',
88 88 'last_name': 'lastname',
89 89 }
90 90 draw, start, limit = self._extract_chunk(self.request)
91 91 search_q, order_by, order_dir = self._extract_ordering(
92 92 self.request, column_map=column_map)
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 users_data_total_inactive_count = User.query()\
104 104 .filter(User.username != User.DEFAULT_USER) \
105 105 .filter(User.active != true())\
106 106 .count()
107 107
108 108 # json generate
109 109 base_q = User.query().filter(User.username != User.DEFAULT_USER)
110 110 base_inactive_q = base_q.filter(User.active != true())
111 111
112 112 if search_q:
113 113 like_expression = u'%{}%'.format(safe_unicode(search_q))
114 114 base_q = base_q.filter(or_(
115 115 User.username.ilike(like_expression),
116 116 User._email.ilike(like_expression),
117 117 User.name.ilike(like_expression),
118 118 User.lastname.ilike(like_expression),
119 119 ))
120 120 base_inactive_q = base_q.filter(User.active != true())
121 121
122 122 users_data_total_filtered_count = base_q.count()
123 123 users_data_total_filtered_inactive_count = base_inactive_q.count()
124 124
125 125 sort_col = getattr(User, order_by, None)
126 126 if sort_col:
127 127 if order_dir == 'asc':
128 128 # handle null values properly to order by NULL last
129 129 if order_by in ['last_activity']:
130 130 sort_col = coalesce(sort_col, datetime.date.max)
131 131 sort_col = sort_col.asc()
132 132 else:
133 133 # handle null values properly to order by NULL last
134 134 if order_by in ['last_activity']:
135 135 sort_col = coalesce(sort_col, datetime.date.min)
136 136 sort_col = sort_col.desc()
137 137
138 138 base_q = base_q.order_by(sort_col)
139 139 base_q = base_q.offset(start).limit(limit)
140 140
141 141 users_list = base_q.all()
142 142
143 143 users_data = []
144 144 for user in users_list:
145 145 users_data.append({
146 146 "username": h.gravatar_with_user(self.request, user.username),
147 147 "email": user.email,
148 148 "first_name": user.first_name,
149 149 "last_name": user.last_name,
150 150 "last_login": h.format_date(user.last_login),
151 151 "last_activity": h.format_date(user.last_activity),
152 152 "active": h.bool2icon(user.active),
153 153 "active_raw": user.active,
154 154 "admin": h.bool2icon(user.admin),
155 155 "extern_type": user.extern_type,
156 156 "extern_name": user.extern_name,
157 157 "action": user_actions(user.user_id, user.username),
158 158 })
159 159 data = ({
160 160 'draw': draw,
161 161 'data': users_data,
162 162 'recordsTotal': users_data_total_count,
163 163 'recordsFiltered': users_data_total_filtered_count,
164 164 'recordsTotalInactive': users_data_total_inactive_count,
165 165 'recordsFilteredInactive': users_data_total_filtered_inactive_count
166 166 })
167 167
168 168 return data
169 169
170 170 def _set_personal_repo_group_template_vars(self, c_obj):
171 171 DummyUser = AttributeDict({
172 172 'username': '${username}',
173 173 'user_id': '${user_id}',
174 174 })
175 175 c_obj.default_create_repo_group = RepoGroupModel() \
176 176 .get_default_create_personal_repo_group()
177 177 c_obj.personal_repo_group_name = RepoGroupModel() \
178 178 .get_personal_group_name(DummyUser)
179 179
180 180 @LoginRequired()
181 181 @HasPermissionAllDecorator('hg.admin')
182 182 @view_config(
183 183 route_name='users_new', request_method='GET',
184 184 renderer='rhodecode:templates/admin/users/user_add.mako')
185 185 def users_new(self):
186 186 _ = self.request.translate
187 187 c = self.load_default_context()
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
188 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
189 189 self._set_personal_repo_group_template_vars(c)
190 190 return self._get_template_context(c)
191 191
192 192 @LoginRequired()
193 193 @HasPermissionAllDecorator('hg.admin')
194 194 @CSRFRequired()
195 195 @view_config(
196 196 route_name='users_create', request_method='POST',
197 197 renderer='rhodecode:templates/admin/users/user_add.mako')
198 198 def users_create(self):
199 199 _ = self.request.translate
200 200 c = self.load_default_context()
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.name
201 c.default_extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
202 202 user_model = UserModel()
203 203 user_form = UserForm(self.request.translate)()
204 204 try:
205 205 form_result = user_form.to_python(dict(self.request.POST))
206 206 user = user_model.create(form_result)
207 207 Session().flush()
208 208 creation_data = user.get_api_data()
209 209 username = form_result['username']
210 210
211 211 audit_logger.store_web(
212 212 'user.create', action_data={'data': creation_data},
213 213 user=c.rhodecode_user)
214 214
215 215 user_link = h.link_to(
216 216 h.escape(username),
217 217 h.route_path('user_edit', user_id=user.user_id))
218 218 h.flash(h.literal(_('Created user %(user_link)s')
219 219 % {'user_link': user_link}), category='success')
220 220 Session().commit()
221 221 except formencode.Invalid as errors:
222 222 self._set_personal_repo_group_template_vars(c)
223 223 data = render(
224 224 'rhodecode:templates/admin/users/user_add.mako',
225 225 self._get_template_context(c), self.request)
226 226 html = formencode.htmlfill.render(
227 227 data,
228 228 defaults=errors.value,
229 229 errors=errors.error_dict or {},
230 230 prefix_error=False,
231 231 encoding="UTF-8",
232 232 force_defaults=False
233 233 )
234 234 return Response(html)
235 235 except UserCreationError as e:
236 236 h.flash(e, 'error')
237 237 except Exception:
238 238 log.exception("Exception creation of user")
239 239 h.flash(_('Error occurred during creation of user %s')
240 240 % self.request.POST.get('username'), category='error')
241 241 raise HTTPFound(h.route_path('users'))
242 242
243 243
244 244 class UsersView(UserAppView):
245 245 ALLOW_SCOPED_TOKENS = False
246 246 """
247 247 This view has alternative version inside EE, if modified please take a look
248 248 in there as well.
249 249 """
250 250
251 251 def load_default_context(self):
252 252 c = self._get_local_tmpl_context()
253 253 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
254 254 c.allowed_languages = [
255 255 ('en', 'English (en)'),
256 256 ('de', 'German (de)'),
257 257 ('fr', 'French (fr)'),
258 258 ('it', 'Italian (it)'),
259 259 ('ja', 'Japanese (ja)'),
260 260 ('pl', 'Polish (pl)'),
261 261 ('pt', 'Portuguese (pt)'),
262 262 ('ru', 'Russian (ru)'),
263 263 ('zh', 'Chinese (zh)'),
264 264 ]
265 265 req = self.request
266 266
267 267 c.available_permissions = req.registry.settings['available_permissions']
268 268 PermissionModel().set_global_permission_choices(
269 269 c, gettext_translator=req.translate)
270 270
271 271 return c
272 272
273 273 @LoginRequired()
274 274 @HasPermissionAllDecorator('hg.admin')
275 275 @CSRFRequired()
276 276 @view_config(
277 277 route_name='user_update', request_method='POST',
278 278 renderer='rhodecode:templates/admin/users/user_edit.mako')
279 279 def user_update(self):
280 280 _ = self.request.translate
281 281 c = self.load_default_context()
282 282
283 283 user_id = self.db_user_id
284 284 c.user = self.db_user
285 285
286 286 c.active = 'profile'
287 287 c.extern_type = c.user.extern_type
288 288 c.extern_name = c.user.extern_name
289 289 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
290 290 available_languages = [x[0] for x in c.allowed_languages]
291 291 _form = UserForm(self.request.translate, edit=True,
292 292 available_languages=available_languages,
293 293 old_data={'user_id': user_id,
294 294 'email': c.user.email})()
295 295 form_result = {}
296 296 old_values = c.user.get_api_data()
297 297 try:
298 298 form_result = _form.to_python(dict(self.request.POST))
299 299 skip_attrs = ['extern_type', 'extern_name']
300 300 # TODO: plugin should define if username can be updated
301 301 if c.extern_type != "rhodecode":
302 302 # forbid updating username for external accounts
303 303 skip_attrs.append('username')
304 304
305 305 UserModel().update_user(
306 306 user_id, skip_attrs=skip_attrs, **form_result)
307 307
308 308 audit_logger.store_web(
309 309 'user.edit', action_data={'old_data': old_values},
310 310 user=c.rhodecode_user)
311 311
312 312 Session().commit()
313 313 h.flash(_('User updated successfully'), category='success')
314 314 except formencode.Invalid as errors:
315 315 data = render(
316 316 'rhodecode:templates/admin/users/user_edit.mako',
317 317 self._get_template_context(c), self.request)
318 318 html = formencode.htmlfill.render(
319 319 data,
320 320 defaults=errors.value,
321 321 errors=errors.error_dict or {},
322 322 prefix_error=False,
323 323 encoding="UTF-8",
324 324 force_defaults=False
325 325 )
326 326 return Response(html)
327 327 except UserCreationError as e:
328 328 h.flash(e, 'error')
329 329 except Exception:
330 330 log.exception("Exception updating user")
331 331 h.flash(_('Error occurred during update of user %s')
332 332 % form_result.get('username'), category='error')
333 333 raise HTTPFound(h.route_path('user_edit', user_id=user_id))
334 334
335 335 @LoginRequired()
336 336 @HasPermissionAllDecorator('hg.admin')
337 337 @CSRFRequired()
338 338 @view_config(
339 339 route_name='user_delete', request_method='POST',
340 340 renderer='rhodecode:templates/admin/users/user_edit.mako')
341 341 def user_delete(self):
342 342 _ = self.request.translate
343 343 c = self.load_default_context()
344 344 c.user = self.db_user
345 345
346 346 _repos = c.user.repositories
347 347 _repo_groups = c.user.repository_groups
348 348 _user_groups = c.user.user_groups
349 349
350 350 handle_repos = None
351 351 handle_repo_groups = None
352 352 handle_user_groups = None
353 353 # dummy call for flash of handle
354 354 set_handle_flash_repos = lambda: None
355 355 set_handle_flash_repo_groups = lambda: None
356 356 set_handle_flash_user_groups = lambda: None
357 357
358 358 if _repos and self.request.POST.get('user_repos'):
359 359 do = self.request.POST['user_repos']
360 360 if do == 'detach':
361 361 handle_repos = 'detach'
362 362 set_handle_flash_repos = lambda: h.flash(
363 363 _('Detached %s repositories') % len(_repos),
364 364 category='success')
365 365 elif do == 'delete':
366 366 handle_repos = 'delete'
367 367 set_handle_flash_repos = lambda: h.flash(
368 368 _('Deleted %s repositories') % len(_repos),
369 369 category='success')
370 370
371 371 if _repo_groups and self.request.POST.get('user_repo_groups'):
372 372 do = self.request.POST['user_repo_groups']
373 373 if do == 'detach':
374 374 handle_repo_groups = 'detach'
375 375 set_handle_flash_repo_groups = lambda: h.flash(
376 376 _('Detached %s repository groups') % len(_repo_groups),
377 377 category='success')
378 378 elif do == 'delete':
379 379 handle_repo_groups = 'delete'
380 380 set_handle_flash_repo_groups = lambda: h.flash(
381 381 _('Deleted %s repository groups') % len(_repo_groups),
382 382 category='success')
383 383
384 384 if _user_groups and self.request.POST.get('user_user_groups'):
385 385 do = self.request.POST['user_user_groups']
386 386 if do == 'detach':
387 387 handle_user_groups = 'detach'
388 388 set_handle_flash_user_groups = lambda: h.flash(
389 389 _('Detached %s user groups') % len(_user_groups),
390 390 category='success')
391 391 elif do == 'delete':
392 392 handle_user_groups = 'delete'
393 393 set_handle_flash_user_groups = lambda: h.flash(
394 394 _('Deleted %s user groups') % len(_user_groups),
395 395 category='success')
396 396
397 397 old_values = c.user.get_api_data()
398 398 try:
399 399 UserModel().delete(c.user, handle_repos=handle_repos,
400 400 handle_repo_groups=handle_repo_groups,
401 401 handle_user_groups=handle_user_groups)
402 402
403 403 audit_logger.store_web(
404 404 'user.delete', action_data={'old_data': old_values},
405 405 user=c.rhodecode_user)
406 406
407 407 Session().commit()
408 408 set_handle_flash_repos()
409 409 set_handle_flash_repo_groups()
410 410 set_handle_flash_user_groups()
411 411 h.flash(_('Successfully deleted user'), category='success')
412 412 except (UserOwnsReposException, UserOwnsRepoGroupsException,
413 413 UserOwnsUserGroupsException, DefaultUserException) as e:
414 414 h.flash(e, category='warning')
415 415 except Exception:
416 416 log.exception("Exception during deletion of user")
417 417 h.flash(_('An error occurred during deletion of user'),
418 418 category='error')
419 419 raise HTTPFound(h.route_path('users'))
420 420
421 421 @LoginRequired()
422 422 @HasPermissionAllDecorator('hg.admin')
423 423 @view_config(
424 424 route_name='user_edit', request_method='GET',
425 425 renderer='rhodecode:templates/admin/users/user_edit.mako')
426 426 def user_edit(self):
427 427 _ = self.request.translate
428 428 c = self.load_default_context()
429 429 c.user = self.db_user
430 430
431 431 c.active = 'profile'
432 432 c.extern_type = c.user.extern_type
433 433 c.extern_name = c.user.extern_name
434 434 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
435 435
436 436 defaults = c.user.get_dict()
437 437 defaults.update({'language': c.user.user_data.get('language')})
438 438
439 439 data = render(
440 440 'rhodecode:templates/admin/users/user_edit.mako',
441 441 self._get_template_context(c), self.request)
442 442 html = formencode.htmlfill.render(
443 443 data,
444 444 defaults=defaults,
445 445 encoding="UTF-8",
446 446 force_defaults=False
447 447 )
448 448 return Response(html)
449 449
450 450 @LoginRequired()
451 451 @HasPermissionAllDecorator('hg.admin')
452 452 @view_config(
453 453 route_name='user_edit_advanced', request_method='GET',
454 454 renderer='rhodecode:templates/admin/users/user_edit.mako')
455 455 def user_edit_advanced(self):
456 456 _ = self.request.translate
457 457 c = self.load_default_context()
458 458
459 459 user_id = self.db_user_id
460 460 c.user = self.db_user
461 461
462 462 c.active = 'advanced'
463 463 c.personal_repo_group = RepoGroup.get_user_personal_repo_group(user_id)
464 464 c.personal_repo_group_name = RepoGroupModel()\
465 465 .get_personal_group_name(c.user)
466 466
467 467 c.user_to_review_rules = sorted(
468 468 (x.user for x in c.user.user_review_rules),
469 469 key=lambda u: u.username.lower())
470 470
471 471 c.first_admin = User.get_first_super_admin()
472 472 defaults = c.user.get_dict()
473 473
474 474 # Interim workaround if the user participated on any pull requests as a
475 475 # reviewer.
476 476 has_review = len(c.user.reviewer_pull_requests)
477 477 c.can_delete_user = not has_review
478 478 c.can_delete_user_message = ''
479 479 inactive_link = h.link_to(
480 480 'inactive', h.route_path('user_edit', user_id=user_id, _anchor='active'))
481 481 if has_review == 1:
482 482 c.can_delete_user_message = h.literal(_(
483 483 'The user participates as reviewer in {} pull request and '
484 484 'cannot be deleted. \nYou can set the user to '
485 485 '"{}" instead of deleting it.').format(
486 486 has_review, inactive_link))
487 487 elif has_review:
488 488 c.can_delete_user_message = h.literal(_(
489 489 'The user participates as reviewer in {} pull requests and '
490 490 'cannot be deleted. \nYou can set the user to '
491 491 '"{}" instead of deleting it.').format(
492 492 has_review, inactive_link))
493 493
494 494 data = render(
495 495 'rhodecode:templates/admin/users/user_edit.mako',
496 496 self._get_template_context(c), self.request)
497 497 html = formencode.htmlfill.render(
498 498 data,
499 499 defaults=defaults,
500 500 encoding="UTF-8",
501 501 force_defaults=False
502 502 )
503 503 return Response(html)
504 504
505 505 @LoginRequired()
506 506 @HasPermissionAllDecorator('hg.admin')
507 507 @view_config(
508 508 route_name='user_edit_global_perms', request_method='GET',
509 509 renderer='rhodecode:templates/admin/users/user_edit.mako')
510 510 def user_edit_global_perms(self):
511 511 _ = self.request.translate
512 512 c = self.load_default_context()
513 513 c.user = self.db_user
514 514
515 515 c.active = 'global_perms'
516 516
517 517 c.default_user = User.get_default_user()
518 518 defaults = c.user.get_dict()
519 519 defaults.update(c.default_user.get_default_perms(suffix='_inherited'))
520 520 defaults.update(c.default_user.get_default_perms())
521 521 defaults.update(c.user.get_default_perms())
522 522
523 523 data = render(
524 524 'rhodecode:templates/admin/users/user_edit.mako',
525 525 self._get_template_context(c), self.request)
526 526 html = formencode.htmlfill.render(
527 527 data,
528 528 defaults=defaults,
529 529 encoding="UTF-8",
530 530 force_defaults=False
531 531 )
532 532 return Response(html)
533 533
534 534 @LoginRequired()
535 535 @HasPermissionAllDecorator('hg.admin')
536 536 @CSRFRequired()
537 537 @view_config(
538 538 route_name='user_edit_global_perms_update', request_method='POST',
539 539 renderer='rhodecode:templates/admin/users/user_edit.mako')
540 540 def user_edit_global_perms_update(self):
541 541 _ = self.request.translate
542 542 c = self.load_default_context()
543 543
544 544 user_id = self.db_user_id
545 545 c.user = self.db_user
546 546
547 547 c.active = 'global_perms'
548 548 try:
549 549 # first stage that verifies the checkbox
550 550 _form = UserIndividualPermissionsForm(self.request.translate)
551 551 form_result = _form.to_python(dict(self.request.POST))
552 552 inherit_perms = form_result['inherit_default_permissions']
553 553 c.user.inherit_default_permissions = inherit_perms
554 554 Session().add(c.user)
555 555
556 556 if not inherit_perms:
557 557 # only update the individual ones if we un check the flag
558 558 _form = UserPermissionsForm(
559 559 self.request.translate,
560 560 [x[0] for x in c.repo_create_choices],
561 561 [x[0] for x in c.repo_create_on_write_choices],
562 562 [x[0] for x in c.repo_group_create_choices],
563 563 [x[0] for x in c.user_group_create_choices],
564 564 [x[0] for x in c.fork_choices],
565 565 [x[0] for x in c.inherit_default_permission_choices])()
566 566
567 567 form_result = _form.to_python(dict(self.request.POST))
568 568 form_result.update({'perm_user_id': c.user.user_id})
569 569
570 570 PermissionModel().update_user_permissions(form_result)
571 571
572 572 # TODO(marcink): implement global permissions
573 573 # audit_log.store_web('user.edit.permissions')
574 574
575 575 Session().commit()
576 576 h.flash(_('User global permissions updated successfully'),
577 577 category='success')
578 578
579 579 except formencode.Invalid as errors:
580 580 data = render(
581 581 'rhodecode:templates/admin/users/user_edit.mako',
582 582 self._get_template_context(c), self.request)
583 583 html = formencode.htmlfill.render(
584 584 data,
585 585 defaults=errors.value,
586 586 errors=errors.error_dict or {},
587 587 prefix_error=False,
588 588 encoding="UTF-8",
589 589 force_defaults=False
590 590 )
591 591 return Response(html)
592 592 except Exception:
593 593 log.exception("Exception during permissions saving")
594 594 h.flash(_('An error occurred during permissions saving'),
595 595 category='error')
596 596 raise HTTPFound(h.route_path('user_edit_global_perms', user_id=user_id))
597 597
598 598 @LoginRequired()
599 599 @HasPermissionAllDecorator('hg.admin')
600 600 @CSRFRequired()
601 601 @view_config(
602 602 route_name='user_force_password_reset', request_method='POST',
603 603 renderer='rhodecode:templates/admin/users/user_edit.mako')
604 604 def user_force_password_reset(self):
605 605 """
606 606 toggle reset password flag for this user
607 607 """
608 608 _ = self.request.translate
609 609 c = self.load_default_context()
610 610
611 611 user_id = self.db_user_id
612 612 c.user = self.db_user
613 613
614 614 try:
615 615 old_value = c.user.user_data.get('force_password_change')
616 616 c.user.update_userdata(force_password_change=not old_value)
617 617
618 618 if old_value:
619 619 msg = _('Force password change disabled for user')
620 620 audit_logger.store_web(
621 621 'user.edit.password_reset.disabled',
622 622 user=c.rhodecode_user)
623 623 else:
624 624 msg = _('Force password change enabled for user')
625 625 audit_logger.store_web(
626 626 'user.edit.password_reset.enabled',
627 627 user=c.rhodecode_user)
628 628
629 629 Session().commit()
630 630 h.flash(msg, category='success')
631 631 except Exception:
632 632 log.exception("Exception during password reset for user")
633 633 h.flash(_('An error occurred during password reset for user'),
634 634 category='error')
635 635
636 636 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
637 637
638 638 @LoginRequired()
639 639 @HasPermissionAllDecorator('hg.admin')
640 640 @CSRFRequired()
641 641 @view_config(
642 642 route_name='user_create_personal_repo_group', request_method='POST',
643 643 renderer='rhodecode:templates/admin/users/user_edit.mako')
644 644 def user_create_personal_repo_group(self):
645 645 """
646 646 Create personal repository group for this user
647 647 """
648 648 from rhodecode.model.repo_group import RepoGroupModel
649 649
650 650 _ = self.request.translate
651 651 c = self.load_default_context()
652 652
653 653 user_id = self.db_user_id
654 654 c.user = self.db_user
655 655
656 656 personal_repo_group = RepoGroup.get_user_personal_repo_group(
657 657 c.user.user_id)
658 658 if personal_repo_group:
659 659 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
660 660
661 661 personal_repo_group_name = RepoGroupModel().get_personal_group_name(
662 662 c.user)
663 663 named_personal_group = RepoGroup.get_by_group_name(
664 664 personal_repo_group_name)
665 665 try:
666 666
667 667 if named_personal_group and named_personal_group.user_id == c.user.user_id:
668 668 # migrate the same named group, and mark it as personal
669 669 named_personal_group.personal = True
670 670 Session().add(named_personal_group)
671 671 Session().commit()
672 672 msg = _('Linked repository group `%s` as personal' % (
673 673 personal_repo_group_name,))
674 674 h.flash(msg, category='success')
675 675 elif not named_personal_group:
676 676 RepoGroupModel().create_personal_repo_group(c.user)
677 677
678 678 msg = _('Created repository group `%s`' % (
679 679 personal_repo_group_name,))
680 680 h.flash(msg, category='success')
681 681 else:
682 682 msg = _('Repository group `%s` is already taken' % (
683 683 personal_repo_group_name,))
684 684 h.flash(msg, category='warning')
685 685 except Exception:
686 686 log.exception("Exception during repository group creation")
687 687 msg = _(
688 688 'An error occurred during repository group creation for user')
689 689 h.flash(msg, category='error')
690 690 Session().rollback()
691 691
692 692 raise HTTPFound(h.route_path('user_edit_advanced', user_id=user_id))
693 693
694 694 @LoginRequired()
695 695 @HasPermissionAllDecorator('hg.admin')
696 696 @view_config(
697 697 route_name='edit_user_auth_tokens', request_method='GET',
698 698 renderer='rhodecode:templates/admin/users/user_edit.mako')
699 699 def auth_tokens(self):
700 700 _ = self.request.translate
701 701 c = self.load_default_context()
702 702 c.user = self.db_user
703 703
704 704 c.active = 'auth_tokens'
705 705
706 706 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
707 707 c.role_values = [
708 708 (x, AuthTokenModel.cls._get_role_name(x))
709 709 for x in AuthTokenModel.cls.ROLES]
710 710 c.role_options = [(c.role_values, _("Role"))]
711 711 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
712 712 c.user.user_id, show_expired=True)
713 713 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
714 714 return self._get_template_context(c)
715 715
716 716 def maybe_attach_token_scope(self, token):
717 717 # implemented in EE edition
718 718 pass
719 719
720 720 @LoginRequired()
721 721 @HasPermissionAllDecorator('hg.admin')
722 722 @CSRFRequired()
723 723 @view_config(
724 724 route_name='edit_user_auth_tokens_add', request_method='POST')
725 725 def auth_tokens_add(self):
726 726 _ = self.request.translate
727 727 c = self.load_default_context()
728 728
729 729 user_id = self.db_user_id
730 730 c.user = self.db_user
731 731
732 732 user_data = c.user.get_api_data()
733 733 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
734 734 description = self.request.POST.get('description')
735 735 role = self.request.POST.get('role')
736 736
737 737 token = UserModel().add_auth_token(
738 738 user=c.user.user_id,
739 739 lifetime_minutes=lifetime, role=role, description=description,
740 740 scope_callback=self.maybe_attach_token_scope)
741 741 token_data = token.get_api_data()
742 742
743 743 audit_logger.store_web(
744 744 'user.edit.token.add', action_data={
745 745 'data': {'token': token_data, 'user': user_data}},
746 746 user=self._rhodecode_user, )
747 747 Session().commit()
748 748
749 749 h.flash(_("Auth token successfully created"), category='success')
750 750 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
751 751
752 752 @LoginRequired()
753 753 @HasPermissionAllDecorator('hg.admin')
754 754 @CSRFRequired()
755 755 @view_config(
756 756 route_name='edit_user_auth_tokens_delete', request_method='POST')
757 757 def auth_tokens_delete(self):
758 758 _ = self.request.translate
759 759 c = self.load_default_context()
760 760
761 761 user_id = self.db_user_id
762 762 c.user = self.db_user
763 763
764 764 user_data = c.user.get_api_data()
765 765
766 766 del_auth_token = self.request.POST.get('del_auth_token')
767 767
768 768 if del_auth_token:
769 769 token = UserApiKeys.get_or_404(del_auth_token)
770 770 token_data = token.get_api_data()
771 771
772 772 AuthTokenModel().delete(del_auth_token, c.user.user_id)
773 773 audit_logger.store_web(
774 774 'user.edit.token.delete', action_data={
775 775 'data': {'token': token_data, 'user': user_data}},
776 776 user=self._rhodecode_user,)
777 777 Session().commit()
778 778 h.flash(_("Auth token successfully deleted"), category='success')
779 779
780 780 return HTTPFound(h.route_path('edit_user_auth_tokens', user_id=user_id))
781 781
782 782 @LoginRequired()
783 783 @HasPermissionAllDecorator('hg.admin')
784 784 @view_config(
785 785 route_name='edit_user_ssh_keys', request_method='GET',
786 786 renderer='rhodecode:templates/admin/users/user_edit.mako')
787 787 def ssh_keys(self):
788 788 _ = self.request.translate
789 789 c = self.load_default_context()
790 790 c.user = self.db_user
791 791
792 792 c.active = 'ssh_keys'
793 793 c.default_key = self.request.GET.get('default_key')
794 794 c.user_ssh_keys = SshKeyModel().get_ssh_keys(c.user.user_id)
795 795 return self._get_template_context(c)
796 796
797 797 @LoginRequired()
798 798 @HasPermissionAllDecorator('hg.admin')
799 799 @view_config(
800 800 route_name='edit_user_ssh_keys_generate_keypair', request_method='GET',
801 801 renderer='rhodecode:templates/admin/users/user_edit.mako')
802 802 def ssh_keys_generate_keypair(self):
803 803 _ = self.request.translate
804 804 c = self.load_default_context()
805 805
806 806 c.user = self.db_user
807 807
808 808 c.active = 'ssh_keys_generate'
809 809 comment = 'RhodeCode-SSH {}'.format(c.user.email or '')
810 810 c.private, c.public = SshKeyModel().generate_keypair(comment=comment)
811 811
812 812 return self._get_template_context(c)
813 813
814 814 @LoginRequired()
815 815 @HasPermissionAllDecorator('hg.admin')
816 816 @CSRFRequired()
817 817 @view_config(
818 818 route_name='edit_user_ssh_keys_add', request_method='POST')
819 819 def ssh_keys_add(self):
820 820 _ = self.request.translate
821 821 c = self.load_default_context()
822 822
823 823 user_id = self.db_user_id
824 824 c.user = self.db_user
825 825
826 826 user_data = c.user.get_api_data()
827 827 key_data = self.request.POST.get('key_data')
828 828 description = self.request.POST.get('description')
829 829
830 830 fingerprint = 'unknown'
831 831 try:
832 832 if not key_data:
833 833 raise ValueError('Please add a valid public key')
834 834
835 835 key = SshKeyModel().parse_key(key_data.strip())
836 836 fingerprint = key.hash_md5()
837 837
838 838 ssh_key = SshKeyModel().create(
839 839 c.user.user_id, fingerprint, key.keydata, description)
840 840 ssh_key_data = ssh_key.get_api_data()
841 841
842 842 audit_logger.store_web(
843 843 'user.edit.ssh_key.add', action_data={
844 844 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
845 845 user=self._rhodecode_user, )
846 846 Session().commit()
847 847
848 848 # Trigger an event on change of keys.
849 849 trigger(SshKeyFileChangeEvent(), self.request.registry)
850 850
851 851 h.flash(_("Ssh Key successfully created"), category='success')
852 852
853 853 except IntegrityError:
854 854 log.exception("Exception during ssh key saving")
855 855 err = 'Such key with fingerprint `{}` already exists, ' \
856 856 'please use a different one'.format(fingerprint)
857 857 h.flash(_('An error occurred during ssh key saving: {}').format(err),
858 858 category='error')
859 859 except Exception as e:
860 860 log.exception("Exception during ssh key saving")
861 861 h.flash(_('An error occurred during ssh key saving: {}').format(e),
862 862 category='error')
863 863
864 864 return HTTPFound(
865 865 h.route_path('edit_user_ssh_keys', user_id=user_id))
866 866
867 867 @LoginRequired()
868 868 @HasPermissionAllDecorator('hg.admin')
869 869 @CSRFRequired()
870 870 @view_config(
871 871 route_name='edit_user_ssh_keys_delete', request_method='POST')
872 872 def ssh_keys_delete(self):
873 873 _ = self.request.translate
874 874 c = self.load_default_context()
875 875
876 876 user_id = self.db_user_id
877 877 c.user = self.db_user
878 878
879 879 user_data = c.user.get_api_data()
880 880
881 881 del_ssh_key = self.request.POST.get('del_ssh_key')
882 882
883 883 if del_ssh_key:
884 884 ssh_key = UserSshKeys.get_or_404(del_ssh_key)
885 885 ssh_key_data = ssh_key.get_api_data()
886 886
887 887 SshKeyModel().delete(del_ssh_key, c.user.user_id)
888 888 audit_logger.store_web(
889 889 'user.edit.ssh_key.delete', action_data={
890 890 'data': {'ssh_key': ssh_key_data, 'user': user_data}},
891 891 user=self._rhodecode_user,)
892 892 Session().commit()
893 893 # Trigger an event on change of keys.
894 894 trigger(SshKeyFileChangeEvent(), self.request.registry)
895 895 h.flash(_("Ssh key successfully deleted"), category='success')
896 896
897 897 return HTTPFound(h.route_path('edit_user_ssh_keys', user_id=user_id))
898 898
899 899 @LoginRequired()
900 900 @HasPermissionAllDecorator('hg.admin')
901 901 @view_config(
902 902 route_name='edit_user_emails', request_method='GET',
903 903 renderer='rhodecode:templates/admin/users/user_edit.mako')
904 904 def emails(self):
905 905 _ = self.request.translate
906 906 c = self.load_default_context()
907 907 c.user = self.db_user
908 908
909 909 c.active = 'emails'
910 910 c.user_email_map = UserEmailMap.query() \
911 911 .filter(UserEmailMap.user == c.user).all()
912 912
913 913 return self._get_template_context(c)
914 914
915 915 @LoginRequired()
916 916 @HasPermissionAllDecorator('hg.admin')
917 917 @CSRFRequired()
918 918 @view_config(
919 919 route_name='edit_user_emails_add', request_method='POST')
920 920 def emails_add(self):
921 921 _ = self.request.translate
922 922 c = self.load_default_context()
923 923
924 924 user_id = self.db_user_id
925 925 c.user = self.db_user
926 926
927 927 email = self.request.POST.get('new_email')
928 928 user_data = c.user.get_api_data()
929 929 try:
930 930
931 931 form = UserExtraEmailForm(self.request.translate)()
932 932 data = form.to_python({'email': email})
933 933 email = data['email']
934 934
935 935 UserModel().add_extra_email(c.user.user_id, email)
936 936 audit_logger.store_web(
937 937 'user.edit.email.add',
938 938 action_data={'email': email, 'user': user_data},
939 939 user=self._rhodecode_user)
940 940 Session().commit()
941 941 h.flash(_("Added new email address `%s` for user account") % email,
942 942 category='success')
943 943 except formencode.Invalid as error:
944 944 h.flash(h.escape(error.error_dict['email']), category='error')
945 945 except IntegrityError:
946 946 log.warning("Email %s already exists", email)
947 947 h.flash(_('Email `{}` is already registered for another user.').format(email),
948 948 category='error')
949 949 except Exception:
950 950 log.exception("Exception during email saving")
951 951 h.flash(_('An error occurred during email saving'),
952 952 category='error')
953 953 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
954 954
955 955 @LoginRequired()
956 956 @HasPermissionAllDecorator('hg.admin')
957 957 @CSRFRequired()
958 958 @view_config(
959 959 route_name='edit_user_emails_delete', request_method='POST')
960 960 def emails_delete(self):
961 961 _ = self.request.translate
962 962 c = self.load_default_context()
963 963
964 964 user_id = self.db_user_id
965 965 c.user = self.db_user
966 966
967 967 email_id = self.request.POST.get('del_email_id')
968 968 user_model = UserModel()
969 969
970 970 email = UserEmailMap.query().get(email_id).email
971 971 user_data = c.user.get_api_data()
972 972 user_model.delete_extra_email(c.user.user_id, email_id)
973 973 audit_logger.store_web(
974 974 'user.edit.email.delete',
975 975 action_data={'email': email, 'user': user_data},
976 976 user=self._rhodecode_user)
977 977 Session().commit()
978 978 h.flash(_("Removed email address from user account"),
979 979 category='success')
980 980 raise HTTPFound(h.route_path('edit_user_emails', user_id=user_id))
981 981
982 982 @LoginRequired()
983 983 @HasPermissionAllDecorator('hg.admin')
984 984 @view_config(
985 985 route_name='edit_user_ips', request_method='GET',
986 986 renderer='rhodecode:templates/admin/users/user_edit.mako')
987 987 def ips(self):
988 988 _ = self.request.translate
989 989 c = self.load_default_context()
990 990 c.user = self.db_user
991 991
992 992 c.active = 'ips'
993 993 c.user_ip_map = UserIpMap.query() \
994 994 .filter(UserIpMap.user == c.user).all()
995 995
996 996 c.inherit_default_ips = c.user.inherit_default_permissions
997 997 c.default_user_ip_map = UserIpMap.query() \
998 998 .filter(UserIpMap.user == User.get_default_user()).all()
999 999
1000 1000 return self._get_template_context(c)
1001 1001
1002 1002 @LoginRequired()
1003 1003 @HasPermissionAllDecorator('hg.admin')
1004 1004 @CSRFRequired()
1005 1005 @view_config(
1006 1006 route_name='edit_user_ips_add', request_method='POST')
1007 1007 # NOTE(marcink): this view is allowed for default users, as we can
1008 1008 # edit their IP white list
1009 1009 def ips_add(self):
1010 1010 _ = self.request.translate
1011 1011 c = self.load_default_context()
1012 1012
1013 1013 user_id = self.db_user_id
1014 1014 c.user = self.db_user
1015 1015
1016 1016 user_model = UserModel()
1017 1017 desc = self.request.POST.get('description')
1018 1018 try:
1019 1019 ip_list = user_model.parse_ip_range(
1020 1020 self.request.POST.get('new_ip'))
1021 1021 except Exception as e:
1022 1022 ip_list = []
1023 1023 log.exception("Exception during ip saving")
1024 1024 h.flash(_('An error occurred during ip saving:%s' % (e,)),
1025 1025 category='error')
1026 1026 added = []
1027 1027 user_data = c.user.get_api_data()
1028 1028 for ip in ip_list:
1029 1029 try:
1030 1030 form = UserExtraIpForm(self.request.translate)()
1031 1031 data = form.to_python({'ip': ip})
1032 1032 ip = data['ip']
1033 1033
1034 1034 user_model.add_extra_ip(c.user.user_id, ip, desc)
1035 1035 audit_logger.store_web(
1036 1036 'user.edit.ip.add',
1037 1037 action_data={'ip': ip, 'user': user_data},
1038 1038 user=self._rhodecode_user)
1039 1039 Session().commit()
1040 1040 added.append(ip)
1041 1041 except formencode.Invalid as error:
1042 1042 msg = error.error_dict['ip']
1043 1043 h.flash(msg, category='error')
1044 1044 except Exception:
1045 1045 log.exception("Exception during ip saving")
1046 1046 h.flash(_('An error occurred during ip saving'),
1047 1047 category='error')
1048 1048 if added:
1049 1049 h.flash(
1050 1050 _("Added ips %s to user whitelist") % (', '.join(ip_list), ),
1051 1051 category='success')
1052 1052 if 'default_user' in self.request.POST:
1053 1053 # case for editing global IP list we do it for 'DEFAULT' user
1054 1054 raise HTTPFound(h.route_path('admin_permissions_ips'))
1055 1055 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1056 1056
1057 1057 @LoginRequired()
1058 1058 @HasPermissionAllDecorator('hg.admin')
1059 1059 @CSRFRequired()
1060 1060 @view_config(
1061 1061 route_name='edit_user_ips_delete', request_method='POST')
1062 1062 # NOTE(marcink): this view is allowed for default users, as we can
1063 1063 # edit their IP white list
1064 1064 def ips_delete(self):
1065 1065 _ = self.request.translate
1066 1066 c = self.load_default_context()
1067 1067
1068 1068 user_id = self.db_user_id
1069 1069 c.user = self.db_user
1070 1070
1071 1071 ip_id = self.request.POST.get('del_ip_id')
1072 1072 user_model = UserModel()
1073 1073 user_data = c.user.get_api_data()
1074 1074 ip = UserIpMap.query().get(ip_id).ip_addr
1075 1075 user_model.delete_extra_ip(c.user.user_id, ip_id)
1076 1076 audit_logger.store_web(
1077 1077 'user.edit.ip.delete', action_data={'ip': ip, 'user': user_data},
1078 1078 user=self._rhodecode_user)
1079 1079 Session().commit()
1080 1080 h.flash(_("Removed ip address from user whitelist"), category='success')
1081 1081
1082 1082 if 'default_user' in self.request.POST:
1083 1083 # case for editing global IP list we do it for 'DEFAULT' user
1084 1084 raise HTTPFound(h.route_path('admin_permissions_ips'))
1085 1085 raise HTTPFound(h.route_path('edit_user_ips', user_id=user_id))
1086 1086
1087 1087 @LoginRequired()
1088 1088 @HasPermissionAllDecorator('hg.admin')
1089 1089 @view_config(
1090 1090 route_name='edit_user_groups_management', request_method='GET',
1091 1091 renderer='rhodecode:templates/admin/users/user_edit.mako')
1092 1092 def groups_management(self):
1093 1093 c = self.load_default_context()
1094 1094 c.user = self.db_user
1095 1095 c.data = c.user.group_member
1096 1096
1097 1097 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
1098 1098 for group in c.user.group_member]
1099 1099 c.groups = json.dumps(groups)
1100 1100 c.active = 'groups'
1101 1101
1102 1102 return self._get_template_context(c)
1103 1103
1104 1104 @LoginRequired()
1105 1105 @HasPermissionAllDecorator('hg.admin')
1106 1106 @CSRFRequired()
1107 1107 @view_config(
1108 1108 route_name='edit_user_groups_management_updates', request_method='POST')
1109 1109 def groups_management_updates(self):
1110 1110 _ = self.request.translate
1111 1111 c = self.load_default_context()
1112 1112
1113 1113 user_id = self.db_user_id
1114 1114 c.user = self.db_user
1115 1115
1116 1116 user_groups = set(self.request.POST.getall('users_group_id'))
1117 1117 user_groups_objects = []
1118 1118
1119 1119 for ugid in user_groups:
1120 1120 user_groups_objects.append(
1121 1121 UserGroupModel().get_group(safe_int(ugid)))
1122 1122 user_group_model = UserGroupModel()
1123 1123 added_to_groups, removed_from_groups = \
1124 1124 user_group_model.change_groups(c.user, user_groups_objects)
1125 1125
1126 1126 user_data = c.user.get_api_data()
1127 1127 for user_group_id in added_to_groups:
1128 1128 user_group = UserGroup.get(user_group_id)
1129 1129 old_values = user_group.get_api_data()
1130 1130 audit_logger.store_web(
1131 1131 'user_group.edit.member.add',
1132 1132 action_data={'user': user_data, 'old_data': old_values},
1133 1133 user=self._rhodecode_user)
1134 1134
1135 1135 for user_group_id in removed_from_groups:
1136 1136 user_group = UserGroup.get(user_group_id)
1137 1137 old_values = user_group.get_api_data()
1138 1138 audit_logger.store_web(
1139 1139 'user_group.edit.member.delete',
1140 1140 action_data={'user': user_data, 'old_data': old_values},
1141 1141 user=self._rhodecode_user)
1142 1142
1143 1143 Session().commit()
1144 1144 c.active = 'user_groups_management'
1145 1145 h.flash(_("Groups successfully changed"), category='success')
1146 1146
1147 1147 return HTTPFound(h.route_path(
1148 1148 'edit_user_groups_management', user_id=user_id))
1149 1149
1150 1150 @LoginRequired()
1151 1151 @HasPermissionAllDecorator('hg.admin')
1152 1152 @view_config(
1153 1153 route_name='edit_user_audit_logs', request_method='GET',
1154 1154 renderer='rhodecode:templates/admin/users/user_edit.mako')
1155 1155 def user_audit_logs(self):
1156 1156 _ = self.request.translate
1157 1157 c = self.load_default_context()
1158 1158 c.user = self.db_user
1159 1159
1160 1160 c.active = 'audit'
1161 1161
1162 1162 p = safe_int(self.request.GET.get('page', 1), 1)
1163 1163
1164 1164 filter_term = self.request.GET.get('filter')
1165 1165 user_log = UserModel().get_user_log(c.user, filter_term)
1166 1166
1167 1167 def url_generator(**kw):
1168 1168 if filter_term:
1169 1169 kw['filter'] = filter_term
1170 1170 return self.request.current_route_path(_query=kw)
1171 1171
1172 1172 c.audit_logs = h.Page(
1173 1173 user_log, page=p, items_per_page=10, url=url_generator)
1174 1174 c.filter_term = filter_term
1175 1175 return self._get_template_context(c)
1176 1176
1177 1177 @LoginRequired()
1178 1178 @HasPermissionAllDecorator('hg.admin')
1179 1179 @view_config(
1180 1180 route_name='edit_user_perms_summary', request_method='GET',
1181 1181 renderer='rhodecode:templates/admin/users/user_edit.mako')
1182 1182 def user_perms_summary(self):
1183 1183 _ = self.request.translate
1184 1184 c = self.load_default_context()
1185 1185 c.user = self.db_user
1186 1186
1187 1187 c.active = 'perms_summary'
1188 1188 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1189 1189
1190 1190 return self._get_template_context(c)
1191 1191
1192 1192 @LoginRequired()
1193 1193 @HasPermissionAllDecorator('hg.admin')
1194 1194 @view_config(
1195 1195 route_name='edit_user_perms_summary_json', request_method='GET',
1196 1196 renderer='json_ext')
1197 1197 def user_perms_summary_json(self):
1198 1198 self.load_default_context()
1199 1199 perm_user = self.db_user.AuthUser(ip_addr=self.request.remote_addr)
1200 1200
1201 1201 return perm_user.permissions
1202 1202
1203 1203 @LoginRequired()
1204 1204 @HasPermissionAllDecorator('hg.admin')
1205 1205 @view_config(
1206 1206 route_name='edit_user_caches', request_method='GET',
1207 1207 renderer='rhodecode:templates/admin/users/user_edit.mako')
1208 1208 def user_caches(self):
1209 1209 _ = self.request.translate
1210 1210 c = self.load_default_context()
1211 1211 c.user = self.db_user
1212 1212
1213 1213 c.active = 'caches'
1214 1214 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1215 1215
1216 1216 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1217 1217 c.region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
1218 1218 c.backend = c.region.backend
1219 1219 c.user_keys = sorted(c.region.backend.list_keys(prefix=cache_namespace_uid))
1220 1220
1221 1221 return self._get_template_context(c)
1222 1222
1223 1223 @LoginRequired()
1224 1224 @HasPermissionAllDecorator('hg.admin')
1225 1225 @CSRFRequired()
1226 1226 @view_config(
1227 1227 route_name='edit_user_caches_update', request_method='POST')
1228 1228 def user_caches_update(self):
1229 1229 _ = self.request.translate
1230 1230 c = self.load_default_context()
1231 1231 c.user = self.db_user
1232 1232
1233 1233 c.active = 'caches'
1234 1234 c.perm_user = c.user.AuthUser(ip_addr=self.request.remote_addr)
1235 1235
1236 1236 cache_namespace_uid = 'cache_user_auth.{}'.format(self.db_user.user_id)
1237 1237 del_keys = rc_cache.clear_cache_namespace('cache_perms', cache_namespace_uid)
1238 1238
1239 1239 h.flash(_("Deleted {} cache keys").format(del_keys), category='success')
1240 1240
1241 1241 return HTTPFound(h.route_path(
1242 1242 'edit_user_caches', user_id=c.user.user_id))
@@ -1,477 +1,486 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 time
22 22 import collections
23 23 import datetime
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import logging
27 27 import urlparse
28 28 import requests
29 29
30 30 from pyramid.httpexceptions import HTTPFound
31 31 from pyramid.view import view_config
32 32
33 33 from rhodecode.apps._base import BaseAppView
34 34 from rhodecode.authentication.base import authenticate, HTTP_TYPE
35 from rhodecode.authentication.plugins import auth_rhodecode
35 36 from rhodecode.events import UserRegistered, trigger
36 37 from rhodecode.lib import helpers as h
37 38 from rhodecode.lib import audit_logger
38 39 from rhodecode.lib.auth import (
39 40 AuthUser, HasPermissionAnyDecorator, CSRFRequired)
40 41 from rhodecode.lib.base import get_ip_addr
41 42 from rhodecode.lib.exceptions import UserCreationError
42 43 from rhodecode.lib.utils2 import safe_str
43 44 from rhodecode.model.db import User, UserApiKeys
44 45 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
45 46 from rhodecode.model.meta import Session
46 47 from rhodecode.model.auth_token import AuthTokenModel
47 48 from rhodecode.model.settings import SettingsModel
48 49 from rhodecode.model.user import UserModel
49 50 from rhodecode.translation import _
50 51
51 52
52 53 log = logging.getLogger(__name__)
53 54
54 55 CaptchaData = collections.namedtuple(
55 56 'CaptchaData', 'active, private_key, public_key')
56 57
57 58
58 59 def store_user_in_session(session, username, remember=False):
59 60 user = User.get_by_username(username, case_insensitive=True)
60 61 auth_user = AuthUser(user.user_id)
61 62 auth_user.set_authenticated()
62 63 cs = auth_user.get_cookie_store()
63 64 session['rhodecode_user'] = cs
64 65 user.update_lastlogin()
65 66 Session().commit()
66 67
67 68 # If they want to be remembered, update the cookie
68 69 if remember:
69 70 _year = (datetime.datetime.now() +
70 71 datetime.timedelta(seconds=60 * 60 * 24 * 365))
71 72 session._set_cookie_expires(_year)
72 73
73 74 session.save()
74 75
75 76 safe_cs = cs.copy()
76 77 safe_cs['password'] = '****'
77 78 log.info('user %s is now authenticated and stored in '
78 79 'session, session attrs %s', username, safe_cs)
79 80
80 81 # dumps session attrs back to cookie
81 82 session._update_cookie_out()
82 83 # we set new cookie
83 84 headers = None
84 85 if session.request['set_cookie']:
85 86 # send set-cookie headers back to response to update cookie
86 87 headers = [('Set-Cookie', session.request['cookie_out'])]
87 88 return headers
88 89
89 90
90 91 def get_came_from(request):
91 92 came_from = safe_str(request.GET.get('came_from', ''))
92 93 parsed = urlparse.urlparse(came_from)
93 94 allowed_schemes = ['http', 'https']
94 95 default_came_from = h.route_path('home')
95 96 if parsed.scheme and parsed.scheme not in allowed_schemes:
96 97 log.error('Suspicious URL scheme detected %s for url %s',
97 98 parsed.scheme, parsed)
98 99 came_from = default_came_from
99 100 elif parsed.netloc and request.host != parsed.netloc:
100 101 log.error('Suspicious NETLOC detected %s for url %s server url '
101 102 'is: %s', parsed.netloc, parsed, request.host)
102 103 came_from = default_came_from
103 104 elif any(bad_str in parsed.path for bad_str in ('\r', '\n')):
104 105 log.error('Header injection detected `%s` for url %s server url ',
105 106 parsed.path, parsed)
106 107 came_from = default_came_from
107 108
108 109 return came_from or default_came_from
109 110
110 111
111 112 class LoginView(BaseAppView):
112 113
113 114 def load_default_context(self):
114 115 c = self._get_local_tmpl_context()
115 116 c.came_from = get_came_from(self.request)
116 117
117 118 return c
118 119
119 120 def _get_captcha_data(self):
120 121 settings = SettingsModel().get_all_settings()
121 122 private_key = settings.get('rhodecode_captcha_private_key')
122 123 public_key = settings.get('rhodecode_captcha_public_key')
123 124 active = bool(private_key)
124 125 return CaptchaData(
125 126 active=active, private_key=private_key, public_key=public_key)
126 127
127 128 def validate_captcha(self, private_key):
128 129
129 130 captcha_rs = self.request.POST.get('g-recaptcha-response')
130 131 url = "https://www.google.com/recaptcha/api/siteverify"
131 132 params = {
132 133 'secret': private_key,
133 134 'response': captcha_rs,
134 135 'remoteip': get_ip_addr(self.request.environ)
135 136 }
136 137 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
137 138 verify_rs = verify_rs.json()
138 139 captcha_status = verify_rs.get('success', False)
139 140 captcha_errors = verify_rs.get('error-codes', [])
140 141 if not isinstance(captcha_errors, list):
141 142 captcha_errors = [captcha_errors]
142 143 captcha_errors = ', '.join(captcha_errors)
143 144 captcha_message = ''
144 145 if captcha_status is False:
145 146 captcha_message = "Bad captcha. Errors: {}".format(
146 147 captcha_errors)
147 148
148 149 return captcha_status, captcha_message
149 150
150 151 @view_config(
151 152 route_name='login', request_method='GET',
152 153 renderer='rhodecode:templates/login.mako')
153 154 def login(self):
154 155 c = self.load_default_context()
155 156 auth_user = self._rhodecode_user
156 157
157 158 # redirect if already logged in
158 159 if (auth_user.is_authenticated and
159 160 not auth_user.is_default and auth_user.ip_allowed):
160 161 raise HTTPFound(c.came_from)
161 162
162 163 # check if we use headers plugin, and try to login using it.
163 164 try:
164 165 log.debug('Running PRE-AUTH for headers based authentication')
165 166 auth_info = authenticate(
166 167 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
167 168 if auth_info:
168 169 headers = store_user_in_session(
169 170 self.session, auth_info.get('username'))
170 171 raise HTTPFound(c.came_from, headers=headers)
171 172 except UserCreationError as e:
172 173 log.error(e)
173 174 h.flash(e, category='error')
174 175
175 176 return self._get_template_context(c)
176 177
177 178 @view_config(
178 179 route_name='login', request_method='POST',
179 180 renderer='rhodecode:templates/login.mako')
180 181 def login_post(self):
181 182 c = self.load_default_context()
182 183
183 184 login_form = LoginForm(self.request.translate)()
184 185
185 186 try:
186 187 self.session.invalidate()
187 188 form_result = login_form.to_python(self.request.POST)
188 189 # form checks for username/password, now we're authenticated
189 190 headers = store_user_in_session(
190 191 self.session,
191 192 username=form_result['username'],
192 193 remember=form_result['remember'])
193 194 log.debug('Redirecting to "%s" after login.', c.came_from)
194 195
195 196 audit_user = audit_logger.UserWrap(
196 197 username=self.request.POST.get('username'),
197 198 ip_addr=self.request.remote_addr)
198 199 action_data = {'user_agent': self.request.user_agent}
199 200 audit_logger.store_web(
200 201 'user.login.success', action_data=action_data,
201 202 user=audit_user, commit=True)
202 203
203 204 raise HTTPFound(c.came_from, headers=headers)
204 205 except formencode.Invalid as errors:
205 206 defaults = errors.value
206 207 # remove password from filling in form again
207 208 defaults.pop('password', None)
208 209 render_ctx = {
209 210 'errors': errors.error_dict,
210 211 'defaults': defaults,
211 212 }
212 213
213 214 audit_user = audit_logger.UserWrap(
214 215 username=self.request.POST.get('username'),
215 216 ip_addr=self.request.remote_addr)
216 217 action_data = {'user_agent': self.request.user_agent}
217 218 audit_logger.store_web(
218 219 'user.login.failure', action_data=action_data,
219 220 user=audit_user, commit=True)
220 221 return self._get_template_context(c, **render_ctx)
221 222
222 223 except UserCreationError as e:
223 224 # headers auth or other auth functions that create users on
224 225 # the fly can throw this exception signaling that there's issue
225 226 # with user creation, explanation should be provided in
226 227 # Exception itself
227 228 h.flash(e, category='error')
228 229 return self._get_template_context(c)
229 230
230 231 @CSRFRequired()
231 232 @view_config(route_name='logout', request_method='POST')
232 233 def logout(self):
233 234 auth_user = self._rhodecode_user
234 235 log.info('Deleting session for user: `%s`', auth_user)
235 236
236 237 action_data = {'user_agent': self.request.user_agent}
237 238 audit_logger.store_web(
238 239 'user.logout', action_data=action_data,
239 240 user=auth_user, commit=True)
240 241 self.session.delete()
241 242 return HTTPFound(h.route_path('home'))
242 243
243 244 @HasPermissionAnyDecorator(
244 245 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
245 246 @view_config(
246 247 route_name='register', request_method='GET',
247 248 renderer='rhodecode:templates/register.mako',)
248 249 def register(self, defaults=None, errors=None):
249 250 c = self.load_default_context()
250 251 defaults = defaults or {}
251 252 errors = errors or {}
252 253
253 254 settings = SettingsModel().get_all_settings()
254 255 register_message = settings.get('rhodecode_register_message') or ''
255 256 captcha = self._get_captcha_data()
256 257 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 258 .AuthUser().permissions['global']
258 259
259 260 render_ctx = self._get_template_context(c)
260 261 render_ctx.update({
261 262 'defaults': defaults,
262 263 'errors': errors,
263 264 'auto_active': auto_active,
264 265 'captcha_active': captcha.active,
265 266 'captcha_public_key': captcha.public_key,
266 267 'register_message': register_message,
267 268 })
268 269 return render_ctx
269 270
270 271 @HasPermissionAnyDecorator(
271 272 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 273 @view_config(
273 274 route_name='register', request_method='POST',
274 275 renderer='rhodecode:templates/register.mako')
275 276 def register_post(self):
276 277 from rhodecode.authentication.plugins import auth_rhodecode
277 278
278 279 self.load_default_context()
279 280 captcha = self._get_captcha_data()
280 281 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
281 282 .AuthUser().permissions['global']
282 283
283 284 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
284 285 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
285 286
286 287 register_form = RegisterForm(self.request.translate)()
287 288 try:
288 289
289 290 form_result = register_form.to_python(self.request.POST)
290 291 form_result['active'] = auto_active
291 292 external_identity = self.request.POST.get('external_identity')
292 293
293 294 if external_identity:
294 295 extern_name = external_identity
295 296 extern_type = external_identity
296 297
297 298 if captcha.active:
298 299 captcha_status, captcha_message = self.validate_captcha(
299 300 captcha.private_key)
300 301
301 302 if not captcha_status:
302 303 _value = form_result
303 304 _msg = _('Bad captcha')
304 305 error_dict = {'recaptcha_field': captcha_message}
305 306 raise formencode.Invalid(
306 307 _msg, _value, None, error_dict=error_dict)
307 308
308 309 new_user = UserModel().create_registration(
309 310 form_result, extern_name=extern_name, extern_type=extern_type)
310 311
311 312 action_data = {'data': new_user.get_api_data(),
312 313 'user_agent': self.request.user_agent}
313 314
314 315
315 316
316 317 if external_identity:
317 318 action_data['external_identity'] = external_identity
318 319
319 320 audit_user = audit_logger.UserWrap(
320 321 username=new_user.username,
321 322 user_id=new_user.user_id,
322 323 ip_addr=self.request.remote_addr)
323 324
324 325 audit_logger.store_web(
325 326 'user.register', action_data=action_data,
326 327 user=audit_user)
327 328
328 329 event = UserRegistered(user=new_user, session=self.session)
329 330 trigger(event)
330 331 h.flash(
331 332 _('You have successfully registered with RhodeCode'),
332 333 category='success')
333 334 Session().commit()
334 335
335 336 redirect_ro = self.request.route_path('login')
336 337 raise HTTPFound(redirect_ro)
337 338
338 339 except formencode.Invalid as errors:
339 340 errors.value.pop('password', None)
340 341 errors.value.pop('password_confirmation', None)
341 342 return self.register(
342 343 defaults=errors.value, errors=errors.error_dict)
343 344
344 345 except UserCreationError as e:
345 346 # container auth or other auth functions that create users on
346 347 # the fly can throw this exception signaling that there's issue
347 348 # with user creation, explanation should be provided in
348 349 # Exception itself
349 350 h.flash(e, category='error')
350 351 return self.register()
351 352
352 353 @view_config(
353 354 route_name='reset_password', request_method=('GET', 'POST'),
354 355 renderer='rhodecode:templates/password_reset.mako')
355 356 def password_reset(self):
356 357 c = self.load_default_context()
357 358 captcha = self._get_captcha_data()
358 359
359 360 template_context = {
360 361 'captcha_active': captcha.active,
361 362 'captcha_public_key': captcha.public_key,
362 363 'defaults': {},
363 364 'errors': {},
364 365 }
365 366
366 367 # always send implicit message to prevent from discovery of
367 368 # matching emails
368 369 msg = _('If such email exists, a password reset link was sent to it.')
369 370
371 def default_response():
372 log.debug('faking response on invalid password reset')
373 # make this take 2s, to prevent brute forcing.
374 time.sleep(2)
375 h.flash(msg, category='success')
376 return HTTPFound(self.request.route_path('reset_password'))
377
370 378 if self.request.POST:
371 379 if h.HasPermissionAny('hg.password_reset.disabled')():
372 380 _email = self.request.POST.get('email', '')
373 381 log.error('Failed attempt to reset password for `%s`.', _email)
374 h.flash(_('Password reset has been disabled.'),
375 category='error')
382 h.flash(_('Password reset has been disabled.'), category='error')
376 383 return HTTPFound(self.request.route_path('reset_password'))
377 384
378 385 password_reset_form = PasswordResetForm(self.request.translate)()
386 description = u'Generated token for password reset from {}'.format(
387 datetime.datetime.now().isoformat())
388
379 389 try:
380 390 form_result = password_reset_form.to_python(
381 391 self.request.POST)
382 392 user_email = form_result['email']
383 393
384 394 if captcha.active:
385 395 captcha_status, captcha_message = self.validate_captcha(
386 396 captcha.private_key)
387 397
388 398 if not captcha_status:
389 399 _value = form_result
390 400 _msg = _('Bad captcha')
391 401 error_dict = {'recaptcha_field': captcha_message}
392 402 raise formencode.Invalid(
393 403 _msg, _value, None, error_dict=error_dict)
394 404
395 405 # Generate reset URL and send mail.
396 406 user = User.get_by_email(user_email)
397 407
408 # only allow rhodecode based users to reset their password
409 # external auth shouldn't allow password reset
410 if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid:
411 log.warning('User %s with external type `%s` tried a password reset. '
412 'This try was rejected', user, user.extern_type)
413 return default_response()
414
398 415 # generate password reset token that expires in 10 minutes
399 description = u'Generated token for password reset from {}'.format(
400 datetime.datetime.now().isoformat())
401
402 416 reset_token = UserModel().add_auth_token(
403 417 user=user, lifetime_minutes=10,
404 418 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
405 419 description=description)
406 420 Session().commit()
407 421
408 422 log.debug('Successfully created password recovery token')
409 423 password_reset_url = self.request.route_url(
410 424 'reset_password_confirmation',
411 425 _query={'key': reset_token.api_key})
412 426 UserModel().reset_password_link(
413 427 form_result, password_reset_url)
414 # Display success message and redirect.
415 h.flash(msg, category='success')
416 428
417 429 action_data = {'email': user_email,
418 430 'user_agent': self.request.user_agent}
419 431 audit_logger.store_web(
420 432 'user.password.reset_request', action_data=action_data,
421 433 user=self._rhodecode_user, commit=True)
422 return HTTPFound(self.request.route_path('reset_password'))
434
435 return default_response()
423 436
424 437 except formencode.Invalid as errors:
425 438 template_context.update({
426 439 'defaults': errors.value,
427 440 'errors': errors.error_dict,
428 441 })
429 442 if not self.request.POST.get('email'):
430 443 # case of empty email, we want to report that
431 444 return self._get_template_context(c, **template_context)
432 445
433 446 if 'recaptcha_field' in errors.error_dict:
434 447 # case of failed captcha
435 448 return self._get_template_context(c, **template_context)
436 449
437 log.debug('faking response on invalid password reset')
438 # make this take 2s, to prevent brute forcing.
439 time.sleep(2)
440 h.flash(msg, category='success')
441 return HTTPFound(self.request.route_path('reset_password'))
450 return default_response()
442 451
443 452 return self._get_template_context(c, **template_context)
444 453
445 454 @view_config(route_name='reset_password_confirmation',
446 455 request_method='GET')
447 456 def password_reset_confirmation(self):
448 457 self.load_default_context()
449 458 if self.request.GET and self.request.GET.get('key'):
450 459 # make this take 2s, to prevent brute forcing.
451 460 time.sleep(2)
452 461
453 462 token = AuthTokenModel().get_auth_token(
454 463 self.request.GET.get('key'))
455 464
456 465 # verify token is the correct role
457 466 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
458 467 log.debug('Got token with role:%s expected is %s',
459 468 getattr(token, 'role', 'EMPTY_TOKEN'),
460 469 UserApiKeys.ROLE_PASSWORD_RESET)
461 470 h.flash(
462 471 _('Given reset token is invalid'), category='error')
463 472 return HTTPFound(self.request.route_path('reset_password'))
464 473
465 474 try:
466 475 owner = token.user
467 476 data = {'email': owner.email, 'token': token.api_key}
468 477 UserModel().reset_password(data)
469 478 h.flash(
470 479 _('Your password reset was successful, '
471 480 'a new password has been sent to your email'),
472 481 category='success')
473 482 except Exception as e:
474 483 log.error(e)
475 484 return HTTPFound(self.request.route_path('reset_password'))
476 485
477 486 return HTTPFound(self.request.route_path('login'))
@@ -1,150 +1,151 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 15 <div class="infoform">
16 16 <div class="fields">
17 17 <p>${_('This user was created from external source (%s). Editing some of the settings is limited.' % c.extern_type)}</p>
18 18 </div>
19 19 </div>
20 20 %endif
21 21 <div class="form">
22 22 <div class="fields">
23 23 <div class="field">
24 24 <div class="label photo">
25 25 ${_('Photo')}:
26 26 </div>
27 27 <div class="input profile">
28 28 %if c.visual.use_gravatar:
29 29 ${base.gravatar(c.user.email, 100)}
30 30 <p class="help-block">${_('Change the avatar at')} <a href="http://gravatar.com">gravatar.com</a>.</p>
31 31 %else:
32 32 ${base.gravatar(c.user.email, 20)}
33 33 ${_('Avatars are disabled')}
34 34 %endif
35 35 </div>
36 36 </div>
37 37 <div class="field">
38 38 <div class="label">
39 39 ${_('Username')}:
40 40 </div>
41 41 <div class="input">
42 42 ${h.text('username', class_='%s medium' % disabled, readonly=readonly)}
43 43 </div>
44 44 </div>
45 45 <div class="field">
46 46 <div class="label">
47 47 <label for="name">${_('First Name')}:</label>
48 48 </div>
49 49 <div class="input">
50 50 ${h.text('firstname', class_="medium")}
51 51 </div>
52 52 </div>
53 53
54 54 <div class="field">
55 55 <div class="label">
56 56 <label for="lastname">${_('Last Name')}:</label>
57 57 </div>
58 58 <div class="input">
59 59 ${h.text('lastname', class_="medium")}
60 60 </div>
61 61 </div>
62 62
63 63 <div class="field">
64 64 <div class="label">
65 65 <label for="email">${_('Email')}:</label>
66 66 </div>
67 67 <div class="input">
68 68 ## we should be able to edit email !
69 69 ${h.text('email', class_="medium")}
70 70 </div>
71 71 </div>
72 72 <div class="field">
73 73 <div class="label">
74 74 ${_('New Password')}:
75 75 </div>
76 76 <div class="input">
77 77 ${h.password('new_password',class_='%s medium' % disabled,autocomplete="off",readonly=readonly)}
78 78 </div>
79 79 </div>
80 80 <div class="field">
81 81 <div class="label">
82 82 ${_('New Password Confirmation')}:
83 83 </div>
84 84 <div class="input">
85 85 ${h.password('password_confirmation',class_="%s medium" % disabled,autocomplete="off",readonly=readonly)}
86 86 </div>
87 87 </div>
88 88 <div class="field">
89 89 <div class="label-text">
90 90 ${_('Active')}:
91 91 </div>
92 92 <div class="input user-checkbox">
93 93 ${h.checkbox('active',value=True)}
94 94 </div>
95 95 </div>
96 96 <div class="field">
97 97 <div class="label-text">
98 98 ${_('Super Admin')}:
99 99 </div>
100 100 <div class="input user-checkbox">
101 101 ${h.checkbox('admin',value=True)}
102 102 </div>
103 103 </div>
104 104 <div class="field">
105 105 <div class="label-text">
106 ${_('Source of Record')}:
106 ${_('Authentication type')}:
107 107 </div>
108 108 <div class="input">
109 109 <p>${c.extern_type}</p>
110 110 ${h.hidden('extern_type', readonly="readonly")}
111 <p class="help-block">${_('User was created using an external source. He is bound to authentication using this method.')}</p>
111 112 </div>
112 113 </div>
113 114 <div class="field">
114 115 <div class="label-text">
115 116 ${_('Name in Source of Record')}:
116 117 </div>
117 118 <div class="input">
118 119 <p>${c.extern_name}</p>
119 120 ${h.hidden('extern_name', readonly="readonly")}
120 121 </div>
121 122 </div>
122 123 <div class="field">
123 124 <div class="label">
124 125 ${_('Language')}:
125 126 </div>
126 127 <div class="input">
127 128 ## allowed_languages is defined in the users.py
128 129 ## c.language comes from base.py as a default language
129 130 ${h.select('language', c.language, c.allowed_languages)}
130 <p class="help-block">${h.literal(_('Help translate %(rc_link)s into your language.') % {'rc_link': h.link_to('RhodeCode Enterprise', h.route_url('rhodecode_translations'))})}</p>
131 <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 132 </div>
132 133 </div>
133 134 <div class="buttons">
134 135 ${h.submit('save', _('Save'), class_="btn")}
135 136 ${h.reset('reset', _('Reset'), class_="btn")}
136 137 </div>
137 138 </div>
138 139 </div>
139 140 ${h.end_form()}
140 141 </div>
141 142 </div>
142 143 </div>
143 144
144 145 <script>
145 146 $('#language').select2({
146 147 'containerCssClass': "drop-menu",
147 148 'dropdownCssClass': "drop-menu-dropdown",
148 149 'dropdownAutoWidth': true
149 150 });
150 151 </script>
General Comments 0
You need to be logged in to leave comments. Login now