File Coverage

blib/lib/Data/Passphrase.pm
Criterion Covered Total %
statement 34 112 30.3
branch 0 42 0.0
condition 0 12 0.0
subroutine 12 19 63.1
pod 5 7 71.4
total 51 192 26.5


line stmt bran cond sub pod time code
1             # $Id: Passphrase.pm,v 1.19 2007/08/14 15:45:51 ajk Exp $
2              
3 1     1   1144 use strict;
  1         4  
  1         52  
4 1     1   8 use warnings;
  1         3  
  1         58  
5              
6             package Data::Passphrase; {
7 1     1   1653 use Object::InsideOut qw(Exporter);
  1         158276  
  1         7  
8              
9 1     1   22349 use Readonly;
  1         17322  
  1         1255  
10              
11             Readonly my $DEFAULT_RULES_FILE => '/etc/passphrase_rules';
12             Readonly my $RULE_ERROR_CODE => 550;
13              
14             # evaluate to passphrase text in string context
15 1     1 0 12 sub as_string :Stringify { $_[0]->get_passphrase() }
  1     0   3  
  1         12  
  0         0  
16              
17             # evaluate to passphrase length in numeric context
18             sub as_number :Numerify {
19 0     0 0 0 my ($self) = @_;
20 0         0 my $passphrase = $self->get_passphrase();
21 0 0       0 return defined $passphrase ? length $passphrase : 0;
22 1     1   834 }
  1         1  
  1         5  
23              
24 1     1   1982 use version; our $VERSION = qv('0.0.7');
  1         6898  
  1         8  
25              
26 1     1   1100 use Data::Passphrase::Ruleset;
  1         5  
  1         10  
27 1     1   1387 use Carp;
  1         3  
  1         303  
28 1     1   3491 use Fatal qw(open close);
  1         33280  
  1         9  
29 1     1   3862 use HTTP::Status;
  1         6274  
  1         355  
30              
31             # export procedural subroutine
32             BEGIN {
33 1     1   1278 our @EXPORT_OK = qw(validate_passphrase);
34             }
35              
36             # object attributes
37             my @code :Field(Std => 'code', );
38             my @custom :Field(Std => 'custom', Type => 'Hash_ref' );
39             my @debug :Field(Std => 'debug', Type => 'Numeric' );
40             my @message :Field(Std => 'message' );
41             my @passphrase :Field(Std => 'passphrase' );
42             my @ruleset :Field(Std => 'ruleset', );
43             my @score :Field(Std => 'score', Type => 'Numeric' );
44             my @username :Field(Std => 'username' );
45              
46             my %init_args :InitArgs = (
47             code => { Field => \@code, Type => 'Numeric' },
48             custom => { Field => \@custom, Type => 'Hash_ref'},
49             debug => { Def => 0, Field => \@debug, Type => 'Numeric' },
50             message => { Field => \@message },
51             passphrase => { Field => \@passphrase },
52             ruleset => { Field => \@ruleset },
53             score => { Field => \@score, Type => 'Numeric' },
54             username => { Field => \@username },
55             );
56              
57             sub new {
58 0     0 1   my ($class, $arg_ref) = @_;
59              
60             # unpack arguments
61 0           my $debug = $arg_ref->{debug};
62              
63 0 0         $debug and warn 'initializing ', __PACKAGE__, ' object';
64              
65             # select a default rules file
66 0           my $rules_file;
67 0 0         if (!exists $arg_ref->{ruleset}) {
    0          
68 0 0         $debug and warn 'autoconstructing ruleset with default file';
69 0           $rules_file = $DEFAULT_RULES_FILE;
70             }
71              
72             # allow
73             elsif (!ref $arg_ref->{ruleset}) {
74 0           $rules_file = $arg_ref->{ruleset};
75             }
76              
77             # autoconstruct ruleset object
78 0 0         if ($rules_file) {
79 0           $arg_ref->{ruleset} = Data::Passphrase::Ruleset->new({
80             debug => $debug,
81             file => $rules_file,
82             });
83             }
84              
85             # construct object
86 0           my $self = $class->Object::InsideOut::new($arg_ref);
87              
88 0           return $self;
89             }
90              
91             # access a hash with custom user data for use in rules
92             sub get_data {
93 0     0 1   my ($self, $name) = @_;
94 0           my $custom_data = $self->get_custom();
95 0 0         return defined $name ? $custom_data->{$name} : $custom_data;
96             }
97              
98             # set custom data values
99             sub set_data {
100 0     0 1   my ($self, $name, $value) = @_;
101 0           my $custom_data = $self->get_custom();
102 0           $custom_data->{$name} = $value;
103 0           $self->set_custom($custom_data);
104             }
105              
106             # check the passphrase against rules
107             sub validate {
108 0     0 1   my ($self) = @_;
109              
110             # unpack attributes
111 0           my $debug = $self->get_debug ();
112 0           my $passphrase = $self->get_passphrase();
113              
114             # reset code, message, & score
115 0           $self->set_code (undef);
116 0           $self->set_message(undef);
117 0           $self->set_score (0 );
118              
119             # declare failure status variables
120 0           my ($first_failure_code, $first_failure_message);
121              
122             # collect a score from each rule & remember the lowest
123 0           my @scores = ();
124 0           my $minimum_score;
125              
126             # iterate through rules
127 0           my $ruleset = $self->get_ruleset();
128 0           my $passing_score = $ruleset->get_passing_score();
129 0           my @rules = @{ $ruleset->get_rules() };
  0            
130 0 0         $debug and warn 'invoking ', scalar @rules, ' rules';
131 0           foreach my $rule (@rules) {
132              
133             # unpack rule attributes
134 0           my $code = $rule->get_code ();
135 0           my $disabled = $rule->get_disabled();
136 0           my $message = $rule->get_message ();
137 0           my $validate = $rule->get_validate();
138              
139             # skip test-only rules
140 0 0 0       next if !defined $validate || $disabled;
141              
142 0 0         $debug and warn 'invoking rule: ',
    0          
143             defined $message ? $message : '[message not available]';
144              
145             # call the subroutine of the next rule, passing data hash
146 0           my $score = eval { $validate->($self, $self->get_data()) };
  0            
147              
148             # catch errors
149 0 0         if ($@) {
150 0           carp $@;
151 0           $self->set_code ($RULE_ERROR_CODE);
152 0           $self->set_message('rule error' );
153 0           return;
154             }
155              
156             # the lowest score will be the resultant score for the ruleset
157 0 0 0       if (!defined $minimum_score || $score < $minimum_score) {
158 0           $minimum_score = $score;
159             }
160              
161             # handle failure
162 0 0         if ($score < $passing_score) {
163              
164             # let the validate method set these if it wants to
165 0 0         if ( !defined $self->get_code() ) {
166 0           $self->set_code($code);
167             }
168 0 0         if ( !defined $self->get_message() ) {
169 0           $self->set_message($message);
170             }
171              
172             # the first code & message will be resultant for the ruleset
173 0   0       $first_failure_code ||= $code;
174 0   0       $first_failure_message ||= $message;
175             }
176              
177             # a score of -1 means short-circuit
178 0 0         last if $score == -1;
179             }
180              
181 0 0         if (defined $first_failure_code) {
182 0           $self->set_code ($first_failure_code );
183 0           $self->set_message($first_failure_message);
184             }
185             else {
186 0           $self->set_code(RC_OK);
187 0           $self->set_message('acceptable');
188             }
189              
190             # a passphrase is only as strong as its weakest link?
191 0           $self->set_score( int( $minimum_score * 100 ) );
192              
193 0           return;
194             }
195              
196             =begin WSDL
197              
198             _IN request $Data::Passphrase::Request request parameters
199             _RETURN $Data::Passphrase::Response response parameters
200             _DOC validate a passphrase
201              
202             =end WSDL
203              
204             =cut
205              
206             # procedural interface: given a passphrase and an optional
207             # username, validate the passphrase
208             sub validate_passphrase {
209 0     0 1   my ($class, $arg_ref) = @_;
210              
211             # accept class as first argument for use with SOAP::Lite
212 0 0         if (!defined $arg_ref) {
213 0           $arg_ref = $class;
214 0           $class = __PACKAGE__;
215             }
216              
217             # unpack arguments
218 0           my $debug = $arg_ref->{debug};
219              
220 0 0         $debug and warn 'validating supplied passphrase';
221 0           my $passphrase_object = $class->new($arg_ref);
222              
223 0 0         $debug and warn 'calling validate()';
224 0           $passphrase_object->validate(1);
225              
226             return {
227 0           code => $passphrase_object->get_code (),
228             message => $passphrase_object->get_message(),
229             score => $passphrase_object->get_score (),
230             };
231             }
232             }
233              
234             1;
235             __END__