##// END OF EJS Templates
feat(2fa): updates routes names and urls to better reflect actions
super-admin -
r5368:4bc46af9 default
parent child Browse files
Show More
@@ -1,554 +1,554 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import time
20 20 import json
21 21 import pyotp
22 22 import qrcode
23 23 import collections
24 24 import datetime
25 25 import formencode
26 26 import formencode.htmlfill
27 27 import logging
28 28 import urllib.parse
29 29 import requests
30 30 from io import BytesIO
31 31 from base64 import b64encode
32 32
33 33 from pyramid.renderers import render
34 34 from pyramid.response import Response
35 35 from pyramid.httpexceptions import HTTPFound
36 36
37 37
38 38 from rhodecode.apps._base import BaseAppView
39 39 from rhodecode.authentication.base import authenticate, HTTP_TYPE
40 40 from rhodecode.authentication.plugins import auth_rhodecode
41 41 from rhodecode.events import UserRegistered, trigger
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib import audit_logger
44 44 from rhodecode.lib.auth import (
45 45 AuthUser, HasPermissionAnyDecorator, CSRFRequired, LoginRequired, NotAnonymous)
46 46 from rhodecode.lib.base import get_ip_addr
47 47 from rhodecode.lib.exceptions import UserCreationError
48 48 from rhodecode.lib.utils2 import safe_str
49 49 from rhodecode.model.db import User, UserApiKeys
50 50 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm, TOTPForm
51 51 from rhodecode.model.meta import Session
52 52 from rhodecode.model.auth_token import AuthTokenModel
53 53 from rhodecode.model.settings import SettingsModel
54 54 from rhodecode.model.user import UserModel
55 55 from rhodecode.translation import _
56 56
57 57
58 58 log = logging.getLogger(__name__)
59 59
60 60 CaptchaData = collections.namedtuple(
61 61 'CaptchaData', 'active, private_key, public_key')
62 62
63 63
64 64 def store_user_in_session(session, user_identifier, remember=False):
65 65 user = User.get_by_username_or_primary_email(user_identifier)
66 66 auth_user = AuthUser(user.user_id)
67 67 auth_user.set_authenticated()
68 68 cs = auth_user.get_cookie_store()
69 69 session['rhodecode_user'] = cs
70 70 user.update_lastlogin()
71 71 Session().commit()
72 72
73 73 # If they want to be remembered, update the cookie
74 74 if remember:
75 75 _year = (datetime.datetime.now() +
76 76 datetime.timedelta(seconds=60 * 60 * 24 * 365))
77 77 session._set_cookie_expires(_year)
78 78
79 79 session.save()
80 80
81 81 safe_cs = cs.copy()
82 82 safe_cs['password'] = '****'
83 83 log.info('user %s is now authenticated and stored in '
84 84 'session, session attrs %s', user_identifier, safe_cs)
85 85
86 86 # dumps session attrs back to cookie
87 87 session._update_cookie_out()
88 88 # we set new cookie
89 89 headers = None
90 90 if session.request['set_cookie']:
91 91 # send set-cookie headers back to response to update cookie
92 92 headers = [('Set-Cookie', session.request['cookie_out'])]
93 93 return headers
94 94
95 95
96 96 def get_came_from(request):
97 97 came_from = safe_str(request.GET.get('came_from', ''))
98 98 parsed = urllib.parse.urlparse(came_from)
99 99
100 100 allowed_schemes = ['http', 'https']
101 101 default_came_from = h.route_path('home')
102 102 if parsed.scheme and parsed.scheme not in allowed_schemes:
103 103 log.error('Suspicious URL scheme detected %s for url %s',
104 104 parsed.scheme, parsed)
105 105 came_from = default_came_from
106 106 elif parsed.netloc and request.host != parsed.netloc:
107 107 log.error('Suspicious NETLOC detected %s for url %s server url '
108 108 'is: %s', parsed.netloc, parsed, request.host)
109 109 came_from = default_came_from
110 110 elif any(bad_char in came_from for bad_char in ('\r', '\n')):
111 111 log.error('Header injection detected `%s` for url %s server url ',
112 112 parsed.path, parsed)
113 113 came_from = default_came_from
114 114
115 115 return came_from or default_came_from
116 116
117 117
118 118 class LoginView(BaseAppView):
119 119
120 120 def load_default_context(self):
121 121 c = self._get_local_tmpl_context()
122 122 c.came_from = get_came_from(self.request)
123 123 return c
124 124
125 125 def _get_captcha_data(self):
126 126 settings = SettingsModel().get_all_settings()
127 127 private_key = settings.get('rhodecode_captcha_private_key')
128 128 public_key = settings.get('rhodecode_captcha_public_key')
129 129 active = bool(private_key)
130 130 return CaptchaData(
131 131 active=active, private_key=private_key, public_key=public_key)
132 132
133 133 def validate_captcha(self, private_key):
134 134
135 135 captcha_rs = self.request.POST.get('g-recaptcha-response')
136 136 url = "https://www.google.com/recaptcha/api/siteverify"
137 137 params = {
138 138 'secret': private_key,
139 139 'response': captcha_rs,
140 140 'remoteip': get_ip_addr(self.request.environ)
141 141 }
142 142 verify_rs = requests.get(url, params=params, verify=True, timeout=60)
143 143 verify_rs = verify_rs.json()
144 144 captcha_status = verify_rs.get('success', False)
145 145 captcha_errors = verify_rs.get('error-codes', [])
146 146 if not isinstance(captcha_errors, list):
147 147 captcha_errors = [captcha_errors]
148 148 captcha_errors = ', '.join(captcha_errors)
149 149 captcha_message = ''
150 150 if captcha_status is False:
151 151 captcha_message = "Bad captcha. Errors: {}".format(
152 152 captcha_errors)
153 153
154 154 return captcha_status, captcha_message
155 155
156 156 def login(self):
157 157 c = self.load_default_context()
158 158 auth_user = self._rhodecode_user
159 159
160 160 # redirect if already logged in
161 161 if (auth_user.is_authenticated and
162 162 not auth_user.is_default and auth_user.ip_allowed):
163 163 raise HTTPFound(c.came_from)
164 164
165 165 # check if we use headers plugin, and try to login using it.
166 166 try:
167 167 log.debug('Running PRE-AUTH for headers based authentication')
168 168 auth_info = authenticate(
169 169 '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
170 170 if auth_info:
171 171 headers = store_user_in_session(
172 172 self.session, auth_info.get('username'))
173 173 raise HTTPFound(c.came_from, headers=headers)
174 174 except UserCreationError as e:
175 175 log.error(e)
176 176 h.flash(e, category='error')
177 177
178 178 return self._get_template_context(c)
179 179
180 180 def login_post(self):
181 181 c = self.load_default_context()
182 182
183 183 login_form = LoginForm(self.request.translate)()
184 184
185 185 try:
186 186 self.session.invalidate()
187 187 form_result = login_form.to_python(self.request.POST)
188 188 # form checks for username/password, now we're authenticated
189 189 username = form_result['username']
190 190 if (user := User.get_by_username_or_primary_email(username)).has_enabled_2fa:
191 191 user.has_check_2fa_flag = True
192 192
193 193 headers = store_user_in_session(
194 194 self.session,
195 195 user_identifier=username,
196 196 remember=form_result['remember'])
197 197 log.debug('Redirecting to "%s" after login.', c.came_from)
198 198
199 199 audit_user = audit_logger.UserWrap(
200 200 username=self.request.POST.get('username'),
201 201 ip_addr=self.request.remote_addr)
202 202 action_data = {'user_agent': self.request.user_agent}
203 203 audit_logger.store_web(
204 204 'user.login.success', action_data=action_data,
205 205 user=audit_user, commit=True)
206 206
207 207 raise HTTPFound(c.came_from, headers=headers)
208 208 except formencode.Invalid as errors:
209 209 defaults = errors.value
210 210 # remove password from filling in form again
211 211 defaults.pop('password', None)
212 212 render_ctx = {
213 213 'errors': errors.error_dict,
214 214 'defaults': defaults,
215 215 }
216 216
217 217 audit_user = audit_logger.UserWrap(
218 218 username=self.request.POST.get('username'),
219 219 ip_addr=self.request.remote_addr)
220 220 action_data = {'user_agent': self.request.user_agent}
221 221 audit_logger.store_web(
222 222 'user.login.failure', action_data=action_data,
223 223 user=audit_user, commit=True)
224 224 return self._get_template_context(c, **render_ctx)
225 225
226 226 except UserCreationError as e:
227 227 # headers auth or other auth functions that create users on
228 228 # the fly can throw this exception signaling that there's issue
229 229 # with user creation, explanation should be provided in
230 230 # Exception itself
231 231 h.flash(e, category='error')
232 232 return self._get_template_context(c)
233 233
234 234 @CSRFRequired()
235 235 def logout(self):
236 236 auth_user = self._rhodecode_user
237 237 log.info('Deleting session for user: `%s`', auth_user)
238 238
239 239 action_data = {'user_agent': self.request.user_agent}
240 240 audit_logger.store_web(
241 241 'user.logout', action_data=action_data,
242 242 user=auth_user, commit=True)
243 243 self.session.delete()
244 244 return HTTPFound(h.route_path('home'))
245 245
246 246 @HasPermissionAnyDecorator(
247 247 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
248 248 def register(self, defaults=None, errors=None):
249 249 c = self.load_default_context()
250 250 defaults = defaults or {}
251 251 errors = errors or {}
252 252
253 253 settings = SettingsModel().get_all_settings()
254 254 register_message = settings.get('rhodecode_register_message') or ''
255 255 captcha = self._get_captcha_data()
256 256 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
257 257 .AuthUser().permissions['global']
258 258
259 259 render_ctx = self._get_template_context(c)
260 260 render_ctx.update({
261 261 'defaults': defaults,
262 262 'errors': errors,
263 263 'auto_active': auto_active,
264 264 'captcha_active': captcha.active,
265 265 'captcha_public_key': captcha.public_key,
266 266 'register_message': register_message,
267 267 })
268 268 return render_ctx
269 269
270 270 @HasPermissionAnyDecorator(
271 271 'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
272 272 def register_post(self):
273 273 from rhodecode.authentication.plugins import auth_rhodecode
274 274
275 275 self.load_default_context()
276 276 captcha = self._get_captcha_data()
277 277 auto_active = 'hg.register.auto_activate' in User.get_default_user()\
278 278 .AuthUser().permissions['global']
279 279
280 280 extern_name = auth_rhodecode.RhodeCodeAuthPlugin.uid
281 281 extern_type = auth_rhodecode.RhodeCodeAuthPlugin.uid
282 282
283 283 register_form = RegisterForm(self.request.translate)()
284 284 try:
285 285
286 286 form_result = register_form.to_python(self.request.POST)
287 287 form_result['active'] = auto_active
288 288 external_identity = self.request.POST.get('external_identity')
289 289
290 290 if external_identity:
291 291 extern_name = external_identity
292 292 extern_type = external_identity
293 293
294 294 if captcha.active:
295 295 captcha_status, captcha_message = self.validate_captcha(
296 296 captcha.private_key)
297 297
298 298 if not captcha_status:
299 299 _value = form_result
300 300 _msg = _('Bad captcha')
301 301 error_dict = {'recaptcha_field': captcha_message}
302 302 raise formencode.Invalid(
303 303 _msg, _value, None, error_dict=error_dict)
304 304
305 305 new_user = UserModel().create_registration(
306 306 form_result, extern_name=extern_name, extern_type=extern_type)
307 307
308 308 action_data = {'data': new_user.get_api_data(),
309 309 'user_agent': self.request.user_agent}
310 310
311 311 if external_identity:
312 312 action_data['external_identity'] = external_identity
313 313
314 314 audit_user = audit_logger.UserWrap(
315 315 username=new_user.username,
316 316 user_id=new_user.user_id,
317 317 ip_addr=self.request.remote_addr)
318 318
319 319 audit_logger.store_web(
320 320 'user.register', action_data=action_data,
321 321 user=audit_user)
322 322
323 323 event = UserRegistered(user=new_user, session=self.session)
324 324 trigger(event)
325 325 h.flash(
326 326 _('You have successfully registered with RhodeCode. You can log-in now.'),
327 327 category='success')
328 328 if external_identity:
329 329 h.flash(
330 330 _('Please use the {identity} button to log-in').format(
331 331 identity=external_identity),
332 332 category='success')
333 333 Session().commit()
334 334
335 335 redirect_ro = self.request.route_path('login')
336 336 raise HTTPFound(redirect_ro)
337 337
338 338 except formencode.Invalid as errors:
339 339 errors.value.pop('password', None)
340 340 errors.value.pop('password_confirmation', None)
341 341 return self.register(
342 342 defaults=errors.value, errors=errors.error_dict)
343 343
344 344 except UserCreationError as e:
345 345 # container auth or other auth functions that create users on
346 346 # the fly can throw this exception signaling that there's issue
347 347 # with user creation, explanation should be provided in
348 348 # Exception itself
349 349 h.flash(e, category='error')
350 350 return self.register()
351 351
352 352 def password_reset(self):
353 353 c = self.load_default_context()
354 354 captcha = self._get_captcha_data()
355 355
356 356 template_context = {
357 357 'captcha_active': captcha.active,
358 358 'captcha_public_key': captcha.public_key,
359 359 'defaults': {},
360 360 'errors': {},
361 361 }
362 362
363 363 # always send implicit message to prevent from discovery of
364 364 # matching emails
365 365 msg = _('If such email exists, a password reset link was sent to it.')
366 366
367 367 def default_response():
368 368 log.debug('faking response on invalid password reset')
369 369 # make this take 2s, to prevent brute forcing.
370 370 time.sleep(2)
371 371 h.flash(msg, category='success')
372 372 return HTTPFound(self.request.route_path('reset_password'))
373 373
374 374 if self.request.POST:
375 375 if h.HasPermissionAny('hg.password_reset.disabled')():
376 376 _email = self.request.POST.get('email', '')
377 377 log.error('Failed attempt to reset password for `%s`.', _email)
378 378 h.flash(_('Password reset has been disabled.'), category='error')
379 379 return HTTPFound(self.request.route_path('reset_password'))
380 380
381 381 password_reset_form = PasswordResetForm(self.request.translate)()
382 382 description = 'Generated token for password reset from {}'.format(
383 383 datetime.datetime.now().isoformat())
384 384
385 385 try:
386 386 form_result = password_reset_form.to_python(
387 387 self.request.POST)
388 388 user_email = form_result['email']
389 389
390 390 if captcha.active:
391 391 captcha_status, captcha_message = self.validate_captcha(
392 392 captcha.private_key)
393 393
394 394 if not captcha_status:
395 395 _value = form_result
396 396 _msg = _('Bad captcha')
397 397 error_dict = {'recaptcha_field': captcha_message}
398 398 raise formencode.Invalid(
399 399 _msg, _value, None, error_dict=error_dict)
400 400
401 401 # Generate reset URL and send mail.
402 402 user = User.get_by_email(user_email)
403 403
404 404 # only allow rhodecode based users to reset their password
405 405 # external auth shouldn't allow password reset
406 406 if user and user.extern_type != auth_rhodecode.RhodeCodeAuthPlugin.uid:
407 407 log.warning('User %s with external type `%s` tried a password reset. '
408 408 'This try was rejected', user, user.extern_type)
409 409 return default_response()
410 410
411 411 # generate password reset token that expires in 10 minutes
412 412 reset_token = UserModel().add_auth_token(
413 413 user=user, lifetime_minutes=10,
414 414 role=UserModel.auth_token_role.ROLE_PASSWORD_RESET,
415 415 description=description)
416 416 Session().commit()
417 417
418 418 log.debug('Successfully created password recovery token')
419 419 password_reset_url = self.request.route_url(
420 420 'reset_password_confirmation',
421 421 _query={'key': reset_token.api_key})
422 422 UserModel().reset_password_link(
423 423 form_result, password_reset_url)
424 424
425 425 action_data = {'email': user_email,
426 426 'user_agent': self.request.user_agent}
427 427 audit_logger.store_web(
428 428 'user.password.reset_request', action_data=action_data,
429 429 user=self._rhodecode_user, commit=True)
430 430
431 431 return default_response()
432 432
433 433 except formencode.Invalid as errors:
434 434 template_context.update({
435 435 'defaults': errors.value,
436 436 'errors': errors.error_dict,
437 437 })
438 438 if not self.request.POST.get('email'):
439 439 # case of empty email, we want to report that
440 440 return self._get_template_context(c, **template_context)
441 441
442 442 if 'recaptcha_field' in errors.error_dict:
443 443 # case of failed captcha
444 444 return self._get_template_context(c, **template_context)
445 445
446 446 return default_response()
447 447
448 448 return self._get_template_context(c, **template_context)
449 449
450 450 @LoginRequired()
451 451 @NotAnonymous()
452 452 def password_reset_confirmation(self):
453 453 self.load_default_context()
454 454 if self.request.GET and self.request.GET.get('key'):
455 455 # make this take 2s, to prevent brute forcing.
456 456 time.sleep(2)
457 457
458 458 token = AuthTokenModel().get_auth_token(
459 459 self.request.GET.get('key'))
460 460
461 461 # verify token is the correct role
462 462 if token is None or token.role != UserApiKeys.ROLE_PASSWORD_RESET:
463 463 log.debug('Got token with role:%s expected is %s',
464 464 getattr(token, 'role', 'EMPTY_TOKEN'),
465 465 UserApiKeys.ROLE_PASSWORD_RESET)
466 466 h.flash(
467 467 _('Given reset token is invalid'), category='error')
468 468 return HTTPFound(self.request.route_path('reset_password'))
469 469
470 470 try:
471 471 owner = token.user
472 472 data = {'email': owner.email, 'token': token.api_key}
473 473 UserModel().reset_password(data)
474 474 h.flash(
475 475 _('Your password reset was successful, '
476 476 'a new password has been sent to your email'),
477 477 category='success')
478 478 except Exception as e:
479 479 log.error(e)
480 480 return HTTPFound(self.request.route_path('reset_password'))
481 481
482 482 return HTTPFound(self.request.route_path('login'))
483 483
484 484 @LoginRequired()
485 485 @NotAnonymous()
486 486 def setup_2fa(self):
487 487 _ = self.request.translate
488 488 c = self.load_default_context()
489 489 user_instance = self._rhodecode_db_user
490 490 form = TOTPForm(_, user_instance)()
491 491 render_ctx = {}
492 492 if self.request.method == 'POST':
493 493 post_items = dict(self.request.POST)
494 494
495 495 try:
496 496 form_details = form.to_python(post_items)
497 497 secret = form_details['secret_totp']
498 498
499 499 user_instance.init_2fa_recovery_codes(persist=True, force=True)
500 500 user_instance.set_2fa_secret(secret)
501 501
502 502 Session().commit()
503 raise HTTPFound(self.request.route_path('my_account_enable_2fa', _query={'show-recovery-codes': 1}))
503 raise HTTPFound(self.request.route_path('my_account_configure_2fa', _query={'show-recovery-codes': 1}))
504 504 except formencode.Invalid as errors:
505 505 defaults = errors.value
506 506 render_ctx = {
507 507 'errors': errors.error_dict,
508 508 'defaults': defaults,
509 509 }
510 510
511 511 # NOTE: here we DO NOT persist the secret 2FA, since this is only for setup, once a setup is completed
512 512 # only then we should persist it
513 513 secret = user_instance.init_secret_2fa(persist=False)
514 514
515 515 totp_name = f'RhodeCode token ({self.request.user.username})'
516 516
517 517 qr = qrcode.QRCode(version=1, box_size=10, border=5)
518 518 qr.add_data(pyotp.totp.TOTP(secret).provisioning_uri(name=totp_name))
519 519 qr.make(fit=True)
520 520 img = qr.make_image(fill_color='black', back_color='white')
521 521 buffered = BytesIO()
522 522 img.save(buffered)
523 523 return self._get_template_context(
524 524 c,
525 525 qr=b64encode(buffered.getvalue()).decode("utf-8"),
526 526 key=secret,
527 527 totp_name=totp_name,
528 528 ** render_ctx
529 529 )
530 530
531 531 @LoginRequired()
532 532 @NotAnonymous()
533 533 def verify_2fa(self):
534 534 _ = self.request.translate
535 535 c = self.load_default_context()
536 536 render_ctx = {}
537 537 user_instance = self._rhodecode_db_user
538 538 totp_form = TOTPForm(_, user_instance, allow_recovery_code_use=True)()
539 539 if self.request.method == 'POST':
540 540 post_items = dict(self.request.POST)
541 541 # NOTE: inject secret, as it's a post configured saved item.
542 542 post_items['secret_totp'] = user_instance.get_secret_2fa()
543 543 try:
544 544 totp_form.to_python(post_items)
545 545 user_instance.has_check_2fa_flag = False
546 546 Session().commit()
547 547 raise HTTPFound(c.came_from)
548 548 except formencode.Invalid as errors:
549 549 defaults = errors.value
550 550 render_ctx = {
551 551 'errors': errors.error_dict,
552 552 'defaults': defaults,
553 553 }
554 554 return self._get_template_context(c, **render_ctx)
@@ -1,370 +1,370 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 from rhodecode.apps._base import ADMIN_PREFIX
21 21
22 22
23 23 def includeme(config):
24 24 from rhodecode.apps.my_account.views.my_account import MyAccountView
25 25 from rhodecode.apps.my_account.views.my_account_notifications import MyAccountNotificationsView
26 26 from rhodecode.apps.my_account.views.my_account_ssh_keys import MyAccountSshKeysView
27 27
28 28 config.add_route(
29 29 name='my_account_profile',
30 30 pattern=ADMIN_PREFIX + '/my_account/profile')
31 31 config.add_view(
32 32 MyAccountView,
33 33 attr='my_account_profile',
34 34 route_name='my_account_profile', request_method='GET',
35 35 renderer='rhodecode:templates/admin/my_account/my_account.mako')
36 36
37 37 # my account edit details
38 38 config.add_route(
39 39 name='my_account_edit',
40 40 pattern=ADMIN_PREFIX + '/my_account/edit')
41 41 config.add_view(
42 42 MyAccountView,
43 43 attr='my_account_edit',
44 44 route_name='my_account_edit',
45 45 request_method='GET',
46 46 renderer='rhodecode:templates/admin/my_account/my_account.mako')
47 47
48 48 config.add_route(
49 49 name='my_account_update',
50 50 pattern=ADMIN_PREFIX + '/my_account/update')
51 51 config.add_view(
52 52 MyAccountView,
53 53 attr='my_account_update',
54 54 route_name='my_account_update',
55 55 request_method='POST',
56 56 renderer='rhodecode:templates/admin/my_account/my_account.mako')
57 57
58 58 # my account password
59 59 config.add_route(
60 60 name='my_account_password',
61 61 pattern=ADMIN_PREFIX + '/my_account/password')
62 62 config.add_view(
63 63 MyAccountView,
64 64 attr='my_account_password',
65 65 route_name='my_account_password', request_method='GET',
66 66 renderer='rhodecode:templates/admin/my_account/my_account.mako')
67 67
68 68 config.add_route(
69 69 name='my_account_password_update',
70 70 pattern=ADMIN_PREFIX + '/my_account/password/update')
71 71 config.add_view(
72 72 MyAccountView,
73 73 attr='my_account_password_update',
74 74 route_name='my_account_password_update', request_method='POST',
75 75 renderer='rhodecode:templates/admin/my_account/my_account.mako')
76 76
77 77 # my account 2fa
78 78 config.add_route(
79 name='my_account_enable_2fa',
80 pattern=ADMIN_PREFIX + '/my_account/enable_2fa')
79 name='my_account_configure_2fa',
80 pattern=ADMIN_PREFIX + '/my_account/configure_2fa')
81 81 config.add_view(
82 82 MyAccountView,
83 83 attr='my_account_2fa',
84 route_name='my_account_enable_2fa', request_method='GET',
84 route_name='my_account_configure_2fa', request_method='GET',
85 85 renderer='rhodecode:templates/admin/my_account/my_account.mako')
86 86 # my account 2fa save
87 87 config.add_route(
88 name='my_account_enable_2fa_save',
89 pattern=ADMIN_PREFIX + '/my_account/enable_2fa_save')
88 name='my_account_configure_2fa_update',
89 pattern=ADMIN_PREFIX + '/my_account/configure_2fa_update')
90 90 config.add_view(
91 91 MyAccountView,
92 92 attr='my_account_2fa_update',
93 route_name='my_account_enable_2fa_save', request_method='POST',
93 route_name='my_account_configure_2fa_update', request_method='POST',
94 94 renderer='rhodecode:templates/admin/my_account/my_account.mako')
95 95
96 96 # my account 2fa recovery code-reset
97 97 config.add_route(
98 98 name='my_account_show_2fa_recovery_codes',
99 99 pattern=ADMIN_PREFIX + '/my_account/recovery_codes')
100 100 config.add_view(
101 101 MyAccountView,
102 102 attr='my_account_2fa_show_recovery_codes',
103 103 route_name='my_account_show_2fa_recovery_codes', request_method='POST', xhr=True,
104 104 renderer='json_ext')
105 105
106 106 # my account 2fa recovery code-reset
107 107 config.add_route(
108 108 name='my_account_regenerate_2fa_recovery_codes',
109 109 pattern=ADMIN_PREFIX + '/my_account/regenerate_recovery_codes')
110 110 config.add_view(
111 111 MyAccountView,
112 112 attr='my_account_2fa_regenerate_recovery_codes',
113 113 route_name='my_account_regenerate_2fa_recovery_codes', request_method='POST',
114 114 renderer='rhodecode:templates/admin/my_account/my_account.mako')
115 115
116 116 # my account tokens
117 117 config.add_route(
118 118 name='my_account_auth_tokens',
119 119 pattern=ADMIN_PREFIX + '/my_account/auth_tokens')
120 120 config.add_view(
121 121 MyAccountView,
122 122 attr='my_account_auth_tokens',
123 123 route_name='my_account_auth_tokens', request_method='GET',
124 124 renderer='rhodecode:templates/admin/my_account/my_account.mako')
125 125
126 126 config.add_route(
127 127 name='my_account_auth_tokens_view',
128 128 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/view')
129 129 config.add_view(
130 130 MyAccountView,
131 131 attr='my_account_auth_tokens_view',
132 132 route_name='my_account_auth_tokens_view', request_method='POST', xhr=True,
133 133 renderer='json_ext')
134 134
135 135 config.add_route(
136 136 name='my_account_auth_tokens_add',
137 137 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/new')
138 138 config.add_view(
139 139 MyAccountView,
140 140 attr='my_account_auth_tokens_add',
141 141 route_name='my_account_auth_tokens_add', request_method='POST')
142 142
143 143 config.add_route(
144 144 name='my_account_auth_tokens_delete',
145 145 pattern=ADMIN_PREFIX + '/my_account/auth_tokens/delete')
146 146 config.add_view(
147 147 MyAccountView,
148 148 attr='my_account_auth_tokens_delete',
149 149 route_name='my_account_auth_tokens_delete', request_method='POST')
150 150
151 151 # my account ssh keys
152 152 config.add_route(
153 153 name='my_account_ssh_keys',
154 154 pattern=ADMIN_PREFIX + '/my_account/ssh_keys')
155 155 config.add_view(
156 156 MyAccountSshKeysView,
157 157 attr='my_account_ssh_keys',
158 158 route_name='my_account_ssh_keys', request_method='GET',
159 159 renderer='rhodecode:templates/admin/my_account/my_account.mako')
160 160
161 161 config.add_route(
162 162 name='my_account_ssh_keys_generate',
163 163 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/generate')
164 164 config.add_view(
165 165 MyAccountSshKeysView,
166 166 attr='ssh_keys_generate_keypair',
167 167 route_name='my_account_ssh_keys_generate', request_method='GET',
168 168 renderer='rhodecode:templates/admin/my_account/my_account.mako')
169 169
170 170 config.add_route(
171 171 name='my_account_ssh_keys_add',
172 172 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/new')
173 173 config.add_view(
174 174 MyAccountSshKeysView,
175 175 attr='my_account_ssh_keys_add',
176 176 route_name='my_account_ssh_keys_add', request_method='POST',)
177 177
178 178 config.add_route(
179 179 name='my_account_ssh_keys_delete',
180 180 pattern=ADMIN_PREFIX + '/my_account/ssh_keys/delete')
181 181 config.add_view(
182 182 MyAccountSshKeysView,
183 183 attr='my_account_ssh_keys_delete',
184 184 route_name='my_account_ssh_keys_delete', request_method='POST')
185 185
186 186 # my account user group membership
187 187 config.add_route(
188 188 name='my_account_user_group_membership',
189 189 pattern=ADMIN_PREFIX + '/my_account/user_group_membership')
190 190 config.add_view(
191 191 MyAccountView,
192 192 attr='my_account_user_group_membership',
193 193 route_name='my_account_user_group_membership',
194 194 request_method='GET',
195 195 renderer='rhodecode:templates/admin/my_account/my_account.mako')
196 196
197 197 # my account emails
198 198 config.add_route(
199 199 name='my_account_emails',
200 200 pattern=ADMIN_PREFIX + '/my_account/emails')
201 201 config.add_view(
202 202 MyAccountView,
203 203 attr='my_account_emails',
204 204 route_name='my_account_emails', request_method='GET',
205 205 renderer='rhodecode:templates/admin/my_account/my_account.mako')
206 206
207 207 config.add_route(
208 208 name='my_account_emails_add',
209 209 pattern=ADMIN_PREFIX + '/my_account/emails/new')
210 210 config.add_view(
211 211 MyAccountView,
212 212 attr='my_account_emails_add',
213 213 route_name='my_account_emails_add', request_method='POST',
214 214 renderer='rhodecode:templates/admin/my_account/my_account.mako')
215 215
216 216 config.add_route(
217 217 name='my_account_emails_delete',
218 218 pattern=ADMIN_PREFIX + '/my_account/emails/delete')
219 219 config.add_view(
220 220 MyAccountView,
221 221 attr='my_account_emails_delete',
222 222 route_name='my_account_emails_delete', request_method='POST')
223 223
224 224 config.add_route(
225 225 name='my_account_repos',
226 226 pattern=ADMIN_PREFIX + '/my_account/repos')
227 227 config.add_view(
228 228 MyAccountView,
229 229 attr='my_account_repos',
230 230 route_name='my_account_repos', request_method='GET',
231 231 renderer='rhodecode:templates/admin/my_account/my_account.mako')
232 232
233 233 config.add_route(
234 234 name='my_account_watched',
235 235 pattern=ADMIN_PREFIX + '/my_account/watched')
236 236 config.add_view(
237 237 MyAccountView,
238 238 attr='my_account_watched',
239 239 route_name='my_account_watched', request_method='GET',
240 240 renderer='rhodecode:templates/admin/my_account/my_account.mako')
241 241
242 242 config.add_route(
243 243 name='my_account_bookmarks',
244 244 pattern=ADMIN_PREFIX + '/my_account/bookmarks')
245 245 config.add_view(
246 246 MyAccountView,
247 247 attr='my_account_bookmarks',
248 248 route_name='my_account_bookmarks', request_method='GET',
249 249 renderer='rhodecode:templates/admin/my_account/my_account.mako')
250 250
251 251 config.add_route(
252 252 name='my_account_bookmarks_update',
253 253 pattern=ADMIN_PREFIX + '/my_account/bookmarks/update')
254 254 config.add_view(
255 255 MyAccountView,
256 256 attr='my_account_bookmarks_update',
257 257 route_name='my_account_bookmarks_update', request_method='POST')
258 258
259 259 config.add_route(
260 260 name='my_account_goto_bookmark',
261 261 pattern=ADMIN_PREFIX + '/my_account/bookmark/{bookmark_id}')
262 262 config.add_view(
263 263 MyAccountView,
264 264 attr='my_account_goto_bookmark',
265 265 route_name='my_account_goto_bookmark', request_method='GET',
266 266 renderer='rhodecode:templates/admin/my_account/my_account.mako')
267 267
268 268 config.add_route(
269 269 name='my_account_perms',
270 270 pattern=ADMIN_PREFIX + '/my_account/perms')
271 271 config.add_view(
272 272 MyAccountView,
273 273 attr='my_account_perms',
274 274 route_name='my_account_perms', request_method='GET',
275 275 renderer='rhodecode:templates/admin/my_account/my_account.mako')
276 276
277 277 config.add_route(
278 278 name='my_account_notifications',
279 279 pattern=ADMIN_PREFIX + '/my_account/notifications')
280 280 config.add_view(
281 281 MyAccountView,
282 282 attr='my_notifications',
283 283 route_name='my_account_notifications', request_method='GET',
284 284 renderer='rhodecode:templates/admin/my_account/my_account.mako')
285 285
286 286 config.add_route(
287 287 name='my_account_notifications_toggle_visibility',
288 288 pattern=ADMIN_PREFIX + '/my_account/toggle_visibility')
289 289 config.add_view(
290 290 MyAccountView,
291 291 attr='my_notifications_toggle_visibility',
292 292 route_name='my_account_notifications_toggle_visibility',
293 293 request_method='POST', renderer='json_ext')
294 294
295 295 # my account pull requests
296 296 config.add_route(
297 297 name='my_account_pullrequests',
298 298 pattern=ADMIN_PREFIX + '/my_account/pull_requests')
299 299 config.add_view(
300 300 MyAccountView,
301 301 attr='my_account_pullrequests',
302 302 route_name='my_account_pullrequests',
303 303 request_method='GET',
304 304 renderer='rhodecode:templates/admin/my_account/my_account.mako')
305 305
306 306 config.add_route(
307 307 name='my_account_pullrequests_data',
308 308 pattern=ADMIN_PREFIX + '/my_account/pull_requests/data')
309 309 config.add_view(
310 310 MyAccountView,
311 311 attr='my_account_pullrequests_data',
312 312 route_name='my_account_pullrequests_data',
313 313 request_method='GET', renderer='json_ext')
314 314
315 315 # channelstream test
316 316 config.add_route(
317 317 name='my_account_notifications_test_channelstream',
318 318 pattern=ADMIN_PREFIX + '/my_account/test_channelstream')
319 319 config.add_view(
320 320 MyAccountView,
321 321 attr='my_account_notifications_test_channelstream',
322 322 route_name='my_account_notifications_test_channelstream',
323 323 request_method='POST', renderer='json_ext')
324 324
325 325 # notifications
326 326 config.add_route(
327 327 name='notifications_show_all',
328 328 pattern=ADMIN_PREFIX + '/notifications')
329 329 config.add_view(
330 330 MyAccountNotificationsView,
331 331 attr='notifications_show_all',
332 332 route_name='notifications_show_all', request_method='GET',
333 333 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
334 334
335 335 # notifications
336 336 config.add_route(
337 337 name='notifications_mark_all_read',
338 338 pattern=ADMIN_PREFIX + '/notifications_mark_all_read')
339 339 config.add_view(
340 340 MyAccountNotificationsView,
341 341 attr='notifications_mark_all_read',
342 342 route_name='notifications_mark_all_read', request_method='POST',
343 343 renderer='rhodecode:templates/admin/notifications/notifications_show_all.mako')
344 344
345 345 config.add_route(
346 346 name='notifications_show',
347 347 pattern=ADMIN_PREFIX + '/notifications/{notification_id}')
348 348 config.add_view(
349 349 MyAccountNotificationsView,
350 350 attr='notifications_show',
351 351 route_name='notifications_show', request_method='GET',
352 352 renderer='rhodecode:templates/admin/notifications/notifications_show.mako')
353 353
354 354 config.add_route(
355 355 name='notifications_update',
356 356 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/update')
357 357 config.add_view(
358 358 MyAccountNotificationsView,
359 359 attr='notification_update',
360 360 route_name='notifications_update', request_method='POST',
361 361 renderer='json_ext')
362 362
363 363 config.add_route(
364 364 name='notifications_delete',
365 365 pattern=ADMIN_PREFIX + '/notifications/{notification_id}/delete')
366 366 config.add_view(
367 367 MyAccountNotificationsView,
368 368 attr='notification_delete',
369 369 route_name='notifications_delete', request_method='POST',
370 370 renderer='json_ext')
@@ -1,858 +1,858 b''
1 1 # Copyright (C) 2016-2023 RhodeCode GmbH
2 2 #
3 3 # This program is free software: you can redistribute it and/or modify
4 4 # it under the terms of the GNU Affero General Public License, version 3
5 5 # (only), as published by the Free Software Foundation.
6 6 #
7 7 # This program is distributed in the hope that it will be useful,
8 8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 # GNU General Public License for more details.
11 11 #
12 12 # You should have received a copy of the GNU Affero General Public License
13 13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 #
15 15 # This program is dual-licensed. If you wish to learn more about the
16 16 # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 import time
20 20 import logging
21 21 import datetime
22 22 import string
23 23
24 24 import formencode
25 25 import formencode.htmlfill
26 26 import peppercorn
27 27 from pyramid.httpexceptions import HTTPFound, HTTPNotFound
28 28
29 29 from rhodecode.apps._base import BaseAppView, DataGridAppView
30 30 from rhodecode import forms
31 31 from rhodecode.lib import helpers as h
32 32 from rhodecode.lib import audit_logger
33 33 from rhodecode.lib import ext_json
34 34 from rhodecode.lib.auth import (
35 35 LoginRequired, NotAnonymous, CSRFRequired,
36 36 HasRepoPermissionAny, HasRepoGroupPermissionAny, AuthUser)
37 37 from rhodecode.lib.channelstream import (
38 38 channelstream_request, ChannelstreamException)
39 39 from rhodecode.lib.hash_utils import md5_safe
40 40 from rhodecode.lib.utils2 import safe_int, md5, str2bool
41 41 from rhodecode.model.auth_token import AuthTokenModel
42 42 from rhodecode.model.comment import CommentsModel
43 43 from rhodecode.model.db import (
44 44 IntegrityError, or_, in_filter_generator, select,
45 45 Repository, UserEmailMap, UserApiKeys, UserFollowing,
46 46 PullRequest, UserBookmark, RepoGroup, ChangesetStatus)
47 47 from rhodecode.model.forms import TOTPForm
48 48 from rhodecode.model.meta import Session
49 49 from rhodecode.model.pull_request import PullRequestModel
50 50 from rhodecode.model.user import UserModel
51 51 from rhodecode.model.user_group import UserGroupModel
52 52 from rhodecode.model.validation_schema.schemas import user_schema
53 53
54 54 log = logging.getLogger(__name__)
55 55
56 56
57 57 class MyAccountView(BaseAppView, DataGridAppView):
58 58 ALLOW_SCOPED_TOKENS = False
59 59 """
60 60 This view has alternative version inside EE, if modified please take a look
61 61 in there as well.
62 62 """
63 63
64 64 def load_default_context(self):
65 65 c = self._get_local_tmpl_context()
66 66 c.user = c.auth_user.get_instance()
67 67 c.allow_scoped_tokens = self.ALLOW_SCOPED_TOKENS
68 68 return c
69 69
70 70 @LoginRequired()
71 71 @NotAnonymous()
72 72 def my_account_profile(self):
73 73 c = self.load_default_context()
74 74 c.active = 'profile'
75 75 c.extern_type = c.user.extern_type
76 76 return self._get_template_context(c)
77 77
78 78 @LoginRequired()
79 79 @NotAnonymous()
80 80 def my_account_edit(self):
81 81 c = self.load_default_context()
82 82 c.active = 'profile_edit'
83 83 c.extern_type = c.user.extern_type
84 84 c.extern_name = c.user.extern_name
85 85
86 86 schema = user_schema.UserProfileSchema().bind(
87 87 username=c.user.username, user_emails=c.user.emails)
88 88 appstruct = {
89 89 'username': c.user.username,
90 90 'email': c.user.email,
91 91 'firstname': c.user.firstname,
92 92 'lastname': c.user.lastname,
93 93 'description': c.user.description,
94 94 }
95 95 c.form = forms.RcForm(
96 96 schema, appstruct=appstruct,
97 97 action=h.route_path('my_account_update'),
98 98 buttons=(forms.buttons.save, forms.buttons.reset))
99 99
100 100 return self._get_template_context(c)
101 101
102 102 @LoginRequired()
103 103 @NotAnonymous()
104 104 @CSRFRequired()
105 105 def my_account_update(self):
106 106 _ = self.request.translate
107 107 c = self.load_default_context()
108 108 c.active = 'profile_edit'
109 109 c.perm_user = c.auth_user
110 110 c.extern_type = c.user.extern_type
111 111 c.extern_name = c.user.extern_name
112 112
113 113 schema = user_schema.UserProfileSchema().bind(
114 114 username=c.user.username, user_emails=c.user.emails)
115 115 form = forms.RcForm(
116 116 schema, buttons=(forms.buttons.save, forms.buttons.reset))
117 117
118 118 controls = list(self.request.POST.items())
119 119 try:
120 120 valid_data = form.validate(controls)
121 121 skip_attrs = ['admin', 'active', 'extern_type', 'extern_name',
122 122 'new_password', 'password_confirmation']
123 123 if c.extern_type != "rhodecode":
124 124 # forbid updating username for external accounts
125 125 skip_attrs.append('username')
126 126 old_email = c.user.email
127 127 UserModel().update_user(
128 128 self._rhodecode_user.user_id, skip_attrs=skip_attrs,
129 129 **valid_data)
130 130 if old_email != valid_data['email']:
131 131 old = UserEmailMap.query() \
132 132 .filter(UserEmailMap.user == c.user)\
133 133 .filter(UserEmailMap.email == valid_data['email'])\
134 134 .first()
135 135 old.email = old_email
136 136 h.flash(_('Your account was updated successfully'), category='success')
137 137 Session().commit()
138 138 except forms.ValidationFailure as e:
139 139 c.form = e
140 140 return self._get_template_context(c)
141 141
142 142 except Exception:
143 143 log.exception("Exception updating user")
144 144 h.flash(_('Error occurred during update of user'),
145 145 category='error')
146 146 raise HTTPFound(h.route_path('my_account_profile'))
147 147
148 148 @LoginRequired()
149 149 @NotAnonymous()
150 150 def my_account_password(self):
151 151 c = self.load_default_context()
152 152 c.active = 'password'
153 153 c.extern_type = c.user.extern_type
154 154
155 155 schema = user_schema.ChangePasswordSchema().bind(
156 156 username=c.user.username)
157 157
158 158 form = forms.Form(
159 159 schema,
160 160 action=h.route_path('my_account_password_update'),
161 161 buttons=(forms.buttons.save, forms.buttons.reset))
162 162
163 163 c.form = form
164 164 return self._get_template_context(c)
165 165
166 166 @LoginRequired()
167 167 @NotAnonymous()
168 168 @CSRFRequired()
169 169 def my_account_password_update(self):
170 170 _ = self.request.translate
171 171 c = self.load_default_context()
172 172 c.active = 'password'
173 173 c.extern_type = c.user.extern_type
174 174
175 175 schema = user_schema.ChangePasswordSchema().bind(
176 176 username=c.user.username)
177 177
178 178 form = forms.Form(
179 179 schema, buttons=(forms.buttons.save, forms.buttons.reset))
180 180
181 181 if c.extern_type != 'rhodecode':
182 182 raise HTTPFound(self.request.route_path('my_account_password'))
183 183
184 184 controls = list(self.request.POST.items())
185 185 try:
186 186 valid_data = form.validate(controls)
187 187 UserModel().update_user(c.user.user_id, **valid_data)
188 188 c.user.update_userdata(force_password_change=False)
189 189 Session().commit()
190 190 except forms.ValidationFailure as e:
191 191 c.form = e
192 192 return self._get_template_context(c)
193 193
194 194 except Exception:
195 195 log.exception("Exception updating password")
196 196 h.flash(_('Error occurred during update of user password'),
197 197 category='error')
198 198 else:
199 199 instance = c.auth_user.get_instance()
200 200 self.session.setdefault('rhodecode_user', {}).update(
201 201 {'password': md5_safe(instance.password)})
202 202 self.session.save()
203 203 h.flash(_("Successfully updated password"), category='success')
204 204
205 205 raise HTTPFound(self.request.route_path('my_account_password'))
206 206
207 207 @LoginRequired()
208 208 @NotAnonymous()
209 209 def my_account_2fa(self):
210 210 _ = self.request.translate
211 211 c = self.load_default_context()
212 212 c.active = '2FA'
213 213 user_instance = c.auth_user.get_instance()
214 214 locked_by_admin = user_instance.has_forced_2fa
215 215 c.state_of_2fa = user_instance.has_enabled_2fa
216 216 c.user_seen_2fa_recovery_codes = user_instance.has_seen_2fa_codes
217 217 c.locked_2fa = str2bool(locked_by_admin)
218 218 return self._get_template_context(c)
219 219
220 220 @LoginRequired()
221 221 @NotAnonymous()
222 222 @CSRFRequired()
223 223 def my_account_2fa_update(self):
224 224 _ = self.request.translate
225 225 c = self.load_default_context()
226 226 c.active = '2FA'
227 227 user_instance = c.auth_user.get_instance()
228 228
229 229 state = self.request.POST.get('2fa_status') == '1'
230 230 user_instance.has_enabled_2fa = state
231 231 user_instance.update_userdata(update_2fa=time.time())
232 232 Session().commit()
233 233 h.flash(_("Successfully saved 2FA settings"), category='success')
234 raise HTTPFound(self.request.route_path('my_account_enable_2fa'))
234 raise HTTPFound(self.request.route_path('my_account_configure_2fa'))
235 235
236 236 @LoginRequired()
237 237 @NotAnonymous()
238 238 @CSRFRequired()
239 239 def my_account_2fa_show_recovery_codes(self):
240 240 c = self.load_default_context()
241 241 user_instance = c.auth_user.get_instance()
242 242 user_instance.has_seen_2fa_codes = True
243 243 Session().commit()
244 244 return {'recovery_codes': user_instance.get_2fa_recovery_codes()}
245 245
246 246 @LoginRequired()
247 247 @NotAnonymous()
248 248 @CSRFRequired()
249 249 def my_account_2fa_regenerate_recovery_codes(self):
250 250 _ = self.request.translate
251 251 c = self.load_default_context()
252 252 user_instance = c.auth_user.get_instance()
253 253
254 254 totp_form = TOTPForm(_, user_instance, allow_recovery_code_use=True)()
255 255
256 256 post_items = dict(self.request.POST)
257 257 # NOTE: inject secret, as it's a post configured saved item.
258 258 post_items['secret_totp'] = user_instance.get_secret_2fa()
259 259 try:
260 260 totp_form.to_python(post_items)
261 261 user_instance.regenerate_2fa_recovery_codes()
262 262 Session().commit()
263 263 except formencode.Invalid as errors:
264 264 h.flash(_("Failed to generate new recovery codes: {}").format(errors), category='error')
265 raise HTTPFound(self.request.route_path('my_account_enable_2fa'))
265 raise HTTPFound(self.request.route_path('my_account_configure_2fa'))
266 266 except Exception as e:
267 267 h.flash(_("Failed to generate new recovery codes: {}").format(e), category='error')
268 raise HTTPFound(self.request.route_path('my_account_enable_2fa'))
268 raise HTTPFound(self.request.route_path('my_account_configure_2fa'))
269 269
270 raise HTTPFound(self.request.route_path('my_account_enable_2fa', _query={'show-recovery-codes': 1}))
270 raise HTTPFound(self.request.route_path('my_account_configure_2fa', _query={'show-recovery-codes': 1}))
271 271
272 272 @LoginRequired()
273 273 @NotAnonymous()
274 274 def my_account_auth_tokens(self):
275 275 _ = self.request.translate
276 276
277 277 c = self.load_default_context()
278 278 c.active = 'auth_tokens'
279 279 c.lifetime_values = AuthTokenModel.get_lifetime_values(translator=_)
280 280 c.role_values = [
281 281 (x, AuthTokenModel.cls._get_role_name(x))
282 282 for x in AuthTokenModel.cls.ROLES]
283 283 c.role_options = [(c.role_values, _("Role"))]
284 284 c.user_auth_tokens = AuthTokenModel().get_auth_tokens(
285 285 c.user.user_id, show_expired=True)
286 286 c.role_vcs = AuthTokenModel.cls.ROLE_VCS
287 287 return self._get_template_context(c)
288 288
289 289 @LoginRequired()
290 290 @NotAnonymous()
291 291 @CSRFRequired()
292 292 def my_account_auth_tokens_view(self):
293 293 _ = self.request.translate
294 294 c = self.load_default_context()
295 295
296 296 auth_token_id = self.request.POST.get('auth_token_id')
297 297
298 298 if auth_token_id:
299 299 token = UserApiKeys.get_or_404(auth_token_id)
300 300 if token.user.user_id != c.user.user_id:
301 301 raise HTTPNotFound()
302 302
303 303 return {
304 304 'auth_token': token.api_key
305 305 }
306 306
307 307 def maybe_attach_token_scope(self, token):
308 308 # implemented in EE edition
309 309 pass
310 310
311 311 @LoginRequired()
312 312 @NotAnonymous()
313 313 @CSRFRequired()
314 314 def my_account_auth_tokens_add(self):
315 315 _ = self.request.translate
316 316 c = self.load_default_context()
317 317
318 318 lifetime = safe_int(self.request.POST.get('lifetime'), -1)
319 319 description = self.request.POST.get('description')
320 320 role = self.request.POST.get('role')
321 321
322 322 token = UserModel().add_auth_token(
323 323 user=c.user.user_id,
324 324 lifetime_minutes=lifetime, role=role, description=description,
325 325 scope_callback=self.maybe_attach_token_scope)
326 326 token_data = token.get_api_data()
327 327
328 328 audit_logger.store_web(
329 329 'user.edit.token.add', action_data={
330 330 'data': {'token': token_data, 'user': 'self'}},
331 331 user=self._rhodecode_user, )
332 332 Session().commit()
333 333
334 334 h.flash(_("Auth token successfully created"), category='success')
335 335 return HTTPFound(h.route_path('my_account_auth_tokens'))
336 336
337 337 @LoginRequired()
338 338 @NotAnonymous()
339 339 @CSRFRequired()
340 340 def my_account_auth_tokens_delete(self):
341 341 _ = self.request.translate
342 342 c = self.load_default_context()
343 343
344 344 del_auth_token = self.request.POST.get('del_auth_token')
345 345
346 346 if del_auth_token:
347 347 token = UserApiKeys.get_or_404(del_auth_token)
348 348 token_data = token.get_api_data()
349 349
350 350 AuthTokenModel().delete(del_auth_token, c.user.user_id)
351 351 audit_logger.store_web(
352 352 'user.edit.token.delete', action_data={
353 353 'data': {'token': token_data, 'user': 'self'}},
354 354 user=self._rhodecode_user,)
355 355 Session().commit()
356 356 h.flash(_("Auth token successfully deleted"), category='success')
357 357
358 358 return HTTPFound(h.route_path('my_account_auth_tokens'))
359 359
360 360 @LoginRequired()
361 361 @NotAnonymous()
362 362 def my_account_emails(self):
363 363 _ = self.request.translate
364 364
365 365 c = self.load_default_context()
366 366 c.active = 'emails'
367 367
368 368 c.user_email_map = UserEmailMap.query()\
369 369 .filter(UserEmailMap.user == c.user).all()
370 370
371 371 schema = user_schema.AddEmailSchema().bind(
372 372 username=c.user.username, user_emails=c.user.emails)
373 373
374 374 form = forms.RcForm(schema,
375 375 action=h.route_path('my_account_emails_add'),
376 376 buttons=(forms.buttons.save, forms.buttons.reset))
377 377
378 378 c.form = form
379 379 return self._get_template_context(c)
380 380
381 381 @LoginRequired()
382 382 @NotAnonymous()
383 383 @CSRFRequired()
384 384 def my_account_emails_add(self):
385 385 _ = self.request.translate
386 386 c = self.load_default_context()
387 387 c.active = 'emails'
388 388
389 389 schema = user_schema.AddEmailSchema().bind(
390 390 username=c.user.username, user_emails=c.user.emails)
391 391
392 392 form = forms.RcForm(
393 393 schema, action=h.route_path('my_account_emails_add'),
394 394 buttons=(forms.buttons.save, forms.buttons.reset))
395 395
396 396 controls = list(self.request.POST.items())
397 397 try:
398 398 valid_data = form.validate(controls)
399 399 UserModel().add_extra_email(c.user.user_id, valid_data['email'])
400 400 audit_logger.store_web(
401 401 'user.edit.email.add', action_data={
402 402 'data': {'email': valid_data['email'], 'user': 'self'}},
403 403 user=self._rhodecode_user,)
404 404 Session().commit()
405 405 except formencode.Invalid as error:
406 406 h.flash(h.escape(error.error_dict['email']), category='error')
407 407 except forms.ValidationFailure as e:
408 408 c.user_email_map = UserEmailMap.query() \
409 409 .filter(UserEmailMap.user == c.user).all()
410 410 c.form = e
411 411 return self._get_template_context(c)
412 412 except Exception:
413 413 log.exception("Exception adding email")
414 414 h.flash(_('Error occurred during adding email'),
415 415 category='error')
416 416 else:
417 417 h.flash(_("Successfully added email"), category='success')
418 418
419 419 raise HTTPFound(self.request.route_path('my_account_emails'))
420 420
421 421 @LoginRequired()
422 422 @NotAnonymous()
423 423 @CSRFRequired()
424 424 def my_account_emails_delete(self):
425 425 _ = self.request.translate
426 426 c = self.load_default_context()
427 427
428 428 del_email_id = self.request.POST.get('del_email_id')
429 429 if del_email_id:
430 430 email = UserEmailMap.get_or_404(del_email_id).email
431 431 UserModel().delete_extra_email(c.user.user_id, del_email_id)
432 432 audit_logger.store_web(
433 433 'user.edit.email.delete', action_data={
434 434 'data': {'email': email, 'user': 'self'}},
435 435 user=self._rhodecode_user,)
436 436 Session().commit()
437 437 h.flash(_("Email successfully deleted"),
438 438 category='success')
439 439 return HTTPFound(h.route_path('my_account_emails'))
440 440
441 441 @LoginRequired()
442 442 @NotAnonymous()
443 443 @CSRFRequired()
444 444 def my_account_notifications_test_channelstream(self):
445 445 message = 'Test message sent via Channelstream by user: {}, on {}'.format(
446 446 self._rhodecode_user.username, datetime.datetime.now())
447 447 payload = {
448 448 # 'channel': 'broadcast',
449 449 'type': 'message',
450 450 'timestamp': datetime.datetime.utcnow(),
451 451 'user': 'system',
452 452 'pm_users': [self._rhodecode_user.username],
453 453 'message': {
454 454 'message': message,
455 455 'level': 'info',
456 456 'topic': '/notifications'
457 457 }
458 458 }
459 459
460 460 registry = self.request.registry
461 461 rhodecode_plugins = getattr(registry, 'rhodecode_plugins', {})
462 462 channelstream_config = rhodecode_plugins.get('channelstream', {})
463 463
464 464 try:
465 465 channelstream_request(channelstream_config, [payload], '/message')
466 466 except ChannelstreamException as e:
467 467 log.exception('Failed to send channelstream data')
468 468 return {"response": f'ERROR: {e.__class__.__name__}'}
469 469 return {"response": 'Channelstream data sent. '
470 470 'You should see a new live message now.'}
471 471
472 472 def _load_my_repos_data(self, watched=False):
473 473
474 474 allowed_ids = [-1] + self._rhodecode_user.repo_acl_ids_from_stack(AuthUser.repo_read_perms)
475 475
476 476 if watched:
477 477 # repos user watch
478 478 repo_list = Session().query(
479 479 Repository
480 480 ) \
481 481 .join(
482 482 (UserFollowing, UserFollowing.follows_repo_id == Repository.repo_id)
483 483 ) \
484 484 .filter(
485 485 UserFollowing.user_id == self._rhodecode_user.user_id
486 486 ) \
487 487 .filter(or_(
488 488 # generate multiple IN to fix limitation problems
489 489 *in_filter_generator(Repository.repo_id, allowed_ids))
490 490 ) \
491 491 .order_by(Repository.repo_name) \
492 492 .all()
493 493
494 494 else:
495 495 # repos user is owner of
496 496 repo_list = Session().query(
497 497 Repository
498 498 ) \
499 499 .filter(
500 500 Repository.user_id == self._rhodecode_user.user_id
501 501 ) \
502 502 .filter(or_(
503 503 # generate multiple IN to fix limitation problems
504 504 *in_filter_generator(Repository.repo_id, allowed_ids))
505 505 ) \
506 506 .order_by(Repository.repo_name) \
507 507 .all()
508 508
509 509 _render = self.request.get_partial_renderer(
510 510 'rhodecode:templates/data_table/_dt_elements.mako')
511 511
512 512 def repo_lnk(name, rtype, rstate, private, archived, fork_of):
513 513 return _render('repo_name', name, rtype, rstate, private, archived, fork_of,
514 514 short_name=False, admin=False)
515 515
516 516 repos_data = []
517 517 for repo in repo_list:
518 518 row = {
519 519 "name": repo_lnk(repo.repo_name, repo.repo_type, repo.repo_state,
520 520 repo.private, repo.archived, repo.fork),
521 521 "name_raw": repo.repo_name.lower(),
522 522 }
523 523
524 524 repos_data.append(row)
525 525
526 526 # json used to render the grid
527 527 return ext_json.str_json(repos_data)
528 528
529 529 @LoginRequired()
530 530 @NotAnonymous()
531 531 def my_account_repos(self):
532 532 c = self.load_default_context()
533 533 c.active = 'repos'
534 534
535 535 # json used to render the grid
536 536 c.data = self._load_my_repos_data()
537 537 return self._get_template_context(c)
538 538
539 539 @LoginRequired()
540 540 @NotAnonymous()
541 541 def my_account_watched(self):
542 542 c = self.load_default_context()
543 543 c.active = 'watched'
544 544
545 545 # json used to render the grid
546 546 c.data = self._load_my_repos_data(watched=True)
547 547 return self._get_template_context(c)
548 548
549 549 @LoginRequired()
550 550 @NotAnonymous()
551 551 def my_account_bookmarks(self):
552 552 c = self.load_default_context()
553 553 c.active = 'bookmarks'
554 554
555 555 user_bookmarks = \
556 556 select(UserBookmark, Repository, RepoGroup) \
557 557 .where(UserBookmark.user_id == self._rhodecode_user.user_id) \
558 558 .outerjoin(Repository, Repository.repo_id == UserBookmark.bookmark_repo_id) \
559 559 .outerjoin(RepoGroup, RepoGroup.group_id == UserBookmark.bookmark_repo_group_id) \
560 560 .order_by(UserBookmark.position.asc())
561 561
562 562 c.user_bookmark_items = Session().execute(user_bookmarks).all()
563 563 return self._get_template_context(c)
564 564
565 565 def _process_bookmark_entry(self, entry, user_id):
566 566 position = safe_int(entry.get('position'))
567 567 cur_position = safe_int(entry.get('cur_position'))
568 568 if position is None:
569 569 return
570 570
571 571 # check if this is an existing entry
572 572 is_new = False
573 573 db_entry = UserBookmark().get_by_position_for_user(cur_position, user_id)
574 574
575 575 if db_entry and str2bool(entry.get('remove')):
576 576 log.debug('Marked bookmark %s for deletion', db_entry)
577 577 Session().delete(db_entry)
578 578 return
579 579
580 580 if not db_entry:
581 581 # new
582 582 db_entry = UserBookmark()
583 583 is_new = True
584 584
585 585 should_save = False
586 586 default_redirect_url = ''
587 587
588 588 # save repo
589 589 if entry.get('bookmark_repo') and safe_int(entry.get('bookmark_repo')):
590 590 repo = Repository.get(entry['bookmark_repo'])
591 591 perm_check = HasRepoPermissionAny(
592 592 'repository.read', 'repository.write', 'repository.admin')
593 593 if repo and perm_check(repo_name=repo.repo_name):
594 594 db_entry.repository = repo
595 595 should_save = True
596 596 default_redirect_url = '${repo_url}'
597 597 # save repo group
598 598 elif entry.get('bookmark_repo_group') and safe_int(entry.get('bookmark_repo_group')):
599 599 repo_group = RepoGroup.get(entry['bookmark_repo_group'])
600 600 perm_check = HasRepoGroupPermissionAny(
601 601 'group.read', 'group.write', 'group.admin')
602 602
603 603 if repo_group and perm_check(group_name=repo_group.group_name):
604 604 db_entry.repository_group = repo_group
605 605 should_save = True
606 606 default_redirect_url = '${repo_group_url}'
607 607 # save generic info
608 608 elif entry.get('title') and entry.get('redirect_url'):
609 609 should_save = True
610 610
611 611 if should_save:
612 612 # mark user and position
613 613 db_entry.user_id = user_id
614 614 db_entry.position = position
615 615 db_entry.title = entry.get('title')
616 616 db_entry.redirect_url = entry.get('redirect_url') or default_redirect_url
617 617 log.debug('Saving bookmark %s, new:%s', db_entry, is_new)
618 618
619 619 Session().add(db_entry)
620 620
621 621 @LoginRequired()
622 622 @NotAnonymous()
623 623 @CSRFRequired()
624 624 def my_account_bookmarks_update(self):
625 625 _ = self.request.translate
626 626 c = self.load_default_context()
627 627 c.active = 'bookmarks'
628 628
629 629 controls = peppercorn.parse(self.request.POST.items())
630 630 user_id = c.user.user_id
631 631
632 632 # validate positions
633 633 positions = {}
634 634 for entry in controls.get('bookmarks', []):
635 635 position = safe_int(entry['position'])
636 636 if position is None:
637 637 continue
638 638
639 639 if position in positions:
640 640 h.flash(_("Position {} is defined twice. "
641 641 "Please correct this error.").format(position), category='error')
642 642 return HTTPFound(h.route_path('my_account_bookmarks'))
643 643
644 644 entry['position'] = position
645 645 entry['cur_position'] = safe_int(entry.get('cur_position'))
646 646 positions[position] = entry
647 647
648 648 try:
649 649 for entry in positions.values():
650 650 self._process_bookmark_entry(entry, user_id)
651 651
652 652 Session().commit()
653 653 h.flash(_("Update Bookmarks"), category='success')
654 654 except IntegrityError:
655 655 h.flash(_("Failed to update bookmarks. "
656 656 "Make sure an unique position is used."), category='error')
657 657
658 658 return HTTPFound(h.route_path('my_account_bookmarks'))
659 659
660 660 @LoginRequired()
661 661 @NotAnonymous()
662 662 def my_account_goto_bookmark(self):
663 663
664 664 bookmark_id = self.request.matchdict['bookmark_id']
665 665 user_bookmark = UserBookmark().query()\
666 666 .filter(UserBookmark.user_id == self.request.user.user_id) \
667 667 .filter(UserBookmark.position == bookmark_id).scalar()
668 668
669 669 redirect_url = h.route_path('my_account_bookmarks')
670 670 if not user_bookmark:
671 671 raise HTTPFound(redirect_url)
672 672
673 673 # repository set
674 674 if user_bookmark.repository:
675 675 repo_name = user_bookmark.repository.repo_name
676 676 base_redirect_url = h.route_path(
677 677 'repo_summary', repo_name=repo_name)
678 678 if user_bookmark.redirect_url and \
679 679 '${repo_url}' in user_bookmark.redirect_url:
680 680 redirect_url = string.Template(user_bookmark.redirect_url)\
681 681 .safe_substitute({'repo_url': base_redirect_url})
682 682 else:
683 683 redirect_url = base_redirect_url
684 684 # repository group set
685 685 elif user_bookmark.repository_group:
686 686 repo_group_name = user_bookmark.repository_group.group_name
687 687 base_redirect_url = h.route_path(
688 688 'repo_group_home', repo_group_name=repo_group_name)
689 689 if user_bookmark.redirect_url and \
690 690 '${repo_group_url}' in user_bookmark.redirect_url:
691 691 redirect_url = string.Template(user_bookmark.redirect_url)\
692 692 .safe_substitute({'repo_group_url': base_redirect_url})
693 693 else:
694 694 redirect_url = base_redirect_url
695 695 # custom URL set
696 696 elif user_bookmark.redirect_url:
697 697 server_url = h.route_url('home').rstrip('/')
698 698 redirect_url = string.Template(user_bookmark.redirect_url) \
699 699 .safe_substitute({'server_url': server_url})
700 700
701 701 log.debug('Redirecting bookmark %s to %s', user_bookmark, redirect_url)
702 702 raise HTTPFound(redirect_url)
703 703
704 704 @LoginRequired()
705 705 @NotAnonymous()
706 706 def my_account_perms(self):
707 707 c = self.load_default_context()
708 708 c.active = 'perms'
709 709
710 710 c.perm_user = c.auth_user
711 711 return self._get_template_context(c)
712 712
713 713 @LoginRequired()
714 714 @NotAnonymous()
715 715 def my_notifications(self):
716 716 c = self.load_default_context()
717 717 c.active = 'notifications'
718 718
719 719 return self._get_template_context(c)
720 720
721 721 @LoginRequired()
722 722 @NotAnonymous()
723 723 @CSRFRequired()
724 724 def my_notifications_toggle_visibility(self):
725 725 user = self._rhodecode_db_user
726 726 new_status = not user.user_data.get('notification_status', True)
727 727 user.update_userdata(notification_status=new_status)
728 728 Session().commit()
729 729 return user.user_data['notification_status']
730 730
731 731 def _get_pull_requests_list(self, statuses, filter_type=None):
732 732 draw, start, limit = self._extract_chunk(self.request)
733 733 search_q, order_by, order_dir = self._extract_ordering(self.request)
734 734
735 735 _render = self.request.get_partial_renderer(
736 736 'rhodecode:templates/data_table/_dt_elements.mako')
737 737
738 738 if filter_type == 'awaiting_my_review':
739 739 pull_requests = PullRequestModel().get_im_participating_in_for_review(
740 740 user_id=self._rhodecode_user.user_id,
741 741 statuses=statuses, query=search_q,
742 742 offset=start, length=limit, order_by=order_by,
743 743 order_dir=order_dir)
744 744
745 745 pull_requests_total_count = PullRequestModel().count_im_participating_in_for_review(
746 746 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
747 747 else:
748 748 pull_requests = PullRequestModel().get_im_participating_in(
749 749 user_id=self._rhodecode_user.user_id,
750 750 statuses=statuses, query=search_q,
751 751 offset=start, length=limit, order_by=order_by,
752 752 order_dir=order_dir)
753 753
754 754 pull_requests_total_count = PullRequestModel().count_im_participating_in(
755 755 user_id=self._rhodecode_user.user_id, statuses=statuses, query=search_q)
756 756
757 757 data = []
758 758 comments_model = CommentsModel()
759 759 for pr in pull_requests:
760 760 repo_id = pr.target_repo_id
761 761 comments_count = comments_model.get_all_comments(
762 762 repo_id, pull_request=pr, include_drafts=False, count_only=True)
763 763 owned = pr.user_id == self._rhodecode_user.user_id
764 764
765 765 review_statuses = pr.reviewers_statuses(user=self._rhodecode_db_user)
766 766 my_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
767 767 if review_statuses and review_statuses[4]:
768 768 _review_obj, _user, _reasons, _mandatory, statuses = review_statuses
769 769 my_review_status = statuses[0][1].status
770 770
771 771 data.append({
772 772 'target_repo': _render('pullrequest_target_repo',
773 773 pr.target_repo.repo_name),
774 774 'name': _render('pullrequest_name',
775 775 pr.pull_request_id, pr.pull_request_state,
776 776 pr.work_in_progress, pr.target_repo.repo_name,
777 777 short=True),
778 778 'name_raw': pr.pull_request_id,
779 779 'status': _render('pullrequest_status',
780 780 pr.calculated_review_status()),
781 781 'my_status': _render('pullrequest_status',
782 782 my_review_status),
783 783 'title': _render('pullrequest_title', pr.title, pr.description),
784 784 'pr_flow': _render('pullrequest_commit_flow', pr),
785 785 'description': h.escape(pr.description),
786 786 'updated_on': _render('pullrequest_updated_on',
787 787 h.datetime_to_time(pr.updated_on),
788 788 pr.versions_count),
789 789 'updated_on_raw': h.datetime_to_time(pr.updated_on),
790 790 'created_on': _render('pullrequest_updated_on',
791 791 h.datetime_to_time(pr.created_on)),
792 792 'created_on_raw': h.datetime_to_time(pr.created_on),
793 793 'state': pr.pull_request_state,
794 794 'author': _render('pullrequest_author',
795 795 pr.author.full_contact, ),
796 796 'author_raw': pr.author.full_name,
797 797 'comments': _render('pullrequest_comments', comments_count),
798 798 'comments_raw': comments_count,
799 799 'closed': pr.is_closed(),
800 800 'owned': owned
801 801 })
802 802
803 803 # json used to render the grid
804 804 data = ({
805 805 'draw': draw,
806 806 'data': data,
807 807 'recordsTotal': pull_requests_total_count,
808 808 'recordsFiltered': pull_requests_total_count,
809 809 })
810 810 return data
811 811
812 812 @LoginRequired()
813 813 @NotAnonymous()
814 814 def my_account_pullrequests(self):
815 815 c = self.load_default_context()
816 816 c.active = 'pullrequests'
817 817 req_get = self.request.GET
818 818
819 819 c.closed = str2bool(req_get.get('closed'))
820 820 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
821 821
822 822 c.selected_filter = 'all'
823 823 if c.closed:
824 824 c.selected_filter = 'all_closed'
825 825 if c.awaiting_my_review:
826 826 c.selected_filter = 'awaiting_my_review'
827 827
828 828 return self._get_template_context(c)
829 829
830 830 @LoginRequired()
831 831 @NotAnonymous()
832 832 def my_account_pullrequests_data(self):
833 833 self.load_default_context()
834 834 req_get = self.request.GET
835 835
836 836 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
837 837 closed = str2bool(req_get.get('closed'))
838 838
839 839 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
840 840 if closed:
841 841 statuses += [PullRequest.STATUS_CLOSED]
842 842
843 843 filter_type = \
844 844 'awaiting_my_review' if awaiting_my_review \
845 845 else None
846 846
847 847 data = self._get_pull_requests_list(statuses=statuses, filter_type=filter_type)
848 848 return data
849 849
850 850 @LoginRequired()
851 851 @NotAnonymous()
852 852 def my_account_user_group_membership(self):
853 853 c = self.load_default_context()
854 854 c.active = 'user_group_membership'
855 855 groups = [UserGroupModel.get_user_groups_as_dict(group.users_group)
856 856 for group in self._rhodecode_db_user.group_member]
857 857 c.user_groups = ext_json.str_json(groups)
858 858 return self._get_template_context(c)
@@ -1,419 +1,419 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('admin_artifacts', '/_admin/artifacts', []);
16 16 pyroutes.register('admin_artifacts_data', '/_admin/artifacts-data', []);
17 17 pyroutes.register('admin_artifacts_delete', '/_admin/artifacts/%(uid)s/delete', ['uid']);
18 18 pyroutes.register('admin_artifacts_show_all', '/_admin/artifacts', []);
19 19 pyroutes.register('admin_artifacts_show_info', '/_admin/artifacts/%(uid)s', ['uid']);
20 20 pyroutes.register('admin_artifacts_update', '/_admin/artifacts/%(uid)s/update', ['uid']);
21 21 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
22 22 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
23 23 pyroutes.register('admin_automation', '/_admin/automation', []);
24 24 pyroutes.register('admin_automation_update', '/_admin/automation/%(entry_id)s/update', ['entry_id']);
25 25 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
26 26 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
27 27 pyroutes.register('admin_home', '/_admin', []);
28 28 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
29 29 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
30 30 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
31 31 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
32 32 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
33 33 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
34 34 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
35 35 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
36 36 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
37 37 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
38 38 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
39 39 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
40 40 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
41 41 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
42 42 pyroutes.register('admin_scheduler', '/_admin/scheduler', []);
43 43 pyroutes.register('admin_scheduler_show_tasks', '/_admin/scheduler/_tasks', []);
44 44 pyroutes.register('admin_settings', '/_admin/settings', []);
45 45 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
46 46 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
47 47 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
48 48 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
49 49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions_delete_all', []);
50 50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 51 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
52 52 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
53 53 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
54 54 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
55 55 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
56 56 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
57 57 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
58 58 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
59 59 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
60 60 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
61 61 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
62 62 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
63 63 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
64 64 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
65 65 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
66 66 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
67 67 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
68 68 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
69 69 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
70 70 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
71 71 pyroutes.register('admin_settings_scheduler_create', '/_admin/scheduler/create', []);
72 72 pyroutes.register('admin_settings_scheduler_delete', '/_admin/scheduler/%(schedule_id)s/delete', ['schedule_id']);
73 73 pyroutes.register('admin_settings_scheduler_edit', '/_admin/scheduler/%(schedule_id)s', ['schedule_id']);
74 74 pyroutes.register('admin_settings_scheduler_execute', '/_admin/scheduler/%(schedule_id)s/execute', ['schedule_id']);
75 75 pyroutes.register('admin_settings_scheduler_new', '/_admin/scheduler/new', []);
76 76 pyroutes.register('admin_settings_scheduler_update', '/_admin/scheduler/%(schedule_id)s/update', ['schedule_id']);
77 77 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
78 78 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
79 79 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
80 80 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
81 81 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
82 82 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
83 83 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
84 84 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
85 85 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
86 86 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
87 87 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
88 88 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
89 89 pyroutes.register('apiv2', '/_admin/api', []);
90 90 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed-atom', ['repo_name']);
91 91 pyroutes.register('atom_feed_home_old', '/%(repo_name)s/feed/atom', ['repo_name']);
92 92 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
93 93 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
94 94 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
95 95 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
96 96 pyroutes.register('channelstream_proxy', '/_channelstream', []);
97 97 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
98 98 pyroutes.register('check_2fa', '/_admin/check_2fa', []);
99 99 pyroutes.register('commit_draft_comments_submit', '/%(repo_name)s/changeset/%(commit_id)s/draft_comments_submit', ['repo_name', 'commit_id']);
100 100 pyroutes.register('debug_style_email', '/_admin/debug_style/email/%(email_id)s', ['email_id']);
101 101 pyroutes.register('debug_style_email_plain_rendered', '/_admin/debug_style/email-rendered/%(email_id)s', ['email_id']);
102 102 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
103 103 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
104 104 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
105 105 pyroutes.register('download_file_by_token', '/_file_store/token-download/%(_auth_token)s/%(fid)s', ['_auth_token', 'fid']);
106 106 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
107 107 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
108 108 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
109 109 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
110 110 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
111 111 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
112 112 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
113 113 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
114 114 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
115 115 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
116 116 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
117 117 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
118 118 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
119 119 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
120 120 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
121 121 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
122 122 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
123 123 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
124 124 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
125 125 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
126 126 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
127 127 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
128 128 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
129 129 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
130 130 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
131 131 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
132 132 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
133 133 pyroutes.register('edit_repo_perms_set_private', '/%(repo_name)s/settings/permissions/set_private', ['repo_name']);
134 134 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
135 135 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
136 136 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
137 137 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
138 138 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
139 139 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
140 140 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
141 141 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
142 142 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
143 143 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
144 144 pyroutes.register('edit_user_audit_logs_download', '/_admin/users/%(user_id)s/edit/audit/download', ['user_id']);
145 145 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
146 146 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
147 147 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
148 148 pyroutes.register('edit_user_auth_tokens_view', '/_admin/users/%(user_id)s/edit/auth_tokens/view', ['user_id']);
149 149 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
150 150 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
151 151 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
152 152 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
153 153 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
154 154 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
155 155 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
156 156 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
157 157 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
158 158 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
159 159 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
160 160 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
161 161 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
162 162 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
163 163 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
164 164 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
165 165 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
166 166 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
167 167 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
168 168 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
169 169 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
170 170 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
171 171 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
172 172 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
173 173 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
174 174 pyroutes.register('favicon', '/favicon.ico', []);
175 175 pyroutes.register('file_preview', '/_file_preview', []);
176 176 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
177 177 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
178 178 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
179 179 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
180 180 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
181 181 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/rev/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
182 182 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/rev/%(revision)s', ['gist_id', 'revision']);
183 183 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
184 184 pyroutes.register('gists_create', '/_admin/gists/create', []);
185 185 pyroutes.register('gists_new', '/_admin/gists/new', []);
186 186 pyroutes.register('gists_show', '/_admin/gists', []);
187 187 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
188 188 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
189 189 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
190 190 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
191 191 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
192 192 pyroutes.register('goto_switcher_data', '/_goto_data', []);
193 193 pyroutes.register('home', '/', []);
194 194 pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']);
195 195 pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']);
196 196 pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']);
197 197 pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']);
198 198 pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']);
199 199 pyroutes.register('journal', '/_admin/journal', []);
200 200 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
201 201 pyroutes.register('journal_public', '/_admin/public_journal', []);
202 202 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
203 203 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
204 204 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
205 205 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
206 206 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
207 207 pyroutes.register('login', '/_admin/login', []);
208 208 pyroutes.register('logout', '/_admin/logout', []);
209 209 pyroutes.register('main_page_repo_groups_data', '/_home_repo_groups', []);
210 210 pyroutes.register('main_page_repos_data', '/_home_repos', []);
211 211 pyroutes.register('markup_preview', '/_markup_preview', []);
212 212 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
213 213 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
214 214 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
215 215 pyroutes.register('my_account_auth_tokens_view', '/_admin/my_account/auth_tokens/view', []);
216 216 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
217 217 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
218 pyroutes.register('my_account_configure_2fa', '/_admin/my_account/configure_2fa', []);
219 pyroutes.register('my_account_configure_2fa_update', '/_admin/my_account/configure_2fa_update', []);
218 220 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
219 221 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
220 222 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
221 223 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
222 pyroutes.register('my_account_enable_2fa', '/_admin/my_account/enable_2fa', []);
223 pyroutes.register('my_account_enable_2fa_save', '/_admin/my_account/enable_2fa_save', []);
224 224 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
225 225 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
226 226 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
227 227 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
228 228 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
229 229 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
230 230 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
231 231 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
232 232 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
233 233 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
234 234 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
235 235 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
236 236 pyroutes.register('my_account_regenerate_2fa_recovery_codes', '/_admin/my_account/regenerate_recovery_codes', []);
237 237 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
238 238 pyroutes.register('my_account_show_2fa_recovery_codes', '/_admin/my_account/recovery_codes', []);
239 239 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
240 240 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
241 241 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
242 242 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
243 243 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
244 244 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
245 245 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
246 246 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
247 247 pyroutes.register('notifications_mark_all_read', '/_admin/notifications_mark_all_read', []);
248 248 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
249 249 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
250 250 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
251 251 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
252 252 pyroutes.register('ops_healthcheck', '/_admin/ops/status', []);
253 253 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
254 254 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
255 255 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
256 256 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
257 257 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
258 258 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
259 259 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
260 260 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
261 261 pyroutes.register('pullrequest_comment_edit', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/edit', ['repo_name', 'pull_request_id', 'comment_id']);
262 262 pyroutes.register('pullrequest_comments', '/%(repo_name)s/pull-request/%(pull_request_id)s/comments', ['repo_name', 'pull_request_id']);
263 263 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
264 264 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
265 265 pyroutes.register('pullrequest_draft_comments_submit', '/%(repo_name)s/pull-request/%(pull_request_id)s/draft_comments_submit', ['repo_name', 'pull_request_id']);
266 266 pyroutes.register('pullrequest_drafts', '/%(repo_name)s/pull-request/%(pull_request_id)s/drafts', ['repo_name', 'pull_request_id']);
267 267 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
268 268 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
269 269 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
270 270 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
271 271 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
272 272 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
273 273 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
274 274 pyroutes.register('pullrequest_todos', '/%(repo_name)s/pull-request/%(pull_request_id)s/todos', ['repo_name', 'pull_request_id']);
275 275 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
276 276 pyroutes.register('register', '/_admin/register', []);
277 277 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
278 278 pyroutes.register('repo_artifacts_data', '/%(repo_name)s/artifacts_data', ['repo_name']);
279 279 pyroutes.register('repo_artifacts_delete', '/%(repo_name)s/artifacts/delete/%(uid)s', ['repo_name', 'uid']);
280 280 pyroutes.register('repo_artifacts_get', '/%(repo_name)s/artifacts/download/%(uid)s', ['repo_name', 'uid']);
281 281 pyroutes.register('repo_artifacts_info', '/%(repo_name)s/artifacts/info/%(uid)s', ['repo_name', 'uid']);
282 282 pyroutes.register('repo_artifacts_list', '/%(repo_name)s/artifacts', ['repo_name']);
283 283 pyroutes.register('repo_artifacts_new', '/%(repo_name)s/artifacts/new', ['repo_name']);
284 284 pyroutes.register('repo_artifacts_store', '/%(repo_name)s/artifacts/store', ['repo_name']);
285 285 pyroutes.register('repo_artifacts_stream_script', '/_file_store/stream-upload-script', []);
286 286 pyroutes.register('repo_artifacts_stream_store', '/_file_store/stream-upload', []);
287 287 pyroutes.register('repo_artifacts_update', '/%(repo_name)s/artifacts/update/%(uid)s', ['repo_name', 'uid']);
288 288 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
289 289 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
290 290 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
291 291 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
292 292 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
293 293 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
294 294 pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']);
295 295 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
296 296 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
297 297 pyroutes.register('repo_commit_comment_edit', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/edit', ['repo_name', 'commit_id', 'comment_id']);
298 298 pyroutes.register('repo_commit_comment_history_view', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/history_view/%(comment_history_id)s', ['repo_name', 'commit_id', 'comment_id', 'comment_history_id']);
299 299 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
300 300 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
301 301 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
302 302 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
303 303 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
304 304 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
305 305 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
306 306 pyroutes.register('repo_commits', '/%(repo_name)s/commits', ['repo_name']);
307 307 pyroutes.register('repo_commits_elements', '/%(repo_name)s/commits_elements', ['repo_name']);
308 308 pyroutes.register('repo_commits_elements_file', '/%(repo_name)s/commits_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
309 309 pyroutes.register('repo_commits_file', '/%(repo_name)s/commits/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
310 310 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
311 311 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
312 312 pyroutes.register('repo_create', '/_admin/repos/create', []);
313 313 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
314 314 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
315 315 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
316 316 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
317 317 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
318 318 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
319 319 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
320 320 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
321 321 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
322 322 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
323 323 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
324 324 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
325 325 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
326 326 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
327 327 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
328 328 pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
329 329 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
330 330 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
331 331 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
332 332 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
333 333 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
334 334 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
335 335 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
336 336 pyroutes.register('repo_files_replace_binary', '/%(repo_name)s/replace_binary/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
337 337 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
338 338 pyroutes.register('repo_files_upload_file', '/%(repo_name)s/upload_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
339 339 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
340 340 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
341 341 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
342 342 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
343 343 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
344 344 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
345 345 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
346 346 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
347 347 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
348 348 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
349 349 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
350 350 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
351 351 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
352 352 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
353 353 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
354 354 pyroutes.register('repo_groups_data', '/_admin/repo_groups_data', []);
355 355 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
356 356 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
357 357 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
358 358 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
359 359 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
360 360 pyroutes.register('repo_list_data', '/_repos', []);
361 361 pyroutes.register('repo_new', '/_admin/repos/new', []);
362 362 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
363 363 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
364 364 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
365 365 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
366 366 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
367 367 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
368 368 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
369 369 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
370 370 pyroutes.register('repo_settings_quick_actions', '/%(repo_name)s/settings/quick-action', ['repo_name']);
371 371 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
372 372 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
373 373 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
374 374 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
375 375 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
376 376 pyroutes.register('repos', '/_admin/repos', []);
377 377 pyroutes.register('repos_data', '/_admin/repos_data', []);
378 378 pyroutes.register('reset_password', '/_admin/password_reset', []);
379 379 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
380 380 pyroutes.register('robots', '/robots.txt', []);
381 381 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed-rss', ['repo_name']);
382 382 pyroutes.register('rss_feed_home_old', '/%(repo_name)s/feed/rss', ['repo_name']);
383 383 pyroutes.register('search', '/_admin/search', []);
384 384 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
385 385 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
386 386 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
387 387 pyroutes.register('setup_2fa', '/_admin/setup_2fa', []);
388 388 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
389 389 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
390 390 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
391 391 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
392 392 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
393 393 pyroutes.register('upload_file', '/_file_store/upload', []);
394 394 pyroutes.register('user_autocomplete_data', '/_users', []);
395 395 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
396 396 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
397 397 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
398 398 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
399 399 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
400 400 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
401 401 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
402 402 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
403 403 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
404 404 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
405 405 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
406 406 pyroutes.register('user_groups', '/_admin/user_groups', []);
407 407 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
408 408 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
409 409 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
410 410 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
411 411 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
412 412 pyroutes.register('user_notice_dismiss', '/_admin/users/%(user_id)s/notice_dismiss', ['user_id']);
413 413 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
414 414 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
415 415 pyroutes.register('users', '/_admin/users', []);
416 416 pyroutes.register('users_create', '/_admin/users/create', []);
417 417 pyroutes.register('users_data', '/_admin/users_data', []);
418 418 pyroutes.register('users_new', '/_admin/users/new', []);
419 419 }
@@ -1,57 +1,57 b''
1 1 <%inherit file="/base/base.mako"/>
2 2
3 3 <%def name="title()">
4 4 ${_('My account')} ${c.rhodecode_user.username}
5 5 %if c.rhodecode_name:
6 6 &middot; ${h.branding(c.rhodecode_name)}
7 7 %endif
8 8 </%def>
9 9
10 10 <%def name="breadcrumbs_links()">
11 11 ${_('My Account')}
12 12 </%def>
13 13
14 14 <%def name="menu_bar_nav()">
15 15 ${self.menu_items(active='my_account')}
16 16 </%def>
17 17
18 18 <%def name="main()">
19 19 <div class="box">
20 20 <div class="title">
21 21 ${self.breadcrumbs()}
22 22 </div>
23 23
24 24 <div class="sidebar-col-wrapper scw-small">
25 25 ##main
26 26 <div class="sidebar">
27 27 <ul class="nav nav-pills nav-stacked">
28 28 <li class="${h.is_active(['profile', 'profile_edit'], c.active)}"><a href="${h.route_path('my_account_profile')}">${_('Profile')}</a></li>
29 29 <li class="${h.is_active('emails', c.active)}"><a href="${h.route_path('my_account_emails')}">${_('Emails')}</a></li>
30 30 <li class="${h.is_active('password', c.active)}"><a href="${h.route_path('my_account_password')}">${_('Password')}</a></li>
31 <li class="${h.is_active('2FA', c.active)}"><a href="${h.route_path('my_account_enable_2fa')}">${_('2FA')}</a></li>
31 <li class="${h.is_active('2FA', c.active)}"><a href="${h.route_path('my_account_configure_2fa')}">${_('2FA')}</a></li>
32 32 <li class="${h.is_active('bookmarks', c.active)}"><a href="${h.route_path('my_account_bookmarks')}">${_('Bookmarks')}</a></li>
33 33 <li class="${h.is_active('auth_tokens', c.active)}"><a href="${h.route_path('my_account_auth_tokens')}">${_('Auth Tokens')}</a></li>
34 34 <li class="${h.is_active(['ssh_keys', 'ssh_keys_generate'], c.active)}"><a href="${h.route_path('my_account_ssh_keys')}">${_('SSH Keys')}</a></li>
35 35 <li class="${h.is_active('user_group_membership', c.active)}"><a href="${h.route_path('my_account_user_group_membership')}">${_('User Group Membership')}</a></li>
36 36
37 37 ## TODO: Find a better integration of oauth/saml views into navigation.
38 38 <% my_account_external_url = h.route_path_or_none('my_account_external_identity') %>
39 39 % if my_account_external_url:
40 40 <li class="${h.is_active('external_identity', c.active)}"><a href="${my_account_external_url}">${_('External Identities')}</a></li>
41 41 % endif
42 42
43 43 <li class="${h.is_active('repos', c.active)}"><a href="${h.route_path('my_account_repos')}">${_('Owned Repositories')}</a></li>
44 44 <li class="${h.is_active('watched', c.active)}"><a href="${h.route_path('my_account_watched')}">${_('Watched Repositories')}</a></li>
45 45 <li class="${h.is_active('pullrequests', c.active)}"><a href="${h.route_path('my_account_pullrequests')}">${_('Pull Requests')}</a></li>
46 46 <li class="${h.is_active('perms', c.active)}"><a href="${h.route_path('my_account_perms')}">${_('Permissions')}</a></li>
47 47 <li class="${h.is_active('my_notifications', c.active)}"><a href="${h.route_path('my_account_notifications')}">${_('Live Notifications')}</a></li>
48 48 </ul>
49 49 </div>
50 50
51 51 <div class="main-content-full-width">
52 52 <%include file="/admin/my_account/my_account_${c.active}.mako"/>
53 53 </div>
54 54 </div>
55 55 </div>
56 56
57 57 </%def>
@@ -1,134 +1,134 b''
1 1 <%namespace name="base" file="/base/base.mako"/>
2 2
3 3 <div class="panel panel-default">
4 4 <div class="panel-heading">
5 5 <h3 class="panel-title">${_('Enable/Disable 2FA for your account')}</h3>
6 6 </div>
7 ${h.secure_form(h.route_path('my_account_enable_2fa_save'), request=request)}
7 ${h.secure_form(h.route_path('my_account_configure_2fa_update'), request=request)}
8 8 <div class="panel-body">
9 9 <div class="form">
10 10 <div class="fields">
11 11 <div class="field">
12 12 <div class="label">
13 13 <label>${_('2FA status')}:</label>
14 14 </div>
15 15 <div class="checkboxes">
16 16 % if c.locked_2fa:
17 17 <span class="help-block">${_('2FA settings cannot be changed here, because 2FA was forced enabled by RhodeCode Administrator.')}</span>
18 18
19 19 % else:
20 20 <div class="form-check">
21 21 <input type="radio" id="2faEnabled" name="2fa_status" value="1" ${'checked=1' if c.state_of_2fa else ''}/>
22 22 <label for="2faEnabled">${_('Enable 2FA')}</label>
23 23
24 24 <input type="radio" id="2faDisabled" name="2fa_status" value="0" ${'checked=1' if not c.state_of_2fa else ''} />
25 25 <label for="2faDisabled">${_('Disable 2FA')}</label>
26 26 </div>
27 27 % endif
28 28
29 29 </div>
30 30 </div>
31 31 </div>
32 32 <button id="saveBtn" class="btn btn-primary" ${'disabled' if c.locked_2fa else ''}>${_('Save')}</button>
33 33 </div>
34 34 </div>
35 35 ${h.end_form()}
36 36 </div>
37 37
38 38 % if c.state_of_2fa:
39 39
40 40
41 41 % if not c.user_seen_2fa_recovery_codes:
42 42
43 43 <div class="panel panel-warning">
44 44 <div class="panel-heading" id="advanced-archive">
45 45 <h3 class="panel-title">${_('2FA Recovery codes')} <a class="permalink" href="#advanced-archive"> ΒΆ</a></h3>
46 46 </div>
47 47 <div class="panel-body">
48 48 <p>
49 49 ${_('You have not seen your 2FA recovery codes yet.')}
50 50 ${_('Please save them in a safe place, or you will lose access to your account in case of lost access to authenticator app.')}
51 51 </p>
52 52 <br/>
53 <a href="${request.route_path('my_account_enable_2fa', _query={'show-recovery-codes': 1})}" class="btn btn-primary">${_('Show recovery codes')}</a>
53 <a href="${request.route_path('my_account_configure_2fa', _query={'show-recovery-codes': 1})}" class="btn btn-primary">${_('Show recovery codes')}</a>
54 54 </div>
55 55 </div>
56 56 % endif
57 57
58 58
59 59 ${h.secure_form(h.route_path('my_account_regenerate_2fa_recovery_codes'), request=request)}
60 60 <div class="panel panel-default">
61 61 <div class="panel-heading">
62 62 <h3 class="panel-title">${_('Regenerate 2FA recovery codes for your account')}</h3>
63 63 </div>
64 64 <div class="panel-body">
65 65 <form id="2faForm">
66 66 <input type="text" name="totp" placeholder="${_('Verify the code from the app')}" pattern="\d{6}" style="width: 20%">
67 67 <button type="submit" class="btn btn-primary">${_('Verify and generate new codes')}</button>
68 68 </form>
69 69 </div>
70 70
71 71 </div>
72 72 ${h.end_form()}
73 73 % endif
74 74
75 75
76 76 <script>
77 77
78 78 function showRecoveryCodesPopup() {
79 79
80 80 SwalNoAnimation.fire({
81 81 title: _gettext('2FA recovery codes'),
82 82 html: '<span>Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you will lose access to your account.</span>',
83 83 showCancelButton: false,
84 84 showConfirmButton: true,
85 85 showLoaderOnConfirm: true,
86 86 confirmButtonText: _gettext('Show now'),
87 87 allowOutsideClick: function () {
88 88 !Swal.isLoading()
89 89 },
90 90
91 91 preConfirm: function () {
92 92
93 93 var postData = {
94 94 'csrf_token': CSRF_TOKEN
95 95 };
96 96 return new Promise(function (resolve, reject) {
97 97 $.ajax({
98 98 type: 'POST',
99 99 data: postData,
100 100 url: pyroutes.url('my_account_show_2fa_recovery_codes'),
101 101 headers: {'X-PARTIAL-XHR': true}
102 102 })
103 103 .done(function (data) {
104 104 resolve(data);
105 105 })
106 106 .fail(function (jqXHR, textStatus, errorThrown) {
107 107 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
108 108 ajaxErrorSwal(message);
109 109 });
110 110 })
111 111 }
112 112
113 113 })
114 114 .then(function (result) {
115 115 if (result.value) {
116 116 let funcData = {'recoveryCodes': result.value.recovery_codes}
117 117 let recoveryCodesHtml = renderTemplate('recoveryCodes', funcData);
118 118 SwalNoAnimation.fire({
119 119 allowOutsideClick: false,
120 120 confirmButtonText: _gettext('I Copied the codes'),
121 121 title: _gettext('2FA Recovery Codes'),
122 122 html: recoveryCodesHtml
123 123 }).then(function (result) {
124 124 if (result.isConfirmed) {
125 125 window.location.reload()
126 126 }
127 127 })
128 128 }
129 129 })
130 130 }
131 131 % if request.GET.get('show-recovery-codes') == '1' and not c.user_seen_2fa_recovery_codes:
132 132 showRecoveryCodesPopup();
133 133 % endif
134 134 </script>
General Comments 0
You need to be logged in to leave comments. Login now