1#!/usr/bin/perl
2#
3#############################################################################
4# Authen::PluggableCaptcha
5# Pluggable Captcha system for perl
6# Copyright(c) 2006-2007, Jonathan Vanasco (cpan@2xlp.com)
7# Distribute under the Perl Artistic License
8#
9#############################################################################
10
11=head1 NAME
12
13Authen::PluggableCaptcha - A pluggable Captcha framework for Perl
14
15=head1 SYNOPSIS
16
17IMPORTANT-- the .03 release is incompatible with earlier versions.
18Most notably: all external hooks for hash mangling have been replaced with object methods ( ie: $obj->{'__Challenge'} is now $obj->challenge  ) and keyword arguments expecting a class name have the word '_class' as a suffix.
19
20Authen::PluggableCaptcha is a framework for creating Captchas , based on the idea of creating Captchas with a plugin architecture.
21
22The power of this module is that it creates Captchas in the sense that a programmer writes Perl modules-- not just in the sense that a programmer calls a Captcha library for display.
23
24The essence of a Captcha has been broken down into three components: KeyManager , Challenge and Render -- all of which programmers now have full control over.  Mix and match existing classes or create your own.  Authen::PluggableCaptcha helps you make your own captcha tests -- and it helps you do it fast.
25
26The KeyManager component handles creating & validatiing keys that are later used to uniquely identify a CAPTCHA.  By default the KeyManager uses a time-based key system, but it can be trivially extended to integrate with a database and make single-use keys.
27
28The Challenge component maps a key to a set of instructions, a user prompt , and a correct response.
29
30The render component is used to display the challenge - be it text, image or sound.
31
32
33  use Authen::PluggableCaptcha;
34  use Authen::PluggableCaptcha::Challenge::TypeString;
35  use Authen::PluggableCaptcha::Render::Image::Imager;
36
37  # create a new captcha for your form
38  my $captcha= Authen::PluggableCaptcha->new(
39    type=> "new",
40    seed=> $session->user->seed ,
41    site_secret=> $MyApp::Config::site_secret
42  );
43  my $captcha_publickey= $captcha->get_publickey();
44
45  # image captcha?  create an html link to your captcha script with the public key
46  my $html= qq|<img src="/path/to/captcha.pl?captcha_publickey=${captcha_publickey}"/>|;
47
48  # image captcha?  render it
49  my $existing_publickey= 'a33d8ce53691848ee1096061dfdd4639_1149624525';
50  my $existing_publickey = $apr->param('captcha_publickey');
51  my $captcha= Authen::PluggableCaptcha->new(
52    type=> 'existing' ,
53    publickey=> $existing_publickey ,
54    seed=> $session->user->seed ,
55    site_secret=> $MyApp::Config::site_secret
56  );
57
58  # save it as a file
59  my $as_string= $captcha->render(
60    challenge_class=> 'Authen::PluggableCaptcha::Challenge::TypeString',
61    render_class=>'Authen::PluggableCaptcha::Render::Image::Imager' ,
62    format=>'jpeg'
63  );
64  open(WRITE, ">test.jpg");
65  print WRITE $as_string;
66  close(WRITE);
67
68  # or serve it yourself
69  $r->add_header('Content Type: image/jpeg');
70  $r->print( $as_string );
71
72  # wait, what if we want to validate the captcha first?
73  my $captcha= Authen::PluggableCaptcha->new(
74    type=> 'existing' ,
75    publickey=> $apr->param('captcha_publickey'),
76    seed=> $session->user->seed ,
77    site_secret= $MyApp::Config::site_secret
78  );
79  if ( !$captcha->validate_response( user_response=> $apr->param('captcha_response') ) ) {
80	  my $reason= $captcha->get_error('validate_response');
81	  die "could not validate captcha because: ${reason}.";
82  };
83
84in the above example, $captcha->new just configures the captcha.  $captcha->render actually renders the image.
85if the captcha is expired (too old by the default configuration) , the default expired captcha  routine from the plugin will take place
86better yet, handle all the timely and ip/request validation in the application logic.  the timeliness just makes someone answer a captcha 1x every 5minutes, but doesn't prevent re/mis use
87
88render accepts a 'render_class' argument that will internally dispatch the routines to a new instance of that class.
89
90using this method, multiple renderings and formats can be created using a single key and challenge.
91
92=head1 DESCRIPTION
93
94Authen::PluggableCaptcha is a fully modularized and extensible system for making Pluggable Catpcha (Completely Automated Public Turing Test to Tell Computers and Humans Apart) tests.
95
96Pluggable?  All Captcha objects are instantiated and interfaced via the main module, and then manipulated to require various submodules as plug-ins.
97
98Authen::PluggableCaptcha borrows from the functionality in Apache::Session::Flex
99
100=head2 The Base Modules:
101
102=head3 KeyManager
103
104  Consolidates functionality previously found in KeyGenerator and KeyValidator
105
106  Generates , parses and validates publickeys which are used to validate and create captchas
107  Default is Authen::PluggableCaptcha::KeyManager , which makes a key %md5%_%time% and performs no additional checking
108
109  A subclass is highly recommended.
110  Subclasses can contain a regex or a bunch of DB interaction stuff to ensure a key is used only one time per ip address
111
112
113=head3 Challenge
114
115  simply put, a challenge is a test.
116  challenges internally require a ref to a KeyManager instance , it then maps that instance via it's own facilities into a test to render or validate
117  a challege generates 3 bits of text:
118	instructions
119	user_prompt
120	correct_response
121
122  a visual captcha would have user_prompt and correct_response as the same.
123  a text logic puzzle would not.
124
125=head3 Render
126
127  the rendering of a captcha for presentation to a user.
128  This could be an image, sound, block of (obfuscated?) html or just plain text
129
130=head1 Reasoning (reinventing the wheel)
131
132Current CPAN captcha modules all exhibit one or more of the following traits:
133
134=over
135
136=item -
137the module is tied heavily into a given image rendering library
138
139=item -
140the module only supports a single style of an image Catpcha
141
142=item -
143the module renders/saves the image to disk
144
145=back
146
147I wanted a module that works in a clustered environment, could be easily extended / implemented with the following design requirements:
148
149=over
150
151=item 1
152challenges are presented by a public_key
153
154=item 2
155a seed (sessionID ?) + a server key (siteSecret) hash together to create a public key
156
157=item 3
158the public_key is handled by its own module which can be subclassed and replaced as long as it provides the required methods
159
160=back
161
162with this method, generating a public key 'your own way' is very easy, so the module integrates easily into your app
163
164furthermore:
165
166=over
167
168=item *
169the public_key creates a captcha test / challenge ( instructions , user_prompt , correct_repsonse ) for presentation or validation
170
171=over
172
173=item -
174the captcha test is handled by its own module which can be subclassed as long as it provides the required methods
175
176=item -
177    want to upgrade a test? its right there
178
179=item -
180    want a private test?  create a new subclass
181
182=item -
183    want to add tests to cpan?  please do!
184
185=back
186
187=item *
188the rendering is then handled by its own module which can be subclassed as long as it provides the required methods
189
190=item *
191the rendering doesn't just render a jpg for a visual captcha... the captcha challenge can then be rendered in any format
192
193=over
194
195=item -
196image
197
198=item -
199audio
200
201=item -
202text
203
204=back
205
206=back
207
208any single component can be extended or replaced - that means you can cheaply/easily/quickly create new captchas as older ones get defeated. instead of going crazy trying to make the worlds best captcha, you can just make a ton of crappy ones that are faster to make than to break :)
209
210everything is standardized and made for modular interaction
211since the public_key maps to a captcha test, the same key can create an image/audio/text captcha,
212
213Note that Render::Image is never called - it is just a base class.
214The module ships with Render::Img::Imager, which uses the Imager library.  Its admittedly not very good- it is simple a proof-of-concept.
215
216want gd/imagemagick?  write Render::Img::GD or Render::Image::ImageMagick with the appropriate hooks (and submit to CPAN!)
217
218This functionality exists so that you don't need to run GD on your box if you've got a mod_perl setup that aready uses Imager.
219
220Using any of the image libraries should be a snap- just write a render class that can create an image with 'user_prompt' text, and returns 'as_string'
221Using any of the audio libraries will work in the same manner too.
222
223Initial support includes the ability to have Textual logic Catptchas.  They do silly things like say "What is one plus one ? (as text in english)"
224HTML::Email::Obfuscate makes these hard to scrape, though a better solution is needed and welcome.
225
226One of the main points of PluggableCaptcha is that even if you create a Captcha that is one step ahead of spammers ( read: assholes ) , they're not giving up -- they're just going to take longer to break the Captcha-- and once they do, you're sweating trying to protect yourself again.
227
228With PluggableCaptcha, it should be easier to :
229
230=over
231
232=item a-
233create new captchas cheaply: make a new logic puzzle , a new way of rendering images , or change the random character builder into something that creates strings that look like words, so people can spell them easier.
234
235=item b-
236customize existing captchas: subclass captchas from the distribution , or others people submit to CPAN. create some site specific changes on the way fonts are rendered, etc.
237
238=item c-
239constantly change captchas ON THE FLY.  mix and match render and challenge classes.  the only thing that would take much work is swapping from a text to an image.  but 1 line of code controls what is in the image, or how to solve it!
240
241=back
242
243Under this system, ideally, people can change / adapt / update so fast , that spammers never get a break in their efforts to break captcha schemes!
244
245
246=head1 CONSTRUCTOR
247
248=over 4
249
250=item B<new PARAMS>
251Returns a new L<Authen::PluggableCaptcha> object constructed according to PARAMS, where PARAMS are name/value pairs.
252
253PARAMS are name/value pairs.
254
255Required PARAMS are:
256
257=over 8
258
259=item C<type TYPE>
260
261Type of captcha. Valid options are 'new' or 'existing'
262
263=item C<seed TYPE>
264
265seed used for key management.  this could be a session id, a session id + url,  an empty string, or any other defined value.
266
267=item C<site_secret TYPE>
268
269site_secret used for key management.  this could be a shared value for your website.
270
271=back
272
273Optional PARAMS are:
274
275=over 8
276
277=item C<keymanager_args TYPE>
278
279The value for the keymanager_args key will be sent to the KeyManager on instantiation as 'keymanager_args'
280
281This is useful if you need to specify a DB connection or something similar to the keymanager
282
283=item C<do_not_validate_key INT>
284
285This is valid only for 'existing' type captchas.
286
287passing this argument as the integer '1'(1) will not validate the publickey in the keymanager.
288
289This is useful if you are externally handling the key management, and just use this package for Render + Challenge
290
291
292=back
293
294=head1 OBJECT METHODS
295
296=over 4
297
298=item B<captcha_type TYPE>
299
300get the captcha type
301
302=item B<keymanager>
303
304returns an instance of the active keymanager
305
306=item B<challenge_instance TYPE>
307
308returns an instance of a challenge class TYPE
309
310=item B<render_instance TYPE>
311
312returns an instance of a render class TYPE
313
314=item B<die_if_invalid>
315
316calls a die if the captcha is invalid
317
318=item B<get_publickey>
319
320returns a publickey from the keymanager.
321
322=item B<expire_publickey>
323
324instructs the keymanager to expire the publickey. on success returns 1 and sets the captcha as invalid and expired.  returns 0 on failure and -1 on error.
325
326=item B<validate_response>
327
328Validates a user response against the key/time for this captcha
329
330returns 1 on sucess, 0 on failure, -1 on error.
331
332=item B<render PARAMS>
333
334renders the captcha based on the kw_args submitted in PARAMS
335
336returns the rendered captcha as a string
337
338PARAMS are required name/value pairs.  Required PARAMS are:
339
340=over 8
341
342=item C<challenge_class TYPE>
343Full name of a Authen::PluggableCaptcha::Challenge derived class
344
345=item C<render_class TYPE>
346Full name of a Authen::PluggableCaptcha::Render derived class
347
348=back
349
350=back
351
352=head1 DEBUGGING
353
354Set the Following envelope variables for debugging
355
356	$ENV{'Authen::PluggableCaptcha-DEBUG_FUNCTION_NAME'}
357	$ENV{'Authen::PluggableCaptcha-BENCH_RENDER'}
358
359debug messages are sent to STDERR via the ErrorLoggingObject package
360
361
362
363=head1 BUGS/TODO
364
365This is an initial alpha release.
366
367There are a host of issues with it.  Most are discussed here:
368
369To Do:
370
371	priority | task
372	+++| clean up how stuff is stored / passed around / accessing defaults.  there's a lot of messy stuff with in regards to passing around default values and redundancy of vars
373	+++| create a better way to make attributes shared stored and accessed
374	++ | Imager does not have facilities right now to do a 'sine warp' easily.  figure some sort of text warping for the imager module.
375	++ | Port the rendering portions of cpan gd/imagemagick captchas to Img::(GD|ImageMagick)
376	++ | Img::Imager make the default font more of a default
377	++ | Img::Imager add in support to render each letter seperately w/a different font/size
378	+  | Img::Imager better handle as_string/save + support for png format etc
379	-  | is there a way to make the default font more cross platform?
380	-- | add a sound plugin ( text-logic might render that a trivial enhancement depending on how obfuscation treats display )
381
382
383=head1 STYLE GUIDE
384
385If you make your own subclasses or patches, please keep this information in mind:
386
387
388	The '.' and '..' prefixes are reserved namespaces ( ie: $self->{'.Attributes'} , $self->{'..Errors'} )
389
390	Generally: '.' prefixes a shared or inherited trait ; '..' prefixes an class private variable
391
392	If you see a function with _ in the code, its undocumented and unsupported.  Only write code against regular looking functions.  Never write code against _ or __ functions.  Never.
393
394=head1 REFERENCES
395
396Many ideas , most notably the approach to creating layered images, came from PyCaptcha , http://svn.navi.cx/misc/trunk/pycaptcha/
397
398=head1 AUTHOR
399
400Jonathan Vanasco , cpan@2xlp.com
401
402Patches, support, features, additional etc
403
404	Kjetil Kjernsmo, kjetilk@cpan.org
405
406
407=head1 COPYRIGHT AND LICENSE
408
409Copyright (C) 2006 by Jonathan Vanasco
410
411This library is free software; you can redistribute it and/or modify
412it under the same terms as Perl itself.
413
414=cut
415
416#############################################################################
417#head
418
419package Authen::PluggableCaptcha;
420
421use strict;
422use vars qw(@ISA $VERSION);
423$VERSION= '0.05';
424
425#############################################################################
426#ISA modules
427
428use Authen::PluggableCaptcha::ErrorLoggingObject ();
429use Authen::PluggableCaptcha::Helpers ();
430use Authen::PluggableCaptcha::StandardAttributesObject ();
431use Authen::PluggableCaptcha::ValidityObject ();
432@ISA= qw( Authen::PluggableCaptcha::ErrorLoggingObject Authen::PluggableCaptcha::StandardAttributesObject Authen::PluggableCaptcha::ValidityObject );
433
434#############################################################################
435#use constants
436
437use constant DEBUG_FUNCTION_NAME=> $ENV{'Authen::PluggableCaptcha-DEBUG_FUNCTION_NAME'} || 0;
438use constant DEBUG_VALIDATION=> $ENV{'Authen::PluggableCaptcha-DEBUG_VALIDATION'} || 0;
439use constant BENCH_RENDER=> $ENV{'Authen::PluggableCaptcha-BENCH_RENDER'} || 0;
440
441#############################################################################
442#use modules
443
444use Authen::PluggableCaptcha::KeyManager ();
445use Authen::PluggableCaptcha::Render ();
446
447#############################################################################
448#defined variables
449
450our %_DEFAULTS= (
451	time_expiry=> 300,
452	time_expiry_future=> 30,
453);
454
455our %__types= (
456	'existing'=> 1,
457	'new'=> 1
458);
459
460
461#############################################################################
462#begin
463BEGIN {
464	if ( BENCH_RENDER ) {
465		use Time::HiRes();
466	}
467};
468
469#############################################################################
470#subs
471
472# constructor
473sub new {
474	my  ( $proto , %kw_args )= @_;
475	my  $class= ref($proto) || $proto;
476	my  $self= bless ( {} , $class );
477
478	# make sure we have the requisite kw_args
479	my 	@_requires= qw( type seed site_secret );
480	Authen::PluggableCaptcha::Helpers::check_requires(
481		kw_args__ref=> \%kw_args,
482		error_message=> "Missing required element '%s' in new",
483		requires_array__ref=> \@_requires
484	);
485
486	if ( !$__types{$kw_args{'type'}} ) {
487		die "invalid type";
488	}
489
490	$self->_captcha_type( $kw_args{'type'} );
491
492	Authen::PluggableCaptcha::ErrorLoggingObject::_init( $self ); #re- ErrorLoggingObject
493
494	$self->seed( $kw_args{'seed'} );
495	$self->site_secret( $kw_args{'site_secret'} );
496	$self->time_expiry( $kw_args{'time_expiry'} || $Authen::PluggableCaptcha::_DEFAULTS{'time_expiry'} );
497	$self->time_expiry_future( $kw_args{'time_expiry_future'} || $Authen::PluggableCaptcha::_DEFAULTS{'time_expiry_future'} );
498	$self->time_now( time() );
499
500	my 	$keymanager_class= $kw_args{'keymanager_class'} || 'Authen::PluggableCaptcha::KeyManager';
501
502	unless ( $keymanager_class->can('generate_publickey') ) {
503		eval "require $keymanager_class" || die $@ ;
504	}
505	unless ( $keymanager_class->can('validate_publickey') ) {
506		die "keymanager_class can not validate_publickey" ;
507	}
508
509	$self->__keymanager_class( $keymanager_class );
510
511	my 	$keymanager= $self->__keymanager_class->new(
512		seed=> $self->seed ,
513		site_secret=> $self->site_secret ,
514		time_expiry=> $self->time_expiry ,
515		time_expiry_future=> $self->time_expiry_future ,
516		time_now=> $self->time_now ,
517		keymanager_args=> $kw_args{'keymanager_args'}
518	);
519	$self->_keymanager( $keymanager );
520
521	if ( $kw_args{'type'} eq 'existing' ) {
522		$self->__init_existing( \%kw_args );
523	}
524	else {
525		$self->__init_new( \%kw_args );
526	}
527	return $self;
528}
529
530sub captcha_type {
531	my 	( $self )= @_;
532	return $self->{'.captcha_type'};
533}
534sub _captcha_type {
535	my 	( $self , $set_val )= @_;
536	if 	( !defined $set_val ) {
537		die "no captcha_type specified"
538	}
539	$self->{'.captcha_type'}= $set_val;
540	return $self->{'.captcha_type'};
541}
542
543
544sub __init_existing {
545=pod
546existing captcha specific inits
547=cut
548	my 	( $self , $kw_args__ref )= @_;
549	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('__init_existing');
550	if ( ! defined $$kw_args__ref{'publickey'} || ! $$kw_args__ref{'publickey'} ) {
551		die "'publickey' must be supplied during init";
552	}
553	$self->publickey( $$kw_args__ref{'publickey'} );
554
555	if 	(
556			defined $$kw_args__ref{'do_not_validate_key'}
557			&&
558			( $$kw_args__ref{'do_not_validate_key'} == 1 )
559		)
560	{
561		$self->keymanager->publickey( $$kw_args__ref{'publickey'} );
562		return 1;
563	}
564
565	my 	$validate_result= $self->keymanager->validate_publickey( publickey=> $$kw_args__ref{'publickey'} );
566	DEBUG_VALIDATION && print STDERR "\n validate_result -> $validate_result ";
567
568	if ( $validate_result < 0 ) {
569		$self->keymanager->ACCEPTABLE_ERROR or die "Could not init_existing on keymanager";
570	}
571	elsif ( $validate_result == 0 ) {
572		$self->keymanager->ACCEPTABLE_ERROR or die "Could not init_existing on keymanager";
573	}
574	if ( $self->keymanager->EXPIRED ) {
575		$self->EXPIRED(1);
576		return 0;
577	}
578	if ( $self->keymanager->INVALID ) {
579		$self->INVALID(1);
580		return 0;
581	}
582	return 1;
583}
584
585
586sub __init_new {
587=pod
588new captcha specific inits
589=cut
590	my 	( $self , $kw_args__ref )= @_;
591	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('__init_new');
592
593	$self->keymanager->generate_publickey() or die "Could not generate_publickey on keymanager";
594	return 1;
595}
596
597
598sub __keymanager_class {
599	my 	( $self , $class )= @_;
600	if ( defined $class ){
601		$self->{'..keymanager_class'}= $class;
602	}
603	return $self->{'..keymanager_class'};
604}
605
606sub _keymanager {
607	my 	( $self , $instance )= @_;
608	die "no keymanager instance" unless $instance;
609	$self->{'..keymanager_instance'}= $instance;
610	return $self->{'..keymanager_instance'};
611}
612sub keymanager {
613	my 	( $self )= @_;
614	return $self->{'..keymanager_instance'};
615}
616
617
618
619sub render_instance {
620	my 	( $self , $render_instance_class )= @_;
621	die unless $render_instance_class ;
622	return $self->{'..render_instance'}{ $render_instance_class };
623}
624sub challenge_instance {
625	my 	( $self , $challenge_instance_class , $challenge_instance_object )= @_;
626	die unless $challenge_instance_class ;
627	return $self->{'..challenge_instance'}{ $challenge_instance_class };
628}
629
630sub _render_instance {
631	my 	( $self , $render_instance_class , $render_instance_object )= @_;
632	die unless $render_instance_class ;
633	die "no render_instance_object supplied" unless $render_instance_object;
634	$self->{'..render_instance'}{ $render_instance_class }= $render_instance_object;
635	return $self->{'..render_instance'}{ $render_instance_class };
636}
637
638sub _challenge_instance {
639	my 	( $self , $challenge_instance_class , $challenge_instance_object )= @_;
640	die unless $challenge_instance_class ;
641	die "no challenge_instance_object supplied" unless $challenge_instance_object;
642	$self->{'..challenge_instance'}{ $challenge_instance_class }= $challenge_instance_object;
643	return $self->{'..challenge_instance'}{ $challenge_instance_class };
644}
645
646
647sub die_if_invalid {
648	my 	( $self , %kw_args )= @_;
649	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('die_if_invalid');
650
651	if ( $self->INVALID ) {
652		die "Authen::PluggableCaptcha Invalid , can not '$kw_args{from_function}'";
653	}
654}
655
656sub get_publickey {
657=pod
658Generates a key that can be used to ( generate a captcha ) or ( validate a captcha )
659=cut
660	my 	( $self )= @_;
661	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('generate_publickey');
662
663	# die if the captcha is invalid
664	$self->die_if_invalid( from_function=> 'generate_publickey' );
665
666	return $self->keymanager->publickey;
667}
668
669
670sub expire_publickey {
671=pod
672Expires a publickey
673=cut
674	my 	( $self , %kw_args )= @_;
675	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('validate_response');
676
677	my 	$result= $self->keymanager->expire_publickey ;
678	if 	( $result == 1 )
679	{
680		$self->EXPIRED(1);
681		$self->INVALID(1);
682	}
683	return $result;
684}
685
686
687sub validate_response {
688=pod
689Validates a user response against the key/time for this captcha
690=cut
691	my 	( $self , %kw_args )= @_;
692	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('validate_response');
693
694	# die if the captcha is invalid
695	$self->die_if_invalid( from_function=> 'validate_response' );
696	if ( $self->EXPIRED ) {
697		$self->set_error( 'validate_response' , 'KEY expired' );
698		return 0;
699	}
700
701	# make sure we instantiated as an existing captcha
702	if ( $self->captcha_type ne 'existing' ) {
703		die "only 'existing' type can validate";
704	}
705
706	# make sure we have the requisite kw_args
707	my 	@_requires= qw( challenge_class user_response );
708	Authen::PluggableCaptcha::Helpers::check_requires(
709		kw_args__ref=> \%kw_args,
710		error_message=> "Missing required element '%s' in validate",
711		requires_array__ref=> \@_requires
712	);
713
714	# then actually validate the captcha
715
716	# generate a challenge if necessary
717	$self->_generate_challenge( challenge_class=>$kw_args{'challenge_class'} );
718	my 	$challenge_class= $kw_args{'challenge_class'};
719	my 	$challenge= $self->challenge_instance( $challenge_class );
720
721	# validate the actual challenge
722	if ( !$challenge->validate( user_response=> $kw_args{'user_response'} ) ) {
723		$self->set_error('validate_response',"INVALID user_response");
724		return 0;
725	}
726
727	return 1;
728}
729
730sub _generate_challenge {
731	my 	( $self , %kw_args )= @_;
732	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('_generate_challenge');
733
734	# make sure we instantiated as an existing captcha
735	if ( $self->captcha_type ne 'existing' ) {
736		die "only 'existing' type can _generate_challenge";
737	}
738
739	# make sure we have the requisite kw_args
740	my 	@_requires= qw( challenge_class );
741	Authen::PluggableCaptcha::Helpers::check_requires(
742		kw_args__ref=> \%kw_args,
743		error_message=> "Missing required element '%s' in _generate_challenge",
744		requires_array__ref=> \@_requires
745	);
746
747	my 	$challenge_class= $kw_args{'challenge_class'};
748	unless ( $challenge_class->can('generate_challenge') ) {
749		eval "require $challenge_class" || die $@ ;
750	}
751
752	# if we haven't created a challege for this output already, do so
753	if ( !$self->challenge_instance( $challenge_class ) ){
754		$self->__generate_challenge__actual( \%kw_args );
755	}
756}
757
758
759sub __generate_challenge__actual {
760=pod
761	actually generates the challenge for an item and caches internally
762=cut
763	my 	( $self , $kw_args__ref )= @_;
764	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('__generate_challenge__actual');
765	if ( !$$kw_args__ref{'challenge_class'} ) {
766		die "missing challenge_class in __generate_challenge__actual";
767	}
768
769	my 	$challenge_class= $$kw_args__ref{'challenge_class'};
770	delete  $$kw_args__ref{'challenge_class'};
771
772	$$kw_args__ref{'keymanager_instance'}= $self->keymanager || die "No keymanager";
773
774	my 	$challenge= $challenge_class->new( %{$kw_args__ref} );
775	$self->_challenge_instance( $challenge_class , $challenge );
776}
777
778sub render {
779	my 	( $self , %kw_args )= @_;
780	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('render');
781
782	# die if the captcha is invalid
783	$self->die_if_invalid( from_function=> 'render' );
784
785	# make sure we instantiated as an existing captcha
786	if ( $self->captcha_type ne 'existing' ) {
787		die "only 'existing' type can render";
788	}
789
790	# make sure we have the requisite kw_args
791	my 	@_requires= qw( render_class challenge_class );
792	Authen::PluggableCaptcha::Helpers::check_requires(
793		kw_args__ref=> \%kw_args,
794		error_message=> "Missing required element '%s' in render",
795		requires_array__ref=> \@_requires
796	);
797
798
799	my 	$render_class= $kw_args{'render_class'};
800	unless ( $render_class->can('render') ) {
801		eval "require $render_class" || die $@ ;
802	}
803
804	# if we haven't rendered for this output already, do so
805	if ( !$self->render_instance( $render_class ) )
806	{
807
808		# grab a ref to the challenge
809		# and supply the necessary refs
810
811		$self->_generate_challenge( challenge_class=>$kw_args{'challenge_class'} );
812		my 	$challenge_class= $kw_args{'challenge_class'};
813		$kw_args{'challenge_instance'}= $self->challenge_instance( $challenge_class );
814		$kw_args{'keymanager'}= $self->keymanager;
815		$self->__render_actual( \%kw_args );
816	}
817	return $self->render_instance( $render_class )->as_string();
818}
819
820
821sub __render_actual {
822=pod
823	actually renders an item and caches internally
824=cut
825	my 	( $self , $kw_args__ref )= @_;
826	DEBUG_FUNCTION_NAME && Authen::PluggableCaptcha::ErrorLoggingObject::log_function_name('__render_actual');
827
828	# make sure we have the requisite kw_args
829	my 	@_requires= qw( render_class challenge_class );
830	Authen::PluggableCaptcha::Helpers::check_requires(
831		kw_args__ref=> $kw_args__ref,
832		error_message=> "Missing required element '%s' in __render_actual",
833		requires_array__ref=> \@_requires
834	);
835
836	my 	$render_class= $$kw_args__ref{'render_class'};
837	delete  $$kw_args__ref{'render_class'};
838
839	BENCH_RENDER && { $self->{'time_to_render'}= Time::HiRes::time() };
840	my 	$render_object= $render_class->new( %{$kw_args__ref} );
841	if ( $self->EXPIRED ){
842		$render_object->init_expired( $kw_args__ref );
843	}
844	else {
845		$render_object->init_valid( $kw_args__ref );
846	}
847	$render_object->render();
848	$self->_render_instance( $render_class , $render_object );
849	BENCH_RENDER && { $self->{'time_to_render'}= Time::HiRes::time()- $self->{'time_to_render'} };
850}
851
852
853
854#############################################################################
8551;
856