Skip to content

[ENG-10340] Check for flag#11792

Open
bodintsov wants to merge 11 commits into
CenterForOpenScience:feature/pbs-26-13from
bodintsov:fix/use-account-get-disabled
Open

[ENG-10340] Check for flag#11792
bodintsov wants to merge 11 commits into
CenterForOpenScience:feature/pbs-26-13from
bodintsov:fix/use-account-get-disabled

Conversation

@bodintsov

Copy link
Copy Markdown
Contributor

Ticket

https://openscience.atlassian.net/browse/ENG-10340

Purpose

Fix the user's account getting disabled

Changes

Added a check for the frag in the method

Side Effects

TBD

QE Notes

TBD

CE Notes

TBD

Documentation

TBD

@bodintsov bodintsov force-pushed the fix/use-account-get-disabled branch 2 times, most recently from 26e58cf to 4c6cf27 Compare July 1, 2026 14:13
Comment thread osf/models/validators.py Outdated
return False

text = name_content.lower()
if any(suffix in text for suffix in ['m.sc.', 'msc.', 'phd.', 'ph.d.', 'msc.pt', 'pt.', 'prof.', 'dr.', 'md.', 'jd.', 'esq.']):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is far too specific and only handles the cases we know of, not the potential future cases. I think we need to adjust the DOMAIN_REGEX to be better (it should grab complete URLs), and also maybe if the domain regex finds something, pass the suspected domain through urllib.parse.urlsplit to see if it has a scheme and a netloc. If it doesn't have both of those, we could probably return false, since it won't link in an email to a location.

@sh-andriy sh-andriy left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bodintsov I agree with @brianjgeiger, the suffix list is just going to rot as new name formats show up. The urlsplit idea (only count it if it parses to a scheme + netloc) is the cleaner, future-proof version.

One thing though: even a perfect regex only fixes the trigger, not the disable itself. Accounts still get disabled with SPAM_ACCOUNT_SUSPENSION_ENABLED off because user.confirm_spam() calls deactivate_account() directly (user.py:1485), skipping both the suspension flag and the is_hammy check that suspend_spam_user has. So the save()confirm_spam() path can still nuke an assumed-ham user. To actually hit the AC (assumed-ham never auto-disabled, manual only, excluded domain → flagged) we need to guard there too, flag instead of confirm_spam for hammy users.

@cslzchen cslzchen force-pushed the feature/pbs-26-13 branch from a29364e to 3308e4e Compare July 5, 2026 15:33
@bodintsov bodintsov force-pushed the fix/use-account-get-disabled branch from f825b0d to 8ca3c5c Compare July 6, 2026 09:19
@bodintsov bodintsov force-pushed the fix/use-account-get-disabled branch from 8ca3c5c to baa5c5f Compare July 6, 2026 09:32
Comment thread osf/models/validators.py Outdated
Comment on lines +126 to +127
if candidate.startswith('www.'):
candidate = f'https://{candidate}'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no sense checking for parsed.scheme if you're going to add the scheme here. I'm guessing you've added that to handle the case of

        {
            'fullname': 'Judith Sarah',
            'given_name': 'Judith',
            'family_name': 'Preuss',
            'middle_names': 'www.google.com',
            'suffix': 'M.Sc.',
        },

If you're going to do that, we should probably a) not bother checking for parsed.scheme; and b) handle the case of google.com as well as www.google.com.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

google.com and N.Sathesh are both the same for regular expression and urlsplit. It seems not to be possible to at the same time allow for names like that and prohibit URLs without protocol and www

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Harrumph. I was really hoping urlparse would check for valid top level domains. Let's see if we can come up with something better than urlparse for this then. I'll give it some thought.

'given_name': 'Judith',
'family_name': 'Preuss',
'middle_names': 'https://www.google.com',
'suffix': 'M.Sc.',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this is problematic. M.Sc. is a valid domain name. I'm checking with Product on how we want to try to handle it.

Comment thread osf/models/user.py Outdated
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:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user has been marked as ham or maaaybe presumed ham, we need to make sure that they are not marked as spam. We should definitely mark them as spam if their spam situation is unknown.

