FIX: Allow admins to use reserved usernames (#30262)

It is possible for admins to rename users like `system`
to some other username, but if they try to change it back
they cannot, since `system` is a reserved username.

This commit allows admins to change any user's username
to a reserved username _as long as that username is not
already in use_.
This commit is contained in:
Martin Brennan 2025-01-13 17:30:36 +10:00 committed by GitHub
parent cc50b3ea66
commit 7c96d7587e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 54 additions and 9 deletions

View File

@ -269,7 +269,7 @@ class UsersController < ApplicationController
params.require(:new_username) params.require(:new_username)
if clashing_with_existing_route?(params[:new_username]) || if clashing_with_existing_route?(params[:new_username]) ||
User.reserved_username?(params[:new_username]) (User.reserved_username?(params[:new_username]) && !current_user.admin?)
return render_json_error(I18n.t("login.reserved_username")) return render_json_error(I18n.t("login.reserved_username"))
end end
@ -614,7 +614,7 @@ class UsersController < ApplicationController
# The special case where someone is changing the case of their own username # The special case where someone is changing the case of their own username
return render_available_true if changing_case_of_own_username(target_user, username) return render_available_true if changing_case_of_own_username(target_user, username)
checker = UsernameCheckerService.new checker = UsernameCheckerService.new(allow_reserved_username: current_user&.admin?)
email = params[:email] || target_user.try(:email) email = params[:email] || target_user.try(:email)
render json: checker.check_username(username, email) render json: checker.check_username(username, email)
end end

View File

@ -1846,7 +1846,7 @@ RSpec.describe UsersController do
it "raises an error without a new_username param" do it "raises an error without a new_username param" do
put "/u/#{user.username}/preferences/username.json", params: { username: user.username } put "/u/#{user.username}/preferences/username.json", params: { username: user.username }
expect(response.status).to eq(400) expect(response).to be_bad_request
expect(user.reload.username).to eq(old_username) expect(user.reload.username).to eq(old_username)
end end
@ -1862,7 +1862,7 @@ RSpec.describe UsersController do
it "raises an error when change_username fails" do it "raises an error when change_username fails" do
put "/u/#{user.username}/preferences/username.json", params: { new_username: "@" } put "/u/#{user.username}/preferences/username.json", params: { new_username: "@" }
expect(response.status).to eq(422) expect(response).to be_unprocessable
body = response.parsed_body body = response.parsed_body
@ -1876,7 +1876,7 @@ RSpec.describe UsersController do
it "should succeed in normal circumstances" do it "should succeed in normal circumstances" do
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username } put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(200) expect(response).to be_successful
expect(user.reload.username).to eq(new_username) expect(user.reload.username).to eq(new_username)
end end
@ -1895,9 +1895,30 @@ RSpec.describe UsersController do
SiteSetting.reserved_usernames = "reserved" SiteSetting.reserved_usernames = "reserved"
put "/u/#{user.username}/preferences/username.json", params: { new_username: "reserved" } put "/u/#{user.username}/preferences/username.json", params: { new_username: "reserved" }
body = response.parsed_body expect(response).to be_unprocessable
expect(body["errors"].first).to include(I18n.t("login.reserved_username")) expect(response.parsed_body["errors"].first).to include(I18n.t("login.reserved_username"))
end
it "allows admins to change a username to one in the reserved list" do
sign_in(admin)
SiteSetting.reserved_usernames = "reserved"
put "/u/#{user.username}/preferences/username.json", params: { new_username: "reserved" }
expect(response).to be_successful
expect(response.parsed_body["username"]).to eq("reserved")
end
it "does not allow admins to change a username to one in the reserved list if that user already exists" do
sign_in(admin)
SiteSetting.reserved_usernames = "reserved"
Fabricate(:user, username: "reserved")
put "/u/#{user.username}/preferences/username.json", params: { new_username: "reserved" }
expect(response).to be_unprocessable
expect(response.parsed_body["errors"].first).to include("Username must be unique")
end end
it "should fail if the user is old" do it "should fail if the user is old" do
@ -1921,7 +1942,7 @@ RSpec.describe UsersController do
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username } put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(200) expect(response).to be_successful
expect( expect(
UserHistory.where( UserHistory.where(
action: UserHistory.actions[:change_username], action: UserHistory.actions[:change_username],
@ -1947,7 +1968,7 @@ RSpec.describe UsersController do
put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username } put "/u/#{user.username}/preferences/username.json", params: { new_username: new_username }
expect(response.status).to eq(422) expect(response).to be_unprocessable
expect(response.parsed_body["errors"].first).to include( expect(response.parsed_body["errors"].first).to include(
I18n.t("errors.messages.auth_overrides_username"), I18n.t("errors.messages.auth_overrides_username"),
) )
@ -1991,6 +2012,30 @@ RSpec.describe UsersController do
include_examples "when username is unavailable" include_examples "when username is unavailable"
end end
describe "reserved usernames" do
before { SiteSetting.reserved_usernames = SiteSetting.reserved_usernames + "|reserved" }
context "when checking a reserved username" do
before { get "/u/check_username.json", params: { username: "reserved" } }
include_examples "when username is unavailable"
end
context "when checking a reserved username as an admin" do
before { sign_in(admin) }
context "when user already exists" do
fab!(:user) { Fabricate(:user, username: "reserved") }
before { get "/u/check_username.json", params: { username: "reserved" } }
include_examples "when username is unavailable"
end
context "when user does not exist" do
before { get "/u/check_username.json", params: { username: "reserved" } }
include_examples "when username is available"
end
end
end
shared_examples "checking an invalid username" do shared_examples "checking an invalid username" do
it "should not return an available key but should return an error message" do it "should not return an available key but should return an error message" do
expect(response.status).to eq(200) expect(response.status).to eq(200)