##// END OF EJS Templates
requests: cleaned / unified way of handling requests generation from non-web scope....
super-admin -
r4873:d49e3bd8 default
parent child Browse files
Show More
@@ -1,619 +1,610 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 collections
23 import collections
24 import tempfile
24 import tempfile
25 import time
25 import time
26 import logging.config
26 import logging.config
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.settings_maker import SettingsMaker
41 from rhodecode.config.settings_maker import SettingsMaker
42 from rhodecode.config.environment import load_pyramid_environment
42 from rhodecode.config.environment import load_pyramid_environment
43
43
44 import rhodecode.events
44 import rhodecode.events
45 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 from rhodecode.lib.request import Request
46 from rhodecode.lib.request import Request
47 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.vcs import VCSCommunicationError
48 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.exceptions import VCSServerUnavailable
49 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import AttributeDict
52 from rhodecode.lib.utils2 import AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, write_usage_data)
56 write_metadata_if_needed, write_usage_data)
57 from rhodecode.lib.statsd_client import StatsdClient
57 from rhodecode.lib.statsd_client import StatsdClient
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def should_load_all():
67 def should_load_all():
68 """
68 """
69 Returns if all application components should be loaded. In some cases it's
69 Returns if all application components should be loaded. In some cases it's
70 desired to skip apps loading for faster shell script execution
70 desired to skip apps loading for faster shell script execution
71 """
71 """
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
72 ssh_cmd = os.environ.get('RC_CMD_SSH_WRAPPER')
73 if ssh_cmd:
73 if ssh_cmd:
74 return False
74 return False
75
75
76 return True
76 return True
77
77
78
78
79 def make_pyramid_app(global_config, **settings):
79 def make_pyramid_app(global_config, **settings):
80 """
80 """
81 Constructs the WSGI application based on Pyramid.
81 Constructs the WSGI application based on Pyramid.
82
82
83 Specials:
83 Specials:
84
84
85 * The application can also be integrated like a plugin via the call to
85 * The application can also be integrated like a plugin via the call to
86 `includeme`. This is accompanied with the other utility functions which
86 `includeme`. This is accompanied with the other utility functions which
87 are called. Changing this should be done with great care to not break
87 are called. Changing this should be done with great care to not break
88 cases when these fragments are assembled from another place.
88 cases when these fragments are assembled from another place.
89
89
90 """
90 """
91 start_time = time.time()
91 start_time = time.time()
92 log.info('Pyramid app config starting')
92 log.info('Pyramid app config starting')
93
93
94 sanitize_settings_and_apply_defaults(global_config, settings)
94 sanitize_settings_and_apply_defaults(global_config, settings)
95
95
96 # init and bootstrap StatsdClient
96 # init and bootstrap StatsdClient
97 StatsdClient.setup(settings)
97 StatsdClient.setup(settings)
98
98
99 config = Configurator(settings=settings)
99 config = Configurator(settings=settings)
100 # Init our statsd at very start
100 # Init our statsd at very start
101 config.registry.statsd = StatsdClient.statsd
101 config.registry.statsd = StatsdClient.statsd
102
102
103 # Apply compatibility patches
103 # Apply compatibility patches
104 patches.inspect_getargspec()
104 patches.inspect_getargspec()
105
105
106 load_pyramid_environment(global_config, settings)
106 load_pyramid_environment(global_config, settings)
107
107
108 # Static file view comes first
108 # Static file view comes first
109 includeme_first(config)
109 includeme_first(config)
110
110
111 includeme(config)
111 includeme(config)
112
112
113 pyramid_app = config.make_wsgi_app()
113 pyramid_app = config.make_wsgi_app()
114 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
114 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
115 pyramid_app.config = config
115 pyramid_app.config = config
116
116
117 celery_settings = get_celery_config(settings)
117 celery_settings = get_celery_config(settings)
118 config.configure_celery(celery_settings)
118 config.configure_celery(celery_settings)
119
119
120 # creating the app uses a connection - return it after we are done
120 # creating the app uses a connection - return it after we are done
121 meta.Session.remove()
121 meta.Session.remove()
122
122
123 total_time = time.time() - start_time
123 total_time = time.time() - start_time
124 log.info('Pyramid app `%s` created and configured in %.2fs',
124 log.info('Pyramid app `%s` created and configured in %.2fs',
125 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
125 getattr(pyramid_app, 'func_name', 'pyramid_app'), total_time)
126 return pyramid_app
126 return pyramid_app
127
127
128
128
129 def get_celery_config(settings):
129 def get_celery_config(settings):
130 """
130 """
131 Converts basic ini configuration into celery 4.X options
131 Converts basic ini configuration into celery 4.X options
132 """
132 """
133
133
134 def key_converter(key_name):
134 def key_converter(key_name):
135 pref = 'celery.'
135 pref = 'celery.'
136 if key_name.startswith(pref):
136 if key_name.startswith(pref):
137 return key_name[len(pref):].replace('.', '_').lower()
137 return key_name[len(pref):].replace('.', '_').lower()
138
138
139 def type_converter(parsed_key, value):
139 def type_converter(parsed_key, value):
140 # cast to int
140 # cast to int
141 if value.isdigit():
141 if value.isdigit():
142 return int(value)
142 return int(value)
143
143
144 # cast to bool
144 # cast to bool
145 if value.lower() in ['true', 'false', 'True', 'False']:
145 if value.lower() in ['true', 'false', 'True', 'False']:
146 return value.lower() == 'true'
146 return value.lower() == 'true'
147 return value
147 return value
148
148
149 celery_config = {}
149 celery_config = {}
150 for k, v in settings.items():
150 for k, v in settings.items():
151 pref = 'celery.'
151 pref = 'celery.'
152 if k.startswith(pref):
152 if k.startswith(pref):
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
153 celery_config[key_converter(k)] = type_converter(key_converter(k), v)
154
154
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
155 # TODO:rethink if we want to support celerybeat based file config, probably NOT
156 # beat_config = {}
156 # beat_config = {}
157 # for section in parser.sections():
157 # for section in parser.sections():
158 # if section.startswith('celerybeat:'):
158 # if section.startswith('celerybeat:'):
159 # name = section.split(':', 1)[1]
159 # name = section.split(':', 1)[1]
160 # beat_config[name] = get_beat_config(parser, section)
160 # beat_config[name] = get_beat_config(parser, section)
161
161
162 # final compose of settings
162 # final compose of settings
163 celery_settings = {}
163 celery_settings = {}
164
164
165 if celery_config:
165 if celery_config:
166 celery_settings.update(celery_config)
166 celery_settings.update(celery_config)
167 # if beat_config:
167 # if beat_config:
168 # celery_settings.update({'beat_schedule': beat_config})
168 # celery_settings.update({'beat_schedule': beat_config})
169
169
170 return celery_settings
170 return celery_settings
171
171
172
172
173 def not_found_view(request):
173 def not_found_view(request):
174 """
174 """
175 This creates the view which should be registered as not-found-view to
175 This creates the view which should be registered as not-found-view to
176 pyramid.
176 pyramid.
177 """
177 """
178
178
179 if not getattr(request, 'vcs_call', None):
179 if not getattr(request, 'vcs_call', None):
180 # handle like regular case with our error_handler
180 # handle like regular case with our error_handler
181 return error_handler(HTTPNotFound(), request)
181 return error_handler(HTTPNotFound(), request)
182
182
183 # handle not found view as a vcs call
183 # handle not found view as a vcs call
184 settings = request.registry.settings
184 settings = request.registry.settings
185 ae_client = getattr(request, 'ae_client', None)
185 ae_client = getattr(request, 'ae_client', None)
186 vcs_app = VCSMiddleware(
186 vcs_app = VCSMiddleware(
187 HTTPNotFound(), request.registry, settings,
187 HTTPNotFound(), request.registry, settings,
188 appenlight_client=ae_client)
188 appenlight_client=ae_client)
189
189
190 return wsgiapp(vcs_app)(None, request)
190 return wsgiapp(vcs_app)(None, request)
191
191
192
192
193 def error_handler(exception, request):
193 def error_handler(exception, request):
194 import rhodecode
194 import rhodecode
195 from rhodecode.lib import helpers
195 from rhodecode.lib import helpers
196 from rhodecode.lib.utils2 import str2bool
196 from rhodecode.lib.utils2 import str2bool
197
197
198 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
198 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
199
199
200 base_response = HTTPInternalServerError()
200 base_response = HTTPInternalServerError()
201 # prefer original exception for the response since it may have headers set
201 # prefer original exception for the response since it may have headers set
202 if isinstance(exception, HTTPException):
202 if isinstance(exception, HTTPException):
203 base_response = exception
203 base_response = exception
204 elif isinstance(exception, VCSCommunicationError):
204 elif isinstance(exception, VCSCommunicationError):
205 base_response = VCSServerUnavailable()
205 base_response = VCSServerUnavailable()
206
206
207 if is_http_error(base_response):
207 if is_http_error(base_response):
208 log.exception(
208 log.exception(
209 'error occurred handling this request for path: %s', request.path)
209 'error occurred handling this request for path: %s', request.path)
210
210
211 error_explanation = base_response.explanation or str(base_response)
211 error_explanation = base_response.explanation or str(base_response)
212 if base_response.status_code == 404:
212 if base_response.status_code == 404:
213 error_explanation += " Optionally you don't have permission to access this page."
213 error_explanation += " Optionally you don't have permission to access this page."
214 c = AttributeDict()
214 c = AttributeDict()
215 c.error_message = base_response.status
215 c.error_message = base_response.status
216 c.error_explanation = error_explanation
216 c.error_explanation = error_explanation
217 c.visual = AttributeDict()
217 c.visual = AttributeDict()
218
218
219 c.visual.rhodecode_support_url = (
219 c.visual.rhodecode_support_url = (
220 request.registry.settings.get('rhodecode_support_url') or
220 request.registry.settings.get('rhodecode_support_url') or
221 request.route_url('rhodecode_support')
221 request.route_url('rhodecode_support')
222 )
222 )
223 c.redirect_time = 0
223 c.redirect_time = 0
224 c.rhodecode_name = rhodecode_title
224 c.rhodecode_name = rhodecode_title
225 if not c.rhodecode_name:
225 if not c.rhodecode_name:
226 c.rhodecode_name = 'Rhodecode'
226 c.rhodecode_name = 'Rhodecode'
227
227
228 c.causes = []
228 c.causes = []
229 if is_http_error(base_response):
229 if is_http_error(base_response):
230 c.causes.append('Server is overloaded.')
230 c.causes.append('Server is overloaded.')
231 c.causes.append('Server database connection is lost.')
231 c.causes.append('Server database connection is lost.')
232 c.causes.append('Server expected unhandled error.')
232 c.causes.append('Server expected unhandled error.')
233
233
234 if hasattr(base_response, 'causes'):
234 if hasattr(base_response, 'causes'):
235 c.causes = base_response.causes
235 c.causes = base_response.causes
236
236
237 c.messages = helpers.flash.pop_messages(request=request)
237 c.messages = helpers.flash.pop_messages(request=request)
238
238
239 exc_info = sys.exc_info()
239 exc_info = sys.exc_info()
240 c.exception_id = id(exc_info)
240 c.exception_id = id(exc_info)
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
241 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
242 or base_response.status_code > 499
242 or base_response.status_code > 499
243 c.exception_id_url = request.route_url(
243 c.exception_id_url = request.route_url(
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
244 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
245
245
246 if c.show_exception_id:
246 if c.show_exception_id:
247 store_exception(c.exception_id, exc_info)
247 store_exception(c.exception_id, exc_info)
248 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
248 c.exception_debug = str2bool(rhodecode.CONFIG.get('debug'))
249 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
249 c.exception_config_ini = rhodecode.CONFIG.get('__file__')
250
250
251 response = render_to_response(
251 response = render_to_response(
252 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
252 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
253 response=base_response)
253 response=base_response)
254
254
255 statsd = request.registry.statsd
255 statsd = request.registry.statsd
256 if statsd and base_response.status_code > 499:
256 if statsd and base_response.status_code > 499:
257 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
257 exc_type = "{}.{}".format(exception.__class__.__module__, exception.__class__.__name__)
258 statsd.incr('rhodecode_exception_total',
258 statsd.incr('rhodecode_exception_total',
259 tags=["exc_source:web",
259 tags=["exc_source:web",
260 "http_code:{}".format(base_response.status_code),
260 "http_code:{}".format(base_response.status_code),
261 "type:{}".format(exc_type)])
261 "type:{}".format(exc_type)])
262
262
263 return response
263 return response
264
264
265
265
266 def includeme_first(config):
266 def includeme_first(config):
267 # redirect automatic browser favicon.ico requests to correct place
267 # redirect automatic browser favicon.ico requests to correct place
268 def favicon_redirect(context, request):
268 def favicon_redirect(context, request):
269 return HTTPFound(
269 return HTTPFound(
270 request.static_path('rhodecode:public/images/favicon.ico'))
270 request.static_path('rhodecode:public/images/favicon.ico'))
271
271
272 config.add_view(favicon_redirect, route_name='favicon')
272 config.add_view(favicon_redirect, route_name='favicon')
273 config.add_route('favicon', '/favicon.ico')
273 config.add_route('favicon', '/favicon.ico')
274
274
275 def robots_redirect(context, request):
275 def robots_redirect(context, request):
276 return HTTPFound(
276 return HTTPFound(
277 request.static_path('rhodecode:public/robots.txt'))
277 request.static_path('rhodecode:public/robots.txt'))
278
278
279 config.add_view(robots_redirect, route_name='robots')
279 config.add_view(robots_redirect, route_name='robots')
280 config.add_route('robots', '/robots.txt')
280 config.add_route('robots', '/robots.txt')
281
281
282 config.add_static_view(
282 config.add_static_view(
283 '_static/deform', 'deform:static')
283 '_static/deform', 'deform:static')
284 config.add_static_view(
284 config.add_static_view(
285 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
285 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
286
286
287
287
288 def includeme(config, auth_resources=None):
288 def includeme(config, auth_resources=None):
289 from rhodecode.lib.celerylib.loader import configure_celery
289 from rhodecode.lib.celerylib.loader import configure_celery
290 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
290 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
291 settings = config.registry.settings
291 settings = config.registry.settings
292 config.set_request_factory(Request)
292 config.set_request_factory(Request)
293
293
294 # plugin information
294 # plugin information
295 config.registry.rhodecode_plugins = collections.OrderedDict()
295 config.registry.rhodecode_plugins = collections.OrderedDict()
296
296
297 config.add_directive(
297 config.add_directive(
298 'register_rhodecode_plugin', register_rhodecode_plugin)
298 'register_rhodecode_plugin', register_rhodecode_plugin)
299
299
300 config.add_directive('configure_celery', configure_celery)
300 config.add_directive('configure_celery', configure_celery)
301
301
302 if settings.get('appenlight', False):
302 if settings.get('appenlight', False):
303 config.include('appenlight_client.ext.pyramid_tween')
303 config.include('appenlight_client.ext.pyramid_tween')
304
304
305 load_all = should_load_all()
305 load_all = should_load_all()
306
306
307 # Includes which are required. The application would fail without them.
307 # Includes which are required. The application would fail without them.
308 config.include('pyramid_mako')
308 config.include('pyramid_mako')
309 config.include('rhodecode.lib.rc_beaker')
309 config.include('rhodecode.lib.rc_beaker')
310 config.include('rhodecode.lib.rc_cache')
310 config.include('rhodecode.lib.rc_cache')
311 config.include('rhodecode.apps._base.navigation')
311 config.include('rhodecode.apps._base.navigation')
312 config.include('rhodecode.apps._base.subscribers')
312 config.include('rhodecode.apps._base.subscribers')
313 config.include('rhodecode.tweens')
313 config.include('rhodecode.tweens')
314 config.include('rhodecode.authentication')
314 config.include('rhodecode.authentication')
315
315
316 if load_all:
316 if load_all:
317 ce_auth_resources = [
317 ce_auth_resources = [
318 'rhodecode.authentication.plugins.auth_crowd',
318 'rhodecode.authentication.plugins.auth_crowd',
319 'rhodecode.authentication.plugins.auth_headers',
319 'rhodecode.authentication.plugins.auth_headers',
320 'rhodecode.authentication.plugins.auth_jasig_cas',
320 'rhodecode.authentication.plugins.auth_jasig_cas',
321 'rhodecode.authentication.plugins.auth_ldap',
321 'rhodecode.authentication.plugins.auth_ldap',
322 'rhodecode.authentication.plugins.auth_pam',
322 'rhodecode.authentication.plugins.auth_pam',
323 'rhodecode.authentication.plugins.auth_rhodecode',
323 'rhodecode.authentication.plugins.auth_rhodecode',
324 'rhodecode.authentication.plugins.auth_token',
324 'rhodecode.authentication.plugins.auth_token',
325 ]
325 ]
326
326
327 # load CE authentication plugins
327 # load CE authentication plugins
328
328
329 if auth_resources:
329 if auth_resources:
330 ce_auth_resources.extend(auth_resources)
330 ce_auth_resources.extend(auth_resources)
331
331
332 for resource in ce_auth_resources:
332 for resource in ce_auth_resources:
333 config.include(resource)
333 config.include(resource)
334
334
335 # Auto discover authentication plugins and include their configuration.
335 # Auto discover authentication plugins and include their configuration.
336 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
336 if asbool(settings.get('auth_plugin.import_legacy_plugins', 'true')):
337 from rhodecode.authentication import discover_legacy_plugins
337 from rhodecode.authentication import discover_legacy_plugins
338 discover_legacy_plugins(config)
338 discover_legacy_plugins(config)
339
339
340 # apps
340 # apps
341 if load_all:
341 if load_all:
342 config.include('rhodecode.api')
342 config.include('rhodecode.api')
343 config.include('rhodecode.apps._base')
343 config.include('rhodecode.apps._base')
344 config.include('rhodecode.apps.hovercards')
344 config.include('rhodecode.apps.hovercards')
345 config.include('rhodecode.apps.ops')
345 config.include('rhodecode.apps.ops')
346 config.include('rhodecode.apps.channelstream')
346 config.include('rhodecode.apps.channelstream')
347 config.include('rhodecode.apps.file_store')
347 config.include('rhodecode.apps.file_store')
348 config.include('rhodecode.apps.admin')
348 config.include('rhodecode.apps.admin')
349 config.include('rhodecode.apps.login')
349 config.include('rhodecode.apps.login')
350 config.include('rhodecode.apps.home')
350 config.include('rhodecode.apps.home')
351 config.include('rhodecode.apps.journal')
351 config.include('rhodecode.apps.journal')
352
352
353 config.include('rhodecode.apps.repository')
353 config.include('rhodecode.apps.repository')
354 config.include('rhodecode.apps.repo_group')
354 config.include('rhodecode.apps.repo_group')
355 config.include('rhodecode.apps.user_group')
355 config.include('rhodecode.apps.user_group')
356 config.include('rhodecode.apps.search')
356 config.include('rhodecode.apps.search')
357 config.include('rhodecode.apps.user_profile')
357 config.include('rhodecode.apps.user_profile')
358 config.include('rhodecode.apps.user_group_profile')
358 config.include('rhodecode.apps.user_group_profile')
359 config.include('rhodecode.apps.my_account')
359 config.include('rhodecode.apps.my_account')
360 config.include('rhodecode.apps.gist')
360 config.include('rhodecode.apps.gist')
361
361
362 config.include('rhodecode.apps.svn_support')
362 config.include('rhodecode.apps.svn_support')
363 config.include('rhodecode.apps.ssh_support')
363 config.include('rhodecode.apps.ssh_support')
364 config.include('rhodecode.apps.debug_style')
364 config.include('rhodecode.apps.debug_style')
365
365
366 if load_all:
366 if load_all:
367 config.include('rhodecode.integrations')
367 config.include('rhodecode.integrations')
368
368
369 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
369 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
370 config.add_translation_dirs('rhodecode:i18n/')
370 config.add_translation_dirs('rhodecode:i18n/')
371 settings['default_locale_name'] = settings.get('lang', 'en')
371 settings['default_locale_name'] = settings.get('lang', 'en')
372
372
373 # Add subscribers.
373 # Add subscribers.
374 if load_all:
374 if load_all:
375 config.add_subscriber(scan_repositories_if_enabled,
375 config.add_subscriber(scan_repositories_if_enabled,
376 pyramid.events.ApplicationCreated)
376 pyramid.events.ApplicationCreated)
377 config.add_subscriber(write_metadata_if_needed,
377 config.add_subscriber(write_metadata_if_needed,
378 pyramid.events.ApplicationCreated)
378 pyramid.events.ApplicationCreated)
379 config.add_subscriber(write_usage_data,
379 config.add_subscriber(write_usage_data,
380 pyramid.events.ApplicationCreated)
380 pyramid.events.ApplicationCreated)
381 config.add_subscriber(write_js_routes_if_enabled,
381 config.add_subscriber(write_js_routes_if_enabled,
382 pyramid.events.ApplicationCreated)
382 pyramid.events.ApplicationCreated)
383
383
384 # request custom methods
385 config.add_request_method(
386 'rhodecode.lib.partial_renderer.get_partial_renderer',
387 'get_partial_renderer')
388
389 config.add_request_method(
390 'rhodecode.lib.request_counter.get_request_counter',
391 'request_count')
392
393 # Set the authorization policy.
384 # Set the authorization policy.
394 authz_policy = ACLAuthorizationPolicy()
385 authz_policy = ACLAuthorizationPolicy()
395 config.set_authorization_policy(authz_policy)
386 config.set_authorization_policy(authz_policy)
396
387
397 # Set the default renderer for HTML templates to mako.
388 # Set the default renderer for HTML templates to mako.
398 config.add_mako_renderer('.html')
389 config.add_mako_renderer('.html')
399
390
400 config.add_renderer(
391 config.add_renderer(
401 name='json_ext',
392 name='json_ext',
402 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
393 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
403
394
404 config.add_renderer(
395 config.add_renderer(
405 name='string_html',
396 name='string_html',
406 factory='rhodecode.lib.string_renderer.html')
397 factory='rhodecode.lib.string_renderer.html')
407
398
408 # include RhodeCode plugins
399 # include RhodeCode plugins
409 includes = aslist(settings.get('rhodecode.includes', []))
400 includes = aslist(settings.get('rhodecode.includes', []))
410 for inc in includes:
401 for inc in includes:
411 config.include(inc)
402 config.include(inc)
412
403
413 # custom not found view, if our pyramid app doesn't know how to handle
404 # custom not found view, if our pyramid app doesn't know how to handle
414 # the request pass it to potential VCS handling ap
405 # the request pass it to potential VCS handling ap
415 config.add_notfound_view(not_found_view)
406 config.add_notfound_view(not_found_view)
416 if not settings.get('debugtoolbar.enabled', False):
407 if not settings.get('debugtoolbar.enabled', False):
417 # disabled debugtoolbar handle all exceptions via the error_handlers
408 # disabled debugtoolbar handle all exceptions via the error_handlers
418 config.add_view(error_handler, context=Exception)
409 config.add_view(error_handler, context=Exception)
419
410
420 # all errors including 403/404/50X
411 # all errors including 403/404/50X
421 config.add_view(error_handler, context=HTTPError)
412 config.add_view(error_handler, context=HTTPError)
422
413
423
414
424 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
415 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
425 """
416 """
426 Apply outer WSGI middlewares around the application.
417 Apply outer WSGI middlewares around the application.
427 """
418 """
428 registry = config.registry
419 registry = config.registry
429 settings = registry.settings
420 settings = registry.settings
430
421
431 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
422 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
432 pyramid_app = HttpsFixup(pyramid_app, settings)
423 pyramid_app = HttpsFixup(pyramid_app, settings)
433
424
434 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
425 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
435 pyramid_app, settings)
426 pyramid_app, settings)
436 registry.ae_client = _ae_client
427 registry.ae_client = _ae_client
437
428
438 if settings['gzip_responses']:
429 if settings['gzip_responses']:
439 pyramid_app = make_gzip_middleware(
430 pyramid_app = make_gzip_middleware(
440 pyramid_app, settings, compress_level=1)
431 pyramid_app, settings, compress_level=1)
441
432
442 # this should be the outer most middleware in the wsgi stack since
433 # this should be the outer most middleware in the wsgi stack since
443 # middleware like Routes make database calls
434 # middleware like Routes make database calls
444 def pyramid_app_with_cleanup(environ, start_response):
435 def pyramid_app_with_cleanup(environ, start_response):
445 try:
436 try:
446 return pyramid_app(environ, start_response)
437 return pyramid_app(environ, start_response)
447 finally:
438 finally:
448 # Dispose current database session and rollback uncommitted
439 # Dispose current database session and rollback uncommitted
449 # transactions.
440 # transactions.
450 meta.Session.remove()
441 meta.Session.remove()
451
442
452 # In a single threaded mode server, on non sqlite db we should have
443 # In a single threaded mode server, on non sqlite db we should have
453 # '0 Current Checked out connections' at the end of a request,
444 # '0 Current Checked out connections' at the end of a request,
454 # if not, then something, somewhere is leaving a connection open
445 # if not, then something, somewhere is leaving a connection open
455 pool = meta.Base.metadata.bind.engine.pool
446 pool = meta.Base.metadata.bind.engine.pool
456 log.debug('sa pool status: %s', pool.status())
447 log.debug('sa pool status: %s', pool.status())
457 log.debug('Request processing finalized')
448 log.debug('Request processing finalized')
458
449
459 return pyramid_app_with_cleanup
450 return pyramid_app_with_cleanup
460
451
461
452
462 def sanitize_settings_and_apply_defaults(global_config, settings):
453 def sanitize_settings_and_apply_defaults(global_config, settings):
463 """
454 """
464 Applies settings defaults and does all type conversion.
455 Applies settings defaults and does all type conversion.
465
456
466 We would move all settings parsing and preparation into this place, so that
457 We would move all settings parsing and preparation into this place, so that
467 we have only one place left which deals with this part. The remaining parts
458 we have only one place left which deals with this part. The remaining parts
468 of the application would start to rely fully on well prepared settings.
459 of the application would start to rely fully on well prepared settings.
469
460
470 This piece would later be split up per topic to avoid a big fat monster
461 This piece would later be split up per topic to avoid a big fat monster
471 function.
462 function.
472 """
463 """
473
464
474 global_settings_maker = SettingsMaker(global_config)
465 global_settings_maker = SettingsMaker(global_config)
475 global_settings_maker.make_setting('debug', default=False, parser='bool')
466 global_settings_maker.make_setting('debug', default=False, parser='bool')
476 debug_enabled = asbool(global_config.get('debug'))
467 debug_enabled = asbool(global_config.get('debug'))
477
468
478 settings_maker = SettingsMaker(settings)
469 settings_maker = SettingsMaker(settings)
479
470
480 settings_maker.make_setting(
471 settings_maker.make_setting(
481 'logging.autoconfigure',
472 'logging.autoconfigure',
482 default=False,
473 default=False,
483 parser='bool')
474 parser='bool')
484
475
485 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
476 logging_conf = os.path.join(os.path.dirname(global_config.get('__file__')), 'logging.ini')
486 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
477 settings_maker.enable_logging(logging_conf, level='INFO' if debug_enabled else 'DEBUG')
487
478
488 # Default includes, possible to change as a user
479 # Default includes, possible to change as a user
489 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
480 pyramid_includes = settings_maker.make_setting('pyramid.includes', [], parser='list:newline')
490 log.debug(
481 log.debug(
491 "Using the following pyramid.includes: %s",
482 "Using the following pyramid.includes: %s",
492 pyramid_includes)
483 pyramid_includes)
493
484
494 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
485 settings_maker.make_setting('rhodecode.edition', 'Community Edition')
495 settings_maker.make_setting('rhodecode.edition_id', 'CE')
486 settings_maker.make_setting('rhodecode.edition_id', 'CE')
496
487
497 if 'mako.default_filters' not in settings:
488 if 'mako.default_filters' not in settings:
498 # set custom default filters if we don't have it defined
489 # set custom default filters if we don't have it defined
499 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
490 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
500 settings['mako.default_filters'] = 'h_filter'
491 settings['mako.default_filters'] = 'h_filter'
501
492
502 if 'mako.directories' not in settings:
493 if 'mako.directories' not in settings:
503 mako_directories = settings.setdefault('mako.directories', [
494 mako_directories = settings.setdefault('mako.directories', [
504 # Base templates of the original application
495 # Base templates of the original application
505 'rhodecode:templates',
496 'rhodecode:templates',
506 ])
497 ])
507 log.debug(
498 log.debug(
508 "Using the following Mako template directories: %s",
499 "Using the following Mako template directories: %s",
509 mako_directories)
500 mako_directories)
510
501
511 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
502 # NOTE(marcink): fix redis requirement for schema of connection since 3.X
512 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
503 if 'beaker.session.type' in settings and settings['beaker.session.type'] == 'ext:redis':
513 raw_url = settings['beaker.session.url']
504 raw_url = settings['beaker.session.url']
514 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
505 if not raw_url.startswith(('redis://', 'rediss://', 'unix://')):
515 settings['beaker.session.url'] = 'redis://' + raw_url
506 settings['beaker.session.url'] = 'redis://' + raw_url
516
507
517 settings_maker.make_setting('__file__', global_config.get('__file__'))
508 settings_maker.make_setting('__file__', global_config.get('__file__'))
518
509
519 # TODO: johbo: Re-think this, usually the call to config.include
510 # TODO: johbo: Re-think this, usually the call to config.include
520 # should allow to pass in a prefix.
511 # should allow to pass in a prefix.
521 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
512 settings_maker.make_setting('rhodecode.api.url', '/_admin/api')
522
513
523 # Sanitize generic settings.
514 # Sanitize generic settings.
524 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
515 settings_maker.make_setting('default_encoding', 'UTF-8', parser='list')
525 settings_maker.make_setting('is_test', False, parser='bool')
516 settings_maker.make_setting('is_test', False, parser='bool')
526 settings_maker.make_setting('gzip_responses', False, parser='bool')
517 settings_maker.make_setting('gzip_responses', False, parser='bool')
527
518
528 # statsd
519 # statsd
529 settings_maker.make_setting('statsd.enabled', False, parser='bool')
520 settings_maker.make_setting('statsd.enabled', False, parser='bool')
530 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
521 settings_maker.make_setting('statsd.statsd_host', 'statsd-exporter', parser='string')
531 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
522 settings_maker.make_setting('statsd.statsd_port', 9125, parser='int')
532 settings_maker.make_setting('statsd.statsd_prefix', '')
523 settings_maker.make_setting('statsd.statsd_prefix', '')
533 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
524 settings_maker.make_setting('statsd.statsd_ipv6', False, parser='bool')
534
525
535 settings_maker.make_setting('vcs.svn.compatible_version', '')
526 settings_maker.make_setting('vcs.svn.compatible_version', '')
536 settings_maker.make_setting('vcs.hooks.protocol', 'http')
527 settings_maker.make_setting('vcs.hooks.protocol', 'http')
537 settings_maker.make_setting('vcs.hooks.host', '127.0.0.1')
528 settings_maker.make_setting('vcs.hooks.host', '127.0.0.1')
538 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
529 settings_maker.make_setting('vcs.scm_app_implementation', 'http')
539 settings_maker.make_setting('vcs.server', '')
530 settings_maker.make_setting('vcs.server', '')
540 settings_maker.make_setting('vcs.server.protocol', 'http')
531 settings_maker.make_setting('vcs.server.protocol', 'http')
541 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
532 settings_maker.make_setting('startup.import_repos', 'false', parser='bool')
542 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
533 settings_maker.make_setting('vcs.hooks.direct_calls', 'false', parser='bool')
543 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
534 settings_maker.make_setting('vcs.server.enable', 'true', parser='bool')
544 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
535 settings_maker.make_setting('vcs.start_server', 'false', parser='bool')
545 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
536 settings_maker.make_setting('vcs.backends', 'hg, git, svn', parser='list')
546 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
537 settings_maker.make_setting('vcs.connection_timeout', 3600, parser='int')
547
538
548 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
539 settings_maker.make_setting('vcs.methods.cache', True, parser='bool')
549
540
550 # Support legacy values of vcs.scm_app_implementation. Legacy
541 # Support legacy values of vcs.scm_app_implementation. Legacy
551 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
542 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
552 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
543 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
553 scm_app_impl = settings['vcs.scm_app_implementation']
544 scm_app_impl = settings['vcs.scm_app_implementation']
554 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
545 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
555 settings['vcs.scm_app_implementation'] = 'http'
546 settings['vcs.scm_app_implementation'] = 'http'
556
547
557 settings_maker.make_setting('appenlight', False, parser='bool')
548 settings_maker.make_setting('appenlight', False, parser='bool')
558
549
559 temp_store = tempfile.gettempdir()
550 temp_store = tempfile.gettempdir()
560 default_cache_dir = os.path.join(temp_store, 'rc_cache')
551 default_cache_dir = os.path.join(temp_store, 'rc_cache')
561
552
562 # save default, cache dir, and use it for all backends later.
553 # save default, cache dir, and use it for all backends later.
563 default_cache_dir = settings_maker.make_setting(
554 default_cache_dir = settings_maker.make_setting(
564 'cache_dir',
555 'cache_dir',
565 default=default_cache_dir, default_when_empty=True,
556 default=default_cache_dir, default_when_empty=True,
566 parser='dir:ensured')
557 parser='dir:ensured')
567
558
568 # exception store cache
559 # exception store cache
569 settings_maker.make_setting(
560 settings_maker.make_setting(
570 'exception_tracker.store_path',
561 'exception_tracker.store_path',
571 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
562 default=os.path.join(default_cache_dir, 'exc_store'), default_when_empty=True,
572 parser='dir:ensured'
563 parser='dir:ensured'
573 )
564 )
574
565
575 settings_maker.make_setting(
566 settings_maker.make_setting(
576 'celerybeat-schedule.path',
567 'celerybeat-schedule.path',
577 default=os.path.join(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
568 default=os.path.join(default_cache_dir, 'celerybeat_schedule', 'celerybeat-schedule.db'), default_when_empty=True,
578 parser='file:ensured'
569 parser='file:ensured'
579 )
570 )
580
571
581 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
572 settings_maker.make_setting('exception_tracker.send_email', False, parser='bool')
582 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
573 settings_maker.make_setting('exception_tracker.email_prefix', '[RHODECODE ERROR]', default_when_empty=True)
583
574
584 # cache_general
575 # cache_general
585 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
576 settings_maker.make_setting('rc_cache.cache_general.backend', 'dogpile.cache.rc.file_namespace')
586 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
577 settings_maker.make_setting('rc_cache.cache_general.expiration_time', 60 * 60 * 12, parser='int')
587 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_general.db'))
578 settings_maker.make_setting('rc_cache.cache_general.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_general.db'))
588
579
589 # cache_perms
580 # cache_perms
590 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
581 settings_maker.make_setting('rc_cache.cache_perms.backend', 'dogpile.cache.rc.file_namespace')
591 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
582 settings_maker.make_setting('rc_cache.cache_perms.expiration_time', 60 * 60, parser='int')
592 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_perms.db'))
583 settings_maker.make_setting('rc_cache.cache_perms.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_perms.db'))
593
584
594 # cache_repo
585 # cache_repo
595 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
586 settings_maker.make_setting('rc_cache.cache_repo.backend', 'dogpile.cache.rc.file_namespace')
596 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
587 settings_maker.make_setting('rc_cache.cache_repo.expiration_time', 60 * 60 * 24 * 30, parser='int')
597 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_repo.db'))
588 settings_maker.make_setting('rc_cache.cache_repo.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_repo.db'))
598
589
599 # cache_license
590 # cache_license
600 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
591 settings_maker.make_setting('rc_cache.cache_license.backend', 'dogpile.cache.rc.file_namespace')
601 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
592 settings_maker.make_setting('rc_cache.cache_license.expiration_time', 60 * 5, parser='int')
602 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_license.db'))
593 settings_maker.make_setting('rc_cache.cache_license.arguments.filename', os.path.join(default_cache_dir, 'rhodecode_cache_license.db'))
603
594
604 # cache_repo_longterm memory, 96H
595 # cache_repo_longterm memory, 96H
605 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
596 settings_maker.make_setting('rc_cache.cache_repo_longterm.backend', 'dogpile.cache.rc.memory_lru')
606 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
597 settings_maker.make_setting('rc_cache.cache_repo_longterm.expiration_time', 345600, parser='int')
607 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
598 settings_maker.make_setting('rc_cache.cache_repo_longterm.max_size', 10000, parser='int')
608
599
609 # sql_cache_short
600 # sql_cache_short
610 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
601 settings_maker.make_setting('rc_cache.sql_cache_short.backend', 'dogpile.cache.rc.memory_lru')
611 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
602 settings_maker.make_setting('rc_cache.sql_cache_short.expiration_time', 30, parser='int')
612 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
603 settings_maker.make_setting('rc_cache.sql_cache_short.max_size', 10000, parser='int')
613
604
614 settings_maker.env_expand()
605 settings_maker.env_expand()
615
606
616 # configure instance id
607 # configure instance id
617 config_utils.set_instance_id(settings)
608 config_utils.set_instance_id(settings)
618
609
619 return settings
610 return settings
@@ -1,635 +1,611 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 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
38 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
39 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
41 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
42 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
43 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
44 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
45 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
46 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
48 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
49
51 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
52
51
53
52
54 def _filter_proxy(ip):
53 def _filter_proxy(ip):
55 """
54 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
55 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
56 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
57 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
58 We only care about the first IP which came from the org. client.
60
59
61 :param ip: ip string from headers
60 :param ip: ip string from headers
62 """
61 """
63 if ',' in ip:
62 if ',' in ip:
64 _ips = ip.split(',')
63 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
64 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
65 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
66 return _first_ip
68 return ip
67 return ip
69
68
70
69
71 def _filter_port(ip):
70 def _filter_port(ip):
72 """
71 """
73 Removes a port from ip, there are 4 main cases to handle here.
72 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
73 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
74 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
75 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
76 - ipv6+port eg. [::1]:8080
78
77
79 :param ip:
78 :param ip:
80 """
79 """
81 def is_ipv6(ip_addr):
80 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
81 if hasattr(socket, 'inet_pton'):
83 try:
82 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
83 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
84 except socket.error:
86 return False
85 return False
87 else:
86 else:
88 # fallback to ipaddress
87 # fallback to ipaddress
89 try:
88 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
89 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
90 except Exception:
92 return False
91 return False
93 return True
92 return True
94
93
95 if ':' not in ip: # must be ipv4 pure ip
94 if ':' not in ip: # must be ipv4 pure ip
96 return ip
95 return ip
97
96
98 if '[' in ip and ']' in ip: # ipv6 with port
97 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
98 return ip.split(']')[0][1:].lower()
100
99
101 # must be ipv6 or ipv4 with port
100 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
101 if is_ipv6(ip):
103 return ip
102 return ip
104 else:
103 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
104 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
105 return ip
107
106
108
107
109 def get_ip_addr(environ):
108 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
109 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
110 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
111 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
112 _filters = lambda x: _filter_port(_filter_proxy(x))
114
113
115 ip = environ.get(proxy_key)
114 ip = environ.get(proxy_key)
116 if ip:
115 if ip:
117 return _filters(ip)
116 return _filters(ip)
118
117
119 ip = environ.get(proxy_key2)
118 ip = environ.get(proxy_key2)
120 if ip:
119 if ip:
121 return _filters(ip)
120 return _filters(ip)
122
121
123 ip = environ.get(def_key, '0.0.0.0')
122 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
123 return _filters(ip)
125
124
126
125
127 def get_server_ip_addr(environ, log_errors=True):
126 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
127 hostname = environ.get('SERVER_NAME')
129 try:
128 try:
130 return socket.gethostbyname(hostname)
129 return socket.gethostbyname(hostname)
131 except Exception as e:
130 except Exception as e:
132 if log_errors:
131 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
132 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
133 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
134 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
135 return hostname
137
136
138
137
139 def get_server_port(environ):
138 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
139 return environ.get('SERVER_PORT')
141
140
142
141
143 def get_access_path(environ):
142 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
143 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
144 org_req = environ.get('pylons.original_request')
146 if org_req:
145 if org_req:
147 path = org_req.environ.get('PATH_INFO')
146 path = org_req.environ.get('PATH_INFO')
148 return path
147 return path
149
148
150
149
151 def get_user_agent(environ):
150 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
151 return environ.get('HTTP_USER_AGENT')
153
152
154
153
155 def vcs_operation_context(
154 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
155 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
156 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
157 """
159 Generate the context for a vcs operation, e.g. push or pull.
158 Generate the context for a vcs operation, e.g. push or pull.
160
159
161 This context is passed over the layers so that hooks triggered by the
160 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
161 vcs operation know details like the user, the user's IP address etc.
163
162
164 :param check_locking: Allows to switch of the computation of the locking
163 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
164 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
165 able to disable this for certain operations.
167
166
168 """
167 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
168 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
169 make_lock = None
171 locked_by = [None, None, None]
170 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
171 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
172 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
173 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
174 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
175 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
176 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
177 action, user.user_id)
179 user_id = user.user_id
178 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
179 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
180 ui_settings = settings_model.get_ui_settings()
182
181
183 # NOTE(marcink): This should be also in sync with
182 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
183 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
184 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
185 repo_store = ''
187 if store:
186 if store:
188 repo_store = store[0].value
187 repo_store = store[0].value
189
188
190 scm_data = {
189 scm_data = {
191 'ip': get_ip_addr(environ),
190 'ip': get_ip_addr(environ),
192 'username': username,
191 'username': username,
193 'user_id': user_id,
192 'user_id': user_id,
194 'action': action,
193 'action': action,
195 'repository': repo_name,
194 'repository': repo_name,
196 'scm': scm,
195 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
196 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
197 'repo_store': repo_store,
199 'make_lock': make_lock,
198 'make_lock': make_lock,
200 'locked_by': locked_by,
199 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
200 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
201 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
202 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
203 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
204 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
205 'check_branch_perms': check_branch_perms,
207 }
206 }
208 return scm_data
207 return scm_data
209
208
210
209
211 class BasicAuth(AuthBasicAuthenticator):
210 class BasicAuth(AuthBasicAuthenticator):
212
211
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
212 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
213 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 self.realm = realm
214 self.realm = realm
216 self.rc_realm = rc_realm
215 self.rc_realm = rc_realm
217 self.initial_call = initial_call_detection
216 self.initial_call = initial_call_detection
218 self.authfunc = authfunc
217 self.authfunc = authfunc
219 self.registry = registry
218 self.registry = registry
220 self.acl_repo_name = acl_repo_name
219 self.acl_repo_name = acl_repo_name
221 self._rc_auth_http_code = auth_http_code
220 self._rc_auth_http_code = auth_http_code
222
221
223 def _get_response_from_code(self, http_code):
222 def _get_response_from_code(self, http_code):
224 try:
223 try:
225 return get_exception(safe_int(http_code))
224 return get_exception(safe_int(http_code))
226 except Exception:
225 except Exception:
227 log.exception('Failed to fetch response for code %s', http_code)
226 log.exception('Failed to fetch response for code %s', http_code)
228 return HTTPForbidden
227 return HTTPForbidden
229
228
230 def get_rc_realm(self):
229 def get_rc_realm(self):
231 return safe_str(self.rc_realm)
230 return safe_str(self.rc_realm)
232
231
233 def build_authentication(self):
232 def build_authentication(self):
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
233 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 if self._rc_auth_http_code and not self.initial_call:
234 if self._rc_auth_http_code and not self.initial_call:
236 # return alternative HTTP code if alternative http return code
235 # return alternative HTTP code if alternative http return code
237 # is specified in RhodeCode config, but ONLY if it's not the
236 # is specified in RhodeCode config, but ONLY if it's not the
238 # FIRST call
237 # FIRST call
239 custom_response_klass = self._get_response_from_code(
238 custom_response_klass = self._get_response_from_code(
240 self._rc_auth_http_code)
239 self._rc_auth_http_code)
241 return custom_response_klass(headers=head)
240 return custom_response_klass(headers=head)
242 return HTTPUnauthorized(headers=head)
241 return HTTPUnauthorized(headers=head)
243
242
244 def authenticate(self, environ):
243 def authenticate(self, environ):
245 authorization = AUTHORIZATION(environ)
244 authorization = AUTHORIZATION(environ)
246 if not authorization:
245 if not authorization:
247 return self.build_authentication()
246 return self.build_authentication()
248 (authmeth, auth) = authorization.split(' ', 1)
247 (authmeth, auth) = authorization.split(' ', 1)
249 if 'basic' != authmeth.lower():
248 if 'basic' != authmeth.lower():
250 return self.build_authentication()
249 return self.build_authentication()
251 auth = auth.strip().decode('base64')
250 auth = auth.strip().decode('base64')
252 _parts = auth.split(':', 1)
251 _parts = auth.split(':', 1)
253 if len(_parts) == 2:
252 if len(_parts) == 2:
254 username, password = _parts
253 username, password = _parts
255 auth_data = self.authfunc(
254 auth_data = self.authfunc(
256 username, password, environ, VCS_TYPE,
255 username, password, environ, VCS_TYPE,
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
256 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 if auth_data:
257 if auth_data:
259 return {'username': username, 'auth_data': auth_data}
258 return {'username': username, 'auth_data': auth_data}
260 if username and password:
259 if username and password:
261 # we mark that we actually executed authentication once, at
260 # we mark that we actually executed authentication once, at
262 # that point we can use the alternative auth code
261 # that point we can use the alternative auth code
263 self.initial_call = False
262 self.initial_call = False
264
263
265 return self.build_authentication()
264 return self.build_authentication()
266
265
267 __call__ = authenticate
266 __call__ = authenticate
268
267
269
268
270 def calculate_version_hash(config):
269 def calculate_version_hash(config):
271 return sha1(
270 return sha1(
272 config.get('beaker.session.secret', '') +
271 config.get('beaker.session.secret', '') +
273 rhodecode.__version__)[:8]
272 rhodecode.__version__)[:8]
274
273
275
274
276 def get_current_lang(request):
275 def get_current_lang(request):
277 # NOTE(marcink): remove after pyramid move
276 # NOTE(marcink): remove after pyramid move
278 try:
277 try:
279 return translation.get_lang()[0]
278 return translation.get_lang()[0]
280 except:
279 except:
281 pass
280 pass
282
281
283 return getattr(request, '_LOCALE_', request.locale_name)
282 return getattr(request, '_LOCALE_', request.locale_name)
284
283
285
284
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
285 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 """
286 """
288 Attach variables into template context called `c`.
287 Attach variables into template context called `c`.
289 """
288 """
290 config = request.registry.settings
289 config = request.registry.settings
291
290
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
291 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 context.rc_config = rc_config
292 context.rc_config = rc_config
294 context.rhodecode_version = rhodecode.__version__
293 context.rhodecode_version = rhodecode.__version__
295 context.rhodecode_edition = config.get('rhodecode.edition')
294 context.rhodecode_edition = config.get('rhodecode.edition')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
295 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
297 # unique secret + version does not leak the version but keep consistency
296 # unique secret + version does not leak the version but keep consistency
298 context.rhodecode_version_hash = calculate_version_hash(config)
297 context.rhodecode_version_hash = calculate_version_hash(config)
299
298
300 # Default language set for the incoming request
299 # Default language set for the incoming request
301 context.language = get_current_lang(request)
300 context.language = get_current_lang(request)
302
301
303 # Visual options
302 # Visual options
304 context.visual = AttributeDict({})
303 context.visual = AttributeDict({})
305
304
306 # DB stored Visual Items
305 # DB stored Visual Items
307 context.visual.show_public_icon = str2bool(
306 context.visual.show_public_icon = str2bool(
308 rc_config.get('rhodecode_show_public_icon'))
307 rc_config.get('rhodecode_show_public_icon'))
309 context.visual.show_private_icon = str2bool(
308 context.visual.show_private_icon = str2bool(
310 rc_config.get('rhodecode_show_private_icon'))
309 rc_config.get('rhodecode_show_private_icon'))
311 context.visual.stylify_metatags = str2bool(
310 context.visual.stylify_metatags = str2bool(
312 rc_config.get('rhodecode_stylify_metatags'))
311 rc_config.get('rhodecode_stylify_metatags'))
313 context.visual.dashboard_items = safe_int(
312 context.visual.dashboard_items = safe_int(
314 rc_config.get('rhodecode_dashboard_items', 100))
313 rc_config.get('rhodecode_dashboard_items', 100))
315 context.visual.admin_grid_items = safe_int(
314 context.visual.admin_grid_items = safe_int(
316 rc_config.get('rhodecode_admin_grid_items', 100))
315 rc_config.get('rhodecode_admin_grid_items', 100))
317 context.visual.show_revision_number = str2bool(
316 context.visual.show_revision_number = str2bool(
318 rc_config.get('rhodecode_show_revision_number', True))
317 rc_config.get('rhodecode_show_revision_number', True))
319 context.visual.show_sha_length = safe_int(
318 context.visual.show_sha_length = safe_int(
320 rc_config.get('rhodecode_show_sha_length', 100))
319 rc_config.get('rhodecode_show_sha_length', 100))
321 context.visual.repository_fields = str2bool(
320 context.visual.repository_fields = str2bool(
322 rc_config.get('rhodecode_repository_fields'))
321 rc_config.get('rhodecode_repository_fields'))
323 context.visual.show_version = str2bool(
322 context.visual.show_version = str2bool(
324 rc_config.get('rhodecode_show_version'))
323 rc_config.get('rhodecode_show_version'))
325 context.visual.use_gravatar = str2bool(
324 context.visual.use_gravatar = str2bool(
326 rc_config.get('rhodecode_use_gravatar'))
325 rc_config.get('rhodecode_use_gravatar'))
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
326 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
328 context.visual.default_renderer = rc_config.get(
327 context.visual.default_renderer = rc_config.get(
329 'rhodecode_markup_renderer', 'rst')
328 'rhodecode_markup_renderer', 'rst')
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
329 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
331 context.visual.rhodecode_support_url = \
330 context.visual.rhodecode_support_url = \
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
331 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
333
332
334 context.visual.affected_files_cut_off = 60
333 context.visual.affected_files_cut_off = 60
335
334
336 context.pre_code = rc_config.get('rhodecode_pre_code')
335 context.pre_code = rc_config.get('rhodecode_pre_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
336 context.post_code = rc_config.get('rhodecode_post_code')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
337 context.rhodecode_name = rc_config.get('rhodecode_title')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
338 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
340 # if we have specified default_encoding in the request, it has more
339 # if we have specified default_encoding in the request, it has more
341 # priority
340 # priority
342 if request.GET.get('default_encoding'):
341 if request.GET.get('default_encoding'):
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
342 context.default_encodings.insert(0, request.GET.get('default_encoding'))
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
343 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
345 context.clone_uri_id_tmpl = rc_config.get('rhodecode_clone_uri_id_tmpl')
344 context.clone_uri_id_tmpl = rc_config.get('rhodecode_clone_uri_id_tmpl')
346 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
347
346
348 # INI stored
347 # INI stored
349 context.labs_active = str2bool(
348 context.labs_active = str2bool(
350 config.get('labs_settings_active', 'false'))
349 config.get('labs_settings_active', 'false'))
351 context.ssh_enabled = str2bool(
350 context.ssh_enabled = str2bool(
352 config.get('ssh.generate_authorized_keyfile', 'false'))
351 config.get('ssh.generate_authorized_keyfile', 'false'))
353 context.ssh_key_generator_enabled = str2bool(
352 context.ssh_key_generator_enabled = str2bool(
354 config.get('ssh.enable_ui_key_generator', 'true'))
353 config.get('ssh.enable_ui_key_generator', 'true'))
355
354
356 context.visual.allow_repo_location_change = str2bool(
355 context.visual.allow_repo_location_change = str2bool(
357 config.get('allow_repo_location_change', True))
356 config.get('allow_repo_location_change', True))
358 context.visual.allow_custom_hooks_settings = str2bool(
357 context.visual.allow_custom_hooks_settings = str2bool(
359 config.get('allow_custom_hooks_settings', True))
358 config.get('allow_custom_hooks_settings', True))
360 context.debug_style = str2bool(config.get('debug_style', False))
359 context.debug_style = str2bool(config.get('debug_style', False))
361
360
362 context.rhodecode_instanceid = config.get('instance_id')
361 context.rhodecode_instanceid = config.get('instance_id')
363
362
364 context.visual.cut_off_limit_diff = safe_int(
363 context.visual.cut_off_limit_diff = safe_int(
365 config.get('cut_off_limit_diff'))
364 config.get('cut_off_limit_diff'))
366 context.visual.cut_off_limit_file = safe_int(
365 context.visual.cut_off_limit_file = safe_int(
367 config.get('cut_off_limit_file'))
366 config.get('cut_off_limit_file'))
368
367
369 context.license = AttributeDict({})
368 context.license = AttributeDict({})
370 context.license.hide_license_info = str2bool(
369 context.license.hide_license_info = str2bool(
371 config.get('license.hide_license_info', False))
370 config.get('license.hide_license_info', False))
372
371
373 # AppEnlight
372 # AppEnlight
374 context.appenlight_enabled = config.get('appenlight', False)
373 context.appenlight_enabled = config.get('appenlight', False)
375 context.appenlight_api_public_key = config.get(
374 context.appenlight_api_public_key = config.get(
376 'appenlight.api_public_key', '')
375 'appenlight.api_public_key', '')
377 context.appenlight_server_url = config.get('appenlight.server_url', '')
376 context.appenlight_server_url = config.get('appenlight.server_url', '')
378
377
379 diffmode = {
378 diffmode = {
380 "unified": "unified",
379 "unified": "unified",
381 "sideside": "sideside"
380 "sideside": "sideside"
382 }.get(request.GET.get('diffmode'))
381 }.get(request.GET.get('diffmode'))
383
382
384 if is_api is not None:
383 if is_api is not None:
385 is_api = hasattr(request, 'rpc_user')
384 is_api = hasattr(request, 'rpc_user')
386 session_attrs = {
385 session_attrs = {
387 # defaults
386 # defaults
388 "clone_url_format": "http",
387 "clone_url_format": "http",
389 "diffmode": "sideside",
388 "diffmode": "sideside",
390 "license_fingerprint": request.session.get('license_fingerprint')
389 "license_fingerprint": request.session.get('license_fingerprint')
391 }
390 }
392
391
393 if not is_api:
392 if not is_api:
394 # don't access pyramid session for API calls
393 # don't access pyramid session for API calls
395 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
396 request.session['rc_user_session_attr.diffmode'] = diffmode
395 request.session['rc_user_session_attr.diffmode'] = diffmode
397
396
398 # session settings per user
397 # session settings per user
399
398
400 for k, v in request.session.items():
399 for k, v in request.session.items():
401 pref = 'rc_user_session_attr.'
400 pref = 'rc_user_session_attr.'
402 if k and k.startswith(pref):
401 if k and k.startswith(pref):
403 k = k[len(pref):]
402 k = k[len(pref):]
404 session_attrs[k] = v
403 session_attrs[k] = v
405
404
406 context.user_session_attrs = session_attrs
405 context.user_session_attrs = session_attrs
407
406
408 # JS template context
407 # JS template context
409 context.template_context = {
408 context.template_context = {
410 'repo_name': None,
409 'repo_name': None,
411 'repo_type': None,
410 'repo_type': None,
412 'repo_landing_commit': None,
411 'repo_landing_commit': None,
413 'rhodecode_user': {
412 'rhodecode_user': {
414 'username': None,
413 'username': None,
415 'email': None,
414 'email': None,
416 'notification_status': False
415 'notification_status': False
417 },
416 },
418 'session_attrs': session_attrs,
417 'session_attrs': session_attrs,
419 'visual': {
418 'visual': {
420 'default_renderer': None
419 'default_renderer': None
421 },
420 },
422 'commit_data': {
421 'commit_data': {
423 'commit_id': None
422 'commit_id': None
424 },
423 },
425 'pull_request_data': {'pull_request_id': None},
424 'pull_request_data': {'pull_request_id': None},
426 'timeago': {
425 'timeago': {
427 'refresh_time': 120 * 1000,
426 'refresh_time': 120 * 1000,
428 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
429 },
428 },
430 'pyramid_dispatch': {
429 'pyramid_dispatch': {
431
430
432 },
431 },
433 'extra': {'plugins': {}}
432 'extra': {'plugins': {}}
434 }
433 }
435 # END CONFIG VARS
434 # END CONFIG VARS
436 if is_api:
435 if is_api:
437 csrf_token = None
436 csrf_token = None
438 else:
437 else:
439 csrf_token = auth.get_csrf_token(session=request.session)
438 csrf_token = auth.get_csrf_token(session=request.session)
440
439
441 context.csrf_token = csrf_token
440 context.csrf_token = csrf_token
442 context.backends = rhodecode.BACKENDS.keys()
441 context.backends = rhodecode.BACKENDS.keys()
443
442
444 unread_count = 0
443 unread_count = 0
445 user_bookmark_list = []
444 user_bookmark_list = []
446 if user_id:
445 if user_id:
447 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
448 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
449 context.unread_notifications = unread_count
448 context.unread_notifications = unread_count
450 context.bookmark_items = user_bookmark_list
449 context.bookmark_items = user_bookmark_list
451
450
452 # web case
451 # web case
453 if hasattr(request, 'user'):
452 if hasattr(request, 'user'):
454 context.auth_user = request.user
453 context.auth_user = request.user
455 context.rhodecode_user = request.user
454 context.rhodecode_user = request.user
456
455
457 # api case
456 # api case
458 if hasattr(request, 'rpc_user'):
457 if hasattr(request, 'rpc_user'):
459 context.auth_user = request.rpc_user
458 context.auth_user = request.rpc_user
460 context.rhodecode_user = request.rpc_user
459 context.rhodecode_user = request.rpc_user
461
460
462 # attach the whole call context to the request
461 # attach the whole call context to the request
463 # use set_call_context method if request has it
462 request.set_call_context(context)
464 # sometimes in Celery context requests is "different"
465 if hasattr(request, 'set_call_context'):
466 request.set_call_context(context)
467 else:
468 request.call_context = context
469
463
470
464
471 def get_auth_user(request):
465 def get_auth_user(request):
472 environ = request.environ
466 environ = request.environ
473 session = request.session
467 session = request.session
474
468
475 ip_addr = get_ip_addr(environ)
469 ip_addr = get_ip_addr(environ)
476
470
477 # make sure that we update permissions each time we call controller
471 # make sure that we update permissions each time we call controller
478 _auth_token = (
472 _auth_token = (
479 # ?auth_token=XXX
473 # ?auth_token=XXX
480 request.GET.get('auth_token', '')
474 request.GET.get('auth_token', '')
481 # ?api_key=XXX !LEGACY
475 # ?api_key=XXX !LEGACY
482 or request.GET.get('api_key', '')
476 or request.GET.get('api_key', '')
483 # or headers....
477 # or headers....
484 or request.headers.get('X-Rc-Auth-Token', '')
478 or request.headers.get('X-Rc-Auth-Token', '')
485 )
479 )
486 if not _auth_token and request.matchdict:
480 if not _auth_token and request.matchdict:
487 url_auth_token = request.matchdict.get('_auth_token')
481 url_auth_token = request.matchdict.get('_auth_token')
488 _auth_token = url_auth_token
482 _auth_token = url_auth_token
489 if _auth_token:
483 if _auth_token:
490 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
484 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
491
485
492 if _auth_token:
486 if _auth_token:
493 # when using API_KEY we assume user exists, and
487 # when using API_KEY we assume user exists, and
494 # doesn't need auth based on cookies.
488 # doesn't need auth based on cookies.
495 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
489 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
496 authenticated = False
490 authenticated = False
497 else:
491 else:
498 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
492 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
499 try:
493 try:
500 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
494 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
501 ip_addr=ip_addr)
495 ip_addr=ip_addr)
502 except UserCreationError as e:
496 except UserCreationError as e:
503 h.flash(e, 'error')
497 h.flash(e, 'error')
504 # container auth or other auth functions that create users
498 # container auth or other auth functions that create users
505 # on the fly can throw this exception signaling that there's
499 # on the fly can throw this exception signaling that there's
506 # issue with user creation, explanation should be provided
500 # issue with user creation, explanation should be provided
507 # in Exception itself. We then create a simple blank
501 # in Exception itself. We then create a simple blank
508 # AuthUser
502 # AuthUser
509 auth_user = AuthUser(ip_addr=ip_addr)
503 auth_user = AuthUser(ip_addr=ip_addr)
510
504
511 # in case someone changes a password for user it triggers session
505 # in case someone changes a password for user it triggers session
512 # flush and forces a re-login
506 # flush and forces a re-login
513 if password_changed(auth_user, session):
507 if password_changed(auth_user, session):
514 session.invalidate()
508 session.invalidate()
515 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
509 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
516 auth_user = AuthUser(ip_addr=ip_addr)
510 auth_user = AuthUser(ip_addr=ip_addr)
517
511
518 authenticated = cookie_store.get('is_authenticated')
512 authenticated = cookie_store.get('is_authenticated')
519
513
520 if not auth_user.is_authenticated and auth_user.is_user_object:
514 if not auth_user.is_authenticated and auth_user.is_user_object:
521 # user is not authenticated and not empty
515 # user is not authenticated and not empty
522 auth_user.set_authenticated(authenticated)
516 auth_user.set_authenticated(authenticated)
523
517
524 return auth_user, _auth_token
518 return auth_user, _auth_token
525
519
526
520
527 def h_filter(s):
521 def h_filter(s):
528 """
522 """
529 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
523 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
530 we wrap this with additional functionality that converts None to empty
524 we wrap this with additional functionality that converts None to empty
531 strings
525 strings
532 """
526 """
533 if s is None:
527 if s is None:
534 return markupsafe.Markup()
528 return markupsafe.Markup()
535 return markupsafe.escape(s)
529 return markupsafe.escape(s)
536
530
537
531
538 def add_events_routes(config):
532 def add_events_routes(config):
539 """
533 """
540 Adds routing that can be used in events. Because some events are triggered
534 Adds routing that can be used in events. Because some events are triggered
541 outside of pyramid context, we need to bootstrap request with some
535 outside of pyramid context, we need to bootstrap request with some
542 routing registered
536 routing registered
543 """
537 """
544
538
545 from rhodecode.apps._base import ADMIN_PREFIX
539 from rhodecode.apps._base import ADMIN_PREFIX
546
540
547 config.add_route(name='home', pattern='/')
541 config.add_route(name='home', pattern='/')
548 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
542 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
549 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
543 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
550
544
551 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
545 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
552 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
546 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
553 config.add_route(name='repo_summary', pattern='/{repo_name}')
547 config.add_route(name='repo_summary', pattern='/{repo_name}')
554 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
548 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
555 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
549 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
556
550
557 config.add_route(name='pullrequest_show',
551 config.add_route(name='pullrequest_show',
558 pattern='/{repo_name}/pull-request/{pull_request_id}')
552 pattern='/{repo_name}/pull-request/{pull_request_id}')
559 config.add_route(name='pull_requests_global',
553 config.add_route(name='pull_requests_global',
560 pattern='/pull-request/{pull_request_id}')
554 pattern='/pull-request/{pull_request_id}')
561
555
562 config.add_route(name='repo_commit',
556 config.add_route(name='repo_commit',
563 pattern='/{repo_name}/changeset/{commit_id}')
557 pattern='/{repo_name}/changeset/{commit_id}')
564 config.add_route(name='repo_files',
558 config.add_route(name='repo_files',
565 pattern='/{repo_name}/files/{commit_id}/{f_path}')
559 pattern='/{repo_name}/files/{commit_id}/{f_path}')
566
560
567 config.add_route(name='hovercard_user',
561 config.add_route(name='hovercard_user',
568 pattern='/_hovercard/user/{user_id}')
562 pattern='/_hovercard/user/{user_id}')
569
563
570 config.add_route(name='hovercard_user_group',
564 config.add_route(name='hovercard_user_group',
571 pattern='/_hovercard/user_group/{user_group_id}')
565 pattern='/_hovercard/user_group/{user_group_id}')
572
566
573 config.add_route(name='hovercard_pull_request',
567 config.add_route(name='hovercard_pull_request',
574 pattern='/_hovercard/pull_request/{pull_request_id}')
568 pattern='/_hovercard/pull_request/{pull_request_id}')
575
569
576 config.add_route(name='hovercard_repo_commit',
570 config.add_route(name='hovercard_repo_commit',
577 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
571 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
578
572
579
573
580 def bootstrap_config(request):
574 def bootstrap_config(request, registry_name='RcTestRegistry'):
581 import pyramid.testing
575 import pyramid.testing
582 registry = pyramid.testing.Registry('RcTestRegistry')
576 registry = pyramid.testing.Registry(registry_name)
583
577
584 config = pyramid.testing.setUp(registry=registry, request=request)
578 config = pyramid.testing.setUp(registry=registry, request=request)
585
579
586 # allow pyramid lookup in testing
580 # allow pyramid lookup in testing
587 config.include('pyramid_mako')
581 config.include('pyramid_mako')
588 config.include('rhodecode.lib.rc_beaker')
582 config.include('rhodecode.lib.rc_beaker')
589 config.include('rhodecode.lib.rc_cache')
583 config.include('rhodecode.lib.rc_cache')
590
584
591 add_events_routes(config)
585 add_events_routes(config)
592
586
593 return config
587 return config
594
588
595
589
596 def bootstrap_request(**kwargs):
590 def bootstrap_request(**kwargs):
597 import pyramid.testing
591 """
592 Returns a thin version of Request Object that is used in non-web context like testing/celery
593 """
598
594
599 class TestRequest(pyramid.testing.DummyRequest):
595 import pyramid.testing
596 from rhodecode.lib.request import ThinRequest as _ThinRequest
597
598 class ThinRequest(_ThinRequest):
600 application_url = kwargs.pop('application_url', 'http://example.com')
599 application_url = kwargs.pop('application_url', 'http://example.com')
601 host = kwargs.pop('host', 'example.com:80')
600 host = kwargs.pop('host', 'example.com:80')
602 domain = kwargs.pop('domain', 'example.com')
601 domain = kwargs.pop('domain', 'example.com')
603
602
604 def translate(self, msg):
603 class ThinSession(pyramid.testing.DummySession):
605 return msg
606
607 def plularize(self, singular, plural, n):
608 return singular
609
610 def get_partial_renderer(self, tmpl_name):
611
612 from rhodecode.lib.partial_renderer import get_partial_renderer
613 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
614
615 _call_context = TemplateArgs()
616 _call_context.visual = TemplateArgs()
617 _call_context.visual.show_sha_length = 12
618 _call_context.visual.show_revision_number = True
619
620 @property
621 def call_context(self):
622 return self._call_context
623
624 def set_call_context(self, new_context):
625 self._call_context = new_context
626
627 class TestDummySession(pyramid.testing.DummySession):
628 def save(*arg, **kw):
604 def save(*arg, **kw):
629 pass
605 pass
630
606
631 request = TestRequest(**kwargs)
607 request = ThinRequest(**kwargs)
632 request.session = TestDummySession()
608 request.session = ThinSession()
633
609
634 return request
610 return request
635
611
@@ -1,329 +1,327 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 Celery loader, run with::
21 Celery loader, run with::
22
22
23 celery worker \
23 celery worker \
24 --task-events \
24 --task-events \
25 --beat \
25 --beat \
26 --autoscale=20,2 \
26 --autoscale=20,2 \
27 --max-tasks-per-child 1 \
27 --max-tasks-per-child 1 \
28 --app rhodecode.lib.celerylib.loader \
28 --app rhodecode.lib.celerylib.loader \
29 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
29 --scheduler rhodecode.lib.celerylib.scheduler.RcScheduler \
30 --loglevel DEBUG --ini=.dev/dev.ini
30 --loglevel DEBUG --ini=.dev/dev.ini
31 """
31 """
32 import os
32 import os
33 import logging
33 import logging
34 import importlib
34 import importlib
35
35
36 from celery import Celery
36 from celery import Celery
37 from celery import signals
37 from celery import signals
38 from celery import Task
38 from celery import Task
39 from celery import exceptions # pragma: no cover
39 from celery import exceptions # pragma: no cover
40 from kombu.serialization import register
40 from kombu.serialization import register
41 from pyramid.threadlocal import get_current_request
41 from pyramid.threadlocal import get_current_request
42
42
43 import rhodecode
43 import rhodecode
44
44
45 from rhodecode.lib.auth import AuthUser
45 from rhodecode.lib.auth import AuthUser
46 from rhodecode.lib.celerylib.utils import parse_ini_vars, ping_db
46 from rhodecode.lib.celerylib.utils import parse_ini_vars, ping_db
47 from rhodecode.lib.ext_json import json
47 from rhodecode.lib.ext_json import json
48 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging, prepare_request
48 from rhodecode.lib.pyramid_utils import bootstrap, setup_logging
49 from rhodecode.lib.utils2 import str2bool
49 from rhodecode.lib.utils2 import str2bool
50 from rhodecode.model import meta
50 from rhodecode.model import meta
51
51
52
52
53 register('json_ext', json.dumps, json.loads,
53 register('json_ext', json.dumps, json.loads,
54 content_type='application/x-json-ext',
54 content_type='application/x-json-ext',
55 content_encoding='utf-8')
55 content_encoding='utf-8')
56
56
57 log = logging.getLogger('celery.rhodecode.loader')
57 log = logging.getLogger('celery.rhodecode.loader')
58
58
59
59
60 def add_preload_arguments(parser):
60 def add_preload_arguments(parser):
61 parser.add_argument(
61 parser.add_argument(
62 '--ini', default=None,
62 '--ini', default=None,
63 help='Path to ini configuration file.'
63 help='Path to ini configuration file.'
64 )
64 )
65 parser.add_argument(
65 parser.add_argument(
66 '--ini-var', default=None,
66 '--ini-var', default=None,
67 help='Comma separated list of key=value to pass to ini.'
67 help='Comma separated list of key=value to pass to ini.'
68 )
68 )
69
69
70
70
71 def get_logger(obj):
71 def get_logger(obj):
72 custom_log = logging.getLogger(
72 custom_log = logging.getLogger(
73 'rhodecode.task.{}'.format(obj.__class__.__name__))
73 'rhodecode.task.{}'.format(obj.__class__.__name__))
74
74
75 if rhodecode.CELERY_ENABLED:
75 if rhodecode.CELERY_ENABLED:
76 try:
76 try:
77 custom_log = obj.get_logger()
77 custom_log = obj.get_logger()
78 except Exception:
78 except Exception:
79 pass
79 pass
80
80
81 return custom_log
81 return custom_log
82
82
83
83
84 imports = ['rhodecode.lib.celerylib.tasks']
84 imports = ['rhodecode.lib.celerylib.tasks']
85
85
86 try:
86 try:
87 # try if we have EE tasks available
87 # try if we have EE tasks available
88 importlib.import_module('rc_ee')
88 importlib.import_module('rc_ee')
89 imports.append('rc_ee.lib.celerylib.tasks')
89 imports.append('rc_ee.lib.celerylib.tasks')
90 except ImportError:
90 except ImportError:
91 pass
91 pass
92
92
93
93
94 base_celery_config = {
94 base_celery_config = {
95 'result_backend': 'rpc://',
95 'result_backend': 'rpc://',
96 'result_expires': 60 * 60 * 24,
96 'result_expires': 60 * 60 * 24,
97 'result_persistent': True,
97 'result_persistent': True,
98 'imports': imports,
98 'imports': imports,
99 'worker_max_tasks_per_child': 100,
99 'worker_max_tasks_per_child': 100,
100 'accept_content': ['json_ext'],
100 'accept_content': ['json_ext'],
101 'task_serializer': 'json_ext',
101 'task_serializer': 'json_ext',
102 'result_serializer': 'json_ext',
102 'result_serializer': 'json_ext',
103 'worker_hijack_root_logger': False,
103 'worker_hijack_root_logger': False,
104 'database_table_names': {
104 'database_table_names': {
105 'task': 'beat_taskmeta',
105 'task': 'beat_taskmeta',
106 'group': 'beat_groupmeta',
106 'group': 'beat_groupmeta',
107 }
107 }
108 }
108 }
109 # init main celery app
109 # init main celery app
110 celery_app = Celery()
110 celery_app = Celery()
111 celery_app.user_options['preload'].add(add_preload_arguments)
111 celery_app.user_options['preload'].add(add_preload_arguments)
112 ini_file_glob = None
112 ini_file_glob = None
113
113
114
114
115 @signals.setup_logging.connect
115 @signals.setup_logging.connect
116 def setup_logging_callback(**kwargs):
116 def setup_logging_callback(**kwargs):
117 setup_logging(ini_file_glob)
117 setup_logging(ini_file_glob)
118
118
119
119
120 @signals.user_preload_options.connect
120 @signals.user_preload_options.connect
121 def on_preload_parsed(options, **kwargs):
121 def on_preload_parsed(options, **kwargs):
122 from rhodecode.config.middleware import get_celery_config
122 from rhodecode.config.middleware import get_celery_config
123
123
124 ini_location = options['ini']
124 ini_location = options['ini']
125 ini_vars = options['ini_var']
125 ini_vars = options['ini_var']
126 celery_app.conf['INI_PYRAMID'] = options['ini']
126 celery_app.conf['INI_PYRAMID'] = options['ini']
127
127
128 if ini_location is None:
128 if ini_location is None:
129 print('You must provide the paste --ini argument')
129 print('You must provide the paste --ini argument')
130 exit(-1)
130 exit(-1)
131
131
132 options = None
132 options = None
133 if ini_vars is not None:
133 if ini_vars is not None:
134 options = parse_ini_vars(ini_vars)
134 options = parse_ini_vars(ini_vars)
135
135
136 global ini_file_glob
136 global ini_file_glob
137 ini_file_glob = ini_location
137 ini_file_glob = ini_location
138
138
139 log.debug('Bootstrapping RhodeCode application...')
139 log.debug('Bootstrapping RhodeCode application...')
140
140
141 env = {}
141 env = {}
142 try:
142 try:
143 env = bootstrap(ini_location, options=options)
143 env = bootstrap(ini_location, options=options)
144 except Exception:
144 except Exception:
145 log.exception('Failed to bootstrap RhodeCode APP')
145 log.exception('Failed to bootstrap RhodeCode APP')
146
146
147 log.debug('Got Pyramid ENV: %s', env)
147 log.debug('Got Pyramid ENV: %s', env)
148 celery_settings = get_celery_config(env['registry'].settings)
148 celery_settings = get_celery_config(env['registry'].settings)
149
149
150 setup_celery_app(
150 setup_celery_app(
151 app=env['app'], root=env['root'], request=env['request'],
151 app=env['app'], root=env['root'], request=env['request'],
152 registry=env['registry'], closer=env['closer'],
152 registry=env['registry'], closer=env['closer'],
153 celery_settings=celery_settings)
153 celery_settings=celery_settings)
154
154
155 # fix the global flag even if it's disabled via .ini file because this
155 # fix the global flag even if it's disabled via .ini file because this
156 # is a worker code that doesn't need this to be disabled.
156 # is a worker code that doesn't need this to be disabled.
157 rhodecode.CELERY_ENABLED = True
157 rhodecode.CELERY_ENABLED = True
158
158
159
159
160 @signals.task_prerun.connect
160 @signals.task_prerun.connect
161 def task_prerun_signal(task_id, task, args, **kwargs):
161 def task_prerun_signal(task_id, task, args, **kwargs):
162 ping_db()
162 ping_db()
163
163
164
164
165 @signals.task_success.connect
165 @signals.task_success.connect
166 def task_success_signal(result, **kwargs):
166 def task_success_signal(result, **kwargs):
167 meta.Session.commit()
167 meta.Session.commit()
168 closer = celery_app.conf['PYRAMID_CLOSER']
168 closer = celery_app.conf['PYRAMID_CLOSER']
169 if closer:
169 if closer:
170 closer()
170 closer()
171
171
172
172
173 @signals.task_retry.connect
173 @signals.task_retry.connect
174 def task_retry_signal(
174 def task_retry_signal(
175 request, reason, einfo, **kwargs):
175 request, reason, einfo, **kwargs):
176 meta.Session.remove()
176 meta.Session.remove()
177 closer = celery_app.conf['PYRAMID_CLOSER']
177 closer = celery_app.conf['PYRAMID_CLOSER']
178 if closer:
178 if closer:
179 closer()
179 closer()
180
180
181
181
182 @signals.task_failure.connect
182 @signals.task_failure.connect
183 def task_failure_signal(
183 def task_failure_signal(
184 task_id, exception, args, kwargs, traceback, einfo, **kargs):
184 task_id, exception, args, kwargs, traceback, einfo, **kargs):
185 from rhodecode.lib.exc_tracking import store_exception
185 from rhodecode.lib.exc_tracking import store_exception
186 from rhodecode.lib.statsd_client import StatsdClient
186 from rhodecode.lib.statsd_client import StatsdClient
187
187
188 meta.Session.remove()
188 meta.Session.remove()
189
189
190 # simulate sys.exc_info()
190 # simulate sys.exc_info()
191 exc_info = (einfo.type, einfo.exception, einfo.tb)
191 exc_info = (einfo.type, einfo.exception, einfo.tb)
192 store_exception(id(exc_info), exc_info, prefix='rhodecode-celery')
192 store_exception(id(exc_info), exc_info, prefix='rhodecode-celery')
193 statsd = StatsdClient.statsd
193 statsd = StatsdClient.statsd
194 if statsd:
194 if statsd:
195 exc_type = "{}.{}".format(einfo.__class__.__module__, einfo.__class__.__name__)
195 exc_type = "{}.{}".format(einfo.__class__.__module__, einfo.__class__.__name__)
196 statsd.incr('rhodecode_exception_total',
196 statsd.incr('rhodecode_exception_total',
197 tags=["exc_source:celery", "type:{}".format(exc_type)])
197 tags=["exc_source:celery", "type:{}".format(exc_type)])
198
198
199 closer = celery_app.conf['PYRAMID_CLOSER']
199 closer = celery_app.conf['PYRAMID_CLOSER']
200 if closer:
200 if closer:
201 closer()
201 closer()
202
202
203
203
204 @signals.task_revoked.connect
204 @signals.task_revoked.connect
205 def task_revoked_signal(
205 def task_revoked_signal(
206 request, terminated, signum, expired, **kwargs):
206 request, terminated, signum, expired, **kwargs):
207 closer = celery_app.conf['PYRAMID_CLOSER']
207 closer = celery_app.conf['PYRAMID_CLOSER']
208 if closer:
208 if closer:
209 closer()
209 closer()
210
210
211
211
212 def setup_celery_app(app, root, request, registry, closer, celery_settings):
212 def setup_celery_app(app, root, request, registry, closer, celery_settings):
213 log.debug('Got custom celery conf: %s', celery_settings)
213 log.debug('Got custom celery conf: %s', celery_settings)
214 celery_config = base_celery_config
214 celery_config = base_celery_config
215 celery_config.update({
215 celery_config.update({
216 # store celerybeat scheduler db where the .ini file is
216 # store celerybeat scheduler db where the .ini file is
217 'beat_schedule_filename': registry.settings['celerybeat-schedule.path'],
217 'beat_schedule_filename': registry.settings['celerybeat-schedule.path'],
218 })
218 })
219
219
220 celery_config.update(celery_settings)
220 celery_config.update(celery_settings)
221 celery_app.config_from_object(celery_config)
221 celery_app.config_from_object(celery_config)
222
222
223 celery_app.conf.update({'PYRAMID_APP': app})
223 celery_app.conf.update({'PYRAMID_APP': app})
224 celery_app.conf.update({'PYRAMID_ROOT': root})
224 celery_app.conf.update({'PYRAMID_ROOT': root})
225 celery_app.conf.update({'PYRAMID_REQUEST': request})
225 celery_app.conf.update({'PYRAMID_REQUEST': request})
226 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
226 celery_app.conf.update({'PYRAMID_REGISTRY': registry})
227 celery_app.conf.update({'PYRAMID_CLOSER': closer})
227 celery_app.conf.update({'PYRAMID_CLOSER': closer})
228
228
229
229
230 def configure_celery(config, celery_settings):
230 def configure_celery(config, celery_settings):
231 """
231 """
232 Helper that is called from our application creation logic. It gives
232 Helper that is called from our application creation logic. It gives
233 connection info into running webapp and allows execution of tasks from
233 connection info into running webapp and allows execution of tasks from
234 RhodeCode itself
234 RhodeCode itself
235 """
235 """
236 # store some globals into rhodecode
236 # store some globals into rhodecode
237 rhodecode.CELERY_ENABLED = str2bool(
237 rhodecode.CELERY_ENABLED = str2bool(
238 config.registry.settings.get('use_celery'))
238 config.registry.settings.get('use_celery'))
239 if rhodecode.CELERY_ENABLED:
239 if rhodecode.CELERY_ENABLED:
240 log.info('Configuring celery based on `%s` settings', celery_settings)
240 log.info('Configuring celery based on `%s` settings', celery_settings)
241 setup_celery_app(
241 setup_celery_app(
242 app=None, root=None, request=None, registry=config.registry,
242 app=None, root=None, request=None, registry=config.registry,
243 closer=None, celery_settings=celery_settings)
243 closer=None, celery_settings=celery_settings)
244
244
245
245
246 def maybe_prepare_env(req):
246 def maybe_prepare_env(req):
247 environ = {}
247 environ = {}
248 try:
248 try:
249 environ.update({
249 environ.update({
250 'PATH_INFO': req.environ['PATH_INFO'],
250 'PATH_INFO': req.environ['PATH_INFO'],
251 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
251 'SCRIPT_NAME': req.environ['SCRIPT_NAME'],
252 'HTTP_HOST': req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
252 'HTTP_HOST': req.environ.get('HTTP_HOST', req.environ['SERVER_NAME']),
253 'SERVER_NAME': req.environ['SERVER_NAME'],
253 'SERVER_NAME': req.environ['SERVER_NAME'],
254 'SERVER_PORT': req.environ['SERVER_PORT'],
254 'SERVER_PORT': req.environ['SERVER_PORT'],
255 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
255 'wsgi.url_scheme': req.environ['wsgi.url_scheme'],
256 })
256 })
257 except Exception:
257 except Exception:
258 pass
258 pass
259
259
260 return environ
260 return environ
261
261
262
262
263 class RequestContextTask(Task):
263 class RequestContextTask(Task):
264 """
264 """
265 This is a celery task which will create a rhodecode app instance context
265 This is a celery task which will create a rhodecode app instance context
266 for the task, patch pyramid with the original request
266 for the task, patch pyramid with the original request
267 that created the task and also add the user to the context.
267 that created the task and also add the user to the context.
268 """
268 """
269
269
270 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
270 def apply_async(self, args=None, kwargs=None, task_id=None, producer=None,
271 link=None, link_error=None, shadow=None, **options):
271 link=None, link_error=None, shadow=None, **options):
272 """ queue the job to run (we are in web request context here) """
272 """ queue the job to run (we are in web request context here) """
273
273
274 req = get_current_request()
274 req = self.app.conf['PYRAMID_REQUEST'] or get_current_request()
275
275 log.debug('Running Task with class: %s. Request Class: %s',
276 log.debug('Running Task with class: %s. Request Class: %s',
276 self.__class__, req.__class__)
277 self.__class__, req.__class__)
277
278
279 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
280 log.debug('celery proxy data:%r', proxy_data)
281
282 user_id = None
283 ip_addr = None
284 if proxy_data:
285 user_id = proxy_data['auth_user']['user_id']
286 ip_addr = proxy_data['auth_user']['ip_addr']
287
278 # web case
288 # web case
279 if hasattr(req, 'user'):
289 if hasattr(req, 'user'):
280 ip_addr = req.user.ip_addr
290 ip_addr = req.user.ip_addr
281 user_id = req.user.user_id
291 user_id = req.user.user_id
282
292
283 # api case
293 # api case
284 elif hasattr(req, 'rpc_user'):
294 elif hasattr(req, 'rpc_user'):
285 ip_addr = req.rpc_user.ip_addr
295 ip_addr = req.rpc_user.ip_addr
286 user_id = req.rpc_user.user_id
296 user_id = req.rpc_user.user_id
287 else:
297 else:
288 raise Exception(
298 if user_id and ip_addr:
289 'Unable to fetch required data from request: {}. \n'
299 log.debug('Using data from celery proxy user')
290 'This task is required to be executed from context of '
300
291 'request in a webapp. Task: {}'.format(
301 else:
292 repr(req),
302 raise Exception(
293 self
303 'Unable to fetch required data from request: {}. \n'
304 'This task is required to be executed from context of '
305 'request in a webapp. Task: {}'.format(
306 repr(req),
307 self.__class__
308 )
294 )
309 )
295 )
296
310
297 if req:
311 if req:
298 # we hook into kwargs since it is the only way to pass our data to
312 # we hook into kwargs since it is the only way to pass our data to
299 # the celery worker
313 # the celery worker
300 environ = maybe_prepare_env(req)
314 environ = maybe_prepare_env(req)
301 options['headers'] = options.get('headers', {})
315 options['headers'] = options.get('headers', {})
302 options['headers'].update({
316 options['headers'].update({
303 'rhodecode_proxy_data': {
317 'rhodecode_proxy_data': {
304 'environ': environ,
318 'environ': environ,
305 'auth_user': {
319 'auth_user': {
306 'ip_addr': ip_addr,
320 'ip_addr': ip_addr,
307 'user_id': user_id
321 'user_id': user_id
308 },
322 },
309 }
323 }
310 })
324 })
311
325
312 return super(RequestContextTask, self).apply_async(
326 return super(RequestContextTask, self).apply_async(
313 args, kwargs, task_id, producer, link, link_error, shadow, **options)
327 args, kwargs, task_id, producer, link, link_error, shadow, **options)
314
315 def __call__(self, *args, **kwargs):
316 """ rebuild the context and then run task on celery worker """
317
318 proxy_data = getattr(self.request, 'rhodecode_proxy_data', None)
319 if not proxy_data:
320 return super(RequestContextTask, self).__call__(*args, **kwargs)
321
322 log.debug('using celery proxy data to run task: %r', proxy_data)
323 # re-inject and register threadlocals for proper routing support
324 request = prepare_request(proxy_data['environ'])
325 request.user = AuthUser(user_id=proxy_data['auth_user']['user_id'],
326 ip_addr=proxy_data['auth_user']['ip_addr'])
327
328 return super(RequestContextTask, self).__call__(*args, **kwargs)
329
@@ -1,76 +1,54 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2020 RhodeCode GmbH
3 # Copyright (C) 2016-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 from pyramid.compat import configparser
22 from pyramid.compat import configparser
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover
23 from pyramid.paster import bootstrap as pyramid_bootstrap, setup_logging # pragma: no cover
24 from pyramid.scripting import prepare
24 from pyramid.scripting import prepare
25
25
26 from rhodecode.lib.request import Request
26 from rhodecode.lib.request import Request
27
27
28
28
29 def get_config(ini_path, **kwargs):
29 def get_config(ini_path, **kwargs):
30 parser = configparser.ConfigParser(**kwargs)
30 parser = configparser.ConfigParser(**kwargs)
31 parser.read(ini_path)
31 parser.read(ini_path)
32 return parser
32 return parser
33
33
34
34
35 def get_app_config(ini_path):
35 def get_app_config(ini_path):
36 from paste.deploy.loadwsgi import appconfig
36 from paste.deploy.loadwsgi import appconfig
37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
37 return appconfig('config:{}'.format(ini_path), relative_to=os.getcwd())
38
38
39
39
40 class BootstrappedRequest(Request):
40 def bootstrap(config_uri, options=None, env=None):
41 """
42 Special version of Request Which has some available methods like in pyramid.
43 Some code (used for template rendering) requires this, and we unsure it's present.
44 """
45
46 def translate(self, msg):
47 return msg
48
49 def plularize(self, singular, plural, n):
50 return singular
51
52 def get_partial_renderer(self, tmpl_name):
53 from rhodecode.lib.partial_renderer import get_partial_renderer
54 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
55
56
57 def bootstrap(config_uri, request=None, options=None, env=None):
58 if env:
41 if env:
59 os.environ.update(env)
42 os.environ.update(env)
60
43
61 config = get_config(config_uri)
44 config = get_config(config_uri)
62 base_url = 'http://rhodecode.local'
45 base_url = 'http://rhodecode.local'
63 try:
46 try:
64 base_url = config.get('app:main', 'app.base_url')
47 base_url = config.get('app:main', 'app.base_url')
65 except (configparser.NoSectionError, configparser.NoOptionError):
48 except (configparser.NoSectionError, configparser.NoOptionError):
66 pass
49 pass
67
50
68 request = request or BootstrappedRequest.blank('/', base_url=base_url)
51 request = Request.blank('/', base_url=base_url)
69
52
70 return pyramid_bootstrap(config_uri, request=request, options=options)
53 return pyramid_bootstrap(config_uri, request=request, options=options)
71
54
72
73 def prepare_request(environ):
74 request = Request.blank('/', environ=environ)
75 prepare(request) # set pyramid threadlocal request
76 return request
@@ -1,50 +1,109 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 import pyramid.testing
22 from pyramid.decorator import reify
23 from pyramid.decorator import reify
23 from pyramid.request import Request as _Request
24 from pyramid.request import Request as _Request
24 from rhodecode.translation import _ as tsf
25 from rhodecode.translation import _ as tsf
26 from rhodecode.lib.utils2 import StrictAttributeDict
27
28
29 class TemplateArgs(StrictAttributeDict):
30 pass
25
31
26
32
27 class Request(_Request):
33 # Base Class with DummyMethods, testing / CLI scripts
34 class RequestBase(object):
28 _req_id_bucket = list()
35 _req_id_bucket = list()
36 _call_context = TemplateArgs()
37 _call_context.visual = TemplateArgs()
38 _call_context.visual.show_sha_length = 12
39 _call_context.visual.show_revision_number = True
29
40
30 @reify
41 @reify
31 def req_id(self):
42 def req_id(self):
32 return str(uuid4())
43 return str(uuid4())
33
44
34 @property
45 @property
35 def req_id_bucket(self):
46 def req_id_bucket(self):
36 return self._req_id_bucket
47 return self._req_id_bucket
37
48
38 def req_id_records_init(self):
49 def req_id_records_init(self):
39 self._req_id_bucket = list()
50 self._req_id_bucket = list()
40
51
52 def translate(self, *args, **kwargs):
53 raise NotImplementedError()
54
55 def plularize(self, *args, **kwargs):
56 raise NotImplementedError()
57
58 def get_partial_renderer(self, tmpl_name):
59 raise NotImplementedError()
60
61 @property
62 def call_context(self):
63 return self._call_context
64
65 def set_call_context(self, new_context):
66 self._call_context = new_context
67
68
69 # for thin non-web/cli etc
70 class ThinRequest(RequestBase, pyramid.testing.DummyRequest):
71
72 def translate(self, msg):
73 return msg
74
75 def plularize(self, singular, plural, n):
76 return singular
77
78 def get_partial_renderer(self, tmpl_name):
79 from rhodecode.lib.partial_renderer import get_partial_renderer
80 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
81
82
83 # for real-web-based
84 class RealRequest(RequestBase, _Request):
85 def get_partial_renderer(self, tmpl_name):
86 from rhodecode.lib.partial_renderer import get_partial_renderer
87 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
88
89 def request_count(self):
90 from rhodecode.lib.request_counter import get_request_counter
91 return get_request_counter()
92
41 def plularize(self, *args, **kwargs):
93 def plularize(self, *args, **kwargs):
42 return self.localizer.pluralize(*args, **kwargs)
94 return self.localizer.pluralize(*args, **kwargs)
43
95
44 def translate(self, *args, **kwargs):
96 def translate(self, *args, **kwargs):
45 localizer = self.localizer
97 localizer = self.localizer
46
98
47 def auto_translate(*_args, **_kwargs):
99 def auto_translate(*_args, **_kwargs):
48 return localizer.translate(tsf(*_args, **_kwargs))
100 return localizer.translate(tsf(*_args, **_kwargs))
49
101
50 return auto_translate(*args, **kwargs)
102 return auto_translate(*args, **kwargs)
103
104
105 class Request(RealRequest):
106 """
107 This is the main request object used in web-context
108 """
109 pass
@@ -1,27 +1,27 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 counter = 0
21 counter = 0
22
22
23
23
24 def get_request_counter(request):
24 def get_request_counter():
25 global counter
25 global counter
26 counter += 1
26 counter += 1
27 return counter
27 return counter
General Comments 0
You need to be logged in to leave comments. Login now