zilencer: Disambiguate no MX records from the domain not existing.

This switches to dnspython, since it offers the higher-level
`resolve_name` method to look up both A and AAAA records, and set
timeouts.
This commit is contained in:
Alex Vandiver 2024-05-23 18:31:31 +00:00 committed by Tim Abbott
parent 9388805471
commit e5a0b3b3c5
2 changed files with 32 additions and 18 deletions

View File

@ -8,7 +8,6 @@ from typing import Any, Dict, Iterator, List, Mapping, Optional, Tuple, Union
from unittest import mock, skipUnless from unittest import mock, skipUnless
import aioapns import aioapns
import DNS
import orjson import orjson
import responses import responses
import time_machine import time_machine
@ -19,6 +18,7 @@ from django.http.response import ResponseHeaders
from django.test import override_settings from django.test import override_settings
from django.utils.crypto import get_random_string from django.utils.crypto import get_random_string
from django.utils.timezone import now from django.utils.timezone import now
from dns.resolver import NoAnswer as DNSNoAnswer
from requests.exceptions import ConnectionError from requests.exceptions import ConnectionError
from requests.models import PreparedRequest from requests.models import PreparedRequest
from typing_extensions import override from typing_extensions import override
@ -5247,17 +5247,24 @@ class PushBouncerSignupTest(ZulipTestCase):
self.assert_json_error(result, "Please use your real email address.") self.assert_json_error(result, "Please use your real email address.")
request["contact_email"] = "admin@zulip.com" request["contact_email"] = "admin@zulip.com"
with mock.patch("DNS.mxlookup", side_effect=DNS.Base.ServerError("test", 1)): with mock.patch("zilencer.views.dns_resolver.Resolver") as resolver:
resolver.return_value.resolve.side_effect = DNSNoAnswer
resolver.return_value.resolve_name.return_value = ["whee"]
result = self.client_post("/api/v1/remotes/server/register", request) result = self.client_post("/api/v1/remotes/server/register", request)
self.assert_json_error( self.assert_json_error(
result, "zulip.com does not exist or is not configured to accept email." result, "zulip.com is invalid because it does not have any MX records"
) )
with mock.patch("DNS.mxlookup", return_value=[]): with mock.patch("zilencer.views.dns_resolver.Resolver") as resolver:
resolver.return_value.resolve.side_effect = DNSNoAnswer
resolver.return_value.resolve_name.side_effect = DNSNoAnswer
result = self.client_post("/api/v1/remotes/server/register", request) result = self.client_post("/api/v1/remotes/server/register", request)
self.assert_json_error( self.assert_json_error(result, "zulip.com does not exist")
result, "zulip.com does not exist or is not configured to accept email."
) with mock.patch("zilencer.views.dns_resolver.Resolver") as resolver:
resolver.return_value.resolve.return_value = ["whee"]
result = self.client_post("/api/v1/remotes/server/register", request)
self.assert_json_success(result)
class TestUserPushIdentityCompat(ZulipTestCase): class TestUserPushIdentityCompat(ZulipTestCase):

View File

@ -5,7 +5,6 @@ from email.headerregistry import Address
from typing import Any, Dict, List, Optional, Type, TypedDict, TypeVar, Union from typing import Any, Dict, List, Optional, Type, TypedDict, TypeVar, Union
from uuid import UUID from uuid import UUID
import DNS
import orjson import orjson
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
@ -18,6 +17,8 @@ from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.utils.translation import gettext as err_ from django.utils.translation import gettext as err_
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from dns import resolver as dns_resolver
from dns.exception import DNSException
from pydantic import BaseModel, ConfigDict, Json, StringConstraints from pydantic import BaseModel, ConfigDict, Json, StringConstraints
from pydantic.functional_validators import AfterValidator from pydantic.functional_validators import AfterValidator
from typing_extensions import Annotated from typing_extensions import Annotated
@ -170,19 +171,25 @@ def register_remote_server(
raise JsonableError(_("Invalid email address.")) raise JsonableError(_("Invalid email address."))
# Check if the domain has an MX record # Check if the domain has an MX record
resolver = dns_resolver.Resolver()
resolver.timeout = 3
dns_mx_check_successful = False
try: try:
records = DNS.mxlookup(contact_email_domain) if resolver.resolve(contact_email_domain, "MX"):
dns_mx_check_successful = True dns_mx_check_successful = True
if not records: except DNSException:
dns_mx_check_successful = False pass
except DNS.Base.ServerError:
dns_mx_check_successful = False
if not dns_mx_check_successful: if not dns_mx_check_successful:
raise JsonableError( # Check if the A/AAAA exist, for better error reporting
_("{domain} does not exist or is not configured to accept email.").format( try:
domain=contact_email_domain resolver.resolve_name(contact_email_domain)
raise JsonableError(
_("{domain} is invalid because it does not have any MX records").format(
domain=contact_email_domain
)
) )
) except DNSException:
raise JsonableError(_("{domain} does not exist").format(domain=contact_email_domain))
try: try:
validate_uuid(zulip_org_id) validate_uuid(zulip_org_id)