Provide api calls for permitting helpdesk users to access user information by user...
[astakos] / snf-astakos-app / astakos / im / api.py
index 4df9f84..98d2eb9 100644 (file)
 # interpreted as representing official policies, either expressed
 # or implied, of GRNET S.A.
 
+import logging
+import urllib
+
+from functools import wraps
 from traceback import format_exc
 from time import time, mktime
 from urllib import quote
@@ -41,9 +45,15 @@ from django.http import HttpResponse
 from django.utils import simplejson as json
 from django.core.urlresolvers import reverse
 
-from astakos.im.faults import BadRequest, Unauthorized, InternalServerError
+from astakos.im.faults import BadRequest, Unauthorized, InternalServerError, \
+Fault, ItemNotFound, Forbidden
 from astakos.im.models import AstakosUser
-from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED
+from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED, COOKIE_NAME, \
+EMAILCHANGE_ENABLED
+from astakos.im.util import epoch
+
+logger = logging.getLogger(__name__)
+format = ('%a, %d %b %Y %H:%M:%S GMT')
 
 def render_fault(request, fault):
     if isinstance(fault, InternalServerError) and settings.DEBUG:
@@ -57,50 +67,111 @@ def render_fault(request, fault):
     response['Content-Length'] = len(response.content)
     return response
 
-def authenticate(request):
+def api_method(http_method=None, token_required=False, perms=None):
+    """Decorator function for views that implement an API method."""
+    if not perms:
+        perms = []
+    
+    def decorator(func):
+        @wraps(func)
+        def wrapper(request, *args, **kwargs):
+            try:
+                if http_method and request.method != http_method:
+                    raise BadRequest('Method not allowed.')
+                x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN')
+                if token_required:
+                    if not x_auth_token:
+                        raise Unauthorized('Access denied')
+                    try:
+                        user = AstakosUser.objects.get(auth_token=x_auth_token)
+                        if not user.has_perms(perms):
+                            raise Forbidden('Unauthorized request')
+                    except AstakosUser.DoesNotExist, e:
+                        raise Unauthorized('Invalid X-Auth-Token')
+                    kwargs['user'] = user
+                response = func(request, *args, **kwargs)
+                return response
+            except Fault, fault:
+                return render_fault(request, fault)
+            except BaseException, e:
+                logger.exception('Unexpected error: %s' % e)
+                fault = InternalServerError('Unexpected error')
+                return render_fault(request, fault)
+        return wrapper
+    return decorator
+
+@api_method(http_method='GET', token_required=True)
+def authenticate_old(request, user=None):
     # Normal Response Codes: 204
     # Error Response Codes: internalServerError (500)
     #                       badRequest (400)
     #                       unauthorised (401)
-    try:
-        if request.method != 'GET':
-            raise BadRequest('Method not allowed.')
-        x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN')
-        if not x_auth_token:
-            return render_fault(request, BadRequest('Missing X-Auth-Token'))
-
-        try:
-            user = AstakosUser.objects.get(auth_token=x_auth_token)
-        except AstakosUser.DoesNotExist, e:
-            return render_fault(request, Unauthorized('Invalid X-Auth-Token'))
+    if not user:
+        raise BadRequest('No user')
+    
+    # Check if the is active.
+    if not user.is_active:
+        raise Unauthorized('User inactive')
 
-        # Check if the is active.
-        if not user.is_active:
-            return render_fault(request, Unauthorized('User inactive'))
+    # Check if the token has expired.
+    if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
+        raise Unauthorized('Authentication expired')
+    
+    if not user.signed_terms():
+        raise Unauthorized('Pending approval terms')
+    
+    response = HttpResponse()
+    response.status=204
+    user_info = {'username':user.username,
+                 'uniq':user.email,
+                 'auth_token':user.auth_token,
+                 'auth_token_created':user.auth_token_created.isoformat(),
+                 'auth_token_expires':user.auth_token_expires.isoformat(),
+                 'has_credits':user.has_credits,
+                 'has_signed_terms':user.signed_terms()}
+    response.content = json.dumps(user_info)
+    response['Content-Type'] = 'application/json; charset=UTF-8'
+    response['Content-Length'] = len(response.content)
+    return response
 
