##// END OF EJS Templates
vcs: Move code for parsing the repository names (url, vcs, acl) from vcs to simplevcs.
Martin Bornhold -
r889:4fa21815 default
parent child Browse files
Show More
@@ -1,453 +1,479 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2016 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 logging
28 28 import importlib
29 import re
29 30 from functools import wraps
30 31
31 32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 33 from webob.exc import (
33 34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 35
35 36 import rhodecode
36 37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 40 from rhodecode.lib.exceptions import (
40 41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 42 NotAllowedToCreateUserError)
42 43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 44 from rhodecode.lib.middleware import appenlight
44 45 from rhodecode.lib.middleware.utils import scm_app
45 46 from rhodecode.lib.utils import (
46 47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 49 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 50 from rhodecode.lib.vcs.backends import base
50 51 from rhodecode.model import meta
51 52 from rhodecode.model.db import User, Repository
52 53 from rhodecode.model.scm import ScmModel
53 54
54 55
55 56 log = logging.getLogger(__name__)
56 57
57 58
58 59 def initialize_generator(factory):
59 60 """
60 61 Initializes the returned generator by draining its first element.
61 62
62 63 This can be used to give a generator an initializer, which is the code
63 64 up to the first yield statement. This decorator enforces that the first
64 65 produced element has the value ``"__init__"`` to make its special
65 66 purpose very explicit in the using code.
66 67 """
67 68
68 69 @wraps(factory)
69 70 def wrapper(*args, **kwargs):
70 71 gen = factory(*args, **kwargs)
71 72 try:
72 73 init = gen.next()
73 74 except StopIteration:
74 75 raise ValueError('Generator must yield at least one element.')
75 76 if init != "__init__":
76 77 raise ValueError('First yielded element must be "__init__".')
77 78 return gen
78 79 return wrapper
79 80
80 81
81 82 class SimpleVCS(object):
82 83 """Common functionality for SCM HTTP handlers."""
83 84
84 85 SCM = 'unknown'
85 86
86 87 acl_repo_name = None
87 88 url_repo_name = None
88 89 vcs_repo_name = None
89 90
90 91 def __init__(self, application, config, registry):
91 92 self.registry = registry
92 93 self.application = application
93 94 self.config = config
94 95 # re-populated by specialized middleware
95 96 self.repo_vcs_config = base.Config()
96 97
97 98 # base path of repo locations
98 99 self.basepath = get_rhodecode_base_path()
99 100 # authenticate this VCS request using authfunc
100 101 auth_ret_code_detection = \
101 102 str2bool(self.config.get('auth_ret_code_detection', False))
102 103 self.authenticate = BasicAuth(
103 104 '', authenticate, registry, config.get('auth_ret_code'),
104 105 auth_ret_code_detection)
105 106 self.ip_addr = '0.0.0.0'
106 107
108 def set_repo_names(self, environ):
109 """
110 This will populate the attributes acl_repo_name, url_repo_name and
111 vcs_repo_name on the current instance.
112 """
113 # TODO: martinb: Move to class or module scope.
114 pr_regex = re.compile(
115 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/'
116 '(?P<repo_name>[\w-]+)'
117 '/pull-request/(?P<pr_id>\d+)/repository')
118
119 self.url_repo_name = self._get_repository_name(environ)
120 match = pr_regex.match(self.url_repo_name)
121
122 if match:
123 match_dict = match.groupdict()
124 self.acl_repo_name = '{base_name}/{repo_name}'.format(**match_dict)
125 self.vcs_repo_name = '{base_name}/.__shadow_{repo_name}_pr-{pr_id}'.format(
126 **match_dict)
127 self.pr_id = match_dict.get('pr_id')
128 else:
129 self.acl_repo_name = self.url_repo_name
130 self.vcs_repo_name = self.url_repo_name
131 self.pr_id = None
132
107 133 @property
108 134 def repo_name(self):
109 135 # TODO: johbo: Remove, switch to correct repo name attribute
110 136 return self.acl_repo_name
111 137
112 138 @property
113 139 def scm_app(self):
114 140 custom_implementation = self.config.get('vcs.scm_app_implementation')
115 141 if custom_implementation and custom_implementation != 'pyro4':
116 142 log.info(
117 143 "Using custom implementation of scm_app: %s",
118 144 custom_implementation)
119 145 scm_app_impl = importlib.import_module(custom_implementation)
120 146 else:
121 147 scm_app_impl = scm_app
122 148 return scm_app_impl
123 149
124 150 def _get_by_id(self, repo_name):
125 151 """
126 152 Gets a special pattern _<ID> from clone url and tries to replace it
127 153 with a repository_name for support of _<ID> non changeable urls
128 154 """
129 155
130 156 data = repo_name.split('/')
131 157 if len(data) >= 2:
132 158 from rhodecode.model.repo import RepoModel
133 159 by_id_match = RepoModel().get_repo_by_id(repo_name)
134 160 if by_id_match:
135 161 data[1] = by_id_match.repo_name
136 162
137 163 return safe_str('/'.join(data))
138 164
139 165 def _invalidate_cache(self, repo_name):
140 166 """
141 167 Set's cache for this repository for invalidation on next access
142 168
143 169 :param repo_name: full repo name, also a cache key
144 170 """
145 171 ScmModel().mark_for_invalidation(repo_name)
146 172
147 173 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
148 174 db_repo = Repository.get_by_repo_name(repo_name)
149 175 if not db_repo:
150 176 log.debug('Repository `%s` not found inside the database.',
151 177 repo_name)
152 178 return False
153 179
154 180 if db_repo.repo_type != scm_type:
155 181 log.warning(
156 182 'Repository `%s` have incorrect scm_type, expected %s got %s',
157 183 repo_name, db_repo.repo_type, scm_type)
158 184 return False
159 185
160 186 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
161 187
162 188 def valid_and_active_user(self, user):
163 189 """
164 190 Checks if that user is not empty, and if it's actually object it checks
165 191 if he's active.
166 192
167 193 :param user: user object or None
168 194 :return: boolean
169 195 """
170 196 if user is None:
171 197 return False
172 198
173 199 elif user.active:
174 200 return True
175 201
176 202 return False
177 203
178 204 def _check_permission(self, action, user, repo_name, ip_addr=None):
179 205 """
180 206 Checks permissions using action (push/pull) user and repository
181 207 name
182 208
183 209 :param action: push or pull action
184 210 :param user: user instance
185 211 :param repo_name: repository name
186 212 """
187 213 # check IP
188 214 inherit = user.inherit_default_permissions
189 215 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
190 216 inherit_from_default=inherit)
191 217 if ip_allowed:
192 218 log.info('Access for IP:%s allowed', ip_addr)
193 219 else:
194 220 return False
195 221
196 222 if action == 'push':
197 223 if not HasPermissionAnyMiddleware('repository.write',
198 224 'repository.admin')(user,
199 225 repo_name):
200 226 return False
201 227
202 228 else:
203 229 # any other action need at least read permission
204 230 if not HasPermissionAnyMiddleware('repository.read',
205 231 'repository.write',
206 232 'repository.admin')(user,
207 233 repo_name):
208 234 return False
209 235
210 236 return True
211 237
212 238 def _check_ssl(self, environ, start_response):
213 239 """
214 240 Checks the SSL check flag and returns False if SSL is not present
215 241 and required True otherwise
216 242 """
217 243 org_proto = environ['wsgi._org_proto']
218 244 # check if we have SSL required ! if not it's a bad request !
219 245 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
220 246 if require_ssl and org_proto == 'http':
221 247 log.debug('proto is %s and SSL is required BAD REQUEST !',
222 248 org_proto)
223 249 return False
224 250 return True
225 251
226 252 def __call__(self, environ, start_response):
227 253 try:
228 254 return self._handle_request(environ, start_response)
229 255 except Exception:
230 256 log.exception("Exception while handling request")
231 257 appenlight.track_exception(environ)
232 258 return HTTPInternalServerError()(environ, start_response)
233 259 finally:
234 260 meta.Session.remove()
235 261
236 262 def _handle_request(self, environ, start_response):
237 263
238 264 if not self._check_ssl(environ, start_response):
239 265 reason = ('SSL required, while RhodeCode was unable '
240 266 'to detect this as SSL request')
241 267 log.debug('User not allowed to proceed, %s', reason)
242 268 return HTTPNotAcceptable(reason)(environ, start_response)
243 269
244 270 if not self.repo_name:
245 271 log.warning('Repository name is empty: %s', self.repo_name)
246 272 # failed to get repo name, we fail now
247 273 return HTTPNotFound()(environ, start_response)
248 274 log.debug('Extracted repo name is %s', self.repo_name)
249 275
250 276 ip_addr = get_ip_addr(environ)
251 277 username = None
252 278
253 279 # skip passing error to error controller
254 280 environ['pylons.status_code_redirect'] = True
255 281
256 282 # ======================================================================
257 283 # GET ACTION PULL or PUSH
258 284 # ======================================================================
259 285 action = self._get_action(environ)
260 286
261 287 # ======================================================================
262 288 # CHECK ANONYMOUS PERMISSION
263 289 # ======================================================================
264 290 if action in ['pull', 'push']:
265 291 anonymous_user = User.get_default_user()
266 292 username = anonymous_user.username
267 293 if anonymous_user.active:
268 294 # ONLY check permissions if the user is activated
269 295 anonymous_perm = self._check_permission(
270 296 action, anonymous_user, self.repo_name, ip_addr)
271 297 else:
272 298 anonymous_perm = False
273 299
274 300 if not anonymous_user.active or not anonymous_perm:
275 301 if not anonymous_user.active:
276 302 log.debug('Anonymous access is disabled, running '
277 303 'authentication')
278 304
279 305 if not anonymous_perm:
280 306 log.debug('Not enough credentials to access this '
281 307 'repository as anonymous user')
282 308
283 309 username = None
284 310 # ==============================================================
285 311 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
286 312 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
287 313 # ==============================================================
288 314
289 315 # try to auth based on environ, container auth methods
290 316 log.debug('Running PRE-AUTH for container based authentication')
291 317 pre_auth = authenticate(
292 318 '', '', environ, VCS_TYPE, registry=self.registry)
293 319 if pre_auth and pre_auth.get('username'):
294 320 username = pre_auth['username']
295 321 log.debug('PRE-AUTH got %s as username', username)
296 322
297 323 # If not authenticated by the container, running basic auth
298 324 if not username:
299 325 self.authenticate.realm = get_rhodecode_realm()
300 326
301 327 try:
302 328 result = self.authenticate(environ)
303 329 except (UserCreationError, NotAllowedToCreateUserError) as e:
304 330 log.error(e)
305 331 reason = safe_str(e)
306 332 return HTTPNotAcceptable(reason)(environ, start_response)
307 333
308 334 if isinstance(result, str):
309 335 AUTH_TYPE.update(environ, 'basic')
310 336 REMOTE_USER.update(environ, result)
311 337 username = result
312 338 else:
313 339 return result.wsgi_application(environ, start_response)
314 340
315 341 # ==============================================================
316 342 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
317 343 # ==============================================================
318 344 user = User.get_by_username(username)
319 345 if not self.valid_and_active_user(user):
320 346 return HTTPForbidden()(environ, start_response)
321 347 username = user.username
322 348 user.update_lastactivity()
323 349 meta.Session().commit()
324 350
325 351 # check user attributes for password change flag
326 352 user_obj = user
327 353 if user_obj and user_obj.username != User.DEFAULT_USER and \
328 354 user_obj.user_data.get('force_password_change'):
329 355 reason = 'password change required'
330 356 log.debug('User not allowed to authenticate, %s', reason)
331 357 return HTTPNotAcceptable(reason)(environ, start_response)
332 358
333 359 # check permissions for this repository
334 360 perm = self._check_permission(
335 361 action, user, self.repo_name, ip_addr)
336 362 if not perm:
337 363 return HTTPForbidden()(environ, start_response)
338 364
339 365 # extras are injected into UI object and later available
340 366 # in hooks executed by rhodecode
341 367 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
342 368 extras = vcs_operation_context(
343 369 environ, repo_name=self.repo_name, username=username,
344 370 action=action, scm=self.SCM,
345 371 check_locking=check_locking)
346 372
347 373 # ======================================================================
348 374 # REQUEST HANDLING
349 375 # ======================================================================
350 376 str_repo_name = safe_str(self.repo_name)
351 377 repo_path = os.path.join(
352 378 safe_str(self.basepath), safe_str(self.vcs_repo_name))
353 379 log.debug('Repository path is %s', repo_path)
354 380
355 381 fix_PATH()
356 382
357 383 log.info(
358 384 '%s action on %s repo "%s" by "%s" from %s',
359 385 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
360 386
361 387 return self._generate_vcs_response(
362 388 environ, start_response, repo_path, self.url_repo_name, extras, action)
363 389
364 390 @initialize_generator
365 391 def _generate_vcs_response(
366 392 self, environ, start_response, repo_path, repo_name, extras,
367 393 action):
368 394 """
369 395 Returns a generator for the response content.
370 396
371 397 This method is implemented as a generator, so that it can trigger
372 398 the cache validation after all content sent back to the client. It
373 399 also handles the locking exceptions which will be triggered when
374 400 the first chunk is produced by the underlying WSGI application.
375 401 """
376 402 callback_daemon, extras = self._prepare_callback_daemon(extras)
377 403 config = self._create_config(extras, self.acl_repo_name)
378 404 log.debug('HOOKS extras is %s', extras)
379 405 app = self._create_wsgi_app(repo_path, repo_name, config)
380 406
381 407 try:
382 408 with callback_daemon:
383 409 try:
384 410 response = app(environ, start_response)
385 411 finally:
386 412 # This statement works together with the decorator
387 413 # "initialize_generator" above. The decorator ensures that
388 414 # we hit the first yield statement before the generator is
389 415 # returned back to the WSGI server. This is needed to
390 416 # ensure that the call to "app" above triggers the
391 417 # needed callback to "start_response" before the
392 418 # generator is actually used.
393 419 yield "__init__"
394 420
395 421 for chunk in response:
396 422 yield chunk
397 423 except Exception as exc:
398 424 # TODO: johbo: Improve "translating" back the exception.
399 425 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
400 426 exc = HTTPLockedRC(*exc.args)
401 427 _code = rhodecode.CONFIG.get('lock_ret_code')
402 428 log.debug('Repository LOCKED ret code %s!', (_code,))
403 429 elif getattr(exc, '_vcs_kind', None) == 'requirement':
404 430 log.debug(
405 431 'Repository requires features unknown to this Mercurial')
406 432 exc = HTTPRequirementError(*exc.args)
407 433 else:
408 434 raise
409 435
410 436 for chunk in exc(environ, start_response):
411 437 yield chunk
412 438 finally:
413 439 # invalidate cache on push
414 440 try:
415 441 if action == 'push':
416 442 self._invalidate_cache(repo_name)
417 443 finally:
418 444 meta.Session.remove()
419 445
420 446 def _get_repository_name(self, environ):
421 447 """Get repository name out of the environmnent
422 448
423 449 :param environ: WSGI environment
424 450 """
425 451 raise NotImplementedError()
426 452
427 453 def _get_action(self, environ):
428 454 """Map request commands into a pull or push command.
429 455
430 456 :param environ: WSGI environment
431 457 """
432 458 raise NotImplementedError()
433 459
434 460 def _create_wsgi_app(self, repo_path, repo_name, config):
435 461 """Return the WSGI app that will finally handle the request."""
436 462 raise NotImplementedError()
437 463
438 464 def _create_config(self, extras, repo_name):
439 465 """Create a Pyro safe config representation."""
440 466 raise NotImplementedError()
441 467
442 468 def _prepare_callback_daemon(self, extras):
443 469 return prepare_callback_daemon(
444 470 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
445 471 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
446 472
447 473
448 474 def _should_check_locking(query_string):
449 475 # this is kind of hacky, but due to how mercurial handles client-server
450 476 # server see all operation on commit; bookmarks, phases and
451 477 # obsolescence marker in different transaction, we don't want to check
452 478 # locking on those
453 479 return query_string not in ['cmd=listkeys']
@@ -1,226 +1,211 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 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 import gzip
22 22 import re
23 23 import shutil
24 24 import logging
25 25 import tempfile
26 26 import urlparse
27 27
28 28 from webob.exc import HTTPNotFound
29 29
30 30 import rhodecode
31 31 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
32 32 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
33 33 from rhodecode.lib.middleware.simplehg import SimpleHg
34 34 from rhodecode.lib.middleware.simplesvn import SimpleSvn
35 35 from rhodecode.model.settings import VcsSettingsModel
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 def is_git(environ):
41 41 """
42 42 Returns True if requests should be handled by GIT wsgi middleware
43 43 """
44 44 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
45 45 log.debug(
46 46 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
47 47 is_git_path is not None)
48 48
49 49 return is_git_path
50 50
51 51
52 52 def is_hg(environ):
53 53 """
54 54 Returns True if requests target is mercurial server - header
55 55 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
56 56 """
57 57 is_hg_path = False
58 58
59 59 http_accept = environ.get('HTTP_ACCEPT')
60 60
61 61 if http_accept and http_accept.startswith('application/mercurial'):
62 62 query = urlparse.parse_qs(environ['QUERY_STRING'])
63 63 if 'cmd' in query:
64 64 is_hg_path = True
65 65
66 66 log.debug(
67 67 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
68 68 is_hg_path)
69 69
70 70 return is_hg_path
71 71
72 72
73 73 def is_svn(environ):
74 74 """
75 75 Returns True if requests target is Subversion server
76 76 """
77 77 http_dav = environ.get('HTTP_DAV', '')
78 78 magic_path_segment = rhodecode.CONFIG.get(
79 79 'rhodecode_subversion_magic_path', '/!svn')
80 80 is_svn_path = (
81 81 'subversion' in http_dav or
82 82 magic_path_segment in environ['PATH_INFO'])
83 83 log.debug(
84 84 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
85 85 is_svn_path)
86 86
87 87 return is_svn_path
88 88
89 89
90 90 class GunzipMiddleware(object):
91 91 """
92 92 WSGI middleware that unzips gzip-encoded requests before
93 93 passing on to the underlying application.
94 94 """
95 95
96 96 def __init__(self, application):
97 97 self.app = application
98 98
99 99 def __call__(self, environ, start_response):
100 100 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
101 101
102 102 if b'gzip' in accepts_encoding_header:
103 103 log.debug('gzip detected, now running gunzip wrapper')
104 104 wsgi_input = environ['wsgi.input']
105 105
106 106 if not hasattr(environ['wsgi.input'], 'seek'):
107 107 # The gzip implementation in the standard library of Python 2.x
108 108 # requires the '.seek()' and '.tell()' methods to be available
109 109 # on the input stream. Read the data into a temporary file to
110 110 # work around this limitation.
111 111
112 112 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
113 113 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
114 114 wsgi_input.seek(0)
115 115
116 116 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
117 117 # since we "Ungzipped" the content we say now it's no longer gzip
118 118 # content encoding
119 119 del environ['HTTP_CONTENT_ENCODING']
120 120
121 121 # content length has changes ? or i'm not sure
122 122 if 'CONTENT_LENGTH' in environ:
123 123 del environ['CONTENT_LENGTH']
124 124 else:
125 125 log.debug('content not gzipped, gzipMiddleware passing '
126 126 'request further')
127 127 return self.app(environ, start_response)
128 128
129 129
130 130 class VCSMiddleware(object):
131 131
132 132 def __init__(self, app, config, appenlight_client, registry):
133 133 self.application = app
134 134 self.config = config
135 135 self.appenlight_client = appenlight_client
136 136 self.registry = registry
137 137 self.use_gzip = True
138 138 # order in which we check the middlewares, based on vcs.backends config
139 139 self.check_middlewares = config['vcs.backends']
140 140 self.checks = {
141 141 'hg': (is_hg, SimpleHg),
142 142 'git': (is_git, SimpleGit),
143 143 'svn': (is_svn, SimpleSvn),
144 144 }
145 145
146 146 def vcs_config(self, repo_name=None):
147 147 """
148 148 returns serialized VcsSettings
149 149 """
150 150 return VcsSettingsModel(repo=repo_name).get_ui_settings_as_config_obj()
151 151
152 152 def wrap_in_gzip_if_enabled(self, app, config):
153 153 if self.use_gzip:
154 154 app = GunzipMiddleware(app)
155 155 return app
156 156
157 157 def _get_handler_app(self, environ):
158 158 app = None
159 159 log.debug('Checking vcs types in order: %r', self.check_middlewares)
160 160 for vcs_type in self.check_middlewares:
161 161 vcs_check, handler = self.checks[vcs_type]
162 162 if vcs_check(environ):
163 163 log.debug(
164 164 'Found VCS Middleware to handle the request %s', handler)
165 165 app = handler(self.application, self.config, self.registry)
166 166 break
167 167
168 168 return app
169 169
170 170 def __call__(self, environ, start_response):
171 171 # check if we handle one of interesting protocols, optionally extract
172 172 # specific vcsSettings and allow changes of how things are wrapped
173 173 vcs_handler = self._get_handler_app(environ)
174 174 if vcs_handler:
175 175 # translate the _REPO_ID into real repo NAME for usage
176 176 # in middleware
177 177 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
178 repo_name = vcs_handler._get_repository_name(environ)
179 178
180 acl_repo_name = repo_name
181 vcs_repo_name = repo_name
182 url_repo_name = repo_name
183 pr_id = None
184
185 pr_regex = re.compile(
186 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/'
187 '(?P<repo_name>[\w-]+)'
188 '/pull-request/(?P<pr_id>\d+)/repository')
189 match = pr_regex.match(repo_name)
190 if match:
191 match_dict = match.groupdict()
192 pr_id = match_dict.get('pr_id')
193 acl_repo_name = '{base_name}/{repo_name}'.format(**match_dict)
194 vcs_repo_name = '{base_name}/.__shadow_{repo_name}_pr-{pr_id}'.format(
195 **match_dict)
196
179 # Set repo names for permission checks, vcs and web interaction.
180 vcs_handler.set_repo_names(environ)
197 181 log.debug('repo_names %s', {
198 'acl_repo_name': acl_repo_name,
199 'vcs_repo_name': vcs_repo_name,
200 'url_repo_name': url_repo_name,
182 'acl_repo_name': vcs_handler.acl_repo_name,
183 'vcs_repo_name': vcs_handler.vcs_repo_name,
184 'url_repo_name': vcs_handler.url_repo_name,
201 185 })
202 log.debug('pull_request %s', pr_id)
186 log.debug('pull_request %s', vcs_handler.pr_id)
203 187
204 188 # check for type, presence in database and on filesystem
205 189 if not vcs_handler.is_valid_and_existing_repo(
206 acl_repo_name, vcs_handler.basepath, vcs_handler.SCM):
190 vcs_handler.acl_repo_name,
191 vcs_handler.basepath,
192 vcs_handler.SCM):
207 193 return HTTPNotFound()(environ, start_response)
208 194
209 195 # TODO: johbo: Needed for the Pyro4 backend and Mercurial only.
210 196 # Remove once we fully switched to the HTTP backend.
211 environ['REPO_NAME'] = url_repo_name
197 environ['REPO_NAME'] = vcs_handler.url_repo_name
212 198
213 # register repo_name and it's config back to the handler
214 vcs_handler.acl_repo_name = acl_repo_name
215 vcs_handler.url_repo_name = url_repo_name
216 vcs_handler.vcs_repo_name = vcs_repo_name
217 vcs_handler.pr_id = pr_id
218 vcs_handler.repo_vcs_config = self.vcs_config(acl_repo_name)
199 # register repo config back to the handler
200 vcs_handler.repo_vcs_config = self.vcs_config(
201 vcs_handler.acl_repo_name)
219 202
203 # Wrap handler in middlewares if they are enabled.
220 204 vcs_handler = self.wrap_in_gzip_if_enabled(
221 205 vcs_handler, self.config)
222 206 vcs_handler, _ = wrap_in_appenlight_if_enabled(
223 207 vcs_handler, self.config, self.appenlight_client)
208
224 209 return vcs_handler(environ, start_response)
225 210
226 211 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now