Restrict next url parameter
[astakos] / snf-astakos-app / astakos / im / util.py
index c82eebb..eb9d759 100644 (file)
 
 import logging
 import datetime
+import time
 
 from urllib import quote
-from urlparse import urlsplit, urlunsplit
-from functools import wraps
+from urlparse import urlsplit, urlunsplit, urlparse
 
 from datetime import tzinfo, timedelta
-from django.http import HttpResponse, urlencode
+from django.http import HttpResponse, HttpResponseBadRequest, urlencode
 from django.template import RequestContext
-from django.contrib.sites.models import Site
 from django.utils.translation import ugettext as _
-from django.contrib.auth import login, authenticate
+from django.contrib.auth import authenticate
 from django.core.urlresolvers import reverse
+from django.core.exceptions import ValidationError
 
-from astakos.im.models import AstakosUser, Invitation
-from astakos.im.settings import INVITATIONS_PER_LEVEL, COOKIE_NAME, COOKIE_DOMAIN, COOKIE_SECURE, FORCE_PROFILE_UPDATE
+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.functions import login
 
 logger = logging.getLogger(__name__)
 
@@ -66,27 +70,8 @@ def isoformat(d):
 
    return d.replace(tzinfo=UTC()).isoformat()
 
-def get_or_create_user(email, realname='', first_name='', last_name='', affiliation='', level=0, provider='local', password=''):
-    """Find or register a user into the internal database
-       and issue a token for subsequent requests.
-    """
-    user, created = AstakosUser.objects.get_or_create(email=email,
-        defaults={
-            'password':password,
-            'affiliation':affiliation,
-            'level':level,
-            'invitations':INVITATIONS_PER_LEVEL.get(level, 0),
-            'provider':provider,
-            'realname':realname,
-            'first_name':first_name,
-            'last_name':last_name
-        })
-    if created:
-        user.renew_token()
-        user.save()
-        logger.info('Created user %s', user)
-    
-    return user
+def epoch(datetime):
+    return int(time.mktime(datetime.timetuple())*1000)
 
 def get_context(request, extra_context={}, **kwargs):
     if not extra_context:
@@ -98,26 +83,66 @@ def get_invitation(request):
     """
     Returns the invitation identified by the ``code``.
     
-    Raises Invitation.DoesNotExist and Exception if the invitation is consumed
+    Raises ValueError if the invitation is consumed or there is another account
+    associated with this email.
     """
     code = request.GET.get('code')
     if request.method == 'POST':
         code = request.POST.get('code')
     if not code:
-        if 'invitation_code' in request.session:
-            code = request.session.pop('invitation_code')
-    if not code:
         return
     invitation = Invitation.objects.get(code = code)
     if invitation.is_consumed:
         raise ValueError(_('Invitation is used'))
-    try:
-        AstakosUser.objects.get(email = invitation.username)
+    if reserved_email(invitation.username):
         raise ValueError(_('Email: %s is reserved' % invitation.username))
-    except AstakosUser.DoesNotExist:
-        pass
     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,
@@ -128,12 +153,16 @@ def prepare_response(request, user, next='', renew=False):
        expired, if the 'renew' parameter is present
        or user has not a valid token.
     """
-    
     renew = renew or (not user.auth_token)
     renew = renew or (user.auth_token_expires and user.auth_token_expires < datetime.datetime.now())
     if renew:
         user.renew_token()
-        user.save()
+        try:
+            user.save()
+        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 = ''
@@ -147,6 +176,7 @@ def prepare_response(request, user, next='', renew=False):
     user = authenticate(email=user.email, auth_token=user.auth_token)
     login(request, user)
     set_cookie(response, user)
+    request.session.set_expiry(user.auth_token_expires)
     
     if not next:
         next = reverse('astakos.im.views.index')
@@ -161,3 +191,28 @@ def set_cookie(response, user):
     response.set_cookie(COOKIE_NAME, value=cookie_value,
                         expires=expire_fmt, path='/',
                         domain=COOKIE_DOMAIN, secure=COOKIE_SECURE)
+    msg = 'Cookie [expiring %s] set for %s' % (user.auth_token_expires, user.email)
+    logger._log(LOGGING_LEVEL, msg, [])
+
+class lazy_string(object):
+    def __init__(self, function, *args, **kwargs):
+        self.function=function
+        self.args=args
+        self.kwargs=kwargs
+        
+    def __str__(self):
+        if not hasattr(self, 'str'):
+            self.str=self.function(*self.args, **self.kwargs)
+        return self.str
+
+def reverse_lazy(*args, **kwargs):
+    return lazy_string(reverse, *args, **kwargs)
+
+def reserved_email(email):
+    return AstakosUser.objects.filter(email = email).count() != 0
+
+def get_query(request):
+    try:
+        return request.__getattribute__(request.method)
+    except AttributeError:
+        return {}
\ No newline at end of file