-
Notifications
You must be signed in to change notification settings - Fork 8.8k
DEV: simplify username suggester #14531
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…ly we always give suggestion based on `username` not `username || name || email`
… `allowed_username` because we already checked in enclosing if that `username != user.username`
…an remove the parameter
…ions of usernames
eviltrout
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Thanks for the explanation.
|
@eviltrout thank you for review! |
| GENERIC_NAMES = ['i', 'me', 'info', 'support', 'admin', 'webmaster', 'hello', 'mail', 'office', 'contact', 'team'] | ||
| LAST_RESORT_USERNAME = "user" | ||
|
|
||
| def self.suggest(name_or_email, allowed_username = nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you change all the places where we call this method using the 2nd arg?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, there were only two such places in Core. There are no such calls from plugins.
| user.username = username # there may be a change of case | ||
| elsif user.username != username | ||
| user.username = UserNameSuggester.suggest(username || name || email, user.username) | ||
| elsif user.username != UserNameSuggester.fix_username(username) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've discovered this after finishing this fix and opening PR, a test failure helped to find it.
We fix the username if it is too long or contains disallowed characters. So if we want to check if username already exists, we should be checking fixed versions. This part was actually the important job that we were doing inside the username suggester when passing the second argument:
# we're kind of checking if a user already was registered with this username,
# but it won't work, for example, if sso sends us a username with invalid characters:
elsif user.username != username
# we pass user.username as the second parameter,
# just to compare it with a fixed version of the first parameter inside the suggester
# in fact we comparing usernames the second time inside the suggester
user.username = UserNameSuggester.suggest(username, user.username)
endIt's better to check it once, explicitly, and don't call the suggester at all if a user already has this username.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the change makes sense, but -- and that's not your fault -- this piece of code needs some changes. Maybe in another PR?
- We need to use
UsernameChanger.change()instead of simply updatinguser.usernamein order to fix Updating mentions when username is changed via SSO user.username.downcaseshould be replaced withuser.username_lowerusername.downcaseshould be replaced withUser.normalize_username(username)to correctly handle Unicode- It might be worth checking that this and this bug reports are fixed, so that we can close them.
| user.username = username # there may be a change of case | ||
| elsif user.username != username | ||
| user.username = UserNameSuggester.suggest(username || name || email, user.username) | ||
| elsif user.username != UserNameSuggester.fix_username(username) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the change makes sense, but -- and that's not your fault -- this piece of code needs some changes. Maybe in another PR?
- We need to use
UsernameChanger.change()instead of simply updatinguser.usernamein order to fix Updating mentions when username is changed via SSO user.username.downcaseshould be replaced withuser.username_lowerusername.downcaseshould be replaced withUser.normalize_username(username)to correctly handle Unicode- It might be worth checking that this and this bug reports are fixed, so that we can close them.
| if SiteSetting.auth_overrides_username? && username.present? && username != user.username | ||
| user.username = UserNameSuggester.suggest(username_suggester_attributes, user.username) | ||
| if SiteSetting.auth_overrides_username? && username.present? && UserNameSuggester.fix_username(username) != user.username | ||
| user.username = UserNameSuggester.suggest(username) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, work for another PR: This might have the same problem as the SSO code and would require the usage of UsernameChanger.change()? On first glance, it doesn't seem to handle case changes. Maybe code can be reused between SSO and here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gschlager thank you for comments, these all make sense! I'm going to look at this before merging it, maybe will do a part in this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth checking that this and this bug reports are fixed, so that we can close them.
I've checked them, both was fixed before this PR. We already have a test case for the first one, and I've added cases for the second one here.
@gschlager also I eventually decided to address all your other comments on subsequent PRs, I'm working on them now.
This PR doesn't change any behavior, but just removes code that wasn't in use. This is a pretty dangerous place to change, since it gets called during user's registration. At the same time the refactoring is very straightforward, it's clear that this code wasn't doing any work (it still needs to be double-checked during review though). Also, the test coverage of
UserNameSuggesteris good.Here is the explanation of what I've done (or you can just look at the commit history in this PR). We have this code:
discourse/lib/auth/result.rb
Lines 80 to 83 in 20e70d0
If we inline
username_suggester_attributes, we'll get this:But we check if
username.present?in the if condition, so it's always present inside the if block, so it's clear that|| name || emailnever gets called, we can remove it:Now, we pass
user.usernameas a second parameter toUserNameSuggester, the name of that parameter isallowed_username. If the suggester has found a suggestion that's equal toallowed_usernameit stops looking for another available usernames and just returns this suggestion. We need it to avoid the situation when the suggester tries to suggest a username of the current user (without this parameter, the suggester would think that this name isn't free, when in fact it's fine to use this username).But, we check if
username != user.usernamein the condition of if block, so there is no point in passinguser.usernameto the username suggester, we already know that this isn't the username of the current user.There is only one more place when we pass the second parameter to
UserNameSuggesterand that place can be simplified using the same steps.After that, we can just remove the second parameter from
UserNameSuggester.suggestwhich simplifies the code significantly and also makes the signature logical. The signature was confusing before:discourse/lib/user_name_suggester.rb
Line 7 in 20e70d0
Why pass
allowed_usernametoUserNameSuggester? I mean, if we already know the allowed username, why callUserNameSuggesterat all? We can just use that name. After this refactoring, we'll have just: