Commit e71c95c0 authored by Kostas Papadimitriou's avatar Kostas Papadimitriou
Browse files

astakos: Improved email validator

Custom EmailValidator class backported from django 1.6
parent 5b890f44
# Copyright 2013 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 re
from django.utils.translation import ugettext as _
from django.utils.encoding import smart_str, force_text
from django import forms
class EmailValidator(object):
"""
Email validator. Backported from django 1.6
"""
message = _('Enter a valid email address.')
code = 'invalid'
user_regex = re.compile(
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*$" # dot-atom
r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"$)', # quoted-string
re.IGNORECASE)
domain_regex = re.compile(
r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}|[A-Z0-9-]{2,})$' # domain
# literal form, ipv4 address (SMTP 4.1.3)
r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$',
re.IGNORECASE)
domain_whitelist = ['localhost']
def __init__(self, message=None, code=None, whitelist=None):
if message is not None:
self.message = message
if code is not None:
self.code = code
if whitelist is not None:
self.domain_whitelist = whitelist
def __call__(self, value):
value = force_text(value)
if not value or '@' not in value:
raise forms.ValidationError(self.message, code=self.code)
user_part, domain_part = value.rsplit('@', 1)
if not self.user_regex.match(user_part):
raise forms.ValidationError(self.message, code=self.code)
if (not domain_part in self.domain_whitelist and
not self.domain_regex.match(domain_part)):
# Try for possible IDN domain-part
try:
domain_part = domain_part.encode('idna').decode('ascii')
if not self.domain_regex.match(domain_part):
raise forms.ValidationError(self.message, code=self.code)
else:
return
except UnicodeError:
pass
raise forms.ValidationError(self.message, code=self.code)
class EmailField(forms.EmailField):
default_validators = [EmailValidator()]
......@@ -48,6 +48,7 @@ from django.core import validators
from synnefo.util import units
from synnefo_branding.utils import render_to_string
from synnefo.lib import join_urls
from astakos.im.fields import EmailField
from astakos.im.models import AstakosUser, EmailChange, Invitation, Resource, \
PendingThirdPartyUser, get_latest_terms, ProjectApplication, Project
from astakos.im import presentation
......@@ -86,6 +87,7 @@ class LocalUserCreationForm(UserCreationForm):
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
recaptcha_response_field = forms.CharField(
widget=RecaptchaWidget, label='')
email = EmailField()
class Meta:
model = AstakosUser
......@@ -175,7 +177,7 @@ class LocalUserCreationForm(UserCreationForm):
class ThirdPartyUserCreationForm(forms.ModelForm):
email = forms.EmailField(
email = EmailField(
label='Contact email',
help_text='This is needed for contact purposes. '
'It doesn't need to be the same with the one you '
......@@ -257,7 +259,7 @@ class ThirdPartyUserCreationForm(forms.ModelForm):
class LoginForm(AuthenticationForm):
username = forms.EmailField(label=_("Email"))
username = EmailField(label=_("Email"))
recaptcha_challenge_field = forms.CharField(widget=DummyWidget)
recaptcha_response_field = forms.CharField(
widget=RecaptchaWidget, label='')
......@@ -342,8 +344,8 @@ class ProfileForm(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.
"""
email = forms.EmailField(label='E-mail address',
help_text='E-mail address')
email = EmailField(label='E-mail address',
help_text='E-mail address')
renew = forms.BooleanField(label='Renew token', required=False)
class Meta:
......@@ -389,9 +391,9 @@ class SendInvitationForm(forms.Form):
Form for sending an invitations
"""
email = forms.EmailField(required=True, label='Email address')
first_name = forms.EmailField(label='First name')
last_name = forms.EmailField(label='Last name')
email = EmailField(required=True, label='Email address')
first_name = EmailField(label='First name')
last_name = EmailField(label='Last name')
class ExtendedPasswordResetForm(PasswordResetForm):
......@@ -456,6 +458,8 @@ class ExtendedPasswordResetForm(PasswordResetForm):
class EmailChangeForm(forms.ModelForm):
new_email_address = EmailField()
class Meta:
model = EmailChange
fields = ('new_email_address',)
......@@ -507,7 +511,7 @@ class SignApprovalTermsForm(forms.ModelForm):
class InvitationForm(forms.ModelForm):
username = forms.EmailField(label=_("Email"))
username = EmailField(label=_("Email"))
def __init__(self, *args, **kwargs):
super(InvitationForm, self).__init__(*args, **kwargs)
......
......@@ -668,6 +668,24 @@ class TestLocal(TestCase):
class UserActionsTests(TestCase):
def test_email_validation(self):
backend = activation_backends.get_backend()
form = backend.get_signup_form('local')
self.assertTrue(isinstance(form, forms.LocalUserCreationForm))
user_data = {
'email': 'kpap@synnefo.org',
'first_name': 'Kostas Papas',
'password1': '123',
'password2': '123'
}
form = backend.get_signup_form(provider='local',
initial_data=user_data)
self.assertTrue(form.is_valid())
user_data['email'] = 'kpap@synnefo.org.'
form = backend.get_signup_form(provider='local',
initial_data=user_data)
self.assertFalse(form.is_valid())
def test_email_change(self):
# to test existing email validation
get_local_user('existing@synnefo.org')
......@@ -686,9 +704,16 @@ class UserActionsTests(TestCase):
self.assertEqual(r.status_code, 200)
self.assertFalse(user.email_change_is_pending())
# invalid email format
data = {'new_email_address': 'existing@synnefo.org.'}
r = self.client.post(ui_url('email_change'), data)
form = r.context['form']
self.assertFalse(form.is_valid())
# request email change to an existing email fails
data = {'new_email_address': 'existing@synnefo.org'}
r = self.client.post(ui_url('email_change'), data)
self.assertContains(r, messages.EMAIL_USED)
# proper email change
......
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