##// END OF EJS Templates
debugging: expose logs/exception when debug log is enabled.
super-admin -
r4768:f604047c default
parent child Browse files
Show More
@@ -1,782 +1,785 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
50 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
51 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.exc_tracking import store_exception
52 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.subscribers import (
53 from rhodecode.subscribers import (
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
54 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 write_metadata_if_needed, write_usage_data)
55 write_metadata_if_needed, write_usage_data)
56
56
57
57
58 log = logging.getLogger(__name__)
58 log = logging.getLogger(__name__)
59
59
60
60
61 def is_http_error(response):
61 def is_http_error(response):
62 # error which should have traceback
62 # error which should have traceback
63 return response.status_code > 499
63 return response.status_code > 499
64
64
65
65
66 def should_load_all():
66 def should_load_all():
67 """
67 """
68 Returns if all application components should be loaded. In some cases it's
68 Returns if all application components should be loaded. In some cases it's
69 desired to skip apps loading for faster shell script execution
69 desired to skip apps loading for faster shell script execution
70 """
70 """
71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
71 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 if ssh_cmd:
72 if ssh_cmd:
73 return False
73 return False
74
74
75 return True
75 return True
76
76
77
77
78 def make_pyramid_app(global_config, **settings):
78 def make_pyramid_app(global_config, **settings):
79 """
79 """
80 Constructs the WSGI application based on Pyramid.
80 Constructs the WSGI application based on Pyramid.
81
81
82 Specials:
82 Specials:
83
83
84 * The application can also be integrated like a plugin via the call to
84 * The application can also be integrated like a plugin via the call to
85 `includeme`. This is accompanied with the other utility functions which
85 `includeme`. This is accompanied with the other utility functions which
86 are called. Changing this should be done with great care to not break
86 are called. Changing this should be done with great care to not break
87 cases when these fragments are assembled from another place.
87 cases when these fragments are assembled from another place.
88
88
89 """
89 """
90
90
91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
91 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
92 # will be replaced by the value of the environment variable "NAME" in this case.
92 # will be replaced by the value of the environment variable "NAME" in this case.
93 start_time = time.time()
93 start_time = time.time()
94 log.info('Pyramid app config starting')
94 log.info('Pyramid app config starting')
95
95
96 debug = asbool(global_config.get('debug'))
96 debug = asbool(global_config.get('debug'))
97 if debug:
97 if debug:
98 enable_debug()
98 enable_debug()
99
99
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
100 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
101
101
102 global_config = _substitute_values(global_config, environ)
102 global_config = _substitute_values(global_config, environ)
103 settings = _substitute_values(settings, environ)
103 settings = _substitute_values(settings, environ)
104
104
105 sanitize_settings_and_apply_defaults(global_config, settings)
105 sanitize_settings_and_apply_defaults(global_config, settings)
106
106
107 config = Configurator(settings=settings)
107 config = Configurator(settings=settings)
108
108
109 # Apply compatibility patches
109 # Apply compatibility patches
110 patches.inspect_getargspec()
110 patches.inspect_getargspec()
111
111
112 load_pyramid_environment(global_config, settings)
112 load_pyramid_environment(global_config, settings)
113
113
114 # Static file view comes first
114 # Static file view comes first
115 includeme_first(config)
115 includeme_first(config)
116
116
117 includeme(config)
117 includeme(config)
118
118
119 pyramid_app = config.make_wsgi_app()
119 pyramid_app = config.make_wsgi_app()
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
120 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
121 pyramid_app.config = config
121 pyramid_app.config = config
122
122
123 config.configure_celery(global_config['__file__'])
123 config.configure_celery(global_config['__file__'])
124
124
125 # creating the app uses a connection - return it after we are done
125 # creating the app uses a connection - return it after we are done
126 meta.Session.remove()
126 meta.Session.remove()
127 total_time = time.time() - start_time
127 total_time = time.time() - start_time
128 log.info('Pyramid app `%s` created and configured in %.2fs',
128 log.info('Pyramid app `%s` created and configured in %.2fs',
129 pyramid_app.func_name, total_time)
129 pyramid_app.func_name, total_time)
130
130
131 return pyramid_app
131 return pyramid_app
132
132
133
133
134 def not_found_view(request):
134 def not_found_view(request):
135 """
135 """
136 This creates the view which should be registered as not-found-view to
136 This creates the view which should be registered as not-found-view to
137 pyramid.
137 pyramid.
138 """
138 """
139
139
140 if not getattr(request, 'vcs_call', None):
140 if not getattr(request, 'vcs_call', None):
141 # handle like regular case with our error_handler
141 # handle like regular case with our error_handler
142 return error_handler(HTTPNotFound(), request)
142 return error_handler(HTTPNotFound(), request)
143
143
144 # handle not found view as a vcs call
144 # handle not found view as a vcs call
145 settings = request.registry.settings
145 settings = request.registry.settings
146 ae_client = getattr(request, 'ae_client', None)
146 ae_client = getattr(request, 'ae_client', None)
147 vcs_app = VCSMiddleware(
147 vcs_app = VCSMiddleware(
148 HTTPNotFound(), request.registry, settings,
148 HTTPNotFound(), request.registry, settings,
149 appenlight_client=ae_client)
149 appenlight_client=ae_client)
150
150
151 return wsgiapp(vcs_app)(None, request)
151 return wsgiapp(vcs_app)(None, request)
152
152
153
153
154 def error_handler(exception, request):
154 def error_handler(exception, request):
155 import rhodecode
155 import rhodecode
156 from rhodecode.lib import helpers
156 from rhodecode.lib import helpers
157 from rhodecode.lib.utils2 import str2bool
157
158
158 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
159 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
159
160
160 base_response = HTTPInternalServerError()
161 base_response = HTTPInternalServerError()
161 # prefer original exception for the response since it may have headers set
162 # prefer original exception for the response since it may have headers set
162 if isinstance(exception, HTTPException):
163 if isinstance(exception, HTTPException):
163 base_response = exception
164 base_response = exception
164 elif isinstance(exception, VCSCommunicationError):
165 elif isinstance(exception, VCSCommunicationError):
165 base_response = VCSServerUnavailable()
166 base_response = VCSServerUnavailable()
166
167
167 if is_http_error(base_response):
168 if is_http_error(base_response):
168 log.exception(
169 log.exception(
169 'error occurred handling this request for path: %s', request.path)
170 'error occurred handling this request for path: %s', request.path)
170
171
171 error_explanation = base_response.explanation or str(base_response)
172 error_explanation = base_response.explanation or str(base_response)
172 if base_response.status_code == 404:
173 if base_response.status_code == 404:
173 error_explanation += " Optionally you don't have permission to access this page."
174 error_explanation += " Optionally you don't have permission to access this page."
174 c = AttributeDict()
175 c = AttributeDict()
175 c.error_message = base_response.status
176 c.error_message = base_response.status
176 c.error_explanation = error_explanation
177 c.error_explanation = error_explanation
177 c.visual = AttributeDict()
178 c.visual = AttributeDict()
178
179
179 c.visual.rhodecode_support_url = (
180 c.visual.rhodecode_support_url = (
180 request.registry.settings.get('rhodecode_support_url') or
181 request.registry.settings.get('rhodecode_support_url') or
181 request.route_url('rhodecode_support')
182 request.route_url('rhodecode_support')
182 )
183 )
183 c.redirect_time = 0
184 c.redirect_time = 0
184 c.rhodecode_name = rhodecode_title
185 c.rhodecode_name = rhodecode_title
185 if not c.rhodecode_name:
186 if not c.rhodecode_name:
186 c.rhodecode_name = 'Rhodecode'
187 c.rhodecode_name = 'Rhodecode'
187
188
188 c.causes = []
189 c.causes = []
189 if is_http_error(base_response):
190 if is_http_error(base_response):
190 c.causes.append('Server is overloaded.')
191 c.causes.append('Server is overloaded.')
191 c.causes.append('Server database connection is lost.')
192 c.causes.append('Server database connection is lost.')
192 c.causes.append('Server expected unhandled error.')
193 c.causes.append('Server expected unhandled error.')
193
194
194 if hasattr(base_response, 'causes'):
195 if hasattr(base_response, 'causes'):
195 c.causes = base_response.causes
196 c.causes = base_response.causes
196
197
197 c.messages = helpers.flash.pop_messages(request=request)
198 c.messages = helpers.flash.pop_messages(request=request)
198
199
199 exc_info = sys.exc_info()
200 exc_info = sys.exc_info()
200 c.exception_id = id(exc_info)
201 c.exception_id = id(exc_info)
201 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
202 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
202 or base_response.status_code > 499
203 or base_response.status_code > 499
203 c.exception_id_url = request.route_url(
204 c.exception_id_url = request.route_url(
204 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
205 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
205
206
206 if c.show_exception_id:
207 if c.show_exception_id:
207 store_exception(c.exception_id, exc_info)
208 store_exception(c.exception_id, exc_info)
209 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
210 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
208
211
209 response = render_to_response(
212 response = render_to_response(
210 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
213 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
211 response=base_response)
214 response=base_response)
212
215
213 return response
216 return response
214
217
215
218
216 def includeme_first(config):
219 def includeme_first(config):
217 # redirect automatic browser favicon.ico requests to correct place
220 # redirect automatic browser favicon.ico requests to correct place
218 def favicon_redirect(context, request):
221 def favicon_redirect(context, request):
219 return HTTPFound(
222 return HTTPFound(
220 request.static_path('rhodecode:public/images/favicon.ico'))
223 request.static_path('rhodecode:public/images/favicon.ico'))
221
224
222 config.add_view(favicon_redirect, route_name='favicon')
225 config.add_view(favicon_redirect, route_name='favicon')
223 config.add_route('favicon', '/favicon.ico')
226 config.add_route('favicon', '/favicon.ico')
224
227
225 def robots_redirect(context, request):
228 def robots_redirect(context, request):
226 return HTTPFound(
229 return HTTPFound(
227 request.static_path('rhodecode:public/robots.txt'))
230 request.static_path('rhodecode:public/robots.txt'))
228
231
229 config.add_view(robots_redirect, route_name='robots')
232 config.add_view(robots_redirect, route_name='robots')
230 config.add_route('robots', '/robots.txt')
233 config.add_route('robots', '/robots.txt')
231
234
232 config.add_static_view(
235 config.add_static_view(
233 '_static/deform', 'deform:static')
236 '_static/deform', 'deform:static')
234 config.add_static_view(
237 config.add_static_view(
235 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
238 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
236
239
237
240
238 def includeme(config, auth_resources=None):
241 def includeme(config, auth_resources=None):
239 from rhodecode.lib.celerylib.loader import configure_celery
242 from rhodecode.lib.celerylib.loader import configure_celery
240 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
243 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
241 settings = config.registry.settings
244 settings = config.registry.settings
242 config.set_request_factory(Request)
245 config.set_request_factory(Request)
243
246
244 # plugin information
247 # plugin information
245 config.registry.rhodecode_plugins = collections.OrderedDict()
248 config.registry.rhodecode_plugins = collections.OrderedDict()
246
249
247 config.add_directive(
250 config.add_directive(
248 'register_rhodecode_plugin', register_rhodecode_plugin)
251 'register_rhodecode_plugin', register_rhodecode_plugin)
249
252
250 config.add_directive('configure_celery', configure_celery)
253 config.add_directive('configure_celery', configure_celery)
251
254
252 if asbool(settings.get('appenlight', 'false')):
255 if asbool(settings.get('appenlight', 'false')):
253 config.include('appenlight_client.ext.pyramid_tween')
256 config.include('appenlight_client.ext.pyramid_tween')
254
257
255 load_all = should_load_all()
258 load_all = should_load_all()
256
259
257 # Includes which are required. The application would fail without them.
260 # Includes which are required. The application would fail without them.
258 config.include('pyramid_mako')
261 config.include('pyramid_mako')
259 config.include('rhodecode.lib.rc_beaker')
262 config.include('rhodecode.lib.rc_beaker')
260 config.include('rhodecode.lib.rc_cache')
263 config.include('rhodecode.lib.rc_cache')
261 config.include('rhodecode.apps._base.navigation')
264 config.include('rhodecode.apps._base.navigation')
262 config.include('rhodecode.apps._base.subscribers')
265 config.include('rhodecode.apps._base.subscribers')
263 config.include('rhodecode.tweens')
266 config.include('rhodecode.tweens')
264 config.include('rhodecode.authentication')
267 config.include('rhodecode.authentication')
265
268
266 if load_all:
269 if load_all:
267 ce_auth_resources = [
270 ce_auth_resources = [
268 'rhodecode.authentication.plugins.auth_crowd',
271 'rhodecode.authentication.plugins.auth_crowd',
269 'rhodecode.authentication.plugins.auth_headers',
272 'rhodecode.authentication.plugins.auth_headers',
270 'rhodecode.authentication.plugins.auth_jasig_cas',
273 'rhodecode.authentication.plugins.auth_jasig_cas',
271 'rhodecode.authentication.plugins.auth_ldap',
274 'rhodecode.authentication.plugins.auth_ldap',
272 'rhodecode.authentication.plugins.auth_pam',
275 'rhodecode.authentication.plugins.auth_pam',
273 'rhodecode.authentication.plugins.auth_rhodecode',
276 'rhodecode.authentication.plugins.auth_rhodecode',
274 'rhodecode.authentication.plugins.auth_token',
277 'rhodecode.authentication.plugins.auth_token',
275 ]
278 ]
276
279
277 # load CE authentication plugins
280 # load CE authentication plugins
278
281
279 if auth_resources:
282 if auth_resources:
280 ce_auth_resources.extend(auth_resources)
283 ce_auth_resources.extend(auth_resources)
281
284
282 for resource in ce_auth_resources:
285 for resource in ce_auth_resources:
283 config.include(resource)
286 config.include(resource)
284
287
285 # Auto discover authentication plugins and include their configuration.
288 # Auto discover authentication plugins and include their configuration.
286 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
289 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
287 from rhodecode.authentication import discover_legacy_plugins
290 from rhodecode.authentication import discover_legacy_plugins
288 discover_legacy_plugins(config)
291 discover_legacy_plugins(config)
289
292
290 # apps
293 # apps
291 if load_all:
294 if load_all:
292 config.include('rhodecode.api')
295 config.include('rhodecode.api')
293 config.include('rhodecode.apps._base')
296 config.include('rhodecode.apps._base')
294 config.include('rhodecode.apps.hovercards')
297 config.include('rhodecode.apps.hovercards')
295 config.include('rhodecode.apps.ops')
298 config.include('rhodecode.apps.ops')
296 config.include('rhodecode.apps.channelstream')
299 config.include('rhodecode.apps.channelstream')
297 config.include('rhodecode.apps.file_store')
300 config.include('rhodecode.apps.file_store')
298 config.include('rhodecode.apps.admin')
301 config.include('rhodecode.apps.admin')
299 config.include('rhodecode.apps.login')
302 config.include('rhodecode.apps.login')
300 config.include('rhodecode.apps.home')
303 config.include('rhodecode.apps.home')
301 config.include('rhodecode.apps.journal')
304 config.include('rhodecode.apps.journal')
302
305
303 config.include('rhodecode.apps.repository')
306 config.include('rhodecode.apps.repository')
304 config.include('rhodecode.apps.repo_group')
307 config.include('rhodecode.apps.repo_group')
305 config.include('rhodecode.apps.user_group')
308 config.include('rhodecode.apps.user_group')
306 config.include('rhodecode.apps.search')
309 config.include('rhodecode.apps.search')
307 config.include('rhodecode.apps.user_profile')
310 config.include('rhodecode.apps.user_profile')
308 config.include('rhodecode.apps.user_group_profile')
311 config.include('rhodecode.apps.user_group_profile')
309 config.include('rhodecode.apps.my_account')
312 config.include('rhodecode.apps.my_account')
310 config.include('rhodecode.apps.gist')
313 config.include('rhodecode.apps.gist')
311
314
312 config.include('rhodecode.apps.svn_support')
315 config.include('rhodecode.apps.svn_support')
313 config.include('rhodecode.apps.ssh_support')
316 config.include('rhodecode.apps.ssh_support')
314 config.include('rhodecode.apps.debug_style')
317 config.include('rhodecode.apps.debug_style')
315
318
316 if load_all:
319 if load_all:
317 config.include('rhodecode.integrations')
320 config.include('rhodecode.integrations')
318
321
319 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
322 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
320 config.add_translation_dirs('rhodecode:i18n/')
323 config.add_translation_dirs('rhodecode:i18n/')
321 settings['default_locale_name'] = settings.get('lang', 'en')
324 settings['default_locale_name'] = settings.get('lang', 'en')
322
325
323 # Add subscribers.
326 # Add subscribers.
324 if load_all:
327 if load_all:
325 config.add_subscriber(scan_repositories_if_enabled,
328 config.add_subscriber(scan_repositories_if_enabled,
326 pyramid.events.ApplicationCreated)
329 pyramid.events.ApplicationCreated)
327 config.add_subscriber(write_metadata_if_needed,
330 config.add_subscriber(write_metadata_if_needed,
328 pyramid.events.ApplicationCreated)
331 pyramid.events.ApplicationCreated)
329 config.add_subscriber(write_usage_data,
332 config.add_subscriber(write_usage_data,
330 pyramid.events.ApplicationCreated)
333 pyramid.events.ApplicationCreated)
331 config.add_subscriber(write_js_routes_if_enabled,
334 config.add_subscriber(write_js_routes_if_enabled,
332 pyramid.events.ApplicationCreated)
335 pyramid.events.ApplicationCreated)
333
336
334 # request custom methods
337 # request custom methods
335 config.add_request_method(
338 config.add_request_method(
336 'rhodecode.lib.partial_renderer.get_partial_renderer',
339 'rhodecode.lib.partial_renderer.get_partial_renderer',
337 'get_partial_renderer')
340 'get_partial_renderer')
338
341
339 config.add_request_method(
342 config.add_request_method(
340 'rhodecode.lib.request_counter.get_request_counter',
343 'rhodecode.lib.request_counter.get_request_counter',
341 'request_count')
344 'request_count')
342
345
343 config.add_request_method(
346 config.add_request_method(
344 'rhodecode.lib._vendor.statsd.get_statsd_client',
347 'rhodecode.lib._vendor.statsd.get_statsd_client',
345 'statsd', reify=True)
348 'statsd', reify=True)
346
349
347 # Set the authorization policy.
350 # Set the authorization policy.
348 authz_policy = ACLAuthorizationPolicy()
351 authz_policy = ACLAuthorizationPolicy()
349 config.set_authorization_policy(authz_policy)
352 config.set_authorization_policy(authz_policy)
350
353
351 # Set the default renderer for HTML templates to mako.
354 # Set the default renderer for HTML templates to mako.
352 config.add_mako_renderer('.html')
355 config.add_mako_renderer('.html')
353
356
354 config.add_renderer(
357 config.add_renderer(
355 name='json_ext',
358 name='json_ext',
356 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
359 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
357
360
358 config.add_renderer(
361 config.add_renderer(
359 name='string_html',
362 name='string_html',
360 factory='rhodecode.lib.string_renderer.html')
363 factory='rhodecode.lib.string_renderer.html')
361
364
362 # include RhodeCode plugins
365 # include RhodeCode plugins
363 includes = aslist(settings.get('rhodecode.includes', []))
366 includes = aslist(settings.get('rhodecode.includes', []))
364 for inc in includes:
367 for inc in includes:
365 config.include(inc)
368 config.include(inc)
366
369
367 # custom not found view, if our pyramid app doesn't know how to handle
370 # custom not found view, if our pyramid app doesn't know how to handle
368 # the request pass it to potential VCS handling ap
371 # the request pass it to potential VCS handling ap
369 config.add_notfound_view(not_found_view)
372 config.add_notfound_view(not_found_view)
370 if not settings.get('debugtoolbar.enabled', False):
373 if not settings.get('debugtoolbar.enabled', False):
371 # disabled debugtoolbar handle all exceptions via the error_handlers
374 # disabled debugtoolbar handle all exceptions via the error_handlers
372 config.add_view(error_handler, context=Exception)
375 config.add_view(error_handler, context=Exception)
373
376
374 # all errors including 403/404/50X
377 # all errors including 403/404/50X
375 config.add_view(error_handler, context=HTTPError)
378 config.add_view(error_handler, context=HTTPError)
376
379
377
380
378 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
381 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
379 """
382 """
380 Apply outer WSGI middlewares around the application.
383 Apply outer WSGI middlewares around the application.
381 """
384 """
382 registry = config.registry
385 registry = config.registry
383 settings = registry.settings
386 settings = registry.settings
384
387
385 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
388 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
386 pyramid_app = HttpsFixup(pyramid_app, settings)
389 pyramid_app = HttpsFixup(pyramid_app, settings)
387
390
388 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
391 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
389 pyramid_app, settings)
392 pyramid_app, settings)
390 registry.ae_client = _ae_client
393 registry.ae_client = _ae_client
391
394
392 if settings['gzip_responses']:
395 if settings['gzip_responses']:
393 pyramid_app = make_gzip_middleware(
396 pyramid_app = make_gzip_middleware(
394 pyramid_app, settings, compress_level=1)
397 pyramid_app, settings, compress_level=1)
395
398
396 # this should be the outer most middleware in the wsgi stack since
399 # this should be the outer most middleware in the wsgi stack since
397 # middleware like Routes make database calls
400 # middleware like Routes make database calls
398 def pyramid_app_with_cleanup(environ, start_response):
401 def pyramid_app_with_cleanup(environ, start_response):
399 try:
402 try:
400 return pyramid_app(environ, start_response)
403 return pyramid_app(environ, start_response)
401 finally:
404 finally:
402 # Dispose current database session and rollback uncommitted
405 # Dispose current database session and rollback uncommitted
403 # transactions.
406 # transactions.
404 meta.Session.remove()
407 meta.Session.remove()
405
408
406 # In a single threaded mode server, on non sqlite db we should have
409 # In a single threaded mode server, on non sqlite db we should have
407 # '0 Current Checked out connections' at the end of a request,
410 # '0 Current Checked out connections' at the end of a request,
408 # if not, then something, somewhere is leaving a connection open
411 # if not, then something, somewhere is leaving a connection open
409 pool = meta.Base.metadata.bind.engine.pool
412 pool = meta.Base.metadata.bind.engine.pool
410 log.debug('sa pool status: %s', pool.status())
413 log.debug('sa pool status: %s', pool.status())
411 log.debug('Request processing finalized')
414 log.debug('Request processing finalized')
412
415
413 return pyramid_app_with_cleanup
416 return pyramid_app_with_cleanup
414
417
415
418
416 def sanitize_settings_and_apply_defaults(global_config, settings):
419 def sanitize_settings_and_apply_defaults(global_config, settings):
417 """
420 """
418 Applies settings defaults and does all type conversion.
421 Applies settings defaults and does all type conversion.
419
422
420 We would move all settings parsing and preparation into this place, so that
423 We would move all settings parsing and preparation into this place, so that
421 we have only one place left which deals with this part. The remaining parts
424 we have only one place left which deals with this part. The remaining parts
422 of the application would start to rely fully on well prepared settings.
425 of the application would start to rely fully on well prepared settings.
423
426
424 This piece would later be split up per topic to avoid a big fat monster
427 This piece would later be split up per topic to avoid a big fat monster
425 function.
428 function.
426 """
429 """
427
430
428 settings.setdefault('rhodecode.edition', 'Community Edition')
431 settings.setdefault('rhodecode.edition', 'Community Edition')
429 settings.setdefault('rhodecode.edition_id', 'CE')
432 settings.setdefault('rhodecode.edition_id', 'CE')
430
433
431 if 'mako.default_filters' not in settings:
434 if 'mako.default_filters' not in settings:
432 # set custom default filters if we don't have it defined
435 # set custom default filters if we don't have it defined
433 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
436 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
434 settings['mako.default_filters'] = 'h_filter'
437 settings['mako.default_filters'] = 'h_filter'
435
438
436 if 'mako.directories' not in settings:
439 if 'mako.directories' not in settings:
437 mako_directories = settings.setdefault('mako.directories', [
440 mako_directories = settings.setdefault('mako.directories', [
438 # Base templates of the original application
441 # Base templates of the original application
439 'rhodecode:templates',
442 'rhodecode:templates',
440 ])
443 ])
441 log.debug(
444 log.debug(
442 "Using the following Mako template directories: %s",
445 "Using the following Mako template directories: %s",
443 mako_directories)
446 mako_directories)
444
447
445 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
448 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
446 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
449 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
447 raw_url = settings['beaker.session.url']
450 raw_url = settings['beaker.session.url']
448 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
451 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
449 settings['beaker.session.url'] = 'redis://' + raw_url
452 settings['beaker.session.url'] = 'redis://' + raw_url
450
453
451 # Default includes, possible to change as a user
454 # Default includes, possible to change as a user
452 pyramid_includes = settings.setdefault('pyramid.includes', [])
455 pyramid_includes = settings.setdefault('pyramid.includes', [])
453 log.debug(
456 log.debug(
454 "Using the following pyramid.includes: %s",
457 "Using the following pyramid.includes: %s",
455 pyramid_includes)
458 pyramid_includes)
456
459
457 # TODO: johbo: Re-think this, usually the call to config.include
460 # TODO: johbo: Re-think this, usually the call to config.include
458 # should allow to pass in a prefix.
461 # should allow to pass in a prefix.
459 settings.setdefault('rhodecode.api.url', '/_admin/api')
462 settings.setdefault('rhodecode.api.url', '/_admin/api')
460 settings.setdefault('__file__', global_config.get('__file__'))
463 settings.setdefault('__file__', global_config.get('__file__'))
461
464
462 # Sanitize generic settings.
465 # Sanitize generic settings.
463 _list_setting(settings, 'default_encoding', 'UTF-8')
466 _list_setting(settings, 'default_encoding', 'UTF-8')
464 _bool_setting(settings, 'is_test', 'false')
467 _bool_setting(settings, 'is_test', 'false')
465 _bool_setting(settings, 'gzip_responses', 'false')
468 _bool_setting(settings, 'gzip_responses', 'false')
466
469
467 # Call split out functions that sanitize settings for each topic.
470 # Call split out functions that sanitize settings for each topic.
468 _sanitize_appenlight_settings(settings)
471 _sanitize_appenlight_settings(settings)
469 _sanitize_vcs_settings(settings)
472 _sanitize_vcs_settings(settings)
470 _sanitize_cache_settings(settings)
473 _sanitize_cache_settings(settings)
471
474
472 # configure instance id
475 # configure instance id
473 config_utils.set_instance_id(settings)
476 config_utils.set_instance_id(settings)
474
477
475 return settings
478 return settings
476
479
477
480
478 def enable_debug():
481 def enable_debug():
479 """
482 """
480 Helper to enable debug on running instance
483 Helper to enable debug on running instance
481 :return:
484 :return:
482 """
485 """
483 import tempfile
486 import tempfile
484 import textwrap
487 import textwrap
485 import logging.config
488 import logging.config
486
489
487 ini_template = textwrap.dedent("""
490 ini_template = textwrap.dedent("""
488 #####################################
491 #####################################
489 ### DEBUG LOGGING CONFIGURATION ####
492 ### DEBUG LOGGING CONFIGURATION ####
490 #####################################
493 #####################################
491 [loggers]
494 [loggers]
492 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
495 keys = root, sqlalchemy, beaker, celery, rhodecode, ssh_wrapper
493
496
494 [handlers]
497 [handlers]
495 keys = console, console_sql
498 keys = console, console_sql
496
499
497 [formatters]
500 [formatters]
498 keys = generic, color_formatter, color_formatter_sql
501 keys = generic, color_formatter, color_formatter_sql
499
502
500 #############
503 #############
501 ## LOGGERS ##
504 ## LOGGERS ##
502 #############
505 #############
503 [logger_root]
506 [logger_root]
504 level = NOTSET
507 level = NOTSET
505 handlers = console
508 handlers = console
506
509
507 [logger_sqlalchemy]
510 [logger_sqlalchemy]
508 level = INFO
511 level = INFO
509 handlers = console_sql
512 handlers = console_sql
510 qualname = sqlalchemy.engine
513 qualname = sqlalchemy.engine
511 propagate = 0
514 propagate = 0
512
515
513 [logger_beaker]
516 [logger_beaker]
514 level = DEBUG
517 level = DEBUG
515 handlers =
518 handlers =
516 qualname = beaker.container
519 qualname = beaker.container
517 propagate = 1
520 propagate = 1
518
521
519 [logger_rhodecode]
522 [logger_rhodecode]
520 level = DEBUG
523 level = DEBUG
521 handlers =
524 handlers =
522 qualname = rhodecode
525 qualname = rhodecode
523 propagate = 1
526 propagate = 1
524
527
525 [logger_ssh_wrapper]
528 [logger_ssh_wrapper]
526 level = DEBUG
529 level = DEBUG
527 handlers =
530 handlers =
528 qualname = ssh_wrapper
531 qualname = ssh_wrapper
529 propagate = 1
532 propagate = 1
530
533
531 [logger_celery]
534 [logger_celery]
532 level = DEBUG
535 level = DEBUG
533 handlers =
536 handlers =
534 qualname = celery
537 qualname = celery
535
538
536
539
537 ##############
540 ##############
538 ## HANDLERS ##
541 ## HANDLERS ##
539 ##############
542 ##############
540
543
541 [handler_console]
544 [handler_console]
542 class = StreamHandler
545 class = StreamHandler
543 args = (sys.stderr, )
546 args = (sys.stderr, )
544 level = DEBUG
547 level = DEBUG
545 formatter = color_formatter
548 formatter = color_formatter
546
549
547 [handler_console_sql]
550 [handler_console_sql]
548 # "level = DEBUG" logs SQL queries and results.
551 # "level = DEBUG" logs SQL queries and results.
549 # "level = INFO" logs SQL queries.
552 # "level = INFO" logs SQL queries.
550 # "level = WARN" logs neither. (Recommended for production systems.)
553 # "level = WARN" logs neither. (Recommended for production systems.)
551 class = StreamHandler
554 class = StreamHandler
552 args = (sys.stderr, )
555 args = (sys.stderr, )
553 level = WARN
556 level = WARN
554 formatter = color_formatter_sql
557 formatter = color_formatter_sql
555
558
556 ################
559 ################
557 ## FORMATTERS ##
560 ## FORMATTERS ##
558 ################
561 ################
559
562
560 [formatter_generic]
563 [formatter_generic]
561 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
564 class = rhodecode.lib.logging_formatter.ExceptionAwareFormatter
562 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
565 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
563 datefmt = %Y-%m-%d %H:%M:%S
566 datefmt = %Y-%m-%d %H:%M:%S
564
567
565 [formatter_color_formatter]
568 [formatter_color_formatter]
566 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
569 class = rhodecode.lib.logging_formatter.ColorRequestTrackingFormatter
567 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
570 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s | %(req_id)s
568 datefmt = %Y-%m-%d %H:%M:%S
571 datefmt = %Y-%m-%d %H:%M:%S
569
572
570 [formatter_color_formatter_sql]
573 [formatter_color_formatter_sql]
571 class = rhodecode.lib.logging_formatter.ColorFormatterSql
574 class = rhodecode.lib.logging_formatter.ColorFormatterSql
572 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
575 format = %(asctime)s.%(msecs)03d [%(process)d] %(levelname)-5.5s [%(name)s] %(message)s
573 datefmt = %Y-%m-%d %H:%M:%S
576 datefmt = %Y-%m-%d %H:%M:%S
574 """)
577 """)
575
578
576 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
579 with tempfile.NamedTemporaryFile(prefix='rc_debug_logging_', suffix='.ini',
577 delete=False) as f:
580 delete=False) as f:
578 log.info('Saved Temporary DEBUG config at %s', f.name)
581 log.info('Saved Temporary DEBUG config at %s', f.name)
579 f.write(ini_template)
582 f.write(ini_template)
580
583
581 logging.config.fileConfig(f.name)
584 logging.config.fileConfig(f.name)
582 log.debug('DEBUG MODE ON')
585 log.debug('DEBUG MODE ON')
583 os.remove(f.name)
586 os.remove(f.name)
584
587
585
588
586 def _sanitize_appenlight_settings(settings):
589 def _sanitize_appenlight_settings(settings):
587 _bool_setting(settings, 'appenlight', 'false')
590 _bool_setting(settings, 'appenlight', 'false')
588
591
589
592
590 def _sanitize_vcs_settings(settings):
593 def _sanitize_vcs_settings(settings):
591 """
594 """
592 Applies settings defaults and does type conversion for all VCS related
595 Applies settings defaults and does type conversion for all VCS related
593 settings.
596 settings.
594 """
597 """
595 _string_setting(settings, 'vcs.svn.compatible_version', '')
598 _string_setting(settings, 'vcs.svn.compatible_version', '')
596 _string_setting(settings, 'vcs.hooks.protocol', 'http')
599 _string_setting(settings, 'vcs.hooks.protocol', 'http')
597 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
600 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
598 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
601 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
599 _string_setting(settings, 'vcs.server', '')
602 _string_setting(settings, 'vcs.server', '')
600 _string_setting(settings, 'vcs.server.protocol', 'http')
603 _string_setting(settings, 'vcs.server.protocol', 'http')
601 _bool_setting(settings, 'startup.import_repos', 'false')
604 _bool_setting(settings, 'startup.import_repos', 'false')
602 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
605 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
603 _bool_setting(settings, 'vcs.server.enable', 'true')
606 _bool_setting(settings, 'vcs.server.enable', 'true')
604 _bool_setting(settings, 'vcs.start_server', 'false')
607 _bool_setting(settings, 'vcs.start_server', 'false')
605 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
608 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
606 _int_setting(settings, 'vcs.connection_timeout', 3600)
609 _int_setting(settings, 'vcs.connection_timeout', 3600)
607
610
608 # Support legacy values of vcs.scm_app_implementation. Legacy
611 # Support legacy values of vcs.scm_app_implementation. Legacy
609 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
612 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
610 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
613 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
611 scm_app_impl = settings['vcs.scm_app_implementation']
614 scm_app_impl = settings['vcs.scm_app_implementation']
612 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
615 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
613 settings['vcs.scm_app_implementation'] = 'http'
616 settings['vcs.scm_app_implementation'] = 'http'
614
617
615
618
616 def _sanitize_cache_settings(settings):
619 def _sanitize_cache_settings(settings):
617 temp_store = tempfile.gettempdir()
620 temp_store = tempfile.gettempdir()
618 default_cache_dir = os.path.join(temp_store, 'rc_cache')
621 default_cache_dir = os.path.join(temp_store, 'rc_cache')
619
622
620 # save default, cache dir, and use it for all backends later.
623 # save default, cache dir, and use it for all backends later.
621 default_cache_dir = _string_setting(
624 default_cache_dir = _string_setting(
622 settings,
625 settings,
623 'cache_dir',
626 'cache_dir',
624 default_cache_dir, lower=False, default_when_empty=True)
627 default_cache_dir, lower=False, default_when_empty=True)
625
628
626 # ensure we have our dir created
629 # ensure we have our dir created
627 if not os.path.isdir(default_cache_dir):
630 if not os.path.isdir(default_cache_dir):
628 os.makedirs(default_cache_dir, mode=0o755)
631 os.makedirs(default_cache_dir, mode=0o755)
629
632
630 # exception store cache
633 # exception store cache
631 _string_setting(
634 _string_setting(
632 settings,
635 settings,
633 'exception_tracker.store_path',
636 'exception_tracker.store_path',
634 temp_store, lower=False, default_when_empty=True)
637 temp_store, lower=False, default_when_empty=True)
635 _bool_setting(
638 _bool_setting(
636 settings,
639 settings,
637 'exception_tracker.send_email',
640 'exception_tracker.send_email',
638 'false')
641 'false')
639 _string_setting(
642 _string_setting(
640 settings,
643 settings,
641 'exception_tracker.email_prefix',
644 'exception_tracker.email_prefix',
642 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
645 '[RHODECODE ERROR]', lower=False, default_when_empty=True)
643
646
644 # cache_perms
647 # cache_perms
645 _string_setting(
648 _string_setting(
646 settings,
649 settings,
647 'rc_cache.cache_perms.backend',
650 'rc_cache.cache_perms.backend',
648 'dogpile.cache.rc.file_namespace', lower=False)
651 'dogpile.cache.rc.file_namespace', lower=False)
649 _int_setting(
652 _int_setting(
650 settings,
653 settings,
651 'rc_cache.cache_perms.expiration_time',
654 'rc_cache.cache_perms.expiration_time',
652 60)
655 60)
653 _string_setting(
656 _string_setting(
654 settings,
657 settings,
655 'rc_cache.cache_perms.arguments.filename',
658 'rc_cache.cache_perms.arguments.filename',
656 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
659 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
657
660
658 # cache_repo
661 # cache_repo
659 _string_setting(
662 _string_setting(
660 settings,
663 settings,
661 'rc_cache.cache_repo.backend',
664 'rc_cache.cache_repo.backend',
662 'dogpile.cache.rc.file_namespace', lower=False)
665 'dogpile.cache.rc.file_namespace', lower=False)
663 _int_setting(
666 _int_setting(
664 settings,
667 settings,
665 'rc_cache.cache_repo.expiration_time',
668 'rc_cache.cache_repo.expiration_time',
666 60)
669 60)
667 _string_setting(
670 _string_setting(
668 settings,
671 settings,
669 'rc_cache.cache_repo.arguments.filename',
672 'rc_cache.cache_repo.arguments.filename',
670 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
673 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
671
674
672 # cache_license
675 # cache_license
673 _string_setting(
676 _string_setting(
674 settings,
677 settings,
675 'rc_cache.cache_license.backend',
678 'rc_cache.cache_license.backend',
676 'dogpile.cache.rc.file_namespace', lower=False)
679 'dogpile.cache.rc.file_namespace', lower=False)
677 _int_setting(
680 _int_setting(
678 settings,
681 settings,
679 'rc_cache.cache_license.expiration_time',
682 'rc_cache.cache_license.expiration_time',
680 5*60)
683 5*60)
681 _string_setting(
684 _string_setting(
682 settings,
685 settings,
683 'rc_cache.cache_license.arguments.filename',
686 'rc_cache.cache_license.arguments.filename',
684 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
687 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
685
688
686 # cache_repo_longterm memory, 96H
689 # cache_repo_longterm memory, 96H
687 _string_setting(
690 _string_setting(
688 settings,
691 settings,
689 'rc_cache.cache_repo_longterm.backend',
692 'rc_cache.cache_repo_longterm.backend',
690 'dogpile.cache.rc.memory_lru', lower=False)
693 'dogpile.cache.rc.memory_lru', lower=False)
691 _int_setting(
694 _int_setting(
692 settings,
695 settings,
693 'rc_cache.cache_repo_longterm.expiration_time',
696 'rc_cache.cache_repo_longterm.expiration_time',
694 345600)
697 345600)
695 _int_setting(
698 _int_setting(
696 settings,
699 settings,
697 'rc_cache.cache_repo_longterm.max_size',
700 'rc_cache.cache_repo_longterm.max_size',
698 10000)
701 10000)
699
702
700 # sql_cache_short
703 # sql_cache_short
701 _string_setting(
704 _string_setting(
702 settings,
705 settings,
703 'rc_cache.sql_cache_short.backend',
706 'rc_cache.sql_cache_short.backend',
704 'dogpile.cache.rc.memory_lru', lower=False)
707 'dogpile.cache.rc.memory_lru', lower=False)
705 _int_setting(
708 _int_setting(
706 settings,
709 settings,
707 'rc_cache.sql_cache_short.expiration_time',
710 'rc_cache.sql_cache_short.expiration_time',
708 30)
711 30)
709 _int_setting(
712 _int_setting(
710 settings,
713 settings,
711 'rc_cache.sql_cache_short.max_size',
714 'rc_cache.sql_cache_short.max_size',
712 10000)
715 10000)
713
716
714
717
715 def _int_setting(settings, name, default):
718 def _int_setting(settings, name, default):
716 settings[name] = int(settings.get(name, default))
719 settings[name] = int(settings.get(name, default))
717 return settings[name]
720 return settings[name]
718
721
719
722
720 def _bool_setting(settings, name, default):
723 def _bool_setting(settings, name, default):
721 input_val = settings.get(name, default)
724 input_val = settings.get(name, default)
722 if isinstance(input_val, unicode):
725 if isinstance(input_val, unicode):
723 input_val = input_val.encode('utf8')
726 input_val = input_val.encode('utf8')
724 settings[name] = asbool(input_val)
727 settings[name] = asbool(input_val)
725 return settings[name]
728 return settings[name]
726
729
727
730
728 def _list_setting(settings, name, default):
731 def _list_setting(settings, name, default):
729 raw_value = settings.get(name, default)
732 raw_value = settings.get(name, default)
730
733
731 old_separator = ','
734 old_separator = ','
732 if old_separator in raw_value:
735 if old_separator in raw_value:
733 # If we get a comma separated list, pass it to our own function.
736 # If we get a comma separated list, pass it to our own function.
734 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
737 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
735 else:
738 else:
736 # Otherwise we assume it uses pyramids space/newline separation.
739 # Otherwise we assume it uses pyramids space/newline separation.
737 settings[name] = aslist(raw_value)
740 settings[name] = aslist(raw_value)
738 return settings[name]
741 return settings[name]
739
742
740
743
741 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
744 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
742 value = settings.get(name, default)
745 value = settings.get(name, default)
743
746
744 if default_when_empty and not value:
747 if default_when_empty and not value:
745 # use default value when value is empty
748 # use default value when value is empty
746 value = default
749 value = default
747
750
748 if lower:
751 if lower:
749 value = value.lower()
752 value = value.lower()
750 settings[name] = value
753 settings[name] = value
751 return settings[name]
754 return settings[name]
752
755
753
756
754 def _substitute_values(mapping, substitutions):
757 def _substitute_values(mapping, substitutions):
755 result = {}
758 result = {}
756
759
757 try:
760 try:
758 for key, value in mapping.items():
761 for key, value in mapping.items():
759 # initialize without substitution first
762 # initialize without substitution first
760 result[key] = value
763 result[key] = value
761
764
762 # Note: Cannot use regular replacements, since they would clash
765 # Note: Cannot use regular replacements, since they would clash
763 # with the implementation of ConfigParser. Using "format" instead.
766 # with the implementation of ConfigParser. Using "format" instead.
764 try:
767 try:
765 result[key] = value.format(**substitutions)
768 result[key] = value.format(**substitutions)
766 except KeyError as e:
769 except KeyError as e:
767 env_var = '{}'.format(e.args[0])
770 env_var = '{}'.format(e.args[0])
768
771
769 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
772 msg = 'Failed to substitute: `{key}={{{var}}}` with environment entry. ' \
770 'Make sure your environment has {var} set, or remove this ' \
773 'Make sure your environment has {var} set, or remove this ' \
771 'variable from config file'.format(key=key, var=env_var)
774 'variable from config file'.format(key=key, var=env_var)
772
775
773 if env_var.startswith('ENV_'):
776 if env_var.startswith('ENV_'):
774 raise ValueError(msg)
777 raise ValueError(msg)
775 else:
778 else:
776 log.warning(msg)
779 log.warning(msg)
777
780
778 except ValueError as e:
781 except ValueError as e:
779 log.warning('Failed to substitute ENV variable: %s', e)
782 log.warning('Failed to substitute ENV variable: %s', e)
780 result = mapping
783 result = mapping
781
784
782 return result
785 return result
@@ -1,171 +1,186 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import sys
21 import sys
22 import logging
22 import logging
23
23
24
24
25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
25 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
26
26
27 # Sequences
27 # Sequences
28 RESET_SEQ = "\033[0m"
28 RESET_SEQ = "\033[0m"
29 COLOR_SEQ = "\033[0;%dm"
29 COLOR_SEQ = "\033[0;%dm"
30 BOLD_SEQ = "\033[1m"
30 BOLD_SEQ = "\033[1m"
31
31
32 COLORS = {
32 COLORS = {
33 'CRITICAL': MAGENTA,
33 'CRITICAL': MAGENTA,
34 'ERROR': RED,
34 'ERROR': RED,
35 'WARNING': CYAN,
35 'WARNING': CYAN,
36 'INFO': GREEN,
36 'INFO': GREEN,
37 'DEBUG': BLUE,
37 'DEBUG': BLUE,
38 'SQL': YELLOW
38 'SQL': YELLOW
39 }
39 }
40
40
41
41
42 def one_space_trim(s):
42 def one_space_trim(s):
43 if s.find(" ") == -1:
43 if s.find(" ") == -1:
44 return s
44 return s
45 else:
45 else:
46 s = s.replace(' ', ' ')
46 s = s.replace(' ', ' ')
47 return one_space_trim(s)
47 return one_space_trim(s)
48
48
49
49
50 def format_sql(sql):
50 def format_sql(sql):
51 sql = sql.replace('\n', '')
51 sql = sql.replace('\n', '')
52 sql = one_space_trim(sql)
52 sql = one_space_trim(sql)
53 sql = sql\
53 sql = sql\
54 .replace(',', ',\n\t')\
54 .replace(',', ',\n\t')\
55 .replace('SELECT', '\n\tSELECT \n\t')\
55 .replace('SELECT', '\n\tSELECT \n\t')\
56 .replace('UPDATE', '\n\tUPDATE \n\t')\
56 .replace('UPDATE', '\n\tUPDATE \n\t')\
57 .replace('DELETE', '\n\tDELETE \n\t')\
57 .replace('DELETE', '\n\tDELETE \n\t')\
58 .replace('FROM', '\n\tFROM')\
58 .replace('FROM', '\n\tFROM')\
59 .replace('ORDER BY', '\n\tORDER BY')\
59 .replace('ORDER BY', '\n\tORDER BY')\
60 .replace('LIMIT', '\n\tLIMIT')\
60 .replace('LIMIT', '\n\tLIMIT')\
61 .replace('WHERE', '\n\tWHERE')\
61 .replace('WHERE', '\n\tWHERE')\
62 .replace('AND', '\n\tAND')\
62 .replace('AND', '\n\tAND')\
63 .replace('LEFT', '\n\tLEFT')\
63 .replace('LEFT', '\n\tLEFT')\
64 .replace('INNER', '\n\tINNER')\
64 .replace('INNER', '\n\tINNER')\
65 .replace('INSERT', '\n\tINSERT')\
65 .replace('INSERT', '\n\tINSERT')\
66 .replace('DELETE', '\n\tDELETE')
66 .replace('DELETE', '\n\tDELETE')
67 return sql
67 return sql
68
68
69
69
70 class ExceptionAwareFormatter(logging.Formatter):
70 class ExceptionAwareFormatter(logging.Formatter):
71 """
71 """
72 Extended logging formatter which prints out remote tracebacks.
72 Extended logging formatter which prints out remote tracebacks.
73 """
73 """
74
74
75 def formatException(self, ei):
75 def formatException(self, ei):
76 ex_type, ex_value, ex_tb = ei
76 ex_type, ex_value, ex_tb = ei
77
77
78 local_tb = logging.Formatter.formatException(self, ei)
78 local_tb = logging.Formatter.formatException(self, ei)
79 if hasattr(ex_value, '_vcs_server_traceback'):
79 if hasattr(ex_value, '_vcs_server_traceback'):
80
80
81 def formatRemoteTraceback(remote_tb_lines):
81 def formatRemoteTraceback(remote_tb_lines):
82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
82 result = ["\n +--- This exception occured remotely on VCSServer - Remote traceback:\n\n"]
83 result.append(remote_tb_lines)
83 result.append(remote_tb_lines)
84 result.append("\n +--- End of remote traceback\n")
84 result.append("\n +--- End of remote traceback\n")
85 return result
85 return result
86
86
87 try:
87 try:
88 if ex_type is not None and ex_value is None and ex_tb is None:
88 if ex_type is not None and ex_value is None and ex_tb is None:
89 # possible old (3.x) call syntax where caller is only
89 # possible old (3.x) call syntax where caller is only
90 # providing exception object
90 # providing exception object
91 if type(ex_type) is not type:
91 if type(ex_type) is not type:
92 raise TypeError(
92 raise TypeError(
93 "invalid argument: ex_type should be an exception "
93 "invalid argument: ex_type should be an exception "
94 "type, or just supply no arguments at all")
94 "type, or just supply no arguments at all")
95 if ex_type is None and ex_tb is None:
95 if ex_type is None and ex_tb is None:
96 ex_type, ex_value, ex_tb = sys.exc_info()
96 ex_type, ex_value, ex_tb = sys.exc_info()
97
97
98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
98 remote_tb = getattr(ex_value, "_vcs_server_traceback", None)
99
99
100 if remote_tb:
100 if remote_tb:
101 remote_tb = formatRemoteTraceback(remote_tb)
101 remote_tb = formatRemoteTraceback(remote_tb)
102 return local_tb + ''.join(remote_tb)
102 return local_tb + ''.join(remote_tb)
103 finally:
103 finally:
104 # clean up cycle to traceback, to allow proper GC
104 # clean up cycle to traceback, to allow proper GC
105 del ex_type, ex_value, ex_tb
105 del ex_type, ex_value, ex_tb
106
106
107 return local_tb
107 return local_tb
108
108
109
109
110 class ColorFormatter(ExceptionAwareFormatter):
110 class ColorFormatter(ExceptionAwareFormatter):
111
111
112 def format(self, record):
112 def format(self, record):
113 """
113 """
114 Changes record's levelname to use with COLORS enum
114 Changes record's levelname to use with COLORS enum
115 """
115 """
116
116
117 levelname = record.levelname
117 levelname = record.levelname
118 start = COLOR_SEQ % (COLORS[levelname])
118 start = COLOR_SEQ % (COLORS[levelname])
119 def_record = logging.Formatter.format(self, record)
119 def_record = logging.Formatter.format(self, record)
120 end = RESET_SEQ
120 end = RESET_SEQ
121
121
122 colored_record = ''.join([start, def_record, end])
122 colored_record = ''.join([start, def_record, end])
123 return colored_record
123 return colored_record
124
124
125
125
126 def _inject_req_id(record):
126 def _inject_req_id(record):
127 from pyramid.threadlocal import get_current_request
127 from pyramid.threadlocal import get_current_request
128 dummy = '00000000-0000-0000-0000-000000000000'
129 req_id = None
130
128 req = get_current_request()
131 req = get_current_request()
129 dummy = '00000000-0000-0000-0000-000000000000'
132 if req:
130 req_id = 'req_id:%-36s' % (getattr(req, 'req_id', dummy))
133 req_id = getattr(req, 'req_id', None)
134
135 req_id = 'req_id:%-36s' % (req_id or dummy)
131 record.req_id = req_id
136 record.req_id = req_id
132
137
133
138
139 def _add_log_to_debug_bucket(formatted_record):
140 from pyramid.threadlocal import get_current_request
141 req = get_current_request()
142 if req:
143 req.req_id_bucket.append(formatted_record)
144
145
134 class RequestTrackingFormatter(ExceptionAwareFormatter):
146 class RequestTrackingFormatter(ExceptionAwareFormatter):
135 def format(self, record):
147 def format(self, record):
136 _inject_req_id(record)
148 _inject_req_id(record)
137 def_record = logging.Formatter.format(self, record)
149 def_record = logging.Formatter.format(self, record)
150 _add_log_to_debug_bucket(def_record)
138 return def_record
151 return def_record
139
152
140
153
141 class ColorRequestTrackingFormatter(ColorFormatter):
154 class ColorRequestTrackingFormatter(ColorFormatter):
155
142 def format(self, record):
156 def format(self, record):
143 """
157 """
144 Changes record's levelname to use with COLORS enum
158 Changes record's levelname to use with COLORS enum
145 """
159 """
146 _inject_req_id(record)
160 _inject_req_id(record)
147 levelname = record.levelname
161 levelname = record.levelname
148 start = COLOR_SEQ % (COLORS[levelname])
162 start = COLOR_SEQ % (COLORS[levelname])
149 def_record = logging.Formatter.format(self, record)
163 def_record = logging.Formatter.format(self, record)
150 end = RESET_SEQ
164 end = RESET_SEQ
151
165
152 colored_record = ''.join([start, def_record, end])
166 colored_record = ''.join([start, def_record, end])
167 _add_log_to_debug_bucket(def_record)
153 return colored_record
168 return colored_record
154
169
155
170
156 class ColorFormatterSql(logging.Formatter):
171 class ColorFormatterSql(logging.Formatter):
157
172
158 def format(self, record):
173 def format(self, record):
159 """
174 """
160 Changes record's levelname to use with COLORS enum
175 Changes record's levelname to use with COLORS enum
161 """
176 """
162
177
163 start = COLOR_SEQ % (COLORS['SQL'])
178 start = COLOR_SEQ % (COLORS['SQL'])
164 def_record = format_sql(logging.Formatter.format(self, record))
179 def_record = format_sql(logging.Formatter.format(self, record))
165 end = RESET_SEQ
180 end = RESET_SEQ
166
181
167 colored_record = ''.join([start, def_record, end])
182 colored_record = ''.join([start, def_record, end])
168 return colored_record
183 return colored_record
169
184
170 # marcink: needs to stay with this name for backward .ini compatability
185 # marcink: needs to stay with this name for backward .ini compatability
171 Pyro4AwareFormatter = ExceptionAwareFormatter
186 Pyro4AwareFormatter = ExceptionAwareFormatter
@@ -1,29 +1,38 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2020 RhodeCode GmbH
3 # Copyright (C) 2017-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 from uuid import uuid4
21 from uuid import uuid4
22 from pyramid.decorator import reify
22 from pyramid.decorator import reify
23 from pyramid.request import Request as _Request
23 from pyramid.request import Request as _Request
24
24
25
25
26 class Request(_Request):
26 class Request(_Request):
27 _req_id_bucket = list()
28
27 @reify
29 @reify
28 def req_id(self):
30 def req_id(self):
29 return str(uuid4())
31 return str(uuid4())
32
33 @property
34 def req_id_bucket(self):
35 return self._req_id_bucket
36
37 def req_id_records_init(self):
38 self._req_id_bucket = list()
@@ -1,397 +1,405 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import io
20 import io
21 import shlex
21 import shlex
22
22
23 import math
23 import math
24 import re
24 import re
25 import os
25 import os
26 import datetime
26 import datetime
27 import logging
27 import logging
28 import Queue
28 import Queue
29 import subprocess32
29 import subprocess32
30
30
31
31
32 from dateutil.parser import parse
32 from dateutil.parser import parse
33 from pyramid.threadlocal import get_current_request
33 from pyramid.threadlocal import get_current_request
34 from pyramid.interfaces import IRoutesMapper
34 from pyramid.interfaces import IRoutesMapper
35 from pyramid.settings import asbool
35 from pyramid.settings import asbool
36 from pyramid.path import AssetResolver
36 from pyramid.path import AssetResolver
37 from threading import Thread
37 from threading import Thread
38
38
39 from rhodecode.translation import _ as tsf
39 from rhodecode.translation import _ as tsf
40 from rhodecode.config.jsroutes import generate_jsroutes_content
40 from rhodecode.config.jsroutes import generate_jsroutes_content
41 from rhodecode.lib import auth
41 from rhodecode.lib import auth
42 from rhodecode.lib.base import get_auth_user
42 from rhodecode.lib.base import get_auth_user
43
43
44 import rhodecode
44 import rhodecode
45
45
46
46
47 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
48
48
49
49
50 def add_renderer_globals(event):
50 def add_renderer_globals(event):
51 from rhodecode.lib import helpers
51 from rhodecode.lib import helpers
52
52
53 # TODO: When executed in pyramid view context the request is not available
53 # TODO: When executed in pyramid view context the request is not available
54 # in the event. Find a better solution to get the request.
54 # in the event. Find a better solution to get the request.
55 request = event['request'] or get_current_request()
55 request = event['request'] or get_current_request()
56
56
57 # Add Pyramid translation as '_' to context
57 # Add Pyramid translation as '_' to context
58 event['_'] = request.translate
58 event['_'] = request.translate
59 event['_ungettext'] = request.plularize
59 event['_ungettext'] = request.plularize
60 event['h'] = helpers
60 event['h'] = helpers
61
61
62
62
63 def add_localizer(event):
63 def add_localizer(event):
64 request = event.request
64 request = event.request
65 localizer = request.localizer
65 localizer = request.localizer
66
66
67 def auto_translate(*args, **kwargs):
67 def auto_translate(*args, **kwargs):
68 return localizer.translate(tsf(*args, **kwargs))
68 return localizer.translate(tsf(*args, **kwargs))
69
69
70 request.translate = auto_translate
70 request.translate = auto_translate
71 request.plularize = localizer.pluralize
71 request.plularize = localizer.pluralize
72
72
73
73
74 def set_user_lang(event):
74 def set_user_lang(event):
75 request = event.request
75 request = event.request
76 cur_user = getattr(request, 'user', None)
76 cur_user = getattr(request, 'user', None)
77
77
78 if cur_user:
78 if cur_user:
79 user_lang = cur_user.get_instance().user_data.get('language')
79 user_lang = cur_user.get_instance().user_data.get('language')
80 if user_lang:
80 if user_lang:
81 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
81 log.debug('lang: setting current user:%s language to: %s', cur_user, user_lang)
82 event.request._LOCALE_ = user_lang
82 event.request._LOCALE_ = user_lang
83
83
84
84
85 def add_request_user_context(event):
85 def add_request_user_context(event):
86 """
86 """
87 Adds auth user into request context
87 Adds auth user into request context
88 """
88 """
89 request = event.request
89 request = event.request
90 # access req_id as soon as possible
90 # access req_id as soon as possible
91 req_id = request.req_id
91 req_id = request.req_id
92
92
93 if hasattr(request, 'vcs_call'):
93 if hasattr(request, 'vcs_call'):
94 # skip vcs calls
94 # skip vcs calls
95 return
95 return
96
96
97 if hasattr(request, 'rpc_method'):
97 if hasattr(request, 'rpc_method'):
98 # skip api calls
98 # skip api calls
99 return
99 return
100
100
101 auth_user, auth_token = get_auth_user(request)
101 auth_user, auth_token = get_auth_user(request)
102 request.user = auth_user
102 request.user = auth_user
103 request.user_auth_token = auth_token
103 request.user_auth_token = auth_token
104 request.environ['rc_auth_user'] = auth_user
104 request.environ['rc_auth_user'] = auth_user
105 request.environ['rc_auth_user_id'] = auth_user.user_id
105 request.environ['rc_auth_user_id'] = auth_user.user_id
106 request.environ['rc_req_id'] = req_id
106 request.environ['rc_req_id'] = req_id
107
107
108
108
109 def reset_log_bucket(event):
110 """
111 reset the log bucket on new request
112 """
113 request = event.request
114 request.req_id_records_init()
115
116
109 def scan_repositories_if_enabled(event):
117 def scan_repositories_if_enabled(event):
110 """
118 """
111 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
119 This is subscribed to the `pyramid.events.ApplicationCreated` event. It
112 does a repository scan if enabled in the settings.
120 does a repository scan if enabled in the settings.
113 """
121 """
114 settings = event.app.registry.settings
122 settings = event.app.registry.settings
115 vcs_server_enabled = settings['vcs.server.enable']
123 vcs_server_enabled = settings['vcs.server.enable']
116 import_on_startup = settings['startup.import_repos']
124 import_on_startup = settings['startup.import_repos']
117 if vcs_server_enabled and import_on_startup:
125 if vcs_server_enabled and import_on_startup:
118 from rhodecode.model.scm import ScmModel
126 from rhodecode.model.scm import ScmModel
119 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
127 from rhodecode.lib.utils import repo2db_mapper, get_rhodecode_base_path
120 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
128 repositories = ScmModel().repo_scan(get_rhodecode_base_path())
121 repo2db_mapper(repositories, remove_obsolete=False)
129 repo2db_mapper(repositories, remove_obsolete=False)
122
130
123
131
124 def write_metadata_if_needed(event):
132 def write_metadata_if_needed(event):
125 """
133 """
126 Writes upgrade metadata
134 Writes upgrade metadata
127 """
135 """
128 import rhodecode
136 import rhodecode
129 from rhodecode.lib import system_info
137 from rhodecode.lib import system_info
130 from rhodecode.lib import ext_json
138 from rhodecode.lib import ext_json
131
139
132 fname = '.rcmetadata.json'
140 fname = '.rcmetadata.json'
133 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
141 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
134 metadata_destination = os.path.join(ini_loc, fname)
142 metadata_destination = os.path.join(ini_loc, fname)
135
143
136 def get_update_age():
144 def get_update_age():
137 now = datetime.datetime.utcnow()
145 now = datetime.datetime.utcnow()
138
146
139 with open(metadata_destination, 'rb') as f:
147 with open(metadata_destination, 'rb') as f:
140 data = ext_json.json.loads(f.read())
148 data = ext_json.json.loads(f.read())
141 if 'created_on' in data:
149 if 'created_on' in data:
142 update_date = parse(data['created_on'])
150 update_date = parse(data['created_on'])
143 diff = now - update_date
151 diff = now - update_date
144 return diff.total_seconds() / 60.0
152 return diff.total_seconds() / 60.0
145
153
146 return 0
154 return 0
147
155
148 def write():
156 def write():
149 configuration = system_info.SysInfo(
157 configuration = system_info.SysInfo(
150 system_info.rhodecode_config)()['value']
158 system_info.rhodecode_config)()['value']
151 license_token = configuration['config']['license_token']
159 license_token = configuration['config']['license_token']
152
160
153 setup = dict(
161 setup = dict(
154 workers=configuration['config']['server:main'].get(
162 workers=configuration['config']['server:main'].get(
155 'workers', '?'),
163 'workers', '?'),
156 worker_type=configuration['config']['server:main'].get(
164 worker_type=configuration['config']['server:main'].get(
157 'worker_class', 'sync'),
165 'worker_class', 'sync'),
158 )
166 )
159 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
167 dbinfo = system_info.SysInfo(system_info.database_info)()['value']
160 del dbinfo['url']
168 del dbinfo['url']
161
169
162 metadata = dict(
170 metadata = dict(
163 desc='upgrade metadata info',
171 desc='upgrade metadata info',
164 license_token=license_token,
172 license_token=license_token,
165 created_on=datetime.datetime.utcnow().isoformat(),
173 created_on=datetime.datetime.utcnow().isoformat(),
166 usage=system_info.SysInfo(system_info.usage_info)()['value'],
174 usage=system_info.SysInfo(system_info.usage_info)()['value'],
167 platform=system_info.SysInfo(system_info.platform_type)()['value'],
175 platform=system_info.SysInfo(system_info.platform_type)()['value'],
168 database=dbinfo,
176 database=dbinfo,
169 cpu=system_info.SysInfo(system_info.cpu)()['value'],
177 cpu=system_info.SysInfo(system_info.cpu)()['value'],
170 memory=system_info.SysInfo(system_info.memory)()['value'],
178 memory=system_info.SysInfo(system_info.memory)()['value'],
171 setup=setup
179 setup=setup
172 )
180 )
173
181
174 with open(metadata_destination, 'wb') as f:
182 with open(metadata_destination, 'wb') as f:
175 f.write(ext_json.json.dumps(metadata))
183 f.write(ext_json.json.dumps(metadata))
176
184
177 settings = event.app.registry.settings
185 settings = event.app.registry.settings
178 if settings.get('metadata.skip'):
186 if settings.get('metadata.skip'):
179 return
187 return
180
188
181 # only write this every 24h, workers restart caused unwanted delays
189 # only write this every 24h, workers restart caused unwanted delays
182 try:
190 try:
183 age_in_min = get_update_age()
191 age_in_min = get_update_age()
184 except Exception:
192 except Exception:
185 age_in_min = 0
193 age_in_min = 0
186
194
187 if age_in_min > 60 * 60 * 24:
195 if age_in_min > 60 * 60 * 24:
188 return
196 return
189
197
190 try:
198 try:
191 write()
199 write()
192 except Exception:
200 except Exception:
193 pass
201 pass
194
202
195
203
196 def write_usage_data(event):
204 def write_usage_data(event):
197 import rhodecode
205 import rhodecode
198 from rhodecode.lib import system_info
206 from rhodecode.lib import system_info
199 from rhodecode.lib import ext_json
207 from rhodecode.lib import ext_json
200
208
201 settings = event.app.registry.settings
209 settings = event.app.registry.settings
202 instance_tag = settings.get('metadata.write_usage_tag')
210 instance_tag = settings.get('metadata.write_usage_tag')
203 if not settings.get('metadata.write_usage'):
211 if not settings.get('metadata.write_usage'):
204 return
212 return
205
213
206 def get_update_age(dest_file):
214 def get_update_age(dest_file):
207 now = datetime.datetime.utcnow()
215 now = datetime.datetime.utcnow()
208
216
209 with open(dest_file, 'rb') as f:
217 with open(dest_file, 'rb') as f:
210 data = ext_json.json.loads(f.read())
218 data = ext_json.json.loads(f.read())
211 if 'created_on' in data:
219 if 'created_on' in data:
212 update_date = parse(data['created_on'])
220 update_date = parse(data['created_on'])
213 diff = now - update_date
221 diff = now - update_date
214 return math.ceil(diff.total_seconds() / 60.0)
222 return math.ceil(diff.total_seconds() / 60.0)
215
223
216 return 0
224 return 0
217
225
218 utc_date = datetime.datetime.utcnow()
226 utc_date = datetime.datetime.utcnow()
219 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
227 hour_quarter = int(math.ceil((utc_date.hour + utc_date.minute/60.0) / 6.))
220 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
228 fname = '.rc_usage_{date.year}{date.month:02d}{date.day:02d}_{hour}.json'.format(
221 date=utc_date, hour=hour_quarter)
229 date=utc_date, hour=hour_quarter)
222 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
230 ini_loc = os.path.dirname(rhodecode.CONFIG.get('__file__'))
223
231
224 usage_dir = os.path.join(ini_loc, '.rcusage')
232 usage_dir = os.path.join(ini_loc, '.rcusage')
225 if not os.path.isdir(usage_dir):
233 if not os.path.isdir(usage_dir):
226 os.makedirs(usage_dir)
234 os.makedirs(usage_dir)
227 usage_metadata_destination = os.path.join(usage_dir, fname)
235 usage_metadata_destination = os.path.join(usage_dir, fname)
228
236
229 try:
237 try:
230 age_in_min = get_update_age(usage_metadata_destination)
238 age_in_min = get_update_age(usage_metadata_destination)
231 except Exception:
239 except Exception:
232 age_in_min = 0
240 age_in_min = 0
233
241
234 # write every 6th hour
242 # write every 6th hour
235 if age_in_min and age_in_min < 60 * 6:
243 if age_in_min and age_in_min < 60 * 6:
236 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
244 log.debug('Usage file created %s minutes ago, skipping (threshold: %s minutes)...',
237 age_in_min, 60 * 6)
245 age_in_min, 60 * 6)
238 return
246 return
239
247
240 def write(dest_file):
248 def write(dest_file):
241 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
249 configuration = system_info.SysInfo(system_info.rhodecode_config)()['value']
242 license_token = configuration['config']['license_token']
250 license_token = configuration['config']['license_token']
243
251
244 metadata = dict(
252 metadata = dict(
245 desc='Usage data',
253 desc='Usage data',
246 instance_tag=instance_tag,
254 instance_tag=instance_tag,
247 license_token=license_token,
255 license_token=license_token,
248 created_on=datetime.datetime.utcnow().isoformat(),
256 created_on=datetime.datetime.utcnow().isoformat(),
249 usage=system_info.SysInfo(system_info.usage_info)()['value'],
257 usage=system_info.SysInfo(system_info.usage_info)()['value'],
250 )
258 )
251
259
252 with open(dest_file, 'wb') as f:
260 with open(dest_file, 'wb') as f:
253 f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
261 f.write(ext_json.json.dumps(metadata, indent=2, sort_keys=True))
254
262
255 try:
263 try:
256 log.debug('Writing usage file at: %s', usage_metadata_destination)
264 log.debug('Writing usage file at: %s', usage_metadata_destination)
257 write(usage_metadata_destination)
265 write(usage_metadata_destination)
258 except Exception:
266 except Exception:
259 pass
267 pass
260
268
261
269
262 def write_js_routes_if_enabled(event):
270 def write_js_routes_if_enabled(event):
263 registry = event.app.registry
271 registry = event.app.registry
264
272
265 mapper = registry.queryUtility(IRoutesMapper)
273 mapper = registry.queryUtility(IRoutesMapper)
266 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
274 _argument_prog = re.compile('\{(.*?)\}|:\((.*)\)')
267
275
268 def _extract_route_information(route):
276 def _extract_route_information(route):
269 """
277 """
270 Convert a route into tuple(name, path, args), eg:
278 Convert a route into tuple(name, path, args), eg:
271 ('show_user', '/profile/%(username)s', ['username'])
279 ('show_user', '/profile/%(username)s', ['username'])
272 """
280 """
273
281
274 routepath = route.pattern
282 routepath = route.pattern
275 pattern = route.pattern
283 pattern = route.pattern
276
284
277 def replace(matchobj):
285 def replace(matchobj):
278 if matchobj.group(1):
286 if matchobj.group(1):
279 return "%%(%s)s" % matchobj.group(1).split(':')[0]
287 return "%%(%s)s" % matchobj.group(1).split(':')[0]
280 else:
288 else:
281 return "%%(%s)s" % matchobj.group(2)
289 return "%%(%s)s" % matchobj.group(2)
282
290
283 routepath = _argument_prog.sub(replace, routepath)
291 routepath = _argument_prog.sub(replace, routepath)
284
292
285 if not routepath.startswith('/'):
293 if not routepath.startswith('/'):
286 routepath = '/'+routepath
294 routepath = '/'+routepath
287
295
288 return (
296 return (
289 route.name,
297 route.name,
290 routepath,
298 routepath,
291 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
299 [(arg[0].split(':')[0] if arg[0] != '' else arg[1])
292 for arg in _argument_prog.findall(pattern)]
300 for arg in _argument_prog.findall(pattern)]
293 )
301 )
294
302
295 def get_routes():
303 def get_routes():
296 # pyramid routes
304 # pyramid routes
297 for route in mapper.get_routes():
305 for route in mapper.get_routes():
298 if not route.name.startswith('__'):
306 if not route.name.startswith('__'):
299 yield _extract_route_information(route)
307 yield _extract_route_information(route)
300
308
301 if asbool(registry.settings.get('generate_js_files', 'false')):
309 if asbool(registry.settings.get('generate_js_files', 'false')):
302 static_path = AssetResolver().resolve('rhodecode:public').abspath()
310 static_path = AssetResolver().resolve('rhodecode:public').abspath()
303 jsroutes = get_routes()
311 jsroutes = get_routes()
304 jsroutes_file_content = generate_jsroutes_content(jsroutes)
312 jsroutes_file_content = generate_jsroutes_content(jsroutes)
305 jsroutes_file_path = os.path.join(
313 jsroutes_file_path = os.path.join(
306 static_path, 'js', 'rhodecode', 'routes.js')
314 static_path, 'js', 'rhodecode', 'routes.js')
307
315
308 try:
316 try:
309 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
317 with io.open(jsroutes_file_path, 'w', encoding='utf-8') as f:
310 f.write(jsroutes_file_content)
318 f.write(jsroutes_file_content)
311 except Exception:
319 except Exception:
312 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
320 log.exception('Failed to write routes.js into %s', jsroutes_file_path)
313
321
314
322
315 class Subscriber(object):
323 class Subscriber(object):
316 """
324 """
317 Base class for subscribers to the pyramid event system.
325 Base class for subscribers to the pyramid event system.
318 """
326 """
319 def __call__(self, event):
327 def __call__(self, event):
320 self.run(event)
328 self.run(event)
321
329
322 def run(self, event):
330 def run(self, event):
323 raise NotImplementedError('Subclass has to implement this.')
331 raise NotImplementedError('Subclass has to implement this.')
324
332
325
333
326 class AsyncSubscriber(Subscriber):
334 class AsyncSubscriber(Subscriber):
327 """
335 """
328 Subscriber that handles the execution of events in a separate task to not
336 Subscriber that handles the execution of events in a separate task to not
329 block the execution of the code which triggers the event. It puts the
337 block the execution of the code which triggers the event. It puts the
330 received events into a queue from which the worker process takes them in
338 received events into a queue from which the worker process takes them in
331 order.
339 order.
332 """
340 """
333 def __init__(self):
341 def __init__(self):
334 self._stop = False
342 self._stop = False
335 self._eventq = Queue.Queue()
343 self._eventq = Queue.Queue()
336 self._worker = self.create_worker()
344 self._worker = self.create_worker()
337 self._worker.start()
345 self._worker.start()
338
346
339 def __call__(self, event):
347 def __call__(self, event):
340 self._eventq.put(event)
348 self._eventq.put(event)
341
349
342 def create_worker(self):
350 def create_worker(self):
343 worker = Thread(target=self.do_work)
351 worker = Thread(target=self.do_work)
344 worker.daemon = True
352 worker.daemon = True
345 return worker
353 return worker
346
354
347 def stop_worker(self):
355 def stop_worker(self):
348 self._stop = False
356 self._stop = False
349 self._eventq.put(None)
357 self._eventq.put(None)
350 self._worker.join()
358 self._worker.join()
351
359
352 def do_work(self):
360 def do_work(self):
353 while not self._stop:
361 while not self._stop:
354 event = self._eventq.get()
362 event = self._eventq.get()
355 if event is not None:
363 if event is not None:
356 self.run(event)
364 self.run(event)
357
365
358
366
359 class AsyncSubprocessSubscriber(AsyncSubscriber):
367 class AsyncSubprocessSubscriber(AsyncSubscriber):
360 """
368 """
361 Subscriber that uses the subprocess32 module to execute a command if an
369 Subscriber that uses the subprocess32 module to execute a command if an
362 event is received. Events are handled asynchronously::
370 event is received. Events are handled asynchronously::
363
371
364 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
372 subscriber = AsyncSubprocessSubscriber('ls -la', timeout=10)
365 subscriber(dummyEvent) # running __call__(event)
373 subscriber(dummyEvent) # running __call__(event)
366
374
367 """
375 """
368
376
369 def __init__(self, cmd, timeout=None):
377 def __init__(self, cmd, timeout=None):
370 if not isinstance(cmd, (list, tuple)):
378 if not isinstance(cmd, (list, tuple)):
371 cmd = shlex.split(cmd)
379 cmd = shlex.split(cmd)
372 super(AsyncSubprocessSubscriber, self).__init__()
380 super(AsyncSubprocessSubscriber, self).__init__()
373 self._cmd = cmd
381 self._cmd = cmd
374 self._timeout = timeout
382 self._timeout = timeout
375
383
376 def run(self, event):
384 def run(self, event):
377 cmd = self._cmd
385 cmd = self._cmd
378 timeout = self._timeout
386 timeout = self._timeout
379 log.debug('Executing command %s.', cmd)
387 log.debug('Executing command %s.', cmd)
380
388
381 try:
389 try:
382 output = subprocess32.check_output(
390 output = subprocess32.check_output(
383 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
391 cmd, timeout=timeout, stderr=subprocess32.STDOUT)
384 log.debug('Command finished %s', cmd)
392 log.debug('Command finished %s', cmd)
385 if output:
393 if output:
386 log.debug('Command output: %s', output)
394 log.debug('Command output: %s', output)
387 except subprocess32.TimeoutExpired as e:
395 except subprocess32.TimeoutExpired as e:
388 log.exception('Timeout while executing command.')
396 log.exception('Timeout while executing command.')
389 if e.output:
397 if e.output:
390 log.error('Command output: %s', e.output)
398 log.error('Command output: %s', e.output)
391 except subprocess32.CalledProcessError as e:
399 except subprocess32.CalledProcessError as e:
392 log.exception('Error while executing command.')
400 log.exception('Error while executing command.')
393 if e.output:
401 if e.output:
394 log.error('Command output: %s', e.output)
402 log.error('Command output: %s', e.output)
395 except Exception:
403 except Exception:
396 log.exception(
404 log.exception(
397 'Exception while executing command %s.', cmd)
405 'Exception while executing command %s.', cmd)
@@ -1,94 +1,112 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <!DOCTYPE html>
2 <!DOCTYPE html>
3 <html xmlns="http://www.w3.org/1999/xhtml">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head>
4 <head>
5 <title>Error - ${c.error_message}</title>
5 <title>Error - ${c.error_message}</title>
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
6 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
7 <meta name="robots" content="index, nofollow"/>
7 <meta name="robots" content="index, nofollow"/>
8
8
9 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
9 <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
10 %if c.redirect_time:
10 %if c.redirect_time:
11 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
11 <meta http-equiv="refresh" content="${c.redirect_time}; url=${c.url_redirect}"/>
12 %endif
12 %endif
13
13
14 <link id="favicon" rel="shortcut icon" type="image/png" href="data:image/png;base64,AAABAAIAEBAAAAEAIABoBAAAJgAAACAgAAABACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALMiIiK1OTk5ADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMEsLCz/SUlJ/xUVFcM3NzcAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAM8eHh7/8/Pz//39/f9BQUH/Dw8P0DY2NgMzMzMAMzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAMcjIyP/8vLy////////////9/f3/0RERf8REhTINzc3ADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAKUWFhb/7Ozs///////o6Of/6Onq///////z+v//NUFh/xgaIao3NjIAMzMzADMzMwAAAAAAAAAAAAAAAGgAAAD/0dHR///////o5+X/7O/2/+v5///j5Oj//+ic/92wMv83MB3/Jys2ajMzMwAzMzMAAAAAAAAAABYAAAD4kZGR///////p6er/7Pf//+jv+//my4D/6q0J9PqkAJz/zAAlXlcoeRshOf8zMzMaMzMzAAAAAAAAAACRMDAw///////09fj/6vj//+Xcwv/vtBns/7UAav+8AAP/vgAAyZUKACotNQAtLzXyMzMzsDMzMwAAAAAKAAAA8aSjov//////6PX//+fOif/2qwCH/7YAAKN7EgBsWSQAU0gqAC4wNAAqLTUANTQyZjMzM/8zMzMOAAAAUBMTEv/x8vb/9f///+nLdfL2ogAz/7kAAG5bIwAFFD81Dhs9ShskOU8qLTZMNTQyKTMzMwAzMzP/MzMzZgAAAIVJSEf//////+nRjeb4pQAV/9sAAKiFFQAADkEAMDE0UzQ0M+IzMzOOMzMzvDMzM2QzMzMAMzMz3zMzM6oAAACeXGV9////7/j/yAAe/70AALiDAAA0NTcALDJAADMzMlEzMzPVMzMzgDMzM30zMzMjMzMzADMzM8MzMzPIAAAAnWBlaf//5V86nGYAACgeAAAAAAAABgcNACsrKQA2NjYKMzMzEDMzMwwzMzMGMzMzDDMzMwAzMzPNMzMzvwAAAG0bFQv/JRgHfQAAAB4AAAAAAAAAAAAAAAADAwMAMjIyADY2NgAzMzMAMzMzADMzMxIzMzOKMzMz/zMzM3EAAAADAAAAjAAAAP8AAAD/AAAA/QAAANAAAACZAgICXzExMV82NjaZMzMz0DMzM/wzMzP/MzMz/zMzM5gzMzMAAAAAAAAAAAAAAAAAAAAAOAAAAIoAAADKAAAA9AICAv8xMTH/NjY29DMzM8ozMzOLMzMzODMzMwAzMzMAMzMzAP5/AAD8PwAA+B8AAPAPAADgBwAA4AcAAMAbAACA+QAAgf0AAIf9AAAPjAAAH5wAAD/8AAC/+QAAgYEAAPAPAAAoAAAAIAAAAEAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICtjExMbk2NjYAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAM4BAQH/MDAw/zY2NtEzMzMNMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMAAADsAAAA/wEBAf8XFxf/MDAw/zU1Ne4zMzMmMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA2AAAA/QAAAP8AAAD/eXl5/56env8ODg7/Jycn/zY2Nv8zMzM6MzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQAAAP8AAAD/AAAA/4SEhP///////////6Ghof8QEBD/IiIi/zc3N/8zMzNFMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEEAAAD/AAAA/wAAAP+bm5v//////+/v7//u7u7//////7S0tP8VFRX/ICAg/zc3N/8zMzNFMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA3AAAA/wAAAP8AAAD/p6en///////u7u7/6urq/+rq6v/t7e3//////729vf8WFhb/ICAg/zc3N/8zMzM6MzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAAPgAAAD/AAAA/6ampv//////7e3t/+rq6v/r6+v/6+vr/+rq6v/s7Oz//////729vf8UFBT/IyMj/zY2NvozMzMlMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAUAAADdAAAA/wAAAP+ampr//////+3t7f/q6ur/6+vr/+vr6//r6+v/6+vr/+rq6v/s7Oz//////7Kysf8PDw//KSkp/zU1NeAzMzMIMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAsQAAAP8AAAD/f39////////u7u7/6urq/+vr6//r6+v/6+vr/+vr6//r6+v/6+vr/+rq6f/t7e///////5ynwf8KEy3/MC8t/zQ0M7UzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHUAAAD/AAAA/1paWv//////8vLy/+rq6v/r6+v/6+vr/+vr6//r6+v/6+vr/+vq6f/r7PD/7/f//+v3///o6Oj//+mZ/3FcJv8OGDL/NjUy/zMzM3ozMzMAMzMzADMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAAAAAAAuAAAA/wAAAP8sLCz///////v7+//p6en/6+vr/+vr6//r6+v/6+vr/+vq6f/r6+7/7/j//+r2///h2sf/37tY/9+iA//zpgD//74A/2BRJv8eJTn/MzMz/zMzMzIzMzMAMzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAMUAAAD/AAAA/9DQ0P//////6Ojo/+vr6//r6+v/6+vr/+vr6v/r6uv/7vX+/+v4///i2sb/4LZC/+OfAP/sngD/9K0A/fCuALz/zgBgoIMYRRAZPPUzMzP/MzMzyTMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAABfAAAA/wAAAP9+fn7//////+np6f/r6+v/6+vr/+vr6//r6uj/7O/z/+36///k5OH/4btP/+WfAP/voQD/9K8AyvCwAGTvrgAQ764AAP/CAABrWSUAFyA6eTMzM/8zMzP/MzMzYzMzMwAzMzMAMzMzAAAAAAAAAAAAAAAAAAAAAN4AAAD/HR0d//r6+v/4+Pj/6urq/+vr6//r6+v/6+rp/+31///o9P//4sqI/+SjAP/unwD/9K8Aou+vACjurgAA8K8AAPayAAD/xAAA6K0FACwuNAArLjUAMzMz2jMzM/8zMzPiMzMzADMzMwAzMzMAAAAAAAAAAAAAAABgAAAA/wAAAP+dnZ3//////+jo6P/r6+v/6+rq/+zr7f/t+f//5ebi/+OzMv/rnQD/8aoAnfKxABT/ugAA/8IAAP/EAAD/wQAA/LYAAP+5AACNbhoAEh48ADU0MwAzMzNaMzMz/zMzM/8zMzNkMzMzADMzMwAAAAAAAAAAAAAAAMgAAAD/IiIi//z8/P/09PT/6+vr/+vq6f/s7fD/6/r//+TYsf/npQP/8aEAwe+tAB34uAAA/8MAAN+iBAC+jg0Apn8TAHJcIgBYSykAPDkwACcrNgAxMjQAMzMzADMzMwAzMzPYMzMz/zMzM8wzMzMAMzMzAAAAAAAAAAAwAAAA/wAAAP+Hh4f//////+np6f/r6un/7O/z/+r4///lzIf/658A+/KoAFburQAA/8EAAP+/AACCZR0AKSw2ABwkOQAWIDsAEBw8ABoiOgAjKDcALzA0ADU0MgAzMzMAMzMzADMzM3AzMzP/MzMz/zMzMzQzMzMAAAAAAAAAAHoAAAD/AAAA/9ra2v//////6+rp/+zv8//q+P//5cdy/+2eAMvyqwAP7KwAAP+/AADqrQMAUEUrAAcWPwAkKTcAMDE0ADIyMwA0MzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzGTMzM/8zMzP/MzMzfzMzMwAAAAAkAAAAvgAAAP8+Pj7//////+3s6//s7fD/6vj//+fIdP/ungCa8a0AAO6uAAD+uAAA6q0DADAxMwAMGT4ANTQzCDQ0M8gzMzOOMzMzKjMzM8QzMzOQMzMz1DMzM0szMzO9MzMzSTMzMwAzMzMAMzMzvDMzM/8zMzPCMzMzJwAAAFMAAADsAAAA/3d3d///////6urq/+r5///nz4v/7p4AffGsAADvrwAA7asAAP/OAACUdRoAABBAADc1MgAzMzMAMzMzyzMzM6QzMzOFMzMzyDMzM0AzMzPXMzMzLzMzM+gzMzMcMzMzADMzMwAzMzOAMzMz/zMzM+8zMzNXAAAAegAAAP8AAAD/mJiY///////r9///6dyz/+6hAHfwqwAA768AAO2sAAD/vgAA8LQDADUzMgAmKjcAMzMzADMzMwAzMzOdMzMz4zMzM+szMzN4MzMzADMzM+UzMzPPMzMz1DMzMwAzMzMAMzMzADMzM1ozMzP/MzMz/zMzM3wAAACUAAAA/wAAAP+traz//////+ns5//uqguL8KcAAO2tAAD5tAAA/9IAAP/UAABoVCkADho8ADc2MgAzMzMAMzMzADMzM8IzMzOoMzMzdjMzM9ozMzMkMzMz5TMzM5QzMzMmMzMzADMzMwAzMzMAMzMzQjMzM/8zMzP/MzMzkwAAAJ4AAAD/AAAA/7S1tv//////7L5RtfCfAAD8uwAA/9MAAPy9AACxfQAASTgLABYhPwA+Pj0ANDQzADIyMgAzMzMGMzMzwzMzM8kzMzPNMzMzRDMzM24zMzPiMzMzADMzMyEzMzNTMzMzFDMzMwAzMzM5MzMz/zMzM/8zMzOaAAAAlAAAAP8AAAD/q7fS///80O//tgAQ/9MAAPSzAACUagAAIBcAAAAAAAAAAAAABwcHACcnJgA9PT0AOjo6ADIyMgEzMzMBMzMzATMzMwEzMzMAMzMzEDMzMwYzMzMAMzMzRjMzM1wzMzMSMzMzADMzM0IzMzP/MzMz/zMzM5MAAAB5AAAA/wAAAP+fp6r///5ZR96WAAB0VQAADgoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8PDwAvLy8ANjY2ADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMBMzMzATMzMwAzMzMAMzMzWzMzM/8zMzP/MzMzegAAAE0AAADmAAAA/1BDKeFvUA4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEBAAzMzMANjY2ADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzOrMzMz/zMzM+ozMzNRAAAAEgAAAKkAAAD/AAAA/wAAAPUAAACnAAAAVgAAABEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgICADExMQA2NjYAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzDzMzM1UzMzOlMzMz9TMzM/8zMzP/MzMzrjMzMxYAAAAAAAAAMAAAAOoAAAD/AAAA/wAAAP8AAAD/AAAA/QAAAMgAAACQAAAAXgAAADEAAAAKAAAAAAAAAAACAgIAMTExADY2NgAzMzMAMzMzCTMzMzEzMzNdMzMzjzMzM8czMzP8MzMz/zMzM/8zMzP/MzMz/zMzM+wzMzMzMzMzAAAAAAAAAAAAAAAAAAAAAD0AAACaAAAA5wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAPYAAADPAAAArAICAoQxMTGDNjY2qzMzM88zMzP1MzMz/zMzM/8zMzP/MzMz/zMzM/8zMzP/MzMz6TMzM5wzMzM/MzMzADMzMwAzMzMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMgAAAHQAAACvAAAA5QAAAP8AAAD/AAAA/wAAAP8AAAD/AgIC/zExMf82Njb/MzMz/zMzM/8zMzP/MzMz/zMzM+UzMzOvMzMzdjMzMzQzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIgAAAFEAAAB7AAAAowAAAMYCAgLqMTEx6zY2NsczMzOkMzMzfDMzM1EzMzMjMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzADMzMwAzMzMAMzMzAP/+f////D////gf///wD///4Af//8AD//+AAf//AAD//gAAf/wAAD/8AAA/+AAAH/AAAY/wAA/P4AA/x+AA/+fAA//jwA//88Af//OAP5FxgP+FcYH/jHkB/5T4A/+N+Af///iP///5j///8YP//8HAP/wD8AAAD/8AAP//+B//">
14 <link id="favicon" rel="shortcut icon" type="image/png" href="data:image/png;base64,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">
15 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
15 <script src="${h.asset('js/vendors/webcomponentsjs/custom-elements-es5-adapter.js', ver=c.rhodecode_version_hash)}"></script>
16 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
16 <script src="${h.asset('js/vendors/webcomponentsjs/webcomponents-bundle.js', ver=c.rhodecode_version_hash)}"></script>
17
17
18 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
18 <link rel="stylesheet" type="text/css" href="${h.asset('css/style.css', ver=c.rhodecode_version_hash)}" media="screen"/>
19 <style>body { background:#eeeeee; }</style>
19 <style>body { background:#eeeeee; }</style>
20 <script type="text/javascript">
20 <script type="text/javascript">
21 // register templateContext to pass template variables to JS
21 // register templateContext to pass template variables to JS
22 var templateContext = {timeago: {}};
22 var templateContext = {timeago: {}};
23 </script>
23 </script>
24 <%include file="/base/plugins_base.mako"/>
24 <%include file="/base/plugins_base.mako"/>
25 <script type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
25 <script type="text/javascript" src="${h.asset('js/scripts.min.js', ver=c.rhodecode_version_hash)}"></script>
26 </head>
26 </head>
27 <body>
27 <body>
28
28
29 <div class="wrapper error_page">
29 <div class="wrapper error_page">
30 <div class="sidebar">
30 <div class="sidebar">
31 <a href="${h.route_path('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
31 <a href="${h.route_path('home')}"><img class="error-page-logo" src="${h.asset('images/RhodeCode_Logo_Black.png')}" alt="RhodeCode"/></a>
32 </div>
32 </div>
33 <div class="main-content">
33 <div class="main-content">
34 <h1>
34 <h1>
35 <span class="error-branding">
35 <span class="error-branding">
36 ${h.branding(c.rhodecode_name)}
36 ${h.branding(c.rhodecode_name)}
37 </span><br/>
37 </span><br/>
38 ${c.error_message}
38 ${c.error_message}
39 <br/>
39 <br/>
40 <span class="error_message">${c.error_explanation}</span>
40 <span class="error_message">${c.error_explanation}</span>
41 </h1>
41 </h1>
42 % if c.messages:
42 % if c.messages:
43 % for message in c.messages:
43 % for message in c.messages:
44 <div class="alert alert-${message.category}">${message}</div>
44 <div class="alert alert-${message.category}">${message}</div>
45 % endfor
45 % endfor
46 % endif
46 % endif
47 %if c.redirect_time:
47 %if c.redirect_time:
48 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
48 <p>${_('You will be redirected to %s in %s seconds') % (c.redirect_module,c.redirect_time)}</p>
49 %endif
49 %endif
50 <div class="inner-column">
50 <div class="inner-column">
51 <h4>Possible Causes</h4>
51 <h4>Possible Causes</h4>
52 <ul>
52 <ul>
53 % if c.causes:
53 % if c.causes:
54 %for cause in c.causes:
54 %for cause in c.causes:
55 <li>${cause}</li>
55 <li>${cause}</li>
56 %endfor
56 %endfor
57 %else:
57 %else:
58 <li>The resource may have been deleted.</li>
58 <li>The resource may have been deleted.</li>
59 <li>You may not have access to this repository.</li>
59 <li>You may not have access to this repository.</li>
60 <li>The link may be incorrect.</li>
60 <li>The link may be incorrect.</li>
61 %endif
61 %endif
62 </ul>
62 </ul>
63 </div>
63 </div>
64 <div class="inner-column">
64 <div class="inner-column">
65 <h4>Support</h4>
65 <h4>Support</h4>
66 <p>For help and support, go to the <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support Page')}</a>.
66 <p>For help and support, go to the <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support Page')}</a>.
67 It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>.
67 It may be useful to include your log file; see the log file locations <a href="${h.route_url('enterprise_log_file_locations')}">here</a>.
68 </p>
68 </p>
69
69
70 </div>
70 </div>
71 <div class="inner-column">
71 <div class="inner-column">
72 <h4>Documentation</h4>
72 <h4>Documentation</h4>
73 <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
73 <p>For more information, see <a href="${h.route_url('enterprise_docs')}">docs.rhodecode.com</a>.</p>
74 </div>
74 </div>
75 </div>
75 </div>
76
76
77 % if c.show_exception_id:
77 % if c.show_exception_id:
78 <div class="sidebar" style="width: 130px">
78 <div class="sidebar" style="width: 130px">
79
79
80 </div>
80 </div>
81 <div class="main-content">
81 <div class="main-content">
82 <p>
82 <p>
83 <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/>
83 <strong>Exception ID: <code><a href="${c.exception_id_url}">${c.exception_id}</a></code> </strong> <br/>
84
84
85 Super-admins can see details of the above error in the exception tracker found under
85 Super-admins can see details of the above error in the exception tracker found under
86 <a href="${h.route_url('admin_settings_exception_tracker')}">admin > settings > exception tracker</a>.
86 <a href="${h.route_url('admin_settings_exception_tracker')}">admin > settings > exception tracker</a>.
87
88 % if c.exception_debug:
89 <pre>
90 <strong>DEBUG MODE ON FOR EXCEPTION: ${c.exception_id}</strong>
91 <strong>REQUEST_ID: ${getattr(request, 'req_id', None)}</strong>
92 ----------------
93 debug mode is controlled by
94 ${c.exception_config_ini}
95 file settings:
96
97 debug = true
98 ----------------
99
100 % for rec in getattr(request, 'req_id_bucket', []):
101 ${rec}
102 % endfor
103 </pre>
104 % endif
87 </p>
105 </p>
88 </div>
106 </div>
89 % endif
107 % endif
90 </div>
108 </div>
91
109
92 </body>
110 </body>
93
111
94 </html>
112 </html>
@@ -1,122 +1,124 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import logging
22 import logging
23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
24
24
25 from rhodecode.lib.middleware.vcs import (
25 from rhodecode.lib.middleware.vcs import (
26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
27
27
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 def vcs_detection_tween_factory(handler, registry):
32 def vcs_detection_tween_factory(handler, registry):
33
33
34 def vcs_detection_tween(request):
34 def vcs_detection_tween(request):
35 """
35 """
36 Do detection of vcs type, and save results for other layers to re-use
36 Do detection of vcs type, and save results for other layers to re-use
37 this information
37 this information
38 """
38 """
39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
40 vcs_handler = vcs_server_enabled and detect_vcs_request(
40 vcs_handler = vcs_server_enabled and detect_vcs_request(
41 request.environ, request.registry.settings.get('vcs.backends'))
41 request.environ, request.registry.settings.get('vcs.backends'))
42
42
43 if vcs_handler:
43 if vcs_handler:
44 # save detected VCS type for later re-use
44 # save detected VCS type for later re-use
45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
46 request.vcs_call = vcs_handler.SCM
46 request.vcs_call = vcs_handler.SCM
47
47
48 log.debug('Processing request with `%s` handler', handler)
48 log.debug('Processing request with `%s` handler', handler)
49 return handler(request)
49 return handler(request)
50
50
51 # mark that we didn't detect an VCS, and we can skip detection later on
51 # mark that we didn't detect an VCS, and we can skip detection later on
52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
53
53
54 log.debug('Processing request with `%s` handler', handler)
54 log.debug('Processing request with `%s` handler', handler)
55 return handler(request)
55 return handler(request)
56
56
57 return vcs_detection_tween
57 return vcs_detection_tween
58
58
59
59
60 def junk_encoding_detector(request):
60 def junk_encoding_detector(request):
61 """
61 """
62 Detect bad encoded GET params, and fail immediately with BadRequest
62 Detect bad encoded GET params, and fail immediately with BadRequest
63 """
63 """
64
64
65 try:
65 try:
66 request.GET.get("", None)
66 request.GET.get("", None)
67 except UnicodeDecodeError:
67 except UnicodeDecodeError:
68 raise HTTPBadRequest("Invalid bytes in query string.")
68 raise HTTPBadRequest("Invalid bytes in query string.")
69
69
70
70
71 def bad_url_data_detector(request):
71 def bad_url_data_detector(request):
72 """
72 """
73 Detect invalid bytes in a path.
73 Detect invalid bytes in a path.
74 """
74 """
75 try:
75 try:
76 request.path_info
76 request.path_info
77 except UnicodeDecodeError:
77 except UnicodeDecodeError:
78 raise HTTPBadRequest("Invalid bytes in URL.")
78 raise HTTPBadRequest("Invalid bytes in URL.")
79
79
80
80
81 def junk_form_data_detector(request):
81 def junk_form_data_detector(request):
82 """
82 """
83 Detect bad encoded POST params, and fail immediately with BadRequest
83 Detect bad encoded POST params, and fail immediately with BadRequest
84 """
84 """
85
85
86 if request.method == "POST":
86 if request.method == "POST":
87 try:
87 try:
88 request.POST.get("", None)
88 request.POST.get("", None)
89 except ValueError:
89 except ValueError:
90 raise HTTPBadRequest("Invalid bytes in form data.")
90 raise HTTPBadRequest("Invalid bytes in form data.")
91
91
92
92
93 def sanity_check_factory(handler, registry):
93 def sanity_check_factory(handler, registry):
94 def sanity_check(request):
94 def sanity_check(request):
95 log.debug('Checking current URL sanity for bad data')
95 log.debug('Checking current URL sanity for bad data')
96 try:
96 try:
97 junk_encoding_detector(request)
97 junk_encoding_detector(request)
98 bad_url_data_detector(request)
98 bad_url_data_detector(request)
99 junk_form_data_detector(request)
99 junk_form_data_detector(request)
100 except HTTPException as exc:
100 except HTTPException as exc:
101 return exc
101 return exc
102
102
103 return handler(request)
103 return handler(request)
104
104
105 return sanity_check
105 return sanity_check
106
106
107
107
108 def includeme(config):
108 def includeme(config):
109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
110 'pyramid.events.BeforeRender')
110 'pyramid.events.BeforeRender')
111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
112 'pyramid.events.NewRequest')
112 'pyramid.events.NewRequest')
113 config.add_subscriber('rhodecode.subscribers.add_localizer',
113 config.add_subscriber('rhodecode.subscribers.add_localizer',
114 'pyramid.events.NewRequest')
114 'pyramid.events.NewRequest')
115 config.add_subscriber('rhodecode.subscribers.reset_log_bucket',
116 'pyramid.events.NewRequest')
115 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
117 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
116 'pyramid.events.ContextFound')
118 'pyramid.events.ContextFound')
117 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
119 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
118 config.add_tween('rhodecode.tweens.sanity_check_factory')
120 config.add_tween('rhodecode.tweens.sanity_check_factory')
119
121
120 # This needs to be the LAST item
122 # This needs to be the LAST item
121 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
123 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
122 log.debug('configured all tweens')
124 log.debug('configured all tweens')
General Comments 0
You need to be logged in to leave comments. Login now