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