##// END OF EJS Templates
vcs: re-use repo config propagated to the middleware to extract push_ssl...
marcink -
r756:3d871443 default
parent child Browse files
Show More
@@ -1,450 +1,449 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 from functools import wraps
29 from functools import wraps
30
30
31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
31 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from webob.exc import (
32 from webob.exc import (
33 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
33 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34
34
35 import rhodecode
35 import rhodecode
36 from rhodecode.authentication.base import authenticate, VCS_TYPE
36 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
37 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
38 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.exceptions import (
39 from rhodecode.lib.exceptions import (
40 HTTPLockedRC, HTTPRequirementError, UserCreationError,
40 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 NotAllowedToCreateUserError)
41 NotAllowedToCreateUserError)
42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
42 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.middleware import appenlight
43 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware.utils import scm_app
44 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.utils import (
45 from rhodecode.lib.utils import (
46 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
46 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
47 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.vcs.conf import settings as vcs_settings
48 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.backends import base
49 from rhodecode.lib.vcs.backends import base
50 from rhodecode.model import meta
50 from rhodecode.model import meta
51 from rhodecode.model.db import User, Repository
51 from rhodecode.model.db import User, Repository
52 from rhodecode.model.scm import ScmModel
52 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.settings import SettingsModel
53 from rhodecode.model.settings import SettingsModel
54
54
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 def initialize_generator(factory):
59 def initialize_generator(factory):
60 """
60 """
61 Initializes the returned generator by draining its first element.
61 Initializes the returned generator by draining its first element.
62
62
63 This can be used to give a generator an initializer, which is the code
63 This can be used to give a generator an initializer, which is the code
64 up to the first yield statement. This decorator enforces that the first
64 up to the first yield statement. This decorator enforces that the first
65 produced element has the value ``"__init__"`` to make its special
65 produced element has the value ``"__init__"`` to make its special
66 purpose very explicit in the using code.
66 purpose very explicit in the using code.
67 """
67 """
68
68
69 @wraps(factory)
69 @wraps(factory)
70 def wrapper(*args, **kwargs):
70 def wrapper(*args, **kwargs):
71 gen = factory(*args, **kwargs)
71 gen = factory(*args, **kwargs)
72 try:
72 try:
73 init = gen.next()
73 init = gen.next()
74 except StopIteration:
74 except StopIteration:
75 raise ValueError('Generator must yield at least one element.')
75 raise ValueError('Generator must yield at least one element.')
76 if init != "__init__":
76 if init != "__init__":
77 raise ValueError('First yielded element must be "__init__".')
77 raise ValueError('First yielded element must be "__init__".')
78 return gen
78 return gen
79 return wrapper
79 return wrapper
80
80
81
81
82 class SimpleVCS(object):
82 class SimpleVCS(object):
83 """Common functionality for SCM HTTP handlers."""
83 """Common functionality for SCM HTTP handlers."""
84
84
85 SCM = 'unknown'
85 SCM = 'unknown'
86
86
87 def __init__(self, application, config, registry):
87 def __init__(self, application, config, registry):
88 self.registry = registry
88 self.registry = registry
89 self.application = application
89 self.application = application
90 self.config = config
90 self.config = config
91 # re-populated by specialized middlewares
91 # re-populated by specialized middlewares
92 self.repo_vcs_config = base.Config()
92 self.repo_vcs_config = base.Config()
93 # base path of repo locations
93 # base path of repo locations
94 self.basepath = get_rhodecode_base_path()
94 self.basepath = get_rhodecode_base_path()
95 # authenticate this VCS request using authfunc
95 # authenticate this VCS request using authfunc
96 auth_ret_code_detection = \
96 auth_ret_code_detection = \
97 str2bool(self.config.get('auth_ret_code_detection', False))
97 str2bool(self.config.get('auth_ret_code_detection', False))
98 self.authenticate = BasicAuth(
98 self.authenticate = BasicAuth(
99 '', authenticate, registry, config.get('auth_ret_code'),
99 '', authenticate, registry, config.get('auth_ret_code'),
100 auth_ret_code_detection)
100 auth_ret_code_detection)
101 self.ip_addr = '0.0.0.0'
101 self.ip_addr = '0.0.0.0'
102
102
103 @property
103 @property
104 def scm_app(self):
104 def scm_app(self):
105 custom_implementation = self.config.get('vcs.scm_app_implementation')
105 custom_implementation = self.config.get('vcs.scm_app_implementation')
106 if custom_implementation and custom_implementation != 'pyro4':
106 if custom_implementation and custom_implementation != 'pyro4':
107 log.info(
107 log.info(
108 "Using custom implementation of scm_app: %s",
108 "Using custom implementation of scm_app: %s",
109 custom_implementation)
109 custom_implementation)
110 scm_app_impl = importlib.import_module(custom_implementation)
110 scm_app_impl = importlib.import_module(custom_implementation)
111 else:
111 else:
112 scm_app_impl = scm_app
112 scm_app_impl = scm_app
113 return scm_app_impl
113 return scm_app_impl
114
114
115 def _get_by_id(self, repo_name):
115 def _get_by_id(self, repo_name):
116 """
116 """
117 Gets a special pattern _<ID> from clone url and tries to replace it
117 Gets a special pattern _<ID> from clone url and tries to replace it
118 with a repository_name for support of _<ID> non changable urls
118 with a repository_name for support of _<ID> non changable urls
119
119
120 :param repo_name:
120 :param repo_name:
121 """
121 """
122
122
123 data = repo_name.split('/')
123 data = repo_name.split('/')
124 if len(data) >= 2:
124 if len(data) >= 2:
125 from rhodecode.model.repo import RepoModel
125 from rhodecode.model.repo import RepoModel
126 by_id_match = RepoModel().get_repo_by_id(repo_name)
126 by_id_match = RepoModel().get_repo_by_id(repo_name)
127 if by_id_match:
127 if by_id_match:
128 data[1] = by_id_match.repo_name
128 data[1] = by_id_match.repo_name
129
129
130 return safe_str('/'.join(data))
130 return safe_str('/'.join(data))
131
131
132 def _invalidate_cache(self, repo_name):
132 def _invalidate_cache(self, repo_name):
133 """
133 """
134 Set's cache for this repository for invalidation on next access
134 Set's cache for this repository for invalidation on next access
135
135
136 :param repo_name: full repo name, also a cache key
136 :param repo_name: full repo name, also a cache key
137 """
137 """
138 ScmModel().mark_for_invalidation(repo_name)
138 ScmModel().mark_for_invalidation(repo_name)
139
139
140 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
140 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
141 db_repo = Repository.get_by_repo_name(repo_name)
141 db_repo = Repository.get_by_repo_name(repo_name)
142 if not db_repo:
142 if not db_repo:
143 log.debug('Repository `%s` not found inside the database.',
143 log.debug('Repository `%s` not found inside the database.',
144 repo_name)
144 repo_name)
145 return False
145 return False
146
146
147 if db_repo.repo_type != scm_type:
147 if db_repo.repo_type != scm_type:
148 log.warning(
148 log.warning(
149 'Repository `%s` have incorrect scm_type, expected %s got %s',
149 'Repository `%s` have incorrect scm_type, expected %s got %s',
150 repo_name, db_repo.repo_type, scm_type)
150 repo_name, db_repo.repo_type, scm_type)
151 return False
151 return False
152
152
153 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
153 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
154
154
155 def valid_and_active_user(self, user):
155 def valid_and_active_user(self, user):
156 """
156 """
157 Checks if that user is not empty, and if it's actually object it checks
157 Checks if that user is not empty, and if it's actually object it checks
158 if he's active.
158 if he's active.
159
159
160 :param user: user object or None
160 :param user: user object or None
161 :return: boolean
161 :return: boolean
162 """
162 """
163 if user is None:
163 if user is None:
164 return False
164 return False
165
165
166 elif user.active:
166 elif user.active:
167 return True
167 return True
168
168
169 return False
169 return False
170
170
171 def _check_permission(self, action, user, repo_name, ip_addr=None):
171 def _check_permission(self, action, user, repo_name, ip_addr=None):
172 """
172 """
173 Checks permissions using action (push/pull) user and repository
173 Checks permissions using action (push/pull) user and repository
174 name
174 name
175
175
176 :param action: push or pull action
176 :param action: push or pull action
177 :param user: user instance
177 :param user: user instance
178 :param repo_name: repository name
178 :param repo_name: repository name
179 """
179 """
180 # check IP
180 # check IP
181 inherit = user.inherit_default_permissions
181 inherit = user.inherit_default_permissions
182 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
182 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
183 inherit_from_default=inherit)
183 inherit_from_default=inherit)
184 if ip_allowed:
184 if ip_allowed:
185 log.info('Access for IP:%s allowed', ip_addr)
185 log.info('Access for IP:%s allowed', ip_addr)
186 else:
186 else:
187 return False
187 return False
188
188
189 if action == 'push':
189 if action == 'push':
190 if not HasPermissionAnyMiddleware('repository.write',
190 if not HasPermissionAnyMiddleware('repository.write',
191 'repository.admin')(user,
191 'repository.admin')(user,
192 repo_name):
192 repo_name):
193 return False
193 return False
194
194
195 else:
195 else:
196 # any other action need at least read permission
196 # any other action need at least read permission
197 if not HasPermissionAnyMiddleware('repository.read',
197 if not HasPermissionAnyMiddleware('repository.read',
198 'repository.write',
198 'repository.write',
199 'repository.admin')(user,
199 'repository.admin')(user,
200 repo_name):
200 repo_name):
201 return False
201 return False
202
202
203 return True
203 return True
204
204
205 def _check_ssl(self, environ, start_response):
205 def _check_ssl(self, environ, start_response):
206 """
206 """
207 Checks the SSL check flag and returns False if SSL is not present
207 Checks the SSL check flag and returns False if SSL is not present
208 and required True otherwise
208 and required True otherwise
209 """
209 """
210 org_proto = environ['wsgi._org_proto']
210 org_proto = environ['wsgi._org_proto']
211 # check if we have SSL required ! if not it's a bad request !
211 # check if we have SSL required ! if not it's a bad request !
212 require_ssl = str2bool(
212 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
213 SettingsModel().get_ui_by_key('push_ssl').ui_value)
214 if require_ssl and org_proto == 'http':
213 if require_ssl and org_proto == 'http':
215 log.debug('proto is %s and SSL is required BAD REQUEST !',
214 log.debug('proto is %s and SSL is required BAD REQUEST !',
216 org_proto)
215 org_proto)
217 return False
216 return False
218 return True
217 return True
219
218
220 def __call__(self, environ, start_response):
219 def __call__(self, environ, start_response):
221 try:
220 try:
222 return self._handle_request(environ, start_response)
221 return self._handle_request(environ, start_response)
223 except Exception:
222 except Exception:
224 log.exception("Exception while handling request")
223 log.exception("Exception while handling request")
225 appenlight.track_exception(environ)
224 appenlight.track_exception(environ)
226 return HTTPInternalServerError()(environ, start_response)
225 return HTTPInternalServerError()(environ, start_response)
227 finally:
226 finally:
228 meta.Session.remove()
227 meta.Session.remove()
229
228
230 def _handle_request(self, environ, start_response):
229 def _handle_request(self, environ, start_response):
231
230
232 if not self._check_ssl(environ, start_response):
231 if not self._check_ssl(environ, start_response):
233 reason = ('SSL required, while RhodeCode was unable '
232 reason = ('SSL required, while RhodeCode was unable '
234 'to detect this as SSL request')
233 'to detect this as SSL request')
235 log.debug('User not allowed to proceed, %s', reason)
234 log.debug('User not allowed to proceed, %s', reason)
236 return HTTPNotAcceptable(reason)(environ, start_response)
235 return HTTPNotAcceptable(reason)(environ, start_response)
237
236
238 ip_addr = get_ip_addr(environ)
237 ip_addr = get_ip_addr(environ)
239 username = None
238 username = None
240
239
241 # skip passing error to error controller
240 # skip passing error to error controller
242 environ['pylons.status_code_redirect'] = True
241 environ['pylons.status_code_redirect'] = True
243
242
244 # ======================================================================
243 # ======================================================================
245 # EXTRACT REPOSITORY NAME FROM ENV SET IN `class VCSMiddleware`
244 # EXTRACT REPOSITORY NAME FROM ENV SET IN `class VCSMiddleware`
246 # ======================================================================
245 # ======================================================================
247 repo_name = environ['REPO_NAME']
246 repo_name = environ['REPO_NAME']
248 log.debug('Extracted repo name is %s', repo_name)
247 log.debug('Extracted repo name is %s', repo_name)
249
248
250 # check for type, presence in database and on filesystem
249 # check for type, presence in database and on filesystem
251 if not self.is_valid_and_existing_repo(
250 if not self.is_valid_and_existing_repo(
252 repo_name, self.basepath, self.SCM):
251 repo_name, self.basepath, self.SCM):
253 return HTTPNotFound()(environ, start_response)
252 return HTTPNotFound()(environ, start_response)
254
253
255 # ======================================================================
254 # ======================================================================
256 # GET ACTION PULL or PUSH
255 # GET ACTION PULL or PUSH
257 # ======================================================================
256 # ======================================================================
258 action = self._get_action(environ)
257 action = self._get_action(environ)
259
258
260 # ======================================================================
259 # ======================================================================
261 # CHECK ANONYMOUS PERMISSION
260 # CHECK ANONYMOUS PERMISSION
262 # ======================================================================
261 # ======================================================================
263 if action in ['pull', 'push']:
262 if action in ['pull', 'push']:
264 anonymous_user = User.get_default_user()
263 anonymous_user = User.get_default_user()
265 username = anonymous_user.username
264 username = anonymous_user.username
266 if anonymous_user.active:
265 if anonymous_user.active:
267 # ONLY check permissions if the user is activated
266 # ONLY check permissions if the user is activated
268 anonymous_perm = self._check_permission(
267 anonymous_perm = self._check_permission(
269 action, anonymous_user, repo_name, ip_addr)
268 action, anonymous_user, repo_name, ip_addr)
270 else:
269 else:
271 anonymous_perm = False
270 anonymous_perm = False
272
271
273 if not anonymous_user.active or not anonymous_perm:
272 if not anonymous_user.active or not anonymous_perm:
274 if not anonymous_user.active:
273 if not anonymous_user.active:
275 log.debug('Anonymous access is disabled, running '
274 log.debug('Anonymous access is disabled, running '
276 'authentication')
275 'authentication')
277
276
278 if not anonymous_perm:
277 if not anonymous_perm:
279 log.debug('Not enough credentials to access this '
278 log.debug('Not enough credentials to access this '
280 'repository as anonymous user')
279 'repository as anonymous user')
281
280
282 username = None
281 username = None
283 # ==============================================================
282 # ==============================================================
284 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
283 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
285 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
284 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
286 # ==============================================================
285 # ==============================================================
287
286
288 # try to auth based on environ, container auth methods
287 # try to auth based on environ, container auth methods
289 log.debug('Running PRE-AUTH for container based authentication')
288 log.debug('Running PRE-AUTH for container based authentication')
290 pre_auth = authenticate(
289 pre_auth = authenticate(
291 '', '', environ, VCS_TYPE, registry=self.registry)
290 '', '', environ, VCS_TYPE, registry=self.registry)
292 if pre_auth and pre_auth.get('username'):
291 if pre_auth and pre_auth.get('username'):
293 username = pre_auth['username']
292 username = pre_auth['username']
294 log.debug('PRE-AUTH got %s as username', username)
293 log.debug('PRE-AUTH got %s as username', username)
295
294
296 # If not authenticated by the container, running basic auth
295 # If not authenticated by the container, running basic auth
297 if not username:
296 if not username:
298 self.authenticate.realm = get_rhodecode_realm()
297 self.authenticate.realm = get_rhodecode_realm()
299
298
300 try:
299 try:
301 result = self.authenticate(environ)
300 result = self.authenticate(environ)
302 except (UserCreationError, NotAllowedToCreateUserError) as e:
301 except (UserCreationError, NotAllowedToCreateUserError) as e:
303 log.error(e)
302 log.error(e)
304 reason = safe_str(e)
303 reason = safe_str(e)
305 return HTTPNotAcceptable(reason)(environ, start_response)
304 return HTTPNotAcceptable(reason)(environ, start_response)
306
305
307 if isinstance(result, str):
306 if isinstance(result, str):
308 AUTH_TYPE.update(environ, 'basic')
307 AUTH_TYPE.update(environ, 'basic')
309 REMOTE_USER.update(environ, result)
308 REMOTE_USER.update(environ, result)
310 username = result
309 username = result
311 else:
310 else:
312 return result.wsgi_application(environ, start_response)
311 return result.wsgi_application(environ, start_response)
313
312
314 # ==============================================================
313 # ==============================================================
315 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
314 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
316 # ==============================================================
315 # ==============================================================
317 user = User.get_by_username(username)
316 user = User.get_by_username(username)
318 if not self.valid_and_active_user(user):
317 if not self.valid_and_active_user(user):
319 return HTTPForbidden()(environ, start_response)
318 return HTTPForbidden()(environ, start_response)
320 username = user.username
319 username = user.username
321 user.update_lastactivity()
320 user.update_lastactivity()
322 meta.Session().commit()
321 meta.Session().commit()
323
322
324 # check user attributes for password change flag
323 # check user attributes for password change flag
325 user_obj = user
324 user_obj = user
326 if user_obj and user_obj.username != User.DEFAULT_USER and \
325 if user_obj and user_obj.username != User.DEFAULT_USER and \
327 user_obj.user_data.get('force_password_change'):
326 user_obj.user_data.get('force_password_change'):
328 reason = 'password change required'
327 reason = 'password change required'
329 log.debug('User not allowed to authenticate, %s', reason)
328 log.debug('User not allowed to authenticate, %s', reason)
330 return HTTPNotAcceptable(reason)(environ, start_response)
329 return HTTPNotAcceptable(reason)(environ, start_response)
331
330
332 # check permissions for this repository
331 # check permissions for this repository
333 perm = self._check_permission(action, user, repo_name, ip_addr)
332 perm = self._check_permission(action, user, repo_name, ip_addr)
334 if not perm:
333 if not perm:
335 return HTTPForbidden()(environ, start_response)
334 return HTTPForbidden()(environ, start_response)
336
335
337 # extras are injected into UI object and later available
336 # extras are injected into UI object and later available
338 # in hooks executed by rhodecode
337 # in hooks executed by rhodecode
339 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
338 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
340 extras = vcs_operation_context(
339 extras = vcs_operation_context(
341 environ, repo_name=repo_name, username=username,
340 environ, repo_name=repo_name, username=username,
342 action=action, scm=self.SCM,
341 action=action, scm=self.SCM,
343 check_locking=check_locking)
342 check_locking=check_locking)
344
343
345 # ======================================================================
344 # ======================================================================
346 # REQUEST HANDLING
345 # REQUEST HANDLING
347 # ======================================================================
346 # ======================================================================
348 str_repo_name = safe_str(repo_name)
347 str_repo_name = safe_str(repo_name)
349 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
348 repo_path = os.path.join(safe_str(self.basepath), str_repo_name)
350 log.debug('Repository path is %s', repo_path)
349 log.debug('Repository path is %s', repo_path)
351
350
352 fix_PATH()
351 fix_PATH()
353
352
354 log.info(
353 log.info(
355 '%s action on %s repo "%s" by "%s" from %s',
354 '%s action on %s repo "%s" by "%s" from %s',
356 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
355 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
357
356
358 return self._generate_vcs_response(
357 return self._generate_vcs_response(
359 environ, start_response, repo_path, repo_name, extras, action)
358 environ, start_response, repo_path, repo_name, extras, action)
360
359
361 @initialize_generator
360 @initialize_generator
362 def _generate_vcs_response(
361 def _generate_vcs_response(
363 self, environ, start_response, repo_path, repo_name, extras,
362 self, environ, start_response, repo_path, repo_name, extras,
364 action):
363 action):
365 """
364 """
366 Returns a generator for the response content.
365 Returns a generator for the response content.
367
366
368 This method is implemented as a generator, so that it can trigger
367 This method is implemented as a generator, so that it can trigger
369 the cache validation after all content sent back to the client. It
368 the cache validation after all content sent back to the client. It
370 also handles the locking exceptions which will be triggered when
369 also handles the locking exceptions which will be triggered when
371 the first chunk is produced by the underlying WSGI application.
370 the first chunk is produced by the underlying WSGI application.
372 """
371 """
373 callback_daemon, extras = self._prepare_callback_daemon(extras)
372 callback_daemon, extras = self._prepare_callback_daemon(extras)
374 config = self._create_config(extras, repo_name)
373 config = self._create_config(extras, repo_name)
375 log.debug('HOOKS extras is %s', extras)
374 log.debug('HOOKS extras is %s', extras)
376 app = self._create_wsgi_app(repo_path, repo_name, config)
375 app = self._create_wsgi_app(repo_path, repo_name, config)
377
376
378 try:
377 try:
379 with callback_daemon:
378 with callback_daemon:
380 try:
379 try:
381 response = app(environ, start_response)
380 response = app(environ, start_response)
382 finally:
381 finally:
383 # This statement works together with the decorator
382 # This statement works together with the decorator
384 # "initialize_generator" above. The decorator ensures that
383 # "initialize_generator" above. The decorator ensures that
385 # we hit the first yield statement before the generator is
384 # we hit the first yield statement before the generator is
386 # returned back to the WSGI server. This is needed to
385 # returned back to the WSGI server. This is needed to
387 # ensure that the call to "app" above triggers the
386 # ensure that the call to "app" above triggers the
388 # needed callback to "start_response" before the
387 # needed callback to "start_response" before the
389 # generator is actually used.
388 # generator is actually used.
390 yield "__init__"
389 yield "__init__"
391
390
392 for chunk in response:
391 for chunk in response:
393 yield chunk
392 yield chunk
394 except Exception as exc:
393 except Exception as exc:
395 # TODO: johbo: Improve "translating" back the exception.
394 # TODO: johbo: Improve "translating" back the exception.
396 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
395 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
397 exc = HTTPLockedRC(*exc.args)
396 exc = HTTPLockedRC(*exc.args)
398 _code = rhodecode.CONFIG.get('lock_ret_code')
397 _code = rhodecode.CONFIG.get('lock_ret_code')
399 log.debug('Repository LOCKED ret code %s!', (_code,))
398 log.debug('Repository LOCKED ret code %s!', (_code,))
400 elif getattr(exc, '_vcs_kind', None) == 'requirement':
399 elif getattr(exc, '_vcs_kind', None) == 'requirement':
401 log.debug(
400 log.debug(
402 'Repository requires features unknown to this Mercurial')
401 'Repository requires features unknown to this Mercurial')
403 exc = HTTPRequirementError(*exc.args)
402 exc = HTTPRequirementError(*exc.args)
404 else:
403 else:
405 raise
404 raise
406
405
407 for chunk in exc(environ, start_response):
406 for chunk in exc(environ, start_response):
408 yield chunk
407 yield chunk
409 finally:
408 finally:
410 # invalidate cache on push
409 # invalidate cache on push
411 try:
410 try:
412 if action == 'push':
411 if action == 'push':
413 self._invalidate_cache(repo_name)
412 self._invalidate_cache(repo_name)
414 finally:
413 finally:
415 meta.Session.remove()
414 meta.Session.remove()
416
415
417 def _get_repository_name(self, environ):
416 def _get_repository_name(self, environ):
418 """Get repository name out of the environmnent
417 """Get repository name out of the environmnent
419
418
420 :param environ: WSGI environment
419 :param environ: WSGI environment
421 """
420 """
422 raise NotImplementedError()
421 raise NotImplementedError()
423
422
424 def _get_action(self, environ):
423 def _get_action(self, environ):
425 """Map request commands into a pull or push command.
424 """Map request commands into a pull or push command.
426
425
427 :param environ: WSGI environment
426 :param environ: WSGI environment
428 """
427 """
429 raise NotImplementedError()
428 raise NotImplementedError()
430
429
431 def _create_wsgi_app(self, repo_path, repo_name, config):
430 def _create_wsgi_app(self, repo_path, repo_name, config):
432 """Return the WSGI app that will finally handle the request."""
431 """Return the WSGI app that will finally handle the request."""
433 raise NotImplementedError()
432 raise NotImplementedError()
434
433
435 def _create_config(self, extras, repo_name):
434 def _create_config(self, extras, repo_name):
436 """Create a Pyro safe config representation."""
435 """Create a Pyro safe config representation."""
437 raise NotImplementedError()
436 raise NotImplementedError()
438
437
439 def _prepare_callback_daemon(self, extras):
438 def _prepare_callback_daemon(self, extras):
440 return prepare_callback_daemon(
439 return prepare_callback_daemon(
441 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
440 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
442 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
441 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
443
442
444
443
445 def _should_check_locking(query_string):
444 def _should_check_locking(query_string):
446 # this is kind of hacky, but due to how mercurial handles client-server
445 # this is kind of hacky, but due to how mercurial handles client-server
447 # server see all operation on commit; bookmarks, phases and
446 # server see all operation on commit; bookmarks, phases and
448 # obsolescence marker in different transaction, we don't want to check
447 # obsolescence marker in different transaction, we don't want to check
449 # locking on those
448 # locking on those
450 return query_string not in ['cmd=listkeys']
449 return query_string not in ['cmd=listkeys']
General Comments 0
You need to be logged in to leave comments. Login now