Skip to content

Commit 19d95c6

Browse files
DEV: simplify username suggester (#14531)
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 UserNameSuggester is good.
1 parent 69f0f48 commit 19d95c6

File tree

5 files changed

+52
-62
lines changed

5 files changed

+52
-62
lines changed

app/models/discourse_single_sign_on.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -323,8 +323,8 @@ def change_external_attributes_and_override(sso_record, user)
323323
if SiteSetting.auth_overrides_username? && username.present?
324324
if user.username.downcase == username.downcase
325325
user.username = username # there may be a change of case
326-
elsif user.username != username
327-
user.username = UserNameSuggester.suggest(username || name || email, user.username)
326+
elsif user.username != UserNameSuggester.fix_username(username)
327+
user.username = UserNameSuggester.suggest(username)
328328
end
329329
end
330330

lib/auth/result.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ def self.from_session_data(data, user:)
7777

7878
def apply_user_attributes!
7979
change_made = false
80-
if SiteSetting.auth_overrides_username? && username.present? && username != user.username
81-
user.username = UserNameSuggester.suggest(username_suggester_attributes, user.username)
80+
if SiteSetting.auth_overrides_username? && username.present? && UserNameSuggester.fix_username(username) != user.username
81+
user.username = UserNameSuggester.suggest(username)
8282
change_made = true
8383
end
8484

lib/user_name_suggester.rb

Lines changed: 8 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ module UserNameSuggester
44
GENERIC_NAMES = ['i', 'me', 'info', 'support', 'admin', 'webmaster', 'hello', 'mail', 'office', 'contact', 'team']
55
LAST_RESORT_USERNAME = "user"
66

7-
def self.suggest(name_or_email, allowed_username = nil)
7+
def self.suggest(name_or_email)
88
name = parse_name_from_email(name_or_email)
9-
find_available_username_based_on(name, allowed_username)
9+
find_available_username_based_on(name)
1010
end
1111

1212
def self.parse_name_from_email(name_or_email)
@@ -20,22 +20,13 @@ def self.parse_name_from_email(name_or_email)
2020
name
2121
end
2222

23-
def self.find_available_username_based_on(name, allowed_username = nil)
23+
def self.find_available_username_based_on(name)
2424
name = fix_username(name)
2525
offset = nil
2626
i = 1
2727

2828
attempt = name
29-
normalized_attempt = User.normalize_username(attempt)
30-
31-
original_allowed_username = allowed_username
32-
allowed_username = User.normalize_username(allowed_username) if allowed_username
33-
34-
until (
35-
normalized_attempt == allowed_username ||
36-
User.username_available?(attempt) ||
37-
i > 100
38-
)
29+
until User.username_available?(attempt) || i > 100
3930

4031
if offset.nil?
4132
normalized = User.normalize_username(name)
@@ -51,20 +42,15 @@ def self.find_available_username_based_on(name, allowed_username = nil)
5142

5243
params = {
5344
count: count + 10,
54-
name: normalized,
55-
allowed_normalized: allowed_username || ''
45+
name: normalized
5646
}
5747

5848
# increasing the search space a bit to allow for some extra noise
5949
available = DB.query_single(<<~SQL, params).first
6050
WITH numbers AS (SELECT generate_series(1, :count) AS n)
6151
6252
SELECT n FROM numbers
63-
LEFT JOIN users ON (
64-
username_lower = :name || n::varchar
65-
) AND (
66-
username_lower <> :allowed_normalized
67-
)
53+
LEFT JOIN users ON (username_lower = :name || n::varchar)
6854
WHERE users.id IS NULL
6955
ORDER by n ASC
7056
LIMIT 1
@@ -82,22 +68,15 @@ def self.find_available_username_based_on(name, allowed_username = nil)
8268

8369
max_length = User.username_length.end - suffix.length
8470
attempt = "#{truncate(name, max_length)}#{suffix}"
85-
normalized_attempt = User.normalize_username(attempt)
8671
i += 1
8772
end
8873

89-
until normalized_attempt == allowed_username || User.username_available?(attempt) || i > 200
74+
until User.username_available?(attempt) || i > 200
9075
attempt = SecureRandom.hex[1..SiteSetting.max_username_length]
91-
normalized_attempt = User.normalize_username(attempt)
9276
i += 1
9377
end
9478

95-
if allowed_username == normalized_attempt
96-
original_allowed_username
97-
else
98-
attempt
99-
end
100-
79+
attempt
10180
end
10281

10382
def self.fix_username(name)

spec/components/user_name_suggester_spec.rb

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,6 @@
160160
expect(UserNameSuggester.suggest('য়া')).to eq('য়া11')
161161
end
162162

163-
it "does not skip ove allowed names" do
164-
Fabricate(:user, username: 'sam')
165-
Fabricate(:user, username: 'saM1')
166-
Fabricate(:user, username: 'sam2')
167-
168-
expect(UserNameSuggester.suggest('SaM', 'Sam1')).to eq('Sam1')
169-
end
170-
171163
it "normalizes usernames" do
172164
actual = 'Löwe' # NFD, "Lo\u0308we"
173165
expected = 'Löwe' # NFC, "L\u00F6we"

spec/models/discourse_single_sign_on_spec.rb

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -265,27 +265,6 @@ def test_parsed(parsed, sso)
265265
expect(add_group4.usernames).to eq(user.username)
266266
end
267267

268-
it 'can override username properly when only the case changes' do
269-
SiteSetting.auth_overrides_username = true
270-
271-
sso = new_discourse_sso
272-
sso.username = "testuser"
273-
sso.name = "test user"
274-
sso.email = "test@test.com"
275-
sso.external_id = "100"
276-
sso.bio = "This **is** the bio"
277-
sso.suppress_welcome_message = true
278-
279-
# create the original user
280-
user = sso.lookup_or_create_user(ip_address)
281-
expect(user.username).to eq "testuser"
282-
283-
# change the username case
284-
sso.username = "TestUser"
285-
user = sso.lookup_or_create_user(ip_address)
286-
expect(user.username).to eq "TestUser"
287-
end
288-
289268
it 'behaves properly when auth_overrides_username is set but username is missing or blank' do
290269
SiteSetting.auth_overrides_username = true
291270

@@ -347,6 +326,46 @@ def test_parsed(parsed, sso)
347326
expect(admin.name).to eq "Louis C.K."
348327
end
349328

329+
it 'can override username properly when only the case changes' do
330+
SiteSetting.auth_overrides_username = true
331+
332+
sso = new_discourse_sso
333+
sso.username = "testuser"
334+
sso.name = "test user"
335+
sso.email = "test@test.com"
336+
sso.external_id = "100"
337+
sso.bio = "This **is** the bio"
338+
sso.suppress_welcome_message = true
339+
340+
# create the original user
341+
user = sso.lookup_or_create_user(ip_address)
342+
expect(user.username).to eq "testuser"
343+
344+
# change the username case
345+
sso.username = "TestUser"
346+
user = sso.lookup_or_create_user(ip_address)
347+
expect(user.username).to eq "TestUser"
348+
end
349+
350+
it 'do not override username when a new username after fixing is the same' do
351+
SiteSetting.auth_overrides_username = true
352+
353+
sso = new_discourse_sso
354+
sso.username = "testuser"
355+
sso.name = "test user"
356+
sso.email = "test@test.com"
357+
sso.external_id = "100"
358+
359+
# create the original user
360+
user = sso.lookup_or_create_user(ip_address)
361+
expect(user.username).to eq "testuser"
362+
363+
# change the username case
364+
sso.username = "testuserგამარჯობა"
365+
user = sso.lookup_or_create_user(ip_address)
366+
expect(user.username).to eq "testuser"
367+
end
368+
350369
it "doesn't use email as a source for username suggestions by default" do
351370
sso = new_discourse_sso
352371
sso.external_id = "100"

0 commit comments

Comments
 (0)