Fix im settings.
[pithos] / pithos / im / views.py
1 # Copyright 2011 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 json
35 import logging
36 import socket
37
38 from datetime import datetime
39 from functools import wraps
40 from math import ceil
41 from random import randint
42 from smtplib import SMTPException
43
44 from django.conf import settings
45 from django.core.mail import send_mail
46 from django.http import HttpResponse, HttpResponseRedirect
47 from django.shortcuts import redirect
48 from django.template.loader import render_to_string
49 from django.utils.http import urlencode
50 from django.utils.translation import ugettext as _
51 from django.core.urlresolvers import reverse
52
53 from urllib import quote
54
55 from pithos.im.models import User, Invitation
56 from pithos.im.util import isoformat
57
58
59 def render_response(template, tab=None, status=200, **kwargs):
60     if tab is None:
61         tab = template.partition('_')[0]
62     kwargs.setdefault('tab', tab)
63     html = render_to_string(template, kwargs)
64     return HttpResponse(html, status=status)
65
66
67 def requires_login(func):
68     @wraps(func)
69     def wrapper(request, *args):
70         if not settings.BYPASS_ADMIN_AUTH:
71             if not request.user:
72                 next = urlencode({'next': request.build_absolute_uri()})
73                 login_uri = reverse(index) + '?' + next
74                 return HttpResponseRedirect(login_uri)
75         return func(request, *args)
76     return wrapper
77
78
79 def requires_admin(func):
80     @wraps(func)
81     def wrapper(request, *args):
82         if not settings.BYPASS_ADMIN_AUTH:
83             if not request.user:
84                 next = urlencode({'next': request.build_absolute_uri()})
85                 login_uri = reverse(index) + '?' + next
86                 return HttpResponseRedirect(login_uri)
87             if not request.user.is_admin:
88                 return HttpResponse('Forbidden', status=403)
89         return func(request, *args)
90     return wrapper
91
92
93 def index(request):
94     return render_response('index.html', next=request.GET.get('next', ''))
95
96
97 @requires_admin
98 def admin(request):
99     stats = {}
100     stats['users'] = User.objects.count()
101     
102     invitations = Invitation.objects.all()
103     stats['invitations'] = invitations.count()
104     stats['invitations_accepted'] = invitations.filter(is_accepted=True).count()
105     
106     return render_response('admin.html', tab='home', stats=stats)
107
108
109 @requires_admin
110 def users_list(request):
111     users = User.objects.order_by('id')
112     
113     filter = request.GET.get('filter', '')
114     if filter:
115         if filter.startswith('-'):
116             users = users.exclude(uniq__icontains=filter[1:])
117         else:
118             users = users.filter(uniq__icontains=filter)
119     
120     try:
121         page = int(request.GET.get('page', 1))
122     except ValueError:
123         page = 1
124     offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
125     limit = offset + settings.ADMIN_PAGE_LIMIT
126     
127     npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
128     prev = page - 1 if page > 1 else None
129     next = page + 1 if page < npages else None
130     return render_response('users_list.html',
131                             users=users[offset:limit],
132                             filter=filter,
133                             pages=range(1, npages + 1),
134                             page=page,
135                             prev=prev,
136                             next=next)
137     
138 @requires_admin
139 def users_create(request):
140     if request.method == 'GET':
141         return render_response('users_create.html')
142     if request.method == 'POST':
143         user = User()
144         user.uniq = request.POST.get('uniq')
145         user.realname = request.POST.get('realname')
146         user.is_admin = True if request.POST.get('admin') else False
147         user.affiliation = request.POST.get('affiliation')
148         user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
149         user.renew_token()
150         user.save()
151         return redirect(users_info, user.id)
152
153 @requires_admin
154 def users_info(request, user_id):
155     user = User.objects.get(id=user_id)
156     states = [x[0] for x in User.ACCOUNT_STATE]
157     return render_response('users_info.html',
158                             user=user,
159                             states=states)
160
161
162 @requires_admin
163 def users_modify(request, user_id):
164     user = User.objects.get(id=user_id)
165     user.uniq = request.POST.get('uniq')
166     user.realname = request.POST.get('realname')
167     user.is_admin = True if request.POST.get('admin') else False
168     user.affiliation = request.POST.get('affiliation')
169     user.state = request.POST.get('state')
170     user.invitations = int(request.POST.get('invitations') or 0)
171     user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3)  # In GiB
172     user.auth_token = request.POST.get('auth_token')
173     try:
174         auth_token_expires = request.POST.get('auth_token_expires')
175         d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
176         user.auth_token_expires = d
177     except ValueError:
178         pass
179     user.save()
180     return redirect(users_info, user.id)
181
182
183 @requires_admin
184 def users_delete(request, user_id):
185     user = User.objects.get(id=user_id)
186     user.delete()
187     return redirect(users_list)
188
189
190 def generate_invitation_code():
191     while True:
192         code = randint(1, 2L**63 - 1)
193         try:
194             Invitation.objects.get(code=code)
195             # An invitation with this code already exists, try again
196         except Invitation.DoesNotExist:
197             return code
198
199
200 def send_invitation(inv):
201     url = settings.INVITATION_LOGIN_TARGET % inv.code
202     subject = _('Invitation to Pithos')
203     message = render_to_string('invitation.txt', {
204                 'invitation': inv,
205                 'url': url,
206                 'baseurl': settings.BASE_URL,
207                 'service': settings.SERVICE_NAME,
208                 'support': settings.DEFAULT_CONTACT_EMAIL})
209     sender = settings.DEFAULT_FROM_EMAIL
210     send_mail(subject, message, sender, [inv.uniq])
211     logging.info('Sent invitation %s', inv)
212
213
214 @requires_login
215 def invite(request):
216     status = None
217     message = None
218     inviter = request.user
219
220     if request.method == 'POST':
221         uniq = request.POST.get('uniq')
222         realname = request.POST.get('realname')
223         
224         if inviter.invitations > 0:
225             code = generate_invitation_code()
226             invitation, created = Invitation.objects.get_or_create(
227                 inviter=inviter,
228                 uniq=uniq,
229                 defaults={'code': code, 'realname': realname})
230             
231             try:
232                 send_invitation(invitation)
233                 if created:
234                     inviter.invitations = max(0, inviter.invitations - 1)
235                     inviter.save()
236                 status = 'success'
237                 message = _('Invitation sent to %s' % uniq)
238             except (SMTPException, socket.error) as e:
239                 status = 'error'
240                 message = getattr(e, 'strerror', '')
241         else:
242             status = 'error'
243             message = _('No invitations left')
244
245     if request.GET.get('format') == 'json':
246         sent = [{'email': inv.uniq,
247                  'realname': inv.realname,
248                  'is_accepted': inv.is_accepted}
249                     for inv in inviter.invitations_sent.all()]
250         rep = {'invitations': inviter.invitations, 'sent': sent}
251         return HttpResponse(json.dumps(rep))
252     
253     html = render_to_string('invitations.html', {
254             'user': inviter,
255             'status': status,
256             'message': message})
257     return HttpResponse(html)
258
259 def send_verification(user):
260     url = settings.ACTIVATION_LOGIN_TARGET % quote(user.auth_token)
261     message = render_to_string('activation.txt', {
262             'user': user,
263             'url': url,
264             'baseurl': settings.BASE_URL,
265             'service': settings.SERVICE_NAME,
266             'support': settings.DEFAULT_CONTACT_EMAIL})
267     sender = settings.DEFAULT_FROM_EMAIL
268     send_mail('Pithos account activation', message, sender, [user.email])
269     logging.info('Sent activation %s', user)
270
271 def local_create(request):
272     if request.method == 'GET':
273         return render_response('local_create.html')
274     elif request.method == 'POST':
275         username = request.POST.get('uniq')
276         realname = request.POST.get('realname')
277         email = request.POST.get('email')
278         password = request.POST.get('password')
279         status = 'success'
280         if not username:
281             status = 'error'
282             message = 'No username provided'
283         elif not password:
284             status = 'error'
285             message = 'No password provided'
286         elif not email:
287             status = 'error'
288             message = 'No email provided'
289         
290         if status == 'success':
291             username = '%s@local' % username
292             try:
293                 user = User.objects.get(uniq=username)
294                 status = 'error'
295                 message = 'Username is not available'
296             except User.DoesNotExist:
297                 user = User()
298                 user.uniq = username 
299                 user.realname = realname
300                 user.email = request.POST.get('email')
301                 user.password = request.POST.get('password')
302                 user.is_admin = False
303                 user.quota = 0
304                 user.state = 'UNVERIFIED'
305                 user.level = 1
306                 user.renew_token()
307                 try:
308                     send_verification(user)
309                     message = _('Verification sent to %s' % user.email)
310                     user.save()
311                 except (SMTPException, socket.error) as e:
312                     status = 'error'
313                     name = 'strerror'
314                     message = getattr(e, name) if hasattr(e, name) else e
315         
316         html = render_to_string('local_create.html', {
317                 'status': status,
318                 'message': message})
319         return HttpResponse(html)
320
321 def send_password(user):
322     url = settings.PASSWORD_RESET_TARGET % quote(user.auth_token)
323     message = render_to_string('password.txt', {
324             'user': user,
325             'url': url,
326             'baseurl': settings.BASE_URL,
327             'service': settings.SERVICE_NAME,
328             'support': settings.DEFAULT_CONTACT_EMAIL})
329     sender = settings.DEFAULT_FROM_EMAIL
330     send_mail('Pithos password recovering', message, sender, [user.email])
331     logging.info('Sent password %s', user)
332
333 def reclaim_password(request):
334     if request.method == 'GET':
335         return render_response('reclaim.html')
336     elif request.method == 'POST':
337         username = request.POST.get('uniq')
338         username = '%s@local' % username
339         try:
340             user = User.objects.get(uniq=username)
341             try:
342                 send_password(user)
343                 status = 'success'
344                 message = _('Password reset sent to %s' % user.email)
345                 user.save()
346             except (SMTPException, socket.error) as e:
347                 status = 'error'
348                 name = 'strerror'
349                 message = getattr(e, name) if hasattr(e, name) else e
350         except User.DoesNotExist:
351             status = 'error'
352             message = 'Username does not exist'
353         
354         html = render_to_string('reclaim.html', {
355                 'status': status,
356                 'message': message})
357         return HttpResponse(html)
358
359 def reset_password(request):
360     if request.method == 'GET':
361         token = request.GET.get('auth')
362         next = request.GET.get('next')
363         kwargs = {'auth': token,
364                   'next': next}
365         if not token:
366             kwargs.update({'status': 'error',
367                            'message': 'Missing token'})
368         html = render_to_string('reset.html', kwargs)
369         return HttpResponse(html)
370     elif request.method == 'POST':
371         token = request.POST.get('auth')
372         password = request.POST.get('password')
373         url = request.POST.get('next')
374         if not token:
375             status = 'error'
376             message = 'Bad Request: missing token'
377         try:
378             user = User.objects.get(auth_token=token)
379             user.password = password
380             user.save()
381             if url:
382                 return HttpResponseRedirect(url)
383         except User.DoesNotExist:
384             status = 'error'
385             message = 'Bad Request: invalid token'
386             
387         html = render_to_string('reset.html', {
388                 'status': status,
389                 'message': message})
390         return HttpResponse(html)