{% block tabs %}
<ul class="tabs">
<li{% ifequal tab "home" %} class="active"{% endifequal %}>
- <a href="{% url astakos.im.views.admin %}">Home</a>
+ <a href="{% url astakos.im.admin.views.admin %}">Home</a>
</li>
<li{% ifequal tab "users" %} class="active"{% endifequal %}>
- <a href="{% url astakos.im.views.users_list %}">Users</a>
+ <a href="{% url astakos.im.admin.views.users_list %}">Users</a>
</li>
<li{% ifequal tab "pending" %} class="active"{% endifequal %}>
- <a href="{% url astakos.im.views.pending_users %}">Pending Users</a>
+ <a href="{% url astakos.im.admin.views.pending_users %}">Pending Users</a>
</li>
<li{% ifequal tab "invitations" %} class="active"{% endifequal %}>
- <a href="{% url astakos.im.views.invitations_list %}">Invitations</a>
+ <a href="{% url astakos.im.admin.views.invitations_list %}">Invitations</a>
</li>
</ul>
{% endblock %}
<thead>
<tr>
<th>ID</th>
- <th>Uniq</th>
+ <th>Username</th>
<th>Real Name</th>
<th>Code</th>
- <th>Inviter Uniq</th>
+ <th>Inviter username</th>
<th>Inviter Real Name</th>
<th>Is consumed</th>
<th>Created</th>
{% for inv in invitations %}
<tr>
<td>{{ inv.id }}</td>
- <td>{{ inv.uniq }}</td>
+ <td>{{ inv.username }}</td>
<td>{{ inv.realname }}</td>
<td>{{ inv.code }}</td>
- <td>{{ inv.inviter.uniq }}</td>
+ <td>{{ inv.inviter.username }}</td>
<td>{{ inv.inviter.realname }}</td>
<td>{{ inv.is_consumed }}</td>
<td>{{ inv.created }}</td>
</div>
{% endif %}
-<a class="btn success" href="{% url astakos.im.views.invitations_export %}">Export</a>
+<a class="btn success" href="{% url astakos.im.admin.views.invitations_export %}">Export</a>
<br /><br />
{% endblock body %}
<thead>
<tr>
<th>ID</th>
- <th>Uniq</th>
+ <th>Username</th>
<th>Real Name</th>
<th>Affiliation</th>
<th>Email</th>
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
- <td>{{ user.uniq }}</td>
+ <td>{{ user.username }}</td>
<td>{{ user.realname }}</td>
<td>{{ user.affiliation }}</td>
<td>{{ user.email }}</td>
<td>{{ user.inviter.realname }}</td>
<td>
- <form action="{% url astakos.im.views.users_activate user.id %}" method="post">
+ <form action="{% url astakos.im.admin.views.users_activate user.id %}" method="post">
<input type="hidden" name="page" value="{{ page }}">
<button type="submit" class="btn primary">Activate</button>
</form>
{% block body %}
-<form action="{% url astakos.im.views.users_create %}" method="post">
+<form action="{% url astakos.im.admin.views.users_create %}" method="post">
<div class="clearfix">
- <label for="user-uniq">Uniq</label>
+ <label for="user-username">Username</label>
<div class="input">
- <input class="span4" id="user-uniq" name="uniq" type="text" />
+ <input class="span4" id="user-username" name="username" type="text" />
+ </div>
+ </div>
+
+ <div class="clearfix">
+ <label for="user-email">Email</label>
+ <div class="input">
+ <input class="span4" id="user-email" name="email" type="text" />
</div>
</div>
<div class="clearfix">
- <label for="user-realname">Real Name</label>
+ <label for="user-first-name">First Name</label>
+ <div class="input">
+ <input class="span4" id="user-first-name" name="first_name" type="text" />
+ </div>
+ </div>
+
+ <div class="clearfix">
+ <label for="user-last-name">Last Name</label>
<div class="input">
- <input class="span4" id="user-realname" name="realname" type="text" />
+ <input class="span4" id="user-last-name" name="last_name" type="text" />
</div>
</div>
{% block body %}
-<form action="{% url astakos.im.views.users_modify user.id %}" method="post">
+<form action="{% url astakos.im.admin.views.users_modify user.id %}" method="post">
<div class="clearfix">
<label for="user-id">ID</label>
<div class="input">
</div>
<div class="clearfix">
- <label for="user-uniq">Uniq</label>
+ <label for="user-username">Username</label>
<div class="input">
- <input class="span4" id="user-uniq" name="uniq" value="{{ user.uniq }}" type="text" />
+ <input class="span4" id="user-username" name="username" value="{{ user.username }}" type="text" />
</div>
</div>
<div class="clearfix">
- <label for="user-realname">Real Name</label>
+ <label for="user-first-name">First Name</label>
<div class="input">
- <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
+ <input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" />
+ </div>
+ </div>
+
+ <div class="clearfix">
+ <label for="user-last-name">Last Name</label>
+ <div class="input">
+ <input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" />
</div>
</div>
<ul class="inputs-list">
<li>
<label>
- <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %}>
+ <input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %}>
</label>
</li>
</ul>
</div>
<div class="clearfix">
- <label for="user-state">State</label>
+ <label for="user-is-active">Is active?</label>
<div class="input">
- <select class="medium" id="user-state" name="state">
- {% for state in states %}
- <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
- {% endfor %}
- </select>
+ <ul class="inputs-list">
+ <li>
+ <label>
+ <input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %}>
+ </label>
+ </li>
+ </ul>
</div>
</div>
</div>
<div class="clearfix">
- <label for="user-created">Created</label>
+ <label for="user-date-joined">Created</label>
<div class="input">
- <span class="uneditable-input" id="user-created">{{ user.created }}</span>
+ <span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span>
</div>
</div>
<button type="submit" class="btn primary">Save Changes</button>
<button type="reset" class="btn">Reset</button>
- <a class="btn danger needs-confirm" href="{% url astakos.im.views.users_delete user.id %}">Delete User</a>
+ <a class="btn danger needs-confirm" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete User</a>
</div>
<div class="alert-message block-message error">
<p><strong>WARNING:</strong> Are you sure you want to delete this user?</p>
<div class="alert-actions">
- <a class="btn danger" href="{% url astakos.im.views.users_delete user.id %}">Delete</a>
+ <a class="btn danger" href="{% url astakos.im.admin.views.users_delete user.id %}">Delete</a>
<a class="btn alert-close">Cancel</a>
</div>
</div>
<thead>
<tr>
<th>ID</th>
- <th>Uniq</th>
+ <th>Username</th>
<th>Real Name</th>
<th>Admin</th>
<th>Affiliation</th>
- <th>State</th>
+ <th>Is active?</th>
<th>Quota</th>
<th>Updated</th>
</tr>
<tbody>
{% for user in users %}
<tr>
- <td><a href="{% url astakos.im.views.users_info user.id %}">{{ user.id }}</a></td>
- <td><a href="{% url astakos.im.views.users_info user.id %}">{{ user.uniq }}</a></td>
+ <td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.id }}</a></td>
+ <td><a href="{% url astakos.im.admin.views.users_info user.id %}">{{ user.username }}</a></td>
<td>{{ user.realname }}</td>
- <td>{{ user.is_admin }}</td>
+ <td>{{ user.is_superuser }}</td>
<td>{{ user.affiliation }}</td>
- <td>{{ user.state }}</td>
+ <td>{{ user.is_active }}</td>
<td>{{ user.quota|GiB }} GiB</td>
<td>{{ user.updated }}</td>
</tr>
</div>
{% endif %}
-<a class="btn success" href="{% url astakos.im.views.users_create %}">Create a user</a>
-<a class="btn success" href="{% url astakos.im.views.users_export %}">Export</a>
+<a class="btn success" href="{% url astakos.im.admin.views.users_create %}">Create a user</a>
+<a class="btn success" href="{% url astakos.im.admin.views.users_export %}">Export</a>
<br /><br />
{% endblock body %}
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.conf.urls.defaults import patterns
+
+urlpatterns = patterns('astakos.im.admin.views',
+ (r'^$', 'admin'),
+
+ (r'^users/?$', 'users_list'),
+ (r'^users/(\d+)/?$', 'users_info'),
+ (r'^users/create$', 'users_create'),
+ (r'^users/(\d+)/modify/?$', 'users_modify'),
+ (r'^users/(\d+)/delete/?$', 'users_delete'),
+ (r'^users/export/?$', 'users_export'),
+ (r'^users/pending/?$', 'pending_users'),
+ (r'^users/activate/(\d+)/?$', 'users_activate'),
+
+ (r'^invitations/?$', 'invitations_list'),
+ (r'^invitations/export/?$', 'invitations_export'),
+)
\ No newline at end of file
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+import json
+import logging
+import socket
+import csv
+import sys
+
+from datetime import datetime
+from functools import wraps
+from math import ceil
+from random import randint
+from smtplib import SMTPException
+from hashlib import new as newhasher
+from urllib import quote
+
+from django.conf import settings
+from django.core.mail import send_mail
+from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
+from django.shortcuts import redirect
+from django.template.loader import render_to_string
+from django.utils.http import urlencode
+from django.utils.translation import ugettext as _
+from django.core.urlresolvers import reverse
+
+#from astakos.im.openid_store import PithosOpenIDStore
+from astakos.im.models import AstakosUser, Invitation
+from astakos.im.util import isoformat, get_or_create_user, get_context
+from astakos.im.forms import *
+from astakos.im.backends import get_backend
+from astakos.im.views import render_response, index
+
+def requires_admin(func):
+ @wraps(func)
+ def wrapper(request, *args):
+ if not settings.BYPASS_ADMIN_AUTH:
+ if not request.user:
+ next = urlencode({'next': request.build_absolute_uri()})
+ login_uri = reverse(index) + '?' + next
+ return HttpResponseRedirect(login_uri)
+ if not request.user.is_superuser:
+ return HttpResponse('Forbidden', status=403)
+ return func(request, *args)
+ return wrapper
+
+@requires_admin
+def admin(request, template_name='admin.html', extra_context={}):
+ stats = {}
+ stats['users'] = AstakosUser.objects.count()
+ stats['pending'] = AstakosUser.objects.filter(is_active = False).count()
+
+ invitations = Invitation.objects.all()
+ stats['invitations'] = invitations.count()
+ stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
+
+ kwargs = {'tab': 'home', 'stats': stats}
+ context = get_context(request, extra_context, **kwargs)
+ return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_list(request, template_name='users_list.html', extra_context={}):
+ users = AstakosUser.objects.order_by('id')
+
+ filter = request.GET.get('filter', '')
+ if filter:
+ if filter.startswith('-'):
+ users = users.exclude(username__icontains=filter[1:])
+ else:
+ users = users.filter(username__icontains=filter)
+
+ try:
+ page = int(request.GET.get('page', 1))
+ except ValueError:
+ page = 1
+ offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+ limit = offset + settings.ADMIN_PAGE_LIMIT
+
+ npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+ prev = page - 1 if page > 1 else None
+ next = page + 1 if page < npages else None
+
+ kwargs = {'users':users[offset:limit],
+ 'filter':filter,
+ 'pages':range(1, npages + 1),
+ 'prev':prev,
+ 'next':next}
+ context = get_context(request, extra_context, **kwargs)
+ return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_info(request, user_id, template_name='users_info.html', extra_context={}):
+ if not extra_context:
+ extra_context = {}
+ kwargs = {'user':AstakosUser.objects.get(id=user_id)}
+ context = get_context(request, extra_context, **kwargs)
+ return render_response(template_name, context_instance = context)
+
+@requires_admin
+def users_modify(request, user_id):
+ user = AstakosUser.objects.get(id=user_id)
+ user.username = request.POST.get('username')
+ user.first_name = request.POST.get('first_name')
+ user.last_name = request.POST.get('last_name')
+ user.is_superuser = True if request.POST.get('admin') else False
+ user.affiliation = request.POST.get('affiliation')
+ user.is_active = True if request.POST.get('is_active') else False
+ user.invitations = int(request.POST.get('invitations') or 0)
+ #user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB
+ user.auth_token = request.POST.get('auth_token')
+ try:
+ auth_token_expires = request.POST.get('auth_token_expires')
+ d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
+ user.auth_token_expires = d
+ except ValueError:
+ pass
+ user.save()
+ return redirect(users_info, user.id)
+
+@requires_admin
+def users_delete(request, user_id):
+ user = AstakosUser.objects.get(id=user_id)
+ user.delete()
+ return redirect(users_list)
+
+@requires_admin
+def pending_users(request, template_name='pending_users.html', extra_context={}):
+ users = AstakosUser.objects.order_by('id')
+
+ users = users.filter(is_active = False)
+
+ filter = request.GET.get('filter', '')
+ if filter:
+ if filter.startswith('-'):
+ users = users.exclude(username__icontains=filter[1:])
+ else:
+ users = users.filter(username__icontains=filter)
+
+ try:
+ page = int(request.GET.get('page', 1))
+ except ValueError:
+ page = 1
+ offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+ limit = offset + settings.ADMIN_PAGE_LIMIT
+
+ npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+ prev = page - 1 if page > 1 else None
+ next = page + 1 if page < npages else None
+ kwargs = {'users':users[offset:limit],
+ 'filter':filter,
+ 'pages':range(1, npages + 1),
+ 'page':page,
+ 'prev':prev,
+ 'next':next}
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context, **kwargs))
+
+def _send_greeting(baseurl, user):
+ url = reverse('astakos.im.views.index')
+ subject = _('Welcome to %s' %settings.SERVICE_NAME)
+ message = render_to_string('welcome.txt', {
+ 'user': user,
+ 'url': url,
+ 'baseurl': baseurl,
+ 'service': settings.SERVICE_NAME,
+ 'support': settings.DEFAULT_CONTACT_EMAIL})
+ sender = settings.DEFAULT_FROM_EMAIL
+ send_mail(subject, message, sender, [user.email])
+ logging.info('Sent greeting %s', user)
+
+@requires_admin
+def users_activate(request, user_id, template_name='pending_users.html', extra_context={}):
+ user = AstakosUser.objects.get(id=user_id)
+ user.is_active = True
+ status = 'success'
+ try:
+ _send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
+ message = _('Greeting sent to %s' % user.email)
+ user.save()
+ except (SMTPException, socket.error) as e:
+ status = 'error'
+ name = 'strerror'
+ message = getattr(e, name) if hasattr(e, name) else e
+
+ users = AstakosUser.objects.order_by('id')
+ users = users.filter(is_active = False)
+
+ try:
+ page = int(request.POST.get('page', 1))
+ except ValueError:
+ page = 1
+ offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+ limit = offset + settings.ADMIN_PAGE_LIMIT
+
+ npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
+ prev = page - 1 if page > 1 else None
+ next = page + 1 if page < npages else None
+ kwargs = {'users':users[offset:limit],
+ 'filter':'',
+ 'pages':range(1, npages + 1),
+ 'page':page,
+ 'prev':prev,
+ 'next':next,
+ 'message':message}
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context, **kwargs))
+
+@requires_admin
+def invitations_list(request, template_name='invitations_list.html', extra_context={}):
+ invitations = Invitation.objects.order_by('id')
+
+ filter = request.GET.get('filter', '')
+ if filter:
+ if filter.startswith('-'):
+ invitations = invitations.exclude(username__icontains=filter[1:])
+ else:
+ invitations = invitations.filter(username__icontains=filter)
+
+ try:
+ page = int(request.GET.get('page', 1))
+ except ValueError:
+ page = 1
+ offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
+ limit = offset + settings.ADMIN_PAGE_LIMIT
+
+ npages = int(ceil(1.0 * invitations.count() / settings.ADMIN_PAGE_LIMIT))
+ prev = page - 1 if page > 1 else None
+ next = page + 1 if page < npages else None
+ kwargs = {'invitations':invitations[offset:limit],
+ 'filter':filter,
+ 'pages':range(1, npages + 1),
+ 'page':page,
+ 'prev':prev,
+ 'next':next}
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context, **kwargs))
+
+@requires_admin
+def invitations_export(request):
+ # Create the HttpResponse object with the appropriate CSV header.
+ response = HttpResponse(mimetype='text/csv')
+ response['Content-Disposition'] = 'attachment; filename=invitations.csv'
+
+ writer = csv.writer(response)
+ writer.writerow(['ID',
+ 'Username',
+ 'Real Name',
+ 'Code',
+ 'Inviter username',
+ 'Inviter Real Name',
+ 'Is_accepted',
+ 'Created',
+ 'Accepted',])
+ invitations = Invitation.objects.order_by('id')
+ for inv in invitations:
+
+ writer.writerow([inv.id,
+ inv.username.encode("utf-8"),
+ inv.realname.encode("utf-8"),
+ inv.code,
+ inv.inviter.username.encode("utf-8"),
+ inv.inviter.realname.encode("utf-8"),
+ inv.is_accepted,
+ inv.created,
+ inv.accepted])
+
+ return response
+
+
+@requires_admin
+def users_export(request):
+ # Create the HttpResponse object with the appropriate CSV header.
+ response = HttpResponse(mimetype='text/csv')
+ response['Content-Disposition'] = 'attachment; filename=users.csv'
+
+ writer = csv.writer(response)
+ writer.writerow(['ID',
+ 'Username',
+ 'Real Name',
+ 'Admin',
+ 'Affiliation',
+ 'Is active?',
+ 'Quota (GiB)',
+ 'Updated',])
+ users = AstakosUser.objects.order_by('id')
+ for u in users:
+ writer.writerow([u.id,
+ u.username.encode("utf-8"),
+ u.realname.encode("utf-8"),
+ u.is_superuser,
+ u.affiliation.encode("utf-8"),
+ u.is_active,
+ u.quota,
+ u.updated])
+
+ return response
+
+@requires_admin
+def users_create(request, template_name='users_create.html', extra_context={}):
+ if request.method == 'GET':
+ return render_response(template_name,
+ context_instance=get_context(request, extra_context))
+ if request.method == 'POST':
+ user = AstakosUser()
+ user.username = request.POST.get('username')
+ user.email = request.POST.get('email')
+ user.first_name = request.POST.get('first_name')
+ user.last_name = request.POST.get('last_name')
+ user.is_superuser = True if request.POST.get('admin') else False
+ user.affiliation = request.POST.get('affiliation')
+ user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB
+ user.renew_token()
+ user.provider = 'local'
+ user.save()
+ return redirect(users_info, user.id)
from django.utils import simplejson as json
from astakos.im.faults import BadRequest, Unauthorized, ServiceUnavailable
-from astakos.im.models import User
+from astakos.im.models import AstakosUser
import datetime
return render_fault(request, BadRequest('Missing X-Auth-Token'))
try:
- user = User.objects.get(auth_token=x_auth_token)
- except User.DoesNotExist, e:
+ user = AstakosUser.objects.get(auth_token=x_auth_token)
+ except AstakosUser.DoesNotExist, e:
return render_fault(request, Unauthorized('Invalid X-Auth-Token'))
# Check if the is active.
- if user.state != 'ACTIVE':
+ if not user.is_active:
return render_fault(request, Unauthorized('User inactive'))
# Check if the token has expired.
--- /dev/null
+from django.conf import settings
+from django.contrib.auth.backends import ModelBackend
+#from django.core.exceptions import ImproperlyConfigured
+#from django.db.models import get_model
+
+from astakos.im.models import AstakosUser
+
+class AstakosUserModelBackend(ModelBackend):
+ def authenticate(self, username=None, password=None):
+ try:
+ user = AstakosUser.objects.get(username=username)
+ if user.check_password(password):
+ return user
+ except AstakosUser.DoesNotExist:
+ return None
+
+ def get_user(self, user_id):
+ try:
+ return AstakosUser.objects.get(pk=user_id)
+ except AstakosUser.DoesNotExist:
+ return None
+
+ #@property
+ #def user_class(self):
+ # if not hasattr(self, '_user_class'):
+ # #self._user_class = get_model(*settings.CUSTOM_USER_MODEL.split('.', 2))
+ # self._user_class = get_model('astakos.im', 'astakosuser')
+ # print '#', self._user_class
+ # if not self._user_class:
+ # raise ImproperlyConfigured('Could not get custom user model')
+ # return self._user_class
\ No newline at end of file
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.conf import settings
+from django.utils.importlib import import_module
+
+def get_backend():
+ """
+ Return an instance of a registration backend,
+ according to the INVITATIONS_ENABLED setting.
+
+ """
+ module = 'invitations' if settings.INVITATIONS_ENABLED else 'simple'
+ module = 'astakos.im.backends.%s' %module
+ backend_class_name = 'Backend'
+ try:
+ mod = import_module(module)
+ except ImportError, e:
+ raise ImproperlyConfigured('Error loading registration backend %s: "%s"' % (module, e))
+ try:
+ backend_class = getattr(mod, backend_class_name)
+ except AttributeError:
+ raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
+ return backend_class()
\ No newline at end of file
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from astakos.im.forms import InvitedLocalRegisterForm, LocalRegisterForm
+from astakos.im.models import AstakosUser, Invitation
+
+class Backend(object):
+ def get_signup_form(self, request):
+ code = request.GET.get('code', '')
+ formclass = 'LocalRegisterForm'
+ if request.method == 'GET':
+ initial_data = None
+ if code:
+ formclass = 'InvitedLocalRegiterForm'
+ invitation = Invitation.objects.get(code=code)
+ if invitation.is_consumed:
+ return HttpResponseBadRequest('Invitation has beeen used')
+ initial_data.update({'username':invitation.username,
+ 'email':invitation.username,
+ 'realname':invitation.realname})
+ inviter = AstakosUser.objects.get(username=invitation.inviter)
+ initial_data['inviter'] = inviter.realname
+ else:
+ initial_data = request.POST
+ return globals()[formclass](initial_data)
+
+ def is_preaccepted(user, code):
+ invitation = self.invitation
+ if invitation and not invitation.is_consumed and invitation.code == code:
+ return True
+ return False
+
+ def signup(self, request, form):
+ kwargs = {}
+ for field in form.fields:
+ if hasattr(AstakosUser(), field):
+ kwargs[field] = form.cleaned_data[field]
+ user = get_or_create_user(**kwargs)
+
+ code = request.POST.get('code')
+ if is_preaccepted(user, code):
+ user.is_active = True
+ user.save()
+ message = _('Registration completed. You can now login.')
+ next = request.POST.get('next')
+ if next:
+ return redirect(next)
+ else:
+ message = _('Registration completed. You will receive an email upon your account\'s activation')
+ status = 'success'
+ return status, message
\ No newline at end of file
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+import socket
+import logging
+
+from django.core.mail import send_mail
+from django.conf import settings
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext as _
+from smtplib import SMTPException
+from urllib import quote
+
+from astakos.im.forms import LocalRegisterForm
+from astakos.im.util import get_or_create_user
+from astakos.im.models import AstakosUser
+
+class Backend(object):
+ def get_signup_form(self, request):
+ initial_data = request.POST if request.method == 'POST' else None
+ return LocalRegisterForm(initial_data)
+
+ def signup(self, request, form, success_url):
+ kwargs = {}
+ for field in form.fields:
+ if hasattr(AstakosUser(), field):
+ kwargs[field] = form.cleaned_data[field]
+ user = get_or_create_user(**kwargs)
+
+ status = 'success'
+ try:
+ send_verification(request.build_absolute_uri('/').rstrip('/'), user)
+ message = _('Verification sent to %s' % user.email)
+ except (SMTPException, socket.error) as e:
+ status = 'error'
+ name = 'strerror'
+ message = getattr(e, name) if hasattr(e, name) else e
+
+ if user and status == 'error':
+ #delete created user
+ user.delete()
+ return status, message
+
+def send_verification(baseurl, user):
+ url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
+ quote(user.auth_token),
+ quote(baseurl))
+ message = render_to_string('activation.txt', {
+ 'user': user,
+ 'url': url,
+ 'baseurl': baseurl,
+ 'service': settings.SERVICE_NAME,
+ 'support': settings.DEFAULT_CONTACT_EMAIL})
+ sender = settings.DEFAULT_FROM_EMAIL
+ send_mail('Pithos account activation', message, sender, [user.email])
+ logging.info('Sent activation %s', user)
\ No newline at end of file
--- /dev/null
+# Copyright 2011 GRNET S.A. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or
+# without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above
+# copyright notice, this list of conditions and the following
+# disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials
+# provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
+# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# The views and conclusions contained in the software and
+# documentation are those of the authors and should not be
+# interpreted as representing official policies, either expressed
+# or implied, of GRNET S.A.
+
+from django.conf import settings
+
+def im_modules(request):
+ return {'im_modules': settings.IM_MODULES}
+
+def next(request):
+ return {'next' : request.GET.get('next', '')}
+
+def code(request):
+ return {'code' : request.GET.get('code', '')}
+
\ No newline at end of file
[
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 1,
"fields": {
- "uniq": "admin",
- "password": "admin",
"level": 0,
- "state": ACTIVE,
"invitations": 10000,
- "is_admin": true,
"auth_token": "0000",
"auth_token_created": "2011-09-11 09:17:14",
"auth_token_expires": "2012-09-11 09:17:14",
- "created": "2011-09-11",
"updated": "2011-09-11"
}
}
[
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 1,
"fields": {
- "uniq": "test",
+ "username": "test",
"level": 0,
- "state": "ACTIVE",
"invitations": 10000,
+ "password": "0000",
"auth_token": "0000",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 2,
"fields": {
- "uniq": "verigak",
+ "username": "verigak",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
- "is_admin": 1,
+ "is_superuser": 1,
+ "password": "0001",
"auth_token": "0001",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 3,
"fields": {
- "uniq": "chazapis",
+ "username": "chazapis",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0002",
"auth_token": "0002",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 4,
"fields": {
- "uniq": "gtsouk",
+ "username": "gtsouk",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0003",
"auth_token": "0003",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 5,
"fields": {
- "uniq": "papagian",
+ "username": "papagian",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0004",
"auth_token": "0004",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 6,
"fields": {
- "uniq": "louridas",
+ "username": "louridas",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0005",
"auth_token": "0005",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 7,
"fields": {
- "uniq": "chstath",
+ "username": "chstath",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0006",
"auth_token": "0006",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 8,
"fields": {
- "uniq": "pkanavos",
+ "username": "pkanavos",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0007",
"auth_token": "0007",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 9,
"fields": {
- "uniq": "mvasilak",
+ "username": "mvasilak",
"level": 1,
- "state": "ACTIVE",
"invitations": 3,
+ "password": "0008",
"auth_token": "0008",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
},
{
- "model": "im.User",
+ "model": "im.AstakosUser",
"pk": 10,
"fields": {
- "uniq": "διογένης",
+ "username": "διογένης",
"level": 2,
- "state": "ACTIVE",
"invitations": 2,
+ "password": "0009",
"auth_token": "0009",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
- "created": "2011-02-06",
"updated": "2011-02-06"
}
}
from django import forms
from django.utils.translation import ugettext as _
from django.conf import settings
+from hashlib import new as newhasher
-from astakos.im.models import User
-
-openid_providers = (
-('Google','https://www.google.com/accounts/o8/id'),
-('Yahoo', 'http://yahoo.com/'),
-('AOL','http://openid.aol.com/%s/'),
-('OpenID', None),
-('MyOpenID','http://%s.myopenid.com/'),
-('LiveJournal', 'http://%s.livejournal.com/'),
-('Flickr', 'http://flickr.com/%s/'),
-('Technorati', 'http://technorati.com/people/technorati/%s/'),
-('Wordpress', 'http://%s.wordpress.com/'),
-('Blogger', 'http://%s.blogspot.com/'),
-('Verisign', 'http://%s.pip.verisignlabs.com/'),
-('Vidoop', 'http://%s.myvidoop.com/'),
-('ClaimID','http://claimid.com/%s')
-)
+from astakos.im.models import AstakosUser
class RegisterForm(forms.Form):
- uniq = forms.CharField(widget=forms.widgets.TextInput())
- provider = forms.CharField(widget=forms.TextInput(),
- label=u'Identity Provider')
+ username = forms.CharField(widget=forms.widgets.TextInput())
email = forms.EmailField(widget=forms.TextInput(),
label=_('Email address'))
- realname = forms.CharField(widget=forms.TextInput(),
- label=u'Real Name')
+ first_name = forms.CharField(widget=forms.TextInput(),
+ label=u'First Name', required=False)
+ last_name = forms.CharField(widget=forms.TextInput(),
+ label=u'Last Name', required=False)
def __init__(self, *args, **kwargs):
super(forms.Form, self).__init__(*args, **kwargs)
-
- #set readonly form fields
- self.fields['provider'].widget.attrs['readonly'] = True
- def clean_uniq(self):
+ def clean_username(self):
"""
- Validate that the uniq is alphanumeric and is not already
+ Validate that the username is alphanumeric and is not already
in use.
"""
try:
- user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
- except User.DoesNotExist:
- return self.cleaned_data['uniq']
- raise forms.ValidationError(_("A user with that uniq already exists."))
-
-class ShibbolethRegisterForm(RegisterForm):
- pass
-
-class TwitterRegisterForm(RegisterForm):
- pass
-
-class OpenidRegisterForm(RegisterForm):
- openidurl = forms.ChoiceField(widget=forms.Select,
- choices=((url, l) for l, url in openid_providers))
+ user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
+ except AstakosUser.DoesNotExist:
+ return self.cleaned_data['username']
+ raise forms.ValidationError(_("A user with that username already exists."))
class LocalRegisterForm(RegisterForm):
""" local signup form"""
def __init__(self, *args, **kwargs):
super(LocalRegisterForm, self).__init__(*args, **kwargs)
- def clean_uniq(self):
+ def clean_username(self):
"""
- Validate that the uniq is alphanumeric and is not already
+ Validate that the username is alphanumeric and is not already
in use.
"""
try:
- user = User.objects.get(uniq__iexact=self.cleaned_data['uniq'])
- except User.DoesNotExist:
- return self.cleaned_data['uniq']
- raise forms.ValidationError(_("A user with that uniq already exists."))
+ user = AstakosUser.objects.get(username__iexact=self.cleaned_data['username'])
+ except AstakosUser.DoesNotExist:
+ return self.cleaned_data['username']
+ raise forms.ValidationError(_("A user with that username already exists."))
def clean(self):
"""
super(RegisterForm, self).__init__(*args, **kwargs)
#set readonly form fields
- self.fields['uniq'].widget.attrs['readonly'] = True
+ self.fields['username'].widget.attrs['readonly'] = True
self.fields['inviter'].widget.attrs['readonly'] = True
- self.fields['provider'].widget.attrs['provider'] = True
class InvitedLocalRegisterForm(LocalRegisterForm, InvitedRegisterForm):
pass
-class InvitedOpenidRegisterForm(OpenidRegisterForm, InvitedRegisterForm):
- pass
-
-class InvitedTwitterRegisterForm(TwitterRegisterForm, InvitedRegisterForm):
- pass
-
-class InvitedShibbolethRegisterForm(ShibbolethRegisterForm, InvitedRegisterForm):
- pass
+class LoginForm(forms.Form):
+ username = forms.CharField(widget=forms.widgets.TextInput())
+ password = forms.CharField(widget=forms.PasswordInput(render_value=False),
+ label=_('Password'))
\ No newline at end of file
+++ /dev/null
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding model 'User'
- db.create_table('im_user', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255, null=True)),
- ('realname', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
- ('email', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
- ('affiliation', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
- ('state', self.gf('django.db.models.fields.CharField')(default='ACTIVE', max_length=16)),
- ('level', self.gf('django.db.models.fields.IntegerField')(default=4)),
- ('invitations', self.gf('django.db.models.fields.IntegerField')(default=0)),
- ('password', self.gf('django.db.models.fields.CharField')(default='', max_length=255)),
- ('is_admin', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('auth_token', self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True)),
- ('auth_token_created', self.gf('django.db.models.fields.DateTimeField')(null=True)),
- ('auth_token_expires', self.gf('django.db.models.fields.DateTimeField')(null=True)),
- ('created', self.gf('django.db.models.fields.DateTimeField')()),
- ('updated', self.gf('django.db.models.fields.DateTimeField')()),
- ))
- db.send_create_signal('im', ['User'])
-
- # Adding model 'Invitation'
- db.create_table('im_invitation', (
- ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
- ('inviter', self.gf('django.db.models.fields.related.ForeignKey')(related_name='invitations_sent', null=True, to=orm['im.User'])),
- ('realname', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('uniq', self.gf('django.db.models.fields.CharField')(max_length=255)),
- ('code', self.gf('django.db.models.fields.BigIntegerField')(db_index=True)),
- ('is_accepted', self.gf('django.db.models.fields.BooleanField')(default=False)),
- ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
- ('accepted', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
- ))
- db.send_create_signal('im', ['Invitation'])
-
-
- def backwards(self, orm):
-
- # Deleting model 'User'
- db.delete_table('im_user')
-
- # Deleting model 'Invitation'
- db.delete_table('im_invitation')
-
-
- models = {
- 'im.invitation': {
- 'Meta': {'object_name': 'Invitation'},
- 'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
- 'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- 'im.user': {
- 'Meta': {'object_name': 'User'},
- 'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
- 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {}),
- 'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
- 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
- 'updated': ('django.db.models.fields.DateTimeField', [], {})
- }
- }
-
- complete_apps = ['im']
+++ /dev/null
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding field 'User.is_verified'
- db.add_column('im_user', 'is_verified', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'User.is_verified'
- db.delete_column('im_user', 'is_verified')
-
-
- models = {
- 'im.invitation': {
- 'Meta': {'object_name': 'Invitation'},
- 'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
- 'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- 'im.user': {
- 'Meta': {'object_name': 'User'},
- 'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
- 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {}),
- 'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
- 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'state': ('django.db.models.fields.CharField', [], {'default': "'ACTIVE'", 'max_length': '16'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
- 'updated': ('django.db.models.fields.DateTimeField', [], {})
- }
- }
-
- complete_apps = ['im']
+++ /dev/null
-# encoding: utf-8
-import datetime
-from south.db import db
-from south.v2 import SchemaMigration
-from django.db import models
-
-class Migration(SchemaMigration):
-
- def forwards(self, orm):
-
- # Adding field 'User.provider'
- db.add_column('im_user', 'provider', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
-
- # Adding field 'User.openidurl'
- db.add_column('im_user', 'openidurl', self.gf('django.db.models.fields.CharField')(default='', max_length=255), keep_default=False)
-
- # Adding field 'Invitation.is_consumed'
- db.add_column('im_invitation', 'is_consumed', self.gf('django.db.models.fields.BooleanField')(default=False), keep_default=False)
-
- # Adding field 'Invitation.consumed'
- db.add_column('im_invitation', 'consumed', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True), keep_default=False)
-
-
- def backwards(self, orm):
-
- # Deleting field 'User.provider'
- db.delete_column('im_user', 'provider')
-
- # Deleting field 'User.openidurl'
- db.delete_column('im_user', 'openidurl')
-
- # Deleting field 'Invitation.is_consumed'
- db.delete_column('im_invitation', 'is_consumed')
-
- # Deleting field 'Invitation.consumed'
- db.delete_column('im_invitation', 'consumed')
-
-
- models = {
- 'im.invitation': {
- 'Meta': {'object_name': 'Invitation'},
- 'accepted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'code': ('django.db.models.fields.BigIntegerField', [], {'db_index': 'True'}),
- 'consumed': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'inviter': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'invitations_sent'", 'null': 'True', 'to': "orm['im.User']"}),
- 'is_accepted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_consumed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'realname': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255'})
- },
- 'im.user': {
- 'Meta': {'object_name': 'User'},
- 'affiliation': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'auth_token': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
- 'auth_token_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'auth_token_expires': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
- 'created': ('django.db.models.fields.DateTimeField', [], {}),
- 'email': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
- 'invitations': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
- 'is_admin': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
- 'level': ('django.db.models.fields.IntegerField', [], {'default': '4'}),
- 'openidurl': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'password': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'provider': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'realname': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '255'}),
- 'state': ('django.db.models.fields.CharField', [], {'default': "'PENDING'", 'max_length': '16'}),
- 'uniq': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True'}),
- 'updated': ('django.db.models.fields.DateTimeField', [], {})
- }
- }
-
- complete_apps = ['im']
from django.conf import settings
from django.db import models
+from django.contrib.auth.models import User, UserManager
from astakos.im.interface import get_quota, set_quota
-from hashlib import new as newhasher
-
-class User(models.Model):
- ACCOUNT_STATE = (
- ('ACTIVE', 'Active'),
- ('DELETED', 'Deleted'),
- ('SUSPENDED', 'Suspended'),
- ('UNVERIFIED', 'Unverified'),
- ('PENDING', 'Pending')
- )
-
- uniq = models.CharField('Unique ID', max_length=255, null=True)
+class AstakosUser(User):
+ # Use UserManager to get the create_user method, etc.
+ objects = UserManager()
- realname = models.CharField('Real Name', max_length=255, default='')
- email = models.CharField('Email', max_length=255, default='')
affiliation = models.CharField('Affiliation', max_length=255, default='')
provider = models.CharField('Provider', max_length=255, default='')
- state = models.CharField('Account state', choices=ACCOUNT_STATE,
- max_length=16, default='PENDING')
#for invitations
level = models.IntegerField('Inviter level', default=4)
invitations = models.IntegerField('Invitations left', default=0)
- #for local
- password = models.CharField('Password', max_length=255, default='')
-
- is_admin = models.BooleanField('Admin?', default=False)
-
auth_token = models.CharField('Authentication Token', max_length=32,
- null=True, blank=True)
- auth_token_created = models.DateTimeField('Token creation date',
- null=True)
- auth_token_expires = models.DateTimeField('Token expiration date',
- null=True)
+ null=True, blank=True)
+ auth_token_created = models.DateTimeField('Token creation date', null=True)
+ auth_token_expires = models.DateTimeField('Token expiration date', null=True)
- created = models.DateTimeField('Creation date')
updated = models.DateTimeField('Update date')
- is_verified = models.BooleanField('Verified?', default=False)
+ @property
+ def realname(self):
+ return '%s %s' %(self.first_name, self.last_name)
- openidurl = models.CharField('OpenID url', max_length=255, default='')
+ @realname.setter
+ def realname(self, value):
+ parts = value.split(' ')
+ if len(parts) == 2:
+ self.first_name = parts[0]
+ self.last_name = parts[1]
+ else:
+ self.last_name = parts[0]
@property
def quota(self):
- return get_quota(self.uniq)
+ return get_quota(self.username)
@quota.setter
def quota(self, value):
- set_quota(self.uniq, value)
+ set_quota(self.username, value)
@property
def invitation(self):
try:
- return Invitation.objects.get(uniq=self.uniq)
+ return Invitation.objects.get(username=self.username)
except Invitation.DoesNotExist:
return None
def save(self, update_timestamps=True, **kwargs):
if update_timestamps:
if not self.id:
- self.created = datetime.now()
+ self.date_joined = datetime.now()
self.updated = datetime.now()
- super(User, self).save(**kwargs)
+ super(AstakosUser, self).save(**kwargs)
#invitation consume
if self.invitation and not self.invitation.is_consumed:
def renew_token(self):
md5 = hashlib.md5()
- md5.update(self.uniq)
+ md5.update(self.username)
md5.update(self.realname.encode('ascii', 'ignore'))
md5.update(asctime())
timedelta(hours=settings.AUTH_TOKEN_DURATION)
def __unicode__(self):
- return self.uniq
+ return self.username
class Invitation(models.Model):
- inviter = models.ForeignKey(User, related_name='invitations_sent',
+ inviter = models.ForeignKey(AstakosUser, related_name='invitations_sent',
null=True)
realname = models.CharField('Real name', max_length=255)
- uniq = models.CharField('Unique ID', max_length=255)
+ username = models.CharField('Unique ID', max_length=255)
code = models.BigIntegerField('Invitation code', db_index=True)
#obsolete: we keep it just for transfering the data
is_accepted = models.BooleanField('Accepted?', default=False)
self.save()
def __unicode__(self):
- return '%s -> %s [%d]' % (self.inviter, self.uniq, self.code)
+ return '%s -> %s [%d]' % (self.inviter, self.username, self.code)
from django.http import HttpResponseBadRequest
from astakos.im.models import Invitation
-from astakos.im.target.util import get_or_create_user, prepare_response
-
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
def login(request):
code = request.GET.get('code')
from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest
from django.conf import settings
from django.template.loader import render_to_string
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.contrib.auth import authenticate
+from django.utils.translation import ugettext as _
from astakos.im.target.util import prepare_response
-from astakos.im.models import User
+from astakos.im.models import AstakosUser
+from astakos.im.forms import LoginForm
from urllib import unquote
from hashlib import new as newhasher
-def login(request):
- username = request.POST.get('username')
- password = request.POST.get('password')
+def login(request, on_failure='index.html'):
+ """
+ on_failure: whatever redirect accepts as to
+ """
+ form = LoginForm(request.POST)
- if not username:
- return HttpResponseBadRequest('No user')
+ if not form.is_valid():
+ return render_to_response(on_failure,
+ {'form':form},
+ context_instance=RequestContext(request))
- if not password:
- return HttpResponseBadRequest('No password')
+ user = authenticate(**form.cleaned_data)
+ status = 'success'
+ if not user:
+ status = 'error'
+ message = _('Cannot authenticate account')
+ elif not user.is_active:
+ status = 'error'
+ message = _('Inactive account')
- try:
- user = User.objects.get(uniq=username)
- except User.DoesNotExist:
- return HttpResponseBadRequest('No such user')
-
- hasher = newhasher('sha256')
- hasher.update(password)
- password = hasher.hexdigest()
-
- if not password or user.password != password:
- return HttpResponseBadRequest('Wrong password')
-
- if user.state == 'UNVERIFIED':
- return HttpResponseBadRequest('Unverified account')
+ if status == 'error':
+ return render_to_response(on_failure,
+ {'form':form,
+ 'message': _('Unverified account')},
+ context_instance=RequestContext(request))
next = request.POST.get('next')
return prepare_response(request, user, next)
token = request.GET.get('auth')
next = request.GET.get('next')
try:
- user = User.objects.get(auth_token=token)
- except User.DoesNotExist:
+ user = AstakosUser.objects.get(auth_token=token)
+ except AstakosUser.DoesNotExist:
return HttpResponseBadRequest('No such user')
- user.state = 'ACTIVE'
+ user.is_active = True
user.save()
return prepare_response(request, user, next, renew=True)
status = 'error'
message = 'Bad Request: missing token'
try:
- user = User.objects.get(auth_token=token)
- if username != user.uniq:
+ user = AstakosUser.objects.get(auth_token=token)
+ if username != user.username:
status = 'error'
message = 'Bad Request: username mismatch'
else:
user.status = 'NORMAL'
user.save()
return prepare_response(request, user, next, renew=True)
- except User.DoesNotExist:
+ except AstakosUser.DoesNotExist:
status = 'error'
message = 'Bad Request: invalid token'
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
-from astakos.im.target.util import get_or_create_user, prepare_response
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
class Tokens:
from django.http import HttpResponse
from django.utils import simplejson as json
-from astakos.im.target.util import get_or_create_user, prepare_response
+from astakos.im.target.util import prepare_response
+from astakos.im.util import get_or_create_user
# It's probably a good idea to put your consumer's OAuth token and
# OAuth secret into your project's settings.
# These two things will likely never be used. Alternatively, you
# can prompt them for their email here. Either way, the password
# should never be used.
- uniq = '%s@twitter.com' % access_token['screen_name']
+ username = '%s@twitter.com' % access_token['screen_name']
realname = access_token['user_id']
return prepare_response(request,
- get_or_create_user(uniq, realname, 'Twitter', 0),
+ get_or_create_user(username, realname, 'Twitter', 0),
request_token.get('next'))
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
-import logging
import datetime
from urlparse import urlsplit, urlunsplit
from urllib import quote
-from django.conf import settings
from django.http import HttpResponse
from django.utils.http import urlencode
from django.core.urlresolvers import reverse
-
-from astakos.im.models import User
-
-def get_or_create_user(uniq, realname, affiliation, level):
- """Find or register a user into the internal database
- and issue a token for subsequent requests.
- """
-
- user, created = User.objects.get_or_create(uniq=uniq,
- defaults={
- 'realname': realname,
- 'affiliation': affiliation,
- 'level': level,
- 'invitations': settings.INVITATIONS_PER_LEVEL[level],
- 'state':'PENDING',
- })
- if created:
- user.renew_token()
- user.save()
- logging.info('Created user %s', user)
-
- return user
+from django.conf import settings
def prepare_response(request, user, next='', renew=False):
"""Return the unique username and the token
expired, if the 'renew' parameter is present.
"""
- if renew or user.auth_token_expires < datetime.datetime.now():
+ auth_token = user.auth_token
+ auth_token_expires = user.auth_token_expires
+ if renew or auth_token_expires < datetime.datetime.now():
user.renew_token()
user.save()
parts = list(urlsplit(next))
# Do not pass on user and token if we are on the same server.
if parts[1] and request.get_host() != parts[1]:
- parts[3] = urlencode({'user': user.uniq, 'token': user.auth_token})
+ parts[3] = urlencode({'user': user.username, 'token': auth_token})
next = urlunsplit(parts)
if settings.FORCE_PROFILE_UPDATE and not user.is_verified:
next = reverse('astakos.im.views.users_profile') + params
response = HttpResponse()
- expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
- cookie_value = quote(user.uniq + '|' + user.auth_token)
+ expire_fmt = auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
+ cookie_value = quote(user.username + '|' + auth_token)
response.set_cookie('_pithos2_a', value=cookie_value, expires=expire_fmt, path='/')
if not next:
- response['X-Auth-User'] = user.uniq
- response['X-Auth-Token'] = user.auth_token
- response.content = user.uniq + '\n' + user.auth_token + '\n'
+ response['X-Auth-User'] = user.username
+ response['X-Auth-Token'] = auth_token
+ response.content = user.username + '\n' + auth_token + '\n'
response.status_code = 200
else:
response['Location'] = next
{% block title%}
<h2>Welcome</h2>
<p>Choose how to login. Or move on to <a href="admin">admin</a>.</p>
+ <p>Don't have an account? <a href="{% url astakos.im.views.signup %}">Sign up</a>.</p>
{% endblock title%}
{% block body%}
<div class="span4">
<h4>Local account</h4>
<form action="{% url astakos.im.target.local.login %}" method="post" class="form-stacked">
- <fieldset>
- <div class="clearfix">
- <label for="username">Username:</label>
- <div class="input">
- <input class="span3" id="user-username" name="username" type="text" />
- </div>
- </div>
- <div class="clearfix">
- <label for="password">Password:</label>
- <div class="input">
- <input class="span3" id="user-password" name="password" type="password" />
- </div>
- </div>
- </fieldset>
- <div>
- <a href="{% url astakos.im.views.register 'local' %}">Sign up</a>
- </div>
+ {{ form.as_p }}
<div>
<a href="{% url astakos.im.views.reclaim_password %}">Forgot your password?</a>
</div>
</form>
</div>
{% endif %}
- {% if other_modules %}
<div class="span8">
- <h4>Other provider</h4>
- {% for o in other_modules %}
- <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+ {% for o in im_modules %}
+ {% if o != 'local' %}
+ <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+ {% endif %}
{% endfor %}
</div>
- {% endif %}
</div>
{% endblock body%}
+++ /dev/null
-{% extends "base.html" %}
-
-{% block title%}
- <h2>Sign up</h2>
-{% endblock title%}
-
-{% block body %}
-<form action="{% url astakos.im.views.local_create %}" method="post">
-{% if provider == 'local'%}
- <div class="clearfix">
- <label for="user-uniq">Username</label>
- <div class="input">
- <input class="span4" id="user-uniq" name="uniq" {%if inv %} value={{ inv.uniq }} {% endif %} type="text" />
- </div>
- </div>
-
- <div class="clearfix">
- <label for="user-password">Password</label>
- <div class="input">
- <input class="span4" id="user-password" name="password" type="password" />
- </div>
- </div>
-
- <div class="clearfix">
- <label for="user-password">Confirm Password</label>
- <div class="input">
- <input class="span4" id="user-retype-password" name="retype_password" type="password" />
- </div>
- </div>
-
- <div class="clearfix">
- <label for="user-realname">Real Name</label>
- <div class="input">
- <input class="span4" id="user-realname" name="realname" {%if inv %} value={{ inv.realname }} {% endif %} type="text" />
- </div>
- </div>
-
- <div class="clearfix">
- <label for="user-email">Email</label>
- <div class="input">
- <input class="span4" id="user-email" name="email" type="text" />
- </div>
- </div>
-{% endif %}
-
-{% if provider == 'twitter' %}
- <div class="clearfix">
- <label for="user-uniq">Twitter </label>
- <div class="input">
- <input class="span4" id="user-uniq" name="uniq" {%if inv %} value={{ inv.uniq }} {% endif %} type="text" />
- </div>
- </div>
-{% endif %}
-
- <div class="actions">
- <button type="submit" class="btn primary">Create</button>
- <button type="reset" class="btn">Reset</button>
- </div>
-</form>
-{% endblock body %}
<div class="clearfix">
<label for="user-uniq">Username</label>
<div class="input">
- <input class="span4" id="user-uniq" name="uniq" type="text" />
+ <input class="span4" id="user-username" name="username" type="text" />
</div>
</div>
{% endblock title%}
{% block body %}
-<form action={%url astakos.im.views.register provider%} method="post">
+<form action={%url astakos.im.views.register%} method="post">
{{ form.as_p }}
<div>
<button type="submit" class="btn primary">Register</button>
</div>
-<input type="hidden" name="provider" value="{{ provider }}">
<input type="hidden" name="code" value="{{ code }}">
<input type="hidden" name="next" value="{{ next }}">
{% block body %}
<form action="{% url astakos.im.target.local.reset_password %}" method="post">
<div class="clearfix">
- <label for="user-uniq">password</label>
+ <label for="user-password">password</label>
<div class="input">
<input class="span4" id="user-password" name="password" type="password" />
</div>
{% block title%}
<h1>Welcome</h1>
+ <p>Choose how to signup.</p>
{% endblock title%}
-{% block body %}
-<form action={%url astakos.im.views.signup %} method="post">
- <p>Select an Identity Provider:</p>
- {% for choice in im_modules%}
+{% block body%}
<div class="row">
- <div class="span8">
- <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice }}" />
- <label for="choice{{ forloop.counter }}"><img src="/im/static/{{ choice }}.png" width="80"></label>
- </div>
- </div>
- {% endfor %}
-<div>
- {% if inv %}
- <input type=hidden name="code" value={{ inv.code }} />
+ {% if "local" in im_modules %}
+ <div class="span4">
+ <h4>Local account</h4>
+ <form action="" method="post" class="form-stacked">
+ {{ form.as_p }}
+ <br>
+ <div class="">
+ <input type="hidden" name="next" value="{{ next }}">
+ <input type="hidden" name="code" value="{{ code }}">
+ <button type="submit" class="btn primary">Go</button>
+ </div>
+ </form>
+ </div>
{% endif %}
- <button type="submit" class="btn primary">Signup</button>
-</div>
-<input type="hidden" name="provider" value="{{ provider }}">
-<input type="hidden" name="code" value="{{ code }}">
-<input type="hidden" name="next" value="{{ next }}">
-</form>
-{% endblock body %}
\ No newline at end of file
+ <div class="span8">
+ {% for o in im_modules %}
+ {% if o != 'local' %}
+ <a href="/im/login/{{ o }}{% ifnotequal next "" %}?next={{ next|urlencode }}{% endifnotequal %}" alt="{{ o|title }}"><img src="/im/static/{{ o }}.png" width="120" height="120"></a>
+ {% endif %}
+ {% endfor %}
+ </div>
+ </div>
+{% endblock body%}
</div>
<div class="clearfix">
- <label for="user-uniq">Uniq</label>
+ <label for="user-username">Username</label>
<div class="input">
- <span class="uneditable-input" id="user-uniq">{{ user.uniq }}</span>
+ <span class="uneditable-input" id="user-username">{{ user.username }}</span>
</div>
</div>
<div class="clearfix">
- <label for="user-realname">Real Name</label>
+ <label for="user-first-name">Real Name</label>
<div class="input">
- <input class="span4" id="user-realname" name="realname" value="{{ user.realname }}" type="text" />
+ <input class="span4" id="user-first-name" name="first_name" value="{{ user.first_name }}" type="text" />
+ </div>
+ </div>
+
+ <div class="clearfix">
+ <label for="user-last-name">Real Name</label>
+ <div class="input">
+ <input class="span4" id="user-last-name" name="last_name" value="{{ user.last_name }}" type="text" />
</div>
</div>
<ul class="inputs-list">
<li>
<label>
- <input type="checkbox" id="user-admin" name="admin"{% if user.is_admin %} checked{% endif %} disabled="disabled">
+ <input type="checkbox" id="user-admin" name="admin"{% if user.is_superuser %} checked{% endif %} disabled="disabled">
</label>
</li>
</ul>
</div>
<div class="clearfix">
- <label for="user-state">State</label>
+ <label for="user-is-active">Is active?</label>
<div class="input">
- <select class="medium" id="user-state" name="state" disabled="disabled">
- {% for state in states %}
- <option{% ifequal state user.state %} selected{% endifequal %}>{{ state }}</option>
- {% endfor %}
- </select>
+ <ul class="inputs-list">
+ <li>
+ <label>
+ <input type="checkbox" id="user-is-active" name="is_active"{% if user.is_active %} checked{% endif %} disabled="disabled">
+ </label>
+ </li>
+ </ul>
</div>
</div>
<div class="clearfix">
<label for="user-created">Created</label>
<div class="input">
- <span class="uneditable-input" id="user-created">{{ user.created }}</span>
+ <span class="uneditable-input" id="user-date-joined">{{ user.date_joined }}</span>
</div>
</div>
--- /dev/null
+--- A translation in English follows ---
+
+Αγαπητέ/η {{ user.realname }},
+
+Ο λογαρισμός σας για την υπηρεσία {{ service }} της ΕΔΕΤκατά την Alpha (πιλοτική)
+φάση λειτουργίας της έχει ενεργοποιηθεί.
+
+Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο:
+
+{{ url }}
+
+Σημείωση:
+
+Η υπηρεσία θα είναι για μερικές εβδομάδες σε φάση λειτουργίας Alpha. Αν
+και έχουμε κάνει ότι είναι δυνατό για να εξασφαλίσουμε την ποιότητα της
+υπηρεσίας, δεν αποκλείεται να εμφανιστούν προβλήματα στο λογισμικό
+διαχείρισης ή η υπηρεσία να μην είναι διαθέσιμη κατά διαστήματα. Για
+αυτό το λόγο, σας παρακαλούμε να μη μεταφέρετε ακόμη σημαντικά κομμάτια
+της δουλειάς σας στην υπηρεσία {{ service }}. Επίσης, παρακαλούμε να έχετε
+υπόψη σας ότι όλα τα δεδομένα, θα διαγραφούν κατά τη μετάβαση από την
+έκδοση Alpha στην έκδοση Beta. Θα υπάρξει έγκαιρη ειδοποίησή σας πριν
+από τη μετάβαση αυτή.
+
+Περισσότερα για την υπηρεσία θα βρείτε στο {{ baseurl }}, αφού
+έχετε ενεργοποιήσει την πρόσκλησή σας.
+
+Αναμένουμε τα σχόλια και τις παρατηρήσεις σας, για να βελτιώσουμε τη
+λειτουργικότητα και την αξιοπιστία της καινοτομικής αυτής υπηρεσίας.
+
+Για όποιες παρατηρήσεις ή προβλήματα στη λειτουργεία της υπηρεσίας μπορείτε να
+απευθυνθείτε στο {{ support }}.
+
+Σας ευχαριστούμε πολύ για τη συμμετοχή στην Alpha λειτουργία του {{ service }}.
+
+Με εκτίμηση,
+
+Εθνικό Δίκτυο Έρευνας και Τεχνολογίας (ΕΔΕΤ/GRNET)
+
+--
+
+Dear {{ user.realname }},
+
+Your account for GRNET's {{ service }} service for its Alpha test phase has been
+activated.
+
+To login, please use the following link:
+
+{{ url }}
+
+Please note:
+
+This service is, for a few weeks, in Alpha. Although every care has been
+taken to ensure high quality, it is possible there may still be software
+bugs, or periods of limited service availability. For this reason, we
+kindly request you do not transfer important parts of your work to
+{{ service }}, yet. Also, please bear in mind that all data, will be deleted
+when the service moves to Beta test. Before the transition, you will be
+notified in time.
+
+For more information, please visit {{ baseurl }}, after
+activating your invitation.
+
+We look forward to your feedback, to improve the functionality and
+reliability of this innovative service.
+
+For any remarks or problems you can contact {{ support }}.
+
+Thank you for participating in the Alpha test of {{ service }}.
+
+Greek Research and Technonogy Network - GRNET
(r'^$', 'index'),
(r'^login/?$', 'index'),
- (r'^admin/?$', 'admin'),
-
- (r'^admin/users/?$', 'users_list'),
- (r'^admin/users/(\d+)/?$', 'users_info'),
- (r'^admin/users/create$', 'users_create'),
- (r'^admin/users/(\d+)/modify/?$', 'users_modify'),
- (r'^admin/users/(\d+)/delete/?$', 'users_delete'),
- (r'^admin/users/export/?$', 'users_export'),
- (r'^admin/users/pending/?$', 'pending_users'),
- (r'^admin/users/activate/(\d+)/?$', 'users_activate'),
-
- (r'^admin/invitations/?$', 'invitations_list'),
- (r'^admin/invitations/export/?$', 'invitations_export'),
+ (r'^admin/', include('astakos.im.admin.urls')),
(r'^profile/?$', 'users_profile'),
(r'^profile/edit/?$', 'users_edit'),
(r'^signup/?$', 'signup'),
- (r'^register/(\w+)?$', 'register'),
+ #(r'^register/(\w+)?$', 'register'),
#(r'^signup/complete/?$', 'signup_complete'),
#(r'^local/create/?$', 'local_create'),
)
{'document_root': settings.PROJECT_PATH + '/im/static'})
)
-
if 'local' in settings.IM_MODULES:
urlpatterns += patterns('astakos.im.views',
# (r'^local/create/?$', 'local_create'),
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
+import logging
+
from datetime import tzinfo, timedelta
+from django.conf import settings
+from django.template import RequestContext
+from astakos.im.models import AstakosUser
class UTC(tzinfo):
def utcoffset(self, dt):
"""Return an ISO8601 date string that includes a timezone."""
return d.replace(tzinfo=UTC()).isoformat()
+
+def get_or_create_user(username, realname=None, first_name=None, last_name=None, affiliation=None, level=0, provider='local', password=None, email=None):
+ """Find or register a user into the internal database
+ and issue a token for subsequent requests.
+ """
+
+ user, created = AstakosUser.objects.get_or_create(username=username,
+ defaults={
+ 'is_active': False,
+ 'password':password,
+ 'email':email,
+ 'affiliation':affiliation,
+ 'level':level,
+ 'invitations':settings.INVITATIONS_PER_LEVEL[level],
+ 'provider':provider,
+ 'realname':realname,
+ 'first_name':first_name,
+ 'last_name':last_name
+ })
+ if created:
+ user.renew_token()
+ user.save()
+ logging.info('Created user %s', user)
+
+ return user
+
+def get_context(request, extra_context={}, **kwargs):
+ if not extra_context:
+ extra_context = {}
+ extra_context.update(kwargs)
+ return RequestContext(request, extra_context)
from math import ceil
from random import randint
from smtplib import SMTPException
+from hashlib import new as newhasher
+from urllib import quote
from django.conf import settings
from django.core.mail import send_mail
from django.utils.http import urlencode
from django.utils.translation import ugettext as _
from django.core.urlresolvers import reverse
-from django.forms import Form
-from django.forms.formsets import formset_factory
-#from openid.consumer.consumer import Consumer, \
-# SUCCESS, CANCEL, FAILURE, SETUP_NEEDED
-
-from hashlib import new as newhasher
-
-from urllib import quote
#from astakos.im.openid_store import PithosOpenIDStore
-from astakos.im.models import User, Invitation
-from astakos.im.util import isoformat
+from astakos.im.models import AstakosUser, Invitation
+from astakos.im.util import isoformat, get_or_create_user, get_context
from astakos.im.forms import *
+from astakos.im.backends import get_backend
-def render_response(template, tab=None, status=200, **kwargs):
+def render_response(template, tab=None, status=200, context_instance=None, **kwargs):
if tab is None:
tab = template.partition('_')[0]
kwargs.setdefault('tab', tab)
- html = render_to_string(template, kwargs)
+ html = render_to_string(template, kwargs, context_instance=context_instance)
return HttpResponse(html, status=status)
-
def requires_login(func):
@wraps(func)
def wrapper(request, *args):
return func(request, *args)
return wrapper
-def requires_admin(func):
- @wraps(func)
- def wrapper(request, *args):
- if not settings.BYPASS_ADMIN_AUTH:
- if not request.user:
- next = urlencode({'next': request.build_absolute_uri()})
- login_uri = reverse(index) + '?' + next
- return HttpResponseRedirect(login_uri)
- if not request.user.is_admin:
- return HttpResponse('Forbidden', status=403)
- return func(request, *args)
- return wrapper
-
-
-def index(request):
- kwargs = {'im_modules':settings.IM_MODULES,
- 'other_modules':settings.IM_MODULES[1:]}
- return render_response('index.html',
- next=request.GET.get('next', ''),
- **kwargs)
-
-
-@requires_admin
-def admin(request):
- stats = {}
- stats['users'] = User.objects.count()
- stats['pending'] = User.objects.filter(state = 'PENDING').count()
-
- invitations = Invitation.objects.all()
- stats['invitations'] = invitations.count()
- stats['invitations_consumed'] = invitations.filter(is_consumed=True).count()
-
- return render_response('admin.html', tab='home', stats=stats)
-
-
-@requires_admin
-def users_list(request):
- users = User.objects.order_by('id')
-
- filter = request.GET.get('filter', '')
- if filter:
- if filter.startswith('-'):
- users = users.exclude(uniq__icontains=filter[1:])
- else:
- users = users.filter(uniq__icontains=filter)
-
- try:
- page = int(request.GET.get('page', 1))
- except ValueError:
- page = 1
- offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
- limit = offset + settings.ADMIN_PAGE_LIMIT
-
- npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
- prev = page - 1 if page > 1 else None
- next = page + 1 if page < npages else None
- return render_response('users_list.html',
- users=users[offset:limit],
- filter=filter,
- pages=range(1, npages + 1),
- page=page,
- prev=prev,
- next=next)
-
-@requires_admin
-def users_info(request, user_id):
- user = User.objects.get(id=user_id)
- states = [x[0] for x in User.ACCOUNT_STATE]
- return render_response('users_info.html',
- user=user,
- states=states)
+def index(request, template_name='index.html', extra_context={}):
+ print '#', get_context(request, extra_context)
+ return render_response(template_name,
+ form = LoginForm(),
+ context_instance = get_context(request, extra_context))
-
-@requires_admin
-def users_modify(request, user_id):
- user = User.objects.get(id=user_id)
- user.uniq = request.POST.get('uniq')
- user.realname = request.POST.get('realname')
- user.is_admin = True if request.POST.get('admin') else False
- user.affiliation = request.POST.get('affiliation')
- user.state = request.POST.get('state')
- user.invitations = int(request.POST.get('invitations') or 0)
- user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB
- user.auth_token = request.POST.get('auth_token')
- try:
- auth_token_expires = request.POST.get('auth_token_expires')
- d = datetime.strptime(auth_token_expires, '%Y-%m-%dT%H:%MZ')
- user.auth_token_expires = d
- except ValueError:
- pass
- user.save()
- return redirect(users_info, user.id)
-
-@requires_admin
-def users_delete(request, user_id):
- user = User.objects.get(id=user_id)
- user.delete()
- return redirect(users_list)
-
-@requires_admin
-def pending_users(request):
- users = User.objects.order_by('id')
-
- users = users.filter(state = 'PENDING')
-
- try:
- page = int(request.GET.get('page', 1))
- except ValueError:
- page = 1
- offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
- limit = offset + settings.ADMIN_PAGE_LIMIT
-
- npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
- prev = page - 1 if page > 1 else None
- next = page + 1 if page < npages else None
- return render_response('pending_users.html',
- users=users[offset:limit],
- filter=filter,
- pages=range(1, npages + 1),
- page=page,
- prev=prev,
- next=next)
-
-def send_greeting(baseurl, user):
- url = baseurl
- subject = _('Welcome to Pithos')
- message = render_to_string('welcome.txt', {
- 'user': user,
- 'url': url,
- 'baseurl': baseurl,
- 'service': settings.SERVICE_NAME,
- 'support': settings.DEFAULT_CONTACT_EMAIL})
- sender = settings.DEFAULT_FROM_EMAIL
- send_mail(subject, message, sender, [user.email])
- logging.info('Sent greeting %s', user)
-
-@requires_admin
-def users_activate(request, user_id):
- user = User.objects.get(id=user_id)
- user.state = 'ACTIVE'
- status = 'success'
- try:
- send_greeting(request.build_absolute_uri('/').rstrip('/'), user)
- message = _('Greeting sent to %s' % user.email)
- user.save()
- except (SMTPException, socket.error) as e:
- status = 'error'
- name = 'strerror'
- message = getattr(e, name) if hasattr(e, name) else e
-
- users = User.objects.order_by('id')
- users = users.filter(state = 'PENDING')
-
- try:
- page = int(request.POST.get('page', 1))
- except ValueError:
- page = 1
- offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
- limit = offset + settings.ADMIN_PAGE_LIMIT
-
- npages = int(ceil(1.0 * users.count() / settings.ADMIN_PAGE_LIMIT))
- prev = page - 1 if page > 1 else None
- next = page + 1 if page < npages else None
- return render_response('pending_users.html',
- users=users[offset:limit],
- filter=filter,
- pages=range(1, npages + 1),
- page=page,
- prev=prev,
- next=next,
- message=message)
-
-def generate_invitation_code():
+def _generate_invitation_code():
while True:
code = randint(1, 2L**63 - 1)
try:
except Invitation.DoesNotExist:
return code
-
-def send_invitation(baseurl, inv):
+def _send_invitation(baseurl, inv):
url = settings.SIGNUP_TARGET % (baseurl, inv.code, quote(baseurl))
subject = _('Invitation to Pithos')
message = render_to_string('invitation.txt', {
'service': settings.SERVICE_NAME,
'support': settings.DEFAULT_CONTACT_EMAIL})
sender = settings.DEFAULT_FROM_EMAIL
- send_mail(subject, message, sender, [inv.uniq])
+ send_mail(subject, message, sender, [inv.username])
logging.info('Sent invitation %s', inv)
-
@requires_login
-def invite(request):
+def invite(request, template_name='invitations.html', extra_context={}):
status = None
message = None
inviter = request.user
if request.method == 'POST':
- uniq = request.POST.get('uniq')
+ username = request.POST.get('uniq')
realname = request.POST.get('realname')
if inviter.invitations > 0:
- code = generate_invitation_code()
+ code = _generate_invitation_code()
invitation, created = Invitation.objects.get_or_create(
inviter=inviter,
- uniq=uniq,
+ username=username,
defaults={'code': code, 'realname': realname})
try:
- send_invitation(request.build_absolute_uri('/').rstrip('/'), invitation)
+ _send_invitation(request.build_absolute_uri('/').rstrip('/'), invitation)
if created:
inviter.invitations = max(0, inviter.invitations - 1)
inviter.save()
status = 'success'
- message = _('Invitation sent to %s' % uniq)
+ message = _('Invitation sent to %s' % username)
except (SMTPException, socket.error) as e:
status = 'error'
message = getattr(e, 'strerror', '')
message = _('No invitations left')
if request.GET.get('format') == 'json':
- sent = [{'email': inv.uniq,
+ sent = [{'email': inv.username,
'realname': inv.realname,
'is_accepted': inv.is_accepted}
for inv in inviter.invitations_sent.all()]
rep = {'invitations': inviter.invitations, 'sent': sent}
return HttpResponse(json.dumps(rep))
- html = render_to_string('invitations.html', {
- 'user': inviter,
- 'status': status,
- 'message': message})
- return HttpResponse(html)
+ kwargs = {'user': inviter, 'status': status, 'message': message}
+ context = get_context(request, extra_context, **kwargs)
+ return render_response(template_name,
+ context_instance = context)
-def send_verification(baseurl, user):
- url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
- quote(user.auth_token),
- quote(baseurl))
- message = render_to_string('activation.txt', {
- 'user': user,
- 'url': url,
- 'baseurl': baseurl,
- 'service': settings.SERVICE_NAME,
- 'support': settings.DEFAULT_CONTACT_EMAIL})
- sender = settings.DEFAULT_FROM_EMAIL
- send_mail('Pithos account activation', message, sender, [user.email])
- logging.info('Sent activation %s', user)
-
-def send_password(baseurl, user):
+def _send_password(baseurl, user):
url = settings.PASSWORD_RESET_TARGET % (baseurl,
- quote(user.uniq),
+ quote(user.username),
quote(baseurl))
message = render_to_string('password.txt', {
'user': user,
send_mail('Pithos password recovering', message, sender, [user.email])
logging.info('Sent password %s', user)
-def reclaim_password(request):
+def reclaim_password(request, template_name='reclaim.html', extra_context={}):
if request.method == 'GET':
- return render_response('reclaim.html')
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context))
elif request.method == 'POST':
- username = request.POST.get('uniq')
+ username = request.POST.get('username')
try:
- user = User.objects.get(uniq=username)
+ user = AstakosUser.objects.get(username=username)
try:
- send_password(request.build_absolute_uri('/').rstrip('/'), user)
+ _send_password(request.build_absolute_uri('/').rstrip('/'), user)
status = 'success'
message = _('Password reset sent to %s' % user.email)
- user.status = 'UNVERIFIED'
+ user.is_active = False
user.save()
except (SMTPException, socket.error) as e:
status = 'error'
name = 'strerror'
message = getattr(e, name) if hasattr(e, name) else e
- except User.DoesNotExist:
+ except AstakosUser.DoesNotExist:
status = 'error'
message = 'Username does not exist'
- html = render_to_string('reclaim.html', {
- 'status': status,
- 'message': message})
- return HttpResponse(html)
-
-@requires_admin
-def invitations_list(request):
- invitations = Invitation.objects.order_by('id')
-
- filter = request.GET.get('filter', '')
- if filter:
- if filter.startswith('-'):
- invitations = invitations.exclude(uniq__icontains=filter[1:])
- else:
- invitations = invitations.filter(uniq__icontains=filter)
-
- try:
- page = int(request.GET.get('page', 1))
- except ValueError:
- page = 1
- offset = max(0, page - 1) * settings.ADMIN_PAGE_LIMIT
- limit = offset + settings.ADMIN_PAGE_LIMIT
-
- npages = int(ceil(1.0 * invitations.count() / settings.ADMIN_PAGE_LIMIT))
- prev = page - 1 if page > 1 else None
- next = page + 1 if page < npages else None
- return render_response('invitations_list.html',
- invitations=invitations[offset:limit],
- filter=filter,
- pages=range(1, npages + 1),
- page=page,
- prev=prev,
- next=next)
-
-@requires_admin
-def invitations_export(request):
- # Create the HttpResponse object with the appropriate CSV header.
- response = HttpResponse(mimetype='text/csv')
- response['Content-Disposition'] = 'attachment; filename=invitations.csv'
-
- writer = csv.writer(response)
- writer.writerow(['ID',
- 'Uniq',
- 'Real Name',
- 'Code',
- 'Inviter Uniq',
- 'Inviter Real Name',
- 'Is_accepted',
- 'Created',
- 'Accepted',])
- invitations = Invitation.objects.order_by('id')
- for inv in invitations:
- writer.writerow([inv.id,
- inv.uniq.encode("utf-8"),
- inv.realname.encode("utf-8"),
- inv.code,
- inv.inviter.uniq.encode("utf-8"),
- inv.inviter.realname.encode("utf-8"),
- inv.is_accepted,
- inv.created,
- inv.accepted])
-
- return response
-
-
-@requires_admin
-def users_export(request):
- # Create the HttpResponse object with the appropriate CSV header.
- response = HttpResponse(mimetype='text/csv')
- response['Content-Disposition'] = 'attachment; filename=users.csv'
-
- writer = csv.writer(response)
- writer.writerow(['ID',
- 'Uniq',
- 'Real Name',
- 'Admin',
- 'Affiliation',
- 'State',
- 'Quota (GiB)',
- 'Updated',])
- users = User.objects.order_by('id')
- for u in users:
- writer.writerow([u.id,
- u.uniq.encode("utf-8"),
- u.realname.encode("utf-8"),
- u.is_admin,
- u.affiliation.encode("utf-8"),
- u.state.encode("utf-8"),
- u.quota,
- u.updated])
-
- return response
-
-@requires_admin
-def users_create(request):
- if request.method == 'GET':
- return render_response('users_local_create.html')
- if request.method == 'POST':
- user = User()
- user.uniq = request.POST.get('uniq')
- user.realname = request.POST.get('realname')
- user.is_admin = True if request.POST.get('admin') else False
- user.affiliation = request.POST.get('affiliation')
- user.quota = int(request.POST.get('quota') or 0) * (1024 ** 3) # In GiB
- user.renew_token()
- user.provider = 'local'
- user.save()
- return redirect(users_info, user.id)
+ kwargs = {'status': status, 'message': message}
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context, **kwargs))
@requires_login
-def users_profile(request):
- next = request.GET.get('next')
+def users_profile(request, template_name='users_profile.html', extra_context={}):
try:
- user = User.objects.get(uniq=request.user)
- except User.DoesNotExist:
- user = User.objects.get(auth_token=request.GET.get('auth', None))
- states = [x[0] for x in User.ACCOUNT_STATE]
- return render_response('users_profile.html',
- user=user,
- states=states,
- next=next)
+ user = AstakosUser.objects.get(username=request.user)
+ except AstakosUser.DoesNotExist:
+ token = request.GET.get('auth', None)
+ user = AstakosUser.objects.get(auth_token=token)
+ return render_response(template_name,
+ context_instance = get_context(request,
+ extra_context,
+ user=user))
@requires_login
-def users_edit(request):
+def users_edit(request, template_name='users_profile.html', extra_context={}):
try:
- user = User.objects.get(uniq=request.user)
- except User.DoesNotExist:
+ user = AstakosUser.objects.get(username=request.user)
+ except AstakosUser.DoesNotExist:
token = request.POST.get('auth', None)
- users = User.objects.all()
- user = User.objects.get(auth_token=token)
- user.realname = request.POST.get('realname')
+ #users = AstakosUser.objects.all()
+ user = AstakosUser.objects.get(auth_token=token)
+ user.first_name = request.POST.get('first_name')
+ user.last_name = request.POST.get('last_name')
user.affiliation = request.POST.get('affiliation')
user.is_verified = True
user.save()
status = 'success'
message = _('Profile has been updated')
- html = render_to_string('users_profile.html', {
- 'user': user,
- 'status': status,
- 'message': message})
- return HttpResponse(html)
+ return render_response(template_name,
+ context_instance = get_context(request, extra_context, **kwargs))
-def signup(request):
+def signup(request, template_name='signup.html', extra_context={}, backend=None, success_url = None):
+ if not backend:
+ backend = get_backend()
if request.method == 'GET':
- kwargs = {'im_modules':settings.IM_MODULES,
- 'next':request.GET.get('next', ''),
- 'code':request.GET.get('code', '')}
- return render_response('signup.html', **kwargs)
- elif request.method == 'POST':
- provider = request.POST.get('choice')
- if not provider:
- return on_failure(_('No provider selected'), template='signup.html')
-
- kwargs = {'code':request.POST.get('code', ''),
- 'next':request.POST.get('next', '')}
- url = '%s%s?' %(reverse('astakos.im.views.register'), provider)
- for k,v in kwargs.items():
- if v:
- url = '%s%s=%s&' %(url, k, v)
- return redirect(url)
-
-def render_registration(provider, code='', next=''):
- initial_data = {'provider':provider}
- if settings.INVITATIONS_ENABLED and code:
try:
- print '#', type(code), code
- invitation = Invitation.objects.get(code=code)
- if invitation.is_consumed:
- return HttpResponseBadRequest('Invitation has beeen used')
- initial_data.update({'uniq':invitation.uniq,
- 'email':invitation.uniq,
- 'realname':invitation.realname})
- try:
- inviter = User.objects.get(uniq=invitation.inviter)
- initial_data['inviter'] = inviter.realname
- except User.DoesNotExist:
- pass
- except Invitation.DoesNotExist:
- return on_failure(_('Wrong invitation code'), template='register.html')
-
- prefix = 'Invited' if code else ''
- formclassname = '%s%sRegisterForm' %(prefix, provider.capitalize())
- form = globals()[formclassname]()
- return render_response('register.html',
- form=form,
- next=next,
- filter=filter,
- code=code,
- provider=provider)
-
-def is_preaccepted(user):
- if user.invitation and not user.invitation.is_consumed:
- return True
-
- return False
-
-def should_send_verification():
- if not settings.INVITATIONS_ENABLED:
- return True
- return False
-
-def register(request, provider):
- print '---', request
- code = request.GET.get('code')
- next = request.GET.get('next')
- if request.method == 'GET':
- code = request.GET.get('code', '')
- next = request.GET.get('next', '')
- if provider not in settings.IM_MODULES:
- return on_failure(_('Invalid provider'))
- return render_registration(provider, code, next)
+ form = backend.get_signup_form(request)
+ return render_response(template_name,
+ form=form,
+ context_instance = get_context(request, extra_context))
+ except Exception, e:
+ return _on_failure(e, template_name=template_name)
elif request.method == 'POST':
- provider = request.POST.get('provider')
- code = request.POST.get('code')
-
- #instantiate the form
- prefix = 'Invited' if code else ''
- formclassname = '%s%sRegisterForm' %(prefix, provider.capitalize())
- form = globals()[formclassname](request.POST)
- if not form.is_valid():
- return render_to_response('register.html',
- {'form':form,
- 'code':code,
- 'next':next})
-
- user = User()
- for field in form.fields:
- if hasattr(user, field):
- setattr(user, field, form.cleaned_data[field])
-
- if user.openidurl:
- redirect_url = reverse('astakos.im.views.create')
- return ask_openid(request,
- user.openidurl,
- redirect_url,
- 'signup')
-
- #save hashed password
- if user.password:
- hasher = newhasher('sha256')
- hasher.update(user.password)
- user.password = hasher.hexdigest()
-
- user.renew_token()
-
- if is_preaccepted(user):
- user.state = 'ACTIVE'
- user.save()
- url = reverse('astakos.im.views.index')
- return redirect(url)
-
- status = 'success'
- if should_send_verification():
- try:
- send_verification(request.build_absolute_uri('/').rstrip('/'), user)
- message = _('Verification sent to %s' % user.email)
- user.save()
- except (SMTPException, socket.error) as e:
- status = 'error'
- name = 'strerror'
- message = getattr(e, name) if hasattr(e, name) else e
- else:
- user.save()
- message = _('Registration completed. You will receive an email upon your account\'s activation')
-
- return info(status, message)
-
-#def discover_extensions(openid_url):
-# service = discover(openid_url)
-# use_ax = False
-# use_sreg = False
-# for endpoint in service[1]:
-# if not use_sreg:
-# use_sreg = sreg.supportsSReg(endpoint)
-# if not use_ax:
-# use_ax = endpoint.usesExtension("http://openid.net/srv/ax/1.0")
-# if use_ax and use_sreg: break
-# return use_ax, use_sreg
-#
-#def ask_openid(request, openid_url, redirect_to, on_failure=None):
-# """ basic function to ask openid and return response """
-# on_failure = on_failure or signin_failure
-# sreg_req = None
-# ax_req = None
-#
-# trust_root = getattr(
-# settings, 'OPENID_TRUST_ROOT', request.build_absolute_uri() + '/'
-# )
-# request.session = {}
-# consumer = Consumer(request.session, PithosOpenIDStore())
-# try:
-# auth_request = consumer.begin(openid_url)
-# except DiscoveryFailure:
-# msg = _("The OpenID %s was invalid") % openid_url
-# return on_failure(request, msg)
-#
-# get capabilities
-# use_ax, use_sreg = discover_extensions(openid_url)
-# if use_sreg:
-# set sreg extension
-# we always ask for nickname and email
-# sreg_attrs = getattr(settings, 'OPENID_SREG', {})
-# sreg_attrs.update({ "optional": ['nickname', 'email'] })
-# sreg_req = sreg.SRegRequest(**sreg_attrs)
-# if use_ax:
-# set ax extension
-# we always ask for nickname and email
-# ax_req = ax.FetchRequest()
-# ax_req.add(ax.AttrInfo('http://schema.openid.net/contact/email',
-# alias='email', required=True))
-# ax_req.add(ax.AttrInfo('http://schema.openid.net/namePerson/friendly',
-# alias='nickname', required=True))
-#
-# add custom ax attrs
-# ax_attrs = getattr(settings, 'OPENID_AX', [])
-# for attr in ax_attrs:
-# if len(attr) == 2:
-# ax_req.add(ax.AttrInfo(attr[0], required=alias[1]))
-# else:
-# ax_req.add(ax.AttrInfo(attr[0]))
-#
-# if sreg_req is not None:
-# auth_request.addExtension(sreg_req)
-# if ax_req is not None:
-# auth_request.addExtension(ax_req)
-#
-# redirect_url = auth_request.redirectURL(trust_root, redirect_to)
-# return HttpResponseRedirect(redirect_url)
-
-def info(status, message, template='base.html'):
- html = render_to_string(template, {
+ try:
+ form = backend.get_signup_form(request)
+ if not form.is_valid():
+ return render_response(template_name,
+ form = form,
+ context_instance = get_context(request, extra_context))
+ status, message = backend.signup(request, form, success_url)
+ next = request.POST.get('next')
+ if next:
+ return redirect(next)
+ return _info(status, message)
+ except Exception, e:
+ return _on_failure(e, template_name=template_name)
+
+def _info(status, message, template_name='base.html'):
+ html = render_to_string(template_name, {
'status': status,
'message': message})
response = HttpResponse(html)
return response
-def on_success(message, template='base.html'):
- return info('success', message)
+def _on_success(message, template_name='base.html'):
+ return _info('success', message, template_name)
-def on_failure(message, template='base.html'):
- return info('error', message)
+def _on_failure(message, template_name='base.html'):
+ return _info('error', message, template_name)
from time import time, mktime
from urllib import quote, unquote
-from astakos.im.models import User
-
+from astakos.im.models import AstakosUser
def get_user_from_token(token):
try:
- return User.objects.get(auth_token=token)
- except User.DoesNotExist:
+ return AstakosUser.objects.get(auth_token=token)
+ except AstakosUser.DoesNotExist:
return None
-
class AuthMiddleware(object):
def process_request(self, request):
request.user = None
return
# Check if the is active.
- if user.state != 'ACTIVE':
+ if not user.is_active:
return
# Check if the token has expired.
return
request.user = user
- request.user_uniq = user.uniq
+ request.user_uniq = user.username
def process_response(self, request, response):
if getattr(request, 'user', None) and getattr(request, 'set_auth_cookie', False):
- expire_fmt = request.user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
- cookie_value = quote(request.user.uniq + '|' + request.user.auth_token)
+ user_profile = request.user.get_profile()
+ expire_fmt = user_profile.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
+ cookie_value = quote(request.user.uniq + '|' + user_profile.auth_token)
response.set_cookie('_pithos2_a', value=cookie_value, expires=expire_fmt, path='/')
- return response
+ return response
\ No newline at end of file
'django.template.loaders.app_directories.Loader',
)
+
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'astakos.middleware.LoggingConfigMiddleware',
# Put strings here, like "/home/html/django_templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
+ '%s/im/admin/templates/' %PROJECT_PATH,
)
INSTALLED_APPS = (
'astakos.im',
- 'south'
+ 'south',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes'
)
+
+TEMPLATE_CONTEXT_PROCESSORS = ('astakos.im.context_processors.im_modules',
+ 'astakos.im.context_processors.next',
+ 'astakos.im.context_processors.code',)
+
+AUTHENTICATION_BACKENDS = (
+ 'astakos.im.auth_backends.AstakosUserModelBackend',
+)
+
+CUSTOM_USER_MODEL = 'astakos.im.AstakosUser'
\ No newline at end of file
4 : 0
}
-SERVICE_NAME = 'Pithos'
+SERVICE_NAME = 'Astakos'
# Address to use for outgoing emails
-DEFAULT_FROM_EMAIL = 'Pithos <no-reply@grnet.gr>'
-DEFAULT_CONTACT_EMAIL = 'support@pithos.grnet.gr'
+DEFAULT_FROM_EMAIL = '%s <no-reply@grnet.gr>' %SERVICE_NAME
+DEFAULT_CONTACT_EMAIL = 'support@%s.grnet.gr' %SERVICE_NAME.lower()
# Where users should signup with their invitation code
SIGNUP_TARGET = '%s/im/signup/?code=%d&next=%s'
FORCE_PROFILE_UPDATE = False
#Enable invitations
-INVITATIONS_ENABLED = True
+INVITATIONS_ENABLED = True
\ No newline at end of file