##// END OF EJS Templates
fix(svn): fix the problems of reading txn_id when used gunicorn/waitress + traefik/no-traefik combinations
super-admin -
r5207:9410d3a2 default
parent child Browse files
Show More
@@ -1,689 +1,700 b''
1
1
2
2
3 # Copyright (C) 2014-2023 RhodeCode GmbH
3 # Copyright (C) 2014-2023 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 re
27 import re
28 import io
28 import io
29 import logging
29 import logging
30 import importlib
30 import importlib
31 from functools import wraps
31 from functools import wraps
32 from lxml import etree
32 from lxml import etree
33
33
34 import time
34 import time
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from pyramid.httpexceptions import (
37 from pyramid.httpexceptions import (
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
38 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
39 from zope.cachedescriptors.property import Lazy as LazyProperty
39 from zope.cachedescriptors.property import Lazy as LazyProperty
40
40
41 import rhodecode
41 import rhodecode
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
42 from rhodecode.authentication.base import authenticate, VCS_TYPE, loadplugin
43 from rhodecode.lib import rc_cache
43 from rhodecode.lib import rc_cache
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
44 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
45 from rhodecode.lib.base import (
45 from rhodecode.lib.base import (
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
46 BasicAuth, get_ip_addr, get_user_agent, vcs_operation_context)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
47 from rhodecode.lib.exceptions import (UserCreationError, NotAllowedToCreateUserError)
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
48 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
49 from rhodecode.lib.middleware import appenlight
49 from rhodecode.lib.middleware import appenlight
50 from rhodecode.lib.middleware.utils import scm_app_http
50 from rhodecode.lib.middleware.utils import scm_app_http
51 from rhodecode.lib.str_utils import safe_bytes
51 from rhodecode.lib.str_utils import safe_bytes
52 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
52 from rhodecode.lib.utils import is_valid_repo, SLUG_RE
53 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
53 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
54 from rhodecode.lib.vcs.conf import settings as vcs_settings
54 from rhodecode.lib.vcs.conf import settings as vcs_settings
55 from rhodecode.lib.vcs.backends import base
55 from rhodecode.lib.vcs.backends import base
56
56
57 from rhodecode.model import meta
57 from rhodecode.model import meta
58 from rhodecode.model.db import User, Repository, PullRequest
58 from rhodecode.model.db import User, Repository, PullRequest
59 from rhodecode.model.scm import ScmModel
59 from rhodecode.model.scm import ScmModel
60 from rhodecode.model.pull_request import PullRequestModel
60 from rhodecode.model.pull_request import PullRequestModel
61 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
61 from rhodecode.model.settings import SettingsModel, VcsSettingsModel
62
62
63 log = logging.getLogger(__name__)
63 log = logging.getLogger(__name__)
64
64
65
65
66 def extract_svn_txn_id(acl_repo_name, data):
66 def extract_svn_txn_id(acl_repo_name, data):
67 """
67 """
68 Helper method for extraction of svn txn_id from submitted XML data during
68 Helper method for extraction of svn txn_id from submitted XML data during
69 POST operations
69 POST operations
70 """
70 """
71 try:
71 try:
72 root = etree.fromstring(data)
72 root = etree.fromstring(data)
73 pat = re.compile(r'/txn/(?P<txn_id>.*)')
73 pat = re.compile(r'/txn/(?P<txn_id>.*)')
74 for el in root:
74 for el in root:
75 if el.tag == '{DAV:}source':
75 if el.tag == '{DAV:}source':
76 for sub_el in el:
76 for sub_el in el:
77 if sub_el.tag == '{DAV:}href':
77 if sub_el.tag == '{DAV:}href':
78 match = pat.search(sub_el.text)
78 match = pat.search(sub_el.text)
79 if match:
79 if match:
80 svn_tx_id = match.groupdict()['txn_id']
80 svn_tx_id = match.groupdict()['txn_id']
81 txn_id = rc_cache.utils.compute_key_from_params(
81 txn_id = rc_cache.utils.compute_key_from_params(
82 acl_repo_name, svn_tx_id)
82 acl_repo_name, svn_tx_id)
83 return txn_id
83 return txn_id
84 except Exception:
84 except Exception:
85 log.exception('Failed to extract txn_id')
85 log.exception('Failed to extract txn_id')
86
86
87
87
88 def initialize_generator(factory):
88 def initialize_generator(factory):
89 """
89 """
90 Initializes the returned generator by draining its first element.
90 Initializes the returned generator by draining its first element.
91
91
92 This can be used to give a generator an initializer, which is the code
92 This can be used to give a generator an initializer, which is the code
93 up to the first yield statement. This decorator enforces that the first
93 up to the first yield statement. This decorator enforces that the first
94 produced element has the value ``"__init__"`` to make its special
94 produced element has the value ``"__init__"`` to make its special
95 purpose very explicit in the using code.
95 purpose very explicit in the using code.
96 """
96 """
97
97
98 @wraps(factory)
98 @wraps(factory)
99 def wrapper(*args, **kwargs):
99 def wrapper(*args, **kwargs):
100 gen = factory(*args, **kwargs)
100 gen = factory(*args, **kwargs)
101 try:
101 try:
102 init = next(gen)
102 init = next(gen)
103 except StopIteration:
103 except StopIteration:
104 raise ValueError('Generator must yield at least one element.')
104 raise ValueError('Generator must yield at least one element.')
105 if init != "__init__":
105 if init != "__init__":
106 raise ValueError('First yielded element must be "__init__".')
106 raise ValueError('First yielded element must be "__init__".')
107 return gen
107 return gen
108 return wrapper
108 return wrapper
109
109
110
110
111 class SimpleVCS(object):
111 class SimpleVCS(object):
112 """Common functionality for SCM HTTP handlers."""
112 """Common functionality for SCM HTTP handlers."""
113
113
114 SCM = 'unknown'
114 SCM = 'unknown'
115
115
116 acl_repo_name = None
116 acl_repo_name = None
117 url_repo_name = None
117 url_repo_name = None
118 vcs_repo_name = None
118 vcs_repo_name = None
119 rc_extras = {}
119 rc_extras = {}
120
120
121 # We have to handle requests to shadow repositories different than requests
121 # We have to handle requests to shadow repositories different than requests
122 # to normal repositories. Therefore we have to distinguish them. To do this
122 # to normal repositories. Therefore we have to distinguish them. To do this
123 # we use this regex which will match only on URLs pointing to shadow
123 # we use this regex which will match only on URLs pointing to shadow
124 # repositories.
124 # repositories.
125 shadow_repo_re = re.compile(
125 shadow_repo_re = re.compile(
126 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
126 '(?P<groups>(?:{slug_pat}/)*)' # repo groups
127 '(?P<target>{slug_pat})/' # target repo
127 '(?P<target>{slug_pat})/' # target repo
128 'pull-request/(?P<pr_id>\\d+)/' # pull request
128 'pull-request/(?P<pr_id>\\d+)/' # pull request
129 'repository$' # shadow repo
129 'repository$' # shadow repo
130 .format(slug_pat=SLUG_RE.pattern))
130 .format(slug_pat=SLUG_RE.pattern))
131
131
132 def __init__(self, config, registry):
132 def __init__(self, config, registry):
133 self.registry = registry
133 self.registry = registry
134 self.config = config
134 self.config = config
135 # re-populated by specialized middleware
135 # re-populated by specialized middleware
136 self.repo_vcs_config = base.Config()
136 self.repo_vcs_config = base.Config()
137
137
138 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
138 rc_settings = SettingsModel().get_all_settings(cache=True, from_request=False)
139 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
139 realm = rc_settings.get('rhodecode_realm') or 'RhodeCode AUTH'
140
140
141 # authenticate this VCS request using authfunc
141 # authenticate this VCS request using authfunc
142 auth_ret_code_detection = \
142 auth_ret_code_detection = \
143 str2bool(self.config.get('auth_ret_code_detection', False))
143 str2bool(self.config.get('auth_ret_code_detection', False))
144 self.authenticate = BasicAuth(
144 self.authenticate = BasicAuth(
145 '', authenticate, registry, config.get('auth_ret_code'),
145 '', authenticate, registry, config.get('auth_ret_code'),
146 auth_ret_code_detection, rc_realm=realm)
146 auth_ret_code_detection, rc_realm=realm)
147 self.ip_addr = '0.0.0.0'
147 self.ip_addr = '0.0.0.0'
148
148
149 @LazyProperty
149 @LazyProperty
150 def global_vcs_config(self):
150 def global_vcs_config(self):
151 try:
151 try:
152 return VcsSettingsModel().get_ui_settings_as_config_obj()
152 return VcsSettingsModel().get_ui_settings_as_config_obj()
153 except Exception:
153 except Exception:
154 return base.Config()
154 return base.Config()
155
155
156 @property
156 @property
157 def base_path(self):
157 def base_path(self):
158 settings_path = self.repo_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
158 settings_path = self.repo_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
159
159
160 if not settings_path:
160 if not settings_path:
161 settings_path = self.global_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
161 settings_path = self.global_vcs_config.get(*VcsSettingsModel.PATH_SETTING)
162
162
163 if not settings_path:
163 if not settings_path:
164 # try, maybe we passed in explicitly as config option
164 # try, maybe we passed in explicitly as config option
165 settings_path = self.config.get('base_path')
165 settings_path = self.config.get('base_path')
166
166
167 if not settings_path:
167 if not settings_path:
168 raise ValueError('FATAL: base_path is empty')
168 raise ValueError('FATAL: base_path is empty')
169 return settings_path
169 return settings_path
170
170
171 def set_repo_names(self, environ):
171 def set_repo_names(self, environ):
172 """
172 """
173 This will populate the attributes acl_repo_name, url_repo_name,
173 This will populate the attributes acl_repo_name, url_repo_name,
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
174 vcs_repo_name and is_shadow_repo. In case of requests to normal (non
175 shadow) repositories all names are equal. In case of requests to a
175 shadow) repositories all names are equal. In case of requests to a
176 shadow repository the acl-name points to the target repo of the pull
176 shadow repository the acl-name points to the target repo of the pull
177 request and the vcs-name points to the shadow repo file system path.
177 request and the vcs-name points to the shadow repo file system path.
178 The url-name is always the URL used by the vcs client program.
178 The url-name is always the URL used by the vcs client program.
179
179
180 Example in case of a shadow repo:
180 Example in case of a shadow repo:
181 acl_repo_name = RepoGroup/MyRepo
181 acl_repo_name = RepoGroup/MyRepo
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
182 url_repo_name = RepoGroup/MyRepo/pull-request/3/repository
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
183 vcs_repo_name = /repo/base/path/RepoGroup/.__shadow_MyRepo_pr-3'
184 """
184 """
185 # First we set the repo name from URL for all attributes. This is the
185 # First we set the repo name from URL for all attributes. This is the
186 # default if handling normal (non shadow) repo requests.
186 # default if handling normal (non shadow) repo requests.
187 self.url_repo_name = self._get_repository_name(environ)
187 self.url_repo_name = self._get_repository_name(environ)
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
188 self.acl_repo_name = self.vcs_repo_name = self.url_repo_name
189 self.is_shadow_repo = False
189 self.is_shadow_repo = False
190
190
191 # Check if this is a request to a shadow repository.
191 # Check if this is a request to a shadow repository.
192 match = self.shadow_repo_re.match(self.url_repo_name)
192 match = self.shadow_repo_re.match(self.url_repo_name)
193 if match:
193 if match:
194 match_dict = match.groupdict()
194 match_dict = match.groupdict()
195
195
196 # Build acl repo name from regex match.
196 # Build acl repo name from regex match.
197 acl_repo_name = safe_str('{groups}{target}'.format(
197 acl_repo_name = safe_str('{groups}{target}'.format(
198 groups=match_dict['groups'] or '',
198 groups=match_dict['groups'] or '',
199 target=match_dict['target']))
199 target=match_dict['target']))
200
200
201 # Retrieve pull request instance by ID from regex match.
201 # Retrieve pull request instance by ID from regex match.
202 pull_request = PullRequest.get(match_dict['pr_id'])
202 pull_request = PullRequest.get(match_dict['pr_id'])
203
203
204 # Only proceed if we got a pull request and if acl repo name from
204 # Only proceed if we got a pull request and if acl repo name from
205 # URL equals the target repo name of the pull request.
205 # URL equals the target repo name of the pull request.
206 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
206 if pull_request and (acl_repo_name == pull_request.target_repo.repo_name):
207
207
208 # Get file system path to shadow repository.
208 # Get file system path to shadow repository.
209 workspace_id = PullRequestModel()._workspace_id(pull_request)
209 workspace_id = PullRequestModel()._workspace_id(pull_request)
210 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
210 vcs_repo_name = pull_request.target_repo.get_shadow_repository_path(workspace_id)
211
211
212 # Store names for later usage.
212 # Store names for later usage.
213 self.vcs_repo_name = vcs_repo_name
213 self.vcs_repo_name = vcs_repo_name
214 self.acl_repo_name = acl_repo_name
214 self.acl_repo_name = acl_repo_name
215 self.is_shadow_repo = True
215 self.is_shadow_repo = True
216
216
217 log.debug('Setting all VCS repository names: %s', {
217 log.debug('Setting all VCS repository names: %s', {
218 'acl_repo_name': self.acl_repo_name,
218 'acl_repo_name': self.acl_repo_name,
219 'url_repo_name': self.url_repo_name,
219 'url_repo_name': self.url_repo_name,
220 'vcs_repo_name': self.vcs_repo_name,
220 'vcs_repo_name': self.vcs_repo_name,
221 })
221 })
222
222
223 @property
223 @property
224 def scm_app(self):
224 def scm_app(self):
225 custom_implementation = self.config['vcs.scm_app_implementation']
225 custom_implementation = self.config['vcs.scm_app_implementation']
226 if custom_implementation == 'http':
226 if custom_implementation == 'http':
227 log.debug('Using HTTP implementation of scm app.')
227 log.debug('Using HTTP implementation of scm app.')
228 scm_app_impl = scm_app_http
228 scm_app_impl = scm_app_http
229 else:
229 else:
230 log.debug('Using custom implementation of scm_app: "{}"'.format(
230 log.debug('Using custom implementation of scm_app: "{}"'.format(
231 custom_implementation))
231 custom_implementation))
232 scm_app_impl = importlib.import_module(custom_implementation)
232 scm_app_impl = importlib.import_module(custom_implementation)
233 return scm_app_impl
233 return scm_app_impl
234
234
235 def _get_by_id(self, repo_name):
235 def _get_by_id(self, repo_name):
236 """
236 """
237 Gets a special pattern _<ID> from clone url and tries to replace it
237 Gets a special pattern _<ID> from clone url and tries to replace it
238 with a repository_name for support of _<ID> non changeable urls
238 with a repository_name for support of _<ID> non changeable urls
239 """
239 """
240
240
241 data = repo_name.split('/')
241 data = repo_name.split('/')
242 if len(data) >= 2:
242 if len(data) >= 2:
243 from rhodecode.model.repo import RepoModel
243 from rhodecode.model.repo import RepoModel
244 by_id_match = RepoModel().get_repo_by_id(repo_name)
244 by_id_match = RepoModel().get_repo_by_id(repo_name)
245 if by_id_match:
245 if by_id_match:
246 data[1] = by_id_match.repo_name
246 data[1] = by_id_match.repo_name
247
247
248 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
248 # Because PEP-3333-WSGI uses bytes-tunneled-in-latin-1 as PATH_INFO
249 # and we use this data
249 # and we use this data
250 maybe_new_path = '/'.join(data)
250 maybe_new_path = '/'.join(data)
251 return safe_bytes(maybe_new_path).decode('latin1')
251 return safe_bytes(maybe_new_path).decode('latin1')
252
252
253 def _invalidate_cache(self, repo_name):
253 def _invalidate_cache(self, repo_name):
254 """
254 """
255 Set's cache for this repository for invalidation on next access
255 Set's cache for this repository for invalidation on next access
256
256
257 :param repo_name: full repo name, also a cache key
257 :param repo_name: full repo name, also a cache key
258 """
258 """
259 ScmModel().mark_for_invalidation(repo_name)
259 ScmModel().mark_for_invalidation(repo_name)
260
260
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
261 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
262 db_repo = Repository.get_by_repo_name(repo_name)
262 db_repo = Repository.get_by_repo_name(repo_name)
263 if not db_repo:
263 if not db_repo:
264 log.debug('Repository `%s` not found inside the database.',
264 log.debug('Repository `%s` not found inside the database.',
265 repo_name)
265 repo_name)
266 return False
266 return False
267
267
268 if db_repo.repo_type != scm_type:
268 if db_repo.repo_type != scm_type:
269 log.warning(
269 log.warning(
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
270 'Repository `%s` have incorrect scm_type, expected %s got %s',
271 repo_name, db_repo.repo_type, scm_type)
271 repo_name, db_repo.repo_type, scm_type)
272 return False
272 return False
273
273
274 config = db_repo._config
274 config = db_repo._config
275 config.set('extensions', 'largefiles', '')
275 config.set('extensions', 'largefiles', '')
276 return is_valid_repo(
276 return is_valid_repo(
277 repo_name, base_path,
277 repo_name, base_path,
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
278 explicit_scm=scm_type, expect_scm=scm_type, config=config)
279
279
280 def valid_and_active_user(self, user):
280 def valid_and_active_user(self, user):
281 """
281 """
282 Checks if that user is not empty, and if it's actually object it checks
282 Checks if that user is not empty, and if it's actually object it checks
283 if he's active.
283 if he's active.
284
284
285 :param user: user object or None
285 :param user: user object or None
286 :return: boolean
286 :return: boolean
287 """
287 """
288 if user is None:
288 if user is None:
289 return False
289 return False
290
290
291 elif user.active:
291 elif user.active:
292 return True
292 return True
293
293
294 return False
294 return False
295
295
296 @property
296 @property
297 def is_shadow_repo_dir(self):
297 def is_shadow_repo_dir(self):
298 return os.path.isdir(self.vcs_repo_name)
298 return os.path.isdir(self.vcs_repo_name)
299
299
300 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
300 def _check_permission(self, action, user, auth_user, repo_name, ip_addr=None,
301 plugin_id='', plugin_cache_active=False, cache_ttl=0):
301 plugin_id='', plugin_cache_active=False, cache_ttl=0):
302 """
302 """
303 Checks permissions using action (push/pull) user and repository
303 Checks permissions using action (push/pull) user and repository
304 name. If plugin_cache and ttl is set it will use the plugin which
304 name. If plugin_cache and ttl is set it will use the plugin which
305 authenticated the user to store the cached permissions result for N
305 authenticated the user to store the cached permissions result for N
306 amount of seconds as in cache_ttl
306 amount of seconds as in cache_ttl
307
307
308 :param action: push or pull action
308 :param action: push or pull action
309 :param user: user instance
309 :param user: user instance
310 :param repo_name: repository name
310 :param repo_name: repository name
311 """
311 """
312
312
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
313 log.debug('AUTH_CACHE_TTL for permissions `%s` active: %s (TTL: %s)',
314 plugin_id, plugin_cache_active, cache_ttl)
314 plugin_id, plugin_cache_active, cache_ttl)
315
315
316 user_id = user.user_id
316 user_id = user.user_id
317 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
317 cache_namespace_uid = f'cache_user_auth.{rc_cache.PERMISSIONS_CACHE_VER}.{user_id}'
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
318 region = rc_cache.get_or_create_region('cache_perms', cache_namespace_uid)
319
319
320 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
320 @region.conditional_cache_on_arguments(namespace=cache_namespace_uid,
321 expiration_time=cache_ttl,
321 expiration_time=cache_ttl,
322 condition=plugin_cache_active)
322 condition=plugin_cache_active)
323 def compute_perm_vcs(
323 def compute_perm_vcs(
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
324 cache_name, plugin_id, action, user_id, repo_name, ip_addr):
325
325
326 log.debug('auth: calculating permission access now...')
326 log.debug('auth: calculating permission access now...')
327 # check IP
327 # check IP
328 inherit = user.inherit_default_permissions
328 inherit = user.inherit_default_permissions
329 ip_allowed = AuthUser.check_ip_allowed(
329 ip_allowed = AuthUser.check_ip_allowed(
330 user_id, ip_addr, inherit_from_default=inherit)
330 user_id, ip_addr, inherit_from_default=inherit)
331 if ip_allowed:
331 if ip_allowed:
332 log.info('Access for IP:%s allowed', ip_addr)
332 log.info('Access for IP:%s allowed', ip_addr)
333 else:
333 else:
334 return False
334 return False
335
335
336 if action == 'push':
336 if action == 'push':
337 perms = ('repository.write', 'repository.admin')
337 perms = ('repository.write', 'repository.admin')
338 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
338 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
339 return False
339 return False
340
340
341 else:
341 else:
342 # any other action need at least read permission
342 # any other action need at least read permission
343 perms = (
343 perms = (
344 'repository.read', 'repository.write', 'repository.admin')
344 'repository.read', 'repository.write', 'repository.admin')
345 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
345 if not HasPermissionAnyMiddleware(*perms)(auth_user, repo_name):
346 return False
346 return False
347
347
348 return True
348 return True
349
349
350 start = time.time()
350 start = time.time()
351 log.debug('Running plugin `%s` permissions check', plugin_id)
351 log.debug('Running plugin `%s` permissions check', plugin_id)
352
352
353 # for environ based auth, password can be empty, but then the validation is
353 # for environ based auth, password can be empty, but then the validation is
354 # on the server that fills in the env data needed for authentication
354 # on the server that fills in the env data needed for authentication
355 perm_result = compute_perm_vcs(
355 perm_result = compute_perm_vcs(
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
356 'vcs_permissions', plugin_id, action, user.user_id, repo_name, ip_addr)
357
357
358 auth_time = time.time() - start
358 auth_time = time.time() - start
359 log.debug('Permissions for plugin `%s` completed in %.4fs, '
359 log.debug('Permissions for plugin `%s` completed in %.4fs, '
360 'expiration time of fetched cache %.1fs.',
360 'expiration time of fetched cache %.1fs.',
361 plugin_id, auth_time, cache_ttl)
361 plugin_id, auth_time, cache_ttl)
362
362
363 return perm_result
363 return perm_result
364
364
365 def _get_http_scheme(self, environ):
365 def _get_http_scheme(self, environ):
366 try:
366 try:
367 return environ['wsgi.url_scheme']
367 return environ['wsgi.url_scheme']
368 except Exception:
368 except Exception:
369 log.exception('Failed to read http scheme')
369 log.exception('Failed to read http scheme')
370 return 'http'
370 return 'http'
371
371
372 def _check_ssl(self, environ, start_response):
372 def _check_ssl(self, environ, start_response):
373 """
373 """
374 Checks the SSL check flag and returns False if SSL is not present
374 Checks the SSL check flag and returns False if SSL is not present
375 and required True otherwise
375 and required True otherwise
376 """
376 """
377 org_proto = environ['wsgi._org_proto']
377 org_proto = environ['wsgi._org_proto']
378 # check if we have SSL required ! if not it's a bad request !
378 # check if we have SSL required ! if not it's a bad request !
379 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
379 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
380 if require_ssl and org_proto == 'http':
380 if require_ssl and org_proto == 'http':
381 log.debug(
381 log.debug(
382 'Bad request: detected protocol is `%s` and '
382 'Bad request: detected protocol is `%s` and '
383 'SSL/HTTPS is required.', org_proto)
383 'SSL/HTTPS is required.', org_proto)
384 return False
384 return False
385 return True
385 return True
386
386
387 def _get_default_cache_ttl(self):
387 def _get_default_cache_ttl(self):
388 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
388 # take AUTH_CACHE_TTL from the `rhodecode` auth plugin
389 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
389 plugin = loadplugin('egg:rhodecode-enterprise-ce#rhodecode')
390 plugin_settings = plugin.get_settings()
390 plugin_settings = plugin.get_settings()
391 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
391 plugin_cache_active, cache_ttl = plugin.get_ttl_cache(
392 plugin_settings) or (False, 0)
392 plugin_settings) or (False, 0)
393 return plugin_cache_active, cache_ttl
393 return plugin_cache_active, cache_ttl
394
394
395 def __call__(self, environ, start_response):
395 def __call__(self, environ, start_response):
396 try:
396 try:
397 return self._handle_request(environ, start_response)
397 return self._handle_request(environ, start_response)
398 except Exception:
398 except Exception:
399 log.exception("Exception while handling request")
399 log.exception("Exception while handling request")
400 appenlight.track_exception(environ)
400 appenlight.track_exception(environ)
401 return HTTPInternalServerError()(environ, start_response)
401 return HTTPInternalServerError()(environ, start_response)
402 finally:
402 finally:
403 meta.Session.remove()
403 meta.Session.remove()
404
404
405 def _handle_request(self, environ, start_response):
405 def _handle_request(self, environ, start_response):
406 if not self._check_ssl(environ, start_response):
406 if not self._check_ssl(environ, start_response):
407 reason = ('SSL required, while RhodeCode was unable '
407 reason = ('SSL required, while RhodeCode was unable '
408 'to detect this as SSL request')
408 'to detect this as SSL request')
409 log.debug('User not allowed to proceed, %s', reason)
409 log.debug('User not allowed to proceed, %s', reason)
410 return HTTPNotAcceptable(reason)(environ, start_response)
410 return HTTPNotAcceptable(reason)(environ, start_response)
411
411
412 if not self.url_repo_name:
412 if not self.url_repo_name:
413 log.warning('Repository name is empty: %s', self.url_repo_name)
413 log.warning('Repository name is empty: %s', self.url_repo_name)
414 # failed to get repo name, we fail now
414 # failed to get repo name, we fail now
415 return HTTPNotFound()(environ, start_response)
415 return HTTPNotFound()(environ, start_response)
416 log.debug('Extracted repo name is %s', self.url_repo_name)
416 log.debug('Extracted repo name is %s', self.url_repo_name)
417
417
418 ip_addr = get_ip_addr(environ)
418 ip_addr = get_ip_addr(environ)
419 user_agent = get_user_agent(environ)
419 user_agent = get_user_agent(environ)
420 username = None
420 username = None
421
421
422 # skip passing error to error controller
422 # skip passing error to error controller
423 environ['pylons.status_code_redirect'] = True
423 environ['pylons.status_code_redirect'] = True
424
424
425 # ======================================================================
425 # ======================================================================
426 # GET ACTION PULL or PUSH
426 # GET ACTION PULL or PUSH
427 # ======================================================================
427 # ======================================================================
428 action = self._get_action(environ)
428 action = self._get_action(environ)
429
429
430 # ======================================================================
430 # ======================================================================
431 # Check if this is a request to a shadow repository of a pull request.
431 # Check if this is a request to a shadow repository of a pull request.
432 # In this case only pull action is allowed.
432 # In this case only pull action is allowed.
433 # ======================================================================
433 # ======================================================================
434 if self.is_shadow_repo and action != 'pull':
434 if self.is_shadow_repo and action != 'pull':
435 reason = 'Only pull action is allowed for shadow repositories.'
435 reason = 'Only pull action is allowed for shadow repositories.'
436 log.debug('User not allowed to proceed, %s', reason)
436 log.debug('User not allowed to proceed, %s', reason)
437 return HTTPNotAcceptable(reason)(environ, start_response)
437 return HTTPNotAcceptable(reason)(environ, start_response)
438
438
439 # Check if the shadow repo actually exists, in case someone refers
439 # Check if the shadow repo actually exists, in case someone refers
440 # to it, and it has been deleted because of successful merge.
440 # to it, and it has been deleted because of successful merge.
441 if self.is_shadow_repo and not self.is_shadow_repo_dir:
441 if self.is_shadow_repo and not self.is_shadow_repo_dir:
442 log.debug(
442 log.debug(
443 'Shadow repo detected, and shadow repo dir `%s` is missing',
443 'Shadow repo detected, and shadow repo dir `%s` is missing',
444 self.is_shadow_repo_dir)
444 self.is_shadow_repo_dir)
445 return HTTPNotFound()(environ, start_response)
445 return HTTPNotFound()(environ, start_response)
446
446
447 # ======================================================================
447 # ======================================================================
448 # CHECK ANONYMOUS PERMISSION
448 # CHECK ANONYMOUS PERMISSION
449 # ======================================================================
449 # ======================================================================
450 detect_force_push = False
450 detect_force_push = False
451 check_branch_perms = False
451 check_branch_perms = False
452 if action in ['pull', 'push']:
452 if action in ['pull', 'push']:
453 user_obj = anonymous_user = User.get_default_user()
453 user_obj = anonymous_user = User.get_default_user()
454 auth_user = user_obj.AuthUser()
454 auth_user = user_obj.AuthUser()
455 username = anonymous_user.username
455 username = anonymous_user.username
456 if anonymous_user.active:
456 if anonymous_user.active:
457 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
457 plugin_cache_active, cache_ttl = self._get_default_cache_ttl()
458 # ONLY check permissions if the user is activated
458 # ONLY check permissions if the user is activated
459 anonymous_perm = self._check_permission(
459 anonymous_perm = self._check_permission(
460 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
460 action, anonymous_user, auth_user, self.acl_repo_name, ip_addr,
461 plugin_id='anonymous_access',
461 plugin_id='anonymous_access',
462 plugin_cache_active=plugin_cache_active,
462 plugin_cache_active=plugin_cache_active,
463 cache_ttl=cache_ttl,
463 cache_ttl=cache_ttl,
464 )
464 )
465 else:
465 else:
466 anonymous_perm = False
466 anonymous_perm = False
467
467
468 if not anonymous_user.active or not anonymous_perm:
468 if not anonymous_user.active or not anonymous_perm:
469 if not anonymous_user.active:
469 if not anonymous_user.active:
470 log.debug('Anonymous access is disabled, running '
470 log.debug('Anonymous access is disabled, running '
471 'authentication')
471 'authentication')
472
472
473 if not anonymous_perm:
473 if not anonymous_perm:
474 log.debug('Not enough credentials to access repo: `%s` '
474 log.debug('Not enough credentials to access repo: `%s` '
475 'repository as anonymous user', self.acl_repo_name)
475 'repository as anonymous user', self.acl_repo_name)
476
476
477
477
478 username = None
478 username = None
479 # ==============================================================
479 # ==============================================================
480 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
480 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
481 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
481 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
482 # ==============================================================
482 # ==============================================================
483
483
484 # try to auth based on environ, container auth methods
484 # try to auth based on environ, container auth methods
485 log.debug('Running PRE-AUTH for container|headers based authentication')
485 log.debug('Running PRE-AUTH for container|headers based authentication')
486
486
487 # headers auth, by just reading special headers and bypass the auth with user/passwd
487 # headers auth, by just reading special headers and bypass the auth with user/passwd
488 pre_auth = authenticate(
488 pre_auth = authenticate(
489 '', '', environ, VCS_TYPE, registry=self.registry,
489 '', '', environ, VCS_TYPE, registry=self.registry,
490 acl_repo_name=self.acl_repo_name)
490 acl_repo_name=self.acl_repo_name)
491
491
492 if pre_auth and pre_auth.get('username'):
492 if pre_auth and pre_auth.get('username'):
493 username = pre_auth['username']
493 username = pre_auth['username']
494 log.debug('PRE-AUTH got `%s` as username', username)
494 log.debug('PRE-AUTH got `%s` as username', username)
495 if pre_auth:
495 if pre_auth:
496 log.debug('PRE-AUTH successful from %s',
496 log.debug('PRE-AUTH successful from %s',
497 pre_auth.get('auth_data', {}).get('_plugin'))
497 pre_auth.get('auth_data', {}).get('_plugin'))
498
498
499 # If not authenticated by the container, running basic auth
499 # If not authenticated by the container, running basic auth
500 # before inject the calling repo_name for special scope checks
500 # before inject the calling repo_name for special scope checks
501 self.authenticate.acl_repo_name = self.acl_repo_name
501 self.authenticate.acl_repo_name = self.acl_repo_name
502
502
503 plugin_cache_active, cache_ttl = False, 0
503 plugin_cache_active, cache_ttl = False, 0
504 plugin = None
504 plugin = None
505
505
506 # regular auth chain
506 # regular auth chain
507 if not username:
507 if not username:
508 self.authenticate.realm = self.authenticate.get_rc_realm()
508 self.authenticate.realm = self.authenticate.get_rc_realm()
509
509
510 try:
510 try:
511 auth_result = self.authenticate(environ)
511 auth_result = self.authenticate(environ)
512 except (UserCreationError, NotAllowedToCreateUserError) as e:
512 except (UserCreationError, NotAllowedToCreateUserError) as e:
513 log.error(e)
513 log.error(e)
514 reason = safe_str(e)
514 reason = safe_str(e)
515 return HTTPNotAcceptable(reason)(environ, start_response)
515 return HTTPNotAcceptable(reason)(environ, start_response)
516
516
517 if isinstance(auth_result, dict):
517 if isinstance(auth_result, dict):
518 AUTH_TYPE.update(environ, 'basic')
518 AUTH_TYPE.update(environ, 'basic')
519 REMOTE_USER.update(environ, auth_result['username'])
519 REMOTE_USER.update(environ, auth_result['username'])
520 username = auth_result['username']
520 username = auth_result['username']
521 plugin = auth_result.get('auth_data', {}).get('_plugin')
521 plugin = auth_result.get('auth_data', {}).get('_plugin')
522 log.info(
522 log.info(
523 'MAIN-AUTH successful for user `%s` from %s plugin',
523 'MAIN-AUTH successful for user `%s` from %s plugin',
524 username, plugin)
524 username, plugin)
525
525
526 plugin_cache_active, cache_ttl = auth_result.get(
526 plugin_cache_active, cache_ttl = auth_result.get(
527 'auth_data', {}).get('_ttl_cache') or (False, 0)
527 'auth_data', {}).get('_ttl_cache') or (False, 0)
528 else:
528 else:
529 return auth_result.wsgi_application(environ, start_response)
529 return auth_result.wsgi_application(environ, start_response)
530
530
531 # ==============================================================
531 # ==============================================================
532 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
532 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
533 # ==============================================================
533 # ==============================================================
534 user = User.get_by_username(username)
534 user = User.get_by_username(username)
535 if not self.valid_and_active_user(user):
535 if not self.valid_and_active_user(user):
536 return HTTPForbidden()(environ, start_response)
536 return HTTPForbidden()(environ, start_response)
537 username = user.username
537 username = user.username
538 user_id = user.user_id
538 user_id = user.user_id
539
539
540 # check user attributes for password change flag
540 # check user attributes for password change flag
541 user_obj = user
541 user_obj = user
542 auth_user = user_obj.AuthUser()
542 auth_user = user_obj.AuthUser()
543 if user_obj and user_obj.username != User.DEFAULT_USER and \
543 if user_obj and user_obj.username != User.DEFAULT_USER and \
544 user_obj.user_data.get('force_password_change'):
544 user_obj.user_data.get('force_password_change'):
545 reason = 'password change required'
545 reason = 'password change required'
546 log.debug('User not allowed to authenticate, %s', reason)
546 log.debug('User not allowed to authenticate, %s', reason)
547 return HTTPNotAcceptable(reason)(environ, start_response)
547 return HTTPNotAcceptable(reason)(environ, start_response)
548
548
549 # check permissions for this repository
549 # check permissions for this repository
550 perm = self._check_permission(
550 perm = self._check_permission(
551 action, user, auth_user, self.acl_repo_name, ip_addr,
551 action, user, auth_user, self.acl_repo_name, ip_addr,
552 plugin, plugin_cache_active, cache_ttl)
552 plugin, plugin_cache_active, cache_ttl)
553 if not perm:
553 if not perm:
554 return HTTPForbidden()(environ, start_response)
554 return HTTPForbidden()(environ, start_response)
555 environ['rc_auth_user_id'] = str(user_id)
555 environ['rc_auth_user_id'] = str(user_id)
556
556
557 if action == 'push':
557 if action == 'push':
558 perms = auth_user.get_branch_permissions(self.acl_repo_name)
558 perms = auth_user.get_branch_permissions(self.acl_repo_name)
559 if perms:
559 if perms:
560 check_branch_perms = True
560 check_branch_perms = True
561 detect_force_push = True
561 detect_force_push = True
562
562
563 # extras are injected into UI object and later available
563 # extras are injected into UI object and later available
564 # in hooks executed by RhodeCode
564 # in hooks executed by RhodeCode
565 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
565 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
566
566
567 extras = vcs_operation_context(
567 extras = vcs_operation_context(
568 environ, repo_name=self.acl_repo_name, username=username,
568 environ, repo_name=self.acl_repo_name, username=username,
569 action=action, scm=self.SCM, check_locking=check_locking,
569 action=action, scm=self.SCM, check_locking=check_locking,
570 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
570 is_shadow_repo=self.is_shadow_repo, check_branch_perms=check_branch_perms,
571 detect_force_push=detect_force_push
571 detect_force_push=detect_force_push
572 )
572 )
573
573
574 # ======================================================================
574 # ======================================================================
575 # REQUEST HANDLING
575 # REQUEST HANDLING
576 # ======================================================================
576 # ======================================================================
577 repo_path = os.path.join(
577 repo_path = os.path.join(
578 safe_str(self.base_path), safe_str(self.vcs_repo_name))
578 safe_str(self.base_path), safe_str(self.vcs_repo_name))
579 log.debug('Repository path is %s', repo_path)
579 log.debug('Repository path is %s', repo_path)
580
580
581 fix_PATH()
581 fix_PATH()
582
582
583 log.info(
583 log.info(
584 '%s action on %s repo "%s" by "%s" from %s %s',
584 '%s action on %s repo "%s" by "%s" from %s %s',
585 action, self.SCM, safe_str(self.url_repo_name),
585 action, self.SCM, safe_str(self.url_repo_name),
586 safe_str(username), ip_addr, user_agent)
586 safe_str(username), ip_addr, user_agent)
587
587
588 return self._generate_vcs_response(
588 return self._generate_vcs_response(
589 environ, start_response, repo_path, extras, action)
589 environ, start_response, repo_path, extras, action)
590
590
591 @initialize_generator
591 @initialize_generator
592 def _generate_vcs_response(
592 def _generate_vcs_response(
593 self, environ, start_response, repo_path, extras, action):
593 self, environ, start_response, repo_path, extras, action):
594 """
594 """
595 Returns a generator for the response content.
595 Returns a generator for the response content.
596
596
597 This method is implemented as a generator, so that it can trigger
597 This method is implemented as a generator, so that it can trigger
598 the cache validation after all content sent back to the client. It
598 the cache validation after all content sent back to the client. It
599 also handles the locking exceptions which will be triggered when
599 also handles the locking exceptions which will be triggered when
600 the first chunk is produced by the underlying WSGI application.
600 the first chunk is produced by the underlying WSGI application.
601 """
601 """
602
602
603 txn_id = ''
603 txn_id = ''
604 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
604 if 'CONTENT_LENGTH' in environ and environ['REQUEST_METHOD'] == 'MERGE':
605 # case for SVN, we want to re-use the callback daemon port
605 # case for SVN, we want to re-use the callback daemon port
606 # so we use the txn_id, for this we peek the body, and still save
606 # so we use the txn_id, for this we peek the body, and still save
607 # it as wsgi.input
607 # it as wsgi.input
608 data: str = safe_str(environ['wsgi.input'].getvalue())
608
609 stream = environ['wsgi.input']
610
611 if isinstance(stream, io.BytesIO):
612 data: str = safe_str(stream.getvalue())
613 elif hasattr(stream, 'buf'): # most likely gunicorn.http.body.Body
614 data: str = safe_str(stream.buf.getvalue())
615 else:
616 # fallback to the crudest way, copy the iterator
617 data = safe_str(stream.read())
618 environ['wsgi.input'] = io.BytesIO(safe_bytes(data))
619
609 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
620 txn_id = extract_svn_txn_id(self.acl_repo_name, data)
610
621
611 callback_daemon, extras = self._prepare_callback_daemon(
622 callback_daemon, extras = self._prepare_callback_daemon(
612 extras, environ, action, txn_id=txn_id)
623 extras, environ, action, txn_id=txn_id)
613 log.debug('HOOKS extras is %s', extras)
624 log.debug('HOOKS extras is %s', extras)
614
625
615 http_scheme = self._get_http_scheme(environ)
626 http_scheme = self._get_http_scheme(environ)
616
627
617 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
628 config = self._create_config(extras, self.acl_repo_name, scheme=http_scheme)
618 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
629 app = self._create_wsgi_app(repo_path, self.url_repo_name, config)
619 with callback_daemon:
630 with callback_daemon:
620 app.rc_extras = extras
631 app.rc_extras = extras
621
632
622 try:
633 try:
623 response = app(environ, start_response)
634 response = app(environ, start_response)
624 finally:
635 finally:
625 # This statement works together with the decorator
636 # This statement works together with the decorator
626 # "initialize_generator" above. The decorator ensures that
637 # "initialize_generator" above. The decorator ensures that
627 # we hit the first yield statement before the generator is
638 # we hit the first yield statement before the generator is
628 # returned back to the WSGI server. This is needed to
639 # returned back to the WSGI server. This is needed to
629 # ensure that the call to "app" above triggers the
640 # ensure that the call to "app" above triggers the
630 # needed callback to "start_response" before the
641 # needed callback to "start_response" before the
631 # generator is actually used.
642 # generator is actually used.
632 yield "__init__"
643 yield "__init__"
633
644
634 # iter content
645 # iter content
635 for chunk in response:
646 for chunk in response:
636 yield chunk
647 yield chunk
637
648
638 try:
649 try:
639 # invalidate cache on push
650 # invalidate cache on push
640 if action == 'push':
651 if action == 'push':
641 self._invalidate_cache(self.url_repo_name)
652 self._invalidate_cache(self.url_repo_name)
642 finally:
653 finally:
643 meta.Session.remove()
654 meta.Session.remove()
644
655
645 def _get_repository_name(self, environ):
656 def _get_repository_name(self, environ):
646 """Get repository name out of the environmnent
657 """Get repository name out of the environmnent
647
658
648 :param environ: WSGI environment
659 :param environ: WSGI environment
649 """
660 """
650 raise NotImplementedError()
661 raise NotImplementedError()
651
662
652 def _get_action(self, environ):
663 def _get_action(self, environ):
653 """Map request commands into a pull or push command.
664 """Map request commands into a pull or push command.
654
665
655 :param environ: WSGI environment
666 :param environ: WSGI environment
656 """
667 """
657 raise NotImplementedError()
668 raise NotImplementedError()
658
669
659 def _create_wsgi_app(self, repo_path, repo_name, config):
670 def _create_wsgi_app(self, repo_path, repo_name, config):
660 """Return the WSGI app that will finally handle the request."""
671 """Return the WSGI app that will finally handle the request."""
661 raise NotImplementedError()
672 raise NotImplementedError()
662
673
663 def _create_config(self, extras, repo_name, scheme='http'):
674 def _create_config(self, extras, repo_name, scheme='http'):
664 """Create a safe config representation."""
675 """Create a safe config representation."""
665 raise NotImplementedError()
676 raise NotImplementedError()
666
677
667 def _should_use_callback_daemon(self, extras, environ, action):
678 def _should_use_callback_daemon(self, extras, environ, action):
668 if extras.get('is_shadow_repo'):
679 if extras.get('is_shadow_repo'):
669 # we don't want to execute hooks, and callback daemon for shadow repos
680 # we don't want to execute hooks, and callback daemon for shadow repos
670 return False
681 return False
671 return True
682 return True
672
683
673 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
684 def _prepare_callback_daemon(self, extras, environ, action, txn_id=None):
674 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
685 direct_calls = vcs_settings.HOOKS_DIRECT_CALLS
675 if not self._should_use_callback_daemon(extras, environ, action):
686 if not self._should_use_callback_daemon(extras, environ, action):
676 # disable callback daemon for actions that don't require it
687 # disable callback daemon for actions that don't require it
677 direct_calls = True
688 direct_calls = True
678
689
679 return prepare_callback_daemon(
690 return prepare_callback_daemon(
680 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
691 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
681 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
692 host=vcs_settings.HOOKS_HOST, use_direct_calls=direct_calls, txn_id=txn_id)
682
693
683
694
684 def _should_check_locking(query_string):
695 def _should_check_locking(query_string):
685 # this is kind of hacky, but due to how mercurial handles client-server
696 # this is kind of hacky, but due to how mercurial handles client-server
686 # server see all operation on commit; bookmarks, phases and
697 # server see all operation on commit; bookmarks, phases and
687 # obsolescence marker in different transaction, we don't want to check
698 # obsolescence marker in different transaction, we don't want to check
688 # locking on those
699 # locking on those
689 return query_string not in ['cmd=listkeys']
700 return query_string not in ['cmd=listkeys']
General Comments 0
You need to be logged in to leave comments. Login now