Django forms EmailValidation not working - django

I have been researching on this issue but it seems there's not a lot of explanation around there covering this.
...
class RangerRegistrationForm(RegistrationFormUniqueEmail):
email = forms.EmailField(label=_("Email Address"), validators=[EmailValidator(whitelist=['gmail.com'])])
...
Here's the part of my script where I check if the user supplies a gmail account. Unfortunately, as long as it's a valid email it will always pass the check.
What am I doing wrong here?

This is NOT a bug in Django (re-read the source code link posted in #catavaran's answer).
A whitelist in this case is not a "block everything except for this domain part" solution. Rather, the whitelist is a domain part that would otherwise be flagged as invalid by Django's EmailValidator.
For example, the default whitelist is set to domain_whitelist = ['localhost']...an otherwise invalid domain_part that is being flagged as being OK for this use case.
To validate the domain part of an email field, you are going to need to write your own clean function. Something like:
class RangerRegistrationForm(forms.Form):
email = forms.EmailField(label=_("Email Address"))
def clean_email(self):
submitted_data = self.cleaned_data['email']
if '#gmail.com' not in submitted_data:
raise forms.ValidationError('You must register using a Gmail address')
return submitted_data

Congratulations! You had found a bug in Django.
Look at this code from the EmailValidator:
if (domain_part not in self.domain_whitelist and
not self.validate_domain_part(domain_part)):
...
If the domain part of the e-mail is valid then checking against the self.domain_whitelist just ignored.

Related

Default regex django uses to validate email

recently, I started playing with Django and created a custom form for user registration. In that form to create the field for email I use something like
email = forms.EmailField()
I observed that address such as a#a.a is considered invalid by the form. Of course this is a nonsense email address. Nonetheless, I wonder how does Django checks for validity.
I found some topics on the net discussing how to check for validity of an email address but all of them were providing some custom ways. Couldn't find something talking about the django default validator.
In their docs on the email filed they specify
Uses EmailValidator to validate that the given value is a valid email address, using a moderately complex regular expression.
However that's not very specific so I decided to ask here.
For anyone also interested in this, I would suggest looking up the implementation (django.core.validators) as was kindly suggested by iklinac in the comments.
In it, there is not just the source but also mentions about standards that were used to derive regexes that check if domain and literal have valid format.
us should check docs here https://www.geeksforgeeks.org/emailfield-django-forms/#:~:text=EmailField%20in%20Django%20Forms%20is,max_length%20and%20min_length%20are%20provided.
if u wanna check validation use clean function like this :
from django.forms.fields import EmailField
email = EmailField()
my_email = "a#a.a"
print(email.clean(my_email))
if your email is valid then this func return value else return validation error

Cannot obtain user by using filter with username and password

I am using the following code
email = validated_data["login"]
password = validated_data["password"]
user_obj = User.objects.filter(Q(email__exact=email) & Q(password__exact=password))
I changed the password from admin however no user is returned. However if I remove the password check then I get a user object back.The object that I get back if I remove the Q(password__exact=password) condition has _password field as None. This code has been working fine for a while but today it is not returning back the object. Am I missing something here ? I verified that I am receiving the correct username and password from the client.I also tried accessing the admin with that username and password (The account has staff status) and I was able to log in. So the password is correct but for some reason I cant obtain that user by filtering. ? What might I be doing wrong ?
password isn't stored in plain text, but as a hash (and a little more). Get the user by username and check the password:
# assumes there can be only one
user = User.objects.get(email=email)
# this checks the plaintext password against the stored hash
correct = user.check_password(password)
BTW, you don't need Q objects for logical AND. filter(email__exact=email, password__exact=password) would suffice, even though it doesn't make much sense, in this case.
it is because Django doesn't stores password as the simple text they are hashed, you cant perform a password__exact on that it will return none every time unless you are getting the same hash password = validated_data["password"] here

Django 1.9 "Common Password Validator" - Strange Behaviour

I'm attempting to replace the built in common-passwords.txt.gz file, which supposedly contains the top 1,000 common passwords, with my own identical version which contains the top 10,000 common passwords for my country, but I've encountered some rather strange behaviour.
Firstly I directly substituted Django's common-passwords.txt.gz file (4KB) with my own containing my .txt file with the same utf-8 encoding as Django (which comes in at 34KB), then restarted the test server. When changing a users password to "password" it does not raise the expected error as it does with Django's common password file.
The first line of both the built in password list and my new one begins 123456password12345678qwerty123456789... so it clearly should do.
When I append a few extra passwords to their common-passwords file it appears to work as it should and raise an error if I try to use them as passwords, so I don't think that it's cached somewhere or anything like that.
Is there some kind of built in file size limit for the common password list or for the gzip.open(password_list_path).read().decode('utf-8').splitlines() function?
Secondly, trying to figure out the above led me to a strange bug. Using Django's built in common-passwords.txt.gz (of which the first line starts 123456password12345678qwerty123456789...) successfully raises a validation error for "password" and "password1", but not for "password12" or "password123"!
As I read it, the Django validation code basically checks if the submitted password is in each line from the common passwords file, and I cannot find any code that exempts passwords above a certain length from the validation. Am I missing something or is this a bug?
The "common password validation" function in Django 1.9 is found in \venv\Lib\site-packages\django\contrib\auth\password_validation.py, the relevant class is below:
class CommonPasswordValidator(object):
"""
Validate whether the password is a common password.
The password is rejected if it occurs in a provided list, which may be gzipped.
The list Django ships with contains 1000 common passwords, created by Mark Burnett:
https://xato.net/passwords/more-top-worst-passwords/
"""
DEFAULT_PASSWORD_LIST_PATH = os.path.join(
os.path.dirname(os.path.realpath(upath(__file__))), 'common-passwords.txt.gz'
)
def __init__(self, password_list_path=DEFAULT_PASSWORD_LIST_PATH):
try:
common_passwords_lines = gzip.open(password_list_path).read().decode('utf-8').splitlines()
except IOError:
with open(password_list_path) as f:
common_passwords_lines = f.readlines()
self.passwords = {p.strip() for p in common_passwords_lines}
def validate(self, password, user=None):
if password.lower().strip() in self.passwords:
raise ValidationError(
_("This password is too common (it would be trivial to crack!)"),
code='password_too_common',
)
def get_help_text(self):
return _("Your password can't be a commonly used password.")
Finally got to the bottom of this!
There is some kind of invisible unrendered character in-between the passwords contained in Django's built in common passwords validation file, this explains both issues I encountered.
I changed my top 10k common passwords file to have the usual newline characters between them instead and now it all works great! Even though there are now 10 times as many passwords for it to compare against it still runs pretty much instantaneously!
I've uploaded my 10,000 most common passwords file to github for any future people who encounter this issue or who just want to improve Django's built-in common password validation: https://github.com/timboss/Django-Common-Password-Validation/

Django testing - fail on sending email

I have a simple function in Django 1.4 that results in a mail being sent. This is put in a try/except, just in case the mailing service might be down (which is an external dependency).
Now, I would like to test this exception. I thought this would be simple, by overriding some email settings (like settings.EMAIL_HOST or settings.EMAIL_BACKEND) but Django test framework does not cause send_mail() to throw an error even if the backend is configured with jibberish...
So the question is: How do I make send_mail() throw an error in my test case?
Thanks!
Answer:
import mock
class MyTestCase(TestCase):
#mock.patch('path.to.your.project.views.send_mail', mock.Mock(side_effect=Exception('Boom!')))
def test_changed_send_mail(self):
I'm not a testing expert, but I think you should use a send_mail mock that raise the exception you want to test.
Probably you could take a look at this stackoverflow question to know more about mocking in Django.
Yes, the test suite does not set up the email system. Unfortunately, I don't know of any way t o test the email system.
You shouldn't really be testing send_mail functions as it is a built in. That said, you can validate the data being passed into send_mail by another function. If you know the domain of expected inputs, you can verify and throw (raise) an exception of your own.
https://docs.djangoproject.com/en/1.4/topics/email/#the-emailmessage-class
This is the more django'y way to send email:
# attempt to send out a welcome email
try :
t = loader.get_template('email_templates/membership/register.html')
c = Context({
'user' : user,
'site' : Site.objects.get(id=settings.SITE_ID)
})
msg = EmailMessage('Welcome to Site', t.render(c), settings.EMAIL_HOST_USER, to=[user.email,])
msg.content_subtype = "html"
msg.send()
except :
messages.info(request, _(u'Our email servers are encountering technical issues, you may not recieve a welcome email.'))
in my settings.py:
import os
EMAIL_HOST_USER = os.environ['SENDGRID_USERNAME']
EMAIL_HOST= 'smtp.sendgrid.net'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_PASSWORD = os.environ['SENDGRID_PASSWORD']
note: SENDGRID_USERNAME and SENDGRID_PASSWORD are added as env variables by a heroku addon, you may have the actual credentials embedded in your settings file which is fine.
so why isnt your email throwing exceptions? https://docs.djangoproject.com/en/1.4/topics/email/#django.core.mail.get_connection
The fail_silently argument controls how the backend should handle errors. If fail_silently is True, exceptions during the email sending process will be silently ignored.
The patch documentation at http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch explains in detail how to go about this.
I noted that the answer indeed was in the question but to help others understand where the catch is, the link above will prove to be quite insightful.
If you want to patch an object reference in class b.py, ensure that your patch call mocks the object reference in b.py rather than in a.py from where the object is imported. This can be a stumbling block for java developers who are getting used to the whole idea of functions being 1st class citizens.

Django-registation : Error on activate new user

I'm using Django registration inside my project on a development server.
When I register a new user, I use EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' to get the activation link.
When I try to put the activation link into the web browser, I have an error, and the account is not activated.
It is said :
Thank you.
This function is used to generate the key.
def create_profile(self, user):
"""
Create a ``RegistrationProfile`` for a given
``User``, and return the ``RegistrationProfile``.
The activation key for the ``RegistrationProfile`` will be a
SHA1 hash, generated from a combination of the ``User``'s
username and a random salt.
"""
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
username = user.username
if isinstance(username, unicode):
username = username.encode('utf-8')
activation_key = hashlib.sha1(salt+username).hexdigest()
return self.create(user=user,
activation_key=activation_key)
I received that mail. But I use EMAIL_BACKEND'django.core.mail.backends.filebased.EmailBackend'.
I think the problem comes from here. But I can't test in production server.
I solved the problem actually It's because I generate the email to send inside a file thanks to the file email backends provided by django for development purpose. Inside this file, when there is a carriage return, it adds an = characters. And this is the case with the link to active the account.
You shouldn't have a = character in your activation key.
Although sergzach's answer will work, I'd be more interested in finding out why that = is there in the first place.
django-registration usually generates the key as follows:
salt = sha.new(str(random.random())).hexdigest()[:5]
activation_key = sha.new(salt+user.username).hexdigest()
Where are you generating yours?
The character '=' is not in the range of \w+. Use [\w=]+ instead of \w+.
Replace ?P<activation_key>\w+ to ?P<activation_key>[\w=]+