FIX: extract and fix overriding of usernames by external auth (#14637)

This commit is contained in:
Andrei Prigorshnev 2021-12-02 14:42:23 +01:00 committed by GitHub
parent ceca34aca6
commit 1c0022c195
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 128 additions and 9 deletions

View File

@ -323,11 +323,7 @@ class DiscourseSingleSignOn < SingleSignOn
end
if SiteSetting.auth_overrides_username? && username.present?
if user.username.downcase == username.downcase
user.username = username # there may be a change of case
elsif user.username != UserNameSuggester.fix_username(username)
user.username = UserNameSuggester.suggest(username)
end
UsernameChanger.override(user, username)
end
if SiteSetting.auth_overrides_name && user.name != name && name.present?

View File

@ -1455,6 +1455,10 @@ class User < ActiveRecord::Base
seen_since?(30.days.ago)
end
def username_equals_to?(another_username)
username_lower == User.normalize_username(another_username)
end
protected
def badge_grant

View File

@ -13,7 +13,23 @@ class UsernameChanger
self.new(user, new_username, actor).change
end
def self.override(user, new_username)
if user.username_equals_to?(new_username)
# override anyway since case could've been changed:
UsernameChanger.change(user, new_username, user)
true
elsif user.username != UserNameSuggester.fix_username(new_username)
suggested_username = UserNameSuggester.suggest(new_username)
UsernameChanger.change(user, suggested_username, user)
true
else
false
end
end
def change(asynchronous: true, run_update_job: true)
return false if @user.username == @new_username
@user.username = @new_username
if @user.save

View File

@ -77,9 +77,8 @@ class Auth::Result
def apply_user_attributes!
change_made = false
if SiteSetting.auth_overrides_username? && username.present? && UserNameSuggester.fix_username(username) != user.username
user.username = UserNameSuggester.suggest(username)
change_made = true
if SiteSetting.auth_overrides_username? && username.present?
change_made = UsernameChanger.override(user, username)
end
if SiteSetting.auth_overrides_email && email_valid && email.present? && user.email != Email.downcase(email)

View File

@ -2601,4 +2601,30 @@ describe User do
expect(user.invited_by).to eq(invite.invited_by)
end
end
describe "#username_equals_to?" do
[
["returns true for equal usernames", "john", "john", true],
["returns false for different usernames", "john", "bill", false],
["considers usernames that are different only in case as equal", "john", "JoHN", true]
].each do |testcase_name, current_username, another_username, is_equal|
it "#{testcase_name}" do
user = Fabricate(:user, username: current_username)
result = user.username_equals_to?(another_username)
expect(result).to be(is_equal)
end
end
it "considers usernames that are equal after unicode normalization as equal" do
SiteSetting.unicode_usernames = true
raw = "Lo\u0308we" # Löwe, u0308 stands for ¨, so o\u0308 adds up to ö
normalized = "l\u00F6we" # Löwe normilized, \u00F6 stands for ö
user = Fabricate(:user, username: normalized)
result = user.username_equals_to?(raw)
expect(result).to be(true)
end
end
end

View File

@ -12,9 +12,10 @@ describe UsernameChanger do
context 'success' do
let!(:old_username) { user.username }
let(:new_username) { "#{user.username}1234" }
it 'should change the username' do
new_username = "#{user.username}1234"
events = DiscourseEvent.track_events {
@result = UsernameChanger.change(user, new_username)
}.last(2)
@ -34,6 +35,17 @@ describe UsernameChanger do
expect(user.username).to eq(new_username)
expect(user.username_lower).to eq(new_username.downcase)
end
it 'do nothing if the new username is the same' do
new_username = user.username
events = DiscourseEvent.track_events {
@result = UsernameChanger.change(user, new_username)
}
expect(@result).to eq(false)
expect(events.count).to be_zero
end
end
context 'failure' do
@ -591,4 +603,70 @@ describe UsernameChanger do
end
end
describe '#override' do
common_test_cases = [
[
"overrides the username if a new name is different",
"john", "bill", "bill", false
],
[
"does not change the username if a new name is the same",
"john", "john", "john", false
],
[
"overrides the username if a new name has different case",
"john", "JoHN", "JoHN", false
]
]
context "unicode_usernames is off" do
before do
SiteSetting.unicode_usernames = false
end
[
*common_test_cases,
[
"does not change the username if a new name after unicode normalization is the same",
"john", "john¥¥", "john"
],
].each do |testcase_name, current, new, overrode|
it "#{testcase_name}" do
user = Fabricate(:user, username: current)
UsernameChanger.override(user, new)
expect(user.username).to eq(overrode)
end
end
it "overrides the username with username suggestions in case the username is already taken" do
user = Fabricate(:user, username: "bill")
Fabricate(:user, username: "john")
UsernameChanger.override(user, "john")
expect(user.username).to eq("john1")
end
end
context "unicode_usernames is on" do
before do
SiteSetting.unicode_usernames = true
end
[
*common_test_cases,
[
"overrides the username if a new name after unicode normalization is different only in case",
"lo\u0308we", "L\u00F6wee", "L\u00F6wee"
],
].each do |testcase_name, current, new, overrode|
it "#{testcase_name}" do
user = Fabricate(:user, username: current)
UsernameChanger.override(user, new)
expect(user.username).to eq(overrode)
end
end
end
end
end