##// END OF EJS Templates
http: improved log for ssl required
marcink -
r2593:b3b99584 default
parent child Browse files
Show More
@@ -1,645 +1,648 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 23 It's implemented with basic auth function
24 24 """
25 25
26 26 import os
27 27 import re
28 28 import logging
29 29 import importlib
30 30 from functools import wraps
31 31
32 32 import time
33 33 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
34 34 # TODO(marcink): check if we should use webob.exc here ?
35 35 from pyramid.httpexceptions import (
36 36 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
37 37 from zope.cachedescriptors.property import Lazy as LazyProperty
38 38
39 39 import rhodecode
40 40 from rhodecode.authentication.base import (
41 41 authenticate, get_perms_cache_manager, VCS_TYPE, loadplugin)
42 42 from rhodecode.lib import caches
43 43 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 44 from rhodecode.lib.base import (
45 45 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 46 from rhodecode.lib.exceptions import (
47 47 HTTPLockedRC, HTTPRequirementError, UserCreationError,
48 48 NotAllowedToCreateUserError)
49 49 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
50 50 from rhodecode.lib.middleware import appenlight
51 51 from rhodecode.lib.middleware.utils import scm_app_http
52 52 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
53 53 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool, safe_unicode
54 54 from rhodecode.lib.vcs.conf import settings as vcs_settings
55 55 from rhodecode.lib.vcs.backends import base
56 56 from rhodecode.model import meta
57 57 from rhodecode.model.db import User, Repository, PullRequest
58 58 from rhodecode.model.scm import ScmModel
59 59 from rhodecode.model.pull_request import PullRequestModel
60 60 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61 61
62 62 log = logging.getLogger(__name__)
63 63
64 64
65 65 def initialize_generator(factory):
66 66 """
67 67 Initializes the returned generator by draining its first element.
68 68
69 69 This can be used to give a generator an initializer, which is the code
70 70 up to the first yield statement. This decorator enforces that the first
71 71 produced element has the value ``"__init__"`` to make its special
72 72 purpose very explicit in the using code.
73 73 """
74 74
75 75 @wraps(factory)
76 76 def wrapper(*args, **kwargs):
77 77 gen = factory(*args, **kwargs)
78 78 try:
79 79 init = gen.next()
80 80 except StopIteration:
81 81 raise ValueError('Generator must yield at least one element.')
82 82 if init != "__init__":
83 83 raise ValueError('First yielded element must be "__init__".')
84 84 return gen
85 85 return wrapper
86 86
87 87
88 88 class SimpleVCS(object):
89 89 """Common functionality for SCM HTTP handlers."""
90 90
91 91 SCM = 'unknown'
92 92
93 93 acl_repo_name = None
94 94 url_repo_name = None
95 95 vcs_repo_name = None
96 96 rc_extras = {}
97 97
98 98 # We have to handle requests to shadow repositories different than requests
99 99 # to normal repositories. Therefore we have to distinguish them. To do this
100 100 # we use this regex which will match only on URLs pointing to shadow
101 101 # repositories.
102 102 shadow_repo_re = re.compile(
103 103 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
104 104 '(?P<target>{slug_pat})/' # target repo
105 105 'pull-request/(?P<pr_id>\d+)/' # pull request
106 106 'repository$' # shadow repo
107 107 .format(slug_pat=SLUG_RE.pattern))
108 108
109 109 def __init__(self, config, registry):
110 110 self.registry = registry
111 111 self.config = config
112 112 # re-populated by specialized middleware
113 113 self.repo_vcs_config = base.Config()
114 114 self.rhodecode_settings = SettingsModel().get_all_settings(cache=True)
115 115
116 116 registry.rhodecode_settings = self.rhodecode_settings
117 117 # authenticate this VCS request using authfunc
118 118 auth_ret_code_detection = \
119 119 str2bool(self.config.get('auth_ret_code_detection', False))
120 120 self.authenticate = BasicAuth(
121 121 '', authenticate, registry, config.get('auth_ret_code'),
122 122 auth_ret_code_detection)
123 123 self.ip_addr = '0.0.0.0'
124 124
125 125 @LazyProperty
126 126 def global_vcs_config(self):
127 127 try:
128 128 return VcsSettingsModel().get_ui_settings_as_config_obj()
129 129 except Exception:
130 130 return base.Config()
131 131
132 132 @property
133 133 def base_path(self):
134 134 settings_path = self.repo_vcs_config.get(
135 135 *VcsSettingsModel.PATH_SETTING)
136 136
137 137 if not settings_path:
138 138 settings_path = self.global_vcs_config.get(
139 139 *VcsSettingsModel.PATH_SETTING)
140 140
141 141 if not settings_path:
142 142 # try, maybe we passed in explicitly as config option
143 143 settings_path = self.config.get('base_path')
144 144
145 145 if not settings_path:
146 146 raise ValueError('FATAL: base_path is empty')
147 147 return settings_path
148 148
149 149 def set_repo_names(self, environ):
150 150 """
151 151 This will populate the attributes acl_repo_name, url_repo_name,
152 152 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
153 153 shadow) repositories all names are equal. In case of requests to a
154 154 shadow repository the acl-name points to the target repo of the pull
155 155 request and the vcs-name points to the shadow repo file system path.
156 156 The url-name is always the URL used by the vcs client program.
157 157
158 158 Example in case of a shadow repo:
159 159 acl_repo_name = RepoGroup/MyRepo
160 160 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
161 161 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
162 162 """
163 163 # First we set the repo name from URL for all attributes. This is the
164 164 # default if handling normal (non shadow) repo requests.
165 165 self.url_repo_name = self._get_repository_name(environ)
166 166 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
167 167 self.is_shadow_repo = False
168 168
169 169 # Check if this is a request to a shadow repository.
170 170 match = self.shadow_repo_re.match(self.url_repo_name)
171 171 if match:
172 172 match_dict = match.groupdict()
173 173
174 174 # Build acl repo name from regex match.
175 175 acl_repo_name = safe_unicode('{groups}{target}'.format(
176 176 groups=match_dict['groups'] or '',
177 177 target=match_dict['target']))
178 178
179 179 # Retrieve pull request instance by ID from regex match.
180 180 pull_request = PullRequest.get(match_dict['pr_id'])
181 181
182 182 # Only proceed if we got a pull request and if acl repo name from
183 183 # URL equals the target repo name of the pull request.
184 184 if pull_request and (acl_repo_name ==
185 185 pull_request.target_repo.repo_name):
186 186 # Get file system path to shadow repository.
187 187 workspace_id = PullRequestModel()._workspace_id(pull_request)
188 188 target_vcs = pull_request.target_repo.scm_instance()
189 189 vcs_repo_name = target_vcs._get_shadow_repository_path(
190 190 workspace_id)
191 191
192 192 # Store names for later usage.
193 193 self.vcs_repo_name = vcs_repo_name
194 194 self.acl_repo_name = acl_repo_name
195 195 self.is_shadow_repo = True
196 196
197 197 log.debug('Setting all VCS repository names: %s', {
198 198 'acl_repo_name': self.acl_repo_name,
199 199 'url_repo_name': self.url_repo_name,
200 200 'vcs_repo_name': self.vcs_repo_name,
201 201 })
202 202
203 203 @property
204 204 def scm_app(self):
205 205 custom_implementation = self.config['vcs.scm_app_implementation']
206 206 if custom_implementation == 'http':
207 207 log.info('Using HTTP implementation of scm app.')
208 208 scm_app_impl = scm_app_http
209 209 else:
210 210 log.info('Using custom implementation of scm_app: "{}"'.format(
211 211 custom_implementation))
212 212 scm_app_impl = importlib.import_module(custom_implementation)
213 213 return scm_app_impl
214 214
215 215 def _get_by_id(self, repo_name):
216 216 """
217 217 Gets a special pattern _<ID> from clone url and tries to replace it
218 218 with a repository_name for support of _<ID> non changeable urls
219 219 """
220 220
221 221 data = repo_name.split('/')
222 222 if len(data) >= 2:
223 223 from rhodecode.model.repo import RepoModel
224 224 by_id_match = RepoModel().get_repo_by_id(repo_name)
225 225 if by_id_match:
226 226 data[1] = by_id_match.repo_name
227 227
228 228 return safe_str('/'.join(data))
229 229
230 230 def _invalidate_cache(self, repo_name):
231 231 """
232 232 Set's cache for this repository for invalidation on next access
233 233
234 234 :param repo_name: full repo name, also a cache key
235 235 """
236 236 ScmModel().mark_for_invalidation(repo_name)
237 237
238 238 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
239 239 db_repo = Repository.get_by_repo_name(repo_name)
240 240 if not db_repo:
241 241 log.debug('Repository `%s` not found inside the database.',
242 242 repo_name)
243 243 return False
244 244
245 245 if db_repo.repo_type != scm_type:
246 246 log.warning(
247 247 'Repository `%s` have incorrect scm_type, expected %s got %s',
248 248 repo_name, db_repo.repo_type, scm_type)
249 249 return False
250 250
251 251 config = db_repo._config
252 252 config.set('extensions', 'largefiles', '')
253 253 return is_valid_repo(
254 254 repo_name, base_path,
255 255 explicit_scm=scm_type, expect_scm=scm_type, config=config)
256 256
257 257 def valid_and_active_user(self, user):
258 258 """
259 259 Checks if that user is not empty, and if it's actually object it checks
260 260 if he's active.
261 261
262 262 :param user: user object or None
263 263 :return: boolean
264 264 """
265 265 if user is None:
266 266 return False
267 267
268 268 elif user.active:
269 269 return True
270 270
271 271 return False
272 272
273 273 @property
274 274 def is_shadow_repo_dir(self):
275 275 return os.path.isdir(self.vcs_repo_name)
276 276
277 277 def _check_permission(self, action, user, repo_name, ip_addr=None,
278 278 plugin_id='', plugin_cache_active=False, cache_ttl=0):
279 279 """
280 280 Checks permissions using action (push/pull) user and repository
281 281 name. If plugin_cache and ttl is set it will use the plugin which
282 282 authenticated the user to store the cached permissions result for N
283 283 amount of seconds as in cache_ttl
284 284
285 285 :param action: push or pull action
286 286 :param user: user instance
287 287 :param repo_name: repository name
288 288 """
289 289
290 290 # get instance of cache manager configured for a namespace
291 291 cache_manager = get_perms_cache_manager(
292 292 custom_ttl=cache_ttl, suffix=user.user_id)
293 293 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
294 294 plugin_id, plugin_cache_active, cache_ttl)
295 295
296 296 # for environ based password can be empty, but then the validation is
297 297 # on the server that fills in the env data needed for authentication
298 298 _perm_calc_hash = caches.compute_key_from_params(
299 299 plugin_id, action, user.user_id, repo_name, ip_addr)
300 300
301 301 # _authenticate is a wrapper for .auth() method of plugin.
302 302 # it checks if .auth() sends proper data.
303 303 # For RhodeCodeExternalAuthPlugin it also maps users to
304 304 # Database and maps the attributes returned from .auth()
305 305 # to RhodeCode database. If this function returns data
306 306 # then auth is correct.
307 307 start = time.time()
308 308 log.debug('Running plugin `%s` permissions check', plugin_id)
309 309
310 310 def perm_func():
311 311 """
312 312 This function is used internally in Cache of Beaker to calculate
313 313 Results
314 314 """
315 315 log.debug('auth: calculating permission access now...')
316 316 # check IP
317 317 inherit = user.inherit_default_permissions
318 318 ip_allowed = AuthUser.check_ip_allowed(
319 319 user.user_id, ip_addr, inherit_from_default=inherit)
320 320 if ip_allowed:
321 321 log.info('Access for IP:%s allowed', ip_addr)
322 322 else:
323 323 return False
324 324
325 325 if action == 'push':
326 326 perms = ('repository.write', 'repository.admin')
327 327 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
328 328 return False
329 329
330 330 else:
331 331 # any other action need at least read permission
332 332 perms = (
333 333 'repository.read', 'repository.write', 'repository.admin')
334 334 if not HasPermissionAnyMiddleware(*perms)(user, repo_name):
335 335 return False
336 336
337 337 return True
338 338
339 339 if plugin_cache_active:
340 340 log.debug('Trying to fetch cached perms by %s', _perm_calc_hash[:6])
341 341 perm_result = cache_manager.get(
342 342 _perm_calc_hash, createfunc=perm_func)
343 343 else:
344 344 perm_result = perm_func()
345 345
346 346 auth_time = time.time() - start
347 347 log.debug('Permissions for plugin `%s` completed in %.3fs, '
348 348 'expiration time of fetched cache %.1fs.',
349 349 plugin_id, auth_time, cache_ttl)
350 350
351 351 return perm_result
352 352
353 353 def _check_ssl(self, environ, start_response):
354 354 """
355 355 Checks the SSL check flag and returns False if SSL is not present
356 356 and required True otherwise
357 357 """
358 358 org_proto = environ['wsgi._org_proto']
359 359 # check if we have SSL required ! if not it's a bad request !
360 360 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
361 361 if require_ssl and org_proto == 'http':
362 log.debug('proto is %s and SSL is required BAD REQUEST !',
363 org_proto)
362 log.debug(
363 'Bad request: detected protocol is `%s` and '
364 'SSL/HTTPS is required.', org_proto)
364 365 return False
365 366 return True
366 367
367 368 def _get_default_cache_ttl(self):
368 369 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
369 370 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
370 371 plugin_settings = plugin.get_settings()
371 372 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
372 373 plugin_settings) or (False, 0)
373 374 return plugin_cache_active, cache_ttl
374 375
375 376 def __call__(self, environ, start_response):
376 377 try:
377 378 return self._handle_request(environ, start_response)
378 379 except Exception:
379 380 log.exception("Exception while handling request")
380 381 appenlight.track_exception(environ)
381 382 return HTTPInternalServerError()(environ, start_response)
382 383 finally:
383 384 meta.Session.remove()
384 385
385 386 def _handle_request(self, environ, start_response):
386 387
387 388 if not self._check_ssl(environ, start_response):
388 389 reason = ('SSL required, while RhodeCode was unable '
389 390 'to detect this as SSL request')
390 391 log.debug('User not allowed to proceed, %s', reason)
391 392 return HTTPNotAcceptable(reason)(environ, start_response)
392 393
393 394 if not self.url_repo_name:
394 395 log.warning('Repository name is empty: %s', self.url_repo_name)
395 396 # failed to get repo name, we fail now
396 397 return HTTPNotFound()(environ, start_response)
397 398 log.debug('Extracted repo name is %s', self.url_repo_name)
398 399
399 400 ip_addr = get_ip_addr(environ)
400 401 user_agent = get_user_agent(environ)
401 402 username = None
402 403
403 404 # skip passing error to error controller
404 405 environ['pylons.status_code_redirect'] = True
405 406
406 407 # ======================================================================
407 408 # GET ACTION PULL or PUSH
408 409 # ======================================================================
409 410 action = self._get_action(environ)
410 411
411 412 # ======================================================================
412 413 # Check if this is a request to a shadow repository of a pull request.
413 414 # In this case only pull action is allowed.
414 415 # ======================================================================
415 416 if self.is_shadow_repo and action != 'pull':
416 417 reason = 'Only pull action is allowed for shadow repositories.'
417 418 log.debug('User not allowed to proceed, %s', reason)
418 419 return HTTPNotAcceptable(reason)(environ, start_response)
419 420
420 421 # Check if the shadow repo actually exists, in case someone refers
421 422 # to it, and it has been deleted because of successful merge.
422 423 if self.is_shadow_repo and not self.is_shadow_repo_dir:
423 log.debug('Shadow repo detected, and shadow repo dir `%s` is missing',
424 self.is_shadow_repo_dir)
424 log.debug(
425 'Shadow repo detected, and shadow repo dir `%s` is missing',
426 self.is_shadow_repo_dir)
425 427 return HTTPNotFound()(environ, start_response)
426 428
427 429 # ======================================================================
428 430 # CHECK ANONYMOUS PERMISSION
429 431 # ======================================================================
430 432 if action in ['pull', 'push']:
431 433 anonymous_user = User.get_default_user()
432 434 username = anonymous_user.username
433 435 if anonymous_user.active:
434 436 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
435 437 # ONLY check permissions if the user is activated
436 438 anonymous_perm = self._check_permission(
437 439 action, anonymous_user, self.acl_repo_name, ip_addr,
438 440 plugin_id='anonymous_access',
439 plugin_cache_active=plugin_cache_active, cache_ttl=cache_ttl,
441 plugin_cache_active=plugin_cache_active,
442 cache_ttl=cache_ttl,
440 443 )
441 444 else:
442 445 anonymous_perm = False
443 446
444 447 if not anonymous_user.active or not anonymous_perm:
445 448 if not anonymous_user.active:
446 449 log.debug('Anonymous access is disabled, running '
447 450 'authentication')
448 451
449 452 if not anonymous_perm:
450 453 log.debug('Not enough credentials to access this '
451 454 'repository as anonymous user')
452 455
453 456 username = None
454 457 # ==============================================================
455 458 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
456 459 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
457 460 # ==============================================================
458 461
459 462 # try to auth based on environ, container auth methods
460 463 log.debug('Running PRE-AUTH for container based authentication')
461 464 pre_auth = authenticate(
462 465 '', '', environ, VCS_TYPE, registry=self.registry,
463 466 acl_repo_name=self.acl_repo_name)
464 467 if pre_auth and pre_auth.get('username'):
465 468 username = pre_auth['username']
466 469 log.debug('PRE-AUTH got %s as username', username)
467 470 if pre_auth:
468 471 log.debug('PRE-AUTH successful from %s',
469 472 pre_auth.get('auth_data', {}).get('_plugin'))
470 473
471 474 # If not authenticated by the container, running basic auth
472 475 # before inject the calling repo_name for special scope checks
473 476 self.authenticate.acl_repo_name = self.acl_repo_name
474 477
475 478 plugin_cache_active, cache_ttl = False, 0
476 479 plugin = None
477 480 if not username:
478 481 self.authenticate.realm = self.authenticate.get_rc_realm()
479 482
480 483 try:
481 484 auth_result = self.authenticate(environ)
482 485 except (UserCreationError, NotAllowedToCreateUserError) as e:
483 486 log.error(e)
484 487 reason = safe_str(e)
485 488 return HTTPNotAcceptable(reason)(environ, start_response)
486 489
487 490 if isinstance(auth_result, dict):
488 491 AUTH_TYPE.update(environ, 'basic')
489 492 REMOTE_USER.update(environ, auth_result['username'])
490 493 username = auth_result['username']
491 494 plugin = auth_result.get('auth_data', {}).get('_plugin')
492 495 log.info(
493 496 'MAIN-AUTH successful for user `%s` from %s plugin',
494 497 username, plugin)
495 498
496 499 plugin_cache_active, cache_ttl = auth_result.get(
497 500 'auth_data', {}).get('_ttl_cache') or (False, 0)
498 501 else:
499 502 return auth_result.wsgi_application(
500 503 environ, start_response)
501 504
502 505
503 506 # ==============================================================
504 507 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
505 508 # ==============================================================
506 509 user = User.get_by_username(username)
507 510 if not self.valid_and_active_user(user):
508 511 return HTTPForbidden()(environ, start_response)
509 512 username = user.username
510 513 user.update_lastactivity()
511 514 meta.Session().commit()
512 515
513 516 # check user attributes for password change flag
514 517 user_obj = user
515 518 if user_obj and user_obj.username != User.DEFAULT_USER and \
516 519 user_obj.user_data.get('force_password_change'):
517 520 reason = 'password change required'
518 521 log.debug('User not allowed to authenticate, %s', reason)
519 522 return HTTPNotAcceptable(reason)(environ, start_response)
520 523
521 524 # check permissions for this repository
522 525 perm = self._check_permission(
523 526 action, user, self.acl_repo_name, ip_addr,
524 527 plugin, plugin_cache_active, cache_ttl)
525 528 if not perm:
526 529 return HTTPForbidden()(environ, start_response)
527 530
528 531 # extras are injected into UI object and later available
529 532 # in hooks executed by RhodeCode
530 533 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
531 534 extras = vcs_operation_context(
532 535 environ, repo_name=self.acl_repo_name, username=username,
533 536 action=action, scm=self.SCM, check_locking=check_locking,
534 537 is_shadow_repo=self.is_shadow_repo
535 538 )
536 539
537 540 # ======================================================================
538 541 # REQUEST HANDLING
539 542 # ======================================================================
540 543 repo_path = os.path.join(
541 544 safe_str(self.base_path), safe_str(self.vcs_repo_name))
542 545 log.debug('Repository path is %s', repo_path)
543 546
544 547 fix_PATH()
545 548
546 549 log.info(
547 550 '%s action on %s repo "%s" by "%s" from %s %s',
548 551 action, self.SCM, safe_str(self.url_repo_name),
549 552 safe_str(username), ip_addr, user_agent)
550 553
551 554 return self._generate_vcs_response(
552 555 environ, start_response, repo_path, extras, action)
553 556
554 557 @initialize_generator
555 558 def _generate_vcs_response(
556 559 self, environ, start_response, repo_path, extras, action):
557 560 """
558 561 Returns a generator for the response content.
559 562
560 563 This method is implemented as a generator, so that it can trigger
561 564 the cache validation after all content sent back to the client. It
562 565 also handles the locking exceptions which will be triggered when
563 566 the first chunk is produced by the underlying WSGI application.
564 567 """
565 568 callback_daemon, extras = self._prepare_callback_daemon(extras)
566 569 config = self._create_config(extras, self.acl_repo_name)
567 570 log.debug('HOOKS extras is %s', extras)
568 571 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
569 572 app.rc_extras = extras
570 573
571 574 try:
572 575 with callback_daemon:
573 576 try:
574 577 response = app(environ, start_response)
575 578 finally:
576 579 # This statement works together with the decorator
577 580 # "initialize_generator" above. The decorator ensures that
578 581 # we hit the first yield statement before the generator is
579 582 # returned back to the WSGI server. This is needed to
580 583 # ensure that the call to "app" above triggers the
581 584 # needed callback to "start_response" before the
582 585 # generator is actually used.
583 586 yield "__init__"
584 587
585 588 for chunk in response:
586 589 yield chunk
587 590 except Exception as exc:
588 591 # TODO: martinb: Exceptions are only raised in case of the Pyro4
589 592 # backend. Refactor this except block after dropping Pyro4 support.
590 593 # TODO: johbo: Improve "translating" back the exception.
591 594 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
592 595 exc = HTTPLockedRC(*exc.args)
593 596 _code = rhodecode.CONFIG.get('lock_ret_code')
594 597 log.debug('Repository LOCKED ret code %s!', (_code,))
595 598 elif getattr(exc, '_vcs_kind', None) == 'requirement':
596 599 log.debug(
597 600 'Repository requires features unknown to this Mercurial')
598 601 exc = HTTPRequirementError(*exc.args)
599 602 else:
600 603 raise
601 604
602 605 for chunk in exc(environ, start_response):
603 606 yield chunk
604 607 finally:
605 608 # invalidate cache on push
606 609 try:
607 610 if action == 'push':
608 611 self._invalidate_cache(self.url_repo_name)
609 612 finally:
610 613 meta.Session.remove()
611 614
612 615 def _get_repository_name(self, environ):
613 616 """Get repository name out of the environmnent
614 617
615 618 :param environ: WSGI environment
616 619 """
617 620 raise NotImplementedError()
618 621
619 622 def _get_action(self, environ):
620 623 """Map request commands into a pull or push command.
621 624
622 625 :param environ: WSGI environment
623 626 """
624 627 raise NotImplementedError()
625 628
626 629 def _create_wsgi_app(self, repo_path, repo_name, config):
627 630 """Return the WSGI app that will finally handle the request."""
628 631 raise NotImplementedError()
629 632
630 633 def _create_config(self, extras, repo_name):
631 634 """Create a safe config representation."""
632 635 raise NotImplementedError()
633 636
634 637 def _prepare_callback_daemon(self, extras):
635 638 return prepare_callback_daemon(
636 639 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
637 640 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
638 641
639 642
640 643 def _should_check_locking(query_string):
641 644 # this is kind of hacky, but due to how mercurial handles client-server
642 645 # server see all operation on commit; bookmarks, phases and
643 646 # obsolescence marker in different transaction, we don't want to check
644 647 # locking on those
645 648 return query_string not in ['cmd=listkeys']
General Comments 0
You need to be logged in to leave comments. Login now