File Coverage

blib/lib/DBIx/Class/PassphraseColumn.pm
Criterion Covered Total %
statement 57 58 98.2
branch 10 14 71.4
condition 4 8 50.0
subroutine 15 15 100.0
pod 3 3 100.0
total 89 98 90.8


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