Provide email change mechanism
[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     index_url = reverse('index')
183     absolute = lambda (url): request.build_absolute_uri(url)
184     l = [{ 'url': absolute(index_url), 'name': "Sign in"}]
185     cookie = urllib.unquote(request.COOKIES.get(COOKIE_NAME, ''))
186     email = cookie.partition('|')[0]
187     try:
188         user = AstakosUser.objects.get(email=email, is_active=True)
189     except AstakosUser.DoesNotExist:
190         pass
191     else:
192         l = []
193         l.append({ 'url': absolute(reverse('astakos.im.views.index')),
194                   'name': user.email})
195         l.append({ 'url': absolute(reverse('astakos.im.views.edit_profile')),
196                   'name': "My account" })
197         if with_extra_links:
198             if user.has_usable_password():
199                 l.append({ 'url': absolute(reverse('password_change')),
200                           'name': "Change password" })
201             l.append({'url':absolute(reverse('email_change')),
202                       'name': "Change email"})
203             if INVITATIONS_ENABLED:
204                 l.append({ 'url': absolute(reverse('astakos.im.views.invite')),
205                           'name': "Invitations" })
206             l.append({ 'url': absolute(reverse('astakos.im.views.feedback')),
207                       'name': "Feedback" })
208         if with_signout:
209             l.append({ 'url': absolute(reverse('astakos.im.views.logout')),
210                       'name': "Sign out"})
211     
212     callback = request.GET.get('callback', None)
213     data = json.dumps(tuple(l))
214     mimetype = 'application/json'
215
216     if callback:
217         mimetype = 'application/javascript'
218         data = '%s(%s)' % (callback, data)
219
220     return HttpResponse(content=data, mimetype=mimetype)
221
222 @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_userid'])
223 def find_userid(request):
224     # Normal Response Codes: 204
225     # Error Response Codes: internalServerError (500)
226     #                       badRequest (400)
227     #                       unauthorised (401)
228     email = request.GET.get('email')
229     if not email:
230         raise BadRequest('Email missing')
231     try:
232         user = AstakosUser.objects.get(email = email, is_active=True)
233     except AstakosUser.DoesNotExist, e:
234         raise BadRequest('Invalid email')
235     else:
236         response = HttpResponse()
237         response.status=204
238         user_info = {'userid':user.username}
239         response.content = json.dumps(user_info)
240         response['Content-Type'] = 'application/json; charset=UTF-8'
241         response['Content-Length'] = len(response.content)
242         return response
243
244 @api_method(http_method='GET', token_required=True, perms=['astakos.im.can_find_email'])
245 def find_email(request):
246     # Normal Response Codes: 204
247     # Error Response Codes: internalServerError (500)
248     #                       badRequest (400)
249     #                       unauthorised (401)
250     userid = request.GET.get('userid')
251     if not userid:
252         raise BadRequest('Userid missing')
253     try:
254         user = AstakosUser.objects.get(username = userid)
255     except AstakosUser.DoesNotExist, e:
256         raise BadRequest('Invalid userid')
257     else:
258         response = HttpResponse()
259         response.status=204
260         user_info = {'userid':user.email}
261         response.content = json.dumps(user_info)
262         response['Content-Type'] = 'application/json; charset=UTF-8'
263         response['Content-Length'] = len(response.content)
264         return response