from django.contrib import messages
from django.utils.http import urlencode
from django.contrib.auth import authenticate
-from django.http import HttpResponse, HttpResponseBadRequest
+from django.http import (
+ HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
+)
from django.core.exceptions import ValidationError
from django.views.decorators.http import require_http_methods
from urlparse import urlunsplit, urlsplit, urlparse, parse_qsl
from astakos.im.settings import COOKIE_NAME, COOKIE_DOMAIN
-from astakos.im.util import set_cookie
+from astakos.im.util import set_cookie, restrict_next
from astakos.im.functions import login as auth_login, logout
import logging
next = request.GET.get('next')
if not next:
return HttpResponseBadRequest(_('No next parameter'))
+ if not restrict_next(
+ next, domain=COOKIE_DOMAIN, allowed_schemes=('pithos',)
+ ):
+ return HttpResponseForbidden(_('Not allowed next parameter'))
force = request.GET.get('force', None)
response = HttpResponse()
if force == '':
import time
from urllib import quote
-from urlparse import urlsplit, urlunsplit
+from urlparse import urlsplit, urlunsplit, urlparse
from datetime import tzinfo, timedelta
from django.http import HttpResponse, HttpResponseBadRequest, urlencode
from django.core.exceptions import ValidationError
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
-from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, \
- COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE, LOGGING_LEVEL
+from astakos.im.settings import (
+ INVITATIONS_PER_LEVEL, COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE,
+ FORCE_PROFILE_UPDATE, LOGGING_LEVEL
+)
from astakos.im.functions import login
logger = logging.getLogger(__name__)
raise ValueError(_('Email: %s is reserved' % invitation.username))
return invitation
+def restrict_next(url, domain=None, allowed_schemes=()):
+ """
+ Return url if having the supplied ``domain`` (if present) or one of the ``allowed_schemes``.
+ Otherwise return None.
+
+ >>> print restrict_next('/im/feedback', '.okeanos.grnet.gr')
+ /im/feedback
+ >>> print restrict_next('pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
+ pithos.okeanos.grnet.gr/im/feedback
+ >>> print restrict_next('https://pithos.okeanos.grnet.gr/im/feedback', '.okeanos.grnet.gr')
+ https://pithos.okeanos.grnet.gr/im/feedback
+ >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('pithos://127.0.0,1', '.okeanos.grnet.gr', allowed_schemes=('pithos'))
+ pithos://127.0.0,1
+ >>> print restrict_next('node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('//node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('https://node1.example.com', '.okeanos.grnet.gr')
+ None
+ >>> print restrict_next('https://node1.example.com')
+ https://node1.example.com
+ >>> print restrict_next('//node1.example.com')
+ //node1.example.com
+ >>> print restrict_next('node1.example.com')
+ node1.example.com
+ """
+ if not url:
+ return
+ parts = urlparse(url, scheme='http')
+ if not parts.netloc:
+ # fix url if does not conforms RFC 1808
+ url = '//%s' % url
+ parts = urlparse(url, scheme='http')
+ # TODO more scientific checks?
+ if not parts.netloc: # internal url
+ return url
+ elif not domain:
+ return url
+ elif parts.netloc.endswith(domain):
+ return url
+ elif parts.scheme in allowed_schemes:
+ return url
+
def prepare_response(request, user, next='', renew=False):
"""Return the unique username and the token
as 'X-Auth-User' and 'X-Auth-Token' headers,
except ValidationError, e:
return HttpResponseBadRequest(e)
+ next = restrict_next(next, domain=COOKIE_DOMAIN)
+
if FORCE_PROFILE_UPDATE and not user.is_verified and not user.is_superuser:
params = ''
if next:
from astakos.im.models import AstakosUser, Invitation, ApprovalTerms
from astakos.im.activation_backends import get_backend, SimpleBackend
-from astakos.im.util import get_context, prepare_response, set_cookie, get_query
+from astakos.im.util import (
+ get_context, prepare_response, set_cookie, get_query, restrict_next
+)
from astakos.im.forms import *
from astakos.im.functions import (send_greeting, send_feedback, SendMailError,
invite as invite_func, logout as auth_logout, activate as activate_func
user = form.save()
reset_cookie = user.auth_token != prev_token
form = ProfileForm(instance=user)
- next = request.POST.get('next')
+ next = restrict_next(
+ request.POST.get('next'),
+ domain=COOKIE_DOMAIN
+ )
if next:
return redirect(next)
msg = _('<p>Profile has been updated successfully</p>')
feedback_form = form,
context_instance = get_context(request, extra_context))
-@require_http_methods(["GET", "POST"])
+@require_http_methods(["GET"])
def logout(request, template='registration/logged_out.html', extra_context={}):
"""
Wraps `django.contrib.auth.logout` and delete the cookie.
response.delete_cookie(COOKIE_NAME, path='/', domain=COOKIE_DOMAIN)
msg = 'Cookie deleted for %s' % email
logger._log(LOGGING_LEVEL, msg, [])
- next = request.GET.get('next')
+ next = restrict_next(
+ request.GET.get('next'),
+ domain=COOKIE_DOMAIN
+ )
if next:
response['Location'] = next
response.status_code = 302
terms = f.read()
if request.method == 'POST':
- next = request.POST.get('next')
+ next = restrict_next(
+ request.POST.get('next'),
+ domain=COOKIE_DOMAIN
+ )
if not next:
next = reverse('astakos.im.views.index')
form = SignApprovalTermsForm(request.POST, instance=request.user)