Apache2-AuthCookieDBImg
view release on metacpan or search on metacpan
lib/Apache2/AuthCookieDBImg.pm view on Meta::CPAN
time and checksum. A new checksum is calculated of the username, issue time,
expire time and the secret key again; if it agrees with the checksum that
the client supplied, we know that the data has not been tampered with. We
next check that the expire time has not passed. If not, the ticket is still
good, so we set the username.
Authorization checks then check that any "require valid-user" or "require
user jacob" settings are passed. Finally, if a "require group foo" directive
was given, the module will look up the username in a groups database and
check that the user is a member of one of the groups listed. If all these
checks pass, the document requested is displayed.
If a ticket has expired or is otherwise invalid it is cleared in the browser
and the login form is shown again.
IMAGE MATCHING
The image matching only occurs if all 3 of the following directives appear
in the Apache configuration file:
PerlSetVar WhatEverDBI_ImgTable "images"
PerlSetVar WhatEverDBI_ImgWordField "imageword"
PerlSetVar WhatEverDBI_ImgKeyField "imagekey"
The first ImgTable var is the DBI table that we will use to store our
image key + word pairs. The key field is set by the second var, the word
is the third var.
Your login form should set the 2 required fields for ALL AuthCookieDBI
login forms:
Your login ID: <input type="text" name="credential_0" value="">
Your password: <input type="password" name="credential_1" value="">
PLUS two additional fields for image processing:
The image says: <input type="text" name="credential_2" value="">
<input type="hidden" name="credential_3" value="a_random_key">
The login form should also have an image displayed that shows the word
that we are expecting to receive via credential_2 as semi-obscured text.
Typically the image that is displayed is selected at random (provide
your own image randomizer here) with the hidden credential_3 field
also being set via the same random selector so that we can lookup
the word in the images table via the key we get in credential_3.
For example, my randomizer (written in perl and called via a perl
page template processor similar to Template::Toolkit) will spit out
my image coding and hidden field coding into my HTML page selecting
a random image + key from the images table. For example, the output
from my perl randomizer spits out:
<img src="/images/dbimg/junk.png"><input type="hidden" name="credential_3" value="1">
To make the work of the randomizer easier I create my images table
like this:
create table images ( imagekey serial, imageurl char(128), imageword char(20));
And load it up like this:
inssert into images (imageurl,imageword) values ('/images/dbimg/junk.png','saywhat?');
Then create an image named junk.png and put it in my web server /images/dbimg folder.
The text on the image has a background picture plus the word "saywhat?" across the front.
The randomizer just looks up the imageurl and imagekey in the database and spits out
the appropriate HTML code. ApacheCookieDBImg then does a reverse operation, looking
up the imageword based on the key.
=head1 CAVEATS
This is not a truly random image, so it is not overly secure. The initial idea is just
to thwart stupid bots. Someone could easily visit the site and build a map of image
sources and the matching words. i.e. when credential_3 == 1 the word is always "saywhat?".
Not fool-proof, just and extra level of bot protection.
=cut
#===============================================================================
#===============================================================================
package Apache2::AuthCookieDBImg;
use strict;
use 5.004;
use vars qw( $VERSION );
$VERSION = '2.2';
use Apache2::AuthCookie;
use vars qw( @ISA );
@ISA = qw( Apache2::AuthCookie );
use Apache2::RequestRec;
use Apache::DBI;
use Apache2::Const -compile => qw( OK HTTP_FORBIDDEN );
use Apache2::ServerUtil;
use Digest::MD5 qw( md5_hex );
use Date::Calc qw( Today_and_Now Add_Delta_DHMS );
# Also uses Crypt::CBC if you're using encrypted cookies.
# Also uses Apache2::Session if you're using sessions.
#===============================================================================
# F U N C T I O N D E C L A R A T I O N S
#===============================================================================
sub _log_not_set($$);
sub _dir_config_var($$);
sub _dbi_config_vars($);
sub _now_year_month_day_hour_minute_second();
sub _percent_encode($);
sub _percent_decode($);
sub extra_session_info($$\@);
sub authen_cred($$\@);
sub authen_ses_key($$$);
sub group($$\@);
#===============================================================================
# P A C K A G E G L O B A L S
#===============================================================================
use vars qw( %CIPHERS );
# Stores Cipher::CBC objects in $CIPHERS{ idea:AuthName },
# $CIPHERS{ des:AuthName } etc.
our @Extra_Data; # CSA Patch - needed for keeping cookie active
lib/Apache2/AuthCookieDBImg.pm view on Meta::CPAN
# my( $crypted_password ) = $sth->fetchrow_array;
my $crypted_password = $sth->fetchrow;
unless ( defined $crypted_password ) {
$r->log_error( "Apache2::AuthCookieDBI: couldn't select password from $c{ DBI_DSN }, $c{ DBI_userstable }, $c{ DBI_userfield } for user $user for auth realm $auth_name", $r->uri );
return undef;
}
# now return unless the passwords match.
if ( lc $c{ DBI_crypttype } eq 'none' ) {
unless ( $password eq $crypted_password ) {
$r->log_error( "Apache2::AuthCookieDBI: plaintext passwords didn't match for user $user for auth realm $auth_name", $r->uri );
return undef;
}
} elsif ( lc $c{ DBI_crypttype } eq 'crypt' ) {
my $salt = substr $crypted_password, 0, 2;
unless ( crypt( $password, $salt ) eq $crypted_password ) {
$r->log_error( "Apache2::AuthCookieDBI: crypted passwords didn't match for user $user for auth realm $auth_name", $r->uri );
return undef;
}
} elsif ( lc $c{ DBI_crypttype } eq 'md5' ) {
unless ( md5_hex( $password ) eq $crypted_password ) {
$r->log_error( "Apache2::AuthCookieDBI: MD5 passwords didn't match for user $user for auth realm $auth_name", $r->uri );
return undef;
}
}
# CSA Patch - New gen_key function for activity reset
# on cookies
#
return $self->gen_key($r, $user, \@Extra_Data);
}
#-------------------------------------------------------------------------------
# Take a session key and check that it is still valid; if so, return the user.
sub authen_ses_key($$$)
{
my( $self, $r, $encrypted_session_key ) = @_;
my $auth_name = $r->auth_name;
# Enable Debugging In Here
my $debug = $r->dir_config("AuthCookieDebug") || 0;
# Get the configuration information.
my %c = _dbi_config_vars $r;
# Get the secret key.
my $secretkey = $c{ DBI_secretkey };
unless ( defined $secretkey ) {
$r->log_error( "Apache2::AuthCookieDBImg: didn't have the secret key from for auth realm $auth_name", $r->uri );
return undef;
}
# Decrypt the session key.
my $session_key;
if ( $c{ DBI_encryptiontype } eq 'none' ) {
$session_key = $encrypted_session_key;
} else {
# Check that this looks like an encrypted hex-encoded string.
unless ( $encrypted_session_key =~ /^[0-9a-fA-F]+$/ ) {
$r->log_error( "Apache2::AuthCookieDBImg: encrypted session key $encrypted_session_key doesn't look like it's properly hex-encoded for auth realm $auth_name", $r->uri );
return undef;
}
# Get the cipher from the cache, or create a new one if the
# cached cipher hasn't been created, & decrypt the session key.
my $cipher;
if ( lc $c{ DBI_encryptiontype } eq 'des' ) {
$cipher = $CIPHERS{ "des:$auth_name" }
||= Crypt::CBC->new( $secretkey, 'DES' );
} elsif ( lc $c{ DBI_encryptiontype } eq 'idea' ) {
$cipher = $CIPHERS{ "idea:$auth_name" }
||= Crypt::CBC->new( $secretkey, 'IDEA' );
} elsif ( lc $c{ DBI_encryptiontype } eq 'blowfish' ) {
$cipher = $CIPHERS{ "blowfish:$auth_name" }
||= Crypt::CBC->new( $secretkey, 'Blowfish' );
} elsif ( lc $c{ DBI_encryptiontype } eq 'blowfish_pp' ) {
$cipher = $CIPHERS{ "blowfish_pp:$auth_name" }
||= Crypt::CBC->new( $secretkey, 'Blowfish_PP' );
} else {
$r->log_error( "Apache2::AuthCookieDBImg: unknown encryption type $c{ DBI_encryptiontype } for auth realm $auth_name", $r->uri );
return undef;
}
$session_key = $cipher->decrypt_hex( $encrypted_session_key );
}
# Break up the session key.
my( $enc_user, $issue_time, $expire_time, $session_id,
$supplied_hash, @rest ) = split /:/, $session_key;
# Let's check that we got passed sensible values in the cookie.
unless ( $enc_user =~ /^[a-zA-Z0-9_\%]+$/ ) {
$r->log_error( "Apache2::AuthCookieDBImg: bad percent-encoded user $enc_user recovered from session ticket for auth_realm $auth_name", $r->uri );
return undef;
}
# decode the user
my $user = _percent_decode $enc_user;
unless ( $issue_time =~ /^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$/ ) {
$r->log_error( "Apache2::AuthCookieDBImg: bad issue time $issue_time recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
return undef;
}
unless ( $expire_time =~ /^\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}$/ ) {
$r->log_error( "Apache2::AuthCookieDBImg: bad expire time $expire_time recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
return undef;
}
unless ( $supplied_hash =~ /^[0-9a-fA-F]{32}$/ ) {
$r->log_error( "Apache2::AuthCookieDBImg: bad hash $supplied_hash recovered from ticket for user $user for auth_realm $auth_name", $r->uri );
return undef;
}
# If we're using a session module, check that their session exist.
if ( defined $c{ DBI_sessionmodule } ) {
my %session;
my $dbh = DBI->connect( $c{ DBI_DSN },
$c{ DBI_user }, $c{ DBI_password } );
unless ( defined $dbh ) {
$r->log_error( "Apache2::AuthCookieDBImg: couldn't connect to $c{ DBI_DSN } for auth realm $auth_name", $r->uri );
return undef;
}
( run in 1.463 second using v1.01-cache-2.11-cpan-ad19def0cd9 )