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(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', {
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(request.build_absolute_uri('/').rstrip('/'), 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(baseurl, user):
260 url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
261 quote(user.auth_token),
263 message = render_to_string('activation.txt', {
267 'service': settings.SERVICE_NAME,
268 'support': settings.DEFAULT_CONTACT_EMAIL})
269 sender = settings.DEFAULT_FROM_EMAIL
270 send_mail('Pithos account activation', message, sender, [user.email])
271 logging.info('Sent activation %s', user)
273 def local_create(request):
274 if request.method == 'GET':
275 return render_response('local_create.html')
276 elif request.method == 'POST':
277 username = request.POST.get('uniq')
278 realname = request.POST.get('realname')
279 email = request.POST.get('email')
280 password = request.POST.get('password')
285 message = 'No username provided'
288 message = 'No password provided'
291 message = 'No email provided'
293 if status == 'success':
294 username = '%s@local' % username
296 user = User.objects.get(uniq=username)
298 message = 'Username is not available'
299 except User.DoesNotExist:
302 user.realname = realname
303 user.email = request.POST.get('email')
304 user.password = request.POST.get('password')
305 user.is_admin = False
307 user.state = 'UNVERIFIED'
311 send_verification(request.build_absolute_uri('/').rstrip('/'), user)
312 message = _('Verification sent to %s' % user.email)
314 except (SMTPException, socket.error) as e:
317 message = getattr(e, name) if hasattr(e, name) else e
319 html = render_to_string('local_create.html', {
322 response = HttpResponse(html)
325 def send_password(baseurl, user):
326 url = settings.PASSWORD_RESET_TARGET % (baseurl,
329 message = render_to_string('password.txt', {
333 'service': settings.SERVICE_NAME,
334 'support': settings.DEFAULT_CONTACT_EMAIL})
335 sender = settings.DEFAULT_FROM_EMAIL
336 send_mail('Pithos password recovering', message, sender, [user.email])
337 logging.info('Sent password %s', user)
339 def reclaim_password(request):
340 if request.method == 'GET':
341 return render_response('reclaim.html')
342 elif request.method == 'POST':
343 username = request.POST.get('uniq')
344 username = '%s@local' % username
346 user = User.objects.get(uniq=username)
348 send_password(request.build_absolute_uri('/').rstrip('/'), user)
350 message = _('Password reset sent to %s' % user.email)
351 user.status = 'UNVERIFIED'
353 except (SMTPException, socket.error) as e:
356 message = getattr(e, name) if hasattr(e, name) else e
357 except User.DoesNotExist:
359 message = 'Username does not exist'
361 html = render_to_string('reclaim.html', {
364 return HttpResponse(html)