refactor: Extract zerver/lib/email_validation.py.

This commit is contained in:
Steve Howell
2020-03-05 12:54:37 +00:00
committed by Tim Abbott
parent 30b43605c3
commit 4f5b07a7e6
9 changed files with 100 additions and 81 deletions

View File

@@ -16,6 +16,7 @@ from jinja2 import Markup as mark_safe
from zerver.lib.actions import do_change_password, email_not_system_bot, \ from zerver.lib.actions import do_change_password, email_not_system_bot, \
validate_email_not_already_in_realm validate_email_not_already_in_realm
from zerver.lib.email_validation import email_allowed_for_realm
from zerver.lib.name_restrictions import is_reserved_subdomain, is_disposable_domain from zerver.lib.name_restrictions import is_reserved_subdomain, is_disposable_domain
from zerver.lib.rate_limiter import RateLimited, get_rate_limit_result_from_request, \ from zerver.lib.rate_limiter import RateLimited, get_rate_limit_result_from_request, \
RateLimitedObject, rate_limit_entity RateLimitedObject, rate_limit_entity
@@ -25,7 +26,7 @@ from zerver.lib.subdomains import get_subdomain, is_root_domain_available
from zerver.lib.users import check_full_name from zerver.lib.users import check_full_name
from zerver.models import Realm, get_user_by_delivery_email, UserProfile, get_realm, \ from zerver.models import Realm, get_user_by_delivery_email, UserProfile, get_realm, \
email_to_domain, \ email_to_domain, \
email_allowed_for_realm, DisposableEmailError, DomainNotAllowedForRealmError, \ DisposableEmailError, DomainNotAllowedForRealmError, \
EmailContainsPlusError EmailContainsPlusError
from zproject.backends import email_auth_enabled, email_belongs_to_ldap, check_password_strength from zproject.backends import email_auth_enabled, email_belongs_to_ldap, check_password_strength

View File

@@ -100,7 +100,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
ScheduledEmail, MAX_TOPIC_NAME_LENGTH, \ ScheduledEmail, MAX_TOPIC_NAME_LENGTH, \
MAX_MESSAGE_LENGTH, get_client, get_stream, \ MAX_MESSAGE_LENGTH, get_client, get_stream, \
get_user_profile_by_id, PreregistrationUser, \ get_user_profile_by_id, PreregistrationUser, \
get_realm_email_validator, email_to_username, \ email_to_username, \
get_user_by_delivery_email, get_stream_cache_key, active_non_guest_user_ids, \ get_user_by_delivery_email, get_stream_cache_key, active_non_guest_user_ids, \
UserActivityInterval, active_user_ids, get_active_streams, \ UserActivityInterval, active_user_ids, get_active_streams, \
realm_filters_for_realm, RealmFilter, stream_name_in_use, \ realm_filters_for_realm, RealmFilter, stream_name_in_use, \
@@ -117,6 +117,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
from zerver.lib.alert_words import get_alert_word_automaton from zerver.lib.alert_words import get_alert_word_automaton
from zerver.lib.avatar import avatar_url, avatar_url_from_dict from zerver.lib.avatar import avatar_url, avatar_url_from_dict
from zerver.lib.email_validation import get_realm_email_validator
from zerver.lib.stream_recipient import StreamRecipientMap from zerver.lib.stream_recipient import StreamRecipientMap
from zerver.lib.validator import check_widget_content from zerver.lib.validator import check_widget_content
from zerver.lib.widget import do_widget_post_save_actions from zerver.lib.widget import do_widget_post_save_actions

View File

