2019-04-29 19:27:42 -05:00
|
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
2022-07-27 21:27:38 -05:00
|
|
|
|
RSpec.describe UsernameValidator do
|
2023-08-13 23:19:05 -05:00
|
|
|
|
def expect_valid(*usernames, failure_reason: "")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
usernames.each do |username|
|
|
|
|
|
validator = UsernameValidator.new(username)
|
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
message = "expected '#{username}' to be valid"
|
|
|
|
|
message = "#{message}, #{failure_reason}" if failure_reason.present?
|
2019-04-23 05:22:47 -05:00
|
|
|
|
aggregate_failures do
|
2023-08-13 23:19:05 -05:00
|
|
|
|
expect(validator.valid_format?).to eq(true), message
|
2019-04-23 05:22:47 -05:00
|
|
|
|
expect(validator.errors).to be_empty
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
def expect_invalid(*usernames, error_message:, failure_reason: "")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
usernames.each do |username|
|
|
|
|
|
validator = UsernameValidator.new(username)
|
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
message = "expected '#{username}' to be invalid"
|
|
|
|
|
message = "#{message}, #{failure_reason}" if failure_reason.present?
|
2019-04-23 05:22:47 -05:00
|
|
|
|
aggregate_failures do
|
2023-08-13 23:19:05 -05:00
|
|
|
|
expect(validator.valid_format?).to eq(false), message
|
2019-04-23 05:22:47 -05:00
|
|
|
|
expect(validator.errors).to include(error_message)
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-08-15 10:12:22 -05:00
|
|
|
|
let(:max_username_length) do
|
|
|
|
|
[
|
|
|
|
|
User.maximum("length(username)"),
|
|
|
|
|
MaxUsernameLengthValidator::MAX_USERNAME_LENGTH_RANGE.begin,
|
|
|
|
|
].max
|
|
|
|
|
end
|
2023-08-15 09:09:26 -05:00
|
|
|
|
let(:min_username_length) { User.minimum("length(username)") }
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
shared_examples "ASCII username" do
|
|
|
|
|
it "is invalid when the username is blank" do
|
|
|
|
|
expect_invalid("", error_message: I18n.t(:"user.username.blank"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username is too short" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.min_username_length = min_username_length
|
|
|
|
|
|
|
|
|
|
usernames = min_username_length.times.map { |i| "a" * i }.filter(&:present?)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-15 09:09:26 -05:00
|
|
|
|
expect_invalid(
|
|
|
|
|
*usernames,
|
2024-04-04 08:02:09 -05:00
|
|
|
|
error_message: I18n.t(:"user.username.short", count: min_username_length),
|
2023-08-15 09:09:26 -05:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username has the minimum length" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.min_username_length = min_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-15 09:09:26 -05:00
|
|
|
|
expect_valid("a" * min_username_length)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username is too long" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.max_username_length = max_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
expect_invalid(
|
2023-08-15 09:09:26 -05:00
|
|
|
|
"a" * (max_username_length + 1),
|
2024-04-04 08:02:09 -05:00
|
|
|
|
error_message: I18n.t(:"user.username.long", count: max_username_length),
|
2023-08-15 09:09:26 -05:00
|
|
|
|
failure_reason: "Should be invalid as username length > #{max_username_length}",
|
2023-08-13 23:19:05 -05:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username has the maximum length" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.max_username_length = max_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
expect_valid(
|
2023-08-15 09:09:26 -05:00
|
|
|
|
"a" * max_username_length,
|
|
|
|
|
failure_reason: "Should be valid as username length = #{max_username_length}",
|
2023-08-13 23:19:05 -05:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username contains alphanumeric characters, dots, underscores and dashes" do
|
|
|
|
|
expect_valid("ab-cd.123_ABC-xYz")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains non-alphanumeric characters other than dots, underscores and dashes" do
|
|
|
|
|
expect_invalid("abc|", "a#bc", "abc xyz", error_message: I18n.t(:"user.username.characters"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username starts with a alphanumeric character or underscore" do
|
|
|
|
|
expect_valid("abcd", "1abc", "_abc")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username starts with a dot or dash" do
|
|
|
|
|
expect_invalid(
|
|
|
|
|
".abc",
|
|
|
|
|
"-abc",
|
|
|
|
|
error_message: I18n.t(:"user.username.must_begin_with_alphanumeric_or_underscore"),
|
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username ends with a alphanumeric character" do
|
|
|
|
|
expect_valid("abcd", "abc9")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username ends with an underscore, a dot or dash" do
|
|
|
|
|
expect_invalid(
|
|
|
|
|
"abc_",
|
|
|
|
|
"abc.",
|
|
|
|
|
"abc-",
|
|
|
|
|
error_message: I18n.t(:"user.username.must_end_with_alphanumeric"),
|
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains consecutive underscores, dots or dashes" do
|
|
|
|
|
expect_invalid(
|
|
|
|
|
"a__bc",
|
|
|
|
|
"a..bc",
|
|
|
|
|
"a--bc",
|
|
|
|
|
error_message: I18n.t(:"user.username.must_not_contain_two_special_chars_in_seq"),
|
|
|
|
|
)
|
2013-02-08 08:52:56 -06:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username ends with certain file extensions" do
|
|
|
|
|
expect_invalid(
|
|
|
|
|
"abc.json",
|
|
|
|
|
"abc.png",
|
|
|
|
|
error_message: I18n.t(:"user.username.must_not_end_with_confusing_suffix"),
|
|
|
|
|
)
|
2013-02-08 08:52:56 -06:00
|
|
|
|
end
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
context "when Unicode usernames are disabled" do
|
2019-04-23 05:22:47 -05:00
|
|
|
|
before { SiteSetting.unicode_usernames = false }
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
include_examples "ASCII username"
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains non-ASCII characters except dots, underscores and dashes" do
|
|
|
|
|
expect_invalid("abcö", "abc象", error_message: I18n.t(:"user.username.characters"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
context "when Unicode usernames are enabled" do
|
2019-04-23 05:22:47 -05:00
|
|
|
|
before { SiteSetting.unicode_usernames = true }
|
|
|
|
|
|
2022-07-27 11:14:14 -05:00
|
|
|
|
context "with ASCII usernames" do
|
2023-01-09 05:18:21 -06:00
|
|
|
|
include_examples "ASCII username"
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2022-07-27 11:14:14 -05:00
|
|
|
|
context "with Unicode usernames" do
|
2019-04-23 05:22:47 -05:00
|
|
|
|
before { SiteSetting.min_username_length = 1 }
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username is too short" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.min_username_length = min_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-15 09:09:26 -05:00
|
|
|
|
usernames = min_username_length.times.map { |i| "鳥" * i }.filter(&:present?)
|
|
|
|
|
|
|
|
|
|
expect_invalid(
|
|
|
|
|
*usernames,
|
2024-04-04 08:02:09 -05:00
|
|
|
|
error_message: I18n.t(:"user.username.short", count: min_username_length),
|
2023-08-15 09:09:26 -05:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username has the minimum length" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.min_username_length = min_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-15 09:09:26 -05:00
|
|
|
|
expect_valid("ط" * min_username_length)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username is too long" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.max_username_length = max_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
expect_invalid(
|
2023-08-15 09:09:26 -05:00
|
|
|
|
"ם" * (max_username_length + 1),
|
|
|
|
|
"äl" * (max_username_length + 1),
|
2024-04-04 08:02:09 -05:00
|
|
|
|
error_message: I18n.t(:"user.username.long", count: max_username_length),
|
2023-08-15 09:09:26 -05:00
|
|
|
|
failure_reason: "Should be invalid as username length are > #{max_username_length}",
|
2023-01-09 05:18:21 -06:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username has the maximum length" do
|
2023-08-15 09:09:26 -05:00
|
|
|
|
SiteSetting.max_username_length = max_username_length
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-08-13 23:19:05 -05:00
|
|
|
|
expect_valid(
|
2023-08-15 09:09:26 -05:00
|
|
|
|
"Д" * (max_username_length),
|
|
|
|
|
"س" * (max_username_length),
|
|
|
|
|
"மி" * (max_username_length),
|
|
|
|
|
failure_reason: "Should be valid as usernames are <= #{max_username_length}",
|
2023-08-13 23:19:05 -05:00
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username has too many Unicode codepoints" do
|
2019-04-23 05:22:47 -05:00
|
|
|
|
SiteSetting.max_username_length = 30
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
expect_invalid(
|
|
|
|
|
"য়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়ায়া",
|
|
|
|
|
error_message: I18n.t(:"user.username.too_long"),
|
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username contains Unicode letters" do
|
|
|
|
|
expect_valid(
|
|
|
|
|
"鳥",
|
|
|
|
|
"طائر",
|
|
|
|
|
"թռչուն",
|
|
|
|
|
"πουλί",
|
|
|
|
|
"পাখি",
|
|
|
|
|
"madár",
|
|
|
|
|
"새",
|
|
|
|
|
"پرنده",
|
|
|
|
|
"птица",
|
|
|
|
|
"fågel",
|
|
|
|
|
"นก",
|
|
|
|
|
"پرندے",
|
|
|
|
|
"ציפור",
|
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username contains numbers from the Nd or Nl Unicode category" do
|
|
|
|
|
expect_valid("arabic٠١٢٣٤٥٦٧٨٩", "bengali০১২৩৪৫৬৭৮৯", "romanⅥ", "hangzhou〺")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains numbers from the No Unicode category" do
|
|
|
|
|
expect_invalid("circled㊸", "fraction¾", error_message: I18n.t(:"user.username.characters"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains symbols or emojis" do
|
2019-04-23 05:22:47 -05:00
|
|
|
|
SiteSetting.min_username_length = 1
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
expect_invalid(
|
|
|
|
|
"©",
|
|
|
|
|
"⇨",
|
|
|
|
|
"“",
|
|
|
|
|
"±",
|
|
|
|
|
"‿",
|
|
|
|
|
"😃",
|
|
|
|
|
"🚗",
|
|
|
|
|
error_message: I18n.t(:"user.username.characters"),
|
|
|
|
|
)
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-05-02 02:34:53 -05:00
|
|
|
|
it "is invalid when the username contains invisible characters" do
|
|
|
|
|
expect_invalid(
|
|
|
|
|
"a\u{034F}b",
|
|
|
|
|
"a\u{115F}b",
|
|
|
|
|
"a\u{1160}b",
|
|
|
|
|
"a\u{17B4}b",
|
|
|
|
|
"a\u{17B5}b",
|
|
|
|
|
"a\u{180B}b",
|
|
|
|
|
"a\u{180C}b",
|
|
|
|
|
"a\u{180D}b",
|
|
|
|
|
"a\u{3164}b",
|
|
|
|
|
"a\u{FFA0}b",
|
|
|
|
|
"a\u{FE00}b",
|
|
|
|
|
"a\u{FE0F}b",
|
|
|
|
|
"a\u{E0100}b",
|
|
|
|
|
"a\u{E01EF}b",
|
|
|
|
|
error_message: I18n.t(:"user.username.characters"),
|
|
|
|
|
)
|
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when the username contains zero width join characters" do
|
|
|
|
|
expect_invalid("ണ്", "র্যাম", error_message: I18n.t(:"user.username.characters"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when the username ends with a Unicode Mark" do
|
|
|
|
|
expect_valid("தமிழ்")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "allows all Unicode letters when the allowlist is empty" do
|
|
|
|
|
expect_valid("鳥")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2020-07-26 19:23:54 -05:00
|
|
|
|
context "with Unicode allowlist" do
|
|
|
|
|
before { SiteSetting.allowed_unicode_username_characters = "[äöüÄÖÜß]" }
|
2019-04-23 05:22:47 -05:00
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is invalid when username contains non-allowlisted letters" do
|
|
|
|
|
expect_invalid("鳥", "francès", error_message: I18n.t(:"user.username.characters"))
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when username contains only allowlisted letters" do
|
|
|
|
|
expect_valid("Löwe", "Ötzi")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid when username contains only ASCII letters and numbers regardless of allowlist" do
|
|
|
|
|
expect_valid("a-z_A-Z.0-9")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
2013-02-08 08:52:56 -06:00
|
|
|
|
|
2023-01-09 05:18:21 -06:00
|
|
|
|
it "is valid after resetting the site setting" do
|
2020-07-26 19:23:54 -05:00
|
|
|
|
SiteSetting.allowed_unicode_username_characters = ""
|
2023-01-09 05:18:21 -06:00
|
|
|
|
expect_valid("鳥")
|
2019-04-23 05:22:47 -05:00
|
|
|
|
end
|
|
|
|
|
end
|
2013-02-08 08:52:56 -06:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|