##// END OF EJS Templates
vcs: Add flag to indicate if repository is a shadow repository.
Martin Bornhold -
r899:3c9ebe4f default
parent child Browse files
Show More
@@ -1,590 +1,592 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 The base Controller API
22 The base Controller API
23 Provides the BaseController class for subclassing. And usage in different
23 Provides the BaseController class for subclassing. And usage in different
24 controllers
24 controllers
25 """
25 """
26
26
27 import logging
27 import logging
28 import socket
28 import socket
29
29
30 import ipaddress
30 import ipaddress
31 import pyramid.threadlocal
31 import pyramid.threadlocal
32
32
33 from paste.auth.basic import AuthBasicAuthenticator
33 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
34 from paste.httpexceptions import HTTPUnauthorized, HTTPForbidden, get_exception
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
35 from paste.httpheaders import WWW_AUTHENTICATE, AUTHORIZATION
36 from pylons import config, tmpl_context as c, request, session, url
36 from pylons import config, tmpl_context as c, request, session, url
37 from pylons.controllers import WSGIController
37 from pylons.controllers import WSGIController
38 from pylons.controllers.util import redirect
38 from pylons.controllers.util import redirect
39 from pylons.i18n import translation
39 from pylons.i18n import translation
40 # marcink: don't remove this import
40 # marcink: don't remove this import
41 from pylons.templating import render_mako as render # noqa
41 from pylons.templating import render_mako as render # noqa
42 from pylons.i18n.translation import _
42 from pylons.i18n.translation import _
43 from webob.exc import HTTPFound
43 from webob.exc import HTTPFound
44
44
45
45
46 import rhodecode
46 import rhodecode
47 from rhodecode.authentication.base import VCS_TYPE
47 from rhodecode.authentication.base import VCS_TYPE
48 from rhodecode.lib import auth, utils2
48 from rhodecode.lib import auth, utils2
49 from rhodecode.lib import helpers as h
49 from rhodecode.lib import helpers as h
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
50 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
51 from rhodecode.lib.exceptions import UserCreationError
51 from rhodecode.lib.exceptions import UserCreationError
52 from rhodecode.lib.utils import (
52 from rhodecode.lib.utils import (
53 get_repo_slug, set_rhodecode_config, password_changed,
53 get_repo_slug, set_rhodecode_config, password_changed,
54 get_enabled_hook_classes)
54 get_enabled_hook_classes)
55 from rhodecode.lib.utils2 import (
55 from rhodecode.lib.utils2 import (
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
56 str2bool, safe_unicode, AttributeDict, safe_int, md5, aslist)
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
57 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
58 from rhodecode.model import meta
58 from rhodecode.model import meta
59 from rhodecode.model.db import Repository, User
59 from rhodecode.model.db import Repository, User
60 from rhodecode.model.notification import NotificationModel
60 from rhodecode.model.notification import NotificationModel
61 from rhodecode.model.scm import ScmModel
61 from rhodecode.model.scm import ScmModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
62 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
63
63
64
64
65 log = logging.getLogger(__name__)
65 log = logging.getLogger(__name__)
66
66
67
67
68 def _filter_proxy(ip):
68 def _filter_proxy(ip):
69 """
69 """
70 Passed in IP addresses in HEADERS can be in a special format of multiple
70 Passed in IP addresses in HEADERS can be in a special format of multiple
71 ips. Those comma separated IPs are passed from various proxies in the
71 ips. Those comma separated IPs are passed from various proxies in the
72 chain of request processing. The left-most being the original client.
72 chain of request processing. The left-most being the original client.
73 We only care about the first IP which came from the org. client.
73 We only care about the first IP which came from the org. client.
74
74
75 :param ip: ip string from headers
75 :param ip: ip string from headers
76 """
76 """
77 if ',' in ip:
77 if ',' in ip:
78 _ips = ip.split(',')
78 _ips = ip.split(',')
79 _first_ip = _ips[0].strip()
79 _first_ip = _ips[0].strip()
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
80 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
81 return _first_ip
81 return _first_ip
82 return ip
82 return ip
83
83
84
84
85 def _filter_port(ip):
85 def _filter_port(ip):
86 """
86 """
87 Removes a port from ip, there are 4 main cases to handle here.
87 Removes a port from ip, there are 4 main cases to handle here.
88 - ipv4 eg. 127.0.0.1
88 - ipv4 eg. 127.0.0.1
89 - ipv6 eg. ::1
89 - ipv6 eg. ::1
90 - ipv4+port eg. 127.0.0.1:8080
90 - ipv4+port eg. 127.0.0.1:8080
91 - ipv6+port eg. [::1]:8080
91 - ipv6+port eg. [::1]:8080
92
92
93 :param ip:
93 :param ip:
94 """
94 """
95 def is_ipv6(ip_addr):
95 def is_ipv6(ip_addr):
96 if hasattr(socket, 'inet_pton'):
96 if hasattr(socket, 'inet_pton'):
97 try:
97 try:
98 socket.inet_pton(socket.AF_INET6, ip_addr)
98 socket.inet_pton(socket.AF_INET6, ip_addr)
99 except socket.error:
99 except socket.error:
100 return False
100 return False
101 else:
101 else:
102 # fallback to ipaddress
102 # fallback to ipaddress
103 try:
103 try:
104 ipaddress.IPv6Address(ip_addr)
104 ipaddress.IPv6Address(ip_addr)
105 except Exception:
105 except Exception:
106 return False
106 return False
107 return True
107 return True
108
108
109 if ':' not in ip: # must be ipv4 pure ip
109 if ':' not in ip: # must be ipv4 pure ip
110 return ip
110 return ip
111
111
112 if '[' in ip and ']' in ip: # ipv6 with port
112 if '[' in ip and ']' in ip: # ipv6 with port
113 return ip.split(']')[0][1:].lower()
113 return ip.split(']')[0][1:].lower()
114
114
115 # must be ipv6 or ipv4 with port
115 # must be ipv6 or ipv4 with port
116 if is_ipv6(ip):
116 if is_ipv6(ip):
117 return ip
117 return ip
118 else:
118 else:
119 ip, _port = ip.split(':')[:2] # means ipv4+port
119 ip, _port = ip.split(':')[:2] # means ipv4+port
120 return ip
120 return ip
121
121
122
122
123 def get_ip_addr(environ):
123 def get_ip_addr(environ):
124 proxy_key = 'HTTP_X_REAL_IP'
124 proxy_key = 'HTTP_X_REAL_IP'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
125 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
126 def_key = 'REMOTE_ADDR'
126 def_key = 'REMOTE_ADDR'
127 _filters = lambda x: _filter_port(_filter_proxy(x))
127 _filters = lambda x: _filter_port(_filter_proxy(x))
128
128
129 ip = environ.get(proxy_key)
129 ip = environ.get(proxy_key)
130 if ip:
130 if ip:
131 return _filters(ip)
131 return _filters(ip)
132
132
133 ip = environ.get(proxy_key2)
133 ip = environ.get(proxy_key2)
134 if ip:
134 if ip:
135 return _filters(ip)
135 return _filters(ip)
136
136
137 ip = environ.get(def_key, '0.0.0.0')
137 ip = environ.get(def_key, '0.0.0.0')
138 return _filters(ip)
138 return _filters(ip)
139
139
140
140
141 def get_server_ip_addr(environ, log_errors=True):
141 def get_server_ip_addr(environ, log_errors=True):
142 hostname = environ.get('SERVER_NAME')
142 hostname = environ.get('SERVER_NAME')
143 try:
143 try:
144 return socket.gethostbyname(hostname)
144 return socket.gethostbyname(hostname)
145 except Exception as e:
145 except Exception as e:
146 if log_errors:
146 if log_errors:
147 # in some cases this lookup is not possible, and we don't want to
147 # in some cases this lookup is not possible, and we don't want to
148 # make it an exception in logs
148 # make it an exception in logs
149 log.exception('Could not retrieve server ip address: %s', e)
149 log.exception('Could not retrieve server ip address: %s', e)
150 return hostname
150 return hostname
151
151
152
152
153 def get_server_port(environ):
153 def get_server_port(environ):
154 return environ.get('SERVER_PORT')
154 return environ.get('SERVER_PORT')
155
155
156
156
157 def get_access_path(environ):
157 def get_access_path(environ):
158 path = environ.get('PATH_INFO')
158 path = environ.get('PATH_INFO')
159 org_req = environ.get('pylons.original_request')
159 org_req = environ.get('pylons.original_request')
160 if org_req:
160 if org_req:
161 path = org_req.environ.get('PATH_INFO')
161 path = org_req.environ.get('PATH_INFO')
162 return path
162 return path
163
163
164
164
165 def vcs_operation_context(
165 def vcs_operation_context(
166 environ, repo_name, username, action, scm, check_locking=True):
166 environ, repo_name, username, action, scm, check_locking=True,
167 is_shadow_repo=False):
167 """
168 """
168 Generate the context for a vcs operation, e.g. push or pull.
169 Generate the context for a vcs operation, e.g. push or pull.
169
170
170 This context is passed over the layers so that hooks triggered by the
171 This context is passed over the layers so that hooks triggered by the
171 vcs operation know details like the user, the user's IP address etc.
172 vcs operation know details like the user, the user's IP address etc.
172
173
173 :param check_locking: Allows to switch of the computation of the locking
174 :param check_locking: Allows to switch of the computation of the locking
174 data. This serves mainly the need of the simplevcs middleware to be
175 data. This serves mainly the need of the simplevcs middleware to be
175 able to disable this for certain operations.
176 able to disable this for certain operations.
176
177
177 """
178 """
178 # Tri-state value: False: unlock, None: nothing, True: lock
179 # Tri-state value: False: unlock, None: nothing, True: lock
179 make_lock = None
180 make_lock = None
180 locked_by = [None, None, None]
181 locked_by = [None, None, None]
181 is_anonymous = username == User.DEFAULT_USER
182 is_anonymous = username == User.DEFAULT_USER
182 if not is_anonymous and check_locking:
183 if not is_anonymous and check_locking:
183 log.debug('Checking locking on repository "%s"', repo_name)
184 log.debug('Checking locking on repository "%s"', repo_name)
184 user = User.get_by_username(username)
185 user = User.get_by_username(username)
185 repo = Repository.get_by_repo_name(repo_name)
186 repo = Repository.get_by_repo_name(repo_name)
186 make_lock, __, locked_by = repo.get_locking_state(
187 make_lock, __, locked_by = repo.get_locking_state(
187 action, user.user_id)
188 action, user.user_id)
188
189
189 settings_model = VcsSettingsModel(repo=repo_name)
190 settings_model = VcsSettingsModel(repo=repo_name)
190 ui_settings = settings_model.get_ui_settings()
191 ui_settings = settings_model.get_ui_settings()
191
192
192 extras = {
193 extras = {
193 'ip': get_ip_addr(environ),
194 'ip': get_ip_addr(environ),
194 'username': username,
195 'username': username,
195 'action': action,
196 'action': action,
196 'repository': repo_name,
197 'repository': repo_name,
197 'scm': scm,
198 'scm': scm,
198 'config': rhodecode.CONFIG['__file__'],
199 'config': rhodecode.CONFIG['__file__'],
199 'make_lock': make_lock,
200 'make_lock': make_lock,
200 'locked_by': locked_by,
201 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
202 'server_url': utils2.get_server_url(environ),
202 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
203 }
205 }
204 return extras
206 return extras
205
207
206
208
207 class BasicAuth(AuthBasicAuthenticator):
209 class BasicAuth(AuthBasicAuthenticator):
208
210
209 def __init__(self, realm, authfunc, registry, auth_http_code=None,
211 def __init__(self, realm, authfunc, registry, auth_http_code=None,
210 initial_call_detection=False):
212 initial_call_detection=False):
211 self.realm = realm
213 self.realm = realm
212 self.initial_call = initial_call_detection
214 self.initial_call = initial_call_detection
213 self.authfunc = authfunc
215 self.authfunc = authfunc
214 self.registry = registry
216 self.registry = registry
215 self._rc_auth_http_code = auth_http_code
217 self._rc_auth_http_code = auth_http_code
216
218
217 def _get_response_from_code(self, http_code):
219 def _get_response_from_code(self, http_code):
218 try:
220 try:
219 return get_exception(safe_int(http_code))
221 return get_exception(safe_int(http_code))
220 except Exception:
222 except Exception:
221 log.exception('Failed to fetch response for code %s' % http_code)
223 log.exception('Failed to fetch response for code %s' % http_code)
222 return HTTPForbidden
224 return HTTPForbidden
223
225
224 def build_authentication(self):
226 def build_authentication(self):
225 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
227 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
226 if self._rc_auth_http_code and not self.initial_call:
228 if self._rc_auth_http_code and not self.initial_call:
227 # return alternative HTTP code if alternative http return code
229 # return alternative HTTP code if alternative http return code
228 # is specified in RhodeCode config, but ONLY if it's not the
230 # is specified in RhodeCode config, but ONLY if it's not the
229 # FIRST call
231 # FIRST call
230 custom_response_klass = self._get_response_from_code(
232 custom_response_klass = self._get_response_from_code(
231 self._rc_auth_http_code)
233 self._rc_auth_http_code)
232 return custom_response_klass(headers=head)
234 return custom_response_klass(headers=head)
233 return HTTPUnauthorized(headers=head)
235 return HTTPUnauthorized(headers=head)
234
236
235 def authenticate(self, environ):
237 def authenticate(self, environ):
236 authorization = AUTHORIZATION(environ)
238 authorization = AUTHORIZATION(environ)
237 if not authorization:
239 if not authorization:
238 return self.build_authentication()
240 return self.build_authentication()
239 (authmeth, auth) = authorization.split(' ', 1)
241 (authmeth, auth) = authorization.split(' ', 1)
240 if 'basic' != authmeth.lower():
242 if 'basic' != authmeth.lower():
241 return self.build_authentication()
243 return self.build_authentication()
242 auth = auth.strip().decode('base64')
244 auth = auth.strip().decode('base64')
243 _parts = auth.split(':', 1)
245 _parts = auth.split(':', 1)
244 if len(_parts) == 2:
246 if len(_parts) == 2:
245 username, password = _parts
247 username, password = _parts
246 if self.authfunc(
248 if self.authfunc(
247 username, password, environ, VCS_TYPE,
249 username, password, environ, VCS_TYPE,
248 registry=self.registry):
250 registry=self.registry):
249 return username
251 return username
250 if username and password:
252 if username and password:
251 # we mark that we actually executed authentication once, at
253 # we mark that we actually executed authentication once, at
252 # that point we can use the alternative auth code
254 # that point we can use the alternative auth code
253 self.initial_call = False
255 self.initial_call = False
254
256
255 return self.build_authentication()
257 return self.build_authentication()
256
258
257 __call__ = authenticate
259 __call__ = authenticate
258
260
259
261
260 def attach_context_attributes(context, request):
262 def attach_context_attributes(context, request):
261 """
263 """
262 Attach variables into template context called `c`, please note that
264 Attach variables into template context called `c`, please note that
263 request could be pylons or pyramid request in here.
265 request could be pylons or pyramid request in here.
264 """
266 """
265 rc_config = SettingsModel().get_all_settings(cache=True)
267 rc_config = SettingsModel().get_all_settings(cache=True)
266
268
267 context.rhodecode_version = rhodecode.__version__
269 context.rhodecode_version = rhodecode.__version__
268 context.rhodecode_edition = config.get('rhodecode.edition')
270 context.rhodecode_edition = config.get('rhodecode.edition')
269 # unique secret + version does not leak the version but keep consistency
271 # unique secret + version does not leak the version but keep consistency
270 context.rhodecode_version_hash = md5(
272 context.rhodecode_version_hash = md5(
271 config.get('beaker.session.secret', '') +
273 config.get('beaker.session.secret', '') +
272 rhodecode.__version__)[:8]
274 rhodecode.__version__)[:8]
273
275
274 # Default language set for the incoming request
276 # Default language set for the incoming request
275 context.language = translation.get_lang()[0]
277 context.language = translation.get_lang()[0]
276
278
277 # Visual options
279 # Visual options
278 context.visual = AttributeDict({})
280 context.visual = AttributeDict({})
279
281
280 # DB stored Visual Items
282 # DB stored Visual Items
281 context.visual.show_public_icon = str2bool(
283 context.visual.show_public_icon = str2bool(
282 rc_config.get('rhodecode_show_public_icon'))
284 rc_config.get('rhodecode_show_public_icon'))
283 context.visual.show_private_icon = str2bool(
285 context.visual.show_private_icon = str2bool(
284 rc_config.get('rhodecode_show_private_icon'))
286 rc_config.get('rhodecode_show_private_icon'))
285 context.visual.stylify_metatags = str2bool(
287 context.visual.stylify_metatags = str2bool(
286 rc_config.get('rhodecode_stylify_metatags'))
288 rc_config.get('rhodecode_stylify_metatags'))
287 context.visual.dashboard_items = safe_int(
289 context.visual.dashboard_items = safe_int(
288 rc_config.get('rhodecode_dashboard_items', 100))
290 rc_config.get('rhodecode_dashboard_items', 100))
289 context.visual.admin_grid_items = safe_int(
291 context.visual.admin_grid_items = safe_int(
290 rc_config.get('rhodecode_admin_grid_items', 100))
292 rc_config.get('rhodecode_admin_grid_items', 100))
291 context.visual.repository_fields = str2bool(
293 context.visual.repository_fields = str2bool(
292 rc_config.get('rhodecode_repository_fields'))
294 rc_config.get('rhodecode_repository_fields'))
293 context.visual.show_version = str2bool(
295 context.visual.show_version = str2bool(
294 rc_config.get('rhodecode_show_version'))
296 rc_config.get('rhodecode_show_version'))
295 context.visual.use_gravatar = str2bool(
297 context.visual.use_gravatar = str2bool(
296 rc_config.get('rhodecode_use_gravatar'))
298 rc_config.get('rhodecode_use_gravatar'))
297 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
299 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
298 context.visual.default_renderer = rc_config.get(
300 context.visual.default_renderer = rc_config.get(
299 'rhodecode_markup_renderer', 'rst')
301 'rhodecode_markup_renderer', 'rst')
300 context.visual.rhodecode_support_url = \
302 context.visual.rhodecode_support_url = \
301 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
303 rc_config.get('rhodecode_support_url') or url('rhodecode_support')
302
304
303 context.pre_code = rc_config.get('rhodecode_pre_code')
305 context.pre_code = rc_config.get('rhodecode_pre_code')
304 context.post_code = rc_config.get('rhodecode_post_code')
306 context.post_code = rc_config.get('rhodecode_post_code')
305 context.rhodecode_name = rc_config.get('rhodecode_title')
307 context.rhodecode_name = rc_config.get('rhodecode_title')
306 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
308 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
307 # if we have specified default_encoding in the request, it has more
309 # if we have specified default_encoding in the request, it has more
308 # priority
310 # priority
309 if request.GET.get('default_encoding'):
311 if request.GET.get('default_encoding'):
310 context.default_encodings.insert(0, request.GET.get('default_encoding'))
312 context.default_encodings.insert(0, request.GET.get('default_encoding'))
311 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
313 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
312
314
313 # INI stored
315 # INI stored
314 context.labs_active = str2bool(
316 context.labs_active = str2bool(
315 config.get('labs_settings_active', 'false'))
317 config.get('labs_settings_active', 'false'))
316 context.visual.allow_repo_location_change = str2bool(
318 context.visual.allow_repo_location_change = str2bool(
317 config.get('allow_repo_location_change', True))
319 config.get('allow_repo_location_change', True))
318 context.visual.allow_custom_hooks_settings = str2bool(
320 context.visual.allow_custom_hooks_settings = str2bool(
319 config.get('allow_custom_hooks_settings', True))
321 config.get('allow_custom_hooks_settings', True))
320 context.debug_style = str2bool(config.get('debug_style', False))
322 context.debug_style = str2bool(config.get('debug_style', False))
321
323
322 context.rhodecode_instanceid = config.get('instance_id')
324 context.rhodecode_instanceid = config.get('instance_id')
323
325
324 # AppEnlight
326 # AppEnlight
325 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
327 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
326 context.appenlight_api_public_key = config.get(
328 context.appenlight_api_public_key = config.get(
327 'appenlight.api_public_key', '')
329 'appenlight.api_public_key', '')
328 context.appenlight_server_url = config.get('appenlight.server_url', '')
330 context.appenlight_server_url = config.get('appenlight.server_url', '')
329
331
330 # JS template context
332 # JS template context
331 context.template_context = {
333 context.template_context = {
332 'repo_name': None,
334 'repo_name': None,
333 'repo_type': None,
335 'repo_type': None,
334 'repo_landing_commit': None,
336 'repo_landing_commit': None,
335 'rhodecode_user': {
337 'rhodecode_user': {
336 'username': None,
338 'username': None,
337 'email': None,
339 'email': None,
338 'notification_status': False
340 'notification_status': False
339 },
341 },
340 'visual': {
342 'visual': {
341 'default_renderer': None
343 'default_renderer': None
342 },
344 },
343 'commit_data': {
345 'commit_data': {
344 'commit_id': None
346 'commit_id': None
345 },
347 },
346 'pull_request_data': {'pull_request_id': None},
348 'pull_request_data': {'pull_request_id': None},
347 'timeago': {
349 'timeago': {
348 'refresh_time': 120 * 1000,
350 'refresh_time': 120 * 1000,
349 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
351 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
350 },
352 },
351 'pylons_dispatch': {
353 'pylons_dispatch': {
352 # 'controller': request.environ['pylons.routes_dict']['controller'],
354 # 'controller': request.environ['pylons.routes_dict']['controller'],
353 # 'action': request.environ['pylons.routes_dict']['action'],
355 # 'action': request.environ['pylons.routes_dict']['action'],
354 },
356 },
355 'pyramid_dispatch': {
357 'pyramid_dispatch': {
356
358
357 },
359 },
358 'extra': {'plugins': {}}
360 'extra': {'plugins': {}}
359 }
361 }
360 # END CONFIG VARS
362 # END CONFIG VARS
361
363
362 # TODO: This dosn't work when called from pylons compatibility tween.
364 # TODO: This dosn't work when called from pylons compatibility tween.
363 # Fix this and remove it from base controller.
365 # Fix this and remove it from base controller.
364 # context.repo_name = get_repo_slug(request) # can be empty
366 # context.repo_name = get_repo_slug(request) # can be empty
365
367
366 context.csrf_token = auth.get_csrf_token()
368 context.csrf_token = auth.get_csrf_token()
367 context.backends = rhodecode.BACKENDS.keys()
369 context.backends = rhodecode.BACKENDS.keys()
368 context.backends.sort()
370 context.backends.sort()
369 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
371 context.unread_notifications = NotificationModel().get_unread_cnt_for_user(
370 context.rhodecode_user.user_id)
372 context.rhodecode_user.user_id)
371
373
372 context.pyramid_request = pyramid.threadlocal.get_current_request()
374 context.pyramid_request = pyramid.threadlocal.get_current_request()
373
375
374
376
375 def get_auth_user(environ):
377 def get_auth_user(environ):
376 ip_addr = get_ip_addr(environ)
378 ip_addr = get_ip_addr(environ)
377 # make sure that we update permissions each time we call controller
379 # make sure that we update permissions each time we call controller
378 _auth_token = (request.GET.get('auth_token', '') or
380 _auth_token = (request.GET.get('auth_token', '') or
379 request.GET.get('api_key', ''))
381 request.GET.get('api_key', ''))
380
382
381 if _auth_token:
383 if _auth_token:
382 # when using API_KEY we are sure user exists.
384 # when using API_KEY we are sure user exists.
383 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
385 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
384 authenticated = False
386 authenticated = False
385 else:
387 else:
386 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
388 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
387 try:
389 try:
388 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
390 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
389 ip_addr=ip_addr)
391 ip_addr=ip_addr)
390 except UserCreationError as e:
392 except UserCreationError as e:
391 h.flash(e, 'error')
393 h.flash(e, 'error')
392 # container auth or other auth functions that create users
394 # container auth or other auth functions that create users
393 # on the fly can throw this exception signaling that there's
395 # on the fly can throw this exception signaling that there's
394 # issue with user creation, explanation should be provided
396 # issue with user creation, explanation should be provided
395 # in Exception itself. We then create a simple blank
397 # in Exception itself. We then create a simple blank
396 # AuthUser
398 # AuthUser
397 auth_user = AuthUser(ip_addr=ip_addr)
399 auth_user = AuthUser(ip_addr=ip_addr)
398
400
399 if password_changed(auth_user, session):
401 if password_changed(auth_user, session):
400 session.invalidate()
402 session.invalidate()
401 cookie_store = CookieStoreWrapper(
403 cookie_store = CookieStoreWrapper(
402 session.get('rhodecode_user'))
404 session.get('rhodecode_user'))
403 auth_user = AuthUser(ip_addr=ip_addr)
405 auth_user = AuthUser(ip_addr=ip_addr)
404
406
405 authenticated = cookie_store.get('is_authenticated')
407 authenticated = cookie_store.get('is_authenticated')
406
408
407 if not auth_user.is_authenticated and auth_user.is_user_object:
409 if not auth_user.is_authenticated and auth_user.is_user_object:
408 # user is not authenticated and not empty
410 # user is not authenticated and not empty
409 auth_user.set_authenticated(authenticated)
411 auth_user.set_authenticated(authenticated)
410
412
411 return auth_user
413 return auth_user
412
414
413
415
414 class BaseController(WSGIController):
416 class BaseController(WSGIController):
415
417
416 def __before__(self):
418 def __before__(self):
417 """
419 """
418 __before__ is called before controller methods and after __call__
420 __before__ is called before controller methods and after __call__
419 """
421 """
420 # on each call propagate settings calls into global settings.
422 # on each call propagate settings calls into global settings.
421 set_rhodecode_config(config)
423 set_rhodecode_config(config)
422 attach_context_attributes(c, request)
424 attach_context_attributes(c, request)
423
425
424 # TODO: Remove this when fixed in attach_context_attributes()
426 # TODO: Remove this when fixed in attach_context_attributes()
425 c.repo_name = get_repo_slug(request) # can be empty
427 c.repo_name = get_repo_slug(request) # can be empty
426
428
427 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
429 self.cut_off_limit_diff = safe_int(config.get('cut_off_limit_diff'))
428 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
430 self.cut_off_limit_file = safe_int(config.get('cut_off_limit_file'))
429 self.sa = meta.Session
431 self.sa = meta.Session
430 self.scm_model = ScmModel(self.sa)
432 self.scm_model = ScmModel(self.sa)
431
433
432 default_lang = c.language
434 default_lang = c.language
433 user_lang = c.language
435 user_lang = c.language
434 try:
436 try:
435 user_obj = self._rhodecode_user.get_instance()
437 user_obj = self._rhodecode_user.get_instance()
436 if user_obj:
438 if user_obj:
437 user_lang = user_obj.user_data.get('language')
439 user_lang = user_obj.user_data.get('language')
438 except Exception:
440 except Exception:
439 log.exception('Failed to fetch user language for user %s',
441 log.exception('Failed to fetch user language for user %s',
440 self._rhodecode_user)
442 self._rhodecode_user)
441
443
442 if user_lang and user_lang != default_lang:
444 if user_lang and user_lang != default_lang:
443 log.debug('set language to %s for user %s', user_lang,
445 log.debug('set language to %s for user %s', user_lang,
444 self._rhodecode_user)
446 self._rhodecode_user)
445 translation.set_lang(user_lang)
447 translation.set_lang(user_lang)
446
448
447 def _dispatch_redirect(self, with_url, environ, start_response):
449 def _dispatch_redirect(self, with_url, environ, start_response):
448 resp = HTTPFound(with_url)
450 resp = HTTPFound(with_url)
449 environ['SCRIPT_NAME'] = '' # handle prefix middleware
451 environ['SCRIPT_NAME'] = '' # handle prefix middleware
450 environ['PATH_INFO'] = with_url
452 environ['PATH_INFO'] = with_url
451 return resp(environ, start_response)
453 return resp(environ, start_response)
452
454
453 def __call__(self, environ, start_response):
455 def __call__(self, environ, start_response):
454 """Invoke the Controller"""
456 """Invoke the Controller"""
455 # WSGIController.__call__ dispatches to the Controller method
457 # WSGIController.__call__ dispatches to the Controller method
456 # the request is routed to. This routing information is
458 # the request is routed to. This routing information is
457 # available in environ['pylons.routes_dict']
459 # available in environ['pylons.routes_dict']
458 from rhodecode.lib import helpers as h
460 from rhodecode.lib import helpers as h
459
461
460 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
462 # Provide the Pylons context to Pyramid's debugtoolbar if it asks
461 if environ.get('debugtoolbar.wants_pylons_context', False):
463 if environ.get('debugtoolbar.wants_pylons_context', False):
462 environ['debugtoolbar.pylons_context'] = c._current_obj()
464 environ['debugtoolbar.pylons_context'] = c._current_obj()
463
465
464 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
466 _route_name = '.'.join([environ['pylons.routes_dict']['controller'],
465 environ['pylons.routes_dict']['action']])
467 environ['pylons.routes_dict']['action']])
466
468
467 self.rc_config = SettingsModel().get_all_settings(cache=True)
469 self.rc_config = SettingsModel().get_all_settings(cache=True)
468 self.ip_addr = get_ip_addr(environ)
470 self.ip_addr = get_ip_addr(environ)
469
471
470 # The rhodecode auth user is looked up and passed through the
472 # The rhodecode auth user is looked up and passed through the
471 # environ by the pylons compatibility tween in pyramid.
473 # environ by the pylons compatibility tween in pyramid.
472 # So we can just grab it from there.
474 # So we can just grab it from there.
473 auth_user = environ['rc_auth_user']
475 auth_user = environ['rc_auth_user']
474
476
475 # set globals for auth user
477 # set globals for auth user
476 request.user = auth_user
478 request.user = auth_user
477 c.rhodecode_user = self._rhodecode_user = auth_user
479 c.rhodecode_user = self._rhodecode_user = auth_user
478
480
479 log.info('IP: %s User: %s accessed %s [%s]' % (
481 log.info('IP: %s User: %s accessed %s [%s]' % (
480 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
482 self.ip_addr, auth_user, safe_unicode(get_access_path(environ)),
481 _route_name)
483 _route_name)
482 )
484 )
483
485
484 # TODO: Maybe this should be move to pyramid to cover all views.
486 # TODO: Maybe this should be move to pyramid to cover all views.
485 # check user attributes for password change flag
487 # check user attributes for password change flag
486 user_obj = auth_user.get_instance()
488 user_obj = auth_user.get_instance()
487 if user_obj and user_obj.user_data.get('force_password_change'):
489 if user_obj and user_obj.user_data.get('force_password_change'):
488 h.flash('You are required to change your password', 'warning',
490 h.flash('You are required to change your password', 'warning',
489 ignore_duplicate=True)
491 ignore_duplicate=True)
490
492
491 skip_user_check_urls = [
493 skip_user_check_urls = [
492 'error.document', 'login.logout', 'login.index',
494 'error.document', 'login.logout', 'login.index',
493 'admin/my_account.my_account_password',
495 'admin/my_account.my_account_password',
494 'admin/my_account.my_account_password_update'
496 'admin/my_account.my_account_password_update'
495 ]
497 ]
496 if _route_name not in skip_user_check_urls:
498 if _route_name not in skip_user_check_urls:
497 return self._dispatch_redirect(
499 return self._dispatch_redirect(
498 url('my_account_password'), environ, start_response)
500 url('my_account_password'), environ, start_response)
499
501
500 return WSGIController.__call__(self, environ, start_response)
502 return WSGIController.__call__(self, environ, start_response)
501
503
502
504
503 class BaseRepoController(BaseController):
505 class BaseRepoController(BaseController):
504 """
506 """
505 Base class for controllers responsible for loading all needed data for
507 Base class for controllers responsible for loading all needed data for
506 repository loaded items are
508 repository loaded items are
507
509
508 c.rhodecode_repo: instance of scm repository
510 c.rhodecode_repo: instance of scm repository
509 c.rhodecode_db_repo: instance of db
511 c.rhodecode_db_repo: instance of db
510 c.repository_requirements_missing: shows that repository specific data
512 c.repository_requirements_missing: shows that repository specific data
511 could not be displayed due to the missing requirements
513 could not be displayed due to the missing requirements
512 c.repository_pull_requests: show number of open pull requests
514 c.repository_pull_requests: show number of open pull requests
513 """
515 """
514
516
515 def __before__(self):
517 def __before__(self):
516 super(BaseRepoController, self).__before__()
518 super(BaseRepoController, self).__before__()
517 if c.repo_name: # extracted from routes
519 if c.repo_name: # extracted from routes
518 db_repo = Repository.get_by_repo_name(c.repo_name)
520 db_repo = Repository.get_by_repo_name(c.repo_name)
519 if not db_repo:
521 if not db_repo:
520 return
522 return
521
523
522 log.debug(
524 log.debug(
523 'Found repository in database %s with state `%s`',
525 'Found repository in database %s with state `%s`',
524 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
526 safe_unicode(db_repo), safe_unicode(db_repo.repo_state))
525 route = getattr(request.environ.get('routes.route'), 'name', '')
527 route = getattr(request.environ.get('routes.route'), 'name', '')
526
528
527 # allow to delete repos that are somehow damages in filesystem
529 # allow to delete repos that are somehow damages in filesystem
528 if route in ['delete_repo']:
530 if route in ['delete_repo']:
529 return
531 return
530
532
531 if db_repo.repo_state in [Repository.STATE_PENDING]:
533 if db_repo.repo_state in [Repository.STATE_PENDING]:
532 if route in ['repo_creating_home']:
534 if route in ['repo_creating_home']:
533 return
535 return
534 check_url = url('repo_creating_home', repo_name=c.repo_name)
536 check_url = url('repo_creating_home', repo_name=c.repo_name)
535 return redirect(check_url)
537 return redirect(check_url)
536
538
537 self.rhodecode_db_repo = db_repo
539 self.rhodecode_db_repo = db_repo
538
540
539 missing_requirements = False
541 missing_requirements = False
540 try:
542 try:
541 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
543 self.rhodecode_repo = self.rhodecode_db_repo.scm_instance()
542 except RepositoryRequirementError as e:
544 except RepositoryRequirementError as e:
543 missing_requirements = True
545 missing_requirements = True
544 self._handle_missing_requirements(e)
546 self._handle_missing_requirements(e)
545
547
546 if self.rhodecode_repo is None and not missing_requirements:
548 if self.rhodecode_repo is None and not missing_requirements:
547 log.error('%s this repository is present in database but it '
549 log.error('%s this repository is present in database but it '
548 'cannot be created as an scm instance', c.repo_name)
550 'cannot be created as an scm instance', c.repo_name)
549
551
550 h.flash(_(
552 h.flash(_(
551 "The repository at %(repo_name)s cannot be located.") %
553 "The repository at %(repo_name)s cannot be located.") %
552 {'repo_name': c.repo_name},
554 {'repo_name': c.repo_name},
553 category='error', ignore_duplicate=True)
555 category='error', ignore_duplicate=True)
554 redirect(url('home'))
556 redirect(url('home'))
555
557
556 # update last change according to VCS data
558 # update last change according to VCS data
557 if not missing_requirements:
559 if not missing_requirements:
558 commit = db_repo.get_commit(
560 commit = db_repo.get_commit(
559 pre_load=["author", "date", "message", "parents"])
561 pre_load=["author", "date", "message", "parents"])
560 db_repo.update_commit_cache(commit)
562 db_repo.update_commit_cache(commit)
561
563
562 # Prepare context
564 # Prepare context
563 c.rhodecode_db_repo = db_repo
565 c.rhodecode_db_repo = db_repo
564 c.rhodecode_repo = self.rhodecode_repo
566 c.rhodecode_repo = self.rhodecode_repo
565 c.repository_requirements_missing = missing_requirements
567 c.repository_requirements_missing = missing_requirements
566
568
567 self._update_global_counters(self.scm_model, db_repo)
569 self._update_global_counters(self.scm_model, db_repo)
568
570
569 def _update_global_counters(self, scm_model, db_repo):
571 def _update_global_counters(self, scm_model, db_repo):
570 """
572 """
571 Base variables that are exposed to every page of repository
573 Base variables that are exposed to every page of repository
572 """
574 """
573 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
575 c.repository_pull_requests = scm_model.get_pull_requests(db_repo)
574
576
575 def _handle_missing_requirements(self, error):
577 def _handle_missing_requirements(self, error):
576 self.rhodecode_repo = None
578 self.rhodecode_repo = None
577 log.error(
579 log.error(
578 'Requirements are missing for repository %s: %s',
580 'Requirements are missing for repository %s: %s',
579 c.repo_name, error.message)
581 c.repo_name, error.message)
580
582
581 summary_url = url('summary_home', repo_name=c.repo_name)
583 summary_url = url('summary_home', repo_name=c.repo_name)
582 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
584 statistics_url = url('edit_repo_statistics', repo_name=c.repo_name)
583 settings_update_url = url('repo', repo_name=c.repo_name)
585 settings_update_url = url('repo', repo_name=c.repo_name)
584 path = request.path
586 path = request.path
585 should_redirect = (
587 should_redirect = (
586 path not in (summary_url, settings_update_url)
588 path not in (summary_url, settings_update_url)
587 and '/settings' not in path or path == statistics_url
589 and '/settings' not in path or path == statistics_url
588 )
590 )
589 if should_redirect:
591 if should_redirect:
590 redirect(summary_url)
592 redirect(summary_url)
@@ -1,505 +1,506 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2016 RhodeCode GmbH
3 # Copyright (C) 2014-2016 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
22 SimpleVCS middleware for handling protocol request (push/clone etc.)
23 It's implemented with basic auth function
23 It's implemented with basic auth function
24 """
24 """
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import importlib
28 import importlib
29 import re
29 import re
30 from functools import wraps
30 from functools import wraps
31
31
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
32 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
33 from webob.exc import (
33 from webob.exc import (
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
34 HTTPNotFound, HTTPForbidden, HTTPNotAcceptable, HTTPInternalServerError)
35
35
36 import rhodecode
36 import rhodecode
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
37 from rhodecode.authentication.base import authenticate, VCS_TYPE
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import AuthUser, HasPermissionAnyMiddleware
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
39 from rhodecode.lib.base import BasicAuth, get_ip_addr, vcs_operation_context
40 from rhodecode.lib.exceptions import (
40 from rhodecode.lib.exceptions import (
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
41 HTTPLockedRC, HTTPRequirementError, UserCreationError,
42 NotAllowedToCreateUserError)
42 NotAllowedToCreateUserError)
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
43 from rhodecode.lib.hooks_daemon import prepare_callback_daemon
44 from rhodecode.lib.middleware import appenlight
44 from rhodecode.lib.middleware import appenlight
45 from rhodecode.lib.middleware.utils import scm_app
45 from rhodecode.lib.middleware.utils import scm_app
46 from rhodecode.lib.utils import (
46 from rhodecode.lib.utils import (
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
47 is_valid_repo, get_rhodecode_realm, get_rhodecode_base_path)
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
48 from rhodecode.lib.utils2 import safe_str, fix_PATH, str2bool
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
49 from rhodecode.lib.vcs.conf import settings as vcs_settings
50 from rhodecode.lib.vcs.backends import base
50 from rhodecode.lib.vcs.backends import base
51 from rhodecode.model import meta
51 from rhodecode.model import meta
52 from rhodecode.model.db import User, Repository, PullRequest
52 from rhodecode.model.db import User, Repository, PullRequest
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54 from rhodecode.model.pull_request import PullRequestModel
54 from rhodecode.model.pull_request import PullRequestModel
55
55
56
56
57 log = logging.getLogger(__name__)
57 log = logging.getLogger(__name__)
58
58
59
59
60 def initialize_generator(factory):
60 def initialize_generator(factory):
61 """
61 """
62 Initializes the returned generator by draining its first element.
62 Initializes the returned generator by draining its first element.
63
63
64 This can be used to give a generator an initializer, which is the code
64 This can be used to give a generator an initializer, which is the code
65 up to the first yield statement. This decorator enforces that the first
65 up to the first yield statement. This decorator enforces that the first
66 produced element has the value ``"__init__"`` to make its special
66 produced element has the value ``"__init__"`` to make its special
67 purpose very explicit in the using code.
67 purpose very explicit in the using code.
68 """
68 """
69
69
70 @wraps(factory)
70 @wraps(factory)
71 def wrapper(*args, **kwargs):
71 def wrapper(*args, **kwargs):
72 gen = factory(*args, **kwargs)
72 gen = factory(*args, **kwargs)
73 try:
73 try:
74 init = gen.next()
74 init = gen.next()
75 except StopIteration:
75 except StopIteration:
76 raise ValueError('Generator must yield at least one element.')
76 raise ValueError('Generator must yield at least one element.')
77 if init != "__init__":
77 if init != "__init__":
78 raise ValueError('First yielded element must be "__init__".')
78 raise ValueError('First yielded element must be "__init__".')
79 return gen
79 return gen
80 return wrapper
80 return wrapper
81
81
82
82
83 class SimpleVCS(object):
83 class SimpleVCS(object):
84 """Common functionality for SCM HTTP handlers."""
84 """Common functionality for SCM HTTP handlers."""
85
85
86 SCM = 'unknown'
86 SCM = 'unknown'
87
87
88 acl_repo_name = None
88 acl_repo_name = None
89 url_repo_name = None
89 url_repo_name = None
90 vcs_repo_name = None
90 vcs_repo_name = None
91
91
92 def __init__(self, application, config, registry):
92 def __init__(self, application, config, registry):
93 self.registry = registry
93 self.registry = registry
94 self.application = application
94 self.application = application
95 self.config = config
95 self.config = config
96 # re-populated by specialized middleware
96 # re-populated by specialized middleware
97 self.repo_vcs_config = base.Config()
97 self.repo_vcs_config = base.Config()
98
98
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = get_rhodecode_base_path()
100 self.basepath = get_rhodecode_base_path()
101 # authenticate this VCS request using authfunc
101 # authenticate this VCS request using authfunc
102 auth_ret_code_detection = \
102 auth_ret_code_detection = \
103 str2bool(self.config.get('auth_ret_code_detection', False))
103 str2bool(self.config.get('auth_ret_code_detection', False))
104 self.authenticate = BasicAuth(
104 self.authenticate = BasicAuth(
105 '', authenticate, registry, config.get('auth_ret_code'),
105 '', authenticate, registry, config.get('auth_ret_code'),
106 auth_ret_code_detection)
106 auth_ret_code_detection)
107 self.ip_addr = '0.0.0.0'
107 self.ip_addr = '0.0.0.0'
108
108
109 def set_repo_names(self, environ):
109 def set_repo_names(self, environ):
110 """
110 """
111 This will populate the attributes acl_repo_name, url_repo_name,
111 This will populate the attributes acl_repo_name, url_repo_name,
112 vcs_repo_name and pr_id on the current instance.
112 vcs_repo_name and is_shadow_repo on the current instance.
113 """
113 """
114 # TODO: martinb: Move to class or module scope.
114 # TODO: martinb: Move to class or module scope.
115 # TODO: martinb: Check if we have to use re.UNICODE.
115 # TODO: martinb: Check if we have to use re.UNICODE.
116 # TODO: martinb: Check which chars are allowed for repo/group names.
116 # TODO: martinb: Check which chars are allowed for repo/group names.
117 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
117 # These chars are excluded: '`?=[]\;\'"<>,/~!@#$%^&*()+{}|: '
118 # Code from: rhodecode/lib/utils.py:repo_name_slug()
118 # Code from: rhodecode/lib/utils.py:repo_name_slug()
119 pr_regex = re.compile(
119 pr_regex = re.compile(
120 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
120 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/' # repo groups
121 '(?P<repo_name>[\w-]+)' # target repo name
121 '(?P<repo_name>[\w-]+)' # target repo name
122 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
122 '/pull-request/(?P<pr_id>\d+)/repository') # pr suffix
123
123
124 # Get url repo name from environment.
124 # Get url repo name from environment.
125 self.url_repo_name = self._get_repository_name(environ)
125 self.url_repo_name = self._get_repository_name(environ)
126
126
127 # Check if this is a request to a shadow repository. In case of a
127 # Check if this is a request to a shadow repository. In case of a
128 # shadow repo set vcs_repo_name to the file system path pointing to the
128 # shadow repo set vcs_repo_name to the file system path pointing to the
129 # shadow repo. And set acl_repo_name to the pull request target repo
129 # shadow repo. And set acl_repo_name to the pull request target repo
130 # because we use the target repo for permission checks. Otherwise all
130 # because we use the target repo for permission checks. Otherwise all
131 # names are equal.
131 # names are equal.
132 match = pr_regex.match(self.url_repo_name)
132 match = pr_regex.match(self.url_repo_name)
133 if match:
133 if match:
134 # Get pull request instance.
134 # Get pull request instance.
135 match_dict = match.groupdict()
135 match_dict = match.groupdict()
136 pr_id = match_dict['pr_id']
136 pr_id = match_dict['pr_id']
137 pull_request = PullRequest.get(pr_id)
137 pull_request = PullRequest.get(pr_id)
138
138
139 # Get file system path to shadow repository.
139 # Get file system path to shadow repository.
140 workspace_id = PullRequestModel()._workspace_id(pull_request)
140 workspace_id = PullRequestModel()._workspace_id(pull_request)
141 target_vcs = pull_request.target_repo.scm_instance()
141 target_vcs = pull_request.target_repo.scm_instance()
142 vcs_repo_name = target_vcs._get_shadow_repository_path(
142 vcs_repo_name = target_vcs._get_shadow_repository_path(
143 workspace_id)
143 workspace_id)
144
144
145 # Store names for later usage.
145 # Store names for later usage.
146 self.pr_id = pr_id
147 self.vcs_repo_name = vcs_repo_name
146 self.vcs_repo_name = vcs_repo_name
148 self.acl_repo_name = pull_request.target_repo.repo_name
147 self.acl_repo_name = pull_request.target_repo.repo_name
148 self.is_shadow_repo = True
149 else:
149 else:
150 # All names are equal for normal (non shadow) repositories.
150 # All names are equal for normal (non shadow) repositories.
151 self.acl_repo_name = self.url_repo_name
151 self.acl_repo_name = self.url_repo_name
152 self.vcs_repo_name = self.url_repo_name
152 self.vcs_repo_name = self.url_repo_name
153 self.pr_id = None
153 self.is_shadow_repo = False
154
154
155 @property
155 @property
156 def scm_app(self):
156 def scm_app(self):
157 custom_implementation = self.config.get('vcs.scm_app_implementation')
157 custom_implementation = self.config.get('vcs.scm_app_implementation')
158 if custom_implementation and custom_implementation != 'pyro4':
158 if custom_implementation and custom_implementation != 'pyro4':
159 log.info(
159 log.info(
160 "Using custom implementation of scm_app: %s",
160 "Using custom implementation of scm_app: %s",
161 custom_implementation)
161 custom_implementation)
162 scm_app_impl = importlib.import_module(custom_implementation)
162 scm_app_impl = importlib.import_module(custom_implementation)
163 else:
163 else:
164 scm_app_impl = scm_app
164 scm_app_impl = scm_app
165 return scm_app_impl
165 return scm_app_impl
166
166
167 def _get_by_id(self, repo_name):
167 def _get_by_id(self, repo_name):
168 """
168 """
169 Gets a special pattern _<ID> from clone url and tries to replace it
169 Gets a special pattern _<ID> from clone url and tries to replace it
170 with a repository_name for support of _<ID> non changeable urls
170 with a repository_name for support of _<ID> non changeable urls
171 """
171 """
172
172
173 data = repo_name.split('/')
173 data = repo_name.split('/')
174 if len(data) >= 2:
174 if len(data) >= 2:
175 from rhodecode.model.repo import RepoModel
175 from rhodecode.model.repo import RepoModel
176 by_id_match = RepoModel().get_repo_by_id(repo_name)
176 by_id_match = RepoModel().get_repo_by_id(repo_name)
177 if by_id_match:
177 if by_id_match:
178 data[1] = by_id_match.repo_name
178 data[1] = by_id_match.repo_name
179
179
180 return safe_str('/'.join(data))
180 return safe_str('/'.join(data))
181
181
182 def _invalidate_cache(self, repo_name):
182 def _invalidate_cache(self, repo_name):
183 """
183 """
184 Set's cache for this repository for invalidation on next access
184 Set's cache for this repository for invalidation on next access
185
185
186 :param repo_name: full repo name, also a cache key
186 :param repo_name: full repo name, also a cache key
187 """
187 """
188 ScmModel().mark_for_invalidation(repo_name)
188 ScmModel().mark_for_invalidation(repo_name)
189
189
190 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
190 def is_valid_and_existing_repo(self, repo_name, base_path, scm_type):
191 db_repo = Repository.get_by_repo_name(repo_name)
191 db_repo = Repository.get_by_repo_name(repo_name)
192 if not db_repo:
192 if not db_repo:
193 log.debug('Repository `%s` not found inside the database.',
193 log.debug('Repository `%s` not found inside the database.',
194 repo_name)
194 repo_name)
195 return False
195 return False
196
196
197 if db_repo.repo_type != scm_type:
197 if db_repo.repo_type != scm_type:
198 log.warning(
198 log.warning(
199 'Repository `%s` have incorrect scm_type, expected %s got %s',
199 'Repository `%s` have incorrect scm_type, expected %s got %s',
200 repo_name, db_repo.repo_type, scm_type)
200 repo_name, db_repo.repo_type, scm_type)
201 return False
201 return False
202
202
203 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
203 return is_valid_repo(repo_name, base_path, expect_scm=scm_type)
204
204
205 def valid_and_active_user(self, user):
205 def valid_and_active_user(self, user):
206 """
206 """
207 Checks if that user is not empty, and if it's actually object it checks
207 Checks if that user is not empty, and if it's actually object it checks
208 if he's active.
208 if he's active.
209
209
210 :param user: user object or None
210 :param user: user object or None
211 :return: boolean
211 :return: boolean
212 """
212 """
213 if user is None:
213 if user is None:
214 return False
214 return False
215
215
216 elif user.active:
216 elif user.active:
217 return True
217 return True
218
218
219 return False
219 return False
220
220
221 def _check_permission(self, action, user, repo_name, ip_addr=None):
221 def _check_permission(self, action, user, repo_name, ip_addr=None):
222 """
222 """
223 Checks permissions using action (push/pull) user and repository
223 Checks permissions using action (push/pull) user and repository
224 name
224 name
225
225
226 :param action: push or pull action
226 :param action: push or pull action
227 :param user: user instance
227 :param user: user instance
228 :param repo_name: repository name
228 :param repo_name: repository name
229 """
229 """
230 # check IP
230 # check IP
231 inherit = user.inherit_default_permissions
231 inherit = user.inherit_default_permissions
232 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
232 ip_allowed = AuthUser.check_ip_allowed(user.user_id, ip_addr,
233 inherit_from_default=inherit)
233 inherit_from_default=inherit)
234 if ip_allowed:
234 if ip_allowed:
235 log.info('Access for IP:%s allowed', ip_addr)
235 log.info('Access for IP:%s allowed', ip_addr)
236 else:
236 else:
237 return False
237 return False
238
238
239 if action == 'push':
239 if action == 'push':
240 if not HasPermissionAnyMiddleware('repository.write',
240 if not HasPermissionAnyMiddleware('repository.write',
241 'repository.admin')(user,
241 'repository.admin')(user,
242 repo_name):
242 repo_name):
243 return False
243 return False
244
244
245 else:
245 else:
246 # any other action need at least read permission
246 # any other action need at least read permission
247 if not HasPermissionAnyMiddleware('repository.read',
247 if not HasPermissionAnyMiddleware('repository.read',
248 'repository.write',
248 'repository.write',
249 'repository.admin')(user,
249 'repository.admin')(user,
250 repo_name):
250 repo_name):
251 return False
251 return False
252
252
253 return True
253 return True
254
254
255 def _check_ssl(self, environ, start_response):
255 def _check_ssl(self, environ, start_response):
256 """
256 """
257 Checks the SSL check flag and returns False if SSL is not present
257 Checks the SSL check flag and returns False if SSL is not present
258 and required True otherwise
258 and required True otherwise
259 """
259 """
260 org_proto = environ['wsgi._org_proto']
260 org_proto = environ['wsgi._org_proto']
261 # check if we have SSL required ! if not it's a bad request !
261 # check if we have SSL required ! if not it's a bad request !
262 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
262 require_ssl = str2bool(self.repo_vcs_config.get('web', 'push_ssl'))
263 if require_ssl and org_proto == 'http':
263 if require_ssl and org_proto == 'http':
264 log.debug('proto is %s and SSL is required BAD REQUEST !',
264 log.debug('proto is %s and SSL is required BAD REQUEST !',
265 org_proto)
265 org_proto)
266 return False
266 return False
267 return True
267 return True
268
268
269 def __call__(self, environ, start_response):
269 def __call__(self, environ, start_response):
270 try:
270 try:
271 return self._handle_request(environ, start_response)
271 return self._handle_request(environ, start_response)
272 except Exception:
272 except Exception:
273 log.exception("Exception while handling request")
273 log.exception("Exception while handling request")
274 appenlight.track_exception(environ)
274 appenlight.track_exception(environ)
275 return HTTPInternalServerError()(environ, start_response)
275 return HTTPInternalServerError()(environ, start_response)
276 finally:
276 finally:
277 meta.Session.remove()
277 meta.Session.remove()
278
278
279 def _handle_request(self, environ, start_response):
279 def _handle_request(self, environ, start_response):
280
280
281 if not self._check_ssl(environ, start_response):
281 if not self._check_ssl(environ, start_response):
282 reason = ('SSL required, while RhodeCode was unable '
282 reason = ('SSL required, while RhodeCode was unable '
283 'to detect this as SSL request')
283 'to detect this as SSL request')
284 log.debug('User not allowed to proceed, %s', reason)
284 log.debug('User not allowed to proceed, %s', reason)
285 return HTTPNotAcceptable(reason)(environ, start_response)
285 return HTTPNotAcceptable(reason)(environ, start_response)
286
286
287 if not self.url_repo_name:
287 if not self.url_repo_name:
288 log.warning('Repository name is empty: %s', self.url_repo_name)
288 log.warning('Repository name is empty: %s', self.url_repo_name)
289 # failed to get repo name, we fail now
289 # failed to get repo name, we fail now
290 return HTTPNotFound()(environ, start_response)
290 return HTTPNotFound()(environ, start_response)
291 log.debug('Extracted repo name is %s', self.url_repo_name)
291 log.debug('Extracted repo name is %s', self.url_repo_name)
292
292
293 ip_addr = get_ip_addr(environ)
293 ip_addr = get_ip_addr(environ)
294 username = None
294 username = None
295
295
296 # skip passing error to error controller
296 # skip passing error to error controller
297 environ['pylons.status_code_redirect'] = True
297 environ['pylons.status_code_redirect'] = True
298
298
299 # ======================================================================
299 # ======================================================================
300 # GET ACTION PULL or PUSH
300 # GET ACTION PULL or PUSH
301 # ======================================================================
301 # ======================================================================
302 action = self._get_action(environ)
302 action = self._get_action(environ)
303
303
304 # ======================================================================
304 # ======================================================================
305 # Check if this is a request to a shadow repository of a pull request.
305 # Check if this is a request to a shadow repository of a pull request.
306 # In this case only pull action is allowed.
306 # In this case only pull action is allowed.
307 # ======================================================================
307 # ======================================================================
308 if self.pr_id is not None and action != 'pull':
308 if self.is_shadow_repo and action != 'pull':
309 reason = 'Only pull action is allowed for shadow repositories.'
309 reason = 'Only pull action is allowed for shadow repositories.'
310 log.debug('User not allowed to proceed, %s', reason)
310 log.debug('User not allowed to proceed, %s', reason)
311 return HTTPNotAcceptable(reason)(environ, start_response)
311 return HTTPNotAcceptable(reason)(environ, start_response)
312
312
313 # ======================================================================
313 # ======================================================================
314 # CHECK ANONYMOUS PERMISSION
314 # CHECK ANONYMOUS PERMISSION
315 # ======================================================================
315 # ======================================================================
316 if action in ['pull', 'push']:
316 if action in ['pull', 'push']:
317 anonymous_user = User.get_default_user()
317 anonymous_user = User.get_default_user()
318 username = anonymous_user.username
318 username = anonymous_user.username
319 if anonymous_user.active:
319 if anonymous_user.active:
320 # ONLY check permissions if the user is activated
320 # ONLY check permissions if the user is activated
321 anonymous_perm = self._check_permission(
321 anonymous_perm = self._check_permission(
322 action, anonymous_user, self.acl_repo_name, ip_addr)
322 action, anonymous_user, self.acl_repo_name, ip_addr)
323 else:
323 else:
324 anonymous_perm = False
324 anonymous_perm = False
325
325
326 if not anonymous_user.active or not anonymous_perm:
326 if not anonymous_user.active or not anonymous_perm:
327 if not anonymous_user.active:
327 if not anonymous_user.active:
328 log.debug('Anonymous access is disabled, running '
328 log.debug('Anonymous access is disabled, running '
329 'authentication')
329 'authentication')
330
330
331 if not anonymous_perm:
331 if not anonymous_perm:
332 log.debug('Not enough credentials to access this '
332 log.debug('Not enough credentials to access this '
333 'repository as anonymous user')
333 'repository as anonymous user')
334
334
335 username = None
335 username = None
336 # ==============================================================
336 # ==============================================================
337 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
337 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
338 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
338 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
339 # ==============================================================
339 # ==============================================================
340
340
341 # try to auth based on environ, container auth methods
341 # try to auth based on environ, container auth methods
342 log.debug('Running PRE-AUTH for container based authentication')
342 log.debug('Running PRE-AUTH for container based authentication')
343 pre_auth = authenticate(
343 pre_auth = authenticate(
344 '', '', environ, VCS_TYPE, registry=self.registry)
344 '', '', environ, VCS_TYPE, registry=self.registry)
345 if pre_auth and pre_auth.get('username'):
345 if pre_auth and pre_auth.get('username'):
346 username = pre_auth['username']
346 username = pre_auth['username']
347 log.debug('PRE-AUTH got %s as username', username)
347 log.debug('PRE-AUTH got %s as username', username)
348
348
349 # If not authenticated by the container, running basic auth
349 # If not authenticated by the container, running basic auth
350 if not username:
350 if not username:
351 self.authenticate.realm = get_rhodecode_realm()
351 self.authenticate.realm = get_rhodecode_realm()
352
352
353 try:
353 try:
354 result = self.authenticate(environ)
354 result = self.authenticate(environ)
355 except (UserCreationError, NotAllowedToCreateUserError) as e:
355 except (UserCreationError, NotAllowedToCreateUserError) as e:
356 log.error(e)
356 log.error(e)
357 reason = safe_str(e)
357 reason = safe_str(e)
358 return HTTPNotAcceptable(reason)(environ, start_response)
358 return HTTPNotAcceptable(reason)(environ, start_response)
359
359
360 if isinstance(result, str):
360 if isinstance(result, str):
361 AUTH_TYPE.update(environ, 'basic')
361 AUTH_TYPE.update(environ, 'basic')
362 REMOTE_USER.update(environ, result)
362 REMOTE_USER.update(environ, result)
363 username = result
363 username = result
364 else:
364 else:
365 return result.wsgi_application(environ, start_response)
365 return result.wsgi_application(environ, start_response)
366
366
367 # ==============================================================
367 # ==============================================================
368 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
368 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
369 # ==============================================================
369 # ==============================================================
370 user = User.get_by_username(username)
370 user = User.get_by_username(username)
371 if not self.valid_and_active_user(user):
371 if not self.valid_and_active_user(user):
372 return HTTPForbidden()(environ, start_response)
372 return HTTPForbidden()(environ, start_response)
373 username = user.username
373 username = user.username
374 user.update_lastactivity()
374 user.update_lastactivity()
375 meta.Session().commit()
375 meta.Session().commit()
376
376
377 # check user attributes for password change flag
377 # check user attributes for password change flag
378 user_obj = user
378 user_obj = user
379 if user_obj and user_obj.username != User.DEFAULT_USER and \
379 if user_obj and user_obj.username != User.DEFAULT_USER and \
380 user_obj.user_data.get('force_password_change'):
380 user_obj.user_data.get('force_password_change'):
381 reason = 'password change required'
381 reason = 'password change required'
382 log.debug('User not allowed to authenticate, %s', reason)
382 log.debug('User not allowed to authenticate, %s', reason)
383 return HTTPNotAcceptable(reason)(environ, start_response)
383 return HTTPNotAcceptable(reason)(environ, start_response)
384
384
385 # check permissions for this repository
385 # check permissions for this repository
386 perm = self._check_permission(
386 perm = self._check_permission(
387 action, user, self.acl_repo_name, ip_addr)
387 action, user, self.acl_repo_name, ip_addr)
388 if not perm:
388 if not perm:
389 return HTTPForbidden()(environ, start_response)
389 return HTTPForbidden()(environ, start_response)
390
390
391 # extras are injected into UI object and later available
391 # extras are injected into UI object and later available
392 # in hooks executed by rhodecode
392 # in hooks executed by rhodecode
393 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
393 check_locking = _should_check_locking(environ.get('QUERY_STRING'))
394 extras = vcs_operation_context(
394 extras = vcs_operation_context(
395 environ, repo_name=self.acl_repo_name, username=username,
395 environ, repo_name=self.acl_repo_name, username=username,
396 action=action, scm=self.SCM,
396 action=action, scm=self.SCM, check_locking=check_locking,
397 check_locking=check_locking)
397 is_shadow_repo=self.is_shadow_repo
398 )
398
399
399 # ======================================================================
400 # ======================================================================
400 # REQUEST HANDLING
401 # REQUEST HANDLING
401 # ======================================================================
402 # ======================================================================
402 str_repo_name = safe_str(self.url_repo_name)
403 str_repo_name = safe_str(self.url_repo_name)
403 repo_path = os.path.join(
404 repo_path = os.path.join(
404 safe_str(self.basepath), safe_str(self.vcs_repo_name))
405 safe_str(self.basepath), safe_str(self.vcs_repo_name))
405 log.debug('Repository path is %s', repo_path)
406 log.debug('Repository path is %s', repo_path)
406
407
407 fix_PATH()
408 fix_PATH()
408
409
409 log.info(
410 log.info(
410 '%s action on %s repo "%s" by "%s" from %s',
411 '%s action on %s repo "%s" by "%s" from %s',
411 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
412 action, self.SCM, str_repo_name, safe_str(username), ip_addr)
412
413
413 return self._generate_vcs_response(
414 return self._generate_vcs_response(
414 environ, start_response, repo_path, self.url_repo_name, extras, action)
415 environ, start_response, repo_path, self.url_repo_name, extras, action)
415
416
416 @initialize_generator
417 @initialize_generator
417 def _generate_vcs_response(
418 def _generate_vcs_response(
418 self, environ, start_response, repo_path, repo_name, extras,
419 self, environ, start_response, repo_path, repo_name, extras,
419 action):
420 action):
420 """
421 """
421 Returns a generator for the response content.
422 Returns a generator for the response content.
422
423
423 This method is implemented as a generator, so that it can trigger
424 This method is implemented as a generator, so that it can trigger
424 the cache validation after all content sent back to the client. It
425 the cache validation after all content sent back to the client. It
425 also handles the locking exceptions which will be triggered when
426 also handles the locking exceptions which will be triggered when
426 the first chunk is produced by the underlying WSGI application.
427 the first chunk is produced by the underlying WSGI application.
427 """
428 """
428 callback_daemon, extras = self._prepare_callback_daemon(extras)
429 callback_daemon, extras = self._prepare_callback_daemon(extras)
429 config = self._create_config(extras, self.acl_repo_name)
430 config = self._create_config(extras, self.acl_repo_name)
430 log.debug('HOOKS extras is %s', extras)
431 log.debug('HOOKS extras is %s', extras)
431 app = self._create_wsgi_app(repo_path, repo_name, config)
432 app = self._create_wsgi_app(repo_path, repo_name, config)
432
433
433 try:
434 try:
434 with callback_daemon:
435 with callback_daemon:
435 try:
436 try:
436 response = app(environ, start_response)
437 response = app(environ, start_response)
437 finally:
438 finally:
438 # This statement works together with the decorator
439 # This statement works together with the decorator
439 # "initialize_generator" above. The decorator ensures that
440 # "initialize_generator" above. The decorator ensures that
440 # we hit the first yield statement before the generator is
441 # we hit the first yield statement before the generator is
441 # returned back to the WSGI server. This is needed to
442 # returned back to the WSGI server. This is needed to
442 # ensure that the call to "app" above triggers the
443 # ensure that the call to "app" above triggers the
443 # needed callback to "start_response" before the
444 # needed callback to "start_response" before the
444 # generator is actually used.
445 # generator is actually used.
445 yield "__init__"
446 yield "__init__"
446
447
447 for chunk in response:
448 for chunk in response:
448 yield chunk
449 yield chunk
449 except Exception as exc:
450 except Exception as exc:
450 # TODO: johbo: Improve "translating" back the exception.
451 # TODO: johbo: Improve "translating" back the exception.
451 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
452 if getattr(exc, '_vcs_kind', None) == 'repo_locked':
452 exc = HTTPLockedRC(*exc.args)
453 exc = HTTPLockedRC(*exc.args)
453 _code = rhodecode.CONFIG.get('lock_ret_code')
454 _code = rhodecode.CONFIG.get('lock_ret_code')
454 log.debug('Repository LOCKED ret code %s!', (_code,))
455 log.debug('Repository LOCKED ret code %s!', (_code,))
455 elif getattr(exc, '_vcs_kind', None) == 'requirement':
456 elif getattr(exc, '_vcs_kind', None) == 'requirement':
456 log.debug(
457 log.debug(
457 'Repository requires features unknown to this Mercurial')
458 'Repository requires features unknown to this Mercurial')
458 exc = HTTPRequirementError(*exc.args)
459 exc = HTTPRequirementError(*exc.args)
459 else:
460 else:
460 raise
461 raise
461
462
462 for chunk in exc(environ, start_response):
463 for chunk in exc(environ, start_response):
463 yield chunk
464 yield chunk
464 finally:
465 finally:
465 # invalidate cache on push
466 # invalidate cache on push
466 try:
467 try:
467 if action == 'push':
468 if action == 'push':
468 self._invalidate_cache(repo_name)
469 self._invalidate_cache(repo_name)
469 finally:
470 finally:
470 meta.Session.remove()
471 meta.Session.remove()
471
472
472 def _get_repository_name(self, environ):
473 def _get_repository_name(self, environ):
473 """Get repository name out of the environmnent
474 """Get repository name out of the environmnent
474
475
475 :param environ: WSGI environment
476 :param environ: WSGI environment
476 """
477 """
477 raise NotImplementedError()
478 raise NotImplementedError()
478
479
479 def _get_action(self, environ):
480 def _get_action(self, environ):
480 """Map request commands into a pull or push command.
481 """Map request commands into a pull or push command.
481
482
482 :param environ: WSGI environment
483 :param environ: WSGI environment
483 """
484 """
484 raise NotImplementedError()
485 raise NotImplementedError()
485
486
486 def _create_wsgi_app(self, repo_path, repo_name, config):
487 def _create_wsgi_app(self, repo_path, repo_name, config):
487 """Return the WSGI app that will finally handle the request."""
488 """Return the WSGI app that will finally handle the request."""
488 raise NotImplementedError()
489 raise NotImplementedError()
489
490
490 def _create_config(self, extras, repo_name):
491 def _create_config(self, extras, repo_name):
491 """Create a Pyro safe config representation."""
492 """Create a Pyro safe config representation."""
492 raise NotImplementedError()
493 raise NotImplementedError()
493
494
494 def _prepare_callback_daemon(self, extras):
495 def _prepare_callback_daemon(self, extras):
495 return prepare_callback_daemon(
496 return prepare_callback_daemon(
496 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
497 extras, protocol=vcs_settings.HOOKS_PROTOCOL,
497 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
498 use_direct_calls=vcs_settings.HOOKS_DIRECT_CALLS)
498
499
499
500
500 def _should_check_locking(query_string):
501 def _should_check_locking(query_string):
501 # this is kind of hacky, but due to how mercurial handles client-server
502 # this is kind of hacky, but due to how mercurial handles client-server
502 # server see all operation on commit; bookmarks, phases and
503 # server see all operation on commit; bookmarks, phases and
503 # obsolescence marker in different transaction, we don't want to check
504 # obsolescence marker in different transaction, we don't want to check
504 # locking on those
505 # locking on those
505 return query_string not in ['cmd=listkeys']
506 return query_string not in ['cmd=listkeys']
General Comments 0
You need to be logged in to leave comments. Login now