File Coverage

blib/lib/CGI/Application/Plugin/CAPTCHA.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package CGI::Application::Plugin::CAPTCHA;
2              
3 4     4   586278 use strict;
  4         13  
  4         195  
4              
5 4     4   5342 use GD::SecurityImage;
  0            
  0            
6             use Digest::SHA1;
7             use vars qw($VERSION @EXPORT);
8              
9             require Exporter;
10              
11             @EXPORT = qw(
12             captcha_config
13             captcha_create
14             captcha_verify
15             );
16              
17             sub import { goto &Exporter::import }
18              
19             =head1 NAME
20              
21             CGI::Application::Plugin::CAPTCHA - Easily create, use, and verify CAPTCHAs in
22             CGI::Application-based web applications.
23              
24             =head1 VERSION
25              
26             Version 0.04
27              
28             =cut
29              
30             $VERSION = '0.04';
31              
32             =head1 SYNOPSIS
33              
34             # In your CGI::Application-based web application module. . .
35             use CGI::Application::Plugin::CAPTCHA;
36              
37             sub setup
38             {
39             my $self = shift;
40              
41             $self->run_modes([ qw/
42             create
43             # Your other run modes go here
44             /]);
45              
46             $self->captcha_config(
47             IMAGE_OPTIONS => {
48             width => 150,
49             height => 40,
50             lines => 10,
51             font => "/Library/Fonts/Arial",
52             ptsize => 18,
53             bgcolor => "#FFFF00",
54             },
55             CREATE_OPTIONS => [ 'ttf', 'rect' ],
56             PARTICLE_OPTIONS => [ 300 ],
57             );
58             }
59              
60             # Create a run mode that calls the CAPTCHA creation method...
61             sub create
62             {
63             my $self = shift;
64             return $self->captcha_create;
65             }
66            
67             # In a template far, far away. . .
68             (to generate a CAPTCHA image)
69              
70             # Back in your application, to verify the CAPTCHA...
71             sub some_other_runmode
72             {
73             my $self = shift;
74             my $request = $self->query;
75            
76             return unless $self->captcha_verify($request->cookie("hash"), $request->param("verify"));
77             }
78              
79             =head1 DESCRIPTION
80              
81             C allows programmers to easily add and
82             verify CAPTCHAs in their CGI::Application-derived web applications.
83              
84             A CAPTCHA (or Completely Automated Public Turing Test to Tell Computers
85             and Humans Apart) is an image with a random string of characters. A user must
86             successfully enter the random string in order to submit a form. This is a
87             simple (yet annoying) procedure for humans to complete, but one that is
88             significantly more difficult for a form-stuffing script to complete without
89             having to integrate some sort of OCR.
90              
91             CAPTCHAs are not a perfect solution. Any skilled, diligent cracker will
92             eventually be able to bypass a CAPTCHA, but it should be able to shut down
93             your average script-kiddie.
94              
95             C is a wrapper for L. It
96             makes it more convenient to access L functionality, and
97             gives a more L-like way of doing it.
98              
99             When a CAPTCHA is created with this module, raw image data is transmitted from
100             your web application to the client browser. A cookie containing a checksum is
101             also transmitted with the image. When the client submits their form for
102             processing (along with their verification of the random string),
103             C generates a checksum of the verification string the user
104             entered. If the newly generated checksum matches the checksum found in the
105             cookie, we trust that the CAPTCHA has been successfully entered, and we allow
106             the user to continue processing their form.
107              
108             The checksum is generated by taking the string in question, and joining it with
109             a SECRET. We then generate an SHA1 hex digest of the resulting string. The
110             end user will not be able to generate their own checksums to bypass the CAPTCHA
111             check, because they do not know the value of our SECRET. This means it is
112             important to choose a good value for your SECRET.
113              
114             An easy way to generate a relatively good secret is to run the following perl
115             snippet:
116              
117             perl -MDigest::SHA1=sha1_base64 -le 'print sha1_base64($$,time(),rand(9999))'
118              
119             The author recognizes that the transmission of a cookie with the CAPTCHA image
120             may not be a popular decision, and welcomes any patches from those who can
121             provide an equally easy-to-implement solution.
122              
123             =head1 FUNCTIONS
124              
125             =head2 captcha_config()
126              
127             This method is used to customize how new CAPTCHA images will be created.
128             Values specified here are passed along to the appropriate functions in
129             L when a new CAPTCHA is created.
130              
131             It is recommended that you call C in the C
132             method of your CGI::Application base class, and in the C method of
133             any derived applications.
134              
135             The following parameters are currently accepted:
136              
137             =head3 IMAGE_OPTIONS
138              
139             This specifies what options will be passed to the constructor of
140             L. Please see the documentation for L
141             for more information.
142              
143             =head3 CREATE_OPTIONS
144              
145             This specifies what options will be passed to the C method of
146             L. Please see the documentation for L
147             for more information.
148              
149             =head3 PARTICLE_OPTIONS
150              
151             This specifies what options will be passed to the C method of
152             L. Please see the documentation for L
153             for more information.
154              
155             =head3 SECRET
156              
157             This specifies the secret that will be used when generating the checksum hash.
158              
159             =cut
160              
161             sub captcha_config
162             {
163             my $self = shift;
164              
165             if (@_)
166             {
167             my $props;
168             if (ref($_[0]) eq 'HASH')
169             {
170             my $rthash = %{$_[0]};
171             $props = $self->_cap_hash($_[0]);
172             }
173             else
174             {
175             $props = $self->_cap_hash({ @_ });
176             }
177              
178             # Check for IMAGE_OPTIONS
179             if ($props->{IMAGE_OPTIONS})
180             {
181             die "captcha_config() error: parameter IMAGE_OPTIONS is not a hash reference" if ref $props->{IMAGE_OPTIONS} ne 'HASH';
182             $self->{__CAP__CAPTCHA_CONFIG}->{IMAGE_OPTIONS} = delete $props->{IMAGE_OPTIONS};
183             }
184              
185             # Check for CREATE_OPTIONS
186             if ($props->{CREATE_OPTIONS})
187             {
188             die "captcha_config() error: parameter CREATE_OPTIONS is not an array reference" if ref $props->{CREATE_OPTIONS} ne 'ARRAY';
189             $self->{__CAP__CAPTCHA_CONFIG}->{CREATE_OPTIONS} = delete $props->{CREATE_OPTIONS};
190             }
191              
192             # Check for PARTICLE_OPTIONS
193             if ($props->{PARTICLE_OPTIONS})
194             {
195             die "captcha_config() error: parameter PARTICLE_OPTIONS is not an array reference" if ref $props->{PARTICLE_OPTIONS} ne 'ARRAY';
196             $self->{__CAP__CAPTCHA_CONFIG}->{PARTICLE_OPTIONS} = delete $props->{PARTICLE_OPTIONS};
197             }
198              
199             # Check for SECRET
200             if ($props->{SECRET})
201             {
202             die "captcha_config() error: parameter SECRET is not a string" if ref $props->{SECRET};
203             $self->{__CAP__CAPTCHA_CONFIG}->{SECRET} = delete $props->{SECRET};
204             }
205              
206             # Check for DEBUG
207             if ($props->{DEBUG})
208             {
209             $self->{__CAP__CAPTCHA_CONFIG}->{DEBUG} = delete $props->{DEBUG};
210             }
211              
212             # If there are still entries left in $props then they are invalid
213             die "Invalid option(s) (".join(', ', keys %$props).") passed to captcha_config" if %$props;
214             }
215              
216             $self->{__CAP__CAPTCHA_CONFIG};
217             }
218              
219             =head2 captcha_create()
220              
221             Creates the CAPTCHA image, and return a cookie with the encrypted hash of the
222             random string. Takes no arguments.
223              
224             The cookie created in this method is named C, and contains only the
225             encrypted hash. Future versions of this module will allow you to specify
226             cookie options in greater detail.
227              
228             =cut
229              
230             sub captcha_create
231             {
232             my $self = shift;
233             my %image_options = %{ $self->{__CAP__CAPTCHA_CONFIG}->{ IMAGE_OPTIONS } };
234             my @create_options = @{ $self->{__CAP__CAPTCHA_CONFIG}->{ CREATE_OPTIONS } };
235             my @particle_options = @{ $self->{__CAP__CAPTCHA_CONFIG}->{ PARTICLE_OPTIONS } };
236             my $secret = $self->{__CAP__CAPTCHA_CONFIG}->{ SECRET } ;
237             my $debug = $self->{__CAP__CAPTCHA_CONFIG}->{ DEBUG } ;
238              
239             # Create the CAPTCHA image
240             my $image = GD::SecurityImage->new( %image_options );
241             $debug == 1 ? $image->random("ABC123") : $image->random;
242             $image->create ( @create_options );
243             $image->particle( @particle_options );
244             my ( $image_data, $mime_type, $random_string ) = $image->out;
245              
246             # check the secret
247             if (!$secret) {
248             $secret = Digest::SHA1::sha1_base64( ref $self );
249             warn "using default SECRET! Please provide a proper SECRET when using the CGI::Application::Plugin::CAPTCHA plugin";
250             }
251              
252             # Create the verification hash
253             my $hash = Digest::SHA1::sha1_base64(join("\0", $secret, $random_string));
254            
255             # Stuff the verification hash in a cookie and push it out to the
256             # client.
257             my $cookie = $self->query->cookie("hash" => $hash);
258             $self->header_type ( 'header' );
259             $self->header_props( -type => $mime_type, -cookie => [ $cookie ], -expires => '-1d', '-cache-control' => 'no-cache', -pragma => 'no-cache' );
260             return $image_data;
261             }
262              
263             =head2 captcha_verify()
264              
265             Verifies that the value entered by the user matches what was in the CAPTCHA
266             image. Argument 1 is the encrypted hash from the cookie sent by
267             C, and argument 2 is the value the user entered to verify
268             the CAPTCHA image. Returns true if the CAPTCHA was successfully verified, else
269             returns false.
270              
271             =cut
272              
273             sub captcha_verify
274             {
275             my ($self, $hash, $verify) = @_;
276             my $secret = $self->{__CAP__CAPTCHA_CONFIG}->{ SECRET } ;
277              
278             # check the secret
279             if (!$secret) {
280             $secret = Digest::SHA1::sha1_base64( ref $self );
281             warn "using default SECRET! Please provide a proper SECRET when using the CGI::Application::Plugin::CAPTCHA plugin";
282             }
283              
284             return 1 if Digest::SHA1::sha1_base64(join("\0", $secret, $verify)) eq $hash;
285             return 0;
286             }
287              
288             =head1 AUTHOR
289              
290             Jason A. Crome, C<< >>
291              
292             =head1 TODO
293              
294             =over 4
295              
296             =item *
297              
298             Allow C to take cookie configuration arguments.
299              
300             =item *
301              
302             Allow the plugin to actually create a run mode in your CGI::Application-based
303             webapp without the developer having to manually create one.
304              
305             =back
306              
307             =head1 BUGS
308              
309             Please report any bugs or feature requests to
310             C, or through the web interface at
311             L.
312             I will be notified, and then you'll automatically be notified of progress on
313             your bug as I make changes.
314              
315             =head1 CONTRIBUTING
316              
317             Patches, questions, and feedback are welcome.
318              
319             =head1 ACKNOWLEDGEMENTS
320              
321             A big thanks to Cees Hek for providing a great module for me to borrow code
322             from (L), to Michael Peters and Tony Fraser
323             for all of their valuable input, and to the rest who contributed ideas and
324             criticisms on the CGI::Application mailing list.
325              
326             Additional thanks to chorny and Cees for the various bug fixes and patches
327             they have submitted.
328              
329             =head1 SEE ALSO
330              
331             L
332             L
333             Wikipedia entry for CAPTCHA - L
334              
335             =head1 COPYRIGHT & LICENSE
336              
337             Copyright 2005-2011 Jason A. Crome, all rights reserved.
338              
339             This program is free software; you can redistribute it and/or modify it
340             under the same terms as Perl itself.
341              
342             =cut
343              
344             1; # End of CGI::Application::Plugin::CAPTCHA
345