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