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