mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 22:13:26 +00:00
refactor: Extract zerver/lib/email_validation.py.
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
86
zerver/lib/email_validation.py
Normal file
86
zerver/lib/email_validation.py
Normal 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)
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'))
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, \
|
||||||
|
|||||||
@@ -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, \
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user