@@ -0,0 +1,86 @@
from typing import Callable
from zerver.lib.name_restrictions import is_disposable_domain
# TODO: Move DisposableEmailError, etc. into here.
from zerver.models import (
email_to_username,
email_to_domain,
DisposableEmailError,
DomainNotAllowedForRealmError,
EmailContainsPlusError,
Realm,
RealmDomain,
)
def validate_disposable(email: str) -> None:
if is_disposable_domain(email_to_domain(email)):
raise DisposableEmailError
def get_realm_email_validator(realm: Realm) -> Callable[[str], None]:
if not realm.emails_restricted_to_domains:
# Should we also do '+' check for non-resticted realms?
if realm.disallow_disposable_email_addresses:
return validate_disposable
# allow any email through
return lambda email: None
'''
RESTRICTIVE REALMS:
Some realms only allow emails within a set
of domains that are configured in RealmDomain.
We get the set of domains up front so that
folks can validate multiple emails without
multiple round trips to the database.
'''
query = RealmDomain.objects.filter(realm=realm)
rows = list(query.values('allow_subdomains', 'domain'))
allowed_domains = {
r['domain'] for r in rows
}
allowed_subdomains = {
r['domain'] for r in rows
if r['allow_subdomains']
}
def validate(email: str) -> None:
'''
We don't have to do a "disposable" check for restricted
domains, since the realm is already giving us
a small whitelist.
'''
if '+' in email_to_username(email):
raise EmailContainsPlusError
domain = email_to_domain(email)
if domain in allowed_domains:
return
while len(domain) > 0:
subdomain, sep, domain = domain.partition('.')
if domain in allowed_subdomains:
return
raise DomainNotAllowedForRealmError
return validate
# Is a user with the given email address allowed to be in the given realm?
# (This function does not check whether the user has been invited to the realm.
# So for invite-only realms, this is the test for whether a user can be invited,
# not whether the user can sign up currently.)
def email_allowed_for_realm(email: str, realm: Realm) -> None:
'''
Avoid calling this in a loop!
Instead, call get_realm_email_validator()
outside of the loop.
'''
get_realm_email_validator(realm)(email)

View File

@@ -5,8 +5,8 @@ from django.core.management.base import CommandError
from confirmation.models import Confirmation, create_confirmation_link from confirmation.models import Confirmation, create_confirmation_link
from zerver.lib.management import ZulipBaseCommand from zerver.lib.management import ZulipBaseCommand
from zerver.models import DomainNotAllowedForRealmError, PreregistrationUser, \ from zerver.models import DomainNotAllowedForRealmError, PreregistrationUser
email_allowed_for_realm from zerver.lib.email_validation import email_allowed_for_realm
class Command(ZulipBaseCommand): class Command(ZulipBaseCommand):

View File

@@ -30,7 +30,6 @@ from zerver.lib import cache
from zerver.lib.validator import check_int, \ from zerver.lib.validator import check_int, \
check_short_string, check_long_string, validate_choice_field, check_date, \ check_short_string, check_long_string, validate_choice_field, check_date, \
check_url, check_list check_url, check_list
from zerver.lib.name_restrictions import is_disposable_domain
from zerver.lib.types import Validator, ExtendedValidator, \ from zerver.lib.types import Validator, ExtendedValidator, \
ProfileDataElement, ProfileData, RealmUserValidator, \ ProfileDataElement, ProfileData, RealmUserValidator, \
ExtendedFieldElement, UserFieldElement, FieldElement, \ ExtendedFieldElement, UserFieldElement, FieldElement, \
@@ -570,78 +569,6 @@ class DisposableEmailError(Exception):
class EmailContainsPlusError(Exception): class EmailContainsPlusError(Exception):
pass pass
# Is a user with the given email address allowed to be in the given realm?
# (This function does not check whether the user has been invited to the realm.
# So for invite-only realms, this is the test for whether a user can be invited,
# not whether the user can sign up currently.)
def email_allowed_for_realm(email: str, realm: Realm) -> None:
'''
Avoid calling this in a loop!
Instead, call get_realm_email_validator()
outside of the loop.
'''
get_realm_email_validator(realm)(email)
def validate_disposable(email: str) -> None:
if is_disposable_domain(email_to_domain(email)):
raise DisposableEmailError
def get_realm_email_validator(realm: Realm) -> Callable[[str], None]:
if not realm.emails_restricted_to_domains:
# Should we also do '+' check for non-resticted realms?
if realm.disallow_disposable_email_addresses:
return validate_disposable
# allow any email through
return lambda email: None
'''
RESTRICTIVE REALMS:
Some realms only allow emails within a set
of domains that are configured in RealmDomain.
We get the set of domains up front so that
folks can validate multiple emails without
multiple round trips to the database.
'''
query = RealmDomain.objects.filter(realm=realm)
rows = list(query.values('allow_subdomains', 'domain'))
allowed_domains = {
r['domain'] for r in rows
}
allowed_subdomains = {
r['domain'] for r in rows
if r['allow_subdomains']
}
def validate(email: str) -> None:
'''
We don't have to do a "disposable" check for restricted
domains, since the realm is already giving us
a small whitelist.
'''
if '+' in email_to_username(email):
raise EmailContainsPlusError
domain = email_to_domain(email)
if domain in allowed_domains:
return
while len(domain) > 0:
subdomain, sep, domain = domain.partition('.')
if domain in allowed_subdomains:
return
raise DomainNotAllowedForRealmError
return validate
def get_realm_domains(realm: Realm) -> List[Dict[str, str]]: def get_realm_domains(realm: Realm) -> List[Dict[str, str]]:
return list(realm.realmdomain_set.values('domain', 'allow_subdomains')) return list(realm.realmdomain_set.values('domain', 'allow_subdomains'))

