I am trying to create password Generate in ruby. At the moment all is working just got stuck at the final piece of generating the password.
I asked user if he/she would like the password to include numbers, lowercase or uppercase. If YES, user will enter 1 and 0 for NO.
I used the code below to generate password if everything is 1. Meaning user want to include numbers, lowercase and uppercase.
if numbers == 1 && lowercase == 1 && uppercase == 1
passGen = [(0..9).to_a ('A'..'Z').to_a ('a'..'z').to_a].flatten.sample(10)
end
p passGen
This works 90% of the time. 10% of the time the generated password will not include say any numbers. But everything else present. I am not sure if this is because of the size or length of Array from which the password is sampled.
Anyway lets go to the main problem below
Here is the problem, I am struggling to write the code to generate password if one or more of input is 0. That's if user don't want to include numbers. Or no numbers and uppercase etc . As I can't predict what user may want or not want. I need help on this please. Thank you.
CodePudding user response:
You will need to make your input array more dynamic:
passGen = []
passGen = (0..9).to_a if numbers == 1
passGen = ('A'..'Z').to_a if uppercase == 1
passGen = ('a'..'z').to_a if lowercase == 1
passGen.sample(10).join
Now, to tackle your other issue with missing characters - this is caused as you are simply taking 10 random characters from an array. So it can just take, for example, all digits.
To tackle this you need to get one character from each generator first and then generate the remaining characters randomly and shuffle the result:
def generators(numbers:, lowercase:, uppercase:)
[
(0..9 if numbers),
('A'..'Z' if uppercase),
('a'..'z' if lowercase)
].compact.map(&:to_a)
end
def generate_password(generators:, length:, min_per_generator: 1)
chars = generators.flat_map {|g| Array.new(min_per_generator) { g.sample }}
chars = Array.new(length - chars.length) { generators.sample.sample }
chars.shuffle.join
end
gens = generators(numbers: numbers == 1, uppercase == 1, lowercase: lowercase == 1)
Array.new(10) { generate_password(generators: gens, length: 10) }
CodePudding user response:
By saying (0..9).to_a ('A'..'Z').to_a ('a'..'z').to_a
, you're creating an Array of 10 26 26 = 62 elements, and then you pick only 10 elements out of it.
At your place I'd wrap password generation around an until
block:
def generate_password_with_digits_and_caps
[(0..9).to_a ('A'..'Z').to_a ('a'..'z').to_a].flatten.sample(10).join
end
passGen = ''
until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
passGen = generate_password_with_digits_and_caps
end
This could also work (closer to your snipppet):
if numbers == 1 && lowercase == 1 && uppercase == 1
passGen = ''
until passGen.match(/[A-Z]/) && passGen.match(/[a-z]/) && passGen.match(/\d/)
passGen = [(0..9).to_a ('A'..'Z').to_a ('a'..'z').to_a].flatten.sample(10).join
end
end
CodePudding user response:
The code doesn't know it needs to include a digit/letter from every group. The sample takes random signs and since you a basically sampling 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
there is a possibility that all the signs will not be digits.
The easiest way to fix it is to check if a sign from every group is in the "password" and then replace a random sign with a sign from group that is not present.
If I were to program this I would do it like that
def random_from_range(range)
range.to_a.sample.to_s
end
def passGen(numbers, lowercase, uppercase)
result = ''
possibleSigns = []
if numbers == 1
range = (0..9)
result = random_from_range(range)
possibleSigns = range.to_a
end
if lowercase == 1
range = ('A'..'Z')
result = random_from_range(range)
possibleSigns = range.to_a
end
if uppercase == 1
range = ('a'..'z')
result = random_from_range(range)
possibleSigns = range.to_a
end
desired_lenth = 10
while result.length < desired_lenth
result = possibleSigns.sample.to_s
end
result
end
puts passGen(1,1,1)
CodePudding user response:
Start with something simple and stupid:
passGen = (('0'..'9').to_a.sample(1) ('A'..'Z').to_a.sample(1) ('a'..'z').to_a.sample(8).shuffle).join
Technically speaking, this already fulfills your requirement. From the viewpoint of aesthetics and security, the disadvantage here is that the number of upper case characters is always 8. A more elegant solution would be to find three non-zero integers which add up to 10, and can be used as the arguments for the sample
call. Also, if no numbers are requested, you simply pass 0
as argument to sample
.
Since this exceeds the scope of your question, and I don't even know whether you want to go so far, I don't elaborate on this here further.