##// END OF EJS Templates
app: disconect auth plugin loading from authentication registry....
marcink -
r3241:611f6ed0 default
parent child Browse files
Show More
@@ -1,111 +1,99 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2018 RhodeCode GmbH
3 # Copyright (C) 2012-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import importlib
22 import importlib
23
23
24 from pyramid.authentication import SessionAuthenticationPolicy
24 from pyramid.authentication import SessionAuthenticationPolicy
25
25
26 from rhodecode.authentication.registry import AuthenticationPluginRegistry
26 from rhodecode.authentication.registry import AuthenticationPluginRegistry
27 from rhodecode.authentication.routes import root_factory
27 from rhodecode.authentication.routes import root_factory
28 from rhodecode.authentication.routes import AuthnRootResource
28 from rhodecode.authentication.routes import AuthnRootResource
29 from rhodecode.apps._base import ADMIN_PREFIX
29 from rhodecode.apps._base import ADMIN_PREFIX
30 from rhodecode.model.settings import SettingsModel
30 from rhodecode.model.settings import SettingsModel
31
31
32 log = logging.getLogger(__name__)
32 log = logging.getLogger(__name__)
33
33
34 legacy_plugin_prefix = 'py:'
34 legacy_plugin_prefix = 'py:'
35 plugin_default_auth_ttl = 30
35 plugin_default_auth_ttl = 30
36
36
37
37
38 def _import_legacy_plugin(plugin_id):
38 def _import_legacy_plugin(plugin_id):
39 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
39 module_name = plugin_id.split(legacy_plugin_prefix, 1)[-1]
40 module = importlib.import_module(module_name)
40 module = importlib.import_module(module_name)
41 return module.plugin_factory(plugin_id=plugin_id)
41 return module.plugin_factory(plugin_id=plugin_id)
42
42
43
43
44 def _discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
44 def discover_legacy_plugins(config, prefix=legacy_plugin_prefix):
45 """
45 """
46 Function that imports the legacy plugins stored in the 'auth_plugins'
46 Function that imports the legacy plugins stored in the 'auth_plugins'
47 setting in database which are using the specified prefix. Normally 'py:' is
47 setting in database which are using the specified prefix. Normally 'py:' is
48 used for the legacy plugins.
48 used for the legacy plugins.
49 """
49 """
50 log.debug('authentication: running legacy plugin discovery for prefix %s',
50 log.debug('authentication: running legacy plugin discovery for prefix %s',
51 legacy_plugin_prefix)
51 legacy_plugin_prefix)
52 try:
52 try:
53 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
53 auth_plugins = SettingsModel().get_setting_by_name('auth_plugins')
54 enabled_plugins = auth_plugins.app_settings_value
54 enabled_plugins = auth_plugins.app_settings_value
55 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
55 legacy_plugins = [id_ for id_ in enabled_plugins if id_.startswith(prefix)]
56 except Exception:
56 except Exception:
57 legacy_plugins = []
57 legacy_plugins = []
58
58
59 for plugin_id in legacy_plugins:
59 for plugin_id in legacy_plugins:
60 log.debug('Legacy plugin discovered: "%s"', plugin_id)
60 log.debug('Legacy plugin discovered: "%s"', plugin_id)
61 try:
61 try:
62 plugin = _import_legacy_plugin(plugin_id)
62 plugin = _import_legacy_plugin(plugin_id)
63 config.include(plugin.includeme)
63 config.include(plugin.includeme)
64 except Exception as e:
64 except Exception as e:
65 log.exception(
65 log.exception(
66 'Exception while loading legacy authentication plugin '
66 'Exception while loading legacy authentication plugin '
67 '"{}": {}'.format(plugin_id, e.message))
67 '"{}": {}'.format(plugin_id, e.message))
68
68
69
69
70 def includeme(config):
70 def includeme(config):
71 # Set authentication policy.
71 # Set authentication policy.
72 authn_policy = SessionAuthenticationPolicy()
72 authn_policy = SessionAuthenticationPolicy()
73 config.set_authentication_policy(authn_policy)
73 config.set_authentication_policy(authn_policy)
74
74
75 # Create authentication plugin registry and add it to the pyramid registry.
75 # Create authentication plugin registry and add it to the pyramid registry.
76 authn_registry = AuthenticationPluginRegistry(config.get_settings())
76 authn_registry = AuthenticationPluginRegistry(config.get_settings())
77 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
77 config.add_directive('add_authn_plugin', authn_registry.add_authn_plugin)
78 config.registry.registerUtility(authn_registry)
78 config.registry.registerUtility(authn_registry)
79
79
80 # Create authentication traversal root resource.
80 # Create authentication traversal root resource.
81 authn_root_resource = root_factory()
81 authn_root_resource = root_factory()
82 config.add_directive('add_authn_resource',
82 config.add_directive('add_authn_resource',
83 authn_root_resource.add_authn_resource)
83 authn_root_resource.add_authn_resource)
84
84
85 # Add the authentication traversal route.
85 # Add the authentication traversal route.
86 config.add_route('auth_home',
86 config.add_route('auth_home',
87 ADMIN_PREFIX + '/auth*traverse',
87 ADMIN_PREFIX + '/auth*traverse',
88 factory=root_factory)
88 factory=root_factory)
89 # Add the authentication settings root views.
89 # Add the authentication settings root views.
90 config.add_view('rhodecode.authentication.views.AuthSettingsView',
90 config.add_view('rhodecode.authentication.views.AuthSettingsView',
91 attr='index',
91 attr='index',
92 request_method='GET',
92 request_method='GET',
93 route_name='auth_home',
93 route_name='auth_home',
94 context=AuthnRootResource)
94 context=AuthnRootResource)
95 config.add_view('rhodecode.authentication.views.AuthSettingsView',
95 config.add_view('rhodecode.authentication.views.AuthSettingsView',
96 attr='auth_settings',
96 attr='auth_settings',
97 request_method='POST',
97 request_method='POST',
98 route_name='auth_home',
98 route_name='auth_home',
99 context=AuthnRootResource)
99 context=AuthnRootResource)
100
101 # load CE authentication plugins
102 config.include('rhodecode.authentication.plugins.auth_crowd')
103 config.include('rhodecode.authentication.plugins.auth_headers')
104 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
105 config.include('rhodecode.authentication.plugins.auth_ldap')
106 config.include('rhodecode.authentication.plugins.auth_pam')
107 config.include('rhodecode.authentication.plugins.auth_rhodecode')
108 config.include('rhodecode.authentication.plugins.auth_token')
109
110 # Auto discover authentication plugins and include their configuration.
111 _discover_legacy_plugins(config)
@@ -1,588 +1,614 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
21 import os
22 import sys
22 import sys
23 import logging
23 import logging
24 import collections
24 import collections
25 import tempfile
25 import tempfile
26 import time
26 import time
27
27
28 from paste.gzipper import make_gzip_middleware
28 from paste.gzipper import make_gzip_middleware
29 import pyramid.events
29 import pyramid.events
30 from pyramid.wsgi import wsgiapp
30 from pyramid.wsgi import wsgiapp
31 from pyramid.authorization import ACLAuthorizationPolicy
31 from pyramid.authorization import ACLAuthorizationPolicy
32 from pyramid.config import Configurator
32 from pyramid.config import Configurator
33 from pyramid.settings import asbool, aslist
33 from pyramid.settings import asbool, aslist
34 from pyramid.httpexceptions import (
34 from pyramid.httpexceptions import (
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
35 HTTPException, HTTPError, HTTPInternalServerError, HTTPFound, HTTPNotFound)
36 from pyramid.renderers import render_to_response
36 from pyramid.renderers import render_to_response
37
37
38 from rhodecode.model import meta
38 from rhodecode.model import meta
39 from rhodecode.config import patches
39 from rhodecode.config import patches
40 from rhodecode.config import utils as config_utils
40 from rhodecode.config import utils as config_utils
41 from rhodecode.config.environment import load_pyramid_environment
41 from rhodecode.config.environment import load_pyramid_environment
42
42
43 import rhodecode.events
43 import rhodecode.events
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
44 from rhodecode.lib.middleware.vcs import VCSMiddleware
45 from rhodecode.lib.request import Request
45 from rhodecode.lib.request import Request
46 from rhodecode.lib.vcs import VCSCommunicationError
46 from rhodecode.lib.vcs import VCSCommunicationError
47 from rhodecode.lib.exceptions import VCSServerUnavailable
47 from rhodecode.lib.exceptions import VCSServerUnavailable
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
48 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
49 from rhodecode.lib.middleware.https_fixup import HttpsFixup
50 from rhodecode.lib.celerylib.loader import configure_celery
50 from rhodecode.lib.celerylib.loader import configure_celery
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
51 from rhodecode.lib.plugins.utils import register_rhodecode_plugin
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
52 from rhodecode.lib.utils2 import aslist as rhodecode_aslist, AttributeDict
53 from rhodecode.lib.exc_tracking import store_exception
53 from rhodecode.lib.exc_tracking import store_exception
54 from rhodecode.subscribers import (
54 from rhodecode.subscribers import (
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
55 scan_repositories_if_enabled, write_js_routes_if_enabled,
56 write_metadata_if_needed, inject_app_settings)
56 write_metadata_if_needed, inject_app_settings)
57
57
58
58
59 log = logging.getLogger(__name__)
59 log = logging.getLogger(__name__)
60
60
61
61
62 def is_http_error(response):
62 def is_http_error(response):
63 # error which should have traceback
63 # error which should have traceback
64 return response.status_code > 499
64 return response.status_code > 499
65
65
66
66
67 def should_load_all():
68 """
69 Returns if all application components should be loaded. In some cases it's
70 desired to skip apps loading for faster shell script execution
71 """
72 return True
73
74
67 def make_pyramid_app(global_config, **settings):
75 def make_pyramid_app(global_config, **settings):
68 """
76 """
69 Constructs the WSGI application based on Pyramid.
77 Constructs the WSGI application based on Pyramid.
70
78
71 Specials:
79 Specials:
72
80
73 * The application can also be integrated like a plugin via the call to
81 * The application can also be integrated like a plugin via the call to
74 `includeme`. This is accompanied with the other utility functions which
82 `includeme`. This is accompanied with the other utility functions which
75 are called. Changing this should be done with great care to not break
83 are called. Changing this should be done with great care to not break
76 cases when these fragments are assembled from another place.
84 cases when these fragments are assembled from another place.
77
85
78 """
86 """
79
87
80 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
88 # Allows to use format style "{ENV_NAME}" placeholders in the configuration. It
81 # will be replaced by the value of the environment variable "NAME" in this case.
89 # will be replaced by the value of the environment variable "NAME" in this case.
82 start_time = time.time()
90 start_time = time.time()
83
91
84 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
92 environ = {'ENV_{}'.format(key): value for key, value in os.environ.items()}
85
93
86 global_config = _substitute_values(global_config, environ)
94 global_config = _substitute_values(global_config, environ)
87 settings = _substitute_values(settings, environ)
95 settings = _substitute_values(settings, environ)
88
96
89 sanitize_settings_and_apply_defaults(settings)
97 sanitize_settings_and_apply_defaults(settings)
90
98
91 config = Configurator(settings=settings)
99 config = Configurator(settings=settings)
92
100
93 # Apply compatibility patches
101 # Apply compatibility patches
94 patches.inspect_getargspec()
102 patches.inspect_getargspec()
95
103
96 load_pyramid_environment(global_config, settings)
104 load_pyramid_environment(global_config, settings)
97
105
98 # Static file view comes first
106 # Static file view comes first
99 includeme_first(config)
107 includeme_first(config)
100
108
101 includeme(config)
109 includeme(config)
102
110
103 pyramid_app = config.make_wsgi_app()
111 pyramid_app = config.make_wsgi_app()
104 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
112 pyramid_app = wrap_app_in_wsgi_middlewares(pyramid_app, config)
105 pyramid_app.config = config
113 pyramid_app.config = config
106
114
107 config.configure_celery(global_config['__file__'])
115 config.configure_celery(global_config['__file__'])
108 # creating the app uses a connection - return it after we are done
116 # creating the app uses a connection - return it after we are done
109 meta.Session.remove()
117 meta.Session.remove()
110 total_time = time.time() - start_time
118 total_time = time.time() - start_time
111 log.info('Pyramid app `%s` created and configured in %.2fs',
119 log.info('Pyramid app `%s` created and configured in %.2fs',
112 pyramid_app.func_name, total_time)
120 pyramid_app.func_name, total_time)
113 return pyramid_app
121 return pyramid_app
114
122
115
123
116 def not_found_view(request):
124 def not_found_view(request):
117 """
125 """
118 This creates the view which should be registered as not-found-view to
126 This creates the view which should be registered as not-found-view to
119 pyramid.
127 pyramid.
120 """
128 """
121
129
122 if not getattr(request, 'vcs_call', None):
130 if not getattr(request, 'vcs_call', None):
123 # handle like regular case with our error_handler
131 # handle like regular case with our error_handler
124 return error_handler(HTTPNotFound(), request)
132 return error_handler(HTTPNotFound(), request)
125
133
126 # handle not found view as a vcs call
134 # handle not found view as a vcs call
127 settings = request.registry.settings
135 settings = request.registry.settings
128 ae_client = getattr(request, 'ae_client', None)
136 ae_client = getattr(request, 'ae_client', None)
129 vcs_app = VCSMiddleware(
137 vcs_app = VCSMiddleware(
130 HTTPNotFound(), request.registry, settings,
138 HTTPNotFound(), request.registry, settings,
131 appenlight_client=ae_client)
139 appenlight_client=ae_client)
132
140
133 return wsgiapp(vcs_app)(None, request)
141 return wsgiapp(vcs_app)(None, request)
134
142
135
143
136 def error_handler(exception, request):
144 def error_handler(exception, request):
137 import rhodecode
145 import rhodecode
138 from rhodecode.lib import helpers
146 from rhodecode.lib import helpers
139
147
140 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
148 rhodecode_title = rhodecode.CONFIG.get('rhodecode_title') or 'RhodeCode'
141
149
142 base_response = HTTPInternalServerError()
150 base_response = HTTPInternalServerError()
143 # prefer original exception for the response since it may have headers set
151 # prefer original exception for the response since it may have headers set
144 if isinstance(exception, HTTPException):
152 if isinstance(exception, HTTPException):
145 base_response = exception
153 base_response = exception
146 elif isinstance(exception, VCSCommunicationError):
154 elif isinstance(exception, VCSCommunicationError):
147 base_response = VCSServerUnavailable()
155 base_response = VCSServerUnavailable()
148
156
149 if is_http_error(base_response):
157 if is_http_error(base_response):
150 log.exception(
158 log.exception(
151 'error occurred handling this request for path: %s', request.path)
159 'error occurred handling this request for path: %s', request.path)
152
160
153 error_explanation = base_response.explanation or str(base_response)
161 error_explanation = base_response.explanation or str(base_response)
154 if base_response.status_code == 404:
162 if base_response.status_code == 404:
155 error_explanation += " Or you don't have permission to access it."
163 error_explanation += " Or you don't have permission to access it."
156 c = AttributeDict()
164 c = AttributeDict()
157 c.error_message = base_response.status
165 c.error_message = base_response.status
158 c.error_explanation = error_explanation
166 c.error_explanation = error_explanation
159 c.visual = AttributeDict()
167 c.visual = AttributeDict()
160
168
161 c.visual.rhodecode_support_url = (
169 c.visual.rhodecode_support_url = (
162 request.registry.settings.get('rhodecode_support_url') or
170 request.registry.settings.get('rhodecode_support_url') or
163 request.route_url('rhodecode_support')
171 request.route_url('rhodecode_support')
164 )
172 )
165 c.redirect_time = 0
173 c.redirect_time = 0
166 c.rhodecode_name = rhodecode_title
174 c.rhodecode_name = rhodecode_title
167 if not c.rhodecode_name:
175 if not c.rhodecode_name:
168 c.rhodecode_name = 'Rhodecode'
176 c.rhodecode_name = 'Rhodecode'
169
177
170 c.causes = []
178 c.causes = []
171 if is_http_error(base_response):
179 if is_http_error(base_response):
172 c.causes.append('Server is overloaded.')
180 c.causes.append('Server is overloaded.')
173 c.causes.append('Server database connection is lost.')
181 c.causes.append('Server database connection is lost.')
174 c.causes.append('Server expected unhandled error.')
182 c.causes.append('Server expected unhandled error.')
175
183
176 if hasattr(base_response, 'causes'):
184 if hasattr(base_response, 'causes'):
177 c.causes = base_response.causes
185 c.causes = base_response.causes
178
186
179 c.messages = helpers.flash.pop_messages(request=request)
187 c.messages = helpers.flash.pop_messages(request=request)
180
188
181 exc_info = sys.exc_info()
189 exc_info = sys.exc_info()
182 c.exception_id = id(exc_info)
190 c.exception_id = id(exc_info)
183 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
191 c.show_exception_id = isinstance(base_response, VCSServerUnavailable) \
184 or base_response.status_code > 499
192 or base_response.status_code > 499
185 c.exception_id_url = request.route_url(
193 c.exception_id_url = request.route_url(
186 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
194 'admin_settings_exception_tracker_show', exception_id=c.exception_id)
187
195
188 if c.show_exception_id:
196 if c.show_exception_id:
189 store_exception(c.exception_id, exc_info)
197 store_exception(c.exception_id, exc_info)
190
198
191 response = render_to_response(
199 response = render_to_response(
192 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
200 '/errors/error_document.mako', {'c': c, 'h': helpers}, request=request,
193 response=base_response)
201 response=base_response)
194
202
195 return response
203 return response
196
204
197
205
198 def includeme_first(config):
206 def includeme_first(config):
199 # redirect automatic browser favicon.ico requests to correct place
207 # redirect automatic browser favicon.ico requests to correct place
200 def favicon_redirect(context, request):
208 def favicon_redirect(context, request):
201 return HTTPFound(
209 return HTTPFound(
202 request.static_path('rhodecode:public/images/favicon.ico'))
210 request.static_path('rhodecode:public/images/favicon.ico'))
203
211
204 config.add_view(favicon_redirect, route_name='favicon')
212 config.add_view(favicon_redirect, route_name='favicon')
205 config.add_route('favicon', '/favicon.ico')
213 config.add_route('favicon', '/favicon.ico')
206
214
207 def robots_redirect(context, request):
215 def robots_redirect(context, request):
208 return HTTPFound(
216 return HTTPFound(
209 request.static_path('rhodecode:public/robots.txt'))
217 request.static_path('rhodecode:public/robots.txt'))
210
218
211 config.add_view(robots_redirect, route_name='robots')
219 config.add_view(robots_redirect, route_name='robots')
212 config.add_route('robots', '/robots.txt')
220 config.add_route('robots', '/robots.txt')
213
221
214 config.add_static_view(
222 config.add_static_view(
215 '_static/deform', 'deform:static')
223 '_static/deform', 'deform:static')
216 config.add_static_view(
224 config.add_static_view(
217 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
225 '_static/rhodecode', path='rhodecode:public', cache_max_age=3600 * 24)
218
226
219
227
220 def includeme(config):
228 def includeme(config):
221 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
229 log.debug('Initializing main includeme from %s', os.path.basename(__file__))
222 settings = config.registry.settings
230 settings = config.registry.settings
223 config.set_request_factory(Request)
231 config.set_request_factory(Request)
224
232
225 # plugin information
233 # plugin information
226 config.registry.rhodecode_plugins = collections.OrderedDict()
234 config.registry.rhodecode_plugins = collections.OrderedDict()
227
235
228 config.add_directive(
236 config.add_directive(
229 'register_rhodecode_plugin', register_rhodecode_plugin)
237 'register_rhodecode_plugin', register_rhodecode_plugin)
230
238
231 config.add_directive('configure_celery', configure_celery)
239 config.add_directive('configure_celery', configure_celery)
232
240
233 if asbool(settings.get('appenlight', 'false')):
241 if asbool(settings.get('appenlight', 'false')):
234 config.include('appenlight_client.ext.pyramid_tween')
242 config.include('appenlight_client.ext.pyramid_tween')
235
243
244 load_all = should_load_all()
245
236 # Includes which are required. The application would fail without them.
246 # Includes which are required. The application would fail without them.
237 config.include('pyramid_mako')
247 config.include('pyramid_mako')
238 config.include('pyramid_beaker')
248 config.include('pyramid_beaker')
239 config.include('rhodecode.lib.rc_cache')
249 config.include('rhodecode.lib.rc_cache')
240
250
241 config.include('rhodecode.apps._base.navigation')
251 config.include('rhodecode.apps._base.navigation')
242 config.include('rhodecode.apps._base.subscribers')
252 config.include('rhodecode.apps._base.subscribers')
243 config.include('rhodecode.tweens')
253 config.include('rhodecode.tweens')
244
254
245 config.include('rhodecode.integrations')
255 config.include('rhodecode.integrations')
246 config.include('rhodecode.authentication')
256 config.include('rhodecode.authentication')
247
257
258 if load_all:
259 from rhodecode.authentication import discover_legacy_plugins
260 # load CE authentication plugins
261 config.include('rhodecode.authentication.plugins.auth_crowd')
262 config.include('rhodecode.authentication.plugins.auth_headers')
263 config.include('rhodecode.authentication.plugins.auth_jasig_cas')
264 config.include('rhodecode.authentication.plugins.auth_ldap')
265 config.include('rhodecode.authentication.plugins.auth_pam')
266 config.include('rhodecode.authentication.plugins.auth_rhodecode')
267 config.include('rhodecode.authentication.plugins.auth_token')
268
269 # Auto discover authentication plugins and include their configuration.
270 discover_legacy_plugins(config)
271
248 # apps
272 # apps
249 config.include('rhodecode.apps._base')
273 config.include('rhodecode.apps._base')
250 config.include('rhodecode.apps.ops')
274
251 config.include('rhodecode.apps.admin')
275 if load_all:
252 config.include('rhodecode.apps.channelstream')
276 config.include('rhodecode.apps.ops')
253 config.include('rhodecode.apps.login')
277 config.include('rhodecode.apps.admin')
254 config.include('rhodecode.apps.home')
278 config.include('rhodecode.apps.channelstream')
255 config.include('rhodecode.apps.journal')
279 config.include('rhodecode.apps.login')
256 config.include('rhodecode.apps.repository')
280 config.include('rhodecode.apps.home')
257 config.include('rhodecode.apps.repo_group')
281 config.include('rhodecode.apps.journal')
258 config.include('rhodecode.apps.user_group')
282 config.include('rhodecode.apps.repository')
259 config.include('rhodecode.apps.search')
283 config.include('rhodecode.apps.repo_group')
260 config.include('rhodecode.apps.user_profile')
284 config.include('rhodecode.apps.user_group')
261 config.include('rhodecode.apps.user_group_profile')
285 config.include('rhodecode.apps.search')
262 config.include('rhodecode.apps.my_account')
286 config.include('rhodecode.apps.user_profile')
263 config.include('rhodecode.apps.svn_support')
287 config.include('rhodecode.apps.user_group_profile')
264 config.include('rhodecode.apps.ssh_support')
288 config.include('rhodecode.apps.my_account')
265 config.include('rhodecode.apps.gist')
289 config.include('rhodecode.apps.svn_support')
266 config.include('rhodecode.apps.debug_style')
290 config.include('rhodecode.apps.ssh_support')
267 config.include('rhodecode.api')
291 config.include('rhodecode.apps.gist')
292 config.include('rhodecode.apps.debug_style')
293 config.include('rhodecode.api')
268
294
269 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
295 config.add_route('rhodecode_support', 'https://rhodecode.com/help/', static=True)
270 config.add_translation_dirs('rhodecode:i18n/')
296 config.add_translation_dirs('rhodecode:i18n/')
271 settings['default_locale_name'] = settings.get('lang', 'en')
297 settings['default_locale_name'] = settings.get('lang', 'en')
272
298
273 # Add subscribers.
299 # Add subscribers.
274 config.add_subscriber(inject_app_settings,
300 config.add_subscriber(inject_app_settings,
275 pyramid.events.ApplicationCreated)
301 pyramid.events.ApplicationCreated)
276 config.add_subscriber(scan_repositories_if_enabled,
302 config.add_subscriber(scan_repositories_if_enabled,
277 pyramid.events.ApplicationCreated)
303 pyramid.events.ApplicationCreated)
278 config.add_subscriber(write_metadata_if_needed,
304 config.add_subscriber(write_metadata_if_needed,
279 pyramid.events.ApplicationCreated)
305 pyramid.events.ApplicationCreated)
280 config.add_subscriber(write_js_routes_if_enabled,
306 config.add_subscriber(write_js_routes_if_enabled,
281 pyramid.events.ApplicationCreated)
307 pyramid.events.ApplicationCreated)
282
308
283 # request custom methods
309 # request custom methods
284 config.add_request_method(
310 config.add_request_method(
285 'rhodecode.lib.partial_renderer.get_partial_renderer',
311 'rhodecode.lib.partial_renderer.get_partial_renderer',
286 'get_partial_renderer')
312 'get_partial_renderer')
287
313
288 # Set the authorization policy.
314 # Set the authorization policy.
289 authz_policy = ACLAuthorizationPolicy()
315 authz_policy = ACLAuthorizationPolicy()
290 config.set_authorization_policy(authz_policy)
316 config.set_authorization_policy(authz_policy)
291
317
292 # Set the default renderer for HTML templates to mako.
318 # Set the default renderer for HTML templates to mako.
293 config.add_mako_renderer('.html')
319 config.add_mako_renderer('.html')
294
320
295 config.add_renderer(
321 config.add_renderer(
296 name='json_ext',
322 name='json_ext',
297 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
323 factory='rhodecode.lib.ext_json_renderer.pyramid_ext_json')
298
324
299 # include RhodeCode plugins
325 # include RhodeCode plugins
300 includes = aslist(settings.get('rhodecode.includes', []))
326 includes = aslist(settings.get('rhodecode.includes', []))
301 for inc in includes:
327 for inc in includes:
302 config.include(inc)
328 config.include(inc)
303
329
304 # custom not found view, if our pyramid app doesn't know how to handle
330 # custom not found view, if our pyramid app doesn't know how to handle
305 # the request pass it to potential VCS handling ap
331 # the request pass it to potential VCS handling ap
306 config.add_notfound_view(not_found_view)
332 config.add_notfound_view(not_found_view)
307 if not settings.get('debugtoolbar.enabled', False):
333 if not settings.get('debugtoolbar.enabled', False):
308 # disabled debugtoolbar handle all exceptions via the error_handlers
334 # disabled debugtoolbar handle all exceptions via the error_handlers
309 config.add_view(error_handler, context=Exception)
335 config.add_view(error_handler, context=Exception)
310
336
311 # all errors including 403/404/50X
337 # all errors including 403/404/50X
312 config.add_view(error_handler, context=HTTPError)
338 config.add_view(error_handler, context=HTTPError)
313
339
314
340
315 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
341 def wrap_app_in_wsgi_middlewares(pyramid_app, config):
316 """
342 """
317 Apply outer WSGI middlewares around the application.
343 Apply outer WSGI middlewares around the application.
318 """
344 """
319 registry = config.registry
345 registry = config.registry
320 settings = registry.settings
346 settings = registry.settings
321
347
322 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
348 # enable https redirects based on HTTP_X_URL_SCHEME set by proxy
323 pyramid_app = HttpsFixup(pyramid_app, settings)
349 pyramid_app = HttpsFixup(pyramid_app, settings)
324
350
325 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
351 pyramid_app, _ae_client = wrap_in_appenlight_if_enabled(
326 pyramid_app, settings)
352 pyramid_app, settings)
327 registry.ae_client = _ae_client
353 registry.ae_client = _ae_client
328
354
329 if settings['gzip_responses']:
355 if settings['gzip_responses']:
330 pyramid_app = make_gzip_middleware(
356 pyramid_app = make_gzip_middleware(
331 pyramid_app, settings, compress_level=1)
357 pyramid_app, settings, compress_level=1)
332
358
333 # this should be the outer most middleware in the wsgi stack since
359 # this should be the outer most middleware in the wsgi stack since
334 # middleware like Routes make database calls
360 # middleware like Routes make database calls
335 def pyramid_app_with_cleanup(environ, start_response):
361 def pyramid_app_with_cleanup(environ, start_response):
336 try:
362 try:
337 return pyramid_app(environ, start_response)
363 return pyramid_app(environ, start_response)
338 finally:
364 finally:
339 # Dispose current database session and rollback uncommitted
365 # Dispose current database session and rollback uncommitted
340 # transactions.
366 # transactions.
341 meta.Session.remove()
367 meta.Session.remove()
342
368
343 # In a single threaded mode server, on non sqlite db we should have
369 # In a single threaded mode server, on non sqlite db we should have
344 # '0 Current Checked out connections' at the end of a request,
370 # '0 Current Checked out connections' at the end of a request,
345 # if not, then something, somewhere is leaving a connection open
371 # if not, then something, somewhere is leaving a connection open
346 pool = meta.Base.metadata.bind.engine.pool
372 pool = meta.Base.metadata.bind.engine.pool
347 log.debug('sa pool status: %s', pool.status())
373 log.debug('sa pool status: %s', pool.status())
348 log.debug('Request processing finalized')
374 log.debug('Request processing finalized')
349
375
350 return pyramid_app_with_cleanup
376 return pyramid_app_with_cleanup
351
377
352
378
353 def sanitize_settings_and_apply_defaults(settings):
379 def sanitize_settings_and_apply_defaults(settings):
354 """
380 """
355 Applies settings defaults and does all type conversion.
381 Applies settings defaults and does all type conversion.
356
382
357 We would move all settings parsing and preparation into this place, so that
383 We would move all settings parsing and preparation into this place, so that
358 we have only one place left which deals with this part. The remaining parts
384 we have only one place left which deals with this part. The remaining parts
359 of the application would start to rely fully on well prepared settings.
385 of the application would start to rely fully on well prepared settings.
360
386
361 This piece would later be split up per topic to avoid a big fat monster
387 This piece would later be split up per topic to avoid a big fat monster
362 function.
388 function.
363 """
389 """
364
390
365 settings.setdefault('rhodecode.edition', 'Community Edition')
391 settings.setdefault('rhodecode.edition', 'Community Edition')
366
392
367 if 'mako.default_filters' not in settings:
393 if 'mako.default_filters' not in settings:
368 # set custom default filters if we don't have it defined
394 # set custom default filters if we don't have it defined
369 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
395 settings['mako.imports'] = 'from rhodecode.lib.base import h_filter'
370 settings['mako.default_filters'] = 'h_filter'
396 settings['mako.default_filters'] = 'h_filter'
371
397
372 if 'mako.directories' not in settings:
398 if 'mako.directories' not in settings:
373 mako_directories = settings.setdefault('mako.directories', [
399 mako_directories = settings.setdefault('mako.directories', [
374 # Base templates of the original application
400 # Base templates of the original application
375 'rhodecode:templates',
401 'rhodecode:templates',
376 ])
402 ])
377 log.debug(
403 log.debug(
378 "Using the following Mako template directories: %s",
404 "Using the following Mako template directories: %s",
379 mako_directories)
405 mako_directories)
380
406
381 # Default includes, possible to change as a user
407 # Default includes, possible to change as a user
382 pyramid_includes = settings.setdefault('pyramid.includes', [
408 pyramid_includes = settings.setdefault('pyramid.includes', [
383 'rhodecode.lib.middleware.request_wrapper',
409 'rhodecode.lib.middleware.request_wrapper',
384 ])
410 ])
385 log.debug(
411 log.debug(
386 "Using the following pyramid.includes: %s",
412 "Using the following pyramid.includes: %s",
387 pyramid_includes)
413 pyramid_includes)
388
414
389 # TODO: johbo: Re-think this, usually the call to config.include
415 # TODO: johbo: Re-think this, usually the call to config.include
390 # should allow to pass in a prefix.
416 # should allow to pass in a prefix.
391 settings.setdefault('rhodecode.api.url', '/_admin/api')
417 settings.setdefault('rhodecode.api.url', '/_admin/api')
392
418
393 # Sanitize generic settings.
419 # Sanitize generic settings.
394 _list_setting(settings, 'default_encoding', 'UTF-8')
420 _list_setting(settings, 'default_encoding', 'UTF-8')
395 _bool_setting(settings, 'is_test', 'false')
421 _bool_setting(settings, 'is_test', 'false')
396 _bool_setting(settings, 'gzip_responses', 'false')
422 _bool_setting(settings, 'gzip_responses', 'false')
397
423
398 # Call split out functions that sanitize settings for each topic.
424 # Call split out functions that sanitize settings for each topic.
399 _sanitize_appenlight_settings(settings)
425 _sanitize_appenlight_settings(settings)
400 _sanitize_vcs_settings(settings)
426 _sanitize_vcs_settings(settings)
401 _sanitize_cache_settings(settings)
427 _sanitize_cache_settings(settings)
402
428
403 # configure instance id
429 # configure instance id
404 config_utils.set_instance_id(settings)
430 config_utils.set_instance_id(settings)
405
431
406 return settings
432 return settings
407
433
408
434
409 def _sanitize_appenlight_settings(settings):
435 def _sanitize_appenlight_settings(settings):
410 _bool_setting(settings, 'appenlight', 'false')
436 _bool_setting(settings, 'appenlight', 'false')
411
437
412
438
413 def _sanitize_vcs_settings(settings):
439 def _sanitize_vcs_settings(settings):
414 """
440 """
415 Applies settings defaults and does type conversion for all VCS related
441 Applies settings defaults and does type conversion for all VCS related
416 settings.
442 settings.
417 """
443 """
418 _string_setting(settings, 'vcs.svn.compatible_version', '')
444 _string_setting(settings, 'vcs.svn.compatible_version', '')
419 _string_setting(settings, 'git_rev_filter', '--all')
445 _string_setting(settings, 'git_rev_filter', '--all')
420 _string_setting(settings, 'vcs.hooks.protocol', 'http')
446 _string_setting(settings, 'vcs.hooks.protocol', 'http')
421 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
447 _string_setting(settings, 'vcs.hooks.host', '127.0.0.1')
422 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
448 _string_setting(settings, 'vcs.scm_app_implementation', 'http')
423 _string_setting(settings, 'vcs.server', '')
449 _string_setting(settings, 'vcs.server', '')
424 _string_setting(settings, 'vcs.server.log_level', 'debug')
450 _string_setting(settings, 'vcs.server.log_level', 'debug')
425 _string_setting(settings, 'vcs.server.protocol', 'http')
451 _string_setting(settings, 'vcs.server.protocol', 'http')
426 _bool_setting(settings, 'startup.import_repos', 'false')
452 _bool_setting(settings, 'startup.import_repos', 'false')
427 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
453 _bool_setting(settings, 'vcs.hooks.direct_calls', 'false')
428 _bool_setting(settings, 'vcs.server.enable', 'true')
454 _bool_setting(settings, 'vcs.server.enable', 'true')
429 _bool_setting(settings, 'vcs.start_server', 'false')
455 _bool_setting(settings, 'vcs.start_server', 'false')
430 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
456 _list_setting(settings, 'vcs.backends', 'hg, git, svn')
431 _int_setting(settings, 'vcs.connection_timeout', 3600)
457 _int_setting(settings, 'vcs.connection_timeout', 3600)
432
458
433 # Support legacy values of vcs.scm_app_implementation. Legacy
459 # Support legacy values of vcs.scm_app_implementation. Legacy
434 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
460 # configurations may use 'rhodecode.lib.middleware.utils.scm_app_http', or
435 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
461 # disabled since 4.13 'vcsserver.scm_app' which is now mapped to 'http'.
436 scm_app_impl = settings['vcs.scm_app_implementation']
462 scm_app_impl = settings['vcs.scm_app_implementation']
437 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
463 if scm_app_impl in ['rhodecode.lib.middleware.utils.scm_app_http', 'vcsserver.scm_app']:
438 settings['vcs.scm_app_implementation'] = 'http'
464 settings['vcs.scm_app_implementation'] = 'http'
439
465
440
466
441 def _sanitize_cache_settings(settings):
467 def _sanitize_cache_settings(settings):
442 temp_store = tempfile.gettempdir()
468 temp_store = tempfile.gettempdir()
443 default_cache_dir = os.path.join(temp_store, 'rc_cache')
469 default_cache_dir = os.path.join(temp_store, 'rc_cache')
444
470
445 # save default, cache dir, and use it for all backends later.
471 # save default, cache dir, and use it for all backends later.
446 default_cache_dir = _string_setting(
472 default_cache_dir = _string_setting(
447 settings,
473 settings,
448 'cache_dir',
474 'cache_dir',
449 default_cache_dir, lower=False, default_when_empty=True)
475 default_cache_dir, lower=False, default_when_empty=True)
450
476
451 # ensure we have our dir created
477 # ensure we have our dir created
452 if not os.path.isdir(default_cache_dir):
478 if not os.path.isdir(default_cache_dir):
453 os.makedirs(default_cache_dir, mode=0755)
479 os.makedirs(default_cache_dir, mode=0755)
454
480
455 # exception store cache
481 # exception store cache
456 _string_setting(
482 _string_setting(
457 settings,
483 settings,
458 'exception_tracker.store_path',
484 'exception_tracker.store_path',
459 temp_store, lower=False, default_when_empty=True)
485 temp_store, lower=False, default_when_empty=True)
460
486
461 # cache_perms
487 # cache_perms
462 _string_setting(
488 _string_setting(
463 settings,
489 settings,
464 'rc_cache.cache_perms.backend',
490 'rc_cache.cache_perms.backend',
465 'dogpile.cache.rc.file_namespace', lower=False)
491 'dogpile.cache.rc.file_namespace', lower=False)
466 _int_setting(
492 _int_setting(
467 settings,
493 settings,
468 'rc_cache.cache_perms.expiration_time',
494 'rc_cache.cache_perms.expiration_time',
469 60)
495 60)
470 _string_setting(
496 _string_setting(
471 settings,
497 settings,
472 'rc_cache.cache_perms.arguments.filename',
498 'rc_cache.cache_perms.arguments.filename',
473 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
499 os.path.join(default_cache_dir, 'rc_cache_1'), lower=False)
474
500
475 # cache_repo
501 # cache_repo
476 _string_setting(
502 _string_setting(
477 settings,
503 settings,
478 'rc_cache.cache_repo.backend',
504 'rc_cache.cache_repo.backend',
479 'dogpile.cache.rc.file_namespace', lower=False)
505 'dogpile.cache.rc.file_namespace', lower=False)
480 _int_setting(
506 _int_setting(
481 settings,
507 settings,
482 'rc_cache.cache_repo.expiration_time',
508 'rc_cache.cache_repo.expiration_time',
483 60)
509 60)
484 _string_setting(
510 _string_setting(
485 settings,
511 settings,
486 'rc_cache.cache_repo.arguments.filename',
512 'rc_cache.cache_repo.arguments.filename',
487 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
513 os.path.join(default_cache_dir, 'rc_cache_2'), lower=False)
488
514
489 # cache_license
515 # cache_license
490 _string_setting(
516 _string_setting(
491 settings,
517 settings,
492 'rc_cache.cache_license.backend',
518 'rc_cache.cache_license.backend',
493 'dogpile.cache.rc.file_namespace', lower=False)
519 'dogpile.cache.rc.file_namespace', lower=False)
494 _int_setting(
520 _int_setting(
495 settings,
521 settings,
496 'rc_cache.cache_license.expiration_time',
522 'rc_cache.cache_license.expiration_time',
497 5*60)
523 5*60)
498 _string_setting(
524 _string_setting(
499 settings,
525 settings,
500 'rc_cache.cache_license.arguments.filename',
526 'rc_cache.cache_license.arguments.filename',
501 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
527 os.path.join(default_cache_dir, 'rc_cache_3'), lower=False)
502
528
503 # cache_repo_longterm memory, 96H
529 # cache_repo_longterm memory, 96H
504 _string_setting(
530 _string_setting(
505 settings,
531 settings,
506 'rc_cache.cache_repo_longterm.backend',
532 'rc_cache.cache_repo_longterm.backend',
507 'dogpile.cache.rc.memory_lru', lower=False)
533 'dogpile.cache.rc.memory_lru', lower=False)
508 _int_setting(
534 _int_setting(
509 settings,
535 settings,
510 'rc_cache.cache_repo_longterm.expiration_time',
536 'rc_cache.cache_repo_longterm.expiration_time',
511 345600)
537 345600)
512 _int_setting(
538 _int_setting(
513 settings,
539 settings,
514 'rc_cache.cache_repo_longterm.max_size',
540 'rc_cache.cache_repo_longterm.max_size',
515 10000)
541 10000)
516
542
517 # sql_cache_short
543 # sql_cache_short
518 _string_setting(
544 _string_setting(
519 settings,
545 settings,
520 'rc_cache.sql_cache_short.backend',
546 'rc_cache.sql_cache_short.backend',
521 'dogpile.cache.rc.memory_lru', lower=False)
547 'dogpile.cache.rc.memory_lru', lower=False)
522 _int_setting(
548 _int_setting(
523 settings,
549 settings,
524 'rc_cache.sql_cache_short.expiration_time',
550 'rc_cache.sql_cache_short.expiration_time',
525 30)
551 30)
526 _int_setting(
552 _int_setting(
527 settings,
553 settings,
528 'rc_cache.sql_cache_short.max_size',
554 'rc_cache.sql_cache_short.max_size',
529 10000)
555 10000)
530
556
531
557
532 def _int_setting(settings, name, default):
558 def _int_setting(settings, name, default):
533 settings[name] = int(settings.get(name, default))
559 settings[name] = int(settings.get(name, default))
534 return settings[name]
560 return settings[name]
535
561
536
562
537 def _bool_setting(settings, name, default):
563 def _bool_setting(settings, name, default):
538 input_val = settings.get(name, default)
564 input_val = settings.get(name, default)
539 if isinstance(input_val, unicode):
565 if isinstance(input_val, unicode):
540 input_val = input_val.encode('utf8')
566 input_val = input_val.encode('utf8')
541 settings[name] = asbool(input_val)
567 settings[name] = asbool(input_val)
542 return settings[name]
568 return settings[name]
543
569
544
570
545 def _list_setting(settings, name, default):
571 def _list_setting(settings, name, default):
546 raw_value = settings.get(name, default)
572 raw_value = settings.get(name, default)
547
573
548 old_separator = ','
574 old_separator = ','
549 if old_separator in raw_value:
575 if old_separator in raw_value:
550 # If we get a comma separated list, pass it to our own function.
576 # If we get a comma separated list, pass it to our own function.
551 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
577 settings[name] = rhodecode_aslist(raw_value, sep=old_separator)
552 else:
578 else:
553 # Otherwise we assume it uses pyramids space/newline separation.
579 # Otherwise we assume it uses pyramids space/newline separation.
554 settings[name] = aslist(raw_value)
580 settings[name] = aslist(raw_value)
555 return settings[name]
581 return settings[name]
556
582
557
583
558 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
584 def _string_setting(settings, name, default, lower=True, default_when_empty=False):
559 value = settings.get(name, default)
585 value = settings.get(name, default)
560
586
561 if default_when_empty and not value:
587 if default_when_empty and not value:
562 # use default value when value is empty
588 # use default value when value is empty
563 value = default
589 value = default
564
590
565 if lower:
591 if lower:
566 value = value.lower()
592 value = value.lower()
567 settings[name] = value
593 settings[name] = value
568 return settings[name]
594 return settings[name]
569
595
570
596
571 def _substitute_values(mapping, substitutions):
597 def _substitute_values(mapping, substitutions):
572
598
573 try:
599 try:
574 result = {
600 result = {
575 # Note: Cannot use regular replacements, since they would clash
601 # Note: Cannot use regular replacements, since they would clash
576 # with the implementation of ConfigParser. Using "format" instead.
602 # with the implementation of ConfigParser. Using "format" instead.
577 key: value.format(**substitutions)
603 key: value.format(**substitutions)
578 for key, value in mapping.items()
604 for key, value in mapping.items()
579 }
605 }
580 except KeyError as e:
606 except KeyError as e:
581 raise ValueError(
607 raise ValueError(
582 'Failed to substitute env variable: {}. '
608 'Failed to substitute env variable: {}. '
583 'Make sure you have specified this env variable without ENV_ prefix'.format(e))
609 'Make sure you have specified this env variable without ENV_ prefix'.format(e))
584 except ValueError as e:
610 except ValueError as e:
585 log.warning('Failed to substitute ENV variable: %s', e)
611 log.warning('Failed to substitute ENV variable: %s', e)
586 result = mapping
612 result = mapping
587
613
588 return result
614 return result
@@ -1,473 +1,475 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import base64
21 import base64
22
22
23 import mock
23 import mock
24 import pytest
24 import pytest
25
25
26 from rhodecode.lib.utils2 import AttributeDict
26 from rhodecode.lib.utils2 import AttributeDict
27 from rhodecode.tests.utils import CustomTestApp
27 from rhodecode.tests.utils import CustomTestApp
28
28
29 from rhodecode.lib.caching_query import FromCache
29 from rhodecode.lib.caching_query import FromCache
30 from rhodecode.lib.hooks_daemon import DummyHooksCallbackDaemon
30 from rhodecode.lib.hooks_daemon import DummyHooksCallbackDaemon
31 from rhodecode.lib.middleware import simplevcs
31 from rhodecode.lib.middleware import simplevcs
32 from rhodecode.lib.middleware.https_fixup import HttpsFixup
32 from rhodecode.lib.middleware.https_fixup import HttpsFixup
33 from rhodecode.lib.middleware.utils import scm_app_http
33 from rhodecode.lib.middleware.utils import scm_app_http
34 from rhodecode.model.db import User, _hash_key
34 from rhodecode.model.db import User, _hash_key
35 from rhodecode.model.meta import Session
35 from rhodecode.model.meta import Session
36 from rhodecode.tests import (
36 from rhodecode.tests import (
37 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
37 HG_REPO, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
38 from rhodecode.tests.lib.middleware import mock_scm_app
38 from rhodecode.tests.lib.middleware import mock_scm_app
39
39
40
40
41 class StubVCSController(simplevcs.SimpleVCS):
41 class StubVCSController(simplevcs.SimpleVCS):
42
42
43 SCM = 'hg'
43 SCM = 'hg'
44 stub_response_body = tuple()
44 stub_response_body = tuple()
45
45
46 def __init__(self, *args, **kwargs):
46 def __init__(self, *args, **kwargs):
47 super(StubVCSController, self).__init__(*args, **kwargs)
47 super(StubVCSController, self).__init__(*args, **kwargs)
48 self._action = 'pull'
48 self._action = 'pull'
49 self._is_shadow_repo_dir = True
49 self._is_shadow_repo_dir = True
50 self._name = HG_REPO
50 self._name = HG_REPO
51 self.set_repo_names(None)
51 self.set_repo_names(None)
52
52
53 @property
53 @property
54 def is_shadow_repo_dir(self):
54 def is_shadow_repo_dir(self):
55 return self._is_shadow_repo_dir
55 return self._is_shadow_repo_dir
56
56
57 def _get_repository_name(self, environ):
57 def _get_repository_name(self, environ):
58 return self._name
58 return self._name
59
59
60 def _get_action(self, environ):
60 def _get_action(self, environ):
61 return self._action
61 return self._action
62
62
63 def _create_wsgi_app(self, repo_path, repo_name, config):
63 def _create_wsgi_app(self, repo_path, repo_name, config):
64 def fake_app(environ, start_response):
64 def fake_app(environ, start_response):
65 headers = [
65 headers = [
66 ('Http-Accept', 'application/mercurial')
66 ('Http-Accept', 'application/mercurial')
67 ]
67 ]
68 start_response('200 OK', headers)
68 start_response('200 OK', headers)
69 return self.stub_response_body
69 return self.stub_response_body
70 return fake_app
70 return fake_app
71
71
72 def _create_config(self, extras, repo_name):
72 def _create_config(self, extras, repo_name):
73 return None
73 return None
74
74
75
75
76 @pytest.fixture
76 @pytest.fixture
77 def vcscontroller(baseapp, config_stub, request_stub):
77 def vcscontroller(baseapp, config_stub, request_stub):
78 config_stub.testing_securitypolicy()
78 config_stub.testing_securitypolicy()
79 config_stub.include('rhodecode.authentication')
79 config_stub.include('rhodecode.authentication')
80 config_stub.include('rhodecode.authentication.plugins.auth_rhodecode')
81 config_stub.include('rhodecode.authentication.plugins.auth_token')
80
82
81 controller = StubVCSController(
83 controller = StubVCSController(
82 baseapp.config.get_settings(), request_stub.registry)
84 baseapp.config.get_settings(), request_stub.registry)
83 app = HttpsFixup(controller, baseapp.config.get_settings())
85 app = HttpsFixup(controller, baseapp.config.get_settings())
84 app = CustomTestApp(app)
86 app = CustomTestApp(app)
85
87
86 _remove_default_user_from_query_cache()
88 _remove_default_user_from_query_cache()
87
89
88 # Sanity checks that things are set up correctly
90 # Sanity checks that things are set up correctly
89 app.get('/' + HG_REPO, status=200)
91 app.get('/' + HG_REPO, status=200)
90
92
91 app.controller = controller
93 app.controller = controller
92 return app
94 return app
93
95
94
96
95 def _remove_default_user_from_query_cache():
97 def _remove_default_user_from_query_cache():
96 user = User.get_default_user(cache=True)
98 user = User.get_default_user(cache=True)
97 query = Session().query(User).filter(User.username == user.username)
99 query = Session().query(User).filter(User.username == user.username)
98 query = query.options(
100 query = query.options(
99 FromCache("sql_cache_short", "get_user_%s" % _hash_key(user.username)))
101 FromCache("sql_cache_short", "get_user_%s" % _hash_key(user.username)))
100 query.invalidate()
102 query.invalidate()
101 Session().expire(user)
103 Session().expire(user)
102
104
103
105
104 def test_handles_exceptions_during_permissions_checks(
106 def test_handles_exceptions_during_permissions_checks(
105 vcscontroller, disable_anonymous_user):
107 vcscontroller, disable_anonymous_user):
106 user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
108 user_and_pass = '%s:%s' % (TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
107 auth_password = base64.encodestring(user_and_pass).strip()
109 auth_password = base64.encodestring(user_and_pass).strip()
108 extra_environ = {
110 extra_environ = {
109 'AUTH_TYPE': 'Basic',
111 'AUTH_TYPE': 'Basic',
110 'HTTP_AUTHORIZATION': 'Basic %s' % auth_password,
112 'HTTP_AUTHORIZATION': 'Basic %s' % auth_password,
111 'REMOTE_USER': TEST_USER_ADMIN_LOGIN,
113 'REMOTE_USER': TEST_USER_ADMIN_LOGIN,
112 }
114 }
113
115
114 # Verify that things are hooked up correctly
116 # Verify that things are hooked up correctly
115 vcscontroller.get('/', status=200, extra_environ=extra_environ)
117 vcscontroller.get('/', status=200, extra_environ=extra_environ)
116
118
117 # Simulate trouble during permission checks
119 # Simulate trouble during permission checks
118 with mock.patch('rhodecode.model.db.User.get_by_username',
120 with mock.patch('rhodecode.model.db.User.get_by_username',
119 side_effect=Exception) as get_user:
121 side_effect=Exception) as get_user:
120 # Verify that a correct 500 is returned and check that the expected
122 # Verify that a correct 500 is returned and check that the expected
121 # code path was hit.
123 # code path was hit.
122 vcscontroller.get('/', status=500, extra_environ=extra_environ)
124 vcscontroller.get('/', status=500, extra_environ=extra_environ)
123 assert get_user.called
125 assert get_user.called
124
126
125
127
126 def test_returns_forbidden_if_no_anonymous_access(
128 def test_returns_forbidden_if_no_anonymous_access(
127 vcscontroller, disable_anonymous_user):
129 vcscontroller, disable_anonymous_user):
128 vcscontroller.get('/', status=401)
130 vcscontroller.get('/', status=401)
129
131
130
132
131 class StubFailVCSController(simplevcs.SimpleVCS):
133 class StubFailVCSController(simplevcs.SimpleVCS):
132 def _handle_request(self, environ, start_response):
134 def _handle_request(self, environ, start_response):
133 raise Exception("BOOM")
135 raise Exception("BOOM")
134
136
135
137
136 @pytest.fixture(scope='module')
138 @pytest.fixture(scope='module')
137 def fail_controller(baseapp):
139 def fail_controller(baseapp):
138 controller = StubFailVCSController(
140 controller = StubFailVCSController(
139 baseapp.config.get_settings(), baseapp.config)
141 baseapp.config.get_settings(), baseapp.config)
140 controller = HttpsFixup(controller, baseapp.config.get_settings())
142 controller = HttpsFixup(controller, baseapp.config.get_settings())
141 controller = CustomTestApp(controller)
143 controller = CustomTestApp(controller)
142 return controller
144 return controller
143
145
144
146
145 def test_handles_exceptions_as_internal_server_error(fail_controller):
147 def test_handles_exceptions_as_internal_server_error(fail_controller):
146 fail_controller.get('/', status=500)
148 fail_controller.get('/', status=500)
147
149
148
150
149 def test_provides_traceback_for_appenlight(fail_controller):
151 def test_provides_traceback_for_appenlight(fail_controller):
150 response = fail_controller.get(
152 response = fail_controller.get(
151 '/', status=500, extra_environ={'appenlight.client': 'fake'})
153 '/', status=500, extra_environ={'appenlight.client': 'fake'})
152 assert 'appenlight.__traceback' in response.request.environ
154 assert 'appenlight.__traceback' in response.request.environ
153
155
154
156
155 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
157 def test_provides_utils_scm_app_as_scm_app_by_default(baseapp, request_stub):
156 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
158 controller = StubVCSController(baseapp.config.get_settings(), request_stub.registry)
157 assert controller.scm_app is scm_app_http
159 assert controller.scm_app is scm_app_http
158
160
159
161
160 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
162 def test_allows_to_override_scm_app_via_config(baseapp, request_stub):
161 config = baseapp.config.get_settings().copy()
163 config = baseapp.config.get_settings().copy()
162 config['vcs.scm_app_implementation'] = (
164 config['vcs.scm_app_implementation'] = (
163 'rhodecode.tests.lib.middleware.mock_scm_app')
165 'rhodecode.tests.lib.middleware.mock_scm_app')
164 controller = StubVCSController(config, request_stub.registry)
166 controller = StubVCSController(config, request_stub.registry)
165 assert controller.scm_app is mock_scm_app
167 assert controller.scm_app is mock_scm_app
166
168
167
169
168 @pytest.mark.parametrize('query_string, expected', [
170 @pytest.mark.parametrize('query_string, expected', [
169 ('cmd=stub_command', True),
171 ('cmd=stub_command', True),
170 ('cmd=listkeys', False),
172 ('cmd=listkeys', False),
171 ])
173 ])
172 def test_should_check_locking(query_string, expected):
174 def test_should_check_locking(query_string, expected):
173 result = simplevcs._should_check_locking(query_string)
175 result = simplevcs._should_check_locking(query_string)
174 assert result == expected
176 assert result == expected
175
177
176
178
177 class TestShadowRepoRegularExpression(object):
179 class TestShadowRepoRegularExpression(object):
178 pr_segment = 'pull-request'
180 pr_segment = 'pull-request'
179 shadow_segment = 'repository'
181 shadow_segment = 'repository'
180
182
181 @pytest.mark.parametrize('url, expected', [
183 @pytest.mark.parametrize('url, expected', [
182 # repo with/without groups
184 # repo with/without groups
183 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
185 ('My-Repo/{pr_segment}/1/{shadow_segment}', True),
184 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
186 ('Group/My-Repo/{pr_segment}/2/{shadow_segment}', True),
185 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
187 ('Group/Sub-Group/My-Repo/{pr_segment}/3/{shadow_segment}', True),
186 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
188 ('Group/Sub-Group1/Sub-Group2/My-Repo/{pr_segment}/3/{shadow_segment}', True),
187
189
188 # pull request ID
190 # pull request ID
189 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
191 ('MyRepo/{pr_segment}/1/{shadow_segment}', True),
190 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
192 ('MyRepo/{pr_segment}/1234567890/{shadow_segment}', True),
191 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
193 ('MyRepo/{pr_segment}/-1/{shadow_segment}', False),
192 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
194 ('MyRepo/{pr_segment}/invalid/{shadow_segment}', False),
193
195
194 # unicode
196 # unicode
195 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
197 (u'Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
196 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
198 (u'Sp€çîál-Gröüp/Sp€çîál-Repö/{pr_segment}/1/{shadow_segment}', True),
197
199
198 # trailing/leading slash
200 # trailing/leading slash
199 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
201 ('/My-Repo/{pr_segment}/1/{shadow_segment}', False),
200 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
202 ('My-Repo/{pr_segment}/1/{shadow_segment}/', False),
201 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
203 ('/My-Repo/{pr_segment}/1/{shadow_segment}/', False),
202
204
203 # misc
205 # misc
204 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
206 ('My-Repo/{pr_segment}/1/{shadow_segment}/extra', False),
205 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
207 ('My-Repo/{pr_segment}/1/{shadow_segment}extra', False),
206 ])
208 ])
207 def test_shadow_repo_regular_expression(self, url, expected):
209 def test_shadow_repo_regular_expression(self, url, expected):
208 from rhodecode.lib.middleware.simplevcs import SimpleVCS
210 from rhodecode.lib.middleware.simplevcs import SimpleVCS
209 url = url.format(
211 url = url.format(
210 pr_segment=self.pr_segment,
212 pr_segment=self.pr_segment,
211 shadow_segment=self.shadow_segment)
213 shadow_segment=self.shadow_segment)
212 match_obj = SimpleVCS.shadow_repo_re.match(url)
214 match_obj = SimpleVCS.shadow_repo_re.match(url)
213 assert (match_obj is not None) == expected
215 assert (match_obj is not None) == expected
214
216
215
217
216 @pytest.mark.backends('git', 'hg')
218 @pytest.mark.backends('git', 'hg')
217 class TestShadowRepoExposure(object):
219 class TestShadowRepoExposure(object):
218
220
219 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
221 def test_pull_on_shadow_repo_propagates_to_wsgi_app(
220 self, baseapp, request_stub):
222 self, baseapp, request_stub):
221 """
223 """
222 Check that a pull action to a shadow repo is propagated to the
224 Check that a pull action to a shadow repo is propagated to the
223 underlying wsgi app.
225 underlying wsgi app.
224 """
226 """
225 controller = StubVCSController(
227 controller = StubVCSController(
226 baseapp.config.get_settings(), request_stub.registry)
228 baseapp.config.get_settings(), request_stub.registry)
227 controller._check_ssl = mock.Mock()
229 controller._check_ssl = mock.Mock()
228 controller.is_shadow_repo = True
230 controller.is_shadow_repo = True
229 controller._action = 'pull'
231 controller._action = 'pull'
230 controller._is_shadow_repo_dir = True
232 controller._is_shadow_repo_dir = True
231 controller.stub_response_body = 'dummy body value'
233 controller.stub_response_body = 'dummy body value'
232 controller._get_default_cache_ttl = mock.Mock(
234 controller._get_default_cache_ttl = mock.Mock(
233 return_value=(False, 0))
235 return_value=(False, 0))
234
236
235 environ_stub = {
237 environ_stub = {
236 'HTTP_HOST': 'test.example.com',
238 'HTTP_HOST': 'test.example.com',
237 'HTTP_ACCEPT': 'application/mercurial',
239 'HTTP_ACCEPT': 'application/mercurial',
238 'REQUEST_METHOD': 'GET',
240 'REQUEST_METHOD': 'GET',
239 'wsgi.url_scheme': 'http',
241 'wsgi.url_scheme': 'http',
240 }
242 }
241
243
242 response = controller(environ_stub, mock.Mock())
244 response = controller(environ_stub, mock.Mock())
243 response_body = ''.join(response)
245 response_body = ''.join(response)
244
246
245 # Assert that we got the response from the wsgi app.
247 # Assert that we got the response from the wsgi app.
246 assert response_body == controller.stub_response_body
248 assert response_body == controller.stub_response_body
247
249
248 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
250 def test_pull_on_shadow_repo_that_is_missing(self, baseapp, request_stub):
249 """
251 """
250 Check that a pull action to a shadow repo is propagated to the
252 Check that a pull action to a shadow repo is propagated to the
251 underlying wsgi app.
253 underlying wsgi app.
252 """
254 """
253 controller = StubVCSController(
255 controller = StubVCSController(
254 baseapp.config.get_settings(), request_stub.registry)
256 baseapp.config.get_settings(), request_stub.registry)
255 controller._check_ssl = mock.Mock()
257 controller._check_ssl = mock.Mock()
256 controller.is_shadow_repo = True
258 controller.is_shadow_repo = True
257 controller._action = 'pull'
259 controller._action = 'pull'
258 controller._is_shadow_repo_dir = False
260 controller._is_shadow_repo_dir = False
259 controller.stub_response_body = 'dummy body value'
261 controller.stub_response_body = 'dummy body value'
260 environ_stub = {
262 environ_stub = {
261 'HTTP_HOST': 'test.example.com',
263 'HTTP_HOST': 'test.example.com',
262 'HTTP_ACCEPT': 'application/mercurial',
264 'HTTP_ACCEPT': 'application/mercurial',
263 'REQUEST_METHOD': 'GET',
265 'REQUEST_METHOD': 'GET',
264 'wsgi.url_scheme': 'http',
266 'wsgi.url_scheme': 'http',
265 }
267 }
266
268
267 response = controller(environ_stub, mock.Mock())
269 response = controller(environ_stub, mock.Mock())
268 response_body = ''.join(response)
270 response_body = ''.join(response)
269
271
270 # Assert that we got the response from the wsgi app.
272 # Assert that we got the response from the wsgi app.
271 assert '404 Not Found' in response_body
273 assert '404 Not Found' in response_body
272
274
273 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
275 def test_push_on_shadow_repo_raises(self, baseapp, request_stub):
274 """
276 """
275 Check that a push action to a shadow repo is aborted.
277 Check that a push action to a shadow repo is aborted.
276 """
278 """
277 controller = StubVCSController(
279 controller = StubVCSController(
278 baseapp.config.get_settings(), request_stub.registry)
280 baseapp.config.get_settings(), request_stub.registry)
279 controller._check_ssl = mock.Mock()
281 controller._check_ssl = mock.Mock()
280 controller.is_shadow_repo = True
282 controller.is_shadow_repo = True
281 controller._action = 'push'
283 controller._action = 'push'
282 controller.stub_response_body = 'dummy body value'
284 controller.stub_response_body = 'dummy body value'
283 environ_stub = {
285 environ_stub = {
284 'HTTP_HOST': 'test.example.com',
286 'HTTP_HOST': 'test.example.com',
285 'HTTP_ACCEPT': 'application/mercurial',
287 'HTTP_ACCEPT': 'application/mercurial',
286 'REQUEST_METHOD': 'GET',
288 'REQUEST_METHOD': 'GET',
287 'wsgi.url_scheme': 'http',
289 'wsgi.url_scheme': 'http',
288 }
290 }
289
291
290 response = controller(environ_stub, mock.Mock())
292 response = controller(environ_stub, mock.Mock())
291 response_body = ''.join(response)
293 response_body = ''.join(response)
292
294
293 assert response_body != controller.stub_response_body
295 assert response_body != controller.stub_response_body
294 # Assert that a 406 error is returned.
296 # Assert that a 406 error is returned.
295 assert '406 Not Acceptable' in response_body
297 assert '406 Not Acceptable' in response_body
296
298
297 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
299 def test_set_repo_names_no_shadow(self, baseapp, request_stub):
298 """
300 """
299 Check that the set_repo_names method sets all names to the one returned
301 Check that the set_repo_names method sets all names to the one returned
300 by the _get_repository_name method on a request to a non shadow repo.
302 by the _get_repository_name method on a request to a non shadow repo.
301 """
303 """
302 environ_stub = {}
304 environ_stub = {}
303 controller = StubVCSController(
305 controller = StubVCSController(
304 baseapp.config.get_settings(), request_stub.registry)
306 baseapp.config.get_settings(), request_stub.registry)
305 controller._name = 'RepoGroup/MyRepo'
307 controller._name = 'RepoGroup/MyRepo'
306 controller.set_repo_names(environ_stub)
308 controller.set_repo_names(environ_stub)
307 assert not controller.is_shadow_repo
309 assert not controller.is_shadow_repo
308 assert (controller.url_repo_name ==
310 assert (controller.url_repo_name ==
309 controller.acl_repo_name ==
311 controller.acl_repo_name ==
310 controller.vcs_repo_name ==
312 controller.vcs_repo_name ==
311 controller._get_repository_name(environ_stub))
313 controller._get_repository_name(environ_stub))
312
314
313 def test_set_repo_names_with_shadow(
315 def test_set_repo_names_with_shadow(
314 self, baseapp, pr_util, config_stub, request_stub):
316 self, baseapp, pr_util, config_stub, request_stub):
315 """
317 """
316 Check that the set_repo_names method sets correct names on a request
318 Check that the set_repo_names method sets correct names on a request
317 to a shadow repo.
319 to a shadow repo.
318 """
320 """
319 from rhodecode.model.pull_request import PullRequestModel
321 from rhodecode.model.pull_request import PullRequestModel
320
322
321 pull_request = pr_util.create_pull_request()
323 pull_request = pr_util.create_pull_request()
322 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
324 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
323 target=pull_request.target_repo.repo_name,
325 target=pull_request.target_repo.repo_name,
324 pr_id=pull_request.pull_request_id,
326 pr_id=pull_request.pull_request_id,
325 pr_segment=TestShadowRepoRegularExpression.pr_segment,
327 pr_segment=TestShadowRepoRegularExpression.pr_segment,
326 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
328 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
327 controller = StubVCSController(
329 controller = StubVCSController(
328 baseapp.config.get_settings(), request_stub.registry)
330 baseapp.config.get_settings(), request_stub.registry)
329 controller._name = shadow_url
331 controller._name = shadow_url
330 controller.set_repo_names({})
332 controller.set_repo_names({})
331
333
332 # Get file system path to shadow repo for assertions.
334 # Get file system path to shadow repo for assertions.
333 workspace_id = PullRequestModel()._workspace_id(pull_request)
335 workspace_id = PullRequestModel()._workspace_id(pull_request)
334 target_vcs = pull_request.target_repo.scm_instance()
336 target_vcs = pull_request.target_repo.scm_instance()
335 vcs_repo_name = target_vcs._get_shadow_repository_path(
337 vcs_repo_name = target_vcs._get_shadow_repository_path(
336 pull_request.target_repo.repo_id, workspace_id)
338 pull_request.target_repo.repo_id, workspace_id)
337
339
338 assert controller.vcs_repo_name == vcs_repo_name
340 assert controller.vcs_repo_name == vcs_repo_name
339 assert controller.url_repo_name == shadow_url
341 assert controller.url_repo_name == shadow_url
340 assert controller.acl_repo_name == pull_request.target_repo.repo_name
342 assert controller.acl_repo_name == pull_request.target_repo.repo_name
341 assert controller.is_shadow_repo
343 assert controller.is_shadow_repo
342
344
343 def test_set_repo_names_with_shadow_but_missing_pr(
345 def test_set_repo_names_with_shadow_but_missing_pr(
344 self, baseapp, pr_util, config_stub, request_stub):
346 self, baseapp, pr_util, config_stub, request_stub):
345 """
347 """
346 Checks that the set_repo_names method enforces matching target repos
348 Checks that the set_repo_names method enforces matching target repos
347 and pull request IDs.
349 and pull request IDs.
348 """
350 """
349 pull_request = pr_util.create_pull_request()
351 pull_request = pr_util.create_pull_request()
350 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
352 shadow_url = '{target}/{pr_segment}/{pr_id}/{shadow_segment}'.format(
351 target=pull_request.target_repo.repo_name,
353 target=pull_request.target_repo.repo_name,
352 pr_id=999999999,
354 pr_id=999999999,
353 pr_segment=TestShadowRepoRegularExpression.pr_segment,
355 pr_segment=TestShadowRepoRegularExpression.pr_segment,
354 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
356 shadow_segment=TestShadowRepoRegularExpression.shadow_segment)
355 controller = StubVCSController(
357 controller = StubVCSController(
356 baseapp.config.get_settings(), request_stub.registry)
358 baseapp.config.get_settings(), request_stub.registry)
357 controller._name = shadow_url
359 controller._name = shadow_url
358 controller.set_repo_names({})
360 controller.set_repo_names({})
359
361
360 assert not controller.is_shadow_repo
362 assert not controller.is_shadow_repo
361 assert (controller.url_repo_name ==
363 assert (controller.url_repo_name ==
362 controller.acl_repo_name ==
364 controller.acl_repo_name ==
363 controller.vcs_repo_name)
365 controller.vcs_repo_name)
364
366
365
367
366 @pytest.mark.usefixtures('baseapp')
368 @pytest.mark.usefixtures('baseapp')
367 class TestGenerateVcsResponse(object):
369 class TestGenerateVcsResponse(object):
368
370
369 def test_ensures_that_start_response_is_called_early_enough(self):
371 def test_ensures_that_start_response_is_called_early_enough(self):
370 self.call_controller_with_response_body(iter(['a', 'b']))
372 self.call_controller_with_response_body(iter(['a', 'b']))
371 assert self.start_response.called
373 assert self.start_response.called
372
374
373 def test_invalidates_cache_after_body_is_consumed(self):
375 def test_invalidates_cache_after_body_is_consumed(self):
374 result = self.call_controller_with_response_body(iter(['a', 'b']))
376 result = self.call_controller_with_response_body(iter(['a', 'b']))
375 assert not self.was_cache_invalidated()
377 assert not self.was_cache_invalidated()
376 # Consume the result
378 # Consume the result
377 list(result)
379 list(result)
378 assert self.was_cache_invalidated()
380 assert self.was_cache_invalidated()
379
381
380 def test_raises_unknown_exceptions(self):
382 def test_raises_unknown_exceptions(self):
381 result = self.call_controller_with_response_body(
383 result = self.call_controller_with_response_body(
382 self.raise_result_iter(vcs_kind='unknown'))
384 self.raise_result_iter(vcs_kind='unknown'))
383 with pytest.raises(Exception):
385 with pytest.raises(Exception):
384 list(result)
386 list(result)
385
387
386 def test_prepare_callback_daemon_is_called(self):
388 def test_prepare_callback_daemon_is_called(self):
387 def side_effect(extras, environ, action, txn_id=None):
389 def side_effect(extras, environ, action, txn_id=None):
388 return DummyHooksCallbackDaemon(), extras
390 return DummyHooksCallbackDaemon(), extras
389
391
390 prepare_patcher = mock.patch.object(
392 prepare_patcher = mock.patch.object(
391 StubVCSController, '_prepare_callback_daemon')
393 StubVCSController, '_prepare_callback_daemon')
392 with prepare_patcher as prepare_mock:
394 with prepare_patcher as prepare_mock:
393 prepare_mock.side_effect = side_effect
395 prepare_mock.side_effect = side_effect
394 self.call_controller_with_response_body(iter(['a', 'b']))
396 self.call_controller_with_response_body(iter(['a', 'b']))
395 assert prepare_mock.called
397 assert prepare_mock.called
396 assert prepare_mock.call_count == 1
398 assert prepare_mock.call_count == 1
397
399
398 def call_controller_with_response_body(self, response_body):
400 def call_controller_with_response_body(self, response_body):
399 settings = {
401 settings = {
400 'base_path': 'fake_base_path',
402 'base_path': 'fake_base_path',
401 'vcs.hooks.protocol': 'http',
403 'vcs.hooks.protocol': 'http',
402 'vcs.hooks.direct_calls': False,
404 'vcs.hooks.direct_calls': False,
403 }
405 }
404 registry = AttributeDict()
406 registry = AttributeDict()
405 controller = StubVCSController(settings, registry)
407 controller = StubVCSController(settings, registry)
406 controller._invalidate_cache = mock.Mock()
408 controller._invalidate_cache = mock.Mock()
407 controller.stub_response_body = response_body
409 controller.stub_response_body = response_body
408 self.start_response = mock.Mock()
410 self.start_response = mock.Mock()
409 result = controller._generate_vcs_response(
411 result = controller._generate_vcs_response(
410 environ={}, start_response=self.start_response,
412 environ={}, start_response=self.start_response,
411 repo_path='fake_repo_path',
413 repo_path='fake_repo_path',
412 extras={}, action='push')
414 extras={}, action='push')
413 self.controller = controller
415 self.controller = controller
414 return result
416 return result
415
417
416 def raise_result_iter(self, vcs_kind='repo_locked'):
418 def raise_result_iter(self, vcs_kind='repo_locked'):
417 """
419 """
418 Simulates an exception due to a vcs raised exception if kind vcs_kind
420 Simulates an exception due to a vcs raised exception if kind vcs_kind
419 """
421 """
420 raise self.vcs_exception(vcs_kind=vcs_kind)
422 raise self.vcs_exception(vcs_kind=vcs_kind)
421 yield "never_reached"
423 yield "never_reached"
422
424
423 def vcs_exception(self, vcs_kind='repo_locked'):
425 def vcs_exception(self, vcs_kind='repo_locked'):
424 locked_exception = Exception('TEST_MESSAGE')
426 locked_exception = Exception('TEST_MESSAGE')
425 locked_exception._vcs_kind = vcs_kind
427 locked_exception._vcs_kind = vcs_kind
426 return locked_exception
428 return locked_exception
427
429
428 def was_cache_invalidated(self):
430 def was_cache_invalidated(self):
429 return self.controller._invalidate_cache.called
431 return self.controller._invalidate_cache.called
430
432
431
433
432 class TestInitializeGenerator(object):
434 class TestInitializeGenerator(object):
433
435
434 def test_drains_first_element(self):
436 def test_drains_first_element(self):
435 gen = self.factory(['__init__', 1, 2])
437 gen = self.factory(['__init__', 1, 2])
436 result = list(gen)
438 result = list(gen)
437 assert result == [1, 2]
439 assert result == [1, 2]
438
440
439 @pytest.mark.parametrize('values', [
441 @pytest.mark.parametrize('values', [
440 [],
442 [],
441 [1, 2],
443 [1, 2],
442 ])
444 ])
443 def test_raises_value_error(self, values):
445 def test_raises_value_error(self, values):
444 with pytest.raises(ValueError):
446 with pytest.raises(ValueError):
445 self.factory(values)
447 self.factory(values)
446
448
447 @simplevcs.initialize_generator
449 @simplevcs.initialize_generator
448 def factory(self, iterable):
450 def factory(self, iterable):
449 for elem in iterable:
451 for elem in iterable:
450 yield elem
452 yield elem
451
453
452
454
453 class TestPrepareHooksDaemon(object):
455 class TestPrepareHooksDaemon(object):
454 def test_calls_imported_prepare_callback_daemon(self, app_settings, request_stub):
456 def test_calls_imported_prepare_callback_daemon(self, app_settings, request_stub):
455 expected_extras = {'extra1': 'value1'}
457 expected_extras = {'extra1': 'value1'}
456 daemon = DummyHooksCallbackDaemon()
458 daemon = DummyHooksCallbackDaemon()
457
459
458 controller = StubVCSController(app_settings, request_stub.registry)
460 controller = StubVCSController(app_settings, request_stub.registry)
459 prepare_patcher = mock.patch.object(
461 prepare_patcher = mock.patch.object(
460 simplevcs, 'prepare_callback_daemon',
462 simplevcs, 'prepare_callback_daemon',
461 return_value=(daemon, expected_extras))
463 return_value=(daemon, expected_extras))
462 with prepare_patcher as prepare_mock:
464 with prepare_patcher as prepare_mock:
463 callback_daemon, extras = controller._prepare_callback_daemon(
465 callback_daemon, extras = controller._prepare_callback_daemon(
464 expected_extras.copy(), {}, 'push')
466 expected_extras.copy(), {}, 'push')
465 prepare_mock.assert_called_once_with(
467 prepare_mock.assert_called_once_with(
466 expected_extras,
468 expected_extras,
467 protocol=app_settings['vcs.hooks.protocol'],
469 protocol=app_settings['vcs.hooks.protocol'],
468 host=app_settings['vcs.hooks.host'],
470 host=app_settings['vcs.hooks.host'],
469 txn_id=None,
471 txn_id=None,
470 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
472 use_direct_calls=app_settings['vcs.hooks.direct_calls'])
471
473
472 assert callback_daemon == daemon
474 assert callback_daemon == daemon
473 assert extras == extras
475 assert extras == extras
@@ -1,294 +1,296 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2018 RhodeCode GmbH
3 # Copyright (C) 2010-2018 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 formencode
22 import formencode
23 import pytest
23 import pytest
24
24
25 from rhodecode.tests import (
25 from rhodecode.tests import (
26 HG_REPO, TEST_USER_REGULAR2_EMAIL, TEST_USER_REGULAR2_LOGIN,
26 HG_REPO, TEST_USER_REGULAR2_EMAIL, TEST_USER_REGULAR2_LOGIN,
27 TEST_USER_REGULAR2_PASS, TEST_USER_ADMIN_LOGIN, TESTS_TMP_PATH)
27 TEST_USER_REGULAR2_PASS, TEST_USER_ADMIN_LOGIN, TESTS_TMP_PATH)
28
28
29 from rhodecode.model import validators as v
29 from rhodecode.model import validators as v
30 from rhodecode.model.user_group import UserGroupModel
30 from rhodecode.model.user_group import UserGroupModel
31
31
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.model.repo_group import RepoGroupModel
33 from rhodecode.model.repo_group import RepoGroupModel
34 from rhodecode.model.db import ChangesetStatus, Repository
34 from rhodecode.model.db import ChangesetStatus, Repository
35 from rhodecode.model.changeset_status import ChangesetStatusModel
35 from rhodecode.model.changeset_status import ChangesetStatusModel
36 from rhodecode.tests.fixture import Fixture
36 from rhodecode.tests.fixture import Fixture
37
37
38 fixture = Fixture()
38 fixture = Fixture()
39
39
40 pytestmark = pytest.mark.usefixtures('baseapp')
40 pytestmark = pytest.mark.usefixtures('baseapp')
41
41
42
42
43 @pytest.fixture
43 @pytest.fixture
44 def localizer():
44 def localizer():
45 def func(msg):
45 def func(msg):
46 return msg
46 return msg
47 return func
47 return func
48
48
49
49
50 def test_Message_extractor(localizer):
50 def test_Message_extractor(localizer):
51 validator = v.ValidUsername(localizer)
51 validator = v.ValidUsername(localizer)
52 pytest.raises(formencode.Invalid, validator.to_python, 'default')
52 pytest.raises(formencode.Invalid, validator.to_python, 'default')
53
53
54 class StateObj(object):
54 class StateObj(object):
55 pass
55 pass
56
56
57 pytest.raises(
57 pytest.raises(
58 formencode.Invalid, validator.to_python, 'default', StateObj)
58 formencode.Invalid, validator.to_python, 'default', StateObj)
59
59
60
60
61 def test_ValidUsername(localizer):
61 def test_ValidUsername(localizer):
62 validator = v.ValidUsername(localizer)
62 validator = v.ValidUsername(localizer)
63
63
64 pytest.raises(formencode.Invalid, validator.to_python, 'default')
64 pytest.raises(formencode.Invalid, validator.to_python, 'default')
65 pytest.raises(formencode.Invalid, validator.to_python, 'new_user')
65 pytest.raises(formencode.Invalid, validator.to_python, 'new_user')
66 pytest.raises(formencode.Invalid, validator.to_python, '.,')
66 pytest.raises(formencode.Invalid, validator.to_python, '.,')
67 pytest.raises(
67 pytest.raises(
68 formencode.Invalid, validator.to_python, TEST_USER_ADMIN_LOGIN)
68 formencode.Invalid, validator.to_python, TEST_USER_ADMIN_LOGIN)
69 assert 'test' == validator.to_python('test')
69 assert 'test' == validator.to_python('test')
70
70
71 validator = v.ValidUsername(localizer, edit=True, old_data={'user_id': 1})
71 validator = v.ValidUsername(localizer, edit=True, old_data={'user_id': 1})
72
72
73
73
74 def test_ValidRepoUser(localizer):
74 def test_ValidRepoUser(localizer):
75 validator = v.ValidRepoUser(localizer)
75 validator = v.ValidRepoUser(localizer)
76 pytest.raises(formencode.Invalid, validator.to_python, 'nouser')
76 pytest.raises(formencode.Invalid, validator.to_python, 'nouser')
77 assert TEST_USER_ADMIN_LOGIN == \
77 assert TEST_USER_ADMIN_LOGIN == \
78 validator.to_python(TEST_USER_ADMIN_LOGIN)
78 validator.to_python(TEST_USER_ADMIN_LOGIN)
79
79
80
80
81 def test_ValidUserGroup(localizer):
81 def test_ValidUserGroup(localizer):
82 validator = v.ValidUserGroup(localizer)
82 validator = v.ValidUserGroup(localizer)
83 pytest.raises(formencode.Invalid, validator.to_python, 'default')
83 pytest.raises(formencode.Invalid, validator.to_python, 'default')
84 pytest.raises(formencode.Invalid, validator.to_python, '.,')
84 pytest.raises(formencode.Invalid, validator.to_python, '.,')
85
85
86 gr = fixture.create_user_group('test')
86 gr = fixture.create_user_group('test')
87 gr2 = fixture.create_user_group('tes2')
87 gr2 = fixture.create_user_group('tes2')
88 Session().commit()
88 Session().commit()
89 pytest.raises(formencode.Invalid, validator.to_python, 'test')
89 pytest.raises(formencode.Invalid, validator.to_python, 'test')
90 assert gr.users_group_id is not None
90 assert gr.users_group_id is not None
91 validator = v.ValidUserGroup(localizer,
91 validator = v.ValidUserGroup(localizer,
92 edit=True,
92 edit=True,
93 old_data={'users_group_id': gr2.users_group_id})
93 old_data={'users_group_id': gr2.users_group_id})
94
94
95 pytest.raises(formencode.Invalid, validator.to_python, 'test')
95 pytest.raises(formencode.Invalid, validator.to_python, 'test')
96 pytest.raises(formencode.Invalid, validator.to_python, 'TesT')
96 pytest.raises(formencode.Invalid, validator.to_python, 'TesT')
97 pytest.raises(formencode.Invalid, validator.to_python, 'TEST')
97 pytest.raises(formencode.Invalid, validator.to_python, 'TEST')
98 UserGroupModel().delete(gr)
98 UserGroupModel().delete(gr)
99 UserGroupModel().delete(gr2)
99 UserGroupModel().delete(gr2)
100 Session().commit()
100 Session().commit()
101
101
102
102
103 @pytest.fixture(scope='function')
103 @pytest.fixture(scope='function')
104 def repo_group(request):
104 def repo_group(request):
105 model = RepoGroupModel()
105 model = RepoGroupModel()
106 gr = model.create(
106 gr = model.create(
107 group_name='test_gr', group_description='desc', just_db=True,
107 group_name='test_gr', group_description='desc', just_db=True,
108 owner=TEST_USER_ADMIN_LOGIN)
108 owner=TEST_USER_ADMIN_LOGIN)
109
109
110 def cleanup():
110 def cleanup():
111 model.delete(gr)
111 model.delete(gr)
112
112
113 request.addfinalizer(cleanup)
113 request.addfinalizer(cleanup)
114
114
115 return gr
115 return gr
116
116
117
117
118 def test_ValidRepoGroup_same_name_as_repo(localizer):
118 def test_ValidRepoGroup_same_name_as_repo(localizer):
119 validator = v.ValidRepoGroup(localizer)
119 validator = v.ValidRepoGroup(localizer)
120 with pytest.raises(formencode.Invalid) as excinfo:
120 with pytest.raises(formencode.Invalid) as excinfo:
121 validator.to_python({'group_name': HG_REPO})
121 validator.to_python({'group_name': HG_REPO})
122 expected_msg = 'Repository with name "vcs_test_hg" already exists'
122 expected_msg = 'Repository with name "vcs_test_hg" already exists'
123 assert expected_msg in str(excinfo.value)
123 assert expected_msg in str(excinfo.value)
124
124
125
125
126 def test_ValidRepoGroup_group_exists(localizer, repo_group):
126 def test_ValidRepoGroup_group_exists(localizer, repo_group):
127 validator = v.ValidRepoGroup(localizer)
127 validator = v.ValidRepoGroup(localizer)
128 with pytest.raises(formencode.Invalid) as excinfo:
128 with pytest.raises(formencode.Invalid) as excinfo:
129 validator.to_python({'group_name': repo_group.group_name})
129 validator.to_python({'group_name': repo_group.group_name})
130 expected_msg = 'Group "test_gr" already exists'
130 expected_msg = 'Group "test_gr" already exists'
131 assert expected_msg in str(excinfo.value)
131 assert expected_msg in str(excinfo.value)
132
132
133
133
134 def test_ValidRepoGroup_invalid_parent(localizer, repo_group):
134 def test_ValidRepoGroup_invalid_parent(localizer, repo_group):
135 validator = v.ValidRepoGroup(localizer, edit=True,
135 validator = v.ValidRepoGroup(localizer, edit=True,
136 old_data={'group_id': repo_group.group_id})
136 old_data={'group_id': repo_group.group_id})
137 with pytest.raises(formencode.Invalid) as excinfo:
137 with pytest.raises(formencode.Invalid) as excinfo:
138 validator.to_python({
138 validator.to_python({
139 'group_name': repo_group.group_name + 'n',
139 'group_name': repo_group.group_name + 'n',
140 'group_parent_id': repo_group.group_id,
140 'group_parent_id': repo_group.group_id,
141 })
141 })
142 expected_msg = 'Cannot assign this group as parent'
142 expected_msg = 'Cannot assign this group as parent'
143 assert expected_msg in str(excinfo.value)
143 assert expected_msg in str(excinfo.value)
144
144
145
145
146 def test_ValidRepoGroup_edit_group_no_root_permission(localizer, repo_group):
146 def test_ValidRepoGroup_edit_group_no_root_permission(localizer, repo_group):
147 validator = v.ValidRepoGroup(localizer,
147 validator = v.ValidRepoGroup(localizer,
148 edit=True, old_data={'group_id': repo_group.group_id},
148 edit=True, old_data={'group_id': repo_group.group_id},
149 can_create_in_root=False)
149 can_create_in_root=False)
150
150
151 # Cannot change parent
151 # Cannot change parent
152 with pytest.raises(formencode.Invalid) as excinfo:
152 with pytest.raises(formencode.Invalid) as excinfo:
153 validator.to_python({'group_parent_id': '25'})
153 validator.to_python({'group_parent_id': '25'})
154 expected_msg = 'no permission to store repository group in root location'
154 expected_msg = 'no permission to store repository group in root location'
155 assert expected_msg in str(excinfo.value)
155 assert expected_msg in str(excinfo.value)
156
156
157 # Chaning all the other fields is allowed
157 # Chaning all the other fields is allowed
158 validator.to_python({'group_name': 'foo', 'group_parent_id': '-1'})
158 validator.to_python({'group_name': 'foo', 'group_parent_id': '-1'})
159 validator.to_python(
159 validator.to_python(
160 {'user': TEST_USER_REGULAR2_LOGIN, 'group_parent_id': '-1'})
160 {'user': TEST_USER_REGULAR2_LOGIN, 'group_parent_id': '-1'})
161 validator.to_python({'group_description': 'bar', 'group_parent_id': '-1'})
161 validator.to_python({'group_description': 'bar', 'group_parent_id': '-1'})
162 validator.to_python({'enable_locking': 'true', 'group_parent_id': '-1'})
162 validator.to_python({'enable_locking': 'true', 'group_parent_id': '-1'})
163
163
164
164
165 def test_ValidPassword(localizer):
165 def test_ValidPassword(localizer):
166 validator = v.ValidPassword(localizer)
166 validator = v.ValidPassword(localizer)
167 assert 'lol' == validator.to_python('lol')
167 assert 'lol' == validator.to_python('lol')
168 assert None == validator.to_python(None)
168 assert None == validator.to_python(None)
169 pytest.raises(formencode.Invalid, validator.to_python, 'ąćżź')
169 pytest.raises(formencode.Invalid, validator.to_python, 'ąćżź')
170
170
171
171
172 def test_ValidPasswordsMatch(localizer):
172 def test_ValidPasswordsMatch(localizer):
173 validator = v.ValidPasswordsMatch(localizer)
173 validator = v.ValidPasswordsMatch(localizer)
174 pytest.raises(
174 pytest.raises(
175 formencode.Invalid,
175 formencode.Invalid,
176 validator.to_python, {'password': 'pass',
176 validator.to_python, {'password': 'pass',
177 'password_confirmation': 'pass2'})
177 'password_confirmation': 'pass2'})
178
178
179 pytest.raises(
179 pytest.raises(
180 formencode.Invalid,
180 formencode.Invalid,
181 validator.to_python, {'new_password': 'pass',
181 validator.to_python, {'new_password': 'pass',
182 'password_confirmation': 'pass2'})
182 'password_confirmation': 'pass2'})
183
183
184 assert {'new_password': 'pass', 'password_confirmation': 'pass'} == \
184 assert {'new_password': 'pass', 'password_confirmation': 'pass'} == \
185 validator.to_python({'new_password': 'pass',
185 validator.to_python({'new_password': 'pass',
186 'password_confirmation': 'pass'})
186 'password_confirmation': 'pass'})
187
187
188 assert {'password': 'pass', 'password_confirmation': 'pass'} == \
188 assert {'password': 'pass', 'password_confirmation': 'pass'} == \
189 validator.to_python({'password': 'pass',
189 validator.to_python({'password': 'pass',
190 'password_confirmation': 'pass'})
190 'password_confirmation': 'pass'})
191
191
192
192
193 def test_ValidAuth(localizer, config_stub):
193 def test_ValidAuth(localizer, config_stub):
194 config_stub.testing_securitypolicy()
194 config_stub.testing_securitypolicy()
195 config_stub.include('rhodecode.authentication')
195 config_stub.include('rhodecode.authentication')
196 config_stub.include('rhodecode.authentication.plugins.auth_rhodecode')
197 config_stub.include('rhodecode.authentication.plugins.auth_token')
196
198
197 validator = v.ValidAuth(localizer)
199 validator = v.ValidAuth(localizer)
198 valid_creds = {
200 valid_creds = {
199 'username': TEST_USER_REGULAR2_LOGIN,
201 'username': TEST_USER_REGULAR2_LOGIN,
200 'password': TEST_USER_REGULAR2_PASS,
202 'password': TEST_USER_REGULAR2_PASS,
201 }
203 }
202 invalid_creds = {
204 invalid_creds = {
203 'username': 'err',
205 'username': 'err',
204 'password': 'err',
206 'password': 'err',
205 }
207 }
206 assert valid_creds == validator.to_python(valid_creds)
208 assert valid_creds == validator.to_python(valid_creds)
207 pytest.raises(
209 pytest.raises(
208 formencode.Invalid, validator.to_python, invalid_creds)
210 formencode.Invalid, validator.to_python, invalid_creds)
209
211
210
212
211 def test_ValidRepoName(localizer):
213 def test_ValidRepoName(localizer):
212 validator = v.ValidRepoName(localizer)
214 validator = v.ValidRepoName(localizer)
213
215
214 pytest.raises(
216 pytest.raises(
215 formencode.Invalid, validator.to_python, {'repo_name': ''})
217 formencode.Invalid, validator.to_python, {'repo_name': ''})
216
218
217 pytest.raises(
219 pytest.raises(
218 formencode.Invalid, validator.to_python, {'repo_name': HG_REPO})
220 formencode.Invalid, validator.to_python, {'repo_name': HG_REPO})
219
221
220 gr = RepoGroupModel().create(group_name='group_test',
222 gr = RepoGroupModel().create(group_name='group_test',
221 group_description='desc',
223 group_description='desc',
222 owner=TEST_USER_ADMIN_LOGIN)
224 owner=TEST_USER_ADMIN_LOGIN)
223 pytest.raises(
225 pytest.raises(
224 formencode.Invalid, validator.to_python, {'repo_name': gr.group_name})
226 formencode.Invalid, validator.to_python, {'repo_name': gr.group_name})
225
227
226 #TODO: write an error case for that ie. create a repo withinh a group
228 #TODO: write an error case for that ie. create a repo withinh a group
227 # pytest.raises(formencode.Invalid,
229 # pytest.raises(formencode.Invalid,
228 # validator.to_python, {'repo_name': 'some',
230 # validator.to_python, {'repo_name': 'some',
229 # 'repo_group': gr.group_id})
231 # 'repo_group': gr.group_id})
230
232
231
233
232 def test_ValidForkName(localizer):
234 def test_ValidForkName(localizer):
233 # this uses ValidRepoName validator
235 # this uses ValidRepoName validator
234 assert True
236 assert True
235
237
236 @pytest.mark.parametrize("name, expected", [
238 @pytest.mark.parametrize("name, expected", [
237 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
239 ('test', 'test'), ('lolz!', 'lolz'), (' aavv', 'aavv'),
238 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
240 ('ala ma kota', 'ala-ma-kota'), ('@nooo', 'nooo'),
239 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
241 ('$!haha lolz !', 'haha-lolz'), ('$$$$$', ''), ('{}OK!', 'OK'),
240 ('/]re po', 're-po')])
242 ('/]re po', 're-po')])
241 def test_SlugifyName(name, expected, localizer):
243 def test_SlugifyName(name, expected, localizer):
242 validator = v.SlugifyName(localizer)
244 validator = v.SlugifyName(localizer)
243 assert expected == validator.to_python(name)
245 assert expected == validator.to_python(name)
244
246
245
247
246 def test_ValidForkType(localizer):
248 def test_ValidForkType(localizer):
247 validator = v.ValidForkType(localizer, old_data={'repo_type': 'hg'})
249 validator = v.ValidForkType(localizer, old_data={'repo_type': 'hg'})
248 assert 'hg' == validator.to_python('hg')
250 assert 'hg' == validator.to_python('hg')
249 pytest.raises(formencode.Invalid, validator.to_python, 'git')
251 pytest.raises(formencode.Invalid, validator.to_python, 'git')
250
252
251
253
252 def test_ValidPath(localizer):
254 def test_ValidPath(localizer):
253 validator = v.ValidPath(localizer)
255 validator = v.ValidPath(localizer)
254 assert TESTS_TMP_PATH == validator.to_python(TESTS_TMP_PATH)
256 assert TESTS_TMP_PATH == validator.to_python(TESTS_TMP_PATH)
255 pytest.raises(
257 pytest.raises(
256 formencode.Invalid, validator.to_python, '/no_such_dir')
258 formencode.Invalid, validator.to_python, '/no_such_dir')
257
259
258
260
259 def test_UniqSystemEmail(localizer):
261 def test_UniqSystemEmail(localizer):
260 validator = v.UniqSystemEmail(localizer, old_data={})
262 validator = v.UniqSystemEmail(localizer, old_data={})
261
263
262 assert 'mail@python.org' == validator.to_python('MaiL@Python.org')
264 assert 'mail@python.org' == validator.to_python('MaiL@Python.org')
263
265
264 email = TEST_USER_REGULAR2_EMAIL
266 email = TEST_USER_REGULAR2_EMAIL
265 pytest.raises(formencode.Invalid, validator.to_python, email)
267 pytest.raises(formencode.Invalid, validator.to_python, email)
266
268
267
269
268 def test_ValidSystemEmail(localizer):
270 def test_ValidSystemEmail(localizer):
269 validator = v.ValidSystemEmail(localizer)
271 validator = v.ValidSystemEmail(localizer)
270 email = TEST_USER_REGULAR2_EMAIL
272 email = TEST_USER_REGULAR2_EMAIL
271
273
272 assert email == validator.to_python(email)
274 assert email == validator.to_python(email)
273 pytest.raises(formencode.Invalid, validator.to_python, 'err')
275 pytest.raises(formencode.Invalid, validator.to_python, 'err')
274
276
275
277
276 def test_NotReviewedRevisions(localizer):
278 def test_NotReviewedRevisions(localizer):
277 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
279 repo_id = Repository.get_by_repo_name(HG_REPO).repo_id
278 validator = v.NotReviewedRevisions(localizer, repo_id)
280 validator = v.NotReviewedRevisions(localizer, repo_id)
279 rev = '0' * 40
281 rev = '0' * 40
280 # add status for a rev, that should throw an error because it is already
282 # add status for a rev, that should throw an error because it is already
281 # reviewed
283 # reviewed
282 new_status = ChangesetStatus()
284 new_status = ChangesetStatus()
283 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
285 new_status.author = ChangesetStatusModel()._get_user(TEST_USER_ADMIN_LOGIN)
284 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
286 new_status.repo = ChangesetStatusModel()._get_repo(HG_REPO)
285 new_status.status = ChangesetStatus.STATUS_APPROVED
287 new_status.status = ChangesetStatus.STATUS_APPROVED
286 new_status.comment = None
288 new_status.comment = None
287 new_status.revision = rev
289 new_status.revision = rev
288 Session().add(new_status)
290 Session().add(new_status)
289 Session().commit()
291 Session().commit()
290 try:
292 try:
291 pytest.raises(formencode.Invalid, validator.to_python, [rev])
293 pytest.raises(formencode.Invalid, validator.to_python, [rev])
292 finally:
294 finally:
293 Session().delete(new_status)
295 Session().delete(new_status)
294 Session().commit()
296 Session().commit()
General Comments 0
You need to be logged in to leave comments. Login now