Skip to content

Commit

Permalink
stay on the same page on a redirect
Browse files Browse the repository at this point in the history
  • Loading branch information
t-8ch committed Apr 14, 2016
1 parent 3c98201 commit 420e3ea
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 5 deletions.
4 changes: 2 additions & 2 deletions oscad/compat.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
try:
from urllib.parse import urljoin # noqa
from urllib.parse import urljoin, urlparse # noqa
except ImportError:
from urlparse import urljoin # noqa
from urlparse import urljoin, urlparse # noqa

try:
from collections import OrderedDict # noqa
Expand Down
3 changes: 2 additions & 1 deletion oscad/templates/oscad/navigation.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
<ul class="nav navbar-nav navbar-right">
{%- for lang in request.available_locales -%}
<li {% if request.locale_name == lang -%} class="active" {%- endif -%}>
<a href="{{ request.route_path('change_language', lang=lang) }}">
<a href="{{ request.route_path('change_language', lang=lang,
_query={'next': request.path_qs}) }}">
{{ lang }}
</a>
</li>
Expand Down
27 changes: 27 additions & 0 deletions oscad/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pyramid.exceptions import ConfigurationError

from .exceptions import UtilException
from .compat import urlparse


def _check_balances(text, begin, end):
Expand Down Expand Up @@ -48,3 +49,29 @@ def load_themes(config, settings):
config.include(theme)
except ConfigurationError:
pass


def is_application_url(request, url):
p = urlparse(url)

if p.scheme and p.scheme != request.scheme:
return False

if p.netloc and p.netloc != request.host:
return False

if not p.path:
return False

return is_url_prefix(request.script_name, p.path)


def is_url_prefix(prefix, url):
if not url.startswith(prefix):
return False

if prefix and prefix[-1] != '/' and \
len(url) > len(prefix) and url[len(prefix)] != '/':
return False

return True
11 changes: 9 additions & 2 deletions oscad/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from babel import Locale
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPSeeOther, HTTPNotFound
from pyramid.httpexceptions import HTTPSeeOther, HTTPNotFound, HTTPForbidden
from pyramid.response import Response
from pkg_resources import resource_stream

Expand All @@ -11,6 +11,7 @@
from . import exceptions
from .models import OSUC, LSUC
from .version import __version__ as oscad_version
from .util import is_application_url

PLANNED_LICENSES = sorted(['CDDL-1.0', 'ZLIB'])
DEFAULT_LICENSE = 'GPLv2.0'
Expand Down Expand Up @@ -142,7 +143,13 @@ def display_name(tag):
@view_config(route_name='change_language')
def change_language(request):
lang = request.matchdict.get('lang')
resp = HTTPSeeOther(request.application_url)
next = request.GET.get('next')
if next and not is_application_url(request, next):
raise HTTPForbidden((
'The URL you accessed would have led to an redirect outside of '
'this webapplication ({}). This is a sign of a phishing attack!'
).format(next))
resp = HTTPSeeOther(next or request.application_url)

if lang is None:
resp.unset_cookie('_LOCALE_')
Expand Down
16 changes: 16 additions & 0 deletions tests/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from oscad.util import is_url_prefix


def test_is_url_prefix():
for a, b, r in [
('/foo', '/foo', True),
('/foo', '/foo/bar', True),
('/foo', '/foobar', False),
('/bar', '/foo', False),
('/foo/', '/foo/', True),
('/foo/', '/foo/bar', True),
('/foo/', '/foobar', False),
('/', '/foo', True),
('', '/foo', True),
]:
assert is_url_prefix(a, b) == r

0 comments on commit 420e3ea

Please sign in to comment.