##// END OF EJS Templates
core: use a custom filter for rendering all mako templates....
marcink -
r1949:8462771d default
parent child Browse files
Show More
@@ -1,529 +1,534 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 Pylons middleware initialization
22 Pylons middleware initialization
23 """
23 """
24 import logging
24 import logging
25 import traceback
25 import traceback
26 from collections import OrderedDict
26 from collections import OrderedDict
27
27
28 from paste.registry import RegistryManager
28 from paste.registry import RegistryManager
29 from paste.gzipper import make_gzip_middleware
29 from paste.gzipper import make_gzip_middleware
30 from pylons.wsgiapp import PylonsApp
30 from pylons.wsgiapp import PylonsApp
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.wsgi import wsgiapp
34 from pyramid.wsgi import wsgiapp
35 from pyramid.httpexceptions import (
35 from pyramid.httpexceptions import (
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
37 from pyramid.events import ApplicationCreated
37 from pyramid.events import ApplicationCreated
38 from pyramid.renderers import render_to_response
38 from pyramid.renderers import render_to_response
39 from routes.middleware import RoutesMiddleware
39 from routes.middleware import RoutesMiddleware
40 import rhodecode
40 import rhodecode
41
41
42 from rhodecode.model import meta
42 from rhodecode.model import meta
43 from rhodecode.config import patches
43 from rhodecode.config import patches
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
44 from rhodecode.config.routing import STATIC_FILE_PREFIX
45 from rhodecode.config.environment import (
45 from rhodecode.config.environment import (
46 load_environment, load_pyramid_environment)
46 load_environment, load_pyramid_environment)
47
47
48 from rhodecode.lib.vcs import VCSCommunicationError
48 from rhodecode.lib.vcs import VCSCommunicationError
49 from rhodecode.lib.exceptions import VCSServerUnavailable
49 from rhodecode.lib.exceptions import VCSServerUnavailable
50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
50 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
51 from rhodecode.lib.middleware.error_handling import (
51 from rhodecode.lib.middleware.error_handling import (
52 PylonsErrorHandlingMiddleware)
52 PylonsErrorHandlingMiddleware)
53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
53 from rhodecode.lib.middleware.https_fixup import HttpsFixup
54 from rhodecode.lib.middleware.vcs import VCSMiddleware
54 from rhodecode.lib.middleware.vcs import VCSMiddleware
55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
55 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
56 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
57 from rhodecode.subscribers import (
57 from rhodecode.subscribers import (
58 scan_repositories_if_enabled, write_js_routes_if_enabled,
58 scan_repositories_if_enabled, write_js_routes_if_enabled,
59 write_metadata_if_needed)
59 write_metadata_if_needed)
60
60
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64
64
65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
65 # this is used to avoid avoid the route lookup overhead in routesmiddleware
66 # for certain routes which won't go to pylons to - eg. static files, debugger
66 # for certain routes which won't go to pylons to - eg. static files, debugger
67 # it is only needed for the pylons migration and can be removed once complete
67 # it is only needed for the pylons migration and can be removed once complete
68 class SkippableRoutesMiddleware(RoutesMiddleware):
68 class SkippableRoutesMiddleware(RoutesMiddleware):
69 """ Routes middleware that allows you to skip prefixes """
69 """ Routes middleware that allows you to skip prefixes """
70
70
71 def __init__(self, *args, **kw):
71 def __init__(self, *args, **kw):
72 self.skip_prefixes = kw.pop('skip_prefixes', [])
72 self.skip_prefixes = kw.pop('skip_prefixes', [])
73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
73 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
74
74
75 def __call__(self, environ, start_response):
75 def __call__(self, environ, start_response):
76 for prefix in self.skip_prefixes:
76 for prefix in self.skip_prefixes:
77 if environ['PATH_INFO'].startswith(prefix):
77 if environ['PATH_INFO'].startswith(prefix):
78 # added to avoid the case when a missing /_static route falls
78 # added to avoid the case when a missing /_static route falls
79 # through to pylons and causes an exception as pylons is
79 # through to pylons and causes an exception as pylons is
80 # expecting wsgiorg.routingargs to be set in the environ
80 # expecting wsgiorg.routingargs to be set in the environ
81 # by RoutesMiddleware.
81 # by RoutesMiddleware.
82 if 'wsgiorg.routing_args' not in environ:
82 if 'wsgiorg.routing_args' not in environ:
83 environ['wsgiorg.routing_args'] = (None, {})
83 environ['wsgiorg.routing_args'] = (None, {})
84 return self.app(environ, start_response)
84 return self.app(environ, start_response)
85
85
86 return super(SkippableRoutesMiddleware, self).__call__(
86 return super(SkippableRoutesMiddleware, self).__call__(
87 environ, start_response)
87 environ, start_response)
88
88
89
89
90 def make_app(global_conf, static_files=True, **app_conf):
90 def make_app(global_conf, static_files=True, **app_conf):
91 """Create a Pylons WSGI application and return it
91 """Create a Pylons WSGI application and return it
92
92
93 ``global_conf``
93 ``global_conf``
94 The inherited configuration for this application. Normally from
94 The inherited configuration for this application. Normally from
95 the [DEFAULT] section of the Paste ini file.
95 the [DEFAULT] section of the Paste ini file.
96
96
97 ``app_conf``
97 ``app_conf``
98 The application's local configuration. Normally specified in
98 The application's local configuration. Normally specified in
99 the [app:<name>] section of the Paste ini file (where <name>
99 the [app:<name>] section of the Paste ini file (where <name>
100 defaults to main).
100 defaults to main).
101
101
102 """
102 """
103 # Apply compatibility patches
103 # Apply compatibility patches
104 patches.kombu_1_5_1_python_2_7_11()
104 patches.kombu_1_5_1_python_2_7_11()
105 patches.inspect_getargspec()
105 patches.inspect_getargspec()
106
106
107 # Configure the Pylons environment
107 # Configure the Pylons environment
108 config = load_environment(global_conf, app_conf)
108 config = load_environment(global_conf, app_conf)
109
109
110 # The Pylons WSGI app
110 # The Pylons WSGI app
111 app = PylonsApp(config=config)
111 app = PylonsApp(config=config)
112
112
113 # Establish the Registry for this application
113 # Establish the Registry for this application
114 app = RegistryManager(app)
114 app = RegistryManager(app)
115
115
116 app.config = config
116 app.config = config
117
117
118 return app
118 return app
119
119
120
120
121 def make_pyramid_app(global_config, **settings):
121 def make_pyramid_app(global_config, **settings):
122 """
122 """
123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
123 Constructs the WSGI application based on Pyramid and wraps the Pylons based
124 application.
124 application.
125
125
126 Specials:
126 Specials:
127
127
128 * We migrate from Pylons to Pyramid. While doing this, we keep both
128 * We migrate from Pylons to Pyramid. While doing this, we keep both
129 frameworks functional. This involves moving some WSGI middlewares around
129 frameworks functional. This involves moving some WSGI middlewares around
130 and providing access to some data internals, so that the old code is
130 and providing access to some data internals, so that the old code is
131 still functional.
131 still functional.
132
132
133 * The application can also be integrated like a plugin via the call to
133 * The application can also be integrated like a plugin via the call to
134 `includeme`. This is accompanied with the other utility functions which
134 `includeme`. This is accompanied with the other utility functions which
135 are called. Changing this should be done with great care to not break
135 are called. Changing this should be done with great care to not break
136 cases when these fragments are assembled from another place.
136 cases when these fragments are assembled from another place.
137
137
138 """
138 """
139 # The edition string should be available in pylons too, so we add it here
139 # The edition string should be available in pylons too, so we add it here
140 # before copying the settings.
140 # before copying the settings.
141 settings.setdefault('rhodecode.edition', 'Community Edition')
141 settings.setdefault('rhodecode.edition', 'Community Edition')
142
142
143 # As long as our Pylons application does expect "unprepared" settings, make
143 # As long as our Pylons application does expect "unprepared" settings, make
144 # sure that we keep an unmodified copy. This avoids unintentional change of
144 # sure that we keep an unmodified copy. This avoids unintentional change of
145 # behavior in the old application.
145 # behavior in the old application.
146 settings_pylons = settings.copy()
146 settings_pylons = settings.copy()
147
147
148 sanitize_settings_and_apply_defaults(settings)
148 sanitize_settings_and_apply_defaults(settings)
149 config = Configurator(settings=settings)
149 config = Configurator(settings=settings)
150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
150 add_pylons_compat_data(config.registry, global_config, settings_pylons)
151
151
152 load_pyramid_environment(global_config, settings)
152 load_pyramid_environment(global_config, settings)
153
153
154 includeme_first(config)
154 includeme_first(config)
155 includeme(config)
155 includeme(config)
156
156
157 pyramid_app = config.make_wsgi_app()
157 pyramid_app = config.make_wsgi_app()
158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
158 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
159 pyramid_app.config = config
159 pyramid_app.config = config
160
160
161 # creating the app uses a connection - return it after we are done
161 # creating the app uses a connection - return it after we are done
162 meta.Session.remove()
162 meta.Session.remove()
163
163
164 return pyramid_app
164 return pyramid_app
165
165
166
166
167 def make_not_found_view(config):
167 def make_not_found_view(config):
168 """
168 """
169 This creates the view which should be registered as not-found-view to
169 This creates the view which should be registered as not-found-view to
170 pyramid. Basically it contains of the old pylons app, converted to a view.
170 pyramid. Basically it contains of the old pylons app, converted to a view.
171 Additionally it is wrapped by some other middlewares.
171 Additionally it is wrapped by some other middlewares.
172 """
172 """
173 settings = config.registry.settings
173 settings = config.registry.settings
174 vcs_server_enabled = settings['vcs.server.enable']
174 vcs_server_enabled = settings['vcs.server.enable']
175
175
176 # Make pylons app from unprepared settings.
176 # Make pylons app from unprepared settings.
177 pylons_app = make_app(
177 pylons_app = make_app(
178 config.registry._pylons_compat_global_config,
178 config.registry._pylons_compat_global_config,
179 **config.registry._pylons_compat_settings)
179 **config.registry._pylons_compat_settings)
180 config.registry._pylons_compat_config = pylons_app.config
180 config.registry._pylons_compat_config = pylons_app.config
181
181
182 # Appenlight monitoring.
182 # Appenlight monitoring.
183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
183 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
184 pylons_app, settings)
184 pylons_app, settings)
185
185
186 # The pylons app is executed inside of the pyramid 404 exception handler.
186 # The pylons app is executed inside of the pyramid 404 exception handler.
187 # Exceptions which are raised inside of it are not handled by pyramid
187 # Exceptions which are raised inside of it are not handled by pyramid
188 # again. Therefore we add a middleware that invokes the error handler in
188 # again. Therefore we add a middleware that invokes the error handler in
189 # case of an exception or error response. This way we return proper error
189 # case of an exception or error response. This way we return proper error
190 # HTML pages in case of an error.
190 # HTML pages in case of an error.
191 reraise = (settings.get('debugtoolbar.enabled', False) or
191 reraise = (settings.get('debugtoolbar.enabled', False) or
192 rhodecode.disable_error_handler)
192 rhodecode.disable_error_handler)
193 pylons_app = PylonsErrorHandlingMiddleware(
193 pylons_app = PylonsErrorHandlingMiddleware(
194 pylons_app, error_handler, reraise)
194 pylons_app, error_handler, reraise)
195
195
196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
196 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
197 # view to handle the request. Therefore it is wrapped around the pylons
197 # view to handle the request. Therefore it is wrapped around the pylons
198 # app. It has to be outside of the error handling otherwise error responses
198 # app. It has to be outside of the error handling otherwise error responses
199 # from the vcsserver are converted to HTML error pages. This confuses the
199 # from the vcsserver are converted to HTML error pages. This confuses the
200 # command line tools and the user won't get a meaningful error message.
200 # command line tools and the user won't get a meaningful error message.
201 if vcs_server_enabled:
201 if vcs_server_enabled:
202 pylons_app = VCSMiddleware(
202 pylons_app = VCSMiddleware(
203 pylons_app, settings, appenlight_client, registry=config.registry)
203 pylons_app, settings, appenlight_client, registry=config.registry)
204
204
205 # Convert WSGI app to pyramid view and return it.
205 # Convert WSGI app to pyramid view and return it.
206 return wsgiapp(pylons_app)
206 return wsgiapp(pylons_app)
207
207
208
208
209 def add_pylons_compat_data(registry, global_config, settings):
209 def add_pylons_compat_data(registry, global_config, settings):
210 """
210 """
211 Attach data to the registry to support the Pylons integration.
211 Attach data to the registry to support the Pylons integration.
212 """
212 """
213 registry._pylons_compat_global_config = global_config
213 registry._pylons_compat_global_config = global_config
214 registry._pylons_compat_settings = settings
214 registry._pylons_compat_settings = settings
215
215
216
216
217 def error_handler(exception, request):
217 def error_handler(exception, request):
218 import rhodecode
218 import rhodecode
219 from rhodecode.lib import helpers
219 from rhodecode.lib import helpers
220
220
221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
221 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
222
222
223 base_response = HTTPInternalServerError()
223 base_response = HTTPInternalServerError()
224 # prefer original exception for the response since it may have headers set
224 # prefer original exception for the response since it may have headers set
225 if isinstance(exception, HTTPException):
225 if isinstance(exception, HTTPException):
226 base_response = exception
226 base_response = exception
227 elif isinstance(exception, VCSCommunicationError):
227 elif isinstance(exception, VCSCommunicationError):
228 base_response = VCSServerUnavailable()
228 base_response = VCSServerUnavailable()
229
229
230 def is_http_error(response):
230 def is_http_error(response):
231 # error which should have traceback
231 # error which should have traceback
232 return response.status_code > 499
232 return response.status_code > 499
233
233
234 if is_http_error(base_response):
234 if is_http_error(base_response):
235 log.exception(
235 log.exception(
236 'error occurred handling this request for path: %s', request.path)
236 'error occurred handling this request for path: %s', request.path)
237
237
238 c = AttributeDict()
238 c = AttributeDict()
239 c.error_message = base_response.status
239 c.error_message = base_response.status
240 c.error_explanation = base_response.explanation or str(base_response)
240 c.error_explanation = base_response.explanation or str(base_response)
241 c.visual = AttributeDict()
241 c.visual = AttributeDict()
242
242
243 c.visual.rhodecode_support_url = (
243 c.visual.rhodecode_support_url = (
244 request.registry.settings.get('rhodecode_support_url') or
244 request.registry.settings.get('rhodecode_support_url') or
245 request.route_url('rhodecode_support')
245 request.route_url('rhodecode_support')
246 )
246 )
247 c.redirect_time = 0
247 c.redirect_time = 0
248 c.rhodecode_name = rhodecode_title
248 c.rhodecode_name = rhodecode_title
249 if not c.rhodecode_name:
249 if not c.rhodecode_name:
250 c.rhodecode_name = 'Rhodecode'
250 c.rhodecode_name = 'Rhodecode'
251
251
252 c.causes = []
252 c.causes = []
253 if hasattr(base_response, 'causes'):
253 if hasattr(base_response, 'causes'):
254 c.causes = base_response.causes
254 c.causes = base_response.causes
255 c.messages = helpers.flash.pop_messages(request=request)
255 c.messages = helpers.flash.pop_messages(request=request)
256 c.traceback = traceback.format_exc()
256 c.traceback = traceback.format_exc()
257 response = render_to_response(
257 response = render_to_response(
258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
258 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
259 response=base_response)
259 response=base_response)
260
260
261 return response
261 return response
262
262
263
263
264 def includeme(config):
264 def includeme(config):
265 settings = config.registry.settings
265 settings = config.registry.settings
266
266
267 # plugin information
267 # plugin information
268 config.registry.rhodecode_plugins = OrderedDict()
268 config.registry.rhodecode_plugins = OrderedDict()
269
269
270 config.add_directive(
270 config.add_directive(
271 'register_rhodecode_plugin', register_rhodecode_plugin)
271 'register_rhodecode_plugin', register_rhodecode_plugin)
272
272
273 if asbool(settings.get('appenlight', 'false')):
273 if asbool(settings.get('appenlight', 'false')):
274 config.include('appenlight_client.ext.pyramid_tween')
274 config.include('appenlight_client.ext.pyramid_tween')
275
275
276 if not 'mako.default_filters' in settings:
277 # set custom default filters if we don't have it defined
278 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
279 settings['mako.default_filters'] = 'h_filter'
280
276 # Includes which are required. The application would fail without them.
281 # Includes which are required. The application would fail without them.
277 config.include('pyramid_mako')
282 config.include('pyramid_mako')
278 config.include('pyramid_beaker')
283 config.include('pyramid_beaker')
279
284
280 config.include('rhodecode.authentication')
285 config.include('rhodecode.authentication')
281 config.include('rhodecode.integrations')
286 config.include('rhodecode.integrations')
282
287
283 # apps
288 # apps
284 config.include('rhodecode.apps._base')
289 config.include('rhodecode.apps._base')
285 config.include('rhodecode.apps.ops')
290 config.include('rhodecode.apps.ops')
286
291
287 config.include('rhodecode.apps.admin')
292 config.include('rhodecode.apps.admin')
288 config.include('rhodecode.apps.channelstream')
293 config.include('rhodecode.apps.channelstream')
289 config.include('rhodecode.apps.login')
294 config.include('rhodecode.apps.login')
290 config.include('rhodecode.apps.home')
295 config.include('rhodecode.apps.home')
291 config.include('rhodecode.apps.journal')
296 config.include('rhodecode.apps.journal')
292 config.include('rhodecode.apps.repository')
297 config.include('rhodecode.apps.repository')
293 config.include('rhodecode.apps.repo_group')
298 config.include('rhodecode.apps.repo_group')
294 config.include('rhodecode.apps.search')
299 config.include('rhodecode.apps.search')
295 config.include('rhodecode.apps.user_profile')
300 config.include('rhodecode.apps.user_profile')
296 config.include('rhodecode.apps.my_account')
301 config.include('rhodecode.apps.my_account')
297 config.include('rhodecode.apps.svn_support')
302 config.include('rhodecode.apps.svn_support')
298 config.include('rhodecode.apps.gist')
303 config.include('rhodecode.apps.gist')
299
304
300 config.include('rhodecode.apps.debug_style')
305 config.include('rhodecode.apps.debug_style')
301 config.include('rhodecode.tweens')
306 config.include('rhodecode.tweens')
302 config.include('rhodecode.api')
307 config.include('rhodecode.api')
303
308
304 config.add_route(
309 config.add_route(
305 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
310 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
306
311
307 config.add_translation_dirs('rhodecode:i18n/')
312 config.add_translation_dirs('rhodecode:i18n/')
308 settings['default_locale_name'] = settings.get('lang', 'en')
313 settings['default_locale_name'] = settings.get('lang', 'en')
309
314
310 # Add subscribers.
315 # Add subscribers.
311 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
316 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
312 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
317 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
313 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
318 config.add_subscriber(write_js_routes_if_enabled, ApplicationCreated)
314
319
315 config.add_request_method(
320 config.add_request_method(
316 'rhodecode.lib.partial_renderer.get_partial_renderer',
321 'rhodecode.lib.partial_renderer.get_partial_renderer',
317 'get_partial_renderer')
322 'get_partial_renderer')
318
323
319 # events
324 # events
320 # TODO(marcink): this should be done when pyramid migration is finished
325 # TODO(marcink): this should be done when pyramid migration is finished
321 # config.add_subscriber(
326 # config.add_subscriber(
322 # 'rhodecode.integrations.integrations_event_handler',
327 # 'rhodecode.integrations.integrations_event_handler',
323 # 'rhodecode.events.RhodecodeEvent')
328 # 'rhodecode.events.RhodecodeEvent')
324
329
325 # Set the authorization policy.
330 # Set the authorization policy.
326 authz_policy = ACLAuthorizationPolicy()
331 authz_policy = ACLAuthorizationPolicy()
327 config.set_authorization_policy(authz_policy)
332 config.set_authorization_policy(authz_policy)
328
333
329 # Set the default renderer for HTML templates to mako.
334 # Set the default renderer for HTML templates to mako.
330 config.add_mako_renderer('.html')
335 config.add_mako_renderer('.html')
331
336
332 config.add_renderer(
337 config.add_renderer(
333 name='json_ext',
338 name='json_ext',
334 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
339 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
335
340
336 # include RhodeCode plugins
341 # include RhodeCode plugins
337 includes = aslist(settings.get('rhodecode.includes', []))
342 includes = aslist(settings.get('rhodecode.includes', []))
338 for inc in includes:
343 for inc in includes:
339 config.include(inc)
344 config.include(inc)
340
345
341 # This is the glue which allows us to migrate in chunks. By registering the
346 # This is the glue which allows us to migrate in chunks. By registering the
342 # pylons based application as the "Not Found" view in Pyramid, we will
347 # pylons based application as the "Not Found" view in Pyramid, we will
343 # fallback to the old application each time the new one does not yet know
348 # fallback to the old application each time the new one does not yet know
344 # how to handle a request.
349 # how to handle a request.
345 config.add_notfound_view(make_not_found_view(config))
350 config.add_notfound_view(make_not_found_view(config))
346
351
347 if not settings.get('debugtoolbar.enabled', False):
352 if not settings.get('debugtoolbar.enabled', False):
348 # disabled debugtoolbar handle all exceptions via the error_handlers
353 # disabled debugtoolbar handle all exceptions via the error_handlers
349 config.add_view(error_handler, context=Exception)
354 config.add_view(error_handler, context=Exception)
350
355
351 config.add_view(error_handler, context=HTTPError)
356 config.add_view(error_handler, context=HTTPError)
352
357
353
358
354 def includeme_first(config):
359 def includeme_first(config):
355 # redirect automatic browser favicon.ico requests to correct place
360 # redirect automatic browser favicon.ico requests to correct place
356 def favicon_redirect(context, request):
361 def favicon_redirect(context, request):
357 return HTTPFound(
362 return HTTPFound(
358 request.static_path('rhodecode:public/images/favicon.ico'))
363 request.static_path('rhodecode:public/images/favicon.ico'))
359
364
360 config.add_view(favicon_redirect, route_name='favicon')
365 config.add_view(favicon_redirect, route_name='favicon')
361 config.add_route('favicon', '/favicon.ico')
366 config.add_route('favicon', '/favicon.ico')
362
367
363 def robots_redirect(context, request):
368 def robots_redirect(context, request):
364 return HTTPFound(
369 return HTTPFound(
365 request.static_path('rhodecode:public/robots.txt'))
370 request.static_path('rhodecode:public/robots.txt'))
366
371
367 config.add_view(robots_redirect, route_name='robots')
372 config.add_view(robots_redirect, route_name='robots')
368 config.add_route('robots', '/robots.txt')
373 config.add_route('robots', '/robots.txt')
369
374
370 config.add_static_view(
375 config.add_static_view(
371 '_static/deform', 'deform:static')
376 '_static/deform', 'deform:static')
372 config.add_static_view(
377 config.add_static_view(
373 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
378 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
374
379
375
380
376 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
381 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
377 """
382 """
378 Apply outer WSGI middlewares around the application.
383 Apply outer WSGI middlewares around the application.
379
384
380 Part of this has been moved up from the Pylons layer, so that the
385 Part of this has been moved up from the Pylons layer, so that the
381 data is also available if old Pylons code is hit through an already ported
386 data is also available if old Pylons code is hit through an already ported
382 view.
387 view.
383 """
388 """
384 settings = config.registry.settings
389 settings = config.registry.settings
385
390
386 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
391 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
387 pyramid_app = HttpsFixup(pyramid_app, settings)
392 pyramid_app = HttpsFixup(pyramid_app, settings)
388
393
389 # Add RoutesMiddleware to support the pylons compatibility tween during
394 # Add RoutesMiddleware to support the pylons compatibility tween during
390 # migration to pyramid.
395 # migration to pyramid.
391
396
392 # TODO(marcink): remove after migration to pyramid
397 # TODO(marcink): remove after migration to pyramid
393 if hasattr(config.registry, '_pylons_compat_config'):
398 if hasattr(config.registry, '_pylons_compat_config'):
394 routes_map = config.registry._pylons_compat_config['routes.map']
399 routes_map = config.registry._pylons_compat_config['routes.map']
395 pyramid_app = SkippableRoutesMiddleware(
400 pyramid_app = SkippableRoutesMiddleware(
396 pyramid_app, routes_map,
401 pyramid_app, routes_map,
397 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
402 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
398
403
399 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
404 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
400
405
401 if settings['gzip_responses']:
406 if settings['gzip_responses']:
402 pyramid_app = make_gzip_middleware(
407 pyramid_app = make_gzip_middleware(
403 pyramid_app, settings, compress_level=1)
408 pyramid_app, settings, compress_level=1)
404
409
405 # this should be the outer most middleware in the wsgi stack since
410 # this should be the outer most middleware in the wsgi stack since
406 # middleware like Routes make database calls
411 # middleware like Routes make database calls
407 def pyramid_app_with_cleanup(environ, start_response):
412 def pyramid_app_with_cleanup(environ, start_response):
408 try:
413 try:
409 return pyramid_app(environ, start_response)
414 return pyramid_app(environ, start_response)
410 finally:
415 finally:
411 # Dispose current database session and rollback uncommitted
416 # Dispose current database session and rollback uncommitted
412 # transactions.
417 # transactions.
413 meta.Session.remove()
418 meta.Session.remove()
414
419
415 # In a single threaded mode server, on non sqlite db we should have
420 # In a single threaded mode server, on non sqlite db we should have
416 # '0 Current Checked out connections' at the end of a request,
421 # '0 Current Checked out connections' at the end of a request,
417 # if not, then something, somewhere is leaving a connection open
422 # if not, then something, somewhere is leaving a connection open
418 pool = meta.Base.metadata.bind.engine.pool
423 pool = meta.Base.metadata.bind.engine.pool
419 log.debug('sa pool status: %s', pool.status())
424 log.debug('sa pool status: %s', pool.status())
420
425
421 return pyramid_app_with_cleanup
426 return pyramid_app_with_cleanup
422
427
423
428
424 def sanitize_settings_and_apply_defaults(settings):
429 def sanitize_settings_and_apply_defaults(settings):
425 """
430 """
426 Applies settings defaults and does all type conversion.
431 Applies settings defaults and does all type conversion.
427
432
428 We would move all settings parsing and preparation into this place, so that
433 We would move all settings parsing and preparation into this place, so that
429 we have only one place left which deals with this part. The remaining parts
434 we have only one place left which deals with this part. The remaining parts
430 of the application would start to rely fully on well prepared settings.
435 of the application would start to rely fully on well prepared settings.
431
436
432 This piece would later be split up per topic to avoid a big fat monster
437 This piece would later be split up per topic to avoid a big fat monster
433 function.
438 function.
434 """
439 """
435
440
436 # Pyramid's mako renderer has to search in the templates folder so that the
441 # Pyramid's mako renderer has to search in the templates folder so that the
437 # old templates still work. Ported and new templates are expected to use
442 # old templates still work. Ported and new templates are expected to use
438 # real asset specifications for the includes.
443 # real asset specifications for the includes.
439 mako_directories = settings.setdefault('mako.directories', [
444 mako_directories = settings.setdefault('mako.directories', [
440 # Base templates of the original Pylons application
445 # Base templates of the original Pylons application
441 'rhodecode:templates',
446 'rhodecode:templates',
442 ])
447 ])
443 log.debug(
448 log.debug(
444 "Using the following Mako template directories: %s",
449 "Using the following Mako template directories: %s",
445 mako_directories)
450 mako_directories)
446
451
447 # Default includes, possible to change as a user
452 # Default includes, possible to change as a user
448 pyramid_includes = settings.setdefault('pyramid.includes', [
453 pyramid_includes = settings.setdefault('pyramid.includes', [
449 'rhodecode.lib.middleware.request_wrapper',
454 'rhodecode.lib.middleware.request_wrapper',
450 ])
455 ])
451 log.debug(
456 log.debug(
452 "Using the following pyramid.includes: %s",
457 "Using the following pyramid.includes: %s",
453 pyramid_includes)
458 pyramid_includes)
454
459
455 # TODO: johbo: Re-think this, usually the call to config.include
460 # TODO: johbo: Re-think this, usually the call to config.include
456 # should allow to pass in a prefix.
461 # should allow to pass in a prefix.
457 settings.setdefault('rhodecode.api.url', '/_admin/api')
462 settings.setdefault('rhodecode.api.url', '/_admin/api')
458
463
459 # Sanitize generic settings.
464 # Sanitize generic settings.
460 _list_setting(settings, 'default_encoding', 'UTF-8')
465 _list_setting(settings, 'default_encoding', 'UTF-8')
461 _bool_setting(settings, 'is_test', 'false')
466 _bool_setting(settings, 'is_test', 'false')
462 _bool_setting(settings, 'gzip_responses', 'false')
467 _bool_setting(settings, 'gzip_responses', 'false')
463
468
464 # Call split out functions that sanitize settings for each topic.
469 # Call split out functions that sanitize settings for each topic.
465 _sanitize_appenlight_settings(settings)
470 _sanitize_appenlight_settings(settings)
466 _sanitize_vcs_settings(settings)
471 _sanitize_vcs_settings(settings)
467
472
468 return settings
473 return settings
469
474
470
475
471 def _sanitize_appenlight_settings(settings):
476 def _sanitize_appenlight_settings(settings):
472 _bool_setting(settings, 'appenlight', 'false')
477 _bool_setting(settings, 'appenlight', 'false')
473
478
474
479
475 def _sanitize_vcs_settings(settings):
480 def _sanitize_vcs_settings(settings):
476 """
481 """
477 Applies settings defaults and does type conversion for all VCS related
482 Applies settings defaults and does type conversion for all VCS related
478 settings.
483 settings.
479 """
484 """
480 _string_setting(settings, 'vcs.svn.compatible_version', '')
485 _string_setting(settings, 'vcs.svn.compatible_version', '')
481 _string_setting(settings, 'git_rev_filter', '--all')
486 _string_setting(settings, 'git_rev_filter', '--all')
482 _string_setting(settings, 'vcs.hooks.protocol', 'http')
487 _string_setting(settings, 'vcs.hooks.protocol', 'http')
483 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
488 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
484 _string_setting(settings, 'vcs.server', '')
489 _string_setting(settings, 'vcs.server', '')
485 _string_setting(settings, 'vcs.server.log_level', 'debug')
490 _string_setting(settings, 'vcs.server.log_level', 'debug')
486 _string_setting(settings, 'vcs.server.protocol', 'http')
491 _string_setting(settings, 'vcs.server.protocol', 'http')
487 _bool_setting(settings, 'startup.import_repos', 'false')
492 _bool_setting(settings, 'startup.import_repos', 'false')
488 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
493 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
489 _bool_setting(settings, 'vcs.server.enable', 'true')
494 _bool_setting(settings, 'vcs.server.enable', 'true')
490 _bool_setting(settings, 'vcs.start_server', 'false')
495 _bool_setting(settings, 'vcs.start_server', 'false')
491 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
496 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
492 _int_setting(settings, 'vcs.connection_timeout', 3600)
497 _int_setting(settings, 'vcs.connection_timeout', 3600)
493
498
494 # Support legacy values of vcs.scm_app_implementation. Legacy
499 # Support legacy values of vcs.scm_app_implementation. Legacy
495 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
500 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
496 # which is now mapped to 'http'.
501 # which is now mapped to 'http'.
497 scm_app_impl = settings['vcs.scm_app_implementation']
502 scm_app_impl = settings['vcs.scm_app_implementation']
498 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
503 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
499 settings['vcs.scm_app_implementation'] = 'http'
504 settings['vcs.scm_app_implementation'] = 'http'
500
505
501
506
502 def _int_setting(settings, name, default):
507 def _int_setting(settings, name, default):
503 settings[name] = int(settings.get(name, default))
508 settings[name] = int(settings.get(name, default))
504
509
505
510
506 def _bool_setting(settings, name, default):
511 def _bool_setting(settings, name, default):
507 input = settings.get(name, default)
512 input = settings.get(name, default)
508 if isinstance(input, unicode):
513 if isinstance(input, unicode):
509 input = input.encode('utf8')
514 input = input.encode('utf8')
510 settings[name] = asbool(input)
515 settings[name] = asbool(input)
511
516
512
517
513 def _list_setting(settings, name, default):
518 def _list_setting(settings, name, default):
514 raw_value = settings.get(name, default)
519 raw_value = settings.get(name, default)
515
520
516 old_separator = ','
521 old_separator = ','
517 if old_separator in raw_value:
522 if old_separator in raw_value:
518 # If we get a comma separated list, pass it to our own function.
523 # If we get a comma separated list, pass it to our own function.
519 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
524 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
520 else:
525 else:
521 # Otherwise we assume it uses pyramids space/newline separation.
526 # Otherwise we assume it uses pyramids space/newline separation.
522 settings[name] = aslist(raw_value)
527 settings[name] = aslist(raw_value)
523
528
524
529
525 def _string_setting(settings, name, default, lower=True):
530 def _string_setting(settings, name, default, lower=True):
526 value = settings.get(name, default)
531 value = settings.get(name, default)
527 if lower:
532 if lower:
528 value = value.lower()
533 value = value.lower()
529 settings[name] = value
534 settings[name] = value
@@ -1,660 +1,672 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 ipaddress
31 import ipaddress
31 import pyramid.threadlocal
32 import pyramid.threadlocal
32
33
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from pylons import config, tmpl_context as c, request, url
37 from pylons import config, tmpl_context as c, request, url
37 from pylons.controllers import WSGIController
38 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
39 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
40 from pylons.i18n import translation
40 # marcink: don't remove this import
41 # marcink: don't remove this import
41 from pylons.templating import render_mako, pylons_globals, literal, cached_template
42 from pylons.templating import render_mako, pylons_globals, literal, cached_template
42 from pylons.i18n.translation import _
43 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
44 from webob.exc import HTTPFound
44
45
45
46
46 import rhodecode
47 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
53 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
55 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
56 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
59 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.db import Repository, User, ChangesetComment
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
64
64
65
65 log = logging.getLogger(__name__)
66 log = logging.getLogger(__name__)
66
67
67
68
68 # hack to make the migration to pyramid easier
69 # hack to make the migration to pyramid easier
69 def render(template_name, extra_vars=None, cache_key=None,
70 def render(template_name, extra_vars=None, cache_key=None,
70 cache_type=None, cache_expire=None):
71 cache_type=None, cache_expire=None):
71 """Render a template with Mako
72 """Render a template with Mako
72
73
73 Accepts the cache options ``cache_key``, ``cache_type``, and
74 Accepts the cache options ``cache_key``, ``cache_type``, and
74 ``cache_expire``.
75 ``cache_expire``.
75
76
76 """
77 """
77 # Create a render callable for the cache function
78 # Create a render callable for the cache function
78 def render_template():
79 def render_template():
79 # Pull in extra vars if needed
80 # Pull in extra vars if needed
80 globs = extra_vars or {}
81 globs = extra_vars or {}
81
82
82 # Second, get the globals
83 # Second, get the globals
83 globs.update(pylons_globals())
84 globs.update(pylons_globals())
84
85
85 globs['_ungettext'] = globs['ungettext']
86 globs['_ungettext'] = globs['ungettext']
86 # Grab a template reference
87 # Grab a template reference
87 template = globs['app_globals'].mako_lookup.get_template(template_name)
88 template = globs['app_globals'].mako_lookup.get_template(template_name)
88
89
89 return literal(template.render_unicode(**globs))
90 return literal(template.render_unicode(**globs))
90
91
91 return cached_template(template_name, render_template, cache_key=cache_key,
92 return cached_template(template_name, render_template, cache_key=cache_key,
92 cache_type=cache_type, cache_expire=cache_expire)
93 cache_type=cache_type, cache_expire=cache_expire)
93
94
94 def _filter_proxy(ip):
95 def _filter_proxy(ip):
95 """
96 """
96 Passed in IP addresses in HEADERS can be in a special format of multiple
97 Passed in IP addresses in HEADERS can be in a special format of multiple
97 ips. Those comma separated IPs are passed from various proxies in the
98 ips. Those comma separated IPs are passed from various proxies in the
98 chain of request processing. The left-most being the original client.
99 chain of request processing. The left-most being the original client.
99 We only care about the first IP which came from the org. client.
100 We only care about the first IP which came from the org. client.
100
101
101 :param ip: ip string from headers
102 :param ip: ip string from headers
102 """
103 """
103 if ',' in ip:
104 if ',' in ip:
104 _ips = ip.split(',')
105 _ips = ip.split(',')
105 _first_ip = _ips[0].strip()
106 _first_ip = _ips[0].strip()
106 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
107 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
107 return _first_ip
108 return _first_ip
108 return ip
109 return ip
109
110
110
111
111 def _filter_port(ip):
112 def _filter_port(ip):
112 """
113 """
113 Removes a port from ip, there are 4 main cases to handle here.
114 Removes a port from ip, there are 4 main cases to handle here.
114 - ipv4 eg. 127.0.0.1
115 - ipv4 eg. 127.0.0.1
115 - ipv6 eg. ::1
116 - ipv6 eg. ::1
116 - ipv4+port eg. 127.0.0.1:8080
117 - ipv4+port eg. 127.0.0.1:8080
117 - ipv6+port eg. [::1]:8080
118 - ipv6+port eg. [::1]:8080
118
119
119 :param ip:
120 :param ip:
120 """
121 """
121 def is_ipv6(ip_addr):
122 def is_ipv6(ip_addr):
122 if hasattr(socket, 'inet_pton'):
123 if hasattr(socket, 'inet_pton'):
123 try:
124 try:
124 socket.inet_pton(socket.AF_INET6, ip_addr)
125 socket.inet_pton(socket.AF_INET6, ip_addr)
125 except socket.error:
126 except socket.error:
126 return False
127 return False
127 else:
128 else:
128 # fallback to ipaddress
129 # fallback to ipaddress
129 try:
130 try:
130 ipaddress.IPv6Address(safe_unicode(ip_addr))
131 ipaddress.IPv6Address(safe_unicode(ip_addr))
131 except Exception:
132 except Exception:
132 return False
133 return False
133 return True
134 return True
134
135
135 if ':' not in ip: # must be ipv4 pure ip
136 if ':' not in ip: # must be ipv4 pure ip
136 return ip
137 return ip
137
138
138 if '[' in ip and ']' in ip: # ipv6 with port
139 if '[' in ip and ']' in ip: # ipv6 with port
139 return ip.split(']')[0][1:].lower()
140 return ip.split(']')[0][1:].lower()
140
141
141 # must be ipv6 or ipv4 with port
142 # must be ipv6 or ipv4 with port
142 if is_ipv6(ip):
143 if is_ipv6(ip):
143 return ip
144 return ip
144 else:
145 else:
145 ip, _port = ip.split(':')[:2] # means ipv4+port
146 ip, _port = ip.split(':')[:2] # means ipv4+port
146 return ip
147 return ip
147
148
148
149
149 def get_ip_addr(environ):
150 def get_ip_addr(environ):
150 proxy_key = 'HTTP_X_REAL_IP'
151 proxy_key = 'HTTP_X_REAL_IP'
151 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
152 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
152 def_key = 'REMOTE_ADDR'
153 def_key = 'REMOTE_ADDR'
153 _filters = lambda x: _filter_port(_filter_proxy(x))
154 _filters = lambda x: _filter_port(_filter_proxy(x))
154
155
155 ip = environ.get(proxy_key)
156 ip = environ.get(proxy_key)
156 if ip:
157 if ip:
157 return _filters(ip)
158 return _filters(ip)
158
159
159 ip = environ.get(proxy_key2)
160 ip = environ.get(proxy_key2)
160 if ip:
161 if ip:
161 return _filters(ip)
162 return _filters(ip)
162
163
163 ip = environ.get(def_key, '0.0.0.0')
164 ip = environ.get(def_key, '0.0.0.0')
164 return _filters(ip)
165 return _filters(ip)
165
166
166
167
167 def get_server_ip_addr(environ, log_errors=True):
168 def get_server_ip_addr(environ, log_errors=True):
168 hostname = environ.get('SERVER_NAME')
169 hostname = environ.get('SERVER_NAME')
169 try:
170 try:
170 return socket.gethostbyname(hostname)
171 return socket.gethostbyname(hostname)
171 except Exception as e:
172 except Exception as e:
172 if log_errors:
173 if log_errors:
173 # in some cases this lookup is not possible, and we don't want to
174 # in some cases this lookup is not possible, and we don't want to
174 # make it an exception in logs
175 # make it an exception in logs
175 log.exception('Could not retrieve server ip address: %s', e)
176 log.exception('Could not retrieve server ip address: %s', e)
176 return hostname
177 return hostname
177
178
178
179
179 def get_server_port(environ):
180 def get_server_port(environ):
180 return environ.get('SERVER_PORT')
181 return environ.get('SERVER_PORT')
181
182
182
183
183 def get_access_path(environ):
184 def get_access_path(environ):
184 path = environ.get('PATH_INFO')
185 path = environ.get('PATH_INFO')
185 org_req = environ.get('pylons.original_request')
186 org_req = environ.get('pylons.original_request')
186 if org_req:
187 if org_req:
187 path = org_req.environ.get('PATH_INFO')
188 path = org_req.environ.get('PATH_INFO')
188 return path
189 return path
189
190
190
191
191 def get_user_agent(environ):
192 def get_user_agent(environ):
192 return environ.get('HTTP_USER_AGENT')
193 return environ.get('HTTP_USER_AGENT')
193
194
194
195
195 def vcs_operation_context(
196 def vcs_operation_context(
196 environ, repo_name, username, action, scm, check_locking=True,
197 environ, repo_name, username, action, scm, check_locking=True,
197 is_shadow_repo=False):
198 is_shadow_repo=False):
198 """
199 """
199 Generate the context for a vcs operation, e.g. push or pull.
200 Generate the context for a vcs operation, e.g. push or pull.
200
201
201 This context is passed over the layers so that hooks triggered by the
202 This context is passed over the layers so that hooks triggered by the
202 vcs operation know details like the user, the user's IP address etc.
203 vcs operation know details like the user, the user's IP address etc.
203
204
204 :param check_locking: Allows to switch of the computation of the locking
205 :param check_locking: Allows to switch of the computation of the locking
205 data. This serves mainly the need of the simplevcs middleware to be
206 data. This serves mainly the need of the simplevcs middleware to be
206 able to disable this for certain operations.
207 able to disable this for certain operations.
207
208
208 """
209 """
209 # Tri-state value: False: unlock, None: nothing, True: lock
210 # Tri-state value: False: unlock, None: nothing, True: lock
210 make_lock = None
211 make_lock = None
211 locked_by = [None, None, None]
212 locked_by = [None, None, None]
212 is_anonymous = username == User.DEFAULT_USER
213 is_anonymous = username == User.DEFAULT_USER
213 if not is_anonymous and check_locking:
214 if not is_anonymous and check_locking:
214 log.debug('Checking locking on repository "%s"', repo_name)
215 log.debug('Checking locking on repository "%s"', repo_name)
215 user = User.get_by_username(username)
216 user = User.get_by_username(username)
216 repo = Repository.get_by_repo_name(repo_name)
217 repo = Repository.get_by_repo_name(repo_name)
217 make_lock, __, locked_by = repo.get_locking_state(
218 make_lock, __, locked_by = repo.get_locking_state(
218 action, user.user_id)
219 action, user.user_id)
219
220
220 settings_model = VcsSettingsModel(repo=repo_name)
221 settings_model = VcsSettingsModel(repo=repo_name)
221 ui_settings = settings_model.get_ui_settings()
222 ui_settings = settings_model.get_ui_settings()
222
223
223 extras = {
224 extras = {
224 'ip': get_ip_addr(environ),
225 'ip': get_ip_addr(environ),
225 'username': username,
226 'username': username,
226 'action': action,
227 'action': action,
227 'repository': repo_name,
228 'repository': repo_name,
228 'scm': scm,
229 'scm': scm,
229 'config': rhodecode.CONFIG['__file__'],
230 'config': rhodecode.CONFIG['__file__'],
230 'make_lock': make_lock,
231 'make_lock': make_lock,
231 'locked_by': locked_by,
232 'locked_by': locked_by,
232 'server_url': utils2.get_server_url(environ),
233 'server_url': utils2.get_server_url(environ),
233 'user_agent': get_user_agent(environ),
234 'user_agent': get_user_agent(environ),
234 'hooks': get_enabled_hook_classes(ui_settings),
235 'hooks': get_enabled_hook_classes(ui_settings),
235 'is_shadow_repo': is_shadow_repo,
236 'is_shadow_repo': is_shadow_repo,
236 }
237 }
237 return extras
238 return extras
238
239
239
240
240 class BasicAuth(AuthBasicAuthenticator):
241 class BasicAuth(AuthBasicAuthenticator):
241
242
242 def __init__(self, realm, authfunc, registry, auth_http_code=None,
243 def __init__(self, realm, authfunc, registry, auth_http_code=None,
243 initial_call_detection=False, acl_repo_name=None):
244 initial_call_detection=False, acl_repo_name=None):
244 self.realm = realm
245 self.realm = realm
245 self.initial_call = initial_call_detection
246 self.initial_call = initial_call_detection
246 self.authfunc = authfunc
247 self.authfunc = authfunc
247 self.registry = registry
248 self.registry = registry
248 self.acl_repo_name = acl_repo_name
249 self.acl_repo_name = acl_repo_name
249 self._rc_auth_http_code = auth_http_code
250 self._rc_auth_http_code = auth_http_code
250
251
251 def _get_response_from_code(self, http_code):
252 def _get_response_from_code(self, http_code):
252 try:
253 try:
253 return get_exception(safe_int(http_code))
254 return get_exception(safe_int(http_code))
254 except Exception:
255 except Exception:
255 log.exception('Failed to fetch response for code %s' % http_code)
256 log.exception('Failed to fetch response for code %s' % http_code)
256 return HTTPForbidden
257 return HTTPForbidden
257
258
258 def build_authentication(self):
259 def build_authentication(self):
259 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
260 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
260 if self._rc_auth_http_code and not self.initial_call:
261 if self._rc_auth_http_code and not self.initial_call:
261 # return alternative HTTP code if alternative http return code
262 # return alternative HTTP code if alternative http return code
262 # is specified in RhodeCode config, but ONLY if it's not the
263 # is specified in RhodeCode config, but ONLY if it's not the
263 # FIRST call
264 # FIRST call
264 custom_response_klass = self._get_response_from_code(
265 custom_response_klass = self._get_response_from_code(
265 self._rc_auth_http_code)
266 self._rc_auth_http_code)
266 return custom_response_klass(headers=head)
267 return custom_response_klass(headers=head)
267 return HTTPUnauthorized(headers=head)
268 return HTTPUnauthorized(headers=head)
268
269
269 def authenticate(self, environ):
270 def authenticate(self, environ):
270 authorization = AUTHORIZATION(environ)
271 authorization = AUTHORIZATION(environ)
271 if not authorization:
272 if not authorization:
272 return self.build_authentication()
273 return self.build_authentication()
273 (authmeth, auth) = authorization.split(' ', 1)
274 (authmeth, auth) = authorization.split(' ', 1)
274 if 'basic' != authmeth.lower():
275 if 'basic' != authmeth.lower():
275 return self.build_authentication()
276 return self.build_authentication()
276 auth = auth.strip().decode('base64')
277 auth = auth.strip().decode('base64')
277 _parts = auth.split(':', 1)
278 _parts = auth.split(':', 1)
278 if len(_parts) == 2:
279 if len(_parts) == 2:
279 username, password = _parts
280 username, password = _parts
280 if self.authfunc(
281 if self.authfunc(
281 username, password, environ, VCS_TYPE,
282 username, password, environ, VCS_TYPE,
282 registry=self.registry, acl_repo_name=self.acl_repo_name):
283 registry=self.registry, acl_repo_name=self.acl_repo_name):
283 return username
284 return username
284 if username and password:
285 if username and password:
285 # we mark that we actually executed authentication once, at
286 # we mark that we actually executed authentication once, at
286 # that point we can use the alternative auth code
287 # that point we can use the alternative auth code
287 self.initial_call = False
288 self.initial_call = False
288
289
289 return self.build_authentication()
290 return self.build_authentication()
290
291
291 __call__ = authenticate
292 __call__ = authenticate
292
293
293
294
294 def calculate_version_hash():
295 def calculate_version_hash():
295 return md5(
296 return md5(
296 config.get('beaker.session.secret', '') +
297 config.get('beaker.session.secret', '') +
297 rhodecode.__version__)[:8]
298 rhodecode.__version__)[:8]
298
299
299
300
300 def get_current_lang(request):
301 def get_current_lang(request):
301 # NOTE(marcink): remove after pyramid move
302 # NOTE(marcink): remove after pyramid move
302 try:
303 try:
303 return translation.get_lang()[0]
304 return translation.get_lang()[0]
304 except:
305 except:
305 pass
306 pass
306
307
307 return getattr(request, '_LOCALE_', request.locale_name)
308 return getattr(request, '_LOCALE_', request.locale_name)
308
309
309
310
310 def attach_context_attributes(context, request, user_id):
311 def attach_context_attributes(context, request, user_id):
311 """
312 """
312 Attach variables into template context called `c`, please note that
313 Attach variables into template context called `c`, please note that
313 request could be pylons or pyramid request in here.
314 request could be pylons or pyramid request in here.
314 """
315 """
315
316
316 rc_config = SettingsModel().get_all_settings(cache=True)
317 rc_config = SettingsModel().get_all_settings(cache=True)
317
318
318 context.rhodecode_version = rhodecode.__version__
319 context.rhodecode_version = rhodecode.__version__
319 context.rhodecode_edition = config.get('rhodecode.edition')
320 context.rhodecode_edition = config.get('rhodecode.edition')
320 # unique secret + version does not leak the version but keep consistency
321 # unique secret + version does not leak the version but keep consistency
321 context.rhodecode_version_hash = calculate_version_hash()
322 context.rhodecode_version_hash = calculate_version_hash()
322
323
323 # Default language set for the incoming request
324 # Default language set for the incoming request
324 context.language = get_current_lang(request)
325 context.language = get_current_lang(request)
325
326
326 # Visual options
327 # Visual options
327 context.visual = AttributeDict({})
328 context.visual = AttributeDict({})
328
329
329 # DB stored Visual Items
330 # DB stored Visual Items
330 context.visual.show_public_icon = str2bool(
331 context.visual.show_public_icon = str2bool(
331 rc_config.get('rhodecode_show_public_icon'))
332 rc_config.get('rhodecode_show_public_icon'))
332 context.visual.show_private_icon = str2bool(
333 context.visual.show_private_icon = str2bool(
333 rc_config.get('rhodecode_show_private_icon'))
334 rc_config.get('rhodecode_show_private_icon'))
334 context.visual.stylify_metatags = str2bool(
335 context.visual.stylify_metatags = str2bool(
335 rc_config.get('rhodecode_stylify_metatags'))
336 rc_config.get('rhodecode_stylify_metatags'))
336 context.visual.dashboard_items = safe_int(
337 context.visual.dashboard_items = safe_int(
337 rc_config.get('rhodecode_dashboard_items', 100))
338 rc_config.get('rhodecode_dashboard_items', 100))
338 context.visual.admin_grid_items = safe_int(
339 context.visual.admin_grid_items = safe_int(
339 rc_config.get('rhodecode_admin_grid_items', 100))
340 rc_config.get('rhodecode_admin_grid_items', 100))
340 context.visual.repository_fields = str2bool(
341 context.visual.repository_fields = str2bool(
341 rc_config.get('rhodecode_repository_fields'))
342 rc_config.get('rhodecode_repository_fields'))
342 context.visual.show_version = str2bool(
343 context.visual.show_version = str2bool(
343 rc_config.get('rhodecode_show_version'))
344 rc_config.get('rhodecode_show_version'))
344 context.visual.use_gravatar = str2bool(
345 context.visual.use_gravatar = str2bool(
345 rc_config.get('rhodecode_use_gravatar'))
346 rc_config.get('rhodecode_use_gravatar'))
346 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
347 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
347 context.visual.default_renderer = rc_config.get(
348 context.visual.default_renderer = rc_config.get(
348 'rhodecode_markup_renderer', 'rst')
349 'rhodecode_markup_renderer', 'rst')
349 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
350 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
350 context.visual.rhodecode_support_url = \
351 context.visual.rhodecode_support_url = \
351 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
352 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
352
353
353 context.visual.affected_files_cut_off = 60
354 context.visual.affected_files_cut_off = 60
354
355
355 context.pre_code = rc_config.get('rhodecode_pre_code')
356 context.pre_code = rc_config.get('rhodecode_pre_code')
356 context.post_code = rc_config.get('rhodecode_post_code')
357 context.post_code = rc_config.get('rhodecode_post_code')
357 context.rhodecode_name = rc_config.get('rhodecode_title')
358 context.rhodecode_name = rc_config.get('rhodecode_title')
358 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
359 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
359 # if we have specified default_encoding in the request, it has more
360 # if we have specified default_encoding in the request, it has more
360 # priority
361 # priority
361 if request.GET.get('default_encoding'):
362 if request.GET.get('default_encoding'):
362 context.default_encodings.insert(0, request.GET.get('default_encoding'))
363 context.default_encodings.insert(0, request.GET.get('default_encoding'))
363 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
364 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
364
365
365 # INI stored
366 # INI stored
366 context.labs_active = str2bool(
367 context.labs_active = str2bool(
367 config.get('labs_settings_active', 'false'))
368 config.get('labs_settings_active', 'false'))
368 context.visual.allow_repo_location_change = str2bool(
369 context.visual.allow_repo_location_change = str2bool(
369 config.get('allow_repo_location_change', True))
370 config.get('allow_repo_location_change', True))
370 context.visual.allow_custom_hooks_settings = str2bool(
371 context.visual.allow_custom_hooks_settings = str2bool(
371 config.get('allow_custom_hooks_settings', True))
372 config.get('allow_custom_hooks_settings', True))
372 context.debug_style = str2bool(config.get('debug_style', False))
373 context.debug_style = str2bool(config.get('debug_style', False))
373
374
374 context.rhodecode_instanceid = config.get('instance_id')
375 context.rhodecode_instanceid = config.get('instance_id')
375
376
376 context.visual.cut_off_limit_diff = safe_int(
377 context.visual.cut_off_limit_diff = safe_int(
377 config.get('cut_off_limit_diff'))
378 config.get('cut_off_limit_diff'))
378 context.visual.cut_off_limit_file = safe_int(
379 context.visual.cut_off_limit_file = safe_int(
379 config.get('cut_off_limit_file'))
380 config.get('cut_off_limit_file'))
380
381
381 # AppEnlight
382 # AppEnlight
382 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
383 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
383 context.appenlight_api_public_key = config.get(
384 context.appenlight_api_public_key = config.get(
384 'appenlight.api_public_key', '')
385 'appenlight.api_public_key', '')
385 context.appenlight_server_url = config.get('appenlight.server_url', '')
386 context.appenlight_server_url = config.get('appenlight.server_url', '')
386
387
387 # JS template context
388 # JS template context
388 context.template_context = {
389 context.template_context = {
389 'repo_name': None,
390 'repo_name': None,
390 'repo_type': None,
391 'repo_type': None,
391 'repo_landing_commit': None,
392 'repo_landing_commit': None,
392 'rhodecode_user': {
393 'rhodecode_user': {
393 'username': None,
394 'username': None,
394 'email': None,
395 'email': None,
395 'notification_status': False
396 'notification_status': False
396 },
397 },
397 'visual': {
398 'visual': {
398 'default_renderer': None
399 'default_renderer': None
399 },
400 },
400 'commit_data': {
401 'commit_data': {
401 'commit_id': None
402 'commit_id': None
402 },
403 },
403 'pull_request_data': {'pull_request_id': None},
404 'pull_request_data': {'pull_request_id': None},
404 'timeago': {
405 'timeago': {
405 'refresh_time': 120 * 1000,
406 'refresh_time': 120 * 1000,
406 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
407 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
407 },
408 },
408 'pylons_dispatch': {
409 'pylons_dispatch': {
409 # 'controller': request.environ['pylons.routes_dict']['controller'],
410 # 'controller': request.environ['pylons.routes_dict']['controller'],
410 # 'action': request.environ['pylons.routes_dict']['action'],
411 # 'action': request.environ['pylons.routes_dict']['action'],
411 },
412 },
412 'pyramid_dispatch': {
413 'pyramid_dispatch': {
413
414
414 },
415 },
415 'extra': {'plugins': {}}
416 'extra': {'plugins': {}}
416 }
417 }
417 # END CONFIG VARS
418 # END CONFIG VARS
418
419
419 # TODO: This dosn't work when called from pylons compatibility tween.
420 # TODO: This dosn't work when called from pylons compatibility tween.
420 # Fix this and remove it from base controller.
421 # Fix this and remove it from base controller.
421 # context.repo_name = get_repo_slug(request) # can be empty
422 # context.repo_name = get_repo_slug(request) # can be empty
422
423
423 diffmode = 'sideside'
424 diffmode = 'sideside'
424 if request.GET.get('diffmode'):
425 if request.GET.get('diffmode'):
425 if request.GET['diffmode'] == 'unified':
426 if request.GET['diffmode'] == 'unified':
426 diffmode = 'unified'
427 diffmode = 'unified'
427 elif request.session.get('diffmode'):
428 elif request.session.get('diffmode'):
428 diffmode = request.session['diffmode']
429 diffmode = request.session['diffmode']
429
430
430 context.diffmode = diffmode
431 context.diffmode = diffmode
431
432
432 if request.session.get('diffmode') != diffmode:
433 if request.session.get('diffmode') != diffmode:
433 request.session['diffmode'] = diffmode
434 request.session['diffmode'] = diffmode
434
435
435 context.csrf_token = auth.get_csrf_token(session=request.session)
436 context.csrf_token = auth.get_csrf_token(session=request.session)
436 context.backends = rhodecode.BACKENDS.keys()
437 context.backends = rhodecode.BACKENDS.keys()
437 context.backends.sort()
438 context.backends.sort()
438 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
439 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(user_id)
439
440
440 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
441 # NOTE(marcink): when migrated to pyramid we don't need to set this anymore,
441 # given request will ALWAYS be pyramid one
442 # given request will ALWAYS be pyramid one
442 pyramid_request = pyramid.threadlocal.get_current_request()
443 pyramid_request = pyramid.threadlocal.get_current_request()
443 context.pyramid_request = pyramid_request
444 context.pyramid_request = pyramid_request
444
445
445 # web case
446 # web case
446 if hasattr(pyramid_request, 'user'):
447 if hasattr(pyramid_request, 'user'):
447 context.auth_user = pyramid_request.user
448 context.auth_user = pyramid_request.user
448 context.rhodecode_user = pyramid_request.user
449 context.rhodecode_user = pyramid_request.user
449
450
450 # api case
451 # api case
451 if hasattr(pyramid_request, 'rpc_user'):
452 if hasattr(pyramid_request, 'rpc_user'):
452 context.auth_user = pyramid_request.rpc_user
453 context.auth_user = pyramid_request.rpc_user
453 context.rhodecode_user = pyramid_request.rpc_user
454 context.rhodecode_user = pyramid_request.rpc_user
454
455
455 # attach the whole call context to the request
456 # attach the whole call context to the request
456 request.call_context = context
457 request.call_context = context
457
458
458
459
459 def get_auth_user(request):
460 def get_auth_user(request):
460 environ = request.environ
461 environ = request.environ
461 session = request.session
462 session = request.session
462
463
463 ip_addr = get_ip_addr(environ)
464 ip_addr = get_ip_addr(environ)
464 # make sure that we update permissions each time we call controller
465 # make sure that we update permissions each time we call controller
465 _auth_token = (request.GET.get('auth_token', '') or
466 _auth_token = (request.GET.get('auth_token', '') or
466 request.GET.get('api_key', ''))
467 request.GET.get('api_key', ''))
467
468
468 if _auth_token:
469 if _auth_token:
469 # when using API_KEY we assume user exists, and
470 # when using API_KEY we assume user exists, and
470 # doesn't need auth based on cookies.
471 # doesn't need auth based on cookies.
471 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
472 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
472 authenticated = False
473 authenticated = False
473 else:
474 else:
474 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
475 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
475 try:
476 try:
476 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
477 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
477 ip_addr=ip_addr)
478 ip_addr=ip_addr)
478 except UserCreationError as e:
479 except UserCreationError as e:
479 h.flash(e, 'error')
480 h.flash(e, 'error')
480 # container auth or other auth functions that create users
481 # container auth or other auth functions that create users
481 # on the fly can throw this exception signaling that there's
482 # on the fly can throw this exception signaling that there's
482 # issue with user creation, explanation should be provided
483 # issue with user creation, explanation should be provided
483 # in Exception itself. We then create a simple blank
484 # in Exception itself. We then create a simple blank
484 # AuthUser
485 # AuthUser
485 auth_user = AuthUser(ip_addr=ip_addr)
486 auth_user = AuthUser(ip_addr=ip_addr)
486
487
487 if password_changed(auth_user, session):
488 if password_changed(auth_user, session):
488 session.invalidate()
489 session.invalidate()
489 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
490 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
490 auth_user = AuthUser(ip_addr=ip_addr)
491 auth_user = AuthUser(ip_addr=ip_addr)
491
492
492 authenticated = cookie_store.get('is_authenticated')
493 authenticated = cookie_store.get('is_authenticated')
493
494
494 if not auth_user.is_authenticated and auth_user.is_user_object:
495 if not auth_user.is_authenticated and auth_user.is_user_object:
495 # user is not authenticated and not empty
496 # user is not authenticated and not empty
496 auth_user.set_authenticated(authenticated)
497 auth_user.set_authenticated(authenticated)
497
498
498 return auth_user
499 return auth_user
499
500
500
501
501 class BaseController(WSGIController):
502 class BaseController(WSGIController):
502
503
503 def __before__(self):
504 def __before__(self):
504 """
505 """
505 __before__ is called before controller methods and after __call__
506 __before__ is called before controller methods and after __call__
506 """
507 """
507 # on each call propagate settings calls into global settings.
508 # on each call propagate settings calls into global settings.
508 set_rhodecode_config(config)
509 set_rhodecode_config(config)
509 attach_context_attributes(c, request, self._rhodecode_user.user_id)
510 attach_context_attributes(c, request, self._rhodecode_user.user_id)
510
511
511 # TODO: Remove this when fixed in attach_context_attributes()
512 # TODO: Remove this when fixed in attach_context_attributes()
512 c.repo_name = get_repo_slug(request) # can be empty
513 c.repo_name = get_repo_slug(request) # can be empty
513
514
514 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
515 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
515 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
516 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
516 self.sa = meta.Session
517 self.sa = meta.Session
517 self.scm_model = ScmModel(self.sa)
518 self.scm_model = ScmModel(self.sa)
518
519
519 # set user language
520 # set user language
520 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
521 user_lang = getattr(c.pyramid_request, '_LOCALE_', None)
521 if user_lang:
522 if user_lang:
522 translation.set_lang(user_lang)
523 translation.set_lang(user_lang)
523 log.debug('set language to %s for user %s',
524 log.debug('set language to %s for user %s',
524 user_lang, self._rhodecode_user)
525 user_lang, self._rhodecode_user)
525
526
526 def _dispatch_redirect(self, with_url, environ, start_response):
527 def _dispatch_redirect(self, with_url, environ, start_response):
527 resp = HTTPFound(with_url)
528 resp = HTTPFound(with_url)
528 environ['SCRIPT_NAME'] = '' # handle prefix middleware
529 environ['SCRIPT_NAME'] = '' # handle prefix middleware
529 environ['PATH_INFO'] = with_url
530 environ['PATH_INFO'] = with_url
530 return resp(environ, start_response)
531 return resp(environ, start_response)
531
532
532 def __call__(self, environ, start_response):
533 def __call__(self, environ, start_response):
533 """Invoke the Controller"""
534 """Invoke the Controller"""
534 # WSGIController.__call__ dispatches to the Controller method
535 # WSGIController.__call__ dispatches to the Controller method
535 # the request is routed to. This routing information is
536 # the request is routed to. This routing information is
536 # available in environ['pylons.routes_dict']
537 # available in environ['pylons.routes_dict']
537 from rhodecode.lib import helpers as h
538 from rhodecode.lib import helpers as h
538
539
539 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
540 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
540 if environ.get('debugtoolbar.wants_pylons_context', False):
541 if environ.get('debugtoolbar.wants_pylons_context', False):
541 environ['debugtoolbar.pylons_context'] = c._current_obj()
542 environ['debugtoolbar.pylons_context'] = c._current_obj()
542
543
543 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
544 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
544 environ['pylons.routes_dict']['action']])
545 environ['pylons.routes_dict']['action']])
545
546
546 self.rc_config = SettingsModel().get_all_settings(cache=True)
547 self.rc_config = SettingsModel().get_all_settings(cache=True)
547 self.ip_addr = get_ip_addr(environ)
548 self.ip_addr = get_ip_addr(environ)
548
549
549 # The rhodecode auth user is looked up and passed through the
550 # The rhodecode auth user is looked up and passed through the
550 # environ by the pylons compatibility tween in pyramid.
551 # environ by the pylons compatibility tween in pyramid.
551 # So we can just grab it from there.
552 # So we can just grab it from there.
552 auth_user = environ['rc_auth_user']
553 auth_user = environ['rc_auth_user']
553
554
554 # set globals for auth user
555 # set globals for auth user
555 request.user = auth_user
556 request.user = auth_user
556 self._rhodecode_user = auth_user
557 self._rhodecode_user = auth_user
557
558
558 log.info('IP: %s User: %s accessed %s [%s]' % (
559 log.info('IP: %s User: %s accessed %s [%s]' % (
559 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
560 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
560 _route_name)
561 _route_name)
561 )
562 )
562
563
563 user_obj = auth_user.get_instance()
564 user_obj = auth_user.get_instance()
564 if user_obj and user_obj.user_data.get('force_password_change'):
565 if user_obj and user_obj.user_data.get('force_password_change'):
565 h.flash('You are required to change your password', 'warning',
566 h.flash('You are required to change your password', 'warning',
566 ignore_duplicate=True)
567 ignore_duplicate=True)
567 return self._dispatch_redirect(
568 return self._dispatch_redirect(
568 url('my_account_password'), environ, start_response)
569 url('my_account_password'), environ, start_response)
569
570
570 return WSGIController.__call__(self, environ, start_response)
571 return WSGIController.__call__(self, environ, start_response)
571
572
572
573
574 def h_filter(s):
575 """
576 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
577 we wrap this with additional functionality that converts None to empty
578 strings
579 """
580 if s is None:
581 return markupsafe.Markup()
582 return markupsafe.escape(s)
583
584
573 class BaseRepoController(BaseController):
585 class BaseRepoController(BaseController):
574 """
586 """
575 Base class for controllers responsible for loading all needed data for
587 Base class for controllers responsible for loading all needed data for
576 repository loaded items are
588 repository loaded items are
577
589
578 c.rhodecode_repo: instance of scm repository
590 c.rhodecode_repo: instance of scm repository
579 c.rhodecode_db_repo: instance of db
591 c.rhodecode_db_repo: instance of db
580 c.repository_requirements_missing: shows that repository specific data
592 c.repository_requirements_missing: shows that repository specific data
581 could not be displayed due to the missing requirements
593 could not be displayed due to the missing requirements
582 c.repository_pull_requests: show number of open pull requests
594 c.repository_pull_requests: show number of open pull requests
583 """
595 """
584
596
585 def __before__(self):
597 def __before__(self):
586 super(BaseRepoController, self).__before__()
598 super(BaseRepoController, self).__before__()
587 if c.repo_name: # extracted from routes
599 if c.repo_name: # extracted from routes
588 db_repo = Repository.get_by_repo_name(c.repo_name)
600 db_repo = Repository.get_by_repo_name(c.repo_name)
589 if not db_repo:
601 if not db_repo:
590 return
602 return
591
603
592 log.debug(
604 log.debug(
593 'Found repository in database %s with state `%s`',
605 'Found repository in database %s with state `%s`',
594 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
606 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
595 route = getattr(request.environ.get('routes.route'), 'name', '')
607 route = getattr(request.environ.get('routes.route'), 'name', '')
596
608
597 # allow to delete repos that are somehow damages in filesystem
609 # allow to delete repos that are somehow damages in filesystem
598 if route in ['delete_repo']:
610 if route in ['delete_repo']:
599 return
611 return
600
612
601 if db_repo.repo_state in [Repository.STATE_PENDING]:
613 if db_repo.repo_state in [Repository.STATE_PENDING]:
602 if route in ['repo_creating_home']:
614 if route in ['repo_creating_home']:
603 return
615 return
604 check_url = url('repo_creating_home', repo_name=c.repo_name)
616 check_url = url('repo_creating_home', repo_name=c.repo_name)
605 return redirect(check_url)
617 return redirect(check_url)
606
618
607 self.rhodecode_db_repo = db_repo
619 self.rhodecode_db_repo = db_repo
608
620
609 missing_requirements = False
621 missing_requirements = False
610 try:
622 try:
611 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
623 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
612 except RepositoryRequirementError as e:
624 except RepositoryRequirementError as e:
613 missing_requirements = True
625 missing_requirements = True
614 self._handle_missing_requirements(e)
626 self._handle_missing_requirements(e)
615
627
616 if self.rhodecode_repo is None and not missing_requirements:
628 if self.rhodecode_repo is None and not missing_requirements:
617 log.error('%s this repository is present in database but it '
629 log.error('%s this repository is present in database but it '
618 'cannot be created as an scm instance', c.repo_name)
630 'cannot be created as an scm instance', c.repo_name)
619
631
620 h.flash(_(
632 h.flash(_(
621 "The repository at %(repo_name)s cannot be located.") %
633 "The repository at %(repo_name)s cannot be located.") %
622 {'repo_name': c.repo_name},
634 {'repo_name': c.repo_name},
623 category='error', ignore_duplicate=True)
635 category='error', ignore_duplicate=True)
624 redirect(h.route_path('home'))
636 redirect(h.route_path('home'))
625
637
626 # update last change according to VCS data
638 # update last change according to VCS data
627 if not missing_requirements:
639 if not missing_requirements:
628 commit = db_repo.get_commit(
640 commit = db_repo.get_commit(
629 pre_load=["author", "date", "message", "parents"])
641 pre_load=["author", "date", "message", "parents"])
630 db_repo.update_commit_cache(commit)
642 db_repo.update_commit_cache(commit)
631
643
632 # Prepare context
644 # Prepare context
633 c.rhodecode_db_repo = db_repo
645 c.rhodecode_db_repo = db_repo
634 c.rhodecode_repo = self.rhodecode_repo
646 c.rhodecode_repo = self.rhodecode_repo
635 c.repository_requirements_missing = missing_requirements
647 c.repository_requirements_missing = missing_requirements
636
648
637 self._update_global_counters(self.scm_model, db_repo)
649 self._update_global_counters(self.scm_model, db_repo)
638
650
639 def _update_global_counters(self, scm_model, db_repo):
651 def _update_global_counters(self, scm_model, db_repo):
640 """
652 """
641 Base variables that are exposed to every page of repository
653 Base variables that are exposed to every page of repository
642 """
654 """
643 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
655 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
644
656
645 def _handle_missing_requirements(self, error):
657 def _handle_missing_requirements(self, error):
646 self.rhodecode_repo = None
658 self.rhodecode_repo = None
647 log.error(
659 log.error(
648 'Requirements are missing for repository %s: %s',
660 'Requirements are missing for repository %s: %s',
649 c.repo_name, error.message)
661 c.repo_name, error.message)
650
662
651 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
663 summary_url = h.route_path('repo_summary', repo_name=c.repo_name)
652 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
664 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
653 settings_update_url = url('repo', repo_name=c.repo_name)
665 settings_update_url = url('repo', repo_name=c.repo_name)
654 path = request.path
666 path = request.path
655 should_redirect = (
667 should_redirect = (
656 path not in (summary_url, settings_update_url)
668 path not in (summary_url, settings_update_url)
657 and '/settings' not in path or path == statistics_url
669 and '/settings' not in path or path == statistics_url
658 )
670 )
659 if should_redirect:
671 if should_redirect:
660 redirect(summary_url)
672 redirect(summary_url)
General Comments 0
You need to be logged in to leave comments. Login now