Fixes and Clouds animations
[astakos] / snf-astakos-app / astakos / im / api.py
1 # Copyright 2011-2012 GRNET S.A. All rights reserved.
2 #
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
5 # conditions are met:
6 #
7 #   1. Redistributions of source code must retain the above
8 #      copyright notice, this list of conditions and the following
9 #      disclaimer.
10 #
11 #   2. Redistributions in binary form must reproduce the above
12 #      copyright notice, this list of conditions and the following
13 #      disclaimer in the documentation and/or other materials
14 #      provided with the distribution.
15 #
16 # THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 # OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 # AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 # POSSIBILITY OF SUCH DAMAGE.
28 #
29 # The views and conclusions contained in the software and
30 # documentation are those of the authors and should not be
31 # interpreted as representing official policies, either expressed
32 # or implied, of GRNET S.A.
33
34 import logging
35 import urllib
36
37 from functools import wraps
38 from traceback import format_exc
39 from time import time, mktime
40 from urllib import quote
41 from urlparse import urlparse
42
43 from django.conf import settings
44 from django.http import HttpResponse
45 from django.utils import simplejson as json
46 from django.core.urlresolvers import reverse
47
48 from astakos.im.faults import BadRequest, Unauthorized, InternalServerError, Fault
49 from astakos.im.models import AstakosUser
50 from astakos.im.settings import CLOUD_SERVICES, INVITATIONS_ENABLED, COOKIE_NAME
51 from astakos.im.util import epoch
52
53 logger = logging.getLogger(__name__)
54
55 def render_fault(request, fault):
56     if isinstance(fault, InternalServerError) and settings.DEBUG:
57         fault.details = format_exc(fault)
58
59     request.serialization = 'text'
60     data = fault.message + '\n'
61     if fault.details:
62         data += '\n' + fault.details
63     response = HttpResponse(data, status=fault.code)
64     response['Content-Length'] = len(response.content)
65     return response
66
67 def api_method(http_method=None, token_required=False, perms=[]):
68     """Decorator function for views that implement an API method."""
69     
70     def decorator(func):
71         @wraps(func)
72         def wrapper(request, *args, **kwargs):
73             try:
74                 if http_method and request.method != http_method:
75                     raise BadRequest('Method not allowed.')
76                 x_auth_token = request.META.get('HTTP_X_AUTH_TOKEN')
77                 if token_required:
78                     if not x_auth_token:
79                         raise Unauthorized('Access denied')
80                     try:
81                         user = AstakosUser.objects.get(auth_token=x_auth_token)
82                         if not user.has_perms(perms):
83                             raise Unauthorized('Unauthorized request')
84                     except AstakosUser.DoesNotExist, e:
85                         raise Unauthorized('Invalid X-Auth-Token')
86                     kwargs['user'] = user
87                 response = func(request, *args, **kwargs)
88                 return response
89             except Fault, fault:
90                 return render_fault(request, fault)
91             except BaseException, e:
92                 logger.exception('Unexpected error: %s' % e)
93                 fault = InternalServerError('Unexpected error')
94                 return render_fault(request, fault)
95         return wrapper
96     return decorator
97
98 @api_method(http_method='GET', token_required=True)
99 def authenticate_old(request, user=None):
100     # Normal Response Codes: 204
101     # Error Response Codes: internalServerError (500)
102     #                       badRequest (400)
103     #                       unauthorised (401)
104     if not user:
105         raise BadRequest('No user')
106     
107     # Check if the is active.
108     if not user.is_active:
109         raise Unauthorized('User inactive')
110
111     # Check if the token has expired.
112     if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
113         raise Unauthorized('Authentication expired')
114     
115     if not user.signed_terms():
116         raise Unauthorized('Pending approval terms')
117     
118     response = HttpResponse()
119     response.status=204
120     user_info = {'username':user.username,
121                  'uniq':user.email,
122                  'auth_token':user.auth_token,
123                  'auth_token_created':user.auth_token_created.isoformat(),
124                  'auth_token_expires':user.auth_token_expires.isoformat(),
125                  'has_credits':user.has_credits,
126                  'has_signed_terms':user.signed_terms()}
127     response.content = json.dumps(user_info)
128     response['Content-Type'] = 'application/json; charset=UTF-8'
129     response['Content-Length'] = len(response.content)
130     return response
131
132 @api_method(http_method='GET', token_required=True)
133 def authenticate(request, user=None):
134     # Normal Response Codes: 204
135     # Error Response Codes: internalServerError (500)
136     #                       badRequest (400)
137     #                       unauthorised (401)
138     if not user:
139         raise BadRequest('No user')
140     
141     # Check if the is active.
142     if not user.is_active:
143         raise Unauthorized('User inactive')
144
145     # Check if the token has expired.
146     if (time() - mktime(user.auth_token_expires.timetuple())) > 0:
147         raise Unauthorized('Authentication expired')
148     
149     if not user.signed_terms():
150         raise Unauthorized('Pending approval terms')
151     
152     response = HttpResponse()
153     response.status=204
154     user_info = {'userid':user.username,
155                  'email':[user.email],
156                  'name':user.realname,
157                  'auth_token':user.auth_token,
158                  'auth_token_created':epoch(user.auth_token_created),
159                  'auth_token_expires':epoch(user.auth_token_expires),
160                  'has_credits':user.has_credits,
161                  'is_active':user.is_active,
162                  'groups':[g.name for g in user.groups.all()]}
163     response.content = json.dumps(user_info)
164     response['Content-Type'] = 'application/json; charset=UTF-8'
165     response['Content-Length'] = len(response.content)
166     return response
167
168 @api_method(http_method='GET')
169 def get_services(request):
170     callback = request.GET.get('callback', None)
171     data = json.dumps(CLOUD_SERVICES)
172     mimetype = 'application/json'
173
174     if callback:
175         mimetype = 'application/javascript'
176         data = '%s(%s)' % (callback, data)
177
178     return HttpResponse(content=data, mimetype=mimetype)
179
180 @api_method()
181 def get_menu(request, with_extra_links=False, with_signout=True):
182     exclude = []
183     index_url = reverse('index')
184     absolute = lambda (url): request.build_absolute_uri(url)
185     l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
186     cookie = urllib.unquote(request.COOKIES.get(COOKIE_NAME, ''))
187     email = cookie.partition('|')[0]
188     try:
189         user = AstakosUser.objects.get(email=email, is_active=True)
190     except AstakosUser.DoesNotExist:
191         pass
192     else:
193         l = []
194         l.append({ 'url': absolute(reverse('astakos.im.views.index')),
195                   'name': user.email})
196         l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
197                   'name': "My account" })
198         if with_extra_links:
199             if user.has_usable_password():
200                 l.append({ 'url': absolute(reverse('password_change')),
201                           'name': "Change password" })
202             if INVITATIONS_ENABLED:
203                 l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
204                           'name': "Invitations" })
205             l.append({ 'url': absolute(reverse('astakos.im.views.feedback')),
206                       'name': "Feedback" })
207         if with_signout:
208             l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
209                       'name': "Sign out"})
210     
211     callback = request.GET.get('callback', None)
212     data = json.dumps(tuple(l))
213     mimetype = 'application/json'
214
215     if callback:
216         mimetype = 'application/javascript'
217         data = '%s(%s)' % (callback, data)
218
219     return HttpResponse(content=data, mimetype=mimetype)
220
221 @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_userid'])
222 def find_userid(request):
223     # Normal Response Codes: 204
224     # Error Response Codes: internalServerError (500)
225     #                       badRequest (400)
226     #                       unauthorised (401)
227     email = request.GET.get('email')
228     if not email:
229         raise BadRequest('Email missing')
230     try:
231         user = AstakosUser.objects.get(email = email, is_active=True)
232     except AstakosUser.DoesNotExist, e:
233         raise BadRequest('Invalid email')
234     else:
235         response = HttpResponse()
236         response.status=204
237         user_info = {'userid':user.username}
238         response.content = json.dumps(user_info)
239         response['Content-Type'] = 'application/json; charset=UTF-8'
240         response['Content-Length'] = len(response.content)
241         return response
242
243 @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_email'])
244 def find_email(request):
245     # Normal Response Codes: 204
246     # Error Response Codes: internalServerError (500)
247     #                       badRequest (400)
248     #                       unauthorised (401)
249     userid = request.GET.get('userid')
250     if not userid:
251         raise BadRequest('Userid missing')
252     try:
253         user = AstakosUser.objects.get(username = userid)
254     except AstakosUser.DoesNotExist, e:
255         raise BadRequest('Invalid userid')
256     else:
257         response = HttpResponse()
258         response.status=204
259         user_info = {'userid':user.email}
260         response.content = json.dumps(user_info)
261         response['Content-Type'] = 'application/json; charset=UTF-8'
262         response['Content-Length'] = len(response.content)
263         return response