1 # Copyright 2011 GRNET S.A. All rights reserved.
3 # Redistribution and use in source and binary forms, with or
4 # without modification, are permitted provided that the following
7 # 1. Redistributions of source code must retain the above
8 # copyright notice, this list of conditions and the following
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.
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.
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.
38 from datetime import datetime
39 from functools import wraps
41 from random import randint
42 from smtplib import SMTPException
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
53 from urllib import quote
55 from pithos.im.models import User, Invitation
56 from pithos.im.util import isoformat
59 def render_response(template, tab=None, status=200, **kwargs):
61 tab = template.partition('_')[0]
62 kwargs.setdefault('tab', tab)
63 html = render_to_string(template, kwargs)
64 return HttpResponse(html, status=status)
67 def requires_login(func):
69 def wrapper(request, *args):
70 if not settings.BYPASS_ADMIN_AUTH:
72 next = urlencode({'next': request.build_absolute_uri()})
73 login_uri = reverse(index) + '?' + next
74 return HttpResponseRedirect(login_uri)
75 return func(request, *args)
79 def requires_admin(func):
81 def wrapper(request, *args):
82 if not settings.BYPASS_ADMIN_AUTH:
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)
94 return render_response('index.html', next=request.GET.get('next', ''))
100 stats['users'] = User.objects.count()
102 invitations = Invitation.objects.all()
103 stats['invitations'] = invitations.count()
104 stats['invitations_accepted'] = invitations.filter(is_accepted=True).count()
106 return render_response('admin.html', tab='home', stats=stats)
110 def users_list(request):
111 users = User.objects.order_by('id')
113 filter = request.GET.get('filter', '')
115 if filter.startswith('-'):
116 users = users.exclude(uniq__icontains=filter[1:])
118 users = users.filter(uniq__icontains=filter)
121 page = int(request.GET.get('page', 1))
124 offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
125 limit = offset + settings.ADMIN_PAGE_LIMIT
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],
133 pages=range(1, npages + 1),
139 def users_create(request):
140 if request.method == 'GET':
141 return render_response('users_create.html')
142 if request.method == 'POST':
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
151 return redirect(users_info, user.id)
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',
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')
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
180 return redirect(users_info, user.id)
184 def users_delete(request, user_id):
185 user = User.objects.get(id=user_id)
187 return redirect(users_list)
190 def generate_invitation_code():
192 code = randint(1, 2L**63 - 1)
194 Invitation.objects.get(code=code)
195 # An invitation with this code already exists, try again
196 except Invitation.DoesNotExist:
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', {
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)
218 inviter = request.user
220 if request.method == 'POST':
221 uniq = request.POST.get('uniq')
222 realname = request.POST.get('realname')
224 if inviter.invitations > 0:
225 code = generate_invitation_code()
226 invitation, created = Invitation.objects.get_or_create(
229 defaults={'code': code, 'realname': realname})
232 send_invitation(invitation)
234 inviter.invitations = max(0, inviter.invitations - 1)
237 message = _('Invitation sent to %s' % uniq)
238 except (SMTPException, socket.error) as e:
240 message = getattr(e, 'strerror', '')
243 message = _('No invitations left')
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))
253 html = render_to_string('invitations.html', {
257 return HttpResponse(html)
259 def send_verification(user):
260 url = settings.ACTIVATION_LOGIN_TARGET % quote(user.auth_token)
261 message = render_to_string('activation.txt', {
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)
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')
282 message = 'No username provided'
285 message = 'No password provided'
288 message = 'No email provided'
290 if status == 'success':
291 username = '%s@local' % username
293 user = User.objects.get(uniq=username)
295 message = 'Username is not available'
296 except User.DoesNotExist:
299 user.realname = realname
300 user.email = request.POST.get('email')
301 user.password = request.POST.get('password')
302 user.is_admin = False
304 user.state = 'UNVERIFIED'
308 send_verification(user)
309 message = _('Verification sent to %s' % user.email)
311 except (SMTPException, socket.error) as e:
314 message = getattr(e, name) if hasattr(e, name) else e
316 html = render_to_string('local_create.html', {
319 return HttpResponse(html)
321 def send_password(user):
322 url = settings.PASSWORD_RESET_TARGET % quote(user.auth_token)
323 message = render_to_string('password.txt', {
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)
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
340 user = User.objects.get(uniq=username)
344 message = _('Password reset sent to %s' % user.email)
346 except (SMTPException, socket.error) as e:
349 message = getattr(e, name) if hasattr(e, name) else e
350 except User.DoesNotExist:
352 message = 'Username does not exist'
354 html = render_to_string('reclaim.html', {
357 return HttpResponse(html)
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,
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')
376 message = 'Bad Request: missing token'
378 user = User.objects.get(auth_token=token)
379 user.password = password
382 return HttpResponseRedirect(url)
383 except User.DoesNotExist:
385 message = 'Bad Request: invalid token'
387 html = render_to_string('reset.html', {
390 return HttpResponse(html)