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