View File

@@ -6,9 +6,10 @@ from django.db.utils import IntegrityError
from zerver.lib.actions import do_change_is_admin, \ from zerver.lib.actions import do_change_is_admin, \
do_change_realm_domain, do_create_realm, \ do_change_realm_domain, do_create_realm, \
do_remove_realm_domain do_remove_realm_domain
from zerver.lib.email_validation import email_allowed_for_realm
from zerver.lib.domains import validate_domain from zerver.lib.domains import validate_domain
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.models import email_allowed_for_realm, get_realm, \ from zerver.models import get_realm, \
RealmDomain, DomainNotAllowedForRealmError RealmDomain, DomainNotAllowedForRealmError
import ujson import ujson

View File

@@ -11,10 +11,11 @@ from django.core.exceptions import ValidationError
from django.core import validators from django.core import validators
from zerver.context_processors import get_realm_from_request, login_context from zerver.context_processors import get_realm_from_request, login_context
from zerver.models import UserProfile, Realm, Stream, MultiuseInvite, \ from zerver.models import UserProfile, Realm, Stream, MultiuseInvite, \
name_changes_disabled, email_to_username, email_allowed_for_realm, \ name_changes_disabled, email_to_username, \
get_realm, get_user_by_delivery_email, get_default_stream_groups, DisposableEmailError, \ get_realm, get_user_by_delivery_email, get_default_stream_groups, DisposableEmailError, \
DomainNotAllowedForRealmError, get_source_profile, EmailContainsPlusError, \ DomainNotAllowedForRealmError, get_source_profile, EmailContainsPlusError, \
PreregistrationUser PreregistrationUser
from zerver.lib.email_validation import email_allowed_for_realm
from zerver.lib.send_email import send_email, FromAddress from zerver.lib.send_email import send_email, FromAddress
from zerver.lib.actions import do_change_password, do_change_full_name, \ from zerver.lib.actions import do_change_password, do_change_full_name, \
do_activate_user, do_create_user, do_create_realm, \ do_activate_user, do_create_user, do_create_realm, \

View File

