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