-        # Check if the token has expired.
-        if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
-            return render_fault(request, Unauthorized('Authentication expired'))
+@api_method(http_method='GET', token_required=True)
+def authenticate(request, user=None):
+    # Normal Response Codes: 204
+    # Error Response Codes: internalServerError (500)
+    #                       badRequest (400)
+    #                       unauthorised (401)
+    if not user:
+        raise BadRequest('No user')
+    
+    # Check if the is active.
+    if not user.is_active:
+        raise Unauthorized('User inactive')
 
-        response = HttpResponse()
-        response.status=204
-        user_info = {'username':user.username,
-                     'uniq':user.email,
-                     'auth_token':user.auth_token,
-                     'auth_token_created':user.auth_token_created.isoformat(),
-                     'auth_token_expires':user.auth_token_expires.isoformat()}
-        response.content = json.dumps(user_info)
-        response['Content-Type'] = 'application/json; charset=UTF-8'
-        response['Content-Length'] = len(response.content)
-        return response
-    except BaseException, e:
-        fault = InternalServerError('Unexpected error')
-        return render_fault(request, fault)
+    # Check if the token has expired.
+    if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
+        raise Unauthorized('Authentication expired')
+    
+    if not user.signed_terms():
+        raise Unauthorized('Pending approval terms')
+    
+    response = HttpResponse()
+    response.status=204
+    user_info = {'userid':user.username,
+                 'email':[user.email],
+                 'name':user.realname,
+                 'auth_token':user.auth_token,
+                 'auth_token_created':epoch(user.auth_token_created),
+                 'auth_token_expires':epoch(user.auth_token_expires),
+                 'has_credits':user.has_credits,
+                 'is_active':user.is_active,
+                 'groups':[g.name for g in user.groups.all()]}
+    response.content = json.dumps(user_info)
+    response['Content-Type'] = 'application/json; charset=UTF-8'
+    response['Content-Length'] = len(response.content)
+    return response
 
+@api_method(http_method='GET')
 def get_services(request):
-    if request.method != 'GET':
-        raise BadRequest('Method not allowed.')
-
     callback = request.GET.get('callback', None)
     data = json.dumps(CLOUD_SERVICES)
     mimetype = 'application/json'
@@ -111,32 +182,39 @@ def get_services(request):
 
     return HttpResponse(content=data, mimetype=mimetype)
 
-def get_menu(request):
-    if request.method != 'GET':
-        raise BadRequest('Method not allowed.')
-    location = request.GET.get('location', '')
+@api_method()
+def get_menu(request, with_extra_links=False, with_signout=True):
+    index_url = reverse('index')
     absolute = lambda (url): request.build_absolute_uri(url)
-    index_url = absolute(reverse('astakos.im.views.index'))
-    if urlparse(location).query.rfind('next=') == -1:
-        index_url = '%s?next=%s' % (index_url, quote(location))
-    l = [{ 'url': index_url, 'name': "login..."}]
-    if request.user.is_authenticated():
+    l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
+    cookie = urllib.unquote(request.COOKIES.get(COOKIE_NAME, ''))
+    email = cookie.partition('|')[0]
+    try:
+        user = AstakosUser.objects.get(email=email, is_active=True)
+    except AstakosUser.DoesNotExist:
+        pass
+    else:
         l = []
+        l.append({ 'url': absolute(reverse('astakos.im.views.index')),
+                  'name': user.email})
         l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
