File Coverage

blib/lib/Password/Policy.pm
Criterion Covered Total %
statement 102 106 96.2
branch 13 16 81.2
condition 11 23 47.8
subroutine 22 25 88.0
pod 3 7 42.8
total 151 177 85.3


line stmt bran cond sub pod time code
1             package Password::Policy;
2             $Password::Policy::VERSION = '0.03';
3             # ABSTRACT: Make managing multiple password strength profiles easy
4              
5 4     4   87206 use strict;
  4         9  
  4         168  
6 4     4   20 use warnings;
  4         5  
  4         125  
7              
8 4     4   1974 use Class::Load;
  4         97749  
  4         236  
9 4     4   1678 use Clone qw/clone/;
  4         9358  
  4         249  
10 4     4   1862 use Config::Any;
  4         30946  
  4         143  
11 4     4   34 use Try::Tiny;
  4         6  
  4         251  
12              
13 4     4   1706 use Password::Policy::Exception;
  4         7  
  4         84  
14 4     4   1438 use Password::Policy::Exception::EmptyPassword;
  4         8  
  4         89  
15 4     4   1367 use Password::Policy::Exception::InvalidAlgorithm;
  4         6  
  4         82  
16 4     4   1372 use Password::Policy::Exception::InvalidProfile;
  4         7  
  4         87  
17 4     4   1345 use Password::Policy::Exception::InvalidRule;
  4         7  
  4         96  
18 4     4   1418 use Password::Policy::Exception::NoAlgorithm;
  4         7  
  4         81  
19 4     4   1503 use Password::Policy::Exception::ReusedPassword;
  4         9  
  4         2742  
20              
21              
22              
23             sub new {
24 3     3 1 40 my ($class, %args) = @_;
25              
26 3         7 my $config_file = $args{config};
27 3   100     16 my $previous = $args{previous} || [];
28              
29 3         37 my $config = Config::Any->load_files({ files => [ $config_file ], use_ext => 1 });
30 3         94566 my $rules = {};
31              
32 3         12 $config = $config->[0]->{$config_file};
33 3         7 my @profiles = keys(%{$config});
  3         14  
34              
35 3         25 my $self = bless {
36             _config => $config,
37             _rules => $rules,
38             _previous => $previous,
39             _profiles => \@profiles,
40             } => $class;
41              
42 3         8 foreach my $key (@profiles) {
43 12         24 $rules->{$key} = $self->_parse_rules($key);
44             }
45              
46 3         5 $self->{_rules} = $rules;
47 3         14 return $self;
48             }
49              
50             sub _parse_rules {
51 21     21   25 my ($self, $profile_name) = @_;
52 21         15 my $rules;
53              
54 21         30 my $profile = clone $self->config->{$profile_name};
55 21 100       52 if(my $parent = delete $profile->{inherit}) {
56 9         21 $rules = $self->_parse_rules($parent);
57             }
58 21         21 foreach my $key (keys(%{$profile})) {
  21         45  
59 57 100       85 if($key eq 'algorithm') {
60 15         28 $rules->{algorithm} = $profile->{$key};
61 15         21 next;
62             }
63 42 100       56 if($rules->{$key}) {
64 9 50       34 $rules->{$key} = $profile->{$key} if($profile->{$key} > $rules->{$key});
65             } else {
66 33         62 $rules->{$key} = $profile->{$key};
67             }
68             }
69 21         55 return $rules;
70             }
71              
72             sub config {
73 21     21 0 189 return (shift)->{_config};
74             }
75              
76             sub profiles {
77 0     0 0 0 return (shift)->{_profiles};
78             }
79              
80             sub previous {
81 16     16 0 47 return (shift)->{_previous};
82             }
83              
84             sub rules {
85 16     16 0 26 my $self = shift;
86 16   100     64 my $profile = shift || 'default';
87 16         25 my $rules = $self->{_rules};
88 16   33     72 return $rules->{$profile} || Password::Policy::Exception::InvalidProfile->throw;
89             }
90              
91              
92             sub process {
93 12     12 1 277 my ($self, $args) = @_;
94 12   33     36 my $password = $args->{password} || Password::Policy::Exception::EmptyPassword->throw;
95              
96 12         40 my $rules = $self->rules($args->{profile});
97 12   33     37 my $algorithm = $rules->{algorithm} || Password::Policy::Exception::NoAlgorithm->throw;
98 12   50     50 my $algorithm_args = $rules->{algorithm_args} || {};
99 12         14 foreach my $rule (sort keys(%{$rules})) {
  12         73  
100 33 100       66 next if($rule eq 'algorithm');
101              
102 21         69 my $rule_class = 'Password::Policy::Rule::' . ucfirst($rule);
103             try {
104 21     21   544 Class::Load::load_class($rule_class);
105             } catch {
106 0     0   0 Password::Policy::Exception::InvalidRule->throw;
107 21         131 };
108 21         1485 my $rule_obj = $rule_class->new($rules->{$rule});
109 21         64 my $check = $rule_obj->check($password);
110 17 50       79 unless($check) {
111             # no idea what failed if we didn't get a more specific exception, so
112             # throw a generic error
113 0         0 Password::Policy::Exception->throw;
114             }
115             }
116              
117 8         43 my $enc_password = $self->encrypt({
118             password => $password,
119             algorithm => $algorithm,
120             algorithm_args => $algorithm_args
121             });
122              
123             # This is a post-encryption rule, so it's a special case.
124 8 50       26 if($self->previous) {
125 8         10 foreach my $previous_password (@{$self->previous}) {
  8         13  
126 5 100       19 Password::Policy::Exception::ReusedPassword->throw if($enc_password eq $previous_password);
127             }
128             }
129 6         43 return $enc_password;
130             }
131              
132              
133             sub encrypt {
134 8     8 1 11 my ($self, $args) = @_;
135              
136 8   33     21 my $password = $args->{password} ||Password::Policy::Exception::EmptyPassword->throw;
137 8   33     16 my $algorithm = $args->{algorithm} ||Password::Policy::Exception::NoAlgorithm->throw;
138 8   50     19 my $algorithm_args = $args->{algorithm_args} || {};
139              
140 8         17 my $enc_class = 'Password::Policy::Encryption::' . $algorithm;
141             try {
142 8     8   202 Class::Load::load_class($enc_class);
143             } catch {
144 0     0   0 Password::Policy::Exception::InvalidAlgorithm->throw;
145 8         49 };
146 8         453 my $enc_obj = $enc_class->new($algorithm_args);
147 8         22 my $new_password = $enc_obj->enc($password);
148 8         66 return $new_password;
149             }
150              
151              
152             1;
153              
154             __END__