@@ -17,6 +17,7 @@ from zerver.lib.actions import do_change_avatar_fields, do_change_bot_owner, \
do_update_user_custom_profile_data_if_changed, check_remove_custom_profile_field_value do_update_user_custom_profile_data_if_changed, check_remove_custom_profile_field_value
from zerver.lib.avatar import avatar_url, get_gravatar_url from zerver.lib.avatar import avatar_url, get_gravatar_url
from zerver.lib.bot_config import set_bot_config from zerver.lib.bot_config import set_bot_config
from zerver.lib.email_validation import email_allowed_for_realm
from zerver.lib.exceptions import CannotDeactivateLastUserError from zerver.lib.exceptions import CannotDeactivateLastUserError
from zerver.lib.integrations import EMBEDDED_BOTS from zerver.lib.integrations import EMBEDDED_BOTS
from zerver.lib.request import has_request_variables, REQ from zerver.lib.request import has_request_variables, REQ
@@ -29,7 +30,7 @@ from zerver.lib.users import check_valid_bot_type, check_bot_creation_policy, \
access_bot_by_id, add_service, access_user_by_id, check_bot_name_available, \ access_bot_by_id, add_service, access_user_by_id, check_bot_name_available, \
validate_user_custom_profile_data, get_raw_user_data, get_api_key validate_user_custom_profile_data, get_raw_user_data, get_api_key
from zerver.lib.utils import generate_api_key, generate_random_token from zerver.lib.utils import generate_api_key, generate_random_token
from zerver.models import UserProfile, Stream, Message, email_allowed_for_realm, \ from zerver.models import UserProfile, Stream, Message, \
get_user_by_delivery_email, Service, get_user_including_cross_realm, \ get_user_by_delivery_email, Service, get_user_including_cross_realm, \
DomainNotAllowedForRealmError, DisposableEmailError, get_user_profile_by_id_in_realm, \ DomainNotAllowedForRealmError, DisposableEmailError, get_user_profile_by_id_in_realm, \
EmailContainsPlusError, get_user_by_id_in_realm_including_cross_realm, Realm, \ EmailContainsPlusError, get_user_by_id_in_realm_including_cross_realm, Realm, \

View File

@@ -54,6 +54,7 @@ from zerver.lib.actions import do_create_user, do_reactivate_user, do_deactivate
from zerver.lib.avatar import is_avatar_new, avatar_url from zerver.lib.avatar import is_avatar_new, avatar_url
from zerver.lib.avatar_hash import user_avatar_content_hash from zerver.lib.avatar_hash import user_avatar_content_hash
from zerver.lib.dev_ldap_directory import init_fakeldap from zerver.lib.dev_ldap_directory import init_fakeldap
from zerver.lib.email_validation import email_allowed_for_realm
from zerver.lib.mobile_auth_otp import is_valid_otp from zerver.lib.mobile_auth_otp import is_valid_otp
from zerver.lib.rate_limiter import clear_history, rate_limit_request_by_entity, RateLimitedObject from zerver.lib.rate_limiter import clear_history, rate_limit_request_by_entity, RateLimitedObject
from zerver.lib.request import JsonableError from zerver.lib.request import JsonableError
@@ -61,7 +62,7 @@ from zerver.lib.users import check_full_name, validate_user_custom_profile_field
from zerver.lib.redis_utils import get_redis_client, get_dict_from_redis, put_dict_in_redis from zerver.lib.redis_utils import get_redis_client, get_dict_from_redis, put_dict_in_redis
from zerver.models import CustomProfileField, DisposableEmailError, DomainNotAllowedForRealmError, \ from zerver.models import CustomProfileField, DisposableEmailError, DomainNotAllowedForRealmError, \
EmailContainsPlusError, PreregistrationUser, UserProfile, Realm, custom_profile_fields_for_realm, \ EmailContainsPlusError, PreregistrationUser, UserProfile, Realm, custom_profile_fields_for_realm, \
email_allowed_for_realm, get_user_profile_by_id, remote_user_to_email, \ get_user_profile_by_id, remote_user_to_email, \
email_to_username, get_realm, get_user_by_delivery_email, supported_auth_backends email_to_username, get_realm, get_user_by_delivery_email, supported_auth_backends
redis_client = get_redis_client() redis_client = get_redis_client()