##// END OF EJS Templates
login: moved to apps module
marcink -
r1501:a960166a default
parent child Browse files
Show More
1 NO CONTENT: file renamed from rhodecode/login/__init__.py to rhodecode/apps/login/__init__.py
NO CONTENT: file renamed from rhodecode/login/__init__.py to rhodecode/apps/login/__init__.py
1 NO CONTENT: file renamed from rhodecode/login/tests/__init__.py to rhodecode/apps/login/tests/__init__.py
NO CONTENT: file renamed from rhodecode/login/tests/__init__.py to rhodecode/apps/login/tests/__init__.py
@@ -1,128 +1,128 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-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 import mock
22 import mock
23 import pytest
23 import pytest
24
24
25 from rhodecode.apps.login.views import LoginView, CaptchaData
25 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.config.routing import ADMIN_PREFIX
26 from rhodecode.login.views import LoginView, CaptchaData
27 from rhodecode.model.settings import SettingsModel
27 from rhodecode.model.settings import SettingsModel
28 from rhodecode.tests.utils import AssertResponse
28 from rhodecode.tests.utils import AssertResponse
29
29
30
30
31 class RhodeCodeSetting(object):
31 class RhodeCodeSetting(object):
32 def __init__(self, name, value):
32 def __init__(self, name, value):
33 self.name = name
33 self.name = name
34 self.value = value
34 self.value = value
35
35
36 def __enter__(self):
36 def __enter__(self):
37 from rhodecode.model.settings import SettingsModel
37 from rhodecode.model.settings import SettingsModel
38 model = SettingsModel()
38 model = SettingsModel()
39 self.old_setting = model.get_setting_by_name(self.name)
39 self.old_setting = model.get_setting_by_name(self.name)
40 model.create_or_update_setting(name=self.name, val=self.value)
40 model.create_or_update_setting(name=self.name, val=self.value)
41 return self
41 return self
42
42
43 def __exit__(self, type, value, traceback):
43 def __exit__(self, type, value, traceback):
44 model = SettingsModel()
44 model = SettingsModel()
45 if self.old_setting:
45 if self.old_setting:
46 model.create_or_update_setting(
46 model.create_or_update_setting(
47 name=self.name, val=self.old_setting.app_settings_value)
47 name=self.name, val=self.old_setting.app_settings_value)
48 else:
48 else:
49 model.create_or_update_setting(name=self.name)
49 model.create_or_update_setting(name=self.name)
50
50
51
51
52 class TestRegisterCaptcha(object):
52 class TestRegisterCaptcha(object):
53
53
54 @pytest.mark.parametrize('private_key, public_key, expected', [
54 @pytest.mark.parametrize('private_key, public_key, expected', [
55 ('', '', CaptchaData(False, '', '')),
55 ('', '', CaptchaData(False, '', '')),
56 ('', 'pubkey', CaptchaData(False, '', 'pubkey')),
56 ('', 'pubkey', CaptchaData(False, '', 'pubkey')),
57 ('privkey', '', CaptchaData(True, 'privkey', '')),
57 ('privkey', '', CaptchaData(True, 'privkey', '')),
58 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
58 ('privkey', 'pubkey', CaptchaData(True, 'privkey', 'pubkey')),
59 ])
59 ])
60 def test_get_captcha_data(self, private_key, public_key, expected, db):
60 def test_get_captcha_data(self, private_key, public_key, expected, db):
61 login_view = LoginView(mock.Mock(), mock.Mock())
61 login_view = LoginView(mock.Mock(), mock.Mock())
62 with RhodeCodeSetting('captcha_private_key', private_key):
62 with RhodeCodeSetting('captcha_private_key', private_key):
63 with RhodeCodeSetting('captcha_public_key', public_key):
63 with RhodeCodeSetting('captcha_public_key', public_key):
64 captcha = login_view._get_captcha_data()
64 captcha = login_view._get_captcha_data()
65 assert captcha == expected
65 assert captcha == expected
66
66
67 @pytest.mark.parametrize('active', [False, True])
67 @pytest.mark.parametrize('active', [False, True])
68 @mock.patch.object(LoginView, '_get_captcha_data')
68 @mock.patch.object(LoginView, '_get_captcha_data')
69 def test_private_key_does_not_leak_to_html(
69 def test_private_key_does_not_leak_to_html(
70 self, m_get_captcha_data, active, app):
70 self, m_get_captcha_data, active, app):
71 captcha = CaptchaData(
71 captcha = CaptchaData(
72 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
72 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
73 m_get_captcha_data.return_value = captcha
73 m_get_captcha_data.return_value = captcha
74
74
75 response = app.get(ADMIN_PREFIX + '/register')
75 response = app.get(ADMIN_PREFIX + '/register')
76 assert 'PRIVATE_KEY' not in response
76 assert 'PRIVATE_KEY' not in response
77
77
78 @pytest.mark.parametrize('active', [False, True])
78 @pytest.mark.parametrize('active', [False, True])
79 @mock.patch.object(LoginView, '_get_captcha_data')
79 @mock.patch.object(LoginView, '_get_captcha_data')
80 def test_register_view_renders_captcha(
80 def test_register_view_renders_captcha(
81 self, m_get_captcha_data, active, app):
81 self, m_get_captcha_data, active, app):
82 captcha = CaptchaData(
82 captcha = CaptchaData(
83 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
83 active=active, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
84 m_get_captcha_data.return_value = captcha
84 m_get_captcha_data.return_value = captcha
85
85
86 response = app.get(ADMIN_PREFIX + '/register')
86 response = app.get(ADMIN_PREFIX + '/register')
87
87
88 assertr = AssertResponse(response)
88 assertr = AssertResponse(response)
89 if active:
89 if active:
90 assertr.one_element_exists('#recaptcha_field')
90 assertr.one_element_exists('#recaptcha_field')
91 else:
91 else:
92 assertr.no_element_exists('#recaptcha_field')
92 assertr.no_element_exists('#recaptcha_field')
93
93
94 @pytest.mark.parametrize('valid', [False, True])
94 @pytest.mark.parametrize('valid', [False, True])
95 @mock.patch('rhodecode.login.views.submit')
95 @mock.patch('rhodecode.apps.login.views.submit')
96 @mock.patch.object(LoginView, '_get_captcha_data')
96 @mock.patch.object(LoginView, '_get_captcha_data')
97 def test_register_with_active_captcha(
97 def test_register_with_active_captcha(
98 self, m_get_captcha_data, m_submit, valid, app, csrf_token):
98 self, m_get_captcha_data, m_submit, valid, app, csrf_token):
99 captcha = CaptchaData(
99 captcha = CaptchaData(
100 active=True, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
100 active=True, private_key='PRIVATE_KEY', public_key='PUBLIC_KEY')
101 m_get_captcha_data.return_value = captcha
101 m_get_captcha_data.return_value = captcha
102 m_response = mock.Mock()
102 m_response = mock.Mock()
103 m_response.is_valid = valid
103 m_response.is_valid = valid
104 m_submit.return_value = m_response
104 m_submit.return_value = m_response
105
105
106 params = {
106 params = {
107 'csrf_token': csrf_token,
107 'csrf_token': csrf_token,
108 'email': 'pytest@example.com',
108 'email': 'pytest@example.com',
109 'firstname': 'pytest-firstname',
109 'firstname': 'pytest-firstname',
110 'lastname': 'pytest-lastname',
110 'lastname': 'pytest-lastname',
111 'password': 'secret',
111 'password': 'secret',
112 'password_confirmation': 'secret',
112 'password_confirmation': 'secret',
113 'username': 'pytest',
113 'username': 'pytest',
114 }
114 }
115 response = app.post(ADMIN_PREFIX + '/register', params=params)
115 response = app.post(ADMIN_PREFIX + '/register', params=params)
116
116
117 if valid:
117 if valid:
118 # If we provided a valid captcha input we expect a successful
118 # If we provided a valid captcha input we expect a successful
119 # registration and redirect to the login page.
119 # registration and redirect to the login page.
120 assert response.status_int == 302
120 assert response.status_int == 302
121 assert 'location' in response.headers
121 assert 'location' in response.headers
122 assert ADMIN_PREFIX + '/login' in response.headers['location']
122 assert ADMIN_PREFIX + '/login' in response.headers['location']
123 else:
123 else:
124 # If captche input is invalid we expect to stay on the registration
124 # If captche input is invalid we expect to stay on the registration
125 # page with an error message displayed.
125 # page with an error message displayed.
126 assertr = AssertResponse(response)
126 assertr = AssertResponse(response)
127 assert response.status_int == 200
127 assert response.status_int == 200
128 assertr.one_element_exists('#recaptcha_field ~ span.error-message')
128 assertr.one_element_exists('#recaptcha_field ~ span.error-message')
1 NO CONTENT: file renamed from rhodecode/login/views.py to rhodecode/apps/login/views.py
NO CONTENT: file renamed from rhodecode/login/views.py to rhodecode/apps/login/views.py
@@ -1,495 +1,495 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 from collections import OrderedDict
25 from collections import OrderedDict
26
26
27 from paste.registry import RegistryManager
27 from paste.registry import RegistryManager
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
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.settings import asbool, aslist
32 from pyramid.settings import asbool, aslist
33 from pyramid.wsgi import wsgiapp
33 from pyramid.wsgi import wsgiapp
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound)
36 from pyramid.events import ApplicationCreated
36 from pyramid.events import ApplicationCreated
37 from pyramid.renderers import render_to_response
37 from pyramid.renderers import render_to_response
38 from routes.middleware import RoutesMiddleware
38 from routes.middleware import RoutesMiddleware
39 import routes.util
39 import routes.util
40
40
41 import rhodecode
41 import rhodecode
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 from rhodecode.lib.middleware import csrf
47 from rhodecode.lib.middleware import csrf
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.error_handling import (
49 from rhodecode.lib.middleware.error_handling import (
50 PylonsErrorHandlingMiddleware)
50 PylonsErrorHandlingMiddleware)
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
51 from rhodecode.lib.middleware.https_fixup import HttpsFixup
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
52 from rhodecode.lib.middleware.vcs import VCSMiddleware
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
53 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
54 from rhodecode.lib.utils2 import aslist as rhodecode_aslist
55 from rhodecode.subscribers import (
55 from rhodecode.subscribers import (
56 scan_repositories_if_enabled, write_metadata_if_needed)
56 scan_repositories_if_enabled, write_metadata_if_needed)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
62 # this is used to avoid avoid the route lookup overhead in routesmiddleware
63 # for certain routes which won't go to pylons to - eg. static files, debugger
63 # for certain routes which won't go to pylons to - eg. static files, debugger
64 # it is only needed for the pylons migration and can be removed once complete
64 # it is only needed for the pylons migration and can be removed once complete
65 class SkippableRoutesMiddleware(RoutesMiddleware):
65 class SkippableRoutesMiddleware(RoutesMiddleware):
66 """ Routes middleware that allows you to skip prefixes """
66 """ Routes middleware that allows you to skip prefixes """
67
67
68 def __init__(self, *args, **kw):
68 def __init__(self, *args, **kw):
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
69 self.skip_prefixes = kw.pop('skip_prefixes', [])
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
70 super(SkippableRoutesMiddleware, self).__init__(*args, **kw)
71
71
72 def __call__(self, environ, start_response):
72 def __call__(self, environ, start_response):
73 for prefix in self.skip_prefixes:
73 for prefix in self.skip_prefixes:
74 if environ['PATH_INFO'].startswith(prefix):
74 if environ['PATH_INFO'].startswith(prefix):
75 # added to avoid the case when a missing /_static route falls
75 # added to avoid the case when a missing /_static route falls
76 # through to pylons and causes an exception as pylons is
76 # through to pylons and causes an exception as pylons is
77 # expecting wsgiorg.routingargs to be set in the environ
77 # expecting wsgiorg.routingargs to be set in the environ
78 # by RoutesMiddleware.
78 # by RoutesMiddleware.
79 if 'wsgiorg.routing_args' not in environ:
79 if 'wsgiorg.routing_args' not in environ:
80 environ['wsgiorg.routing_args'] = (None, {})
80 environ['wsgiorg.routing_args'] = (None, {})
81 return self.app(environ, start_response)
81 return self.app(environ, start_response)
82
82
83 return super(SkippableRoutesMiddleware, self).__call__(
83 return super(SkippableRoutesMiddleware, self).__call__(
84 environ, start_response)
84 environ, start_response)
85
85
86
86
87 def make_app(global_conf, static_files=True, **app_conf):
87 def make_app(global_conf, static_files=True, **app_conf):
88 """Create a Pylons WSGI application and return it
88 """Create a Pylons WSGI application and return it
89
89
90 ``global_conf``
90 ``global_conf``
91 The inherited configuration for this application. Normally from
91 The inherited configuration for this application. Normally from
92 the [DEFAULT] section of the Paste ini file.
92 the [DEFAULT] section of the Paste ini file.
93
93
94 ``app_conf``
94 ``app_conf``
95 The application's local configuration. Normally specified in
95 The application's local configuration. Normally specified in
96 the [app:<name>] section of the Paste ini file (where <name>
96 the [app:<name>] section of the Paste ini file (where <name>
97 defaults to main).
97 defaults to main).
98
98
99 """
99 """
100 # Apply compatibility patches
100 # Apply compatibility patches
101 patches.kombu_1_5_1_python_2_7_11()
101 patches.kombu_1_5_1_python_2_7_11()
102 patches.inspect_getargspec()
102 patches.inspect_getargspec()
103
103
104 # Configure the Pylons environment
104 # Configure the Pylons environment
105 config = load_environment(global_conf, app_conf)
105 config = load_environment(global_conf, app_conf)
106
106
107 # The Pylons WSGI app
107 # The Pylons WSGI app
108 app = PylonsApp(config=config)
108 app = PylonsApp(config=config)
109 if rhodecode.is_test:
109 if rhodecode.is_test:
110 app = csrf.CSRFDetector(app)
110 app = csrf.CSRFDetector(app)
111
111
112 expected_origin = config.get('expected_origin')
112 expected_origin = config.get('expected_origin')
113 if expected_origin:
113 if expected_origin:
114 # The API can be accessed from other Origins.
114 # The API can be accessed from other Origins.
115 app = csrf.OriginChecker(app, expected_origin,
115 app = csrf.OriginChecker(app, expected_origin,
116 skip_urls=[routes.util.url_for('api')])
116 skip_urls=[routes.util.url_for('api')])
117
117
118 # Establish the Registry for this application
118 # Establish the Registry for this application
119 app = RegistryManager(app)
119 app = RegistryManager(app)
120
120
121 app.config = config
121 app.config = config
122
122
123 return app
123 return app
124
124
125
125
126 def make_pyramid_app(global_config, **settings):
126 def make_pyramid_app(global_config, **settings):
127 """
127 """
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
128 Constructs the WSGI application based on Pyramid and wraps the Pylons based
129 application.
129 application.
130
130
131 Specials:
131 Specials:
132
132
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
133 * We migrate from Pylons to Pyramid. While doing this, we keep both
134 frameworks functional. This involves moving some WSGI middlewares around
134 frameworks functional. This involves moving some WSGI middlewares around
135 and providing access to some data internals, so that the old code is
135 and providing access to some data internals, so that the old code is
136 still functional.
136 still functional.
137
137
138 * The application can also be integrated like a plugin via the call to
138 * The application can also be integrated like a plugin via the call to
139 `includeme`. This is accompanied with the other utility functions which
139 `includeme`. This is accompanied with the other utility functions which
140 are called. Changing this should be done with great care to not break
140 are called. Changing this should be done with great care to not break
141 cases when these fragments are assembled from another place.
141 cases when these fragments are assembled from another place.
142
142
143 """
143 """
144 # The edition string should be available in pylons too, so we add it here
144 # The edition string should be available in pylons too, so we add it here
145 # before copying the settings.
145 # before copying the settings.
146 settings.setdefault('rhodecode.edition', 'Community Edition')
146 settings.setdefault('rhodecode.edition', 'Community Edition')
147
147
148 # As long as our Pylons application does expect "unprepared" settings, make
148 # As long as our Pylons application does expect "unprepared" settings, make
149 # sure that we keep an unmodified copy. This avoids unintentional change of
149 # sure that we keep an unmodified copy. This avoids unintentional change of
150 # behavior in the old application.
150 # behavior in the old application.
151 settings_pylons = settings.copy()
151 settings_pylons = settings.copy()
152
152
153 sanitize_settings_and_apply_defaults(settings)
153 sanitize_settings_and_apply_defaults(settings)
154 config = Configurator(settings=settings)
154 config = Configurator(settings=settings)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
155 add_pylons_compat_data(config.registry, global_config, settings_pylons)
156
156
157 load_pyramid_environment(global_config, settings)
157 load_pyramid_environment(global_config, settings)
158
158
159 includeme_first(config)
159 includeme_first(config)
160 includeme(config)
160 includeme(config)
161 pyramid_app = config.make_wsgi_app()
161 pyramid_app = config.make_wsgi_app()
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
162 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
163 pyramid_app.config = config
163 pyramid_app.config = config
164
164
165 # creating the app uses a connection - return it after we are done
165 # creating the app uses a connection - return it after we are done
166 meta.Session.remove()
166 meta.Session.remove()
167
167
168 return pyramid_app
168 return pyramid_app
169
169
170
170
171 def make_not_found_view(config):
171 def make_not_found_view(config):
172 """
172 """
173 This creates the view which should be registered as not-found-view to
173 This creates the view which should be registered as not-found-view to
174 pyramid. Basically it contains of the old pylons app, converted to a view.
174 pyramid. Basically it contains of the old pylons app, converted to a view.
175 Additionally it is wrapped by some other middlewares.
175 Additionally it is wrapped by some other middlewares.
176 """
176 """
177 settings = config.registry.settings
177 settings = config.registry.settings
178 vcs_server_enabled = settings['vcs.server.enable']
178 vcs_server_enabled = settings['vcs.server.enable']
179
179
180 # Make pylons app from unprepared settings.
180 # Make pylons app from unprepared settings.
181 pylons_app = make_app(
181 pylons_app = make_app(
182 config.registry._pylons_compat_global_config,
182 config.registry._pylons_compat_global_config,
183 **config.registry._pylons_compat_settings)
183 **config.registry._pylons_compat_settings)
184 config.registry._pylons_compat_config = pylons_app.config
184 config.registry._pylons_compat_config = pylons_app.config
185
185
186 # Appenlight monitoring.
186 # Appenlight monitoring.
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
187 pylons_app, appenlight_client = wrap_in_appenlight_if_enabled(
188 pylons_app, settings)
188 pylons_app, settings)
189
189
190 # The pylons app is executed inside of the pyramid 404 exception handler.
190 # The pylons app is executed inside of the pyramid 404 exception handler.
191 # Exceptions which are raised inside of it are not handled by pyramid
191 # Exceptions which are raised inside of it are not handled by pyramid
192 # again. Therefore we add a middleware that invokes the error handler in
192 # again. Therefore we add a middleware that invokes the error handler in
193 # case of an exception or error response. This way we return proper error
193 # case of an exception or error response. This way we return proper error
194 # HTML pages in case of an error.
194 # HTML pages in case of an error.
195 reraise = (settings.get('debugtoolbar.enabled', False) or
195 reraise = (settings.get('debugtoolbar.enabled', False) or
196 rhodecode.disable_error_handler)
196 rhodecode.disable_error_handler)
197 pylons_app = PylonsErrorHandlingMiddleware(
197 pylons_app = PylonsErrorHandlingMiddleware(
198 pylons_app, error_handler, reraise)
198 pylons_app, error_handler, reraise)
199
199
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
200 # The VCSMiddleware shall operate like a fallback if pyramid doesn't find a
201 # view to handle the request. Therefore it is wrapped around the pylons
201 # view to handle the request. Therefore it is wrapped around the pylons
202 # app. It has to be outside of the error handling otherwise error responses
202 # app. It has to be outside of the error handling otherwise error responses
203 # from the vcsserver are converted to HTML error pages. This confuses the
203 # from the vcsserver are converted to HTML error pages. This confuses the
204 # command line tools and the user won't get a meaningful error message.
204 # command line tools and the user won't get a meaningful error message.
205 if vcs_server_enabled:
205 if vcs_server_enabled:
206 pylons_app = VCSMiddleware(
206 pylons_app = VCSMiddleware(
207 pylons_app, settings, appenlight_client, registry=config.registry)
207 pylons_app, settings, appenlight_client, registry=config.registry)
208
208
209 # Convert WSGI app to pyramid view and return it.
209 # Convert WSGI app to pyramid view and return it.
210 return wsgiapp(pylons_app)
210 return wsgiapp(pylons_app)
211
211
212
212
213 def add_pylons_compat_data(registry, global_config, settings):
213 def add_pylons_compat_data(registry, global_config, settings):
214 """
214 """
215 Attach data to the registry to support the Pylons integration.
215 Attach data to the registry to support the Pylons integration.
216 """
216 """
217 registry._pylons_compat_global_config = global_config
217 registry._pylons_compat_global_config = global_config
218 registry._pylons_compat_settings = settings
218 registry._pylons_compat_settings = settings
219
219
220
220
221 def error_handler(exception, request):
221 def error_handler(exception, request):
222 import rhodecode
222 import rhodecode
223 from rhodecode.lib.utils2 import AttributeDict
223 from rhodecode.lib.utils2 import AttributeDict
224
224
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
225 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
226
226
227 base_response = HTTPInternalServerError()
227 base_response = HTTPInternalServerError()
228 # prefer original exception for the response since it may have headers set
228 # prefer original exception for the response since it may have headers set
229 if isinstance(exception, HTTPException):
229 if isinstance(exception, HTTPException):
230 base_response = exception
230 base_response = exception
231
231
232 def is_http_error(response):
232 def is_http_error(response):
233 # error which should have traceback
233 # error which should have traceback
234 return response.status_code > 499
234 return response.status_code > 499
235
235
236 if is_http_error(base_response):
236 if is_http_error(base_response):
237 log.exception(
237 log.exception(
238 'error occurred handling this request for path: %s', request.path)
238 'error occurred handling this request for path: %s', request.path)
239
239
240 c = AttributeDict()
240 c = AttributeDict()
241 c.error_message = base_response.status
241 c.error_message = base_response.status
242 c.error_explanation = base_response.explanation or str(base_response)
242 c.error_explanation = base_response.explanation or str(base_response)
243 c.visual = AttributeDict()
243 c.visual = AttributeDict()
244
244
245 c.visual.rhodecode_support_url = (
245 c.visual.rhodecode_support_url = (
246 request.registry.settings.get('rhodecode_support_url') or
246 request.registry.settings.get('rhodecode_support_url') or
247 request.route_url('rhodecode_support')
247 request.route_url('rhodecode_support')
248 )
248 )
249 c.redirect_time = 0
249 c.redirect_time = 0
250 c.rhodecode_name = rhodecode_title
250 c.rhodecode_name = rhodecode_title
251 if not c.rhodecode_name:
251 if not c.rhodecode_name:
252 c.rhodecode_name = 'Rhodecode'
252 c.rhodecode_name = 'Rhodecode'
253
253
254 c.causes = []
254 c.causes = []
255 if hasattr(base_response, 'causes'):
255 if hasattr(base_response, 'causes'):
256 c.causes = base_response.causes
256 c.causes = base_response.causes
257
257
258 response = render_to_response(
258 response = render_to_response(
259 '/errors/error_document.mako', {'c': c}, request=request,
259 '/errors/error_document.mako', {'c': c}, request=request,
260 response=base_response)
260 response=base_response)
261
261
262 return response
262 return response
263
263
264
264
265 def includeme(config):
265 def includeme(config):
266 settings = config.registry.settings
266 settings = config.registry.settings
267
267
268 # plugin information
268 # plugin information
269 config.registry.rhodecode_plugins = OrderedDict()
269 config.registry.rhodecode_plugins = OrderedDict()
270
270
271 config.add_directive(
271 config.add_directive(
272 'register_rhodecode_plugin', register_rhodecode_plugin)
272 'register_rhodecode_plugin', register_rhodecode_plugin)
273
273
274 if asbool(settings.get('appenlight', 'false')):
274 if asbool(settings.get('appenlight', 'false')):
275 config.include('appenlight_client.ext.pyramid_tween')
275 config.include('appenlight_client.ext.pyramid_tween')
276
276
277 # Includes which are required. The application would fail without them.
277 # Includes which are required. The application would fail without them.
278 config.include('pyramid_mako')
278 config.include('pyramid_mako')
279 config.include('pyramid_beaker')
279 config.include('pyramid_beaker')
280 config.include('rhodecode.channelstream')
280 config.include('rhodecode.channelstream')
281 config.include('rhodecode.admin')
281 config.include('rhodecode.admin')
282 config.include('rhodecode.authentication')
282 config.include('rhodecode.authentication')
283 config.include('rhodecode.integrations')
283 config.include('rhodecode.integrations')
284 config.include('rhodecode.login')
284 config.include('rhodecode.apps.login')
285 config.include('rhodecode.tweens')
285 config.include('rhodecode.tweens')
286 config.include('rhodecode.api')
286 config.include('rhodecode.api')
287 config.include('rhodecode.svn_support')
287 config.include('rhodecode.svn_support')
288 config.add_route(
288 config.add_route(
289 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
289 'rhodecode_support', 'https://rhodecode.com/help/', static=True)
290
290
291 config.add_translation_dirs('rhodecode:i18n/')
291 config.add_translation_dirs('rhodecode:i18n/')
292 settings['default_locale_name'] = settings.get('lang', 'en')
292 settings['default_locale_name'] = settings.get('lang', 'en')
293
293
294 # Add subscribers.
294 # Add subscribers.
295 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
295 config.add_subscriber(scan_repositories_if_enabled, ApplicationCreated)
296 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
296 config.add_subscriber(write_metadata_if_needed, ApplicationCreated)
297
297
298 # Set the authorization policy.
298 # Set the authorization policy.
299 authz_policy = ACLAuthorizationPolicy()
299 authz_policy = ACLAuthorizationPolicy()
300 config.set_authorization_policy(authz_policy)
300 config.set_authorization_policy(authz_policy)
301
301
302 # Set the default renderer for HTML templates to mako.
302 # Set the default renderer for HTML templates to mako.
303 config.add_mako_renderer('.html')
303 config.add_mako_renderer('.html')
304
304
305 # include RhodeCode plugins
305 # include RhodeCode plugins
306 includes = aslist(settings.get('rhodecode.includes', []))
306 includes = aslist(settings.get('rhodecode.includes', []))
307 for inc in includes:
307 for inc in includes:
308 config.include(inc)
308 config.include(inc)
309
309
310 # This is the glue which allows us to migrate in chunks. By registering the
310 # This is the glue which allows us to migrate in chunks. By registering the
311 # pylons based application as the "Not Found" view in Pyramid, we will
311 # pylons based application as the "Not Found" view in Pyramid, we will
312 # fallback to the old application each time the new one does not yet know
312 # fallback to the old application each time the new one does not yet know
313 # how to handle a request.
313 # how to handle a request.
314 config.add_notfound_view(make_not_found_view(config))
314 config.add_notfound_view(make_not_found_view(config))
315
315
316 if not settings.get('debugtoolbar.enabled', False):
316 if not settings.get('debugtoolbar.enabled', False):
317 # if no toolbar, then any exception gets caught and rendered
317 # if no toolbar, then any exception gets caught and rendered
318 config.add_view(error_handler, context=Exception)
318 config.add_view(error_handler, context=Exception)
319
319
320 config.add_view(error_handler, context=HTTPError)
320 config.add_view(error_handler, context=HTTPError)
321
321
322
322
323 def includeme_first(config):
323 def includeme_first(config):
324 # redirect automatic browser favicon.ico requests to correct place
324 # redirect automatic browser favicon.ico requests to correct place
325 def favicon_redirect(context, request):
325 def favicon_redirect(context, request):
326 return HTTPFound(
326 return HTTPFound(
327 request.static_path('rhodecode:public/images/favicon.ico'))
327 request.static_path('rhodecode:public/images/favicon.ico'))
328
328
329 config.add_view(favicon_redirect, route_name='favicon')
329 config.add_view(favicon_redirect, route_name='favicon')
330 config.add_route('favicon', '/favicon.ico')
330 config.add_route('favicon', '/favicon.ico')
331
331
332 def robots_redirect(context, request):
332 def robots_redirect(context, request):
333 return HTTPFound(
333 return HTTPFound(
334 request.static_path('rhodecode:public/robots.txt'))
334 request.static_path('rhodecode:public/robots.txt'))
335
335
336 config.add_view(robots_redirect, route_name='robots')
336 config.add_view(robots_redirect, route_name='robots')
337 config.add_route('robots', '/robots.txt')
337 config.add_route('robots', '/robots.txt')
338
338
339 config.add_static_view(
339 config.add_static_view(
340 '_static/deform', 'deform:static')
340 '_static/deform', 'deform:static')
341 config.add_static_view(
341 config.add_static_view(
342 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
342 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
343
343
344
344
345 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
345 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
346 """
346 """
347 Apply outer WSGI middlewares around the application.
347 Apply outer WSGI middlewares around the application.
348
348
349 Part of this has been moved up from the Pylons layer, so that the
349 Part of this has been moved up from the Pylons layer, so that the
350 data is also available if old Pylons code is hit through an already ported
350 data is also available if old Pylons code is hit through an already ported
351 view.
351 view.
352 """
352 """
353 settings = config.registry.settings
353 settings = config.registry.settings
354
354
355 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
355 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
356 pyramid_app = HttpsFixup(pyramid_app, settings)
356 pyramid_app = HttpsFixup(pyramid_app, settings)
357
357
358 # Add RoutesMiddleware to support the pylons compatibility tween during
358 # Add RoutesMiddleware to support the pylons compatibility tween during
359 # migration to pyramid.
359 # migration to pyramid.
360 pyramid_app = SkippableRoutesMiddleware(
360 pyramid_app = SkippableRoutesMiddleware(
361 pyramid_app, config.registry._pylons_compat_config['routes.map'],
361 pyramid_app, config.registry._pylons_compat_config['routes.map'],
362 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
362 skip_prefixes=(STATIC_FILE_PREFIX, '/_debug_toolbar'))
363
363
364 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
364 pyramid_app, _ = wrap_in_appenlight_if_enabled(pyramid_app, settings)
365
365
366 if settings['gzip_responses']:
366 if settings['gzip_responses']:
367 pyramid_app = make_gzip_middleware(
367 pyramid_app = make_gzip_middleware(
368 pyramid_app, settings, compress_level=1)
368 pyramid_app, settings, compress_level=1)
369
369
370 # this should be the outer most middleware in the wsgi stack since
370 # this should be the outer most middleware in the wsgi stack since
371 # middleware like Routes make database calls
371 # middleware like Routes make database calls
372 def pyramid_app_with_cleanup(environ, start_response):
372 def pyramid_app_with_cleanup(environ, start_response):
373 try:
373 try:
374 return pyramid_app(environ, start_response)
374 return pyramid_app(environ, start_response)
375 finally:
375 finally:
376 # Dispose current database session and rollback uncommitted
376 # Dispose current database session and rollback uncommitted
377 # transactions.
377 # transactions.
378 meta.Session.remove()
378 meta.Session.remove()
379
379
380 # In a single threaded mode server, on non sqlite db we should have
380 # In a single threaded mode server, on non sqlite db we should have
381 # '0 Current Checked out connections' at the end of a request,
381 # '0 Current Checked out connections' at the end of a request,
382 # if not, then something, somewhere is leaving a connection open
382 # if not, then something, somewhere is leaving a connection open
383 pool = meta.Base.metadata.bind.engine.pool
383 pool = meta.Base.metadata.bind.engine.pool
384 log.debug('sa pool status: %s', pool.status())
384 log.debug('sa pool status: %s', pool.status())
385
385
386
386
387 return pyramid_app_with_cleanup
387 return pyramid_app_with_cleanup
388
388
389
389
390 def sanitize_settings_and_apply_defaults(settings):
390 def sanitize_settings_and_apply_defaults(settings):
391 """
391 """
392 Applies settings defaults and does all type conversion.
392 Applies settings defaults and does all type conversion.
393
393
394 We would move all settings parsing and preparation into this place, so that
394 We would move all settings parsing and preparation into this place, so that
395 we have only one place left which deals with this part. The remaining parts
395 we have only one place left which deals with this part. The remaining parts
396 of the application would start to rely fully on well prepared settings.
396 of the application would start to rely fully on well prepared settings.
397
397
398 This piece would later be split up per topic to avoid a big fat monster
398 This piece would later be split up per topic to avoid a big fat monster
399 function.
399 function.
400 """
400 """
401
401
402 # Pyramid's mako renderer has to search in the templates folder so that the
402 # Pyramid's mako renderer has to search in the templates folder so that the
403 # old templates still work. Ported and new templates are expected to use
403 # old templates still work. Ported and new templates are expected to use
404 # real asset specifications for the includes.
404 # real asset specifications for the includes.
405 mako_directories = settings.setdefault('mako.directories', [
405 mako_directories = settings.setdefault('mako.directories', [
406 # Base templates of the original Pylons application
406 # Base templates of the original Pylons application
407 'rhodecode:templates',
407 'rhodecode:templates',
408 ])
408 ])
409 log.debug(
409 log.debug(
410 "Using the following Mako template directories: %s",
410 "Using the following Mako template directories: %s",
411 mako_directories)
411 mako_directories)
412
412
413 # Default includes, possible to change as a user
413 # Default includes, possible to change as a user
414 pyramid_includes = settings.setdefault('pyramid.includes', [
414 pyramid_includes = settings.setdefault('pyramid.includes', [
415 'rhodecode.lib.middleware.request_wrapper',
415 'rhodecode.lib.middleware.request_wrapper',
416 ])
416 ])
417 log.debug(
417 log.debug(
418 "Using the following pyramid.includes: %s",
418 "Using the following pyramid.includes: %s",
419 pyramid_includes)
419 pyramid_includes)
420
420
421 # TODO: johbo: Re-think this, usually the call to config.include
421 # TODO: johbo: Re-think this, usually the call to config.include
422 # should allow to pass in a prefix.
422 # should allow to pass in a prefix.
423 settings.setdefault('rhodecode.api.url', '/_admin/api')
423 settings.setdefault('rhodecode.api.url', '/_admin/api')
424
424
425 # Sanitize generic settings.
425 # Sanitize generic settings.
426 _list_setting(settings, 'default_encoding', 'UTF-8')
426 _list_setting(settings, 'default_encoding', 'UTF-8')
427 _bool_setting(settings, 'is_test', 'false')
427 _bool_setting(settings, 'is_test', 'false')
428 _bool_setting(settings, 'gzip_responses', 'false')
428 _bool_setting(settings, 'gzip_responses', 'false')
429
429
430 # Call split out functions that sanitize settings for each topic.
430 # Call split out functions that sanitize settings for each topic.
431 _sanitize_appenlight_settings(settings)
431 _sanitize_appenlight_settings(settings)
432 _sanitize_vcs_settings(settings)
432 _sanitize_vcs_settings(settings)
433
433
434 return settings
434 return settings
435
435
436
436
437 def _sanitize_appenlight_settings(settings):
437 def _sanitize_appenlight_settings(settings):
438 _bool_setting(settings, 'appenlight', 'false')
438 _bool_setting(settings, 'appenlight', 'false')
439
439
440
440
441 def _sanitize_vcs_settings(settings):
441 def _sanitize_vcs_settings(settings):
442 """
442 """
443 Applies settings defaults and does type conversion for all VCS related
443 Applies settings defaults and does type conversion for all VCS related
444 settings.
444 settings.
445 """
445 """
446 _string_setting(settings, 'vcs.svn.compatible_version', '')
446 _string_setting(settings, 'vcs.svn.compatible_version', '')
447 _string_setting(settings, 'git_rev_filter', '--all')
447 _string_setting(settings, 'git_rev_filter', '--all')
448 _string_setting(settings, 'vcs.hooks.protocol', 'http')
448 _string_setting(settings, 'vcs.hooks.protocol', 'http')
449 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
449 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
450 _string_setting(settings, 'vcs.server', '')
450 _string_setting(settings, 'vcs.server', '')
451 _string_setting(settings, 'vcs.server.log_level', 'debug')
451 _string_setting(settings, 'vcs.server.log_level', 'debug')
452 _string_setting(settings, 'vcs.server.protocol', 'http')
452 _string_setting(settings, 'vcs.server.protocol', 'http')
453 _bool_setting(settings, 'startup.import_repos', 'false')
453 _bool_setting(settings, 'startup.import_repos', 'false')
454 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
454 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
455 _bool_setting(settings, 'vcs.server.enable', 'true')
455 _bool_setting(settings, 'vcs.server.enable', 'true')
456 _bool_setting(settings, 'vcs.start_server', 'false')
456 _bool_setting(settings, 'vcs.start_server', 'false')
457 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
457 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
458 _int_setting(settings, 'vcs.connection_timeout', 3600)
458 _int_setting(settings, 'vcs.connection_timeout', 3600)
459
459
460 # Support legacy values of vcs.scm_app_implementation. Legacy
460 # Support legacy values of vcs.scm_app_implementation. Legacy
461 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
461 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http'
462 # which is now mapped to 'http'.
462 # which is now mapped to 'http'.
463 scm_app_impl = settings['vcs.scm_app_implementation']
463 scm_app_impl = settings['vcs.scm_app_implementation']
464 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
464 if scm_app_impl == 'rhodecode.lib.middleware.utils.scm_app_http':
465 settings['vcs.scm_app_implementation'] = 'http'
465 settings['vcs.scm_app_implementation'] = 'http'
466
466
467
467
468 def _int_setting(settings, name, default):
468 def _int_setting(settings, name, default):
469 settings[name] = int(settings.get(name, default))
469 settings[name] = int(settings.get(name, default))
470
470
471
471
472 def _bool_setting(settings, name, default):
472 def _bool_setting(settings, name, default):
473 input = settings.get(name, default)
473 input = settings.get(name, default)
474 if isinstance(input, unicode):
474 if isinstance(input, unicode):
475 input = input.encode('utf8')
475 input = input.encode('utf8')
476 settings[name] = asbool(input)
476 settings[name] = asbool(input)
477
477
478
478
479 def _list_setting(settings, name, default):
479 def _list_setting(settings, name, default):
480 raw_value = settings.get(name, default)
480 raw_value = settings.get(name, default)
481
481
482 old_separator = ','
482 old_separator = ','
483 if old_separator in raw_value:
483 if old_separator in raw_value:
484 # If we get a comma separated list, pass it to our own function.
484 # If we get a comma separated list, pass it to our own function.
485 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
485 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
486 else:
486 else:
487 # Otherwise we assume it uses pyramids space/newline separation.
487 # Otherwise we assume it uses pyramids space/newline separation.
488 settings[name] = aslist(raw_value)
488 settings[name] = aslist(raw_value)
489
489
490
490
491 def _string_setting(settings, name, default, lower=True):
491 def _string_setting(settings, name, default, lower=True):
492 value = settings.get(name, default)
492 value = settings.get(name, default)
493 if lower:
493 if lower:
494 value = value.lower()
494 value = value.lower()
495 settings[name] = value
495 settings[name] = value
General Comments 0
You need to be logged in to leave comments. Login now