File Coverage

blib/lib/CGI/Application/Plugin/Authentication/Store/Cookie.pm
Criterion Covered Total %
statement 80 80 100.0
branch 14 14 100.0
condition 4 5 80.0
subroutine 16 16 100.0
pod 5 5 100.0
total 119 120 99.1


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::Authentication::Store::Cookie;
2              
3 11     11   9336 use strict;
  11         23  
  11         456  
4 11     11   63 use warnings;
  11         21  
  11         600  
5             our $VERSION = '0.20';
6              
7 11     11   94 use base qw(CGI::Application::Plugin::Authentication::Store);
  11         23  
  11         7258  
8 11     11   6961 use MIME::Base64 ();
  11         11403  
  11         312  
9 11     11   9568 use Digest::SHA ();
  11         49508  
  11         311  
10 11     11   8693 use CGI::Cookie ();
  11         31900  
  11         11612  
11              
12             # CONFIGURABLE OPTIONS
13             #
14             # - SECRET
15             #
16             # If this value is defined, it will be used to protect
17             # the Cookie values from tampering. To generate a good
18             # secret, run the following perl script and cut and paste
19             # the value it generates into this variable.
20             #
21             # perl -MDigest::MD5=md5_base64 -l -e 'print md5_base64($$,time(),rand(9999))'
22             #
23             our $SECRET = '';
24              
25              
26             =head1 NAME
27              
28             CGI::Application::Plugin::Authentication::Store::Cookie - Cookie based Store
29              
30             =head1 VERSION
31              
32             This document describes CGI::Application::Plugin::Authentication version 0.20
33              
34             =head1 SYNOPSIS
35              
36             use base qw(CGI::Application);
37             use CGI::Application::Plugin::Session;
38             use CGI::Application::Plugin::Authentication;
39              
40             __PACKAGE__->authen->config(
41             STORE => ['Cookie', SECRET => "Shhh, don't tell anyone", NAME => 'CAPAUTH_DATA', EXPIRY => '+1y'],
42             );
43              
44             =head1 DESCRIPTION
45              
46             This module uses a cookie to store authentication information across multiple requests.
47             It works by creating a cookie that contains the information we would like to store (like
48             the name of the user that is currently authenticated), and then base64 encoding
49             the data. In order to ensure that the information is not manipulated by the end-user, we include
50             a CRC checksum that is generated along with our secret. Since the user does not know the value
51             of the secret, they will not be able to recreate the checksum if they change some of the values, so we
52             will be able to tell if the information in the cookie has been manipulated.
53              
54             =head1 THE SECRET
55              
56             =head2 Choosing a good secret
57              
58             An easy way to generate a relatively good secret is to run the following perl snippet:
59              
60             perl -MDigest::MD5=md5_base64 -l -e 'print md5_base64($$,time(),rand(9999))'
61              
62             Just use the resulting string as your secret.
63              
64             =head2 Configuring the secret
65              
66             There are three ways that you can provide a secret to the module:
67              
68             =over 4
69              
70             =item Hardcode the secret
71              
72             You can hardcode a secret right in the CGI::Application::Plugin::Authentication::Store::Cookie module,
73             so that you don't have to remember to provide one every time you use the module. Just open the
74             source in a text editor and look at the top of the file where it defines 'our $SECRET' and follow
75             the instruction listed there.
76              
77             =item Provide the SECRET option when using the module
78              
79             You can also just provide the secret as an option when using the module using the SECRET
80             parameter.
81              
82             __PACKAGE__->authen->config(
83             STORE => ['Cookie', SECRET => "Shhh, don't tell anyone"],
84             );
85              
86             =item Let the module choose a secret for you
87              
88             And lastly, if you forget to do either of these, the module will use the name of your application
89             as the secret, but that is not a very good value to use, so a warning will be spit out everytime
90             it uses this. This is the least desirable choice, and is only included as a last resort.
91              
92             =back
93              
94             =head1 DEPENDENCIES
95              
96             This module requires the following modules to be available.
97              
98             =over 4
99              
100             =item MIME::Base64
101              
102             =item Digest::SHA
103              
104             =item CGI::Cookie
105              
106             =back
107              
108             =head1 METHODS
109              
110             =head2 fetch
111              
112             This method accepts a list of parameters and fetches them from the cookie data.
113              
114             =cut
115              
116             sub fetch {
117 236     236 1 389 my $self = shift;
118 236         473 my @items = map { $self->{cookie}->{data}->{$_} } @_;
  236         1043  
119 236         1077 return @items[0..$#items];
120             }
121              
122             =head2 save
123              
124             This method accepts a hash of parameters and values and stores them in the cookie data.
125              
126             =cut
127              
128             sub save {
129 26     26 1 72 my $self = shift;
130 26         117 my %items = @_;
131 26         122 while (my ($param, $value) = each %items) {
132 56         275 $self->{cookie}->{data}->{$param} = $value;
133             }
134 26         106 $self->_register_postrun_callback;
135 26         89 return 1;
136             }
137              
138             =head2 delete
139              
140             This method accepts a list of parameters and deletes them from the cookie data.
141              
142             =cut
143              
144             sub delete {
145 1     1 1 3 my $self = shift;
146 1         3 foreach my $param (@_) {
147 4         12 delete $self->{cookie}->{data}->{$param};
148             }
149 1         6 $self->_register_postrun_callback;
150 1         4 return 1;
151             }
152              
153             =head2 initialize
154              
155             This method will check for an existing cookie, and decode the contents for later retrieval.
156              
157             =cut
158              
159             sub initialize {
160 51     51 1 89 my $self = shift;
161              
162 51         368 my @options = $self->options;
163 51 100       242 die "Invalid Store Configuration for the Cookie store - options section must contain a hash of values" if @options % 2;
164 50         209 my %options = @options;
165 50         171 $self->{cookie}->{options} = \%options;
166              
167 50         371 my %cookies = CGI::Cookie->fetch;
168 50 100       2659 if ($cookies{$self->cookie_name}) {
169 6         955 my $rawdata = $cookies{$self->cookie_name}->value;
170 6         53 $self->{cookie}->{data} = $self->_decode($rawdata);
171             }
172             # $self->_register_postrun_callback;
173              
174 50         258 return;
175             }
176              
177             =head2 cookie_name
178              
179             This method will return the name of the cookie
180              
181             =cut
182              
183             sub cookie_name {
184 82     82 1 161 my $self = shift;
185 82   100     953 return $self->{cookie}->{options}->{NAME} || 'CAPAUTH_DATA';
186             }
187              
188             ###
189             ### Helper methods
190             ###
191              
192             # _register_postrun_callback
193             #
194             # We only register the postrun callback once a change has been made to the data
195             # so that we don't unecesarily send out a cookie.
196             sub _register_postrun_callback {
197 27     27   49 my $self = shift;
198 27 100       132 return if $self->{cookie}->{postrun_registered}++;
199              
200 26         179 $self->authen->_cgiapp->add_callback('postrun', \&_postrun_callback);
201 26         450 return;
202             }
203              
204             # _postrun_callback
205             #
206             # This callback will add a cookie to the outgoing headers at the postrun stage
207             sub _postrun_callback {
208 26     26   1306 my $self = shift;
209              
210 26         112 my $store = $self->authen->store;
211 26         150 my $rawdata = $store->_encode($store->{cookie}->{data});
212              
213 26         144 my %cookie_params = (
214             -name => $store->cookie_name,
215             -value => $rawdata,
216             );
217 26 100       168 $cookie_params{'-expires'} = $store->{cookie}->{options}->{EXPIRY} if $store->{cookie}->{options}->{EXPIRY};
218 26         214 my $cookie = new CGI::Cookie(%cookie_params);
219 26         7906 $self->header_add(-cookie => [$cookie]);
220 26         2087 return;
221             }
222              
223             # _decode
224             #
225             # Take a raw cookie value, and decode and verify the data
226             sub _decode {
227 18     18   38 my $self = shift;
228 18         87 my $rawdata = MIME::Base64::decode(shift);
229 18 100       53 return if not $rawdata;
230              
231 17         75 my %hash = map { split /\=/, $_, 2 } split /\0/, $rawdata;
  43         147  
232              
233 17         208 my $checksum = delete $hash{c};
234             # verify checksum
235 17 100       60 if ($checksum eq Digest::SHA::sha1_base64(join("\0", $self->_secret, sort values %hash))) {
236             # Checksum verifies so the data is clean
237 15         71 return \%hash;
238             } else {
239             # The data could not be verified, so we trash it all
240 2         12 return;
241             }
242             }
243              
244             # _encode
245             #
246             # Take the data we want to store and encode the data into a cookie
247             sub _encode {
248 26     26   55 my $self = shift;
249 26         48 my $hash = shift;
250 26         137 my %hash = %$hash;
251              
252 26         124 my $checksum = Digest::SHA::sha1_base64(join("\0", $self->_secret, sort values %hash));
253 26         85 $hash{c} = $checksum;
254 26         81 my $rawdata = join("\0", map { join('=', $_, $hash{$_}) } keys %hash);
  78         295  
255 26         260 return MIME::Base64::encode($rawdata, "");
256             }
257              
258             # _secret
259             #
260             # A unique value for this application that is used to secure the Cookies
261             sub _secret {
262 43     43   74 my $self = shift;
263 43   66     247 my $secret = $self->{cookie}->{options}->{SECRET} || $SECRET;
264 43 100       113 unless ($secret) {
265 17         86 $secret = Digest::SHA::sha1_base64( ref $self->authen->_cgiapp );
266 17         217 warn "using default SECRET! Please provide a proper SECRET when using the Cookie store (See CGI::Application::Plugin::Authentication::Store::Cookie for details)";
267             }
268 43         1866 return $secret;
269             }
270              
271             =head1 SEE ALSO
272              
273             L, L, perl(1)
274              
275              
276             =head1 AUTHOR
277              
278             Cees Hek
279              
280              
281             =head1 LICENCE AND COPYRIGHT
282              
283             Copyright (c) 2005, SiteSuite. All rights reserved.
284              
285             This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
286              
287              
288             =head1 DISCLAIMER OF WARRANTY
289              
290             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.
291              
292             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
293              
294             =cut
295              
296             1;