Fix LoginForm clean incase of login failure
[astakos] / snf-astakos-app / astakos / im / forms.py
index 958c98d..2e136ae 100644 (file)
@@ -35,7 +35,8 @@ from datetime import datetime
 
 from django import forms
 from django.utils.translation import ugettext as _
-from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, PasswordResetForm
+from django.contrib.auth.forms import UserCreationForm, AuthenticationForm, \
+    PasswordResetForm, PasswordChangeForm
 from django.core.mail import send_mail
 from django.contrib.auth.tokens import default_token_generator
 from django.template import Context, loader
@@ -44,16 +45,22 @@ from django.core.urlresolvers import reverse
 from django.utils.functional import lazy
 from django.utils.safestring import mark_safe
 from django.contrib import messages
+from django.utils.encoding import smart_str
 
-from astakos.im.models import AstakosUser, Invitation, get_latest_terms
-from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, RECAPTCHA_ENABLED
+from astakos.im.models import AstakosUser, Invitation, get_latest_terms, EmailChange
+from astakos.im.settings import INVITATIONS_PER_LEVEL, DEFAULT_FROM_EMAIL, \
+    BASEURL, SITENAME, RECAPTCHA_PRIVATE_KEY, DEFAULT_CONTACT_EMAIL, \
+    RECAPTCHA_ENABLED, LOGGING_LEVEL
 from astakos.im.widgets import DummyWidget, RecaptchaWidget
+from astakos.im.functions import send_change_email
 
 # since Django 1.4 use django.core.urlresolvers.reverse_lazy instead
 from astakos.im.util import reverse_lazy, reserved_email, get_query
 
 import logging
+import hashlib
 import recaptcha.client.captcha as captcha
+from random import random
 
 logger = logging.getLogger(__name__)
 
@@ -139,7 +146,7 @@ class LocalUserCreationForm(UserCreationForm):
         user.renew_token()
         if commit:
             user.save()
-        logger.info('Created user %s', user)
+            logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
         return user
 
 class InvitedLocalUserCreationForm(LocalUserCreationForm):
@@ -189,7 +196,7 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
         if get_latest_terms():
             self.fields.keyOrder.append('has_signed_terms')
         #set readonly form fields
-        ro = ["third_party_identifier", "first_name", "last_name"]
+        ro = ["third_party_identifier"]
         for f in ro:
             self.fields[f].widget.attrs['readonly'] = True
         
@@ -220,7 +227,7 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
         user.provider = get_query(self.request).get('provider')
         if commit:
             user.save()
-        logger.info('Created user %s', user)
+            logger._log(LOGGING_LEVEL, 'Created user %s' % user.email, [])
         return user
 
 class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
@@ -249,11 +256,24 @@ class InvitedThirdPartyUserCreationForm(ThirdPartyUserCreationForm):
         return user
 
 class ShibbolethUserCreationForm(ThirdPartyUserCreationForm):
+    additional_email = forms.CharField(widget=forms.HiddenInput(), label='', required = False)
+    
+    def __init__(self, *args, **kwargs):
+        super(ShibbolethUserCreationForm, self).__init__(*args, **kwargs)
+        self.fields.keyOrder.append('additional_email')
+        # copy email value to additional_mail in case user will change it
+        name = 'email'
+        field = self.fields[name]
+        self.initial['additional_email'] = self.initial.get(name, field.initial)
+    
     def clean_email(self):
         email = self.cleaned_data['email']
         for user in AstakosUser.objects.filter(email = email):
             if user.provider == 'shibboleth':
                 raise forms.ValidationError(_("This email is already associated with another shibboleth account."))
+            elif not user.is_active:
+                raise forms.ValidationError(_("This email is already associated with an inactive account. \
+                                              You need to wait to be activated before being able to switch to a shibboleth account."))
         super(ShibbolethUserCreationForm, self).clean_email()
         return email
 
@@ -299,6 +319,12 @@ class LoginForm(AuthenticationForm):
         check = captcha.submit(rcf, rrf, RECAPTCHA_PRIVATE_KEY, self.ip)
         if not check.is_valid:
             raise forms.ValidationError(_('You have not entered the correct words'))
+    
+    def clean(self):
+        super(LoginForm, self).clean()
+        if self.user_cache and self.user_cache.provider not in ('local', ''):
+            raise forms.ValidationError(_('Local login is not the current authentication method for this account.'))
+        return self.cleaned_data
 
 class ProfileForm(forms.ModelForm):
     """
@@ -389,6 +415,26 @@ class ExtendedPasswordResetForm(PasswordResetForm):
             send_mail(_("Password reset on %s alpha2 testing") % SITENAME,
                 t.render(Context(c)), from_email, [user.email])
 
+class EmailChangeForm(forms.ModelForm):
+    class Meta:
+        model = EmailChange
+        fields = ('new_email_address',)
+            
+    def clean_new_email_address(self):
+        addr = self.cleaned_data['new_email_address']
+        if AstakosUser.objects.filter(email__iexact=addr):
+            raise forms.ValidationError(_(u'This email address is already in use. Please supply a different email address.'))
+        return addr
+    
+    def save(self, email_template_name, request, commit=True):
+        ec = super(EmailChangeForm, self).save(commit=False)
+        ec.user = request.user
+        activation_key = hashlib.sha1(str(random()) + smart_str(ec.new_email_address))
+        ec.activation_key=activation_key.hexdigest()
+        if commit:
+            ec.save()
+        send_change_email(ec, request, email_template_name=email_template_name)
+
 class SignApprovalTermsForm(forms.ModelForm):
     class Meta:
         model = AstakosUser
@@ -421,3 +467,21 @@ class InvitationForm(forms.ModelForm):
         except Invitation.DoesNotExist:
             pass
         return username
+
+class ExtendedPasswordChangeForm(PasswordChangeForm):
+    """
+    Extends PasswordChangeForm by enabling user
+    to optionally renew also the token.
+    """
+    renew = forms.BooleanField(label='Renew token', required=False)
+    
+    def __init__(self, user, *args, **kwargs):
+        super(ExtendedPasswordChangeForm, self).__init__(user, *args, **kwargs)
+    
+    def save(self, commit=True):
+        user = super(ExtendedPasswordChangeForm, self).save(commit=False)
+        if self.cleaned_data.get('renew'):
+            user.renew_token()
+        if commit:
+            user.save()
+        return user
\ No newline at end of file