File Coverage

blib/lib/DBIx/Class/PassphraseColumn.pm
Criterion Covered Total %
statement 54 55 98.1
branch 10 14 71.4
condition 4 8 50.0
subroutine 14 14 100.0
pod 3 3 100.0
total 85 94 90.4


line stmt bran cond sub pod time code
1 1     1   266738 use strict;
  1         8  
  1         45  
2 1     1   5 use warnings;
  1         1  
  1         41  
3              
4             package DBIx::Class::PassphraseColumn; # git description: 0.02-6-g962fbc2
5             # ABSTRACT: Automatically hash password/passphrase columns
6              
7             our $VERSION = '0.03';
8              
9 1     1   372 use Class::Load 'load_class';
  1         7884  
  1         49  
10 1     1   6 use Sub::Name 'subname';
  1         2  
  1         33  
11 1     1   5 use namespace::clean;
  1         1  
  1         5  
12              
13 1     1   168 use parent 'DBIx::Class';
  1         1  
  1         6  
14              
15             #pod =head1 SYNOPSIS
16             #pod
17             #pod __PACKAGE__->load_components(qw(PassphraseColumn));
18             #pod
19             #pod __PACKAGE__->add_columns(
20             #pod id => {
21             #pod data_type => 'integer',
22             #pod is_auto_increment => 1,
23             #pod },
24             #pod passphrase => {
25             #pod data_type => 'text',
26             #pod passphrase => 'rfc2307',
27             #pod passphrase_class => 'SaltedDigest',
28             #pod passphrase_args => {
29             #pod algorithm => 'SHA-1',
30             #pod salt_random => 20,
31             #pod },
32             #pod passphrase_check_method => 'check_passphrase',
33             #pod },
34             #pod );
35             #pod
36             #pod __PACKAGE__->set_primary_key('id');
37             #pod
38             #pod
39             #pod In application code:
40             #pod
41             #pod # 'plain' will automatically be hashed using the specified passphrase_class
42             #pod # and passphrase_args. The result of the hashing will stored in the
43             #pod # specified encoding
44             #pod $rs->create({ passphrase => 'plain' });
45             #pod
46             #pod my $row = $rs->find({ id => $id });
47             #pod my $passphrase = $row->passphrase; # an Authen::Passphrase instance
48             #pod
49             #pod if ($row->check_passphrase($input)) { ...
50             #pod
51             #pod $row->passphrase('new passphrase');
52             #pod $row->passphrase( Authen::Passphrase::RejectAll->new );
53             #pod
54             #pod =head1 DESCRIPTION
55             #pod
56             #pod This component can be used to automatically hash password columns using any
57             #pod scheme supported by L whenever the value of these columns is
58             #pod changed.
59             #pod
60             #pod =head1 COMPARISON TO SIMILAR MODULES
61             #pod
62             #pod This module is similar to both L and
63             #pod L. Here's a brief comparison that might help you
64             #pod decide which one to choose.
65             #pod
66             #pod =over 4
67             #pod
68             #pod =item * C performs the hashing operation on C and
69             #pod C. C and C perform the operation when
70             #pod the value is set, or on C.
71             #pod
72             #pod =item * C supports only algorithms of the Digest family.
73             #pod
74             #pod =item * C employs a set of thin wrappers around different cipher
75             #pod modules to provide support for any cipher you wish to use and wrappers are very
76             #pod simple to write.
77             #pod
78             #pod =item * C delegates password hashing and encoding to
79             #pod C, which already has support for a huge number of hashing
80             #pod schemes. Writing a new C subclass to support other schemes
81             #pod is easy.
82             #pod
83             #pod =item * C and C require all values in a hashed column to
84             #pod use the same hashing scheme. C stores both the hashed
85             #pod passphrase value I the scheme used to hash it. Therefore it's possible to
86             #pod have different rows using different hashing schemes.
87             #pod
88             #pod This is especially useful when, for example, being tasked with importing records
89             #pod (e.g. users) from a legacy application, that used a certain hashing scheme and
90             #pod has no plain-text passwords available, into another application that uses
91             #pod another hashing scheme.
92             #pod
93             #pod =item * C and C support having more than one hashed
94             #pod column per table and each column can use a different hashing
95             #pod scheme. C is limited to one hashed column per table.
96             #pod
97             #pod =item * C supports changing certain options at runtime, as well as the
98             #pod option to not automatically hash values on set. Neither C nor
99             #pod C support this.
100             #pod
101             #pod =back
102             #pod
103             #pod =head1 OPTIONS
104             #pod
105             #pod This module provides the following options for C:
106             #pod
107             #pod =begin :list
108             #pod
109             #pod = C<< passphrase => $encoding >>
110             #pod
111             #pod This specifies the encoding that passphrases will be stored in. Possible values are
112             #pod C and C. The value of C<$encoding> is passed on unmodified to the
113             #pod C option provided by
114             #pod L. Please refer to its
115             #pod documentation for details.
116             #pod
117             #pod = C<< passphrase_class => $name >>
118             #pod
119             #pod When receiving a plain string value for a passphrase, that value will be hashed
120             #pod using the C subclass specified by C<$name>. A value of
121             #pod C, for example, will cause passphrases to be hashed using
122             #pod C.
123             #pod
124             #pod = C<< passphrase_args => \%args >>
125             #pod
126             #pod When attempting to hash a given passphrase, the C<%args> specified in this
127             #pod options will be passed to the constructor of the C class
128             #pod specified using C, in addition to the actual password to hash.
129             #pod
130             #pod = C<< passphrase_check_method => $method_name >>
131             #pod
132             #pod If this option is specified, a method with the name C<$method_name> will be
133             #pod created in the result class. This method takes one argument, a plain text
134             #pod passphrase, and returns a true value if the provided passphrase matches the
135             #pod encoded passphrase stored in the row it's being called on.
136             #pod
137             #pod =end :list
138             #pod
139             #pod =cut
140              
141             __PACKAGE__->load_components(qw(InflateColumn::Authen::Passphrase));
142              
143             __PACKAGE__->mk_classdata('_passphrase_columns');
144              
145             #pod =method register_column
146             #pod
147             #pod Chains with the C method in C, and sets up
148             #pod passphrase columns according to the options documented above. This would not
149             #pod normally be directly called by end users.
150             #pod
151             #pod =cut
152              
153             sub register_column {
154 3     3 1 3717 my ($self, $column, $info, @rest) = @_;
155              
156 3 100       10 if (my $encoding = $info->{passphrase}) {
157 2         6 $info->{inflate_passphrase} = $encoding;
158              
159             $self->throw_exception(q['passphrase_class' is a required argument])
160             unless exists $info->{passphrase_class}
161 2 50 33     11 && defined $info->{passphrase_class};
162              
163 2         7 my $class = 'Authen::Passphrase::' . $info->{passphrase_class};
164 2         7 load_class $class;
165              
166 2   50     20081 my $args = $info->{passphrase_args} || {};
167 2 50       8 $self->throw_exception(q['passphrase_args' must be a hash reference])
168             unless ref $args eq 'HASH';
169              
170             my $encoder = sub {
171 4     4   11 my ($val) = @_;
172 4         9 $class->new(%{ $args }, passphrase => $val)->${\"as_${encoding}"};
  4         37  
  4         64219  
173 2         11 };
174              
175             $self->_passphrase_columns({
176 2 100       4 %{ $self->_passphrase_columns || {} },
  2         71  
177             $column => $encoder,
178             });
179              
180 2 50       289 if (defined(my $meth = $info->{passphrase_check_method})) {
181             my $checker = sub {
182 4     4   92825 my ($row, $val) = @_;
        4      
        4      
183 4         15 return $row->get_inflated_column($column)->match($val);
184 2         10 };
185              
186 2         49 my $name = join q[::] => $self->result_class, $meth;
187              
188             {
189 1     1   310 no strict 'refs';
  1         3  
  1         238  
  2         651  
190 2         24 *$name = subname $name => $checker;
191             }
192             }
193             }
194              
195 3         13 $self->next::method($column, $info, @rest);
196             }
197              
198             #pod =method set_column
199             #pod
200             #pod Hash a passphrase column whenever it is set.
201             #pod
202             #pod =cut
203              
204             sub set_column {
205 2     2 1 10895 my ($self, $col, $val, @rest) = @_;
206              
207 2         52 my $ppr_cols = $self->_passphrase_columns;
208             return $self->next::method($col, $ppr_cols->{$col}->($val), @rest)
209 2 50       58 if exists $ppr_cols->{$col};
210              
211 0         0 return $self->next::method($col, $val, @rest);
212             }
213              
214             #pod =method new
215             #pod
216             #pod Hash all passphrase columns on C so that C, C, and
217             #pod others B.
218             #pod
219             #pod =cut
220              
221             sub new {
222 2     2 1 483754 my ($self, $attr, @rest) = @_;
223              
224 2         125 my $ppr_cols = $self->_passphrase_columns;
225 2         65 for my $col (keys %{ $ppr_cols }) {
  2         10  
226 4 100 66     96 next unless exists $attr->{$col} && !ref $attr->{$col};
227 2         10 $attr->{$col} = $ppr_cols->{$col}->( $attr->{$col} );
228             }
229              
230 2         52 return $self->next::method($attr, @rest);
231             }
232              
233             #pod =head1 SEE ALSO
234             #pod
235             #pod L
236             #pod
237             #pod L
238             #pod
239             #pod L
240             #pod
241             #pod =cut
242              
243             1;
244              
245             __END__