Revision 217994f8

b/snf-astakos-app/astakos/im/target/redirect.py
37 37
from django.contrib import messages
38 38
from django.utils.http import urlencode
39 39
from django.contrib.auth import authenticate
40
from django.http import HttpResponse, HttpResponseBadRequest
40
from django.http import (
41
    HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
42
)
41 43
from django.core.exceptions import ValidationError
42 44
from django.views.decorators.http import require_http_methods
43 45

  
......
45 47
from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
46 48

  
47 49
from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
48
from astakos.im.util import set_cookie
50
from astakos.im.util import set_cookie, restrict_next
49 51
from astakos.im.functions import login as auth_login, logout
50 52

  
51 53
import logging
......
65 67
    next = request.GET.get('next')
66 68
    if not next:
67 69
        return HttpResponseBadRequest(_('No next parameter'))
70
    if not restrict_next(
71
        next, domain=COOKIE_DOMAIN, allowed_schemes=('pithos',)
72
    ):
73
        return HttpResponseForbidden(_('Not allowed next parameter'))
68 74
    force = request.GET.get('force', None)
69 75
    response = HttpResponse()
70 76
    if force == '':
b/snf-astakos-app/astakos/im/util.py
36 36
import time
37 37

  
38 38
from urllib import quote
39
from urlparse import urlsplit, urlunsplit
39
from urlparse import urlsplit, urlunsplit, urlparse
40 40

  
41 41
from datetime import tzinfo, timedelta
42 42
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
......
47 47
from django.core.exceptions import ValidationError
48 48

  
49 49
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
50
from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, \
51
    COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
50
from astakos.im.settings import (
51
    INVITATIONS_PER_LEVEL, COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE,
52
    FORCE_PROFILE_UPDATE, LOGGING_LEVEL
53
)
52 54
from astakos.im.functions import login
53 55

  
54 56
logger = logging.getLogger(__name__)
......
96 98
        raise ValueError(_('Email: %s is reserved' % invitation.username))
97 99
    return invitation
98 100

  
101
def restrict_next(url, domain=None, allowed_schemes=()):
102
    """
103
    Return url if having the supplied ``domain`` (if present) or one of the ``allowed_schemes``.
104
    Otherwise return None.
105
    
106
    >>> print restrict_next('/im/feedback', '.okeanos.grnet.gr')
107
    /im/feedback
108
    >>> print restrict_next('pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
109
    pithos.okeanos.grnet.gr/im/feedback
110
    >>> print restrict_next('https://pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
111
    https://pithos.okeanos.grnet.gr/im/feedback
112
    >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr')
113
    None
114
    >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr', allowed_schemes=('pithos'))
115
    pithos://127.0.0,1
116
    >>> print restrict_next('node1.example.com', '.okeanos.grnet.gr')
117
    None
118
    >>> print restrict_next('//node1.example.com', '.okeanos.grnet.gr')
119
    None
120
    >>> print restrict_next('https://node1.example.com', '.okeanos.grnet.gr')
121
    None
122
    >>> print restrict_next('https://node1.example.com')
123
    https://node1.example.com
124
    >>> print restrict_next('//node1.example.com')
125
    //node1.example.com
126
    >>> print restrict_next('node1.example.com')
127
    node1.example.com
128
    """
129
    if not url:
130
        return
131
    parts = urlparse(url, scheme='http')
132
    if not parts.netloc:
133
        # fix url if does not conforms RFC 1808
134
        url = '//%s' % url
135
        parts = urlparse(url, scheme='http')
136
    # TODO more scientific checks?
137
    if not parts.netloc:    # internal url
138
        return url
139
    elif not domain:
140
        return url
141
    elif parts.netloc.endswith(domain):
142
        return url
143
    elif parts.scheme in allowed_schemes:
144
        return url
145

  
99 146
def prepare_response(request, user, next='', renew=False):
100 147
    """Return the unique username and the token
101 148
       as 'X-Auth-User' and 'X-Auth-Token' headers,
......
115 162
        except ValidationError, e:
116 163
            return HttpResponseBadRequest(e) 
117 164
    
165
    next = restrict_next(next, domain=COOKIE_DOMAIN)
166
    
118 167
    if FORCE_PROFILE_UPDATE and not user.is_verified and not user.is_superuser:
119 168
        params = ''
120 169
        if next:
b/snf-astakos-app/astakos/im/views.py
56 56

  
57 57
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
58 58
from astakos.im.activation_backends import get_backend, SimpleBackend
59
from astakos.im.util import get_context, prepare_response, set_cookie, get_query
59
from astakos.im.util import (
60
    get_context, prepare_response, set_cookie, get_query, restrict_next
61
)
60 62
from astakos.im.forms import *
61 63
from astakos.im.functions import (send_greeting, send_feedback, SendMailError,
62 64
    invite as invite_func, logout as auth_logout, activate as activate_func
......
268 270
                user = form.save()
269 271
                reset_cookie = user.auth_token != prev_token
270 272
                form = ProfileForm(instance=user)
271
                next = request.POST.get('next')
273
                next = restrict_next(
274
                    request.POST.get('next'),
275
                    domain=COOKIE_DOMAIN
276
                )
272 277
                if next:
273 278
                    return redirect(next)
274 279
                msg = _('<p>Profile has been updated successfully</p>')
......
419 424
                           feedback_form = form,
420 425
                           context_instance = get_context(request, extra_context))
421 426

  
422
@require_http_methods(["GET", "POST"])
427
@require_http_methods(["GET"])
423 428
def logout(request, template='registration/logged_out.html', extra_context={}):
424 429
    """
425 430
    Wraps `django.contrib.auth.logout` and delete the cookie.
......
431 436
        response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
432 437
        msg = 'Cookie deleted for %s' % email
433 438
        logger._log(LOGGING_LEVEL, msg, [])
434
    next = request.GET.get('next')
439
    next = restrict_next(
440
        request.GET.get('next'),
441
        domain=COOKIE_DOMAIN
442
    )
435 443
    if next:
436 444
        response['Location'] = next
437 445
        response.status_code = 302
......
506 514
    terms = f.read()
507 515

  
508 516
    if request.method == 'POST':
509
        next = request.POST.get('next')
517
        next = restrict_next(
518
            request.POST.get('next'),
519
            domain=COOKIE_DOMAIN
520
        )
510 521
        if not next:
511 522
            next = reverse('astakos.im.views.index')
512 523
        form = SignApprovalTermsForm(request.POST, instance=request.user)

Also available in: Unified diff