What we may want to do is only use the has_domain on presumed ham folks if they are being created as unregistered contributors, and not of they are already in the database. That may be covered by was_creating and maybe it should eb if (has_domain and was_creating) or (has_domain and not self.is_hammy) or similar. I'm not super-familiar with the spam system, so I don't know the ideal conditional to use here, but give some thought based on what I've mentioned above, and see if you can make some tests to cover each of the scenarios.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I talked with Product, and I think if we also add in something that doesn't spam a user if the domain it has is in the notable domains as ignored, that would prevent a lot of the problems we're having as well, and would explicitly handle the M.Sc domain problem below.

Comment thread osf_tests/test_user.py Outdated
Comment on lines +2110 to +2118
def test_validate_domain_in_field_for_existing_not_ham(self):
self.user.fullname = 'google.com'
self.user.save()
assert self.user.is_spammy is True
assert self.user.is_hammy is False

self.user.unspam(save=True)
self.user.fullname = 'not a url'
self.user.save()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we discussed on Slack was having the domains in the username field only flag as spam if you are creating a user for an unregistered contributor. That is the spam vector that it is explicitly trying to prevent. Right now, there are no situations where a user who exists already should be flagged as spam for including a domain in their user name parts. Eliminating that check will reduce the opportunity for this to fail in a way that is causing problems for real users now.

@brianjgeiger brianjgeiger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing to change from the comments.

Also, could you make a test that if a user tries to make an account with a non-notable domain in its name but it's not an unregistered user, that the account creation succeeds? So if a regular user like Brian google.com Geiger tries to register normally it succeeds? This should only target unregistered contributors as things stand.

I think that will do it.

Comment thread osf_tests/test_user.py Outdated
Comment on lines +2096 to +2101
def test_validate_domain_fields_notable_domain_in_field_unregistered_contributor(self):
NotableDomain.objects.get_or_create(domain='google.com', note=NotableDomain.Note.IGNORED)
user = OSFUser.create_unregistered(fullname='google.com')
user.save()
assert user.is_spammy is False
assert user.is_hammy is True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this should be true.

Comment thread osf/models/user.py Outdated
Comment on lines +1061 to +1062
has_domain, status = has_domain_in_user_fields_for_names(self)
should_mark_ham = True if status == SpamStatus.HAM else False

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced about this logic. I don't think we want to mark the user as ham if they have an ignored domain in their user parts. I think we just want to ignore domains that are presumed ham or are ignored.

@brianjgeiger brianjgeiger left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think one last test change and we're good. Should be simple.

Comment thread osf_tests/test_user.py
Comment on lines +2097 to +2100
setattr(self.user, field, 'Brian')
self.user.save()
self.user.refresh_from_db()
assert self.user.is_spammy is False

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like it's testing what the test name is suggesting. This looks like it's taking an existing user and setting all of the name fields to "Brian" and saving the object and making sure that doesn't spam the user, which I wouldn't expect it to since you're not setting a domain on any of those fields.

So I see a test above does the testing of the user that already exists. Can we make this a new user creation? Or is that already happening that way because AuthUserFactory() doesn't finish creating the new user until the save happens? If it is already happening that way, then we probably don't need that test. If it's not, then we probably want to do something like user = AuthUserFactory(full_name='google.com') or similar to make sure we're catching the proper case. Might be safest just to create the user in the test with the full_name as part of the factory and ensure it doesn't spam.

Comment thread osf/models/user.py
Comment on lines +1068 to +1072
if was_creating:
if self.is_hammy and has_domain:
self.flag_spam()
if not self.is_hammy and has_domain:
self.confirm_spam()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this even necessary with L1074-L1075 below?

Comment thread osf/models/user.py
@@ -1059,17 +1059,20 @@ def save(self, *args, **kwargs):
if not self.is_spammy:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I wonder if this should be:

Suggested change
if not self.is_spammy:
if was_creating:

Comment thread osf/models/user.py
self.confirm_spam()

if has_domain and was_creating:
raise ValidationError('Invalid personal information.')

@brianjgeiger brianjgeiger Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, one last thing. Since the user gets created on L1066, we'll need to add a spam status to the user object. Should say "Domain in name fields". Unless we move this ValidationError above L1066, in which case the user will never be saved.

Comment thread osf/models/user.py
Comment on lines +1488 to +1489
if not self.is_hammy:
self.deactivate_account()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can revert this change out with the other changes we're making.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants