Password: add structured answer.
Also, a nearly-complete rewrite to make it works as intended, while still being easy to follow.master
parent
3327cbd675
commit
84c14f7c20
|
@ -3,6 +3,10 @@ package DDG::Goodie::Password;
|
||||||
|
|
||||||
use DDG::Goodie;
|
use DDG::Goodie;
|
||||||
|
|
||||||
|
use List::MoreUtils qw( none );
|
||||||
|
use List::Util qw( min max first );
|
||||||
|
use Scalar::Util qw( looks_like_number );
|
||||||
|
|
||||||
primary_example_queries 'random password', 'random password strong 15';
|
primary_example_queries 'random password', 'random password strong 15';
|
||||||
description 'generates a random password';
|
description 'generates a random password';
|
||||||
name 'Password';
|
name 'Password';
|
||||||
|
@ -14,110 +18,80 @@ topics 'cryptography';
|
||||||
zci answer_type => 'pw';
|
zci answer_type => 'pw';
|
||||||
zci is_cached => 0;
|
zci is_cached => 0;
|
||||||
|
|
||||||
triggers any => 'random', 'password', 'pw', 'pwgen';
|
triggers start => 'password', 'random password', 'pw', 'random pw', 'pwgen';
|
||||||
|
|
||||||
# exclude lower case l's, upper case I's and O's.
|
my %look_alikes = map { $_ => 1 } qw(l O I); # Exclude alphabet characters which can be easily visually confused.
|
||||||
my @pwgen_avg = ('a'..'k','m'..'z','A'..'H','J'..'N','P'..'Z',0..9);
|
my %averages = map { $_ => 1 } (2 .. 9); # 0,1 missing for the same reasons as above.
|
||||||
my @pwgen_weak = ('a'..'k','m'..'z','A'..'H','J'..'N','P'..'Z');
|
my %highs = map { $_ => 1 } ('!', '@', '#', '$', '%', '^', '&', '*', '(', ')');
|
||||||
my @pwgen_strong = ('a'..'k','m'..'z','A'..'H','J'..'N','P'..'Z',0..9,'!','@','#','$','%','^','&','*','(',')');
|
|
||||||
|
my @pwgen_low = grep { !$look_alikes{$_} } ('a' .. 'z', 'A' .. 'Z');
|
||||||
|
my @pwgen_average = (@pwgen_low, keys %averages);
|
||||||
|
my @pwgen_high = (@pwgen_average, keys %highs);
|
||||||
|
|
||||||
my %pw_strengths = (
|
my %pw_strengths = (
|
||||||
'strong' => 'strong',
|
'strong' => 'high',
|
||||||
'hard' => 'strong',
|
'hard' => 'high',
|
||||||
'weak' => 'weak',
|
'easy' => 'low',
|
||||||
'average' => 'avg',
|
'weak' => 'low',
|
||||||
'avg' => 'avg',
|
'normal' => 'average',
|
||||||
|
'avg' => 'average',
|
||||||
);
|
);
|
||||||
|
|
||||||
handle query_lc => sub {
|
foreach my $value (values %pw_strengths) {
|
||||||
if ( $_ =~ /^\!?(?:(?:random |)password(?: generator|)|pw(?:gen|))(?: |)(\d+|strong|hard|weak|average|avg|)(?: |)(\d+|strong|hard|weak|average|avg|)$/i ) {
|
$pw_strengths{$value} = $value; # Add in self-refereces.
|
||||||
|
}
|
||||||
|
|
||||||
# Actually make it random.
|
my $strengths = join('|', keys %pw_strengths);
|
||||||
srand();
|
|
||||||
|
|
||||||
|
handle remainder => sub {
|
||||||
|
my $query = shift;
|
||||||
|
return if ($query && $query !~ /^(?<fw>\d+|$strengths|)\s+(?<sw>\d+|$strengths|)$/i);
|
||||||
|
|
||||||
my $var1 = $1 || '';
|
srand(); # Reseed on each request.
|
||||||
my $var2 = $2 || '';
|
|
||||||
|
|
||||||
# For debugging.
|
my @q_words = map { lc $_ } grep { defined } ($+{'fw'}, $+{'sw'});
|
||||||
# warn qq(VARS: $var1\t$var2\n);
|
|
||||||
|
|
||||||
my $pw_length = 8;
|
my $pw_length = first { looks_like_number($_) } @q_words;
|
||||||
$pw_length = $var1 if $var1 && $var1 =~ /^\d+$/;
|
$pw_length = ($pw_length) ? min(32, max(1, $pw_length)) : 8;
|
||||||
$pw_length = $var2 if $var2 && $var2 =~ /^\d+$/;
|
|
||||||
$pw_length = 1 if $pw_length < 1;
|
|
||||||
$pw_length = 32 if $pw_length > 32;
|
|
||||||
|
|
||||||
# For debugging.
|
my $strength_code = first { $_ && exists $pw_strengths{$_} } @q_words;
|
||||||
# warn $pw_length;
|
my $pw_strength = $pw_strengths{$strength_code || 'average'};
|
||||||
|
|
||||||
my $pw_strength = 'avg';
|
# Password.
|
||||||
$pw_strength = $pw_strengths{ lc $var1 } if $var1 && exists $pw_strengths{ lc $var1 };
|
my @pwgen;
|
||||||
$pw_strength = $pw_strengths{ lc $var2 } if $var2 && exists $pw_strengths{ lc $var2 };
|
|
||||||
|
|
||||||
# For debugging.
|
my @list_to_use = ($pw_strength eq 'low') ? @pwgen_low : ($pw_strength eq 'high') ? @pwgen_high : @pwgen_average;
|
||||||
# warn $pw_strength;
|
|
||||||
# warn exists $data->{pw_strengths}->{lc $var1};
|
|
||||||
|
|
||||||
# Password.
|
# Generate random password of the correct length.
|
||||||
my $pwgen = '';
|
while (scalar @pwgen < $pw_length) {
|
||||||
|
push @pwgen, $list_to_use[int rand scalar @list_to_use];
|
||||||
# Generate random password.
|
|
||||||
for ( my $i = 0 ; $i < $pw_length ; $i++ ) {
|
|
||||||
my $rand = rand;
|
|
||||||
|
|
||||||
if ( $pw_strength eq 'weak' ) {
|
|
||||||
$rand *= scalar(@pwgen_weak);
|
|
||||||
$rand = int($rand);
|
|
||||||
$pwgen .= $pwgen_weak[$rand];
|
|
||||||
} elsif ( $pw_strength eq 'strong' ) {
|
|
||||||
$rand *= scalar(@pwgen_strong);
|
|
||||||
$rand = int($rand);
|
|
||||||
$pwgen .= $pwgen_strong[$rand];
|
|
||||||
} else {
|
|
||||||
$rand *= scalar(@pwgen_avg);
|
|
||||||
$rand = int($rand);
|
|
||||||
$pwgen .= $pwgen_avg[$rand];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Include at least one #.
|
|
||||||
if ( ( $pw_strength eq 'avg' || $pw_strength eq 'strong' ) && $pwgen !~ /\d/ ) {
|
|
||||||
my $rand = rand;
|
|
||||||
$rand *= $pw_length - 1;
|
|
||||||
$rand = int($rand) + 1;
|
|
||||||
|
|
||||||
my $rand2 = rand;
|
|
||||||
$rand2 *= 9;
|
|
||||||
$rand2 = int($rand2);
|
|
||||||
|
|
||||||
# Splice in number.
|
|
||||||
$pwgen = substr( $pwgen, 0, $rand - 1 ) . $rand2 . substr( $pwgen, $rand );
|
|
||||||
}
|
|
||||||
|
|
||||||
# Include at least one special char.
|
|
||||||
if ( $pw_strength eq 'strong' && $pwgen !~ /[\!\@\#\$\%\^\&\*\(\)]/ ) {
|
|
||||||
my $rand = rand;
|
|
||||||
$rand *= $pw_length - 1;
|
|
||||||
$rand = int($rand) + 1;
|
|
||||||
|
|
||||||
my $rand2 = rand;
|
|
||||||
$rand2 *= 10;
|
|
||||||
$rand2 = int($rand2);
|
|
||||||
|
|
||||||
my @rand2 = ( '!', '@', '#', '$', '%', '^', '&', '*', '(', ')' );
|
|
||||||
|
|
||||||
# For debugging.
|
|
||||||
# warn "SPLICE $rand2", $rand2[$rand2], "\n";
|
|
||||||
|
|
||||||
# Splice in number.
|
|
||||||
$pwgen = substr( $pwgen, 0, $rand - 1 ) . $rand2[$rand2] . substr( $pwgen, $rand );
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add password for display.
|
|
||||||
return $pwgen." (random password)";
|
|
||||||
}
|
}
|
||||||
return;
|
if ($pw_strength ne 'low') {
|
||||||
|
# Make sure we have the characters we want;
|
||||||
|
replace_inside_with(\@pwgen, \%averages) if (none { $averages{$_} } @pwgen);
|
||||||
|
replace_inside_with(\@pwgen, \%highs) if ($pw_strength eq 'high' && none { $highs{$_} } @pwgen);
|
||||||
|
}
|
||||||
|
|
||||||
|
my $pw_string = join('', @pwgen);
|
||||||
|
|
||||||
|
# Add password for display.
|
||||||
|
return $pw_string . " (random password)",
|
||||||
|
structured_answer => {
|
||||||
|
input => [$pw_length . ' characters', $pw_strength . ' strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => $pw_string
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sub replace_inside_with {
|
||||||
|
my ($orig, $required_hash) = @_;
|
||||||
|
|
||||||
|
my @keys = keys %$required_hash;
|
||||||
|
|
||||||
|
# replace a random character in the original list with
|
||||||
|
# with a randomly selected key from our hash.
|
||||||
|
$orig->[int rand scalar @$orig] = $keys[int rand scalar @keys];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
1;
|
1;
|
||||||
|
|
99
t/Password.t
99
t/Password.t
|
@ -6,14 +6,99 @@ use Test::More;
|
||||||
use DDG::Test::Goodie;
|
use DDG::Test::Goodie;
|
||||||
|
|
||||||
zci answer_type => 'pw';
|
zci answer_type => 'pw';
|
||||||
zci is_cached => 0;
|
zci is_cached => 0;
|
||||||
|
|
||||||
ddg_goodie_test(
|
ddg_goodie_test(
|
||||||
[qw(
|
[qw( DDG::Goodie::Password)],
|
||||||
DDG::Goodie::Password
|
'random password weak 5' => test_zci(
|
||||||
)],
|
qr/.{5} \(random password\)/,
|
||||||
'password weak 5' => test_zci( qr/.{5} \(random password\)/),
|
structured_answer => {
|
||||||
'password 15 average' => test_zci( qr/.{15} \(random password\)/),
|
input => ['5 characters', 'low strength'],
|
||||||
'password strong 25' => test_zci( qr/.{25} \(random password\)/)
|
operation => 'random password',
|
||||||
|
result => qr/^.{5}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'password 5 EaSy' => test_zci(
|
||||||
|
qr/.{5} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['5 characters', 'low strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{5}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'password low 5' => test_zci(
|
||||||
|
qr/.{5} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['5 characters', 'low strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{5}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'pw 15 average' => test_zci(
|
||||||
|
qr/.{15} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['15 characters', 'average strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{15}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'password normal 15' => test_zci(
|
||||||
|
qr/.{15} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['15 characters', 'average strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{15}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'random pw 15 AVG' => test_zci(
|
||||||
|
qr/.{15} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['15 characters', 'average strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{15}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'pwgen strong 25' => test_zci(
|
||||||
|
qr/.{25} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['25 characters', 'high strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{25}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'password 25 hard' => test_zci(
|
||||||
|
qr/.{25} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['25 characters', 'high strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{25}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'Password High 25' => test_zci(
|
||||||
|
qr/.{25} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['25 characters', 'high strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{25}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
# Example queries
|
||||||
|
'random password' => test_zci(
|
||||||
|
qr/.{8} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['8 characters', 'average strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{8}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
|
'random password strong 15' => test_zci(
|
||||||
|
qr/.{15} \(random password\)/,
|
||||||
|
structured_answer => {
|
||||||
|
input => ['15 characters', 'high strength'],
|
||||||
|
operation => 'random password',
|
||||||
|
result => qr/^.{15}$/
|
||||||
|
}
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
done_testing
|
done_testing
|
||||||
|
|
Loading…
Reference in New Issue