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