Statistics
| Branch: | Tag: | Revision:

root / pithos / im / views.py @ bee5ffa6

History | View | Annotate | Download (13.6 kB)

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.SUPPORT_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.SUPPORT_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.SUPPORT_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)