Commit 5ed6816e authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

- use email to login & authenticate users

- fix user profile editing bug
- fix user activation issue
- consume invitation upon invited user creation
- fix invited user invitations
- add token info in test user fixture
- minor bug fixes

Refs: #1917
Refs: #1911
Refs: #1919
parent 0321ca9b
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
Αγαπητέ/η {{ user.realname }}, Αγαπητέ/η {{ user.realname }},
Ο λογαρισμός σας για την υπηρεσία {{ site_name }} της ΕΔΕΤκατά την Alpha (πιλοτική) Ο λογαρισμός σας για την υπηρεσία {{ site_name }} της ΕΔΕΤ κατά την Alpha (πιλοτική)
φάση λειτουργίας της έχει ενεργοποιηθεί. φάση λειτουργίας της έχει ενεργοποιηθεί.
Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο: Για να συνδεθείτε, χρησιμοποιήστε τον παρακάτω σύνδεσμο:
......
...@@ -223,7 +223,8 @@ def users_modify(request, user_id, template_name='users_info.html', extra_contex ...@@ -223,7 +223,8 @@ def users_modify(request, user_id, template_name='users_info.html', extra_contex
If the ``request.user`` is not a superuser redirects to login page. If the ``request.user`` is not a superuser redirects to login page.
""" """
form = AdminProfileForm(request.POST) user = AstakosUser.objects.get(id = user_id)
form = AdminProfileForm(request.POST, instance=user)
if form.is_valid(): if form.is_valid():
form.save() form.save()
return redirect(users_info, user.id, template_name, extra_context) return redirect(users_info, user.id, template_name, extra_context)
...@@ -307,13 +308,12 @@ def pending_users(request, template_name='pending_users.html', extra_context={}) ...@@ -307,13 +308,12 @@ def pending_users(request, template_name='pending_users.html', extra_context={})
context_instance = get_context(request, extra_context,**kwargs)) context_instance = get_context(request, extra_context,**kwargs))
def _send_greeting(request, user, template_name): def _send_greeting(request, user, template_name):
url = reverse('astakos.im.views.index')
subject = _('Welcome to %s' %settings.SERVICE_NAME) subject = _('Welcome to %s' %settings.SERVICE_NAME)
site = Site.objects.get_current() site = Site.objects.get_current()
baseurl = request.build_absolute_uri('/').rstrip('/') baseurl = request.build_absolute_uri('/').rstrip('/')
message = render_to_string(template_name, { message = render_to_string(template_name, {
'user': user, 'user': user,
'url': url, 'url': site.domain,
'baseurl': baseurl, 'baseurl': baseurl,
'site_name': site.name, 'site_name': site.name,
'support': settings.DEFAULT_CONTACT_EMAIL}) 'support': settings.DEFAULT_CONTACT_EMAIL})
......
...@@ -83,6 +83,7 @@ def authenticate(request): ...@@ -83,6 +83,7 @@ def authenticate(request):
response = HttpResponse() response = HttpResponse()
response.status=204 response.status=204
user_info = {'uniq':user.username, user_info = {'uniq':user.username,
'email':user.email,
'auth_token':user.auth_token, 'auth_token':user.auth_token,
'auth_token_created':user.auth_token_created.isoformat(), 'auth_token_created':user.auth_token_created.isoformat(),
'auth_token_expires':user.auth_token_expires.isoformat()} 'auth_token_expires':user.auth_token_expires.isoformat()}
......
from django.conf import settings from django.conf import settings
from django.contrib.auth.backends import ModelBackend from django.contrib.auth.backends import ModelBackend
#from django.core.exceptions import ImproperlyConfigured from django.core.validators import email_re
#from django.db.models import get_model
from astakos.im.models import AstakosUser from astakos.im.models import AstakosUser
class AstakosUserModelCredentialsBackend(ModelBackend): class TokenBackend(ModelBackend):
""" """
AuthenticationBackend used to authenticate user creadentials AuthenticationBackend used to authenticate using token instead
""" """
def authenticate(self, username=None, password=None): def authenticate(self, email=None, auth_token=None):
try: try:
user = AstakosUser.objects.get(username=username) user = AstakosUser.objects.get(email=email)
if user.check_password(password): if user.auth_token == auth_token:
return user return user
except AstakosUser.DoesNotExist: except AstakosUser.DoesNotExist:
return None return None
...@@ -22,29 +21,31 @@ class AstakosUserModelCredentialsBackend(ModelBackend): ...@@ -22,29 +21,31 @@ class AstakosUserModelCredentialsBackend(ModelBackend):
return AstakosUser.objects.get(pk=user_id) return AstakosUser.objects.get(pk=user_id)
except AstakosUser.DoesNotExist: except AstakosUser.DoesNotExist:
return None 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
class AstakosUserModelTokenBackend(ModelBackend): class EmailBackend(ModelBackend):
""" """
AuthenticationBackend used to authenticate using token instead If the ``username`` parameter is actually an email uses email to authenticate
the user else tries the username.
Used from ``astakos.im.forms.LoginForm`` to authenticate.
""" """
def authenticate(self, username=None, auth_token=None): def authenticate(self, username=None, password=None):
try: #If username is an email address, then try to pull it up
user = AstakosUser.objects.get(username=username) if email_re.search(username):
if user.auth_token == auth_token: try:
return user user = AstakosUser.objects.get(email=username)
except AstakosUser.DoesNotExist: except AstakosUser.DoesNotExist:
return None return None
else:
#We have a non-email address username we
#should try username
try:
user = AstakosUser.objects.get(username=username)
except AstakosUser.DoesNotExist:
return None
if user.check_password(password):
return user
def get_user(self, user_id): def get_user(self, user_id):
try: try:
return AstakosUser.objects.get(pk=user_id) return AstakosUser.objects.get(pk=user_id)
......
...@@ -40,6 +40,7 @@ from django.utils.translation import ugettext as _ ...@@ -40,6 +40,7 @@ from django.utils.translation import ugettext as _
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.contrib import messages from django.contrib import messages
from django.shortcuts import redirect
from smtplib import SMTPException from smtplib import SMTPException
from urllib import quote from urllib import quote
...@@ -48,7 +49,10 @@ from astakos.im.util import get_or_create_user ...@@ -48,7 +49,10 @@ from astakos.im.util import get_or_create_user
from astakos.im.models import AstakosUser, Invitation from astakos.im.models import AstakosUser, Invitation
from astakos.im.forms import ExtendedUserCreationForm, InvitedExtendedUserCreationForm from astakos.im.forms import ExtendedUserCreationForm, InvitedExtendedUserCreationForm
def get_backend(): import socket
import logging
def get_backend(request):
""" """
Return an instance of a registration backend, Return an instance of a registration backend,
according to the INVITATIONS_ENABLED setting according to the INVITATIONS_ENABLED setting
...@@ -69,7 +73,7 @@ def get_backend(): ...@@ -69,7 +73,7 @@ def get_backend():
backend_class = getattr(mod, backend_class_name) backend_class = getattr(mod, backend_class_name)
except AttributeError: except AttributeError:
raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr)) raise ImproperlyConfigured('Module "%s" does not define a registration backend named "%s"' % (module, attr))
return backend_class() return backend_class(request)
class InvitationsBackend(object): class InvitationsBackend(object):
""" """
...@@ -79,26 +83,38 @@ class InvitationsBackend(object): ...@@ -79,26 +83,38 @@ class InvitationsBackend(object):
account is created and the user is going to receive an email as soon as an account is created and the user is going to receive an email as soon as an
administrator activates his/her account. administrator activates his/her account.
""" """
def get_signup_form(self, request): def __init__(self, request):
self.request = request
self.invitation = None
self.set_invitation()
def set_invitation(self):
code = self.request.GET.get('code', '')
if not code:
code = self.request.POST.get('code', '')
if code:
self.invitation = Invitation.objects.get(code=code)
if self.invitation.is_consumed:
raise Exception('Invitation has beeen used')
def get_signup_form(self):
""" """
Returns the necassary registration form depending the user is invited or not Returns the necassary registration form depending the user is invited or not
Throws Invitation.DoesNotExist in case ``code`` is not valid. Throws Invitation.DoesNotExist in case ``code`` is not valid.
""" """
code = request.GET.get('code', '') request = self.request
formclass = 'ExtendedUserCreationForm' formclass = 'ExtendedUserCreationForm'
initial_data = None initial_data = None
if request.method == 'GET': if request.method == 'GET':
if code: if self.invitation:
formclass = 'Invited%s' %formclass formclass = 'Invited%s' %formclass
self.invitation = Invitation.objects.get(code=code)
if self.invitation.is_consumed:
raise Exception('Invitation has beeen used')
initial_data = {'username':self.invitation.username, initial_data = {'username':self.invitation.username,
'email':self.invitation.username, 'email':self.invitation.username,
'realname':self.invitation.realname} 'realname':self.invitation.realname}
inviter = AstakosUser.objects.get(username=self.invitation.inviter) inviter = AstakosUser.objects.get(username=self.invitation.inviter)
initial_data['inviter'] = inviter.realname initial_data['inviter'] = inviter.realname
initial_data['level'] = inviter.level + 1
else: else:
initial_data = request.POST initial_data = request.POST
return globals()[formclass](initial_data) return globals()[formclass](initial_data)
...@@ -110,32 +126,28 @@ class InvitationsBackend(object): ...@@ -110,32 +126,28 @@ class InvitationsBackend(object):
It should be called after ``get_signup_form`` which sets invitation if exists. It should be called after ``get_signup_form`` which sets invitation if exists.
""" """
invitation = getattr(self, 'invitation') if hasattr(self, 'invitation') else None invitation = self.invitation
if not invitation: if not invitation:
return False return False
if invitation.username == user.username and not invitation.is_consumed: if invitation.username == user.email and not invitation.is_consumed:
invitation.consume()
return True return True
return False return False
def signup(self, request): def signup(self, form):
""" """
Creates a incative user account. If the user is preaccepted (has a valid Initially creates an inactive user account. If the user is preaccepted
invitation code) the user is activated and if the request param ``next`` (has a valid invitation code) the user is activated and if the request
is present redirects to it. param ``next`` is present redirects to it.
In any other case the method returns the action status and a message. In any other case the method returns the action status and a message.
""" """
kwargs = {} kwargs = {}
form = self.get_signup_form(request)
user = form.save() user = form.save()
try: try:
if self._is_preaccepted(user): if self._is_preaccepted(user):
user.is_active = True user.is_active = True
user.save() user.save()
message = _('Registration completed. You can now login.') message = _('Registration completed. You can now login.')
next = request.POST.get('next')
if next:
return redirect(next)
else: else:
message = _('Registration completed. You will receive an email upon your account\'s activation') message = _('Registration completed. You will receive an email upon your account\'s activation')
status = messages.SUCCESS status = messages.SUCCESS
...@@ -150,14 +162,18 @@ class SimpleBackend(object): ...@@ -150,14 +162,18 @@ class SimpleBackend(object):
supplies the necessary registation information, an incative user account is supplies the necessary registation information, an incative user account is
created and receives an email in order to activate his/her account. created and receives an email in order to activate his/her account.
""" """
def get_signup_form(self, request): def __init__(self, request):
self.request = request
def get_signup_form(self):
""" """
Returns the UserCreationForm Returns the UserCreationForm
""" """
request = self.request
initial_data = request.POST if request.method == 'POST' else None initial_data = request.POST if request.method == 'POST' else None
return UserCreationForm(initial_data) return ExtendedUserCreationForm(initial_data)
def signup(self, request, email_template_name='activation_email.txt'): def signup(self, form, email_template_name='activation_email.txt'):
""" """
Creates an inactive user account and sends a verification email. Creates an inactive user account and sends a verification email.
...@@ -178,12 +194,11 @@ class SimpleBackend(object): ...@@ -178,12 +194,11 @@ class SimpleBackend(object):
* DEFAULT_FROM_EMAIL: from email * DEFAULT_FROM_EMAIL: from email
""" """
kwargs = {} kwargs = {}
form = self.get_signup_form(request)
user = form.save() user = form.save()
status = messages.SUCCESS status = messages.SUCCESS
try: try:
_send_verification(request, user, email_template_name) _send_verification(self.request, user, email_template_name)
message = _('Verification sent to %s' % user.email) message = _('Verification sent to %s' % user.email)
except (SMTPException, socket.error) as e: except (SMTPException, socket.error) as e:
status = messages.ERROR status = messages.ERROR
...@@ -191,18 +206,18 @@ class SimpleBackend(object): ...@@ -191,18 +206,18 @@ class SimpleBackend(object):
message = getattr(e, name) if hasattr(e, name) else e message = getattr(e, name) if hasattr(e, name) else e
return status, message return status, message
def _send_verification(request, user, template_name): def _send_verification(request, user, template_name):
site = Site.objects.get_current() site = Site.objects.get_current()
baseurl = request.build_absolute_uri('/').rstrip('/') baseurl = request.build_absolute_uri('/').rstrip('/')
url = settings.ACTIVATION_LOGIN_TARGET % (baseurl, url = settings.ACTIVATION_LOGIN_TARGET % (baseurl,
quote(user.auth_token), quote(user.auth_token),
quote(baseurl)) quote(baseurl))
message = render_to_string(template_name, { message = render_to_string(template_name, {
'user': user, 'user': user,
'url': url, 'url': url,
'baseurl': baseurl, 'baseurl': baseurl,
'site_name': site.name, 'site_name': site.name,
'support': settings.DEFAULT_CONTACT_EMAIL}) 'support': settings.DEFAULT_CONTACT_EMAIL})
sender = settings.DEFAULT_FROM_EMAIL sender = settings.DEFAULT_FROM_EMAIL
send_mail('Pithos account activation', message, sender, [user.email]) send_mail('Pithos account activation', message, sender, [user.email])
logging.info('Sent activation %s', user) logging.info('Sent activation %s', user)
...@@ -14,7 +14,10 @@ ...@@ -14,7 +14,10 @@
"fields": { "fields": {
"level": 1, "level": 1,
"invitations": 2, "invitations": 2,
"updated": "2012-01-24" "auth_token": "0000",
"auth_token_created": "2011-04-07 09:17:14",
"auth_token_expires": "2015-04-07 09:17:14",
"updated": "2011-02-06"
} }
} }
] ]
...@@ -33,14 +33,17 @@ ...@@ -33,14 +33,17 @@
from django import forms from django import forms
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django.conf import settings from django.conf import settings
from django.core.validators import email_re
from hashlib import new as newhasher from hashlib import new as newhasher
from astakos.im.models import AstakosUser from astakos.im.models import AstakosUser
from astakos.im.util import get_or_create_user from astakos.im.util import get_or_create_user
import logging import logging
import uuid
class UniqueUserEmailField(forms.EmailField): class UniqueUserEmailField(forms.EmailField):
""" """
...@@ -61,18 +64,14 @@ class ExtendedUserCreationForm(UserCreationForm): ...@@ -61,18 +64,14 @@ class ExtendedUserCreationForm(UserCreationForm):
Extends the built in UserCreationForm in several ways: Extends the built in UserCreationForm in several ways:
* Adds an email field, which uses the custom UniqueUserEmailField. * Adds an email field, which uses the custom UniqueUserEmailField.
* The username field isn't visible and it is assigned the email value. * The username field isn't visible and it is assigned a generated id.
* first_name and last_name fields are added. * first_name and last_name fields are added.
* User is created not active.
""" """
username = forms.CharField(required = False, max_length = 30)
email = UniqueUserEmailField(required = True, label = 'Email address')
first_name = forms.CharField(required = False, max_length = 30)
last_name = forms.CharField(required = False, max_length = 30)
class Meta: class Meta:
model = AstakosUser model = AstakosUser
fields = ("username",) fields = ("email", "first_name", "last_name")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
...@@ -82,76 +81,46 @@ class ExtendedUserCreationForm(UserCreationForm): ...@@ -82,76 +81,46 @@ class ExtendedUserCreationForm(UserCreationForm):
self.fields.keyOrder = ['email', 'first_name', 'last_name', self.fields.keyOrder = ['email', 'first_name', 'last_name',
'password1', 'password2'] 'password1', 'password2']
def clean(self, *args, **kwargs):
"""
Normal cleanup + username generation.
"""
cleaned_data = super(ExtendedUserCreationForm, self).clean(*args, **kwargs)
if cleaned_data.has_key('email'):
cleaned_data['username'] = cleaned_data['email']
return cleaned_data
def save(self, commit=True): def save(self, commit=True):
""" """
Saves the email, first_name and last_name properties, after the normal Saves the email, first_name and last_name properties, after the normal
save behavior is complete. save behavior is complete.
""" """
user = super(ExtendedUserCreationForm, self).save(commit=False) user = super(ExtendedUserCreationForm, self).save(commit=False)
user.username = uuid.uuid4().hex[:30]
user.is_active = False
user.renew_token() user.renew_token()
user.save() user.save()
logging.info('Created user %s', user) logging.info('Created user %s', user)
return user return user
class InvitedExtendedUserCreationForm(UserCreationForm): class InvitedExtendedUserCreationForm(ExtendedUserCreationForm):
""" """
Extends the built in UserCreationForm in several ways: Extends the ExtendedUserCreationForm: adds an inviter readonly field.
* Adds an email field, which uses the custom UniqueUserEmailField.
* The username field isn't visible and it is assigned the email value.
* first_name and last_name fields are added.
""" """
username = forms.CharField(required = False, max_length = 30)
email = UniqueUserEmailField(required = True, label = 'Email address')
first_name = forms.CharField(required = False, max_length = 30)
last_name = forms.CharField(required = False, max_length = 30)
inviter = forms.CharField(widget=forms.TextInput(), label=_('Inviter Real Name')) inviter = forms.CharField(widget=forms.TextInput(), label=_('Inviter Real Name'))
level = forms.IntegerField(widget=forms.TextInput(), label=_('Level'))
class Meta: class Meta:
model = AstakosUser model = AstakosUser
fields = ("username",) fields = ("email", "first_name", "last_name")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
""" """
Changes the order of fields, and removes the username field. Changes the order of fields, and removes the username field.
""" """
super(InvitedExtendedUserCreationForm, self).__init__(*args, **kwargs) super(InvitedExtendedUserCreationForm, self).__init__(*args, **kwargs)
self.fields.keyOrder = ['email', 'inviter', 'first_name', 'last_name', self.fields.keyOrder = ['email', 'inviter', 'level', 'first_name',
'password1', 'password2'] 'last_name', 'password1', 'password2']
#set readonly form fields #set readonly form fields
self.fields['inviter'].widget.attrs['readonly'] = True self.fields['inviter'].widget.attrs['readonly'] = True
self.fields['level'].widget.attrs['readonly'] = True
self.fields['email'].widget.attrs['readonly'] = True self.fields['email'].widget.attrs['readonly'] = True
self.fields['username'].widget.attrs['readonly'] = True self.fields['username'].widget.attrs['readonly'] = True
def clean(self, *args, **kwargs): class LoginForm(AuthenticationForm):
""" username = forms.EmailField(label=_("Email"))
Normal cleanup + username generation.
"""
cleaned_data = super(UserCreationForm, self).clean(*args, **kwargs)
if cleaned_data.has_key('email'):
cleaned_data['username'] = cleaned_data['email']
return cleaned_data
def save(self, commit=True):
"""
Saves the email, first_name and last_name properties, after the normal
save behavior is complete.
"""
user = super(InvitedExtendedUserCreationForm, self).save(commit=False)
user.renew_token()
user.save()
logging.info('Created user %s', user)
return user
class ProfileForm(forms.ModelForm): class ProfileForm(forms.ModelForm):
""" """
...@@ -163,15 +132,14 @@ class ProfileForm(forms.ModelForm): ...@@ -163,15 +132,14 @@ class ProfileForm(forms.ModelForm):
""" """
class Meta: class Meta:
model = AstakosUser model = AstakosUser
exclude = ('groups', 'user_permissions') exclude = ('is_active', 'is_superuser', 'is_staff', 'is_verified', 'groups', 'user_permissions')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(ProfileForm, self).__init__(*args, **kwargs) super(ProfileForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None) instance = getattr(self, 'instance', None)