Statistics
| Branch: | Tag: | Revision:

root / pithos / im / views.py @ a7bdef13

History | View | Annotate | Download (13 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(baseurl, inv):
201
    url = settings.INVITATION_LOGIN_TARGET % (baseurl, inv.code, quote(baseurl))
202
    subject = _('Invitation to Pithos')
203
    message = render_to_string('invitation.txt', {
204
                'invitation': inv,
205
                'url': url,
206
                'baseurl': baseurl,
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(request.get_host(), 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(baseurl, user):
260
    next = quote('http://%s' % baseurl)
261
    url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
262
                                              quote(user.auth_token),
263
                                              next)
264
    message = render_to_string('activation.txt', {
265
            'user': user,
266
            'url': url,
267
            'baseurl': baseurl,
268
            'service': settings.SERVICE_NAME,
269
            'support': settings.DEFAULT_CONTACT_EMAIL})
270
    sender = settings.DEFAULT_FROM_EMAIL
271
    send_mail('Pithos account activation', message, sender, [user.email])
272
    logging.info('Sent activation %s', user)
273

    
274
def local_create(request):
275
    if request.method == 'GET':
276
        return render_response('local_create.html')
277
    elif request.method == 'POST':
278
        username = request.POST.get('uniq')
279
        realname = request.POST.get('realname')
280
        email = request.POST.get('email')
281
        password = request.POST.get('password')
282
        status = 'success'
283
        cookie_value = None
284
        if not username:
285
            status = 'error'
286
            message = 'No username provided'
287
        elif not password:
288
            status = 'error'
289
            message = 'No password provided'
290
        elif not email:
291
            status = 'error'
292
            message = 'No email provided'
293
        
294
        if status == 'success':
295
            username = '%s@local' % username
296
            try:
297
                user = User.objects.get(uniq=username)
298
                status = 'error'
299
                message = 'Username is not available'
300
            except User.DoesNotExist:
301
                user = User()
302
                user.uniq = username 
303
                user.realname = realname
304
                user.email = request.POST.get('email')
305
                user.password = request.POST.get('password')
306
                user.is_admin = False
307
                user.quota = 0
308
                user.state = 'UNVERIFIED'
309
                user.level = 1
310
                user.renew_token()
311
                try:
312
                    send_verification(request.get_host(), user)
313
                    message = _('Verification sent to %s' % user.email)
314
                    user.save()
315
                except (SMTPException, socket.error) as e:
316
                    status = 'error'
317
                    name = 'strerror'
318
                    message = getattr(e, name) if hasattr(e, name) else e
319
        
320
        html = render_to_string('local_create.html', {
321
                'status': status,
322
                'message': message})
323
        response = HttpResponse(html)
324
        return response
325

    
326
def send_password(baseurl, user):
327
    next = quote('http://%s' % baseurl)
328
    url = settings.PASSWORD_RESET_TARGET % (baseurl,
329
                                            quote(user.uniq),
330
                                            next)
331
    message = render_to_string('password.txt', {
332
            'user': user,
333
            'url': url,
334
            'baseurl': baseurl,
335
            'service': settings.SERVICE_NAME,
336
            'support': settings.DEFAULT_CONTACT_EMAIL})
337
    sender = settings.DEFAULT_FROM_EMAIL
338
    send_mail('Pithos password recovering', message, sender, [user.email])
339
    logging.info('Sent password %s', user)
340

    
341
def reclaim_password(request):
342
    if request.method == 'GET':
343
        return render_response('reclaim.html')
344
    elif request.method == 'POST':
345
        username = request.POST.get('uniq')
346
        username = '%s@local' % username
347
        try:
348
            user = User.objects.get(uniq=username)
349
            try:
350
                send_password(request.get_host(), user)
351
                status = 'success'
352
                message = _('Password reset sent to %s' % user.email)
353
                user.status = 'UNVERIFIED'
354
                user.save()
355
            except (SMTPException, socket.error) as e:
356
                status = 'error'
357
                name = 'strerror'
358
                message = getattr(e, name) if hasattr(e, name) else e
359
        except User.DoesNotExist:
360
            status = 'error'
361
            message = 'Username does not exist'
362
        
363
        html = render_to_string('reclaim.html', {
364
                'status': status,
365
                'message': message})
366
        return HttpResponse(html)