##// END OF EJS Templates
token-access: allow token in headers not only in GET/URL
milka -
r4608:374a996c stable
parent child Browse files
Show More
@@ -1,619 +1,626 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 markupsafe
30 import markupsafe
31 import ipaddress
31 import ipaddress
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
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.apps._base import TemplateArgs
38 from rhodecode.apps._base import TemplateArgs
39 from rhodecode.authentication.base import VCS_TYPE
39 from rhodecode.authentication.base import VCS_TYPE
40 from rhodecode.lib import auth, utils2
40 from rhodecode.lib import auth, utils2
41 from rhodecode.lib import helpers as h
41 from rhodecode.lib import helpers as h
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
42 from rhodecode.lib.auth import AuthUser, CookieStoreWrapper
43 from rhodecode.lib.exceptions import UserCreationError
43 from rhodecode.lib.exceptions import UserCreationError
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
44 from rhodecode.lib.utils import (password_changed, get_enabled_hook_classes)
45 from rhodecode.lib.utils2 import (
45 from rhodecode.lib.utils2 import (
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
46 str2bool, safe_unicode, AttributeDict, safe_int, sha1, aslist, safe_str)
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
47 from rhodecode.model.db import Repository, User, ChangesetComment, UserBookmark
48 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.notification import NotificationModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
49 from rhodecode.model.settings import VcsSettingsModel, SettingsModel
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _filter_proxy(ip):
54 def _filter_proxy(ip):
55 """
55 """
56 Passed in IP addresses in HEADERS can be in a special format of multiple
56 Passed in IP addresses in HEADERS can be in a special format of multiple
57 ips. Those comma separated IPs are passed from various proxies in the
57 ips. Those comma separated IPs are passed from various proxies in the
58 chain of request processing. The left-most being the original client.
58 chain of request processing. The left-most being the original client.
59 We only care about the first IP which came from the org. client.
59 We only care about the first IP which came from the org. client.
60
60
61 :param ip: ip string from headers
61 :param ip: ip string from headers
62 """
62 """
63 if ',' in ip:
63 if ',' in ip:
64 _ips = ip.split(',')
64 _ips = ip.split(',')
65 _first_ip = _ips[0].strip()
65 _first_ip = _ips[0].strip()
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
66 log.debug('Got multiple IPs %s, using %s', ','.join(_ips), _first_ip)
67 return _first_ip
67 return _first_ip
68 return ip
68 return ip
69
69
70
70
71 def _filter_port(ip):
71 def _filter_port(ip):
72 """
72 """
73 Removes a port from ip, there are 4 main cases to handle here.
73 Removes a port from ip, there are 4 main cases to handle here.
74 - ipv4 eg. 127.0.0.1
74 - ipv4 eg. 127.0.0.1
75 - ipv6 eg. ::1
75 - ipv6 eg. ::1
76 - ipv4+port eg. 127.0.0.1:8080
76 - ipv4+port eg. 127.0.0.1:8080
77 - ipv6+port eg. [::1]:8080
77 - ipv6+port eg. [::1]:8080
78
78
79 :param ip:
79 :param ip:
80 """
80 """
81 def is_ipv6(ip_addr):
81 def is_ipv6(ip_addr):
82 if hasattr(socket, 'inet_pton'):
82 if hasattr(socket, 'inet_pton'):
83 try:
83 try:
84 socket.inet_pton(socket.AF_INET6, ip_addr)
84 socket.inet_pton(socket.AF_INET6, ip_addr)
85 except socket.error:
85 except socket.error:
86 return False
86 return False
87 else:
87 else:
88 # fallback to ipaddress
88 # fallback to ipaddress
89 try:
89 try:
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
90 ipaddress.IPv6Address(safe_unicode(ip_addr))
91 except Exception:
91 except Exception:
92 return False
92 return False
93 return True
93 return True
94
94
95 if ':' not in ip: # must be ipv4 pure ip
95 if ':' not in ip: # must be ipv4 pure ip
96 return ip
96 return ip
97
97
98 if '[' in ip and ']' in ip: # ipv6 with port
98 if '[' in ip and ']' in ip: # ipv6 with port
99 return ip.split(']')[0][1:].lower()
99 return ip.split(']')[0][1:].lower()
100
100
101 # must be ipv6 or ipv4 with port
101 # must be ipv6 or ipv4 with port
102 if is_ipv6(ip):
102 if is_ipv6(ip):
103 return ip
103 return ip
104 else:
104 else:
105 ip, _port = ip.split(':')[:2] # means ipv4+port
105 ip, _port = ip.split(':')[:2] # means ipv4+port
106 return ip
106 return ip
107
107
108
108
109 def get_ip_addr(environ):
109 def get_ip_addr(environ):
110 proxy_key = 'HTTP_X_REAL_IP'
110 proxy_key = 'HTTP_X_REAL_IP'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
111 proxy_key2 = 'HTTP_X_FORWARDED_FOR'
112 def_key = 'REMOTE_ADDR'
112 def_key = 'REMOTE_ADDR'
113 _filters = lambda x: _filter_port(_filter_proxy(x))
113 _filters = lambda x: _filter_port(_filter_proxy(x))
114
114
115 ip = environ.get(proxy_key)
115 ip = environ.get(proxy_key)
116 if ip:
116 if ip:
117 return _filters(ip)
117 return _filters(ip)
118
118
119 ip = environ.get(proxy_key2)
119 ip = environ.get(proxy_key2)
120 if ip:
120 if ip:
121 return _filters(ip)
121 return _filters(ip)
122
122
123 ip = environ.get(def_key, '0.0.0.0')
123 ip = environ.get(def_key, '0.0.0.0')
124 return _filters(ip)
124 return _filters(ip)
125
125
126
126
127 def get_server_ip_addr(environ, log_errors=True):
127 def get_server_ip_addr(environ, log_errors=True):
128 hostname = environ.get('SERVER_NAME')
128 hostname = environ.get('SERVER_NAME')
129 try:
129 try:
130 return socket.gethostbyname(hostname)
130 return socket.gethostbyname(hostname)
131 except Exception as e:
131 except Exception as e:
132 if log_errors:
132 if log_errors:
133 # in some cases this lookup is not possible, and we don't want to
133 # in some cases this lookup is not possible, and we don't want to
134 # make it an exception in logs
134 # make it an exception in logs
135 log.exception('Could not retrieve server ip address: %s', e)
135 log.exception('Could not retrieve server ip address: %s', e)
136 return hostname
136 return hostname
137
137
138
138
139 def get_server_port(environ):
139 def get_server_port(environ):
140 return environ.get('SERVER_PORT')
140 return environ.get('SERVER_PORT')
141
141
142
142
143 def get_access_path(environ):
143 def get_access_path(environ):
144 path = environ.get('PATH_INFO')
144 path = environ.get('PATH_INFO')
145 org_req = environ.get('pylons.original_request')
145 org_req = environ.get('pylons.original_request')
146 if org_req:
146 if org_req:
147 path = org_req.environ.get('PATH_INFO')
147 path = org_req.environ.get('PATH_INFO')
148 return path
148 return path
149
149
150
150
151 def get_user_agent(environ):
151 def get_user_agent(environ):
152 return environ.get('HTTP_USER_AGENT')
152 return environ.get('HTTP_USER_AGENT')
153
153
154
154
155 def vcs_operation_context(
155 def vcs_operation_context(
156 environ, repo_name, username, action, scm, check_locking=True,
156 environ, repo_name, username, action, scm, check_locking=True,
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
157 is_shadow_repo=False, check_branch_perms=False, detect_force_push=False):
158 """
158 """
159 Generate the context for a vcs operation, e.g. push or pull.
159 Generate the context for a vcs operation, e.g. push or pull.
160
160
161 This context is passed over the layers so that hooks triggered by the
161 This context is passed over the layers so that hooks triggered by the
162 vcs operation know details like the user, the user's IP address etc.
162 vcs operation know details like the user, the user's IP address etc.
163
163
164 :param check_locking: Allows to switch of the computation of the locking
164 :param check_locking: Allows to switch of the computation of the locking
165 data. This serves mainly the need of the simplevcs middleware to be
165 data. This serves mainly the need of the simplevcs middleware to be
166 able to disable this for certain operations.
166 able to disable this for certain operations.
167
167
168 """
168 """
169 # Tri-state value: False: unlock, None: nothing, True: lock
169 # Tri-state value: False: unlock, None: nothing, True: lock
170 make_lock = None
170 make_lock = None
171 locked_by = [None, None, None]
171 locked_by = [None, None, None]
172 is_anonymous = username == User.DEFAULT_USER
172 is_anonymous = username == User.DEFAULT_USER
173 user = User.get_by_username(username)
173 user = User.get_by_username(username)
174 if not is_anonymous and check_locking:
174 if not is_anonymous and check_locking:
175 log.debug('Checking locking on repository "%s"', repo_name)
175 log.debug('Checking locking on repository "%s"', repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
176 repo = Repository.get_by_repo_name(repo_name)
177 make_lock, __, locked_by = repo.get_locking_state(
177 make_lock, __, locked_by = repo.get_locking_state(
178 action, user.user_id)
178 action, user.user_id)
179 user_id = user.user_id
179 user_id = user.user_id
180 settings_model = VcsSettingsModel(repo=repo_name)
180 settings_model = VcsSettingsModel(repo=repo_name)
181 ui_settings = settings_model.get_ui_settings()
181 ui_settings = settings_model.get_ui_settings()
182
182
183 # NOTE(marcink): This should be also in sync with
183 # NOTE(marcink): This should be also in sync with
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
184 # rhodecode/apps/ssh_support/lib/backends/base.py:update_environment scm_data
185 store = [x for x in ui_settings if x.key == '/']
185 store = [x for x in ui_settings if x.key == '/']
186 repo_store = ''
186 repo_store = ''
187 if store:
187 if store:
188 repo_store = store[0].value
188 repo_store = store[0].value
189
189
190 scm_data = {
190 scm_data = {
191 'ip': get_ip_addr(environ),
191 'ip': get_ip_addr(environ),
192 'username': username,
192 'username': username,
193 'user_id': user_id,
193 'user_id': user_id,
194 'action': action,
194 'action': action,
195 'repository': repo_name,
195 'repository': repo_name,
196 'scm': scm,
196 'scm': scm,
197 'config': rhodecode.CONFIG['__file__'],
197 'config': rhodecode.CONFIG['__file__'],
198 'repo_store': repo_store,
198 'repo_store': repo_store,
199 'make_lock': make_lock,
199 'make_lock': make_lock,
200 'locked_by': locked_by,
200 'locked_by': locked_by,
201 'server_url': utils2.get_server_url(environ),
201 'server_url': utils2.get_server_url(environ),
202 'user_agent': get_user_agent(environ),
202 'user_agent': get_user_agent(environ),
203 'hooks': get_enabled_hook_classes(ui_settings),
203 'hooks': get_enabled_hook_classes(ui_settings),
204 'is_shadow_repo': is_shadow_repo,
204 'is_shadow_repo': is_shadow_repo,
205 'detect_force_push': detect_force_push,
205 'detect_force_push': detect_force_push,
206 'check_branch_perms': check_branch_perms,
206 'check_branch_perms': check_branch_perms,
207 }
207 }
208 return scm_data
208 return scm_data
209
209
210
210
211 class BasicAuth(AuthBasicAuthenticator):
211 class BasicAuth(AuthBasicAuthenticator):
212
212
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
213 def __init__(self, realm, authfunc, registry, auth_http_code=None,
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
214 initial_call_detection=False, acl_repo_name=None, rc_realm=''):
215 self.realm = realm
215 self.realm = realm
216 self.rc_realm = rc_realm
216 self.rc_realm = rc_realm
217 self.initial_call = initial_call_detection
217 self.initial_call = initial_call_detection
218 self.authfunc = authfunc
218 self.authfunc = authfunc
219 self.registry = registry
219 self.registry = registry
220 self.acl_repo_name = acl_repo_name
220 self.acl_repo_name = acl_repo_name
221 self._rc_auth_http_code = auth_http_code
221 self._rc_auth_http_code = auth_http_code
222
222
223 def _get_response_from_code(self, http_code):
223 def _get_response_from_code(self, http_code):
224 try:
224 try:
225 return get_exception(safe_int(http_code))
225 return get_exception(safe_int(http_code))
226 except Exception:
226 except Exception:
227 log.exception('Failed to fetch response for code %s', http_code)
227 log.exception('Failed to fetch response for code %s', http_code)
228 return HTTPForbidden
228 return HTTPForbidden
229
229
230 def get_rc_realm(self):
230 def get_rc_realm(self):
231 return safe_str(self.rc_realm)
231 return safe_str(self.rc_realm)
232
232
233 def build_authentication(self):
233 def build_authentication(self):
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
234 head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
235 if self._rc_auth_http_code and not self.initial_call:
235 if self._rc_auth_http_code and not self.initial_call:
236 # return alternative HTTP code if alternative http return code
236 # return alternative HTTP code if alternative http return code
237 # is specified in RhodeCode config, but ONLY if it's not the
237 # is specified in RhodeCode config, but ONLY if it's not the
238 # FIRST call
238 # FIRST call
239 custom_response_klass = self._get_response_from_code(
239 custom_response_klass = self._get_response_from_code(
240 self._rc_auth_http_code)
240 self._rc_auth_http_code)
241 return custom_response_klass(headers=head)
241 return custom_response_klass(headers=head)
242 return HTTPUnauthorized(headers=head)
242 return HTTPUnauthorized(headers=head)
243
243
244 def authenticate(self, environ):
244 def authenticate(self, environ):
245 authorization = AUTHORIZATION(environ)
245 authorization = AUTHORIZATION(environ)
246 if not authorization:
246 if not authorization:
247 return self.build_authentication()
247 return self.build_authentication()
248 (authmeth, auth) = authorization.split(' ', 1)
248 (authmeth, auth) = authorization.split(' ', 1)
249 if 'basic' != authmeth.lower():
249 if 'basic' != authmeth.lower():
250 return self.build_authentication()
250 return self.build_authentication()
251 auth = auth.strip().decode('base64')
251 auth = auth.strip().decode('base64')
252 _parts = auth.split(':', 1)
252 _parts = auth.split(':', 1)
253 if len(_parts) == 2:
253 if len(_parts) == 2:
254 username, password = _parts
254 username, password = _parts
255 auth_data = self.authfunc(
255 auth_data = self.authfunc(
256 username, password, environ, VCS_TYPE,
256 username, password, environ, VCS_TYPE,
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
257 registry=self.registry, acl_repo_name=self.acl_repo_name)
258 if auth_data:
258 if auth_data:
259 return {'username': username, 'auth_data': auth_data}
259 return {'username': username, 'auth_data': auth_data}
260 if username and password:
260 if username and password:
261 # we mark that we actually executed authentication once, at
261 # we mark that we actually executed authentication once, at
262 # that point we can use the alternative auth code
262 # that point we can use the alternative auth code
263 self.initial_call = False
263 self.initial_call = False
264
264
265 return self.build_authentication()
265 return self.build_authentication()
266
266
267 __call__ = authenticate
267 __call__ = authenticate
268
268
269
269
270 def calculate_version_hash(config):
270 def calculate_version_hash(config):
271 return sha1(
271 return sha1(
272 config.get('beaker.session.secret', '') +
272 config.get('beaker.session.secret', '') +
273 rhodecode.__version__)[:8]
273 rhodecode.__version__)[:8]
274
274
275
275
276 def get_current_lang(request):
276 def get_current_lang(request):
277 # NOTE(marcink): remove after pyramid move
277 # NOTE(marcink): remove after pyramid move
278 try:
278 try:
279 return translation.get_lang()[0]
279 return translation.get_lang()[0]
280 except:
280 except:
281 pass
281 pass
282
282
283 return getattr(request, '_LOCALE_', request.locale_name)
283 return getattr(request, '_LOCALE_', request.locale_name)
284
284
285
285
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
286 def attach_context_attributes(context, request, user_id=None, is_api=None):
287 """
287 """
288 Attach variables into template context called `c`.
288 Attach variables into template context called `c`.
289 """
289 """
290 config = request.registry.settings
290 config = request.registry.settings
291
291
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
292 rc_config = SettingsModel().get_all_settings(cache=True, from_request=False)
293 context.rc_config = rc_config
293 context.rc_config = rc_config
294 context.rhodecode_version = rhodecode.__version__
294 context.rhodecode_version = rhodecode.__version__
295 context.rhodecode_edition = config.get('rhodecode.edition')
295 context.rhodecode_edition = config.get('rhodecode.edition')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
296 context.rhodecode_edition_id = config.get('rhodecode.edition_id')
297 # unique secret + version does not leak the version but keep consistency
297 # unique secret + version does not leak the version but keep consistency
298 context.rhodecode_version_hash = calculate_version_hash(config)
298 context.rhodecode_version_hash = calculate_version_hash(config)
299
299
300 # Default language set for the incoming request
300 # Default language set for the incoming request
301 context.language = get_current_lang(request)
301 context.language = get_current_lang(request)
302
302
303 # Visual options
303 # Visual options
304 context.visual = AttributeDict({})
304 context.visual = AttributeDict({})
305
305
306 # DB stored Visual Items
306 # DB stored Visual Items
307 context.visual.show_public_icon = str2bool(
307 context.visual.show_public_icon = str2bool(
308 rc_config.get('rhodecode_show_public_icon'))
308 rc_config.get('rhodecode_show_public_icon'))
309 context.visual.show_private_icon = str2bool(
309 context.visual.show_private_icon = str2bool(
310 rc_config.get('rhodecode_show_private_icon'))
310 rc_config.get('rhodecode_show_private_icon'))
311 context.visual.stylify_metatags = str2bool(
311 context.visual.stylify_metatags = str2bool(
312 rc_config.get('rhodecode_stylify_metatags'))
312 rc_config.get('rhodecode_stylify_metatags'))
313 context.visual.dashboard_items = safe_int(
313 context.visual.dashboard_items = safe_int(
314 rc_config.get('rhodecode_dashboard_items', 100))
314 rc_config.get('rhodecode_dashboard_items', 100))
315 context.visual.admin_grid_items = safe_int(
315 context.visual.admin_grid_items = safe_int(
316 rc_config.get('rhodecode_admin_grid_items', 100))
316 rc_config.get('rhodecode_admin_grid_items', 100))
317 context.visual.show_revision_number = str2bool(
317 context.visual.show_revision_number = str2bool(
318 rc_config.get('rhodecode_show_revision_number', True))
318 rc_config.get('rhodecode_show_revision_number', True))
319 context.visual.show_sha_length = safe_int(
319 context.visual.show_sha_length = safe_int(
320 rc_config.get('rhodecode_show_sha_length', 100))
320 rc_config.get('rhodecode_show_sha_length', 100))
321 context.visual.repository_fields = str2bool(
321 context.visual.repository_fields = str2bool(
322 rc_config.get('rhodecode_repository_fields'))
322 rc_config.get('rhodecode_repository_fields'))
323 context.visual.show_version = str2bool(
323 context.visual.show_version = str2bool(
324 rc_config.get('rhodecode_show_version'))
324 rc_config.get('rhodecode_show_version'))
325 context.visual.use_gravatar = str2bool(
325 context.visual.use_gravatar = str2bool(
326 rc_config.get('rhodecode_use_gravatar'))
326 rc_config.get('rhodecode_use_gravatar'))
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
327 context.visual.gravatar_url = rc_config.get('rhodecode_gravatar_url')
328 context.visual.default_renderer = rc_config.get(
328 context.visual.default_renderer = rc_config.get(
329 'rhodecode_markup_renderer', 'rst')
329 'rhodecode_markup_renderer', 'rst')
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
330 context.visual.comment_types = ChangesetComment.COMMENT_TYPES
331 context.visual.rhodecode_support_url = \
331 context.visual.rhodecode_support_url = \
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
332 rc_config.get('rhodecode_support_url') or h.route_url('rhodecode_support')
333
333
334 context.visual.affected_files_cut_off = 60
334 context.visual.affected_files_cut_off = 60
335
335
336 context.pre_code = rc_config.get('rhodecode_pre_code')
336 context.pre_code = rc_config.get('rhodecode_pre_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
337 context.post_code = rc_config.get('rhodecode_post_code')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
338 context.rhodecode_name = rc_config.get('rhodecode_title')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
339 context.default_encodings = aslist(config.get('default_encoding'), sep=',')
340 # if we have specified default_encoding in the request, it has more
340 # if we have specified default_encoding in the request, it has more
341 # priority
341 # priority
342 if request.GET.get('default_encoding'):
342 if request.GET.get('default_encoding'):
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
343 context.default_encodings.insert(0, request.GET.get('default_encoding'))
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
344 context.clone_uri_tmpl = rc_config.get('rhodecode_clone_uri_tmpl')
345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
345 context.clone_uri_ssh_tmpl = rc_config.get('rhodecode_clone_uri_ssh_tmpl')
346
346
347 # INI stored
347 # INI stored
348 context.labs_active = str2bool(
348 context.labs_active = str2bool(
349 config.get('labs_settings_active', 'false'))
349 config.get('labs_settings_active', 'false'))
350 context.ssh_enabled = str2bool(
350 context.ssh_enabled = str2bool(
351 config.get('ssh.generate_authorized_keyfile', 'false'))
351 config.get('ssh.generate_authorized_keyfile', 'false'))
352 context.ssh_key_generator_enabled = str2bool(
352 context.ssh_key_generator_enabled = str2bool(
353 config.get('ssh.enable_ui_key_generator', 'true'))
353 config.get('ssh.enable_ui_key_generator', 'true'))
354
354
355 context.visual.allow_repo_location_change = str2bool(
355 context.visual.allow_repo_location_change = str2bool(
356 config.get('allow_repo_location_change', True))
356 config.get('allow_repo_location_change', True))
357 context.visual.allow_custom_hooks_settings = str2bool(
357 context.visual.allow_custom_hooks_settings = str2bool(
358 config.get('allow_custom_hooks_settings', True))
358 config.get('allow_custom_hooks_settings', True))
359 context.debug_style = str2bool(config.get('debug_style', False))
359 context.debug_style = str2bool(config.get('debug_style', False))
360
360
361 context.rhodecode_instanceid = config.get('instance_id')
361 context.rhodecode_instanceid = config.get('instance_id')
362
362
363 context.visual.cut_off_limit_diff = safe_int(
363 context.visual.cut_off_limit_diff = safe_int(
364 config.get('cut_off_limit_diff'))
364 config.get('cut_off_limit_diff'))
365 context.visual.cut_off_limit_file = safe_int(
365 context.visual.cut_off_limit_file = safe_int(
366 config.get('cut_off_limit_file'))
366 config.get('cut_off_limit_file'))
367
367
368 context.license = AttributeDict({})
368 context.license = AttributeDict({})
369 context.license.hide_license_info = str2bool(
369 context.license.hide_license_info = str2bool(
370 config.get('license.hide_license_info', False))
370 config.get('license.hide_license_info', False))
371
371
372 # AppEnlight
372 # AppEnlight
373 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
373 context.appenlight_enabled = str2bool(config.get('appenlight', 'false'))
374 context.appenlight_api_public_key = config.get(
374 context.appenlight_api_public_key = config.get(
375 'appenlight.api_public_key', '')
375 'appenlight.api_public_key', '')
376 context.appenlight_server_url = config.get('appenlight.server_url', '')
376 context.appenlight_server_url = config.get('appenlight.server_url', '')
377
377
378 diffmode = {
378 diffmode = {
379 "unified": "unified",
379 "unified": "unified",
380 "sideside": "sideside"
380 "sideside": "sideside"
381 }.get(request.GET.get('diffmode'))
381 }.get(request.GET.get('diffmode'))
382
382
383 if is_api is not None:
383 if is_api is not None:
384 is_api = hasattr(request, 'rpc_user')
384 is_api = hasattr(request, 'rpc_user')
385 session_attrs = {
385 session_attrs = {
386 # defaults
386 # defaults
387 "clone_url_format": "http",
387 "clone_url_format": "http",
388 "diffmode": "sideside",
388 "diffmode": "sideside",
389 "license_fingerprint": request.session.get('license_fingerprint')
389 "license_fingerprint": request.session.get('license_fingerprint')
390 }
390 }
391
391
392 if not is_api:
392 if not is_api:
393 # don't access pyramid session for API calls
393 # don't access pyramid session for API calls
394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
394 if diffmode and diffmode != request.session.get('rc_user_session_attr.diffmode'):
395 request.session['rc_user_session_attr.diffmode'] = diffmode
395 request.session['rc_user_session_attr.diffmode'] = diffmode
396
396
397 # session settings per user
397 # session settings per user
398
398
399 for k, v in request.session.items():
399 for k, v in request.session.items():
400 pref = 'rc_user_session_attr.'
400 pref = 'rc_user_session_attr.'
401 if k and k.startswith(pref):
401 if k and k.startswith(pref):
402 k = k[len(pref):]
402 k = k[len(pref):]
403 session_attrs[k] = v
403 session_attrs[k] = v
404
404
405 context.user_session_attrs = session_attrs
405 context.user_session_attrs = session_attrs
406
406
407 # JS template context
407 # JS template context
408 context.template_context = {
408 context.template_context = {
409 'repo_name': None,
409 'repo_name': None,
410 'repo_type': None,
410 'repo_type': None,
411 'repo_landing_commit': None,
411 'repo_landing_commit': None,
412 'rhodecode_user': {
412 'rhodecode_user': {
413 'username': None,
413 'username': None,
414 'email': None,
414 'email': None,
415 'notification_status': False
415 'notification_status': False
416 },
416 },
417 'session_attrs': session_attrs,
417 'session_attrs': session_attrs,
418 'visual': {
418 'visual': {
419 'default_renderer': None
419 'default_renderer': None
420 },
420 },
421 'commit_data': {
421 'commit_data': {
422 'commit_id': None
422 'commit_id': None
423 },
423 },
424 'pull_request_data': {'pull_request_id': None},
424 'pull_request_data': {'pull_request_id': None},
425 'timeago': {
425 'timeago': {
426 'refresh_time': 120 * 1000,
426 'refresh_time': 120 * 1000,
427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
427 'cutoff_limit': 1000 * 60 * 60 * 24 * 7
428 },
428 },
429 'pyramid_dispatch': {
429 'pyramid_dispatch': {
430
430
431 },
431 },
432 'extra': {'plugins': {}}
432 'extra': {'plugins': {}}
433 }
433 }
434 # END CONFIG VARS
434 # END CONFIG VARS
435 if is_api:
435 if is_api:
436 csrf_token = None
436 csrf_token = None
437 else:
437 else:
438 csrf_token = auth.get_csrf_token(session=request.session)
438 csrf_token = auth.get_csrf_token(session=request.session)
439
439
440 context.csrf_token = csrf_token
440 context.csrf_token = csrf_token
441 context.backends = rhodecode.BACKENDS.keys()
441 context.backends = rhodecode.BACKENDS.keys()
442
442
443 unread_count = 0
443 unread_count = 0
444 user_bookmark_list = []
444 user_bookmark_list = []
445 if user_id:
445 if user_id:
446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
446 unread_count = NotificationModel().get_unread_cnt_for_user(user_id)
447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
447 user_bookmark_list = UserBookmark.get_bookmarks_for_user(user_id)
448 context.unread_notifications = unread_count
448 context.unread_notifications = unread_count
449 context.bookmark_items = user_bookmark_list
449 context.bookmark_items = user_bookmark_list
450
450
451 # web case
451 # web case
452 if hasattr(request, 'user'):
452 if hasattr(request, 'user'):
453 context.auth_user = request.user
453 context.auth_user = request.user
454 context.rhodecode_user = request.user
454 context.rhodecode_user = request.user
455
455
456 # api case
456 # api case
457 if hasattr(request, 'rpc_user'):
457 if hasattr(request, 'rpc_user'):
458 context.auth_user = request.rpc_user
458 context.auth_user = request.rpc_user
459 context.rhodecode_user = request.rpc_user
459 context.rhodecode_user = request.rpc_user
460
460
461 # attach the whole call context to the request
461 # attach the whole call context to the request
462 request.call_context = context
462 request.call_context = context
463
463
464
464
465 def get_auth_user(request):
465 def get_auth_user(request):
466 environ = request.environ
466 environ = request.environ
467 session = request.session
467 session = request.session
468
468
469 ip_addr = get_ip_addr(environ)
469 ip_addr = get_ip_addr(environ)
470
470
471 # make sure that we update permissions each time we call controller
471 # make sure that we update permissions each time we call controller
472 _auth_token = (request.GET.get('auth_token', '') or request.GET.get('api_key', ''))
472 _auth_token = (
473 # ?auth_token=XXX
474 request.GET.get('auth_token', '')
475 # ?api_key=XXX !LEGACY
476 or request.GET.get('api_key', '')
477 # or headers....
478 or request.headers.get('X-Rc-Auth-Token', '')
479 )
473 if not _auth_token and request.matchdict:
480 if not _auth_token and request.matchdict:
474 url_auth_token = request.matchdict.get('_auth_token')
481 url_auth_token = request.matchdict.get('_auth_token')
475 _auth_token = url_auth_token
482 _auth_token = url_auth_token
476 if _auth_token:
483 if _auth_token:
477 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
484 log.debug('Using URL extracted auth token `...%s`', _auth_token[-4:])
478
485
479 if _auth_token:
486 if _auth_token:
480 # when using API_KEY we assume user exists, and
487 # when using API_KEY we assume user exists, and
481 # doesn't need auth based on cookies.
488 # doesn't need auth based on cookies.
482 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
489 auth_user = AuthUser(api_key=_auth_token, ip_addr=ip_addr)
483 authenticated = False
490 authenticated = False
484 else:
491 else:
485 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
492 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
486 try:
493 try:
487 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
494 auth_user = AuthUser(user_id=cookie_store.get('user_id', None),
488 ip_addr=ip_addr)
495 ip_addr=ip_addr)
489 except UserCreationError as e:
496 except UserCreationError as e:
490 h.flash(e, 'error')
497 h.flash(e, 'error')
491 # container auth or other auth functions that create users
498 # container auth or other auth functions that create users
492 # on the fly can throw this exception signaling that there's
499 # on the fly can throw this exception signaling that there's
493 # issue with user creation, explanation should be provided
500 # issue with user creation, explanation should be provided
494 # in Exception itself. We then create a simple blank
501 # in Exception itself. We then create a simple blank
495 # AuthUser
502 # AuthUser
496 auth_user = AuthUser(ip_addr=ip_addr)
503 auth_user = AuthUser(ip_addr=ip_addr)
497
504
498 # in case someone changes a password for user it triggers session
505 # in case someone changes a password for user it triggers session
499 # flush and forces a re-login
506 # flush and forces a re-login
500 if password_changed(auth_user, session):
507 if password_changed(auth_user, session):
501 session.invalidate()
508 session.invalidate()
502 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
509 cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
503 auth_user = AuthUser(ip_addr=ip_addr)
510 auth_user = AuthUser(ip_addr=ip_addr)
504
511
505 authenticated = cookie_store.get('is_authenticated')
512 authenticated = cookie_store.get('is_authenticated')
506
513
507 if not auth_user.is_authenticated and auth_user.is_user_object:
514 if not auth_user.is_authenticated and auth_user.is_user_object:
508 # user is not authenticated and not empty
515 # user is not authenticated and not empty
509 auth_user.set_authenticated(authenticated)
516 auth_user.set_authenticated(authenticated)
510
517
511 return auth_user, _auth_token
518 return auth_user, _auth_token
512
519
513
520
514 def h_filter(s):
521 def h_filter(s):
515 """
522 """
516 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
523 Custom filter for Mako templates. Mako by standard uses `markupsafe.escape`
517 we wrap this with additional functionality that converts None to empty
524 we wrap this with additional functionality that converts None to empty
518 strings
525 strings
519 """
526 """
520 if s is None:
527 if s is None:
521 return markupsafe.Markup()
528 return markupsafe.Markup()
522 return markupsafe.escape(s)
529 return markupsafe.escape(s)
523
530
524
531
525 def add_events_routes(config):
532 def add_events_routes(config):
526 """
533 """
527 Adds routing that can be used in events. Because some events are triggered
534 Adds routing that can be used in events. Because some events are triggered
528 outside of pyramid context, we need to bootstrap request with some
535 outside of pyramid context, we need to bootstrap request with some
529 routing registered
536 routing registered
530 """
537 """
531
538
532 from rhodecode.apps._base import ADMIN_PREFIX
539 from rhodecode.apps._base import ADMIN_PREFIX
533
540
534 config.add_route(name='home', pattern='/')
541 config.add_route(name='home', pattern='/')
535 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
542 config.add_route(name='main_page_repos_data', pattern='/_home_repos')
536 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
543 config.add_route(name='main_page_repo_groups_data', pattern='/_home_repo_groups')
537
544
538 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
545 config.add_route(name='login', pattern=ADMIN_PREFIX + '/login')
539 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
546 config.add_route(name='logout', pattern=ADMIN_PREFIX + '/logout')
540 config.add_route(name='repo_summary', pattern='/{repo_name}')
547 config.add_route(name='repo_summary', pattern='/{repo_name}')
541 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
548 config.add_route(name='repo_summary_explicit', pattern='/{repo_name}/summary')
542 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
549 config.add_route(name='repo_group_home', pattern='/{repo_group_name}')
543
550
544 config.add_route(name='pullrequest_show',
551 config.add_route(name='pullrequest_show',
545 pattern='/{repo_name}/pull-request/{pull_request_id}')
552 pattern='/{repo_name}/pull-request/{pull_request_id}')
546 config.add_route(name='pull_requests_global',
553 config.add_route(name='pull_requests_global',
547 pattern='/pull-request/{pull_request_id}')
554 pattern='/pull-request/{pull_request_id}')
548
555
549 config.add_route(name='repo_commit',
556 config.add_route(name='repo_commit',
550 pattern='/{repo_name}/changeset/{commit_id}')
557 pattern='/{repo_name}/changeset/{commit_id}')
551 config.add_route(name='repo_files',
558 config.add_route(name='repo_files',
552 pattern='/{repo_name}/files/{commit_id}/{f_path}')
559 pattern='/{repo_name}/files/{commit_id}/{f_path}')
553
560
554 config.add_route(name='hovercard_user',
561 config.add_route(name='hovercard_user',
555 pattern='/_hovercard/user/{user_id}')
562 pattern='/_hovercard/user/{user_id}')
556
563
557 config.add_route(name='hovercard_user_group',
564 config.add_route(name='hovercard_user_group',
558 pattern='/_hovercard/user_group/{user_group_id}')
565 pattern='/_hovercard/user_group/{user_group_id}')
559
566
560 config.add_route(name='hovercard_pull_request',
567 config.add_route(name='hovercard_pull_request',
561 pattern='/_hovercard/pull_request/{pull_request_id}')
568 pattern='/_hovercard/pull_request/{pull_request_id}')
562
569
563 config.add_route(name='hovercard_repo_commit',
570 config.add_route(name='hovercard_repo_commit',
564 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
571 pattern='/_hovercard/commit/{repo_name}/{commit_id}')
565
572
566
573
567 def bootstrap_config(request):
574 def bootstrap_config(request):
568 import pyramid.testing
575 import pyramid.testing
569 registry = pyramid.testing.Registry('RcTestRegistry')
576 registry = pyramid.testing.Registry('RcTestRegistry')
570
577
571 config = pyramid.testing.setUp(registry=registry, request=request)
578 config = pyramid.testing.setUp(registry=registry, request=request)
572
579
573 # allow pyramid lookup in testing
580 # allow pyramid lookup in testing
574 config.include('pyramid_mako')
581 config.include('pyramid_mako')
575 config.include('rhodecode.lib.rc_beaker')
582 config.include('rhodecode.lib.rc_beaker')
576 config.include('rhodecode.lib.rc_cache')
583 config.include('rhodecode.lib.rc_cache')
577
584
578 add_events_routes(config)
585 add_events_routes(config)
579
586
580 return config
587 return config
581
588
582
589
583 def bootstrap_request(**kwargs):
590 def bootstrap_request(**kwargs):
584 import pyramid.testing
591 import pyramid.testing
585
592
586 class TestRequest(pyramid.testing.DummyRequest):
593 class TestRequest(pyramid.testing.DummyRequest):
587 application_url = kwargs.pop('application_url', 'http://example.com')
594 application_url = kwargs.pop('application_url', 'http://example.com')
588 host = kwargs.pop('host', 'example.com:80')
595 host = kwargs.pop('host', 'example.com:80')
589 domain = kwargs.pop('domain', 'example.com')
596 domain = kwargs.pop('domain', 'example.com')
590
597
591 def translate(self, msg):
598 def translate(self, msg):
592 return msg
599 return msg
593
600
594 def plularize(self, singular, plural, n):
601 def plularize(self, singular, plural, n):
595 return singular
602 return singular
596
603
597 def get_partial_renderer(self, tmpl_name):
604 def get_partial_renderer(self, tmpl_name):
598
605
599 from rhodecode.lib.partial_renderer import get_partial_renderer
606 from rhodecode.lib.partial_renderer import get_partial_renderer
600 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
607 return get_partial_renderer(request=self, tmpl_name=tmpl_name)
601
608
602 _call_context = TemplateArgs()
609 _call_context = TemplateArgs()
603 _call_context.visual = TemplateArgs()
610 _call_context.visual = TemplateArgs()
604 _call_context.visual.show_sha_length = 12
611 _call_context.visual.show_sha_length = 12
605 _call_context.visual.show_revision_number = True
612 _call_context.visual.show_revision_number = True
606
613
607 @property
614 @property
608 def call_context(self):
615 def call_context(self):
609 return self._call_context
616 return self._call_context
610
617
611 class TestDummySession(pyramid.testing.DummySession):
618 class TestDummySession(pyramid.testing.DummySession):
612 def save(*arg, **kw):
619 def save(*arg, **kw):
613 pass
620 pass
614
621
615 request = TestRequest(**kwargs)
622 request = TestRequest(**kwargs)
616 request.session = TestDummySession()
623 request.session = TestDummySession()
617
624
618 return request
625 return request
619
626
@@ -1,121 +1,122 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 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 import logging
22 import logging
23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
23 from pyramid.httpexceptions import HTTPException, HTTPBadRequest
24
24
25 from rhodecode.lib.middleware.vcs import (
25 from rhodecode.lib.middleware.vcs import (
26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
26 detect_vcs_request, VCS_TYPE_KEY, VCS_TYPE_SKIP)
27
27
28
28
29 log = logging.getLogger(__name__)
29 log = logging.getLogger(__name__)
30
30
31
31
32 def vcs_detection_tween_factory(handler, registry):
32 def vcs_detection_tween_factory(handler, registry):
33
33
34 def vcs_detection_tween(request):
34 def vcs_detection_tween(request):
35 """
35 """
36 Do detection of vcs type, and save results for other layers to re-use
36 Do detection of vcs type, and save results for other layers to re-use
37 this information
37 this information
38 """
38 """
39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
39 vcs_server_enabled = request.registry.settings.get('vcs.server.enable')
40 vcs_handler = vcs_server_enabled and detect_vcs_request(
40 vcs_handler = vcs_server_enabled and detect_vcs_request(
41 request.environ, request.registry.settings.get('vcs.backends'))
41 request.environ, request.registry.settings.get('vcs.backends'))
42
42
43 if vcs_handler:
43 if vcs_handler:
44 # save detected VCS type for later re-use
44 # save detected VCS type for later re-use
45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
45 request.environ[VCS_TYPE_KEY] = vcs_handler.SCM
46 request.vcs_call = vcs_handler.SCM
46 request.vcs_call = vcs_handler.SCM
47
47
48 log.debug('Processing request with `%s` handler', handler)
48 log.debug('Processing request with `%s` handler', handler)
49 return handler(request)
49 return handler(request)
50
50
51 # mark that we didn't detect an VCS, and we can skip detection later on
51 # mark that we didn't detect an VCS, and we can skip detection later on
52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
52 request.environ[VCS_TYPE_KEY] = VCS_TYPE_SKIP
53
53
54 log.debug('Processing request with `%s` handler', handler)
54 log.debug('Processing request with `%s` handler', handler)
55 return handler(request)
55 return handler(request)
56
56
57 return vcs_detection_tween
57 return vcs_detection_tween
58
58
59
59
60 def junk_encoding_detector(request):
60 def junk_encoding_detector(request):
61 """
61 """
62 Detect bad encoded GET params, and fail immediately with BadRequest
62 Detect bad encoded GET params, and fail immediately with BadRequest
63 """
63 """
64
64
65 try:
65 try:
66 request.GET.get("", None)
66 request.GET.get("", None)
67 except UnicodeDecodeError:
67 except UnicodeDecodeError:
68 raise HTTPBadRequest("Invalid bytes in query string.")
68 raise HTTPBadRequest("Invalid bytes in query string.")
69
69
70
70
71 def bad_url_data_detector(request):
71 def bad_url_data_detector(request):
72 """
72 """
73 Detect invalid bytes in a path.
73 Detect invalid bytes in a path.
74 """
74 """
75 try:
75 try:
76 request.path_info
76 request.path_info
77 except UnicodeDecodeError:
77 except UnicodeDecodeError:
78 raise HTTPBadRequest("Invalid bytes in URL.")
78 raise HTTPBadRequest("Invalid bytes in URL.")
79
79
80
80
81 def junk_form_data_detector(request):
81 def junk_form_data_detector(request):
82 """
82 """
83 Detect bad encoded POST params, and fail immediately with BadRequest
83 Detect bad encoded POST params, and fail immediately with BadRequest
84 """
84 """
85
85
86 if request.method == "POST":
86 if request.method == "POST":
87 try:
87 try:
88 request.POST.get("", None)
88 request.POST.get("", None)
89 except ValueError:
89 except ValueError:
90 raise HTTPBadRequest("Invalid bytes in form data.")
90 raise HTTPBadRequest("Invalid bytes in form data.")
91
91
92
92
93 def sanity_check_factory(handler, registry):
93 def sanity_check_factory(handler, registry):
94 def sanity_check(request):
94 def sanity_check(request):
95 log.debug('Checking current URL sanity for bad data')
95 log.debug('Checking current URL sanity for bad data')
96 try:
96 try:
97 junk_encoding_detector(request)
97 junk_encoding_detector(request)
98 bad_url_data_detector(request)
98 bad_url_data_detector(request)
99 junk_form_data_detector(request)
99 junk_form_data_detector(request)
100 except HTTPException as exc:
100 except HTTPException as exc:
101 return exc
101 return exc
102
102
103 return handler(request)
103 return handler(request)
104
104
105 return sanity_check
105 return sanity_check
106
106
107
107
108 def includeme(config):
108 def includeme(config):
109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
109 config.add_subscriber('rhodecode.subscribers.add_renderer_globals',
110 'pyramid.events.BeforeRender')
110 'pyramid.events.BeforeRender')
111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
111 config.add_subscriber('rhodecode.subscribers.set_user_lang',
112 'pyramid.events.NewRequest')
112 'pyramid.events.NewRequest')
113 config.add_subscriber('rhodecode.subscribers.add_localizer',
113 config.add_subscriber('rhodecode.subscribers.add_localizer',
114 'pyramid.events.NewRequest')
114 'pyramid.events.NewRequest')
115 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
115 config.add_subscriber('rhodecode.subscribers.add_request_user_context',
116 'pyramid.events.ContextFound')
116 'pyramid.events.ContextFound')
117 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
117 config.add_tween('rhodecode.tweens.vcs_detection_tween_factory')
118 config.add_tween('rhodecode.tweens.sanity_check_factory')
118 config.add_tween('rhodecode.tweens.sanity_check_factory')
119
119
120 # This needs to be the LAST item
120 # This needs to be the LAST item
121 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
121 config.add_tween('rhodecode.lib.middleware.request_wrapper.RequestWrapperTween')
122 log.debug('configured all tweens')
General Comments 0
You need to be logged in to leave comments. Login now