##// END OF EJS Templates
config: Move HttpsFixup middleware up...
johbo -
r181:7237d87b stable
parent child Browse files
Show More
@@ -1,315 +1,316 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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
25
26 from paste.registry import RegistryManager
26 from paste.registry import RegistryManager
27 from paste.gzipper import make_gzip_middleware
27 from paste.gzipper import make_gzip_middleware
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
28 from pylons.middleware import ErrorHandler, StatusCodeRedirect
29 from pylons.wsgiapp import PylonsApp
29 from pylons.wsgiapp import PylonsApp
30 from pyramid.authorization import ACLAuthorizationPolicy
30 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.config import Configurator
31 from pyramid.config import Configurator
32 from pyramid.static import static_view
32 from pyramid.static import static_view
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 routes.middleware import RoutesMiddleware
35 from routes.middleware import RoutesMiddleware
36 import routes.util
36 import routes.util
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config.environment import (
40 from rhodecode.config.environment import (
41 load_environment, load_pyramid_environment)
41 load_environment, load_pyramid_environment)
42 from rhodecode.lib.middleware import csrf
42 from rhodecode.lib.middleware import csrf
43 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
43 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
44 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
44 from rhodecode.lib.middleware.disable_vcs import DisableVCSPagesWrapper
45 from rhodecode.lib.middleware.https_fixup import HttpsFixup
45 from rhodecode.lib.middleware.https_fixup import HttpsFixup
46 from rhodecode.lib.middleware.vcs import VCSMiddleware
46 from rhodecode.lib.middleware.vcs import VCSMiddleware
47 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
47 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
48
48
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
53 def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
54 """Create a Pylons WSGI application and return it
54 """Create a Pylons WSGI application and return it
55
55
56 ``global_conf``
56 ``global_conf``
57 The inherited configuration for this application. Normally from
57 The inherited configuration for this application. Normally from
58 the [DEFAULT] section of the Paste ini file.
58 the [DEFAULT] section of the Paste ini file.
59
59
60 ``full_stack``
60 ``full_stack``
61 Whether or not this application provides a full WSGI stack (by
61 Whether or not this application provides a full WSGI stack (by
62 default, meaning it handles its own exceptions and errors).
62 default, meaning it handles its own exceptions and errors).
63 Disable full_stack when this application is "managed" by
63 Disable full_stack when this application is "managed" by
64 another WSGI middleware.
64 another WSGI middleware.
65
65
66 ``app_conf``
66 ``app_conf``
67 The application's local configuration. Normally specified in
67 The application's local configuration. Normally specified in
68 the [app:<name>] section of the Paste ini file (where <name>
68 the [app:<name>] section of the Paste ini file (where <name>
69 defaults to main).
69 defaults to main).
70
70
71 """
71 """
72 # Apply compatibility patches
72 # Apply compatibility patches
73 patches.kombu_1_5_1_python_2_7_11()
73 patches.kombu_1_5_1_python_2_7_11()
74 patches.inspect_getargspec()
74 patches.inspect_getargspec()
75
75
76 # Configure the Pylons environment
76 # Configure the Pylons environment
77 config = load_environment(global_conf, app_conf)
77 config = load_environment(global_conf, app_conf)
78
78
79 # The Pylons WSGI app
79 # The Pylons WSGI app
80 app = PylonsApp(config=config)
80 app = PylonsApp(config=config)
81 if rhodecode.is_test:
81 if rhodecode.is_test:
82 app = csrf.CSRFDetector(app)
82 app = csrf.CSRFDetector(app)
83
83
84 expected_origin = config.get('expected_origin')
84 expected_origin = config.get('expected_origin')
85 if expected_origin:
85 if expected_origin:
86 # The API can be accessed from other Origins.
86 # The API can be accessed from other Origins.
87 app = csrf.OriginChecker(app, expected_origin,
87 app = csrf.OriginChecker(app, expected_origin,
88 skip_urls=[routes.util.url_for('api')])
88 skip_urls=[routes.util.url_for('api')])
89
89
90 # Add RoutesMiddleware. Currently we have two instances in the stack. This
90 # Add RoutesMiddleware. Currently we have two instances in the stack. This
91 # is the lower one to make the StatusCodeRedirect middleware happy.
91 # is the lower one to make the StatusCodeRedirect middleware happy.
92 # TODO: johbo: This is not optimal, search for a better solution.
92 # TODO: johbo: This is not optimal, search for a better solution.
93 app = RoutesMiddleware(app, config['routes.map'])
93 app = RoutesMiddleware(app, config['routes.map'])
94
94
95 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
95 # CUSTOM MIDDLEWARE HERE (filtered by error handling middlewares)
96 if asbool(config['pdebug']):
96 if asbool(config['pdebug']):
97 from rhodecode.lib.profiler import ProfilingMiddleware
97 from rhodecode.lib.profiler import ProfilingMiddleware
98 app = ProfilingMiddleware(app)
98 app = ProfilingMiddleware(app)
99
99
100 # Protect from VCS Server error related pages when server is not available
100 # Protect from VCS Server error related pages when server is not available
101 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
101 vcs_server_enabled = asbool(config.get('vcs.server.enable', 'true'))
102 if not vcs_server_enabled:
102 if not vcs_server_enabled:
103 app = DisableVCSPagesWrapper(app)
103 app = DisableVCSPagesWrapper(app)
104
104
105 if asbool(full_stack):
105 if asbool(full_stack):
106
106
107 # Appenlight monitoring and error handler
107 # Appenlight monitoring and error handler
108 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
108 app, appenlight_client = wrap_in_appenlight_if_enabled(app, config)
109
109
110 # Handle Python exceptions
110 # Handle Python exceptions
111 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
111 app = ErrorHandler(app, global_conf, **config['pylons.errorware'])
112
112
113 # we want our low level middleware to get to the request ASAP. We don't
113 # we want our low level middleware to get to the request ASAP. We don't
114 # need any pylons stack middleware in them
114 # need any pylons stack middleware in them
115 app = VCSMiddleware(app, config, appenlight_client)
115 app = VCSMiddleware(app, config, appenlight_client)
116 # Display error documents for 401, 403, 404 status codes (and
116 # Display error documents for 401, 403, 404 status codes (and
117 # 500 when debug is disabled)
117 # 500 when debug is disabled)
118 if asbool(config['debug']):
118 if asbool(config['debug']):
119 app = StatusCodeRedirect(app)
119 app = StatusCodeRedirect(app)
120 else:
120 else:
121 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
121 app = StatusCodeRedirect(app, [400, 401, 403, 404, 500])
122
122
123 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
124 app = HttpsFixup(app, config)
125
126 # Establish the Registry for this application
123 # Establish the Registry for this application
127 app = RegistryManager(app)
124 app = RegistryManager(app)
128
125
129 app.config = config
126 app.config = config
130
127
131 return app
128 return app
132
129
133
130
134 def make_pyramid_app(global_config, **settings):
131 def make_pyramid_app(global_config, **settings):
135 """
132 """
136 Constructs the WSGI application based on Pyramid and wraps the Pylons based
133 Constructs the WSGI application based on Pyramid and wraps the Pylons based
137 application.
134 application.
138
135
139 Specials:
136 Specials:
140
137
141 * We migrate from Pylons to Pyramid. While doing this, we keep both
138 * We migrate from Pylons to Pyramid. While doing this, we keep both
142 frameworks functional. This involves moving some WSGI middlewares around
139 frameworks functional. This involves moving some WSGI middlewares around
143 and providing access to some data internals, so that the old code is
140 and providing access to some data internals, so that the old code is
144 still functional.
141 still functional.
145
142
146 * The application can also be integrated like a plugin via the call to
143 * The application can also be integrated like a plugin via the call to
147 `includeme`. This is accompanied with the other utility functions which
144 `includeme`. This is accompanied with the other utility functions which
148 are called. Changing this should be done with great care to not break
145 are called. Changing this should be done with great care to not break
149 cases when these fragments are assembled from another place.
146 cases when these fragments are assembled from another place.
150
147
151 """
148 """
152 # The edition string should be available in pylons too, so we add it here
149 # The edition string should be available in pylons too, so we add it here
153 # before copying the settings.
150 # before copying the settings.
154 settings.setdefault('rhodecode.edition', 'Community Edition')
151 settings.setdefault('rhodecode.edition', 'Community Edition')
155
152
156 # As long as our Pylons application does expect "unprepared" settings, make
153 # As long as our Pylons application does expect "unprepared" settings, make
157 # sure that we keep an unmodified copy. This avoids unintentional change of
154 # sure that we keep an unmodified copy. This avoids unintentional change of
158 # behavior in the old application.
155 # behavior in the old application.
159 settings_pylons = settings.copy()
156 settings_pylons = settings.copy()
160
157
161 sanitize_settings_and_apply_defaults(settings)
158 sanitize_settings_and_apply_defaults(settings)
162 config = Configurator(settings=settings)
159 config = Configurator(settings=settings)
163 add_pylons_compat_data(config.registry, global_config, settings_pylons)
160 add_pylons_compat_data(config.registry, global_config, settings_pylons)
164
161
165 load_pyramid_environment(global_config, settings)
162 load_pyramid_environment(global_config, settings)
166
163
167 includeme(config)
164 includeme(config)
168 includeme_last(config)
165 includeme_last(config)
169 pyramid_app = config.make_wsgi_app()
166 pyramid_app = config.make_wsgi_app()
170 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
167 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
171 return pyramid_app
168 return pyramid_app
172
169
173
170
174 def add_pylons_compat_data(registry, global_config, settings):
171 def add_pylons_compat_data(registry, global_config, settings):
175 """
172 """
176 Attach data to the registry to support the Pylons integration.
173 Attach data to the registry to support the Pylons integration.
177 """
174 """
178 registry._pylons_compat_global_config = global_config
175 registry._pylons_compat_global_config = global_config
179 registry._pylons_compat_settings = settings
176 registry._pylons_compat_settings = settings
180
177
181
178
182 def includeme(config):
179 def includeme(config):
183 settings = config.registry.settings
180 settings = config.registry.settings
184
181
185 # Includes which are required. The application would fail without them.
182 # Includes which are required. The application would fail without them.
186 config.include('pyramid_mako')
183 config.include('pyramid_mako')
187 config.include('pyramid_beaker')
184 config.include('pyramid_beaker')
188 config.include('rhodecode.authentication')
185 config.include('rhodecode.authentication')
189 config.include('rhodecode.login')
186 config.include('rhodecode.login')
190 config.include('rhodecode.tweens')
187 config.include('rhodecode.tweens')
191 config.include('rhodecode.api')
188 config.include('rhodecode.api')
192
189
193 # Set the authorization policy.
190 # Set the authorization policy.
194 authz_policy = ACLAuthorizationPolicy()
191 authz_policy = ACLAuthorizationPolicy()
195 config.set_authorization_policy(authz_policy)
192 config.set_authorization_policy(authz_policy)
196
193
197 # Set the default renderer for HTML templates to mako.
194 # Set the default renderer for HTML templates to mako.
198 config.add_mako_renderer('.html')
195 config.add_mako_renderer('.html')
199
196
200 # plugin information
197 # plugin information
201 config.registry.rhodecode_plugins = {}
198 config.registry.rhodecode_plugins = {}
202
199
203 config.add_directive(
200 config.add_directive(
204 'register_rhodecode_plugin', register_rhodecode_plugin)
201 'register_rhodecode_plugin', register_rhodecode_plugin)
205 # include RhodeCode plugins
202 # include RhodeCode plugins
206 includes = aslist(settings.get('rhodecode.includes', []))
203 includes = aslist(settings.get('rhodecode.includes', []))
207 for inc in includes:
204 for inc in includes:
208 config.include(inc)
205 config.include(inc)
209
206
210 # This is the glue which allows us to migrate in chunks. By registering the
207 # This is the glue which allows us to migrate in chunks. By registering the
211 # pylons based application as the "Not Found" view in Pyramid, we will
208 # pylons based application as the "Not Found" view in Pyramid, we will
212 # fallback to the old application each time the new one does not yet know
209 # fallback to the old application each time the new one does not yet know
213 # how to handle a request.
210 # how to handle a request.
214 pylons_app = make_app(
211 pylons_app = make_app(
215 config.registry._pylons_compat_global_config,
212 config.registry._pylons_compat_global_config,
216 **config.registry._pylons_compat_settings)
213 **config.registry._pylons_compat_settings)
217 config.registry._pylons_compat_config = pylons_app.config
214 config.registry._pylons_compat_config = pylons_app.config
218 pylons_app_as_view = wsgiapp(pylons_app)
215 pylons_app_as_view = wsgiapp(pylons_app)
219 config.add_notfound_view(pylons_app_as_view)
216 config.add_notfound_view(pylons_app_as_view)
220
217
221
218
222 def includeme_last(config):
219 def includeme_last(config):
223 """
220 """
224 The static file catchall needs to be last in the view configuration.
221 The static file catchall needs to be last in the view configuration.
225 """
222 """
226 settings = config.registry.settings
223 settings = config.registry.settings
227
224
228 # Note: johbo: I would prefer to register a prefix for static files at some
225 # Note: johbo: I would prefer to register a prefix for static files at some
229 # point, e.g. move them under '_static/'. This would fully avoid that we
226 # point, e.g. move them under '_static/'. This would fully avoid that we
230 # can have name clashes with a repository name. Imaging someone calling his
227 # can have name clashes with a repository name. Imaging someone calling his
231 # repo "css" ;-) Also having an external web server to serve out the static
228 # repo "css" ;-) Also having an external web server to serve out the static
232 # files seems to be easier to set up if they have a common prefix.
229 # files seems to be easier to set up if they have a common prefix.
233 #
230 #
234 # Example: config.add_static_view('_static', path='rhodecode:public')
231 # Example: config.add_static_view('_static', path='rhodecode:public')
235 #
232 #
236 # It might be an option to register both paths for a while and then migrate
233 # It might be an option to register both paths for a while and then migrate
237 # over to the new location.
234 # over to the new location.
238
235
239 # Serving static files with a catchall.
236 # Serving static files with a catchall.
240 if settings['static_files']:
237 if settings['static_files']:
241 config.add_route('catchall_static', '/*subpath')
238 config.add_route('catchall_static', '/*subpath')
242 config.add_view(
239 config.add_view(
243 static_view('rhodecode:public'), route_name='catchall_static')
240 static_view('rhodecode:public'), route_name='catchall_static')
244
241
245
242
246 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
243 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
247 """
244 """
248 Apply outer WSGI middlewares around the application.
245 Apply outer WSGI middlewares around the application.
249
246
250 Part of this has been moved up from the Pylons layer, so that the
247 Part of this has been moved up from the Pylons layer, so that the
251 data is also available if old Pylons code is hit through an already ported
248 data is also available if old Pylons code is hit through an already ported
252 view.
249 view.
253 """
250 """
254 settings = config.registry.settings
251 settings = config.registry.settings
255
252
253 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
254 pyramid_app = HttpsFixup(pyramid_app, settings)
255
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
256 # Add RoutesMiddleware. Currently we have two instances in the stack. This
257 # is the upper one to support the pylons compatibility tween during
257 # is the upper one to support the pylons compatibility tween during
258
258 # migration to pyramid.
259 # migration to pyramid.
259 pyramid_app = RoutesMiddleware(
260 pyramid_app = RoutesMiddleware(
260 pyramid_app, config.registry._pylons_compat_config['routes.map'])
261 pyramid_app, config.registry._pylons_compat_config['routes.map'])
261
262
262 # TODO: johbo: Don't really see why we enable the gzip middleware when
263 # TODO: johbo: Don't really see why we enable the gzip middleware when
263 # serving static files, might be something that should have its own setting
264 # serving static files, might be something that should have its own setting
264 # as well?
265 # as well?
265 if settings['static_files']:
266 if settings['static_files']:
266 pyramid_app = make_gzip_middleware(
267 pyramid_app = make_gzip_middleware(
267 pyramid_app, settings, compress_level=1)
268 pyramid_app, settings, compress_level=1)
268
269
269 return pyramid_app
270 return pyramid_app
270
271
271
272
272 def sanitize_settings_and_apply_defaults(settings):
273 def sanitize_settings_and_apply_defaults(settings):
273 """
274 """
274 Applies settings defaults and does all type conversion.
275 Applies settings defaults and does all type conversion.
275
276
276 We would move all settings parsing and preparation into this place, so that
277 We would move all settings parsing and preparation into this place, so that
277 we have only one place left which deals with this part. The remaining parts
278 we have only one place left which deals with this part. The remaining parts
278 of the application would start to rely fully on well prepared settings.
279 of the application would start to rely fully on well prepared settings.
279
280
280 This piece would later be split up per topic to avoid a big fat monster
281 This piece would later be split up per topic to avoid a big fat monster
281 function.
282 function.
282 """
283 """
283
284
284 # Pyramid's mako renderer has to search in the templates folder so that the
285 # Pyramid's mako renderer has to search in the templates folder so that the
285 # old templates still work. Ported and new templates are expected to use
286 # old templates still work. Ported and new templates are expected to use
286 # real asset specifications for the includes.
287 # real asset specifications for the includes.
287 mako_directories = settings.setdefault('mako.directories', [
288 mako_directories = settings.setdefault('mako.directories', [
288 # Base templates of the original Pylons application
289 # Base templates of the original Pylons application
289 'rhodecode:templates',
290 'rhodecode:templates',
290 ])
291 ])
291 log.debug(
292 log.debug(
292 "Using the following Mako template directories: %s",
293 "Using the following Mako template directories: %s",
293 mako_directories)
294 mako_directories)
294
295
295 # Default includes, possible to change as a user
296 # Default includes, possible to change as a user
296 pyramid_includes = settings.setdefault('pyramid.includes', [
297 pyramid_includes = settings.setdefault('pyramid.includes', [
297 'rhodecode.lib.middleware.request_wrapper',
298 'rhodecode.lib.middleware.request_wrapper',
298 ])
299 ])
299 log.debug(
300 log.debug(
300 "Using the following pyramid.includes: %s",
301 "Using the following pyramid.includes: %s",
301 pyramid_includes)
302 pyramid_includes)
302
303
303 # TODO: johbo: Re-think this, usually the call to config.include
304 # TODO: johbo: Re-think this, usually the call to config.include
304 # should allow to pass in a prefix.
305 # should allow to pass in a prefix.
305 settings.setdefault('rhodecode.api.url', '/_admin/api')
306 settings.setdefault('rhodecode.api.url', '/_admin/api')
306
307
307 _bool_setting(settings, 'vcs.server.enable', 'true')
308 _bool_setting(settings, 'vcs.server.enable', 'true')
308 _bool_setting(settings, 'static_files', 'true')
309 _bool_setting(settings, 'static_files', 'true')
309 _bool_setting(settings, 'is_test', 'false')
310 _bool_setting(settings, 'is_test', 'false')
310
311
311 return settings
312 return settings
312
313
313
314
314 def _bool_setting(settings, name, default):
315 def _bool_setting(settings, name, default):
315 settings[name] = asbool(settings.get(name, default))
316 settings[name] = asbool(settings.get(name, default))
General Comments 0
You need to be logged in to leave comments. Login now