From 2313e3f167fabab962b05adf24ae624e215786d1 Mon Sep 17 00:00:00 2001 From: Franck Trouillez Date: Fri, 6 Dec 2024 19:21:03 +0100 Subject: [PATCH] Allow to forbid digits in Faker::Internet.password method This change allows to allow or not digits inside the password. If `digits: true`, a digit is automatically added. --- lib/faker/default/internet.rb | 22 +++++-- test/faker/default/test_faker_internet.rb | 76 +++++++++++++++-------- 2 files changed, 66 insertions(+), 32 deletions(-) diff --git a/lib/faker/default/internet.rb b/lib/faker/default/internet.rb index 6be04446c5..50491fdb66 100644 --- a/lib/faker/default/internet.rb +++ b/lib/faker/default/internet.rb @@ -108,6 +108,7 @@ def username(specifier: nil, separators: %w[. _]) # @param max_length [Integer] The maximum length of the password # @param mix_case [Boolean] Toggles if uppercased letters are allowed. If true, at least one will be added. # @param special_characters [Boolean] Toggles if special characters are allowed. If true, at least one will be added. + # @param digits [Boolean] Toggles if digits are allowed. If true, at least one will be added. # # @return [String] # @@ -121,9 +122,13 @@ def username(specifier: nil, separators: %w[. _]) # Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true) #=> "3k5qS15aNmG" # @example # Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true) #=> "*%NkOnJsH4" + # @example + # Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true, digits: true) #=> "$f%Ym*sc91e4O^" + # @example + # Faker::Internet.password(min_length: 10, max_length: 20, mix_case: true, special_characters: true, digits: false) #=> "rI$XYZgmBONFKWIZ$B" # # @faker.version 2.1.3 - def password(min_length: 8, max_length: 16, mix_case: true, special_characters: false) + def password(min_length: 8, max_length: 16, mix_case: true, special_characters: false, digits: true) raise ArgumentError, 'min_length and max_length must be greater than or equal to one' if min_length < 1 || max_length < 1 raise ArgumentError, 'min_length must be smaller than or equal to max_length' unless min_length <= max_length @@ -140,6 +145,11 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters: required_min_length += 1 end + if digits + character_types << :digits + required_min_length += 1 + end + raise ArgumentError, "min_length should be at least #{required_min_length} to enable #{character_types.join(', ')} configuration" if min_length < required_min_length target_length = rand(min_length..max_length) @@ -152,10 +162,6 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters: password << sample(lower_chars) character_bag += lower_chars - digits = ('0'..'9').to_a - password << sample(digits) - character_bag += digits - if mix_case upper_chars = self::ULetters password << sample(upper_chars) @@ -168,6 +174,12 @@ def password(min_length: 8, max_length: 16, mix_case: true, special_characters: character_bag += special_chars end + if digits + digits_chars = ('0'..'9').to_a + password << sample(digits_chars) + character_bag += digits_chars + end + password << sample(character_bag) while password.length < target_length shuffle(password).join diff --git a/test/faker/default/test_faker_internet.rb b/test/faker/default/test_faker_internet.rb index 7eff8e66b9..b13e499b0b 100644 --- a/test/faker/default/test_faker_internet.rb +++ b/test/faker/default/test_faker_internet.rb @@ -232,8 +232,8 @@ def test_password_with_min_length_eq_1_without_mix_case end def test_password_with_min_length_and_max_length - min_length = 2 - max_length = 5 + min_length = 3 + max_length = 6 password = @tester.password(min_length: min_length, max_length: max_length) assert_match(/\w+/, password) @@ -265,34 +265,39 @@ def test_password_without_special_chars assert_match(/[^!@#$%\^&*]+/, @tester.password(min_length: 8, max_length: 12, mix_case: true)) end - def test_password_with_special_chars_and_mixed_case + def test_password_with_digits + assert_match(/[0-9]+/, @tester.password(min_length: 8, max_length: 12, digits: true)) + end + + def test_password_without_digits + assert_match(/^[^0-9]+$/, @tester.password(min_length: 8, max_length: 12, digits: false)) + end + + def test_password_with_special_chars_and_mixed_case_and_digits 32.times do - password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true) + password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true) - assert_match(/[!@#$%\^&*]+/, password) - assert_match(/[A-z]+/, password) + assert_match(/^[A-Za-z0-9!@#$%\^&*]+$/, password) end end - def test_deterministic_password_with_special_chars_and_mixed_case - deterministically_verify -> { @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true) }, depth: 4 do |password| - assert_match(/[!@#$%\^&*]+/, password) - assert_match(/[A-z]+/, password) + def test_deterministic_password_with_special_chars_and_mixed_case_and_digits + deterministically_verify -> { @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true) }, depth: 4 do |password| + assert_match(/^[A-Za-z0-9!@#$%\^&*]+$/, password) end end - def test_password_with_special_chars_and_mixed_case_on_3chars_password + def test_password_with_special_chars_and_mixed_case_and_digits_on_4chars_password 16.times do - password = @tester.password(min_length: 3, max_length: 6, mix_case: true, special_characters: true) + password = @tester.password(min_length: 4, max_length: 6, mix_case: true, special_characters: true, digits: true) - assert_match(/[!@#$%\^&*]+/, password) - assert_match(/[A-z]+/, password) + assert_match(/^[A-Za-z0-9!@#$%\^&*]+$/, password) end end - def test_password_with_invalid_min_length_for_mix_case_and_special_characters - assert_raise_message 'min_length should be at least 3 to enable mix_case, special_characters configuration' do - @tester.password(min_length: 1, mix_case: true, special_characters: true) + def test_password_with_invalid_min_length_for_mix_case_and_special_characters_and_digits + assert_raise_message 'min_length should be at least 4 to enable mix_case, special_characters, digits configuration' do + @tester.password(min_length: 1, mix_case: true, special_characters: true, digits: true) end end @@ -306,7 +311,7 @@ def test_password_with_invalid_min_max_length def test_password_with_invalid_min_length_for_special_characters_only error = assert_raises(ArgumentError) do - @tester.password(min_length: 0, mix_case: false, special_characters: true) + @tester.password(min_length: 0, mix_case: false, special_characters: true, digits: false) end assert_equal 'min_length and max_length must be greater than or equal to one', error.message @@ -314,27 +319,44 @@ def test_password_with_invalid_min_length_for_special_characters_only def test_password_with_invalid_min_length_for_mix_case_only error = assert_raises(ArgumentError) do - @tester.password(min_length: 1, mix_case: true) + @tester.password(min_length: 1, mix_case: true, special_characters: false, digits: false) end assert_equal 'min_length should be at least 2 to enable mix_case configuration', error.message end + def test_password_with_invalid_min_length_for_digits_only + error = assert_raises(ArgumentError) do + @tester.password(min_length: 0, mix_case: false, special_characters: false, digits: true) + end + + assert_equal 'min_length and max_length must be greater than or equal to one', error.message + end + def test_password_with_compatible_min_length_and_requirements assert_nothing_raised do - [false, true].each do |value| - min_length = value ? 2 : 1 - @tester.password(min_length: min_length, mix_case: value, special_characters: !value) + [false, true].each do |mix_case| + [false, true].each do |special_characters| + [false, true].each do |digits| + min_length = [[mix_case ? 2 : 0, special_characters ? 1 : 0, digits ? 1 : 0].sum, 1].max + + @tester.password(min_length: min_length, mix_case: mix_case, special_characters: special_characters, digits: digits) + end + end end end end def test_deterministic_password_with_compatible_min_length_and_requirements - [false, true].each do |value| - min_length = value ? 2 : 1 - - deterministically_verify -> { @tester.password(min_length: min_length, mix_case: value, special_characters: !value) }, depth: 4 do |password| - assert_nothing_raised { password } + [false, true].each do |mix_case| + [false, true].each do |special_characters| + [false, true].each do |digits| + min_length = [[mix_case ? 2 : 0, special_characters ? 1 : 0, digits ? 1 : 0].sum, 1].max + + deterministically_verify -> { @tester.password(min_length: min_length, mix_case: mix_case, special_characters: special_characters, digits: digits) }, depth: 4 do |password| + assert_nothing_raised { password } + end + end end end end