Authen::PluggableCaptcha::Tutorial - How to use the Captcha System
my $captcha= Authen::PluggableCaptcha->new( type=>'new' , seed=> 'a' , site_secret=> 'z' ); my $captcha_publickey= $captcha->get_publickey();
my $captcha= Authen::PluggableCaptcha->new( type=> 'existing' , publickey=> $captcha_publickey, seed=> 'a' , site_secret=> 'z' ); my $as_string= $captcha->render( challenge_class=> 'Authen::PluggableCaptcha::Challenge::TypeString', render_class=>'Authen::PluggableCaptcha::Render::Image::Imager' , font_filename=> '/usr/X11R6/lib/X11/fonts/TTF/VeraMoIt.ttf', format=>'jpeg' ); # serve it directly, or write it open(WRITE, ">/path/to/dest.jpg"); print WRITE $as_string; close(WRITE);
again, create a new object
my $captcha= Authen::PluggableCaptcha->new( type=> 'existing' , publickey=> $captcha_publickey, seed=> 'a' , site_secret=> 'z' );
render it
my $as_string= $captcha->render( challenge_class=> 'Authen::PluggableCaptcha::Challenge::DoMath', render_class=>'Authen::PluggableCaptcha::Render::Text::HTML' );
now you can serve it directly, or write it-- its just html text.
note: if you put it into Tal or some other formats, it must be marked as a 'structure'
alternately, you could render with Authen::PluggableCaptcha::Render::Text::Plain, which does no obfuscation (bad! bad! bad!)
open(WRITE, ">/path/to/dest.html"); print WRITE $as_string; close(WRITE);
create an object
run the validation 1x through, just so we get the vars set up and can pull the correct_response for the success
my $i_doubt_this_will_work= $captcha->validate_response( challenge_class=> 'Authen::PluggableCaptcha::Challenge::TypeString' , user_response=>'a' ) ? "yes" : "no" ;
then we can toss in the correct response
my $success= $captcha->validate_response( challenge_class=> 'Authen::PluggableCaptcha::Challenge::TypeString' , user_response=>$captcha->challenge_object->('Authen::PluggableCaptcha::Challenge::TypeString')->correct_response ) ? "yes" : "no" ;
Originally Authen::PluggableCaptcha was designed for use in mod_perl under a clustered environment
This is how it is currently implemented on FindMeOn.com and RoadSound.com ( June 2006 )
sub CAPTCHA_init { my ( $pageObject , $sectionName , $overrideFlag )= @_; # pageObject- context object that includes access to session , account, and libapreq # sectionName- the name of the what is requesting a captcha. ie, your registration page would want to say 'registration', or a general auth page would say 'auth' # this is used to create a seed, so the user doesn't get an identical captcha on each page # overrideFlag- bool value to force a re-init of the captcha if ( $pageObject->{'CaptchaInstance'} && !$overrideFlag ) { return; } # the seed for the captcha is a hash of the sectionName and the user's session_id my $seed= md5_hex( $sectionName . '|' . $pageObject->{'PageUser'}->get_session_id() ); # we set/store the publickey to the captcha in the session. we could show it to people, but honestly we do it like this because store/retrieve is easier than generating/validating every damn time my $captcha_key= $pageObject->{'PageUser'}->get_session_stored("captcha_publickey__${sectionName}"); my $captcha; if ( $captcha_key ) { $captcha= Authen::PluggableCaptcha->new( type=> 'existing' , site_secret=> $FindMeOn::Config::site_secret , seed=> $seed , publickey=> $captcha_key, ); if ( $captcha->is_invalid() ) { $captcha_key= undef; $pageObject->{'_CaptchaInvalid'}= 1; } if ( $captcha->is_expired() ) { $pageObject->{'_CaptchaExpired'}= 1; $captcha_key= undef; } } if ( !$captcha_key ) { $captcha= Authen::PluggableCaptcha->new( type=> 'new' , site_secret=> $FindMeOn::Config::site_secret , seed=> $seed , ); $captcha_key= $captcha->get_publickey() or die "Error"; $pageObject->{'PageUser'}->set_session_stored("captcha_publickey__${sectionName}",$captcha_key); $captcha= Authen::PluggableCaptcha->new( type=> 'existing' , site_secret=> $FindMeOn::Config::site_secret , seed=> $seed , publickey=> $captcha_key, ); } $pageObject->{'CaptchaInstance'}= $captcha; }
a page that displays / verifies captchas just calls:
&CAPTCHA_init( $self , 'registration', 1 );
if you didn't read the section above on what CAPTCHA_init does, you should
the cliffnotes are this though:
$self= a page object that contains a context object with access to the session and libapreq functions 'registration'= the name of the page used in creating a public_key seed ( this way register and confirm pages don't have the same captcha ) 1= an override flag, that forces the captcha object to be reset, as sometimes we cache objects
make sure we called the init already
to show a text captcha, we can render this directly into a Petal document
%PetalPersonalizedHash= ( 'Captcha'=> { instructions=> undef, user_prompt=> $self->{'CaptchaInstance'}->render( challenge_class=> 'Authen::PluggableCaptcha::Challenge::DoMath', render_class=>'Authen::PluggableCaptcha:::Render::Text::HTML' ), }, );
if ( !$self->{'CaptchaInstance'}->validate_response( challenge_class=> 'Authen::PluggableCaptcha::Challenge::DoMath', user_response=> $self->{'Form'}->get_validated('captcha_response'), ) ) { $self->{'Form'}->set_error('captcha_response','That answer is not correct, please try again'); return $self->FORM_print(); } else { # we're valid! }
first print the img block on a page that called Captcha Init
my $captcha_embed= "<img src="/path/to/captcha/handler?section=registration" />";
then set up a sub / handler to generate the captcha on a url mapping
in this example, the captcha generator is in a central location -- /service/captcha/ -- so we supply the section name as a query arg. if the captcha generator were locked into a page, then you could just hardcode the section name
sub render_image { my ( $self )= @_ ; my $sectionName= $self->{'PageUser'}{'ApacheRequest'}->param('section'); #initialize the captcha &FindMeOn::Functions::Misc::CAPTCHA_init( $self , $sectionName ); $self->{'PageUser'}{'ApacheRequest'}->content_type('image/jpeg'); $self->{'__BODY'}= $self->{'CaptchaInstance'}->render( challenge_class=> 'Authen::PluggableCaptcha::Challenge::TypeString', render_class=>'Authen::PluggableCaptcha::Render::Image::Imager' , font_filename=> '/usr/X11R6/lib/X11/fonts/TTF/VeraMoIt.ttf', format=>'jpeg' ); return; }
There is a single constructer that 'forks' into 2 separate init routines based on an argument to new().
Both arguments create a new KeyManager instance before they fork.
The type='new' routine immediately calls the KeyManager method generate_publickey ( which could conceivably be a little resource intensive, if you've created a module that hits a db to check for collisions). The fork was an obvious solution to split unnecessary calls out for performance optimization (ie: only run what you need )
The type='existing' routine automatically validates the construction arguments via the KeyManger method validate_publickey, which is unnecessary for new captchas.
Originally there was a single 'new', and from that you could call either 'new()' or 'existing()' -- but more people like 1 line of code.
That depends on how the KeyManager class you specify uses the site_secret to validate the key (which is why site_secret is not required in the base class , it can be an empty string ). A DB backed key does not need the sitesecret for validation. A logic backed key needs all the construction args to validate.
For example:
key= md5( $site_secret , $time , $page_name , $session_id ) + ':' + $session key= 'xxxxxxxxxxxxxxxxxx:10000001'
If we know the site_secret under that formula, we always have every components of the item at our disposal -- and can validate the key for integrity
The default KeyManager class uses a site_secret to create the key.
Yes. it renders directly on this example 'Generate a Captcha' above.
I think there is some confusion in this tutorial because i do 2 things that are a little odd:
a- i run through the captcha generator to pull a new valid key, this way i can use a new example and have a key validate b- i run through the captcha validator while i can 'guess' an obviously wrong answer. The way the system is structured, a solution is only provided when you try to validate the captcha. That is because you might want to 'Render' an existing sound/image captcha which is completely isolated from Validating it. By purposefully solving it wrong, the routine that sets up the correct user_response is run.
To display a CAPTCHA, you just create a new object and call the render method , passing in a challenge class and render class. You can call the render method as often as you'd like. Currently, the module will transparently validate the key in order to render the captcha.
Yes, You could store the data on the backend. The 'new' constructor will automatically call a key validator so it behaves more like this:
my $captcha= new() if ( !$captcha->EXPIRED && !captcha->INVALID ) { render }
right now, you could either
$dbh->do( store $captcha_publickey to db );
or
create a KeyManager subclass, which creates a key and stores it to the db and does the validation you might be interested in L<Authen::PluggableCaptcha::KeyManagerDB>, which is a db backed keymanager class
Good question. expire_publickey is a method of the captcha and keymanager classes. expire it in your page logic, or tell the base class to tell the manager to expire it. but all that code is up to you.
That is totally understandable. A lot of people want DB storage. This module was written to support that as an option- not a requirement.
No.
The public key is something that is outwardly facing.
ie
http://mysite.com/captcha.jpg?key=abcdefg
you could call
my $captcha= PluggableCaptcha->new( type="existing" , public_key="abcdefg" );
and then
render that as an image
process that for an answer
This might clarify-
public_key= sprintf "%s:%s" , md5( time , site_secret , seed ) , time time= temporal component that lets a captcha work for only a 5 minute window. note that is outside of, and inside of, the hash. this makes the time realistically unspoofably site_secret= non-random seed unique to website to keep spammers from spoofing captcha seed= something unique to build this captcha in my setup: $seed= $url . $session{'id'} That makes sure that only 1 captcha is made for a certain URL and a certain session_id ( locked to that time - since the time is used to create the public key, something 1 second later or earlier would be different ) The drawback is that anyone with the same session_id (which can be spoofed / hijacked) can use that same captcha url for the time window If you're comfortable storing session data, this might be better for you: $seed= $url . $session{'id'} . $session{'captchas_solved'} Where $session{'captchas_solved'} is undefined at first, but every time a captcha on that session is solved, session{captchas_solved}++
In the 'equation'/'transaction', the spammer has the URL , the session_id , and the time. BUT he does not know the site secret, or can control the session variables.
If you've got sessions involved on the page that supports captchas, that approach could probably handle things securely. It doesn't do a single-ticket expiry, but it pretty much makes it improbable to recreate the conditions to use a captcha more than once.
Create your own challege class and run a filter or create a function that only uses your subset of characters. This functionality was once provided in the system, but the list of discernable characters became so small it created captchas that were useless. Depending on the way you render your captcha, the list will change- text/image captchas can render iIl1 similiarly, a warped image will make z2 or 5s similar- or even 6G 9q.
Creating your own captcha challenge class, and using it, takes almost no code or time at all to implement (20 lines, maybe? and you can use existing challlenge as a template)
Authen::Pluggable captcha was designed so you can quickly and easily override default functionality like this.
yep. sweet.
Jonathan Vanasco , cpan@2xlp.com
Copyright (c) 2006 by Jonathan Vanasco. All rights reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install Authen::PluggableCaptcha, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Authen::PluggableCaptcha
CPAN shell
perl -MCPAN -e shell install Authen::PluggableCaptcha
For more information on module installation, please visit the detailed CPAN module installation guide.