diff --git a/osf/models/user.py b/osf/models/user.py index 5739813cd48..4f69a30724e 100644 --- a/osf/models/user.py +++ b/osf/models/user.py @@ -1068,7 +1068,10 @@ def save(self, *args, **kwargs): dirty_fields = self.get_dirty_fields(check_relationship=True) ret = super().save(*args, **kwargs) # must save BEFORE spam check, as user needs guid. - if has_domain and not was_creating: + if has_domain and self.is_hammy: + self.flag_spam() + + if has_domain and not was_creating and not self.is_hammy: self.confirm_spam() if set(self.SPAM_USER_PROFILE_FIELDS.keys()).intersection(dirty_fields): @@ -1482,7 +1485,8 @@ def confirm_email(self, token, merge=False): return True def confirm_spam(self, domains=None, save=True, train_spam_services=False, skip_resources_spam=False): - self.deactivate_account() + if not self.is_hammy: + self.deactivate_account() super().confirm_spam(domains=domains, save=save, train_spam_services=train_spam_services) if skip_resources_spam: diff --git a/osf/models/validators.py b/osf/models/validators.py index c5acafd3ea7..7fe745685b8 100644 --- a/osf/models/validators.py +++ b/osf/models/validators.py @@ -1,4 +1,5 @@ import re +from urllib.parse import urlsplit import waffle from jsonschema import ValidationError as JsonSchemaValidationError, SchemaError, Draft7Validator, validate, validators @@ -114,9 +115,18 @@ def validate_email(value): def has_domain_in_user_fields_for_names(user): - name_content = ' '.join([getattr(user, field) or '' for field in user.DOMAIN_VALIDATION_FIELDS]) - return True if DOMAIN_REGEX.search(name_content) else False - + name_content = ' '.join(getattr(user, field) or '' for field in user.DOMAIN_VALIDATION_FIELDS).strip() + for match in DOMAIN_REGEX.finditer(name_content): + candidate = match.group(0).strip() + if looks_like_url(candidate): + return True + return False + +def looks_like_url(candidate): + if candidate.startswith('www.'): + candidate = f'https://{candidate}' + parsed = urlsplit(candidate) + return bool(parsed.scheme and parsed.netloc) def validate_subject_hierarchy_length(parent): from osf.models import Subject diff --git a/osf_tests/test_validators.py b/osf_tests/test_validators.py index dc561389356..6d02aaa38d3 100644 --- a/osf_tests/test_validators.py +++ b/osf_tests/test_validators.py @@ -1,11 +1,21 @@ import pytest from osf.exceptions import ValidationValueError -from osf.models import validators +from types import SimpleNamespace +from osf.models import validators, OSFUser +from osf.models.validators import has_domain_in_user_fields_for_names from osf_tests.factories import SubjectFactory + # Ported from tests/framework/test_mongo.py +def mock_user(user_data): + user = SimpleNamespace() + user.DOMAIN_VALIDATION_FIELDS = OSFUser.DOMAIN_VALIDATION_FIELDS + for field in user.DOMAIN_VALIDATION_FIELDS: + setattr(user, field, user_data.get(field, '')) + return user + def test_string_required_passes_with_string(): assert validators.string_required('Hi!') is True @@ -50,3 +60,134 @@ def test_validate_expand_subject_hierarchy(): subject_list = [fruit._id, '12345_bad_id'] with pytest.raises(ValidationValueError): validators.expand_subject_hierarchy(subject_list) + + +@pytest.mark.parametrize( + 'user_data', + [ + { + 'fullname': 'Judith Sarah Preuss, M.Sc.', + 'given_name': 'Judith', + 'family_name': 'Preuss', + 'middle_names': 'Sarah', + 'suffix': 'M.Sc.', + }, + { + 'fullname': 'J.H. van Hateren', + 'given_name': 'J.H.', + 'family_name': 'van Hateren', + 'middle_names': '', + }, + { + 'fullname': 'Sami-Egil Ahonen', + 'given_name': 'Sami-Egil', + 'family_name': 'Ahonen', + 'middle_names': '', + }, + { + 'fullname': 'Giovanni Luca Ciampaglia', + 'given_name': 'Giovanni Luca', + 'family_name': 'Ciampaglia', + 'middle_names': '', + }, + { + 'fullname': 'Joseph P.R.O. Orgel', + 'given_name': 'Joseph', + 'family_name': 'Orgel', + 'middle_names': 'P.R.O.', + }, + { + 'fullname': 'Andrew Daoust', + 'given_name': 'Andrew', + 'family_name': 'Daoust', + 'middle_names': '', + }, + { + 'fullname': 'Aidan G.C. Wright', + 'given_name': 'Aidan', + 'family_name': 'Wright', + 'middle_names': 'G.C.', + }, + { + 'fullname': 'Guillermo Perez Algorta', + 'given_name': 'Guillermo', + 'family_name': 'Perez Algorta', + 'middle_names': '', + }, + { + 'fullname': 'Sarah Wojkowski, MSc.PT, PhD.', + 'given_name': 'Sarah', + 'family_name': 'Wojkowski', + 'middle_names': 'MSc.PT', + }, + { + 'fullname': 'Brockmann, L.C. (Leon)', + 'given_name': 'Leon', + 'family_name': 'Brockmann', + 'middle_names': 'L.C.', + }, + { + 'fullname': 'Gragnolati, G.M. (Gaia Mariavittoria)', + 'given_name': 'Gaia', + 'family_name': 'Gragnolati', + 'middle_names': 'G.M.', + }, + { + 'fullname': 'F.H. Leeuwis', + 'given_name': 'F.H.', + 'family_name': 'Leeuwis', + 'middle_names': '', + }, + { + 'fullname': 'Grauss, S.E. (Sophie)', + 'given_name': 'Sophie', + 'family_name': 'Grauss', + 'middle_names': 'S.E.', + }, + { + 'fullname': 'Sandhya N.Sathesh', + 'given_name': 'Sandhya', + 'family_name': 'N.Sathesh', + 'middle_names': '', + }, + { + 'fullname': 'John Doe', + 'given_name': 'John', + 'family_name': 'Doe', + 'middle_names': '', + } + ] +) +def test_has_domain_in_user_fields(user_data): + user = mock_user(user_data) + assert has_domain_in_user_fields_for_names(user) is False + + +@pytest.mark.parametrize( + 'user_data', + [ + { + 'fullname': 'Judith Sarah', + 'given_name': 'Judith', + 'family_name': 'Preuss', + 'middle_names': 'https://www.google.com', + 'suffix': 'M.Sc.', + }, + { + 'fullname': 'J.H. van Hateren', + 'given_name': 'J.H.', + 'family_name': 'https://google.com', + 'middle_names': '', + }, + { + 'fullname': 'Judith Sarah', + 'given_name': 'Judith', + 'family_name': 'Preuss', + 'middle_names': 'www.google.com', + 'suffix': 'M.Sc.', + }, + ] +) +def test_has_domain_in_user_fields_fail(user_data): + user = mock_user(user_data) + assert has_domain_in_user_fields_for_names(user) is True