mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 05:53:43 +00:00
This has two goals:
- sets up a future commit to bulk-validate
emails
- the extracted function is more simple,
since it just has errors, and no codes
or deactivated flags
This commit leaves us in a somewhat funny
intermediate state where we have
`action.validate_email` being a glorified
two-line function with strange parameters,
but subsequent commits will clean this up:
- we will eliminate validate_email
- we will move most of the guts of its
other callee to lib/email_validation.py
To be clear, the code is correct here, just
kinda in an ugly, temporarily-disorganized
intermediate state.
111 lines
3.2 KiB
Python
111 lines
3.2 KiB
Python
from typing import Callable, Optional
|
|
|
|
from django.core import validators
|
|
from django.core.exceptions import ValidationError
|
|
from django.utils.translation import ugettext as _
|
|
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)
|
|
|
|
def validate_email_is_valid(
|
|
email: str,
|
|
validate_email_allowed_in_realm: Callable[[str], None],
|
|
) -> Optional[str]:
|
|
|
|
try:
|
|
validators.validate_email(email)
|
|
except ValidationError:
|
|
return _("Invalid address.")
|
|
|
|
try:
|
|
validate_email_allowed_in_realm(email)
|
|
except DomainNotAllowedForRealmError:
|
|
return _("Outside your domain.")
|
|
except DisposableEmailError:
|
|
return _("Please use your real email address.")
|
|
except EmailContainsPlusError:
|
|
return _("Email addresses containing + are not allowed.")
|
|
|
|
return None
|