-                  'name': request.user.email})
-        l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
-                  'name': "view your profile..." })
-        if request.user.password:
-            l.append({ 'url': absolute(reverse('password_change')),
-                      'name': "change your password..." })
-        if INVITATIONS_ENABLED:
-            l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
-                      'name': "invite some friends..." })
-        l.append({ 'url': absolute(reverse('astakos.im.views.send_feedback')),
-                  'name': "feedback..." })
-        l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
-                  'name': "logout..."})
-
+                  'name': "My account" })
+        if with_extra_links:
+            if user.has_usable_password():
+                l.append({ 'url': absolute(reverse('password_change')),
+                          'name': "Change password" })
+            if EMAILCHANGE_ENABLED:
+                l.append({'url':absolute(reverse('email_change')),
+                          'name': "Change email"})
+            if INVITATIONS_ENABLED:
+                l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
+                          'name': "Invitations" })
+            l.append({ 'url': absolute(reverse('astakos.im.views.feedback')),
+                      'name': "Feedback" })
+        if with_signout:
+            l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
+                      'name': "Sign out"})
+    
     callback = request.GET.get('callback', None)
     data = json.dumps(tuple(l))
     mimetype = 'application/json'
@@ -146,3 +224,69 @@ def get_menu(request):
         data = '%s(%s)' % (callback, data)
 
     return HttpResponse(content=data, mimetype=mimetype)
+
+@api_method(http_method='GET', token_required=True, perms=['im.can_access_userinfo'])
+def get_user_by_email(request, user=None):
+    # Normal Response Codes: 200
+    # Error Response Codes: internalServerError (500)
+    #                       badRequest (400)
+    #                       unauthorised (401)
+    #                       forbidden (403)
+    #                       itemNotFound (404)
+    email = request.GET.get('name')
+    if not email:
+        raise BadRequest('Email missing')
+    try:
+        user = AstakosUser.objects.get(email = email)
+    except AstakosUser.DoesNotExist, e:
+        raise ItemNotFound('Invalid email')
+    
+    if not user.is_active:
+        raise ItemNotFound('Inactive user')
+    else:
+        response = HttpResponse()
+        response.status=200
+        user_info = {'id':user.id,
+                     'username':user.username,
+                     'email':[user.email],
+                     'enabled':user.is_active,
+                     'name':user.realname,
+                     'auth_token_created':user.auth_token_created.strftime(format),
+                     'auth_token_expires':user.auth_token_expires.strftime(format),
+                     'has_credits':user.has_credits,
+                     'groups':[g.name for g in user.groups.all()],
+                     'user_permissions':[p.codename for p in user.user_permissions.all()],
+                     'group_permissions': list(user.get_group_permissions())}
+        response.content = json.dumps(user_info)
+        response['Content-Type'] = 'application/json; charset=UTF-8'
+        response['Content-Length'] = len(response.content)
+        return response
+
+@api_method(http_method='GET', token_required=True, perms=['can_access_userinfo'])
+def get_user_by_username(request, user_id, user=None):
+    # Normal Response Codes: 200
+    # Error Response Codes: internalServerError (500)
+    #                       badRequest (400)
+    #                       unauthorised (401)
+    #                       forbidden (403)
+    #                       itemNotFound (404)
+    try:
+        user = AstakosUser.objects.get(username = user_id)
+    except AstakosUser.DoesNotExist, e:
+        raise ItemNotFound('Invalid userid')
+    else:
+        response = HttpResponse()
+        response.status=200
+        user_info = {'id':user.id,
+                     'username':user.username,
+                     'email':[user.email],
+                     'name':user.realname,
+                     'auth_token_created':user.auth_token_created.strftime(format),
+                     'auth_token_expires':user.auth_token_expires.strftime(format),
+                     'has_credits':user.has_credits,
+                     'enabled':user.is_active,
+                     'groups':[g.name for g in user.groups.all()]}
+        response.content = json.dumps(user_info)
+        response['Content-Type'] = 'application/json; charset=UTF-8'
+        response['Content-Length'] = len(response.content)
+        return response
\ No newline at end of file