Trim spaces from around email addresses and usernames when registering.

Additionally, include a stricter email address validation.
This commit is contained in:
Nate Coraor
2022-05-26 14:55:27 -04:00
committed by Sergey Golitsynskiy
parent 8a6a717418
commit 37edbaf74c
5 changed files with 62 additions and 14 deletions
@@ -150,6 +150,9 @@ class MockAppConfig(GalaxyDataTestConfig, CommonConfigurationMixin):
self.expose_dataset_path = True
self.allow_user_dataset_purge = True
self.allow_user_creation = True
self.email_domain_allowlist_content = None
self.email_domain_blocklist_content = None
self.enable_old_display_applications = True
self.redact_username_in_logs = False
self.auth_config_file = "config/auth_conf.xml.sample"
+9 -9
View File
@@ -82,17 +82,17 @@ class UserManager(base.ModelManager, deletable.PurgableManagerMixin):
return None, message
if not email or not username or not password or not confirm:
return None, "Please provide email, username and password."
message = "\n".join(
(
validate_email(trans, email),
validate_password(trans, password, confirm),
validate_publicname(trans, username),
)
).rstrip()
email = util.restore_text(email).strip()
username = util.restore_text(username).strip()
# We could add a separate option here for enabling or disabling domain validation (DNS resolution test)
validate_domain = trans.app.config.user_activation_on
message = "\n".join((validate_email(trans, email, validate_domain=validate_domain),
validate_password(trans, password, confirm),
validate_publicname(trans, username))).rstrip()
if message:
return None, message
email = util.restore_text(email)
username = util.restore_text(username)
message, status = trans.app.auth_manager.check_registration_allowed(email, username, password)
if message:
return None, message
+24 -3
View File
@@ -6,13 +6,22 @@ user inputs - so these methods do not need to be escaped.
"""
import logging
import re
import socket
from sqlalchemy import func
log = logging.getLogger(__name__)
# Email validity parameters
VALID_EMAIL_RE = re.compile(r"[^@]+@[^@]+\.[^@]+")
#
# Many words (and regexes) have been written about validating email addresses and there is no perfect answer on how it
# should be done. We choose to use the HTML5 spec (and corresponding regex) that engages in a "willful violation" of RFC
# 5322 to provide a reasonably good validation. Additionally, we allow Unicode characters in both the user and domain
# parts of the email by using re's '\w' character. Note that \w includes "word" characters but appears to exclude emoji
# characters, which should in fact be valid.
#
# https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
VALID_EMAIL_RE = re.compile(r"^[\w.!#$%&'*+\/=?^_`{|}~-]+@[\w](?:[\w-]{0,61}[\w])?(?:\.[\w](?:[\w-]{0,61}[\w])?)*$")
EMAIL_MAX_LEN = 255
# Public name validity parameters
@@ -53,7 +62,7 @@ def validate_publicname_str(publicname):
return ""
def validate_email(trans, email, user=None, check_dup=True, allow_empty=False):
def validate_email(trans, email, user=None, check_dup=True, allow_empty=False, validate_domain=False):
"""
Validates the email format, also checks whether the domain is blocklisted in the disposable domains configuration.
"""
@@ -79,11 +88,23 @@ def validate_email(trans, email, user=None, check_dup=True, allow_empty=False):
domain = extract_domain(email, base_only=True)
if domain in trans.app.config.email_domain_blocklist_content:
message = "Please enter your permanent email address."
if not message and validate_domain:
domain = email.split('@', 1)[1]
message = validate_domain(domain)
return message
def validate_domain(domain):
message = None
try:
socket.gethostbyname(domain)
except socket.gaierror:
message = "The email domain cannot be resolved."
return message
def extract_domain(email, base_only=False):
domain = email.split("@")[1]
domain = email.split('@', 1)[1]
parts = domain.split(".")
if len(parts) > 2 and base_only:
return (".").join(parts[-2:])
@@ -82,6 +82,18 @@ class UserManagerTestCase(BaseTestCase):
**dict(email="user2a@user2.user2", username="user2", password=default_password),
)
def test_trimming(self):
self.log("emails must be trimmed")
user2b, message = self.user_manager.register(self.trans, email=' user2b@user2.user2 ', username='user2b',
password=default_password, confirm=default_password)
self.assertIsNone(message)
self.assertEqual(user2b.email, 'user2b@user2.user2')
self.log("usernames must be trimmed")
user2c, message = self.user_manager.register(self.trans, email='user2c@user2.user2', username=' user2c ',
password=default_password, confirm=default_password)
self.assertIsNone(message)
self.assertEqual(user2c.username, 'user2c')
def test_email_queries(self):
user2 = self.user_manager.create(**user2_data)
@@ -1,5 +1,6 @@
from galaxy.security.validate_user_input import (
extract_domain,
validate_domain,
validate_email_str,
validate_publicname_str,
)
@@ -16,6 +17,11 @@ def test_extract_base_domain():
assert extract_domain("jack@foo.bar.com", base_only=True) == "bar.com"
def test_validate_domain():
assert validate_domain("example.org") is None
assert validate_domain("this is an invalid domain!") is not None
def test_validate_username():
assert validate_publicname_str("testuser") == ""
assert validate_publicname_str("test.user") == ""
@@ -27,7 +33,13 @@ def test_validate_username():
def test_validate_email():
assert validate_email_str("test@foo.com") == ""
assert validate_email_str("test-dot.user@foo.com") == ""
assert validate_email_str("test@com") != ""
assert validate_email_str("@not-a-domain") != ""
assert validate_email_str("test-plus+user@foo.com") == ""
assert validate_email_str("test-ünicode-user@foo.com") == ""
assert validate_email_str("test@ünicode-domain.com") == ""
assert validate_email_str("test-missing-domain@") != ""
assert validate_email_str("@test-missing-local") != ""
assert validate_email_str("test-invalid-local\\character@foo.com") != ""
assert validate_email_str("test@invalid-domain-character!com") != ""
assert validate_email_str("test@newlines.in.address.are.invalid\n\n.com") != ""
too_long_email = "N" * 255 + "@foo.com"
assert validate_email_str(too_long_email) != ""