diff --git a/lib/galaxy/dependencies/dev-requirements.txt b/lib/galaxy/dependencies/dev-requirements.txt index d34806c71f3..4c088c7063c 100644 --- a/lib/galaxy/dependencies/dev-requirements.txt +++ b/lib/galaxy/dependencies/dev-requirements.txt @@ -57,6 +57,7 @@ defusedxml==0.7.1; python_version >= "3.6" and python_full_version < "3.0.0" and deprecated==1.2.13; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6" deprecation==2.1.0 dictobj==0.4 +dnspython==2.2.1 ; python_version >= "3.7" and python_version < "3.11" docopt==0.6.2 docutils==0.16; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") ecdsa==0.17.0; python_version >= "2.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" diff --git a/lib/galaxy/dependencies/pinned-requirements.txt b/lib/galaxy/dependencies/pinned-requirements.txt index e8c2435c4f4..915b2a24016 100644 --- a/lib/galaxy/dependencies/pinned-requirements.txt +++ b/lib/galaxy/dependencies/pinned-requirements.txt @@ -47,6 +47,7 @@ decorator==5.1.1; python_version >= "3.6" and python_version < "4" defusedxml==0.7.1; python_version >= "3.0" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.0" deprecation==2.1.0 dictobj==0.4 +dnspython==2.2.1 ; python_version >= "3.7" and python_version < "3.11" docopt==0.6.2 docutils==0.16; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0") ecdsa==0.17.0; python_version >= "2.6" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" diff --git a/lib/galaxy/security/validate_user_input.py b/lib/galaxy/security/validate_user_input.py index 3a5b0666a72..f3bd0a680a8 100644 --- a/lib/galaxy/security/validate_user_input.py +++ b/lib/galaxy/security/validate_user_input.py @@ -6,9 +6,11 @@ user inputs - so these methods do not need to be escaped. """ import logging import re -import socket +import dns.resolver +from dns.exception import DNSException from sqlalchemy import func +from typing_extensions import LiteralString log = logging.getLogger(__name__) @@ -71,7 +73,7 @@ def validate_email(trans, email, user=None, check_dup=True, allow_empty=False, v message = validate_email_str(email) if not message and validate_domain: domain = extract_domain(email) - message = validate_domain_resolves(domain) + message = validate_email_domain_name(domain) if ( not message @@ -97,12 +99,17 @@ def validate_email(trans, email, user=None, check_dup=True, allow_empty=False, v return message -def validate_domain_resolves(domain): +def validate_email_domain_name(domain: str) -> LiteralString: message = "" try: - socket.gethostbyname(domain) - except socket.gaierror: - message = "The email domain cannot be resolved." + dns.resolver.resolve(domain, "MX") + except DNSException: + try: + # Per RFC 5321, try to fall back to the A record (implicit MX) for + # the domain, see https://www.rfc-editor.org/rfc/rfc5321#section-5.1 + dns.resolver.resolve(domain, "A") + except DNSException: + message = "The email domain cannot be resolved." return message diff --git a/packages/data/setup.cfg b/packages/data/setup.cfg index b3dff768814..c1b722c96c9 100644 --- a/packages/data/setup.cfg +++ b/packages/data/setup.cfg @@ -38,6 +38,7 @@ install_requires = bdbag bx-python contextvars; python_version >= "3.6" and python_version < "3.7" + dnspython galaxy-sequence-utils h5py isa-rwval diff --git a/pyproject.toml b/pyproject.toml index 08c7d9102e0..1f305af56a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,7 @@ cloudbridge = "*" circus = "*" cwltool = "==3.1.20211107152837" dictobj = "*" +dnspython = "*" docutils = "!=0.17, !=0.17.1" edam-ontology = "*" fastapi = ">=0.68.2, !=0.69.0, !=0.70.0, !=0.70.1" # https://github.com/tiangolo/fastapi/issues/4041 diff --git a/test/unit/data/security/test_validate_user_input.py b/test/unit/data/security/test_validate_user_input.py index 89862ed2b21..e22104e334e 100644 --- a/test/unit/data/security/test_validate_user_input.py +++ b/test/unit/data/security/test_validate_user_input.py @@ -1,6 +1,6 @@ from galaxy.security.validate_user_input import ( extract_domain, - validate_domain_resolves, + validate_email_domain_name, validate_email_str, validate_publicname_str, ) @@ -19,9 +19,9 @@ def test_extract_base_domain(): assert extract_domain("jack@foo.bar.com", base_only=True) == "bar.com" -def test_validate_domain(): - assert validate_domain_resolves("example.org") == "" - assert validate_domain_resolves("this is an invalid domain!") != "" +def test_validate_email_domain_name(): + assert validate_email_domain_name("example.org") == "" + assert validate_email_domain_name("this is an invalid domain!") != "" def test_validate_username():