From 18f94de07fafb14a8dd25247405b4a6a03ba0c6e Mon Sep 17 00:00:00 2001 From: Tim Abbott Date: Thu, 18 Jul 2013 12:48:56 -0400 Subject: [PATCH] Use standard functions for parsing/validating email addresses. This adds two new functions for parsing out the domain and username from an email address, and switches our backend to use them and django.core.validators.valid_email() rather than custom parsing and raw email.split("@"). (imported from commit 3d6e997d66908811eccb5f82f2f7fe349b40f238) --- zephyr/lib/actions.py | 11 +++++++---- zephyr/lib/bulk_create.py | 4 ++-- zephyr/management/commands/create_user.py | 4 ++-- zephyr/management/commands/populate_db.py | 9 +++++---- zephyr/models.py | 12 ++++++++++++ zephyr/tests.py | 4 ++-- zephyr/views.py | 22 ++++++++++++---------- 7 files changed, 42 insertions(+), 24 deletions(-) diff --git a/zephyr/lib/actions.py b/zephyr/lib/actions.py index 4317985ed2..d42b11995a 100644 --- a/zephyr/lib/actions.py +++ b/zephyr/lib/actions.py @@ -9,7 +9,8 @@ from zephyr.models import Realm, Stream, UserProfile, UserActivity, \ DefaultStream, UserPresence, MAX_SUBJECT_LENGTH, \ MAX_MESSAGE_LENGTH, get_client, get_stream, get_recipient, get_huddle, \ get_user_profile_by_id, PreregistrationUser, get_display_recipient, \ - to_dict_cache_key, get_realm, stringify_message_dict, bulk_get_recipients + to_dict_cache_key, get_realm, stringify_message_dict, bulk_get_recipients, \ + email_to_domain, email_to_username from django.db import transaction, IntegrityError from django.db.models import F, Q from django.core.exceptions import ValidationError @@ -190,7 +191,7 @@ def create_mit_user_if_needed(realm, email): try: # Forge a user for this person return create_user(email, initial_password(email), realm, - compute_mit_user_fullname(email), email.split("@")[0], + compute_mit_user_fullname(email), email_to_username(email), active=False) except IntegrityError: # Unless we raced with another thread doing the same @@ -1358,12 +1359,14 @@ def do_invite_users(user_profile, invitee_emails, streams): if email == '': continue - if not validators.email_re.match(email): + try: + validators.validate_email(email) + except ValidationError: errors.append((email, "Invalid address.")) continue if user_profile.realm.restricted_to_domain and \ - email.split('@', 1)[-1].lower() != user_profile.realm.domain.lower(): + email_to_domain(email).lower() != user_profile.realm.domain.lower(): errors.append((email, "Outside your domain.")) continue diff --git a/zephyr/lib/bulk_create.py b/zephyr/lib/bulk_create.py index 41fa7ef88d..b3f2f53fc3 100644 --- a/zephyr/lib/bulk_create.py +++ b/zephyr/lib/bulk_create.py @@ -2,7 +2,7 @@ from __future__ import absolute_import from zephyr.lib.initial_password import initial_password from zephyr.models import Realm, Stream, UserProfile, Huddle, \ - Subscription, Recipient, Client, get_huddle_hash + Subscription, Recipient, Client, get_huddle_hash, email_to_domain from zephyr.lib.create_user import create_user_profile def bulk_create_realms(realm_list): @@ -31,7 +31,7 @@ def bulk_create_users(realms, users_raw): # Now create user_profiles profiles_to_create = [] for (email, full_name, short_name, active) in users: - domain = email.split('@')[1] + domain = email_to_domain(email) profile = create_user_profile(realms[domain], email, initial_password(email), active, False, full_name, short_name, None) diff --git a/zephyr/management/commands/create_user.py b/zephyr/management/commands/create_user.py index 461138308c..8d8d751a39 100644 --- a/zephyr/management/commands/create_user.py +++ b/zephyr/management/commands/create_user.py @@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError from django.db.utils import IntegrityError from django.core import validators -from zephyr.models import Realm +from zephyr.models import Realm, email_to_username from zephyr.lib.actions import do_create_user from zephyr.views import notify_new_user from zephyr.lib.initial_password import initial_password @@ -67,7 +67,7 @@ parameters, or specify no parameters for interactive user creation.""") try: notify_new_user(do_create_user(email, initial_password(email), - realm, full_name, email.split('@')[0]), + realm, full_name, email_to_username(email)), internal=True) except IntegrityError: raise CommandError("User already exists.") diff --git a/zephyr/management/commands/populate_db.py b/zephyr/management/commands/populate_db.py index 8e2a754524..13512e1d16 100644 --- a/zephyr/management/commands/populate_db.py +++ b/zephyr/management/commands/populate_db.py @@ -6,7 +6,8 @@ from django.utils.timezone import now from django.contrib.sites.models import Site from zephyr.models import Message, UserProfile, Stream, Recipient, Client, \ Subscription, Huddle, get_huddle, Realm, UserMessage, \ - get_huddle_hash, clear_database, get_client, get_user_profile_by_id + get_huddle_hash, clear_database, get_client, get_user_profile_by_id, \ + email_to_domain, email_to_username from zephyr.lib.actions import do_send_message, set_default_streams, \ do_activate_user, do_deactivate, do_change_password from zephyr.lib.parallel import run_parallel @@ -32,7 +33,7 @@ settings.TORNADO_SERVER = None def create_users(realms, name_list): user_set = set() for full_name, email in name_list: - (short_name, domain) = email.split("@") + short_name = email_to_username(email) user_set.add((email, full_name, short_name, True)) bulk_create_users(realms, user_set) @@ -336,7 +337,7 @@ def restore_saved_messages(): sender_email = old_message["sender_email"] - domain = sender_email.split('@')[1] + domain = email_to_domain(sender_email) realm_set.add(domain) if old_message["sender_email"] not in email_set: @@ -447,7 +448,7 @@ def restore_saved_messages(): message = Message() sender_email = old_message["sender_email"] - domain = sender_email.split('@')[1] + domain = email_to_domain(sender_email) realm = realms[domain] message.sender = users[sender_email] diff --git a/zephyr/models.py b/zephyr/models.py index 9a7a986f94..94a53a4e7a 100644 --- a/zephyr/models.py +++ b/zephyr/models.py @@ -72,6 +72,18 @@ class Realm(models.Model): ('administer', "Administer a realm"), ) +# These functions should only be used on email addresses that have +# been validated via django.core.validators.validate_email +# +# Note that we need to use some care, since can you have multiple @-signs; e.g. +# "tabbott@test"@humbughq.com +# is valid email address +def email_to_username(email): + return "@".join(email.split("@")[:-1]) + +def email_to_domain(email): + return email.split("@")[-1] + class UserProfile(AbstractBaseUser, PermissionsMixin): # Fields from models.AbstractUser minus last_name and first_name, # which we don't use; email is modified to make it indexed and unique. diff --git a/zephyr/tests.py b/zephyr/tests.py index 47ccabd742..07099e0570 100644 --- a/zephyr/tests.py +++ b/zephyr/tests.py @@ -9,7 +9,7 @@ from django.db.models import Q from zephyr.models import Message, UserProfile, Stream, Recipient, Subscription, \ get_display_recipient, Realm, Client, \ PreregistrationUser, UserMessage, \ - get_user_profile_by_email + get_user_profile_by_email, email_to_domain from zephyr.tornadoviews import json_get_updates, api_get_messages from zephyr.decorator import RespondAsynchronously, RequestVariableConversionError, profiled from zephyr.lib.initial_password import initial_password @@ -2558,7 +2558,7 @@ class UserPresenceTests(AuthedTestCase): self.assertEqual(json['presences'][email][client]['status'], 'idle') # We only want @humbughq.com emails for email in json['presences'].keys(): - self.assertEqual(email.split('@')[1], 'humbughq.com') + self.assertEqual(email_to_domain(email), 'humbughq.com') class UnreadCountTests(AuthedTestCase): diff --git a/zephyr/views.py b/zephyr/views.py index 49945dbf2f..c383b0cd8f 100644 --- a/zephyr/views.py +++ b/zephyr/views.py @@ -20,7 +20,8 @@ from zephyr.models import Message, UserProfile, Stream, Subscription, \ PreregistrationUser, get_client, MitUser, UserActivity, \ MAX_SUBJECT_LENGTH, get_stream, bulk_get_streams, UserPresence, \ get_recipient, valid_stream_name, to_dict_cache_key, to_dict_cache_key_id, \ - extract_message_dict, stringify_message_dict, parse_usermessage_flags + extract_message_dict, stringify_message_dict, parse_usermessage_flags, \ + email_to_domain, email_to_username from zephyr.lib.actions import do_remove_subscription, bulk_remove_subscriptions, \ do_change_password, create_mit_user_if_needed, do_change_full_name, \ do_change_enable_desktop_notifications, do_change_enter_sends, do_change_enable_sounds, \ @@ -233,6 +234,7 @@ def accounts_register(request): email = prereg_user.email mit_beta_user = isinstance(confirmation.content_object, MitUser) + validators.validate_email(email) # If someone invited you, you are joining their realm regardless # of your e-mail address. # @@ -240,7 +242,7 @@ def accounts_register(request): if not mit_beta_user and prereg_user.referred_by: domain = prereg_user.referred_by.realm.domain else: - domain = email.split('@')[-1] + domain = email_to_domain(email) try: if mit_beta_user: @@ -259,7 +261,7 @@ def accounts_register(request): if form.is_valid(): password = form.cleaned_data['password'] full_name = form.cleaned_data['full_name'] - short_name = email.split('@')[0] + short_name = email_to_username(email) (realm, _) = Realm.objects.get_or_create(domain=domain) first_in_realm = len(UserProfile.objects.filter(realm=realm)) == 0 @@ -328,7 +330,7 @@ def accounts_register(request): @login_required(login_url = settings.HOME_NOT_LOGGED_IN) def accounts_accept_terms(request): email = request.user.email - company_name = email.split('@')[-1] + domain = email_to_domain(email) if request.method == "POST": form = ToSForm(request.POST) if form.is_valid(): @@ -347,7 +349,7 @@ def accounts_accept_terms(request): else: form = ToSForm() return render_to_response('zephyr/accounts_accept_terms.html', - { 'form': form, 'company_name': company_name, 'email': email }, + { 'form': form, 'company_name': domain, 'email': email }, context_instance=RequestContext(request)) def api_endpoint_docs(request): @@ -1012,14 +1014,14 @@ def mit_to_mit(user_profile, email): # We have to handle this specially, inferring the domain from the # e-mail address, because the recipient may not existing in Humbug # and we may need to make a stub MIT user on the fly. - if not validators.email_re.match(email): + try: + validators.validate_email(email) + except ValidationError: return False - if user_profile.realm.domain != "mit.edu": - return False + domain = email_to_domain(email) - domain = email.split("@", 1)[1] - return user_profile.realm.domain == domain + return user_profile.realm.domain == "mit.edu" and domain == "mit.edu" def create_mirrored_message_users(request, user_profile, recipients): if "sender" not in request.POST: