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