File Coverage

blib/lib/DBIx/Class/EncodedColumn.pm
Criterion Covered Total %
statement 48 48 100.0
branch 17 24 70.8
condition 9 15 60.0
subroutine 8 8 100.0
pod 3 3 100.0
total 85 98 86.7


line stmt bran cond sub pod time code
1             package DBIx::Class::EncodedColumn;
2              
3 3     3   2315 use strict;
  3         4  
  3         98  
4 3     3   12 use warnings;
  3         3  
  3         69  
5              
6 3     3   9 use base qw/DBIx::Class/;
  3         7  
  3         177  
7 3     3   10 use Sub::Name;
  3         3  
  3         603  
8              
9             __PACKAGE__->mk_classdata( '_column_encoders' );
10              
11             our $VERSION = '0.00015';
12             $VERSION = eval $VERSION;
13              
14             sub register_column {
15 12     12 1 236701 my $self = shift;
16 12         24 my ($column, $info) = @_;
17 12         44 $self->next::method(@_);
18              
19 12 100 100     3797 return unless exists $info->{encode_column} && $info->{encode_column} == 1;
20             $self->throw_exception("'encode_class' is a required argument.")
21 9 50 33     356 unless exists $info->{encode_class} && defined $info->{encode_class};
22 9         12 my $class = $info->{encode_class};
23              
24 9 100       21 my $args = exists $info->{encode_args} ? $info->{encode_args} : {};
25 9 50       24 $self->throw_exception("'encode_args' must be a hashref")
26             unless ref $args eq 'HASH';
27              
28 9         18 $class = join("::", 'DBIx::Class::EncodedColumn', $class);
29 9         442 eval "require ${class};";
30 9 50       33 $self->throw_exception("Failed to use encode_class '${class}': $@") if $@;
31              
32 9 50       11 defined( my $encode_sub = eval{ $class->make_encode_sub($column, $args) }) ||
  9         46  
33             $self->throw_exception("Failed to create encoder with class '$class': $@");
34 9 100       35 $self->_column_encoders({$column => $encode_sub, %{$self->_column_encoders || {}}});
  9         202  
35              
36 9 50 66     785 if ( exists $info->{encode_check_method} && $info->{encode_check_method} ){
37 3     3   15 no strict 'refs';
  3         2  
  3         742  
38 7 50       9 defined( my $check_sub = eval{ $class->make_check_sub($column, $args) }) ||
  7         20  
39             $self->throw_exception("Failed to create checker with class '$class': $@");
40 7         146 my $name = join '::', $self->result_class, $info->{encode_check_method};
41 7         1784 *$name = subname $name, $check_sub;
42             }
43             }
44              
45             sub set_column {
46 12     12 1 80896 my $self = shift;
47 12 100       47 return $self->next::method(@_) unless defined $_[1];
48 9         191 my $encs = $self->_column_encoders;
49 9 100 66     206 if(exists $encs->{$_[0]} && defined(my $encoder = $encs->{$_[0]})){
50 8         29 return $self->next::method($_[0], $encoder->($_[1]));
51             }
52 1         3 $self->next::method(@_);
53             }
54              
55             sub new {
56 3     3 1 328049 my($self, $attr, @rest) = @_;
57 3         108 my $encoders = $self->_column_encoders;
58 3         155 for my $col (grep { defined $encoders->{$_} } keys %$encoders ) {
  12         26  
59 12 50 33     23557 next unless exists $attr->{$col} && defined $attr->{$col};
60 12         48 $attr->{$col} = $encoders->{$col}->( $attr->{$col} );
61             }
62 3         6492 return $self->next::method($attr, @rest);
63             }
64              
65             1;
66              
67             __END__;
68              
69             =head1 NAME
70              
71             DBIx::Class::EncodedColumn - Automatically encode columns
72              
73             =head1 SYNOPSIS
74              
75             In your L<DBIx::Class> Result class
76             (sometimes erroneously referred to as the 'table' class):
77              
78             __PACKAGE__->load_components(qw/EncodedColumn ... Core/);
79              
80             #Digest encoder with hex format and SHA-1 algorithm
81             __PACKAGE__->add_columns(
82             'password' => {
83             data_type => 'CHAR',
84             size => 40,
85             encode_column => 1,
86             encode_class => 'Digest',
87             encode_args => {algorithm => 'SHA-1', format => 'hex'},
88             }
89              
90             #SHA-1 / hex encoding / generate check method
91             __PACKAGE__->add_columns(
92             'password' => {
93             data_type => 'CHAR',
94             size => 40 + 10,
95             encode_column => 1,
96             encode_class => 'Digest',
97             encode_args => {algorithm => 'SHA-1', format => 'hex', salt_length => 10},
98             encode_check_method => 'check_password',
99             }
100              
101             #MD5 / base64 encoding / generate check method
102             __PACKAGE__->add_columns(
103             'password' => {
104             data_type => 'CHAR',
105             size => 22,
106             encode_column => 1,
107             encode_class => 'Digest',
108             encode_args => {algorithm => 'MD5', format => 'base64'},
109             encode_check_method => 'check_password',
110             }
111              
112             #Eksblowfish bcrypt / cost of 8/ no key_nul / generate check method
113             __PACKAGE__->add_columns(
114             'password' => {
115             data_type => 'CHAR',
116             size => 59,
117             encode_column => 1,
118             encode_class => 'Crypt::Eksblowfish::Bcrypt',
119             encode_args => { key_nul => 0, cost => 8 },
120             encode_check_method => 'check_password',
121             }
122              
123             In your application code:
124              
125             #updating the value.
126             $row->password('plaintext');
127             my $digest = $row->password;
128              
129             #checking against an existing value with a check_method
130             $row->check_password('old_password'); #true
131             $row->password('new_password');
132             $row->check_password('new_password'); #returns true
133             $row->check_password('old_password'); #returns false
134              
135              
136             B<Note:> The component needs to be loaded I<before> Core and other components
137             such as Timestamp. Core should always be last.
138              
139             E.g:
140             __PACKAGE__->load_components(qw/EncodedColumn TimeStamp Core/);
141              
142             =head1 DESCRIPTION
143              
144             This L<DBIx::Class> component can be used to automatically encode a column's
145             contents whenever the value of that column is set.
146              
147             This module is similar to the existing L<DBIx::Class::DigestColumns>, but there
148             is some key differences:
149              
150             =over 4
151              
152             =item C<DigestColumns> performs the encode operation on C<insert> and C<update>,
153             and C<EncodedColumn> performs the operation when the value is set, or on C<new>.
154              
155             =item C<DigestColumns> supports only algorithms of the L<Digest> family.
156             C<EncodedColumn> employs a set of thin wrappers around different cipher modules
157             to provide support for any cipher you wish to use and wrappers are very simple
158             to write (typically less than 30 lines).
159              
160             =item C<EncodedColumn> supports having more than one encoded column per table
161             and each column can use a different cipher.
162              
163             =item C<Encode> adds only one item to the namespace of the object utilizing
164             it (C<_column_encoders>).
165              
166             =back
167              
168             There is, unfortunately, some features that C<EncodedColumn> doesn't support.
169             C<DigestColumns> supports changing certain options at runtime, as well as
170             the option to not automatically encode values on set. The author of this module
171             found these options to be non-essential and omitted them by design.
172              
173             =head1 Options added to add_column
174              
175             If any one of these options is present the column will be treated as a digest
176             column and all of the defaults will be applied to the rest of the options.
177              
178             =head2 encode_enable => 1
179              
180             Enable automatic encoding of column values. If this option is not set to true
181             any other options will become no-ops.
182              
183             =head2 encode_check_method => $method_name
184              
185             By using the encode_check_method attribute when you declare a column you
186             can create a check method for that column. The check method accepts a plain
187             text string, and returns a boolean that indicates whether the digest of the
188             provided value matches the current value.
189              
190             =head2 encode_class
191              
192             The class to use for encoding. Available classes are:
193              
194             =over 4
195              
196             =item C<Crypt::Eksblowfish::Bcrypt> - uses
197             L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt> and
198             requires L<Crypt::Eksblowfish::Bcrypt> to be installed
199              
200             =item C<Digest> - uses L<DBIx::Class::EncodedColumn::Digest>
201             requires L<Digest> to be installed as well as the algorithm required
202             (L<Digest::SHA>, L<Digest::Whirlpool>, etc)
203              
204             =item C<Crypt::OpenPGP> - L<DBIx::Class::EncodedColumn::Crypt::OpenPGP>
205             and requires L<Crypt::OpenPGP> to be installed
206              
207             =back
208              
209             Please see the relevant class's documentation for information about the
210             specific arguments accepted by each and make sure you include the encoding
211             algorithm (e.g. L<Crypt::OpenPGP>) in your application's requirements.
212              
213             =head1 EXTENDED METHODS
214              
215             The following L<DBIx::Class::ResultSource> method is extended:
216              
217             =over 4
218              
219             =item B<register_column> - Handle the options described above.
220              
221             =back
222              
223             The following L<DBIx::Class::Row> methods are extended by this module:
224              
225             =over 4
226              
227             =item B<new> - Encode the columns on new() so that copy and create DWIM.
228              
229             =item B<set_column> - Encode values whenever column is set.
230              
231             =back
232              
233             =head1 SEE ALSO
234              
235             L<DBIx::Class::DigestColumns>, L<DBIx::Class>, L<Digest>
236              
237             =head1 AUTHOR
238              
239             Guillermo Roditi (groditi) <groditi@cpan.org>
240              
241             Inspired by the original module written by Tom Kirkpatrick (tkp) <tkp@cpan.org>
242             featuring contributions from Guillermo Roditi (groditi) <groditi@cpan.org>
243             and Marc Mims <marc@questright.com>
244              
245             =head1 CONTRIBUTORS
246              
247             jshirley - J. Shirley <cpan@coldhardcode.com>
248              
249             kentnl - Kent Fredric <kentnl@cpan.org>
250              
251             mst - Matt S Trout <mst@shadowcat.co.uk>
252              
253             wreis - Wallace reis <wreis@cpan.org>
254              
255             =head1 COPYRIGHT
256              
257             Copyright (c) the DBIx::Class::EncodedColumn L</AUTHOR> and L</CONTRIBUTORS> as
258             listed above.
259              
260             =head1 LICENSE
261              
262             This library is free software and may be distributed under the same terms
263             as perl itself.
264              
265             =cut