Commit a196eb7e authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

Several minor changes

* change visible fields in user profile page
* change visible fields in user modification administrator page
* enable administrator to update user quota
* read default user level from settings
* show sent invitations and status in user invite page
* if no next request parameter after login redirect to index page
* after successful login set cookie (read cookie name & domain from settings)
* delete cookie in logout
* rename `dummy` target to `redirect` and change its functionality
parent 805c6378
......@@ -51,17 +51,33 @@ class AdminProfileForm(forms.ModelForm):
The class defines a save method which sets ``is_verified`` to True so as the user
during the next login will not to be redirected to profile page.
"""
quota = forms.CharField(label=_('Quota (GiB)'))
class Meta:
model = AstakosUser
exclude = ('groups', 'user_permissions')
fields = ('email', 'first_name', 'last_name', 'is_superuser',
'affiliation', 'is_active', 'invitations', 'quota',
'auth_token', 'auth_token_created', 'auth_token_expires',
'date_joined', 'updated')
def __init__(self, *args, **kwargs):
super(AdminProfileForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
ro_fields = ('username','date_joined', 'auth_token', 'last_login', 'email')
ro_fields = ('date_joined', 'auth_token', 'auth_token_created',
'auth_token_expires', 'updated', 'email')
if instance and instance.id:
for field in ro_fields:
self.fields[field].widget.attrs['readonly'] = True
user = kwargs['instance']
if user:
quota = lambda x: int(x) / 1024 ** 3
self.fields['quota'].widget.attrs['value'] = quota(user.quota)
def save(self, commit=True):
user = super(AdminProfileForm, self).save(commit=False)
quota = lambda x: int(x or 0) * (1024 ** 3)
user.quota = quota(self.cleaned_data['quota'])
user.save()
class AdminUserCreationForm(LocalUserCreationForm):
class Meta:
......
......@@ -18,7 +18,7 @@
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Real Name</th>
<th>Affiliation</th>
<th>Email</th>
......@@ -30,7 +30,7 @@
{% for user in users %}
<tr>
<td>{{ user.id }}</td>
<td>{{ user.username }}</td>
<td>{{ user.email }}</td>
<td>{{ user.realname }}</td>
<td>{{ user.affiliation }}</td>
<td>{{ user.email }}</td>
......
......@@ -137,18 +137,14 @@ class ProfileForm(forms.ModelForm):
"""
class Meta:
model = AstakosUser
exclude = ('is_active', 'is_superuser', 'is_staff', 'is_verified', 'groups', 'user_permissions')
fields = ('email', 'first_name', 'last_name', 'auth_token', 'auth_token_expires')
def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
ro_fields = ('username','date_joined', 'updated', 'auth_token',
'auth_token_created', 'auth_token_expires', 'invitations',
'level', 'last_login', 'email', )
ro_fields = ('auth_token', 'auth_token_expires', 'email')
if instance and instance.id:
for field in ro_fields:
if isinstance(self.fields[field].widget, forms.CheckboxInput):
self.fields[field].widget.attrs['disabled'] = True
self.fields[field].widget.attrs['readonly'] = True
def save(self, commit=True):
......
......@@ -57,8 +57,9 @@ class AstakosUser(User):
provider = models.CharField('Provider', max_length=255, blank=True)
#for invitations
level = models.IntegerField('Inviter level', default=4)
invitations = models.IntegerField('Invitations left', default=0)
user_level = settings.DEFAULT_USER_LEVEL
level = models.IntegerField('Inviter level', default=user_level)
invitations = models.IntegerField('Invitations left', default=settings.INVITATIONS_PER_LEVEL[user_level])
auth_token = models.CharField('Authentication Token', max_length=32,
null=True, blank=True)
......@@ -102,6 +103,7 @@ class AstakosUser(User):
def save(self, update_timestamps=True, **kwargs):
if update_timestamps:
if not self.id:
# set username
while not self.username:
username = uuid.uuid4().hex[:30]
try:
......
......@@ -34,17 +34,37 @@
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.utils.translation import ugettext as _
from django.contrib import messages
from django.utils.http import urlencode
from urllib import quote
from urlparse import urlunsplit, urlsplit
from astakos.im.target.util import prepare_response
def login(request):
next = request.GET.get('next')
if not next:
return HttpResponseBadRequest('No next step provided')
"""
If the request user is authenticated, redirects to `next` request parameter
if exists, otherwise redirects to astakos index page displaying an error
message.
If the request user is not authenticated, redirects to login in order to
return back here after successful login.
"""
if request.user.is_authenticated():
return prepare_response(request, request.user, next, skip_login=True)
next = request.GET.get('next')
if next:
parts = list(urlsplit(next))
parts[3] = urlencode({'user': request.user.email, 'token': request.user.auth_token})
url = urlunsplit(parts)
return redirect(url)
else:
msg = _('No next parameter')
messages.add_message(request, messages.ERROR, msg)
url = reverse('astakos.im.views.index')
return redirect(url)
else:
# redirect to login with self as next
url = reverse('astakos.im.views.index')
url = '%s?next=%s' % (url, quote(request.build_absolute_uri()))
return redirect(url)
......@@ -32,15 +32,11 @@
# or implied, of GRNET S.A.
from django.http import HttpResponseBadRequest
from django.core.urlresolvers import reverse
from django.contrib.auth import authenticate
from django.contrib import messages
from astakos.im.target.util import prepare_response, requires_anonymous
from astakos.im.util import get_or_create_user, get_context
from astakos.im.models import AstakosUser, Invitation
from astakos.im.views import render_response, create_user
from astakos.im.backends import get_backend
from astakos.im.forms import LocalUserCreationForm, ThirdPartyUserCreationForm
from astakos.im.target.util import prepare_response
from astakos.im.util import get_or_create_user
class Tokens:
# these are mapped by the Shibboleth SP software
......@@ -52,12 +48,8 @@ class Tokens:
SHIB_EP_AFFILIATION = "HTTP_SHIB_EP_AFFILIATION"
SHIB_SESSION_ID = "HTTP_SHIB_SESSION_ID"
@requires_anonymous
def login(request):
# store invitation code and email
request.session['email'] = request.GET.get('email')
request.session['invitation_code'] = request.GET.get('code')
def login(request):
tokens = request.META
try:
......@@ -76,44 +68,10 @@ def login(request):
affiliation = tokens.get(Tokens.SHIB_EP_AFFILIATION, '')
next = request.GET.get('next')
# check first if user with that identifier is registered
user = None
email = request.session.pop('email')
if email:
# signup mode
if not reserved_screen_name(eppn):
try:
user = AstakosUser.objects.get(email = email)
except AstakosUser.DoesNotExist, e:
# register a new user
first_name, space, last_name = realname.partition(' ')
post_data = {'provider':'Shibboleth', 'first_name':first_name,
'last_name':last_name, 'affiliation':affiliation,
'third_party_identifier':eppn}
form = ThirdPartyUserCreationForm({'email':email})
return create_user(request, form, backend, post_data, next, template_name, extra_context)
else:
status = messages.ERROR
message = '%s@shibboleth is already registered' % eppn
messages.add_message(request, messages.ERROR, message)
else:
# login mode
if user and user.is_active:
#in order to login the user we must call authenticate first
user = authenticate(email=user.email, auth_token=user.auth_token)
return prepare_response(request, user, next)
elif user and not user.is_active:
messages.add_message(request, messages.ERROR, 'Inactive account: %s' % user.email)
return render_response(template_name,
form = LocalUserCreationForm(),
context_instance=get_context(request, extra_context))
def reserved_identifier(identifier):
try:
AstakosUser.objects.get(provider='Shibboleth',
third_party_identifier=identifier)
return True
except AstakosUser.DoesNotExist, e:
return False
\ No newline at end of file
user = get_or_create_user(eppn, realname=realname, affiliation=affiliation, provider='shibboleth', level=0)
# in order to login the user we must call authenticate first
user = authenticate(email=user.email, auth_token=user.auth_token)
return prepare_response(request,
user,
request.GET.get('next'),
'renew' in request.GET)
......@@ -78,14 +78,19 @@ def prepare_response(request, user, next='', renew=False, skip_login=False):
login(request, user)
response = HttpResponse()
# set cookie
expire_fmt = user.auth_token_expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
cookie_value = quote(user.email + '|' + user.auth_token)
response.set_cookie(settings.COOKIE_NAME, value=cookie_value,
expires=expire_fmt, path='/',
domain = settings.COOKIE_DOMAIN)
if not next:
response['X-Auth-User'] = user.email
response['X-Auth-Token'] = user.auth_token
response.content = user.email + '\n' + user.auth_token + '\n'
response.status_code = 200
else:
response['Location'] = next
response.status_code = 302
next = reverse('astakos.im.views.index')
response['Location'] = next
response.status_code = 302
return response
def requires_anonymous(func):
......
......@@ -17,7 +17,7 @@
<a href="{% url astakos.im.views.send_feedback %}">Send Feedback</a>
</li>
<li{% ifequal tab "logout" %} class="active"{% endifequal %}>
<a href="{% url django.contrib.auth.views.logout %}">Logout</a>
<a href="{% url astakos.im.views.user_logout %}">Logout</a>
</li>
</ul>
{% endblock %}
......
......@@ -5,6 +5,25 @@
{% block body %}
<div class="container">
<h3>You have {{ user.invitations }} invitation{{ user.invitations|pluralize }} left.</h3>
<table class="zebra-striped id-sorted">
<thead>
<tr>
<th>Email</th>
<th>Real Name</th>
<th>Consumed</th>
</tr>
</thead>
<tbody>
{% for inv in sent %}
<tr>
<td>{{ inv.email }}</td>
<td>{{ inv.realname }} GiB</td>
<td>{{ inv.is_consumed }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if user.invitations %}
<br />
......
......@@ -40,8 +40,7 @@
<div class="span4">
{% if 'shibboleth' in im_modules %}
<img src="/im/static/shibboleth.png" width="120" height="120">
<form action="{% url astakos.im.views.signup %}" method="post" class="form-stacked">{% csrf_token %}
{{ shibboleth_form.as_p }}
<form action="{% url astakos.im.target.shibboleth.login %}" method="post" class="form-stacked">{% csrf_token %}
<br>
<div class="">
<input type="hidden" name="next" value="{{ next }}">
......
......@@ -43,16 +43,16 @@ urlpatterns = patterns('astakos.im.views',
url(r'^profile/?$', 'edit_profile'),
url(r'^feedback/?$', 'send_feedback'),
url(r'^signup/?$', 'signup'),
url(r'^user_logout/?$', 'user_logout'),
url(r'^admin/', include('astakos.im.admin.urls')),
)
urlpatterns += patterns('django.contrib.auth.views',
url(r'^logout/?$', 'logout'),
url(r'^password/?$', 'password_change', {'post_change_redirect':'profile'})
url(r'^logout/?$', 'logout')
)
urlpatterns += patterns('astakos.im.target',
url(r'^login/dummy/?$', 'dummy.login')
url(r'^login/redirect/?$', 'redirect.login')
)
urlpatterns += patterns('',
......@@ -72,7 +72,8 @@ if 'local' in settings.IM_MODULES:
url(r'^local/password_reset_done/?$', 'password_reset_done'),
url(r'^local/reset/confirm/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$',
'password_reset_confirm'),
url(r'^local/password/reset/complete/$', 'password_reset_complete')
url(r'^local/password/reset/complete/$', 'password_reset_complete'),
url(r'^password/?$', 'password_change', {'post_change_redirect':'profile'})
)
if settings.INVITATIONS_ENABLED:
......
......@@ -204,15 +204,12 @@ def invite(request, template_name='invitations.html', extra_context={}):
message = _('No invitations left')
messages.add_message(request, status, message)
if request.GET.get('format') == 'json':
sent = [{'email': inv.username,
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))
kwargs = {'user': inviter}
kwargs = {'user': inviter,
'sent':sent}
context = get_context(request, extra_context, **kwargs)
return render_response(template_name,
context_instance = context)
......@@ -398,3 +395,10 @@ def create_user(request, form, backend=None, post_data={}, next = None, template
return render_response(template_name,
form = LocalUserCreationForm(),
context_instance=get_context(request, extra_context))
def user_logout(request):
response = HttpResponse()
response.delete_cookie(settings.COOKIE_NAME)
response['Location'] = reverse('django.contrib.auth.views.logout')
response.status_code = 302
return response
\ No newline at end of file
......@@ -12,6 +12,8 @@ ADMIN_PAGE_LIMIT = 100
TWITTER_KEY = ''
TWITTER_SECRET = ''
DEFAULT_USER_LEVEL = 4
INVITATIONS_PER_LEVEL = {
0 : 100,
1 : 2,
......@@ -43,4 +45,7 @@ FORCE_PROFILE_UPDATE = True
INVITATIONS_ENABLED = True
# The URL where requests are redirected for login, especially when using the login_required() decorator.
LOGIN_URL = '/im'
\ No newline at end of file
LOGIN_URL = '/im'
COOKIE_NAME = '_pithos2_a'
COOKIE_DOMAIN = None
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment