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