view release on metacpan or search on metacpan
return AUTH_REQUIRED;
=item compare_digest_response()
this method represents a shortcut for comparing a client Digest
request with whatever credentials are stored on the server. the
first argument is the hash reference returned by
get_digest_auth_response(). the second argument is a MD5 digest
of the user credentials. the credentials should be in the form
user:realm:password
before they are hashed. the following Perl one-liner will generate
a suitable digest:
$ perl -MDigest::MD5 -e'print Digest::MD5::md5_hex("user:realm:password"),"\n"'
=back
=head1 EXAMPLE
for a complete example, see the My/DigestAuthenticator.pm file
in the test suite for this package, as well as AuthDigest.pm.
In general, the steps are the same as for Basic authentication,
examples of which abound on CPAN, the Eagle book, and the Cookbook:
AuthDigest.pm view on Meta::CPAN
return DECLINED;
}
my $cfg = Apache::ModuleConfig->get($r, __PACKAGE__);
my ($status, $response) = $r->get_digest_auth_response;
return $status unless $status == OK;
my $password_file = $cfg->{_password_file};
my $fh = Apache::File->new($password_file);
unless ($fh) {
$log->error("Apache::AuthDigest - could not open ",
"password file '$password_file'");
return DECLINED;
}
my $digest = get_user_credentials($r->user, $r->auth_name, $fh);
unless ($digest) {
$log->error("Apache::AuthDigest - user '", $r->user,
"' not found in password file '$password_file'");
$r->note_digest_auth_failure;
return AUTH_REQUIRED;
}
return OK if $r->compare_digest_response($response, $digest);
$log->error("Apache::AuthDigest - user '", $r->user,
"' password mismatch");
$r->note_digest_auth_failure;
return AUTH_REQUIRED;
}
sub get_user_credentials {
my ($user, $realm, $fh) = @_;
my ($username, $userrealm, $digest) = ();
AuthDigest.pm view on Meta::CPAN
sub AuthDigestFile ($$$) {
my ($cfg, $parms, $arg) = @_;
return DECLINE_CMD if Apache->module('mod_digest.c');
die "Invalid AuthDigestFile $arg!" unless -f $arg;
$cfg->{_password_file} = $arg;
}
sub DIR_CREATE {
# Initialize an object instead of using the mod_perl default.
my $class = shift;
my $self = { _password_file => undef };
return bless $self, $class;
}
sub DIR_MERGE {
# Allow the subdirectory to inherit the configuration
# of the parent, while overriding with anything more specific.
my ($parent, $current) = @_;
Revision history for Perl extension Apache::DigestAPI
0.01 09.06.2002
- original version
lots of feeback from Andrew Ho
0.02 09.06.2002
- fixed bug in AuthDigest.pm where the last
digest in the password file was returned if
no user is found
- fixed AuthDigestFile so that AuthDigest.pm
actually works, as do the tests (note to
self: run test suite both with and without
mod_digest compiled in httpd)
- minor tweak to (nonpublic) parse_digest_header()
- never release on friday, especially friday
t/05session.t
t/06parse.t
t/07multi.t
t/99pod.t
t/lib/perl/My/MultiAuthenticator.pm
t/lib/perl/My/DigestAuthenticator.pm
t/lib/perl/My/SessionAuthenticator.pm
t/lib/perl/My/SessionAuthorizor.pm
t/lib/perl/My/SessionGenerator.pm
t/conf/extra.conf.in
t/conf/password_file
contrib/AuthDigestDBI.pm
contrib/README
Makefile.PL view on Meta::CPAN
use ExtUtils::MakeMaker;
use Apache::ExtUtils qw(command_table);
use Apache::src ();
use Config;
use strict;
my @directives = (
{ name => 'AuthDigestFile',
errmsg => 'text file containing user IDs and passwords',
args_how => 'TAKE1',
req_override => 'OR_AUTHCFG', },
);
command_table(\@directives);
my %config;
$config{INC} = Apache::src->new->inc;
contrib/AuthDigestDBI.pm view on Meta::CPAN
# 1: report about cache miss
# 2: full debug output
$Apache::AuthDigestDBI::DEBUG = 0;
# configuration attributes, defaults will be overwritten with values from .htaccess.
my %Config = (
'Auth_DBI_data_source' => '',
'Auth_DBI_username' => '',
'Auth_DBI_password' => '',
'Auth_DBI_pwd_table' => '',
'Auth_DBI_uid_field' => '',
'Auth_DBI_pwd_field' => '',
'Auth_DBI_pwd_whereclause' => '',
'Auth_DBI_grp_table' => '',
'Auth_DBI_grp_field' => '',
'Auth_DBI_grp_whereclause' => '',
'Auth_DBI_log_field' => '',
'Auth_DBI_log_string' => '',
'Auth_DBI_authoritative' => 'on',
'Auth_DBI_nopasswd' => 'off',
'Auth_DBI_encrypted' => 'on',
'Auth_DBI_encryption_salt' => 'password',
'Auth_DBI_uidcasesensitive' => 'on',
'Auth_DBI_pwdcasesensitive' => 'on',
'Auth_DBI_placeholder' => 'off',
);
# stores the configuration of current URL.
# initialized during authentication, eventually re-used for authorization.
my $Attr = { };
# global cache: all records are put into one string.
# record separator is a newline. Field separator is $;.
# every record is a list of id, time of last access, password, groups (authorization only).
# the id is a comma separated list of user_id, data_source, pwd_table, uid_field.
# the first record is a timestamp, which indicates the last run of the CleanupHandler followed by the child counter.
my $Cache = time . "$;0\n";
# unique id which serves as key in $Cache.
# the id is generated during authentication and re-used for authorization.
my $ID;
contrib/AuthDigestDBI.pm view on Meta::CPAN
$type .= 'main' if $r->is_main;
print STDERR "==========\n$prefix request type = >$type< \n";
}
return OK unless $r->is_initial_req; # only the first internal request
print STDERR "REQUEST:\n", $r->as_string if $Apache::AuthDigestDBI::DEBUG > 1;
my $auth = 'digest';
# here the dialog pops up and asks you for username and password
my ($status, $response, $res, $passwd_sent);
if ($r->header_in("Authorization") =~ /^Basic (.*)/i) {
$auth = 'Basic';
my $username;
($username, $passwd_sent) = split ':', old_decode_base64($1);
$r->connection->user($username);
}
if ($auth eq 'digest') {
$r = Apache::AuthDigest::API->new($r);
($status, $response) = $r->get_digest_auth_response;
return $status unless $status == OK;
$passwd_sent = 'digest';
} else {
#($res, $passwd_sent) = $r->get_basic_auth_pw;
#print STDERR "$prefix get_basic_auth_pw: res = >$res<, password sent = >$passwd_sent<\n" if $Apache::AuthDigestDBI::DEBUG > 1;
#return $res if $res; # e.g. HTTP_UNAUTHORIZED
return AUTH_REQUIRED unless $passwd_sent;
}
# get username
my ($user_sent) = $r->connection->user;
print STDERR "$prefix user sent = >$user_sent<\n" if $Apache::AuthDigestDBI::DEBUG > 1;
contrib/AuthDigestDBI.pm view on Meta::CPAN
while(($key, $val) = each %Config) {
$val = $r->dir_config($key) || $val;
$key =~ s/^Auth_DBI_//;
$Attr->{$key} = $val;
printf STDERR "$prefix Config{ %-16s } = %s\n", $key, $val if $Apache::AuthDigestDBI::DEBUG > 1;
}
# parse connect attributes, which may be tilde separated lists
my @data_sources = split(/~/, $Attr->{data_source});
my @usernames = split(/~/, $Attr->{username});
my @passwords = split(/~/, $Attr->{password});
$data_sources[0] = '' unless $data_sources[0]; # use ENV{DBI_DSN} if not defined
# obtain the id for the cache
my $data_src = $Attr->{data_source};
$data_src =~ s/\(.+\)//go; # remove any embedded attributes, because of trouble with regexps
$ID = join ',', $user_sent, $data_src, $Attr->{pwd_table}, $Attr->{uid_field};
# if not configured decline
unless ($Attr->{pwd_table} && $Attr->{uid_field} && $Attr->{pwd_field}) {
printf STDERR "$prefix not configured, return DECLINED\n" if $Apache::AuthDigestDBI::DEBUG > 1;
return DECLINED;
}
# do we want Windows-like case-insensitivity?
$user_sent = lc($user_sent) if $Attr->{uidcasesensitive} eq "off";
$passwd_sent = lc($passwd_sent) if $Attr->{pwdcasesensitive} eq "off";
# check whether the user is cached but consider that the password possibly has changed
my $passwd = '';
my $salt = '';
if ($CacheTime) { # do we use the cache ?
if ($SHMID) { # do we keep the cache in shared memory ?
semop($SEMID, $obtain_lock) or print STDERR "$prefix semop failed \n";
shmread($SHMID, $Cache, 0, $SHMSIZE) or printf STDERR "$prefix shmread failed \n";
substr($Cache, index($Cache, "\0")) = '';
semop($SEMID, $release_lock) or print STDERR "$prefix semop failed \n";
}
# find id in cache
contrib/AuthDigestDBI.pm view on Meta::CPAN
$passwd_cached = $2;
$groups_cached = $3;
printf STDERR "$prefix cache: found >$ID< >$last_access< >$passwd_cached< \n" if $Apache::AuthDigestDBI::DEBUG > 1;
if ($auth eq 'digest') {
$salt = $response->{'realm'};
my $passwd_to_check = Digest::MD5::md5_hex(join ':', $user_sent, $salt, $passwd_cached);
$passwd = $passwd_cached if $r->compare_digest_response($response, $passwd_to_check);
} else {
$salt = $Attr->{encryption_salt} eq 'userid' ? $user_sent : $passwd_cached;
my $passwd_to_check = $Attr->{encrypted} eq 'on' ? crypt($passwd_sent, $salt) : $passwd_sent;
# match cached password with password sent
$passwd = $passwd_cached if $passwd_to_check eq $passwd_cached;
}
}
}
if ($passwd) { # found in cache
printf STDERR "$prefix passwd found in cache \n" if $Apache::AuthDigestDBI::DEBUG > 1;
} else { # password not cached or changed
printf STDERR "$prefix passwd not found in cache \n" if $Apache::AuthDigestDBI::DEBUG;
# connect to database, use all data_sources until the connect succeeds
my $j;
for ($j = 0; $j <= $#data_sources; $j++) {
last if ($dbh = DBI->connect($data_sources[$j], $usernames[$j], $passwords[$j]));
}
unless ($dbh) {
$r->log_reason("$prefix db connect error with data_source >$Attr->{data_source}<", $r->uri);
return SERVER_ERROR;
}
# generate statement
my $user_sent_quoted = $dbh->quote($user_sent);
my $select = "SELECT $Attr->{pwd_field}";
my $from = "FROM $Attr->{pwd_table}";
contrib/AuthDigestDBI.pm view on Meta::CPAN
# fetch result
while ($_ = $sth->fetchrow_array) {
# strip trailing blanks for fixed-length data-type
$_ =~ s/ +$// if $_;
# consider the case with many users sharing the same userid
$passwd .= "$_$;";
}
chop $passwd if $passwd;
undef $passwd if 0 == $sth->rows; # so we can distinguish later on between no password and empty password
if ($sth->err) {
$dbh->disconnect;
return SERVER_ERROR;
}
$sth->finish;
# re-use dbh for logging option below
$dbh->disconnect unless ($Attr->{log_field} && $Attr->{log_string});
}
$r->subprocess_env(REMOTE_PASSWORDS => $passwd);
print STDERR "$prefix passwd = >$passwd<\n" if $Apache::AuthDigestDBI::DEBUG > 1;
# check if password is needed
if (!defined($passwd)) { # not found in database
# if authoritative insist that user is in database
if ($Attr->{authoritative} eq 'on') {
$r->log_reason("$prefix password for user $user_sent not found", $r->uri);
$r->note_basic_auth_failure;
return AUTH_REQUIRED;
} else {
# else pass control to the next authentication module
return DECLINED;
}
}
# allow any password if nopasswd = on and the retrieved password is empty
if ($Attr->{nopasswd} eq 'on' && !$passwd) {
return OK;
}
# if nopasswd is off, reject user
unless ($passwd_sent && $passwd) {
$r->log_reason("$prefix user $user_sent: empty password(s) rejected", $r->uri);
$r->note_basic_auth_failure;
return AUTH_REQUIRED;
}
# compare passwords
my $found = 0;
my $password;
foreach $password (split(/$;/, $passwd)) {
# compare the two passwords possibly crypting the password if needed
my $did_match = 0;
if ($auth eq 'digest') {
$salt = $response->{'realm'};
# password to check is in a reverse role from below
# it's the correct password
my $passwd_to_check = Digest::MD5::md5_hex(join ':', $user_sent, $salt, $password);
$did_match = 1 if $r->compare_digest_response($response, $passwd_to_check);
} else {
$salt = $Attr->{encryption_salt} eq 'userid' ? $user_sent : $password;
my $passwd_to_check = $Attr->{encrypted} eq 'on' ? crypt($passwd_sent, $password) : $passwd_sent;
print STDERR "$prefix user $user_sent: > '$passwd_to_check' eq '$password' < \n" if $Apache::AuthDigestDBI::DEBUG > 1;
$did_match = 1 if $passwd_to_check eq $password;
}
if ($did_match) {
$found = 1;
$r->subprocess_env(REMOTE_PASSWORD => $password);
print STDERR "$prefix user $user_sent: password match for >$password< \n" if $Apache::AuthDigestDBI::DEBUG > 1;
# update timestamp and cache userid/password if CacheTime is configured
if ($CacheTime) { # do we use the cache ?
if ($SHMID) { # do we keep the cache in shared memory ?
semop($SEMID, $obtain_lock) or print STDERR "$prefix semop failed \n";
shmread($SHMID, $Cache, 0, $SHMSIZE) or printf STDERR "$prefix shmread failed \n";
substr($Cache, index($Cache, "\0")) = '';
}
# update timestamp and password or append new record
my $now = time;
if (!($Cache =~ s/$ID$;\d+$;.*$;(.*)\n/$ID$;$now$;$password$;$1\n/)) {
$Cache .= "$ID$;$now$;$password$;\n";
} else {
}
if ($SHMID) { # write cache to shared memory
shmwrite($SHMID, $Cache, 0, $SHMSIZE) or printf STDERR "$prefix shmwrite failed \n";
semop($SEMID, $release_lock) or print STDERR "$prefix semop failed \n";
}
}
last;
}
}
unless ($found) {
$r->log_reason("$prefix user $user_sent: password mismatch", $r->uri);
if ($auth eq 'digest') {
$r->note_digest_auth_failure;
} else {
$r->note_basic_auth_failure;
}
return AUTH_REQUIRED;
}
# logging option
if ($Attr->{log_field} && $Attr->{log_string}) {
if (!$dbh) { # connect to database if not already done
my ($j, $connect);
for ($j = 0; $j <= $#data_sources; $j++) {
if ($dbh = DBI->connect($data_sources[$j], $usernames[$j], $passwords[$j])) {
$connect = 1;
last;
}
}
unless ($connect) {
$r->log_reason("$prefix db connect error with $Attr->{data_source}", $r->uri);
return SERVER_ERROR;
}
}
my $user_sent_quoted = $dbh->quote($user_sent);
contrib/AuthDigestDBI.pm view on Meta::CPAN
# get username
my ($user_sent) = $r->connection->user;
print STDERR "$prefix user sent = >$user_sent<\n" if $Apache::AuthDigestDBI::DEBUG > 1 ;
# here we could read the configuration, but we re-use the configuration from the authentication
# parse connect attributes, which may be tilde separated lists
my @data_sources = split(/~/, $Attr->{data_source});
my @usernames = split(/~/, $Attr->{username});
my @passwords = split(/~/, $Attr->{password});
$data_sources[0] = '' unless $data_sources[0]; # use ENV{DBI_DSN} if not defined
# if not configured decline
unless ($Attr->{pwd_table} && $Attr->{uid_field} && $Attr->{grp_field}) {
printf STDERR "$prefix not configured, return DECLINED\n" if $Apache::AuthDigestDBI::DEBUG > 1;
return DECLINED;
}
# do we want Windows-like case-insensitivity?
$user_sent = lc($user_sent) if $Attr->{uidcasesensitive} eq "off";
contrib/AuthDigestDBI.pm view on Meta::CPAN
}
if ($groups) { # found in cache
printf STDERR "$prefix groups found in cache \n" if $Apache::AuthDigestDBI::DEBUG > 1;
} else { # groups not cached or changed
printf STDERR "$prefix groups not found in cache \n" if $Apache::AuthDigestDBI::DEBUG;
# connect to database, use all data_sources until the connect succeeds
my ($j, $connect);
for ($j = 0; $j <= $#data_sources; $j++) {
if ($dbh = DBI->connect($data_sources[$j], $usernames[$j], $passwords[$j])) {
$connect = 1;
last;
}
}
unless ($connect) {
$r->log_reason("$prefix db connect error with $Attr->{data_source}", $r->uri);
return SERVER_ERROR;
}
# generate statement
contrib/AuthDigestDBI.pm view on Meta::CPAN
# Authentication and Authorization in .htaccess:
AuthName DBI
AuthType Digest
PerlAuthenHandler Apache::AuthDigestDBI::authen
PerlAuthzHandler Apache::AuthDigestDBI::authz
PerlSetVar Auth_DBI_data_source dbi:driver:dsn
PerlSetVar Auth_DBI_username db_username
PerlSetVar Auth_DBI_password db_password
#DBI->connect($data_source, $username, $password)
PerlSetVar Auth_DBI_pwd_table users
PerlSetVar Auth_DBI_uid_field username
PerlSetVar Auth_DBI_pwd_field password
# authentication: SELECT pwd_field FROM pwd_table WHERE uid_field=$user
PerlSetVar Auth_DBI_grp_field groupname
# authorization: SELECT grp_field FROM pwd_table WHERE uid_field=$user
require valid-user
require user user_1 user_2 ...
require group group_1 group_2 ...
The AuthType may be Digest or Basic. It will 'fallback' to Basic if the client
ignores the request for Digest authentication. The password B<must not> be encrypted
for Digest authentication and the fallback to Basic. For Basic authentication,
passwords may be encrypted.
You may use one or more valid require lines. For a single require line with the
requirement 'valid-user' or with the requirements 'user user_1 user_2 ...' it is
sufficient to use only the authentication handler.
=head1 DESCRIPTION
This is a hacked up version Apache::AuthDBI that uses Apache::AuthDigest to do
Digest authentication. Please see the docs for Apache::AuthDBI for full usage.
contrib/AuthDigestDBI.pm view on Meta::CPAN
Note that this module requires Apache::AuthDBI and Apache::AuthDigest.
=head1 SEE ALSO
L<Apache::AuthDBI>, L<Apache::AuthDigest::API>, L<Apache>, L<mod_perl>, L<DBI>
=head1 BUGS
The password must not be encrypted for use with Digest authentication.
When Digest authentication is requested, it accepts Basic authentication. (This
isn't a bug, except that you cannot shut this behavior off.)
=head1 AUTHORS
=item *
Apache::AuthDigestDBI variation by Robert Giseburt <rob@heavyhosting.net>
# many kudos to LWP for supporting Digest authentication natively!
my $url = '/protected/index.html';
my $response = GET $url;
ok $response->code == 401;
ok $response->header('WWW-Authenticate') =~ m/Digest realm="cookbook"/;
$response = GET $url, username => 'geoff', password => 'geoff';
ok $response->code == 200;
$response = GET $url, username => 'geoff', password => 'badpass';
ok $response->code == 401;
plan tests => 7, have_lwp;
my $url = '/dropin/index.html';
my $response = GET $url;
ok $response->code == 401;
ok $response->header('WWW-Authenticate') =~ m/Digest realm="flatfile"/;
$response = GET $url, username => 'geoff', password => 'geoff';
ok $response->code == 200;
$response = GET $url, username => 'geoff', password => 'badpass';
ok $response->code == 401;
$response = GET $url, username => 'test2', password => 'badpass';
ok $response->code == 401;
$response = GET $url, username => 'test', password => 'test';
ok $response->code == 200;
$response = GET $url, username => 'nouser', password => 'nopass';
ok $response->code == 401;
t/04authz.t view on Meta::CPAN
plan tests => 4, have_lwp;
my $url = '/authz/index.html';
my $response = GET $url;
ok $response->code == 401;
ok $response->header('WWW-Authenticate') =~ m/Digest realm="flatfile"/;
$response = GET $url, username => 'geoff', password => 'geoff';
ok $response->code == 401;
$response = GET $url, username => 'test', password => 'test';
ok $response->code == 200;
t/05session.t view on Meta::CPAN
plan tests => 7, have_lwp;
my $url = '/session/index.html';
my $response = GET "$url?init";
ok $response->code == 401;
ok $response->header('WWW-Authenticate') =~ m/nonce="e37f0136aa3ffaf149b351f6a4c948e9"/;
$response = GET "$url?session1", username => 'geoff', password => 'geoff';
ok $response->code == 200;
ok $response->request->header('Authorization') =~ m/nonce="43fd828731048cda3a0a050b22bed4f3"/;
$response = GET "$url?expired", username => 'geoff', password => 'geoff';
ok $response->code == 401;
$response = GET "$url?session2", username => 'newuser', password => 'newpass';
ok $response->code == 401;
ok $response->request->header('Authorization') =~ m/nonce="98432f23b96c8138c2606ef8bebc0a82"/;
t/06parse.t view on Meta::CPAN
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestRequest;
plan tests => 4, have_lwp;
# some mod_dav URL
my $url = '/parse/EPD%20Lookup/lots%201-66,%20A-G.txt';
my $response = GET $url, username => 'geoff', password => 'geoff';
ok $response->code == 200;
ok $response->content eq q!/parse/EPD%20Lookup/lots%201-66,%20A-G.txt!;
$url = '/parse/emb=edded+stuff&other$garble';
$response = GET $url, username => 'geoff', password => 'geoff';
ok $response->code == 200;
ok $response->content eq q!/parse/emb=edded+stuff&other$garble!;
t/conf/extra.conf.in view on Meta::CPAN
Require valid-user
PerlAuthenHandler My::DigestAuthenticator
</Location>
Alias /dropin @DocumentRoot@
<Location /dropin>
AuthType Digest
AuthName flatfile
Require valid-user
PerlAuthenHandler Apache::AuthDigest
AuthDigestFile @ServerRoot@/conf/password_file
</Location>
Alias /authz @DocumentRoot@
<Location /authz>
AuthType Digest
AuthName flatfile
Require user test
PerlAuthenHandler Apache::AuthDigest
PerlAuthzHandler Apache::AuthzDigest
AuthDigestFile @ServerRoot@/conf/password_file
</Location>
Alias /session @DocumentRoot@
<Location /session>
AuthType Digest
AuthName cookbook
Require valid-user
PerlAuthenHandler My::SessionAuthenticator
PerlAuthzHandler My::SessionAuthorizor
DigestSessionKey MYSESSION
t/lib/perl/My/DigestAuthenticator.pm view on Meta::CPAN
sub get_credentials {
my ($user, $realm) = @_;
# this represents a routine that fetches the Digest::MD5 hash of
# the credentials for user $r->user at realm $r->auth_name
# to generate your own credentials, use the htdigest utility
# program that ships with Apache, or the Perl one-liner
# $ perl -MDigest::MD5 -e'print Digest::MD5::md5_hex("user:realm:password"),"\n"'
return '966b699e9ada71dbefb7276e0fc1aaf1';
}
1;
t/lib/perl/My/SessionAuthenticator.pm view on Meta::CPAN
sub get_credentials {
my ($user, $realm) = @_;
# this represents a routine that fetches the Digest::MD5 hash of
# the credentials for user $r->user at realm $r->auth_name
# to generate your own credentials, use the htdigest utility
# program that ships with Apache, or the Perl one-liner
# $ perl -MDigest::MD5 -e'print Digest::MD5::md5_hex("user:realm:password"),"\n"'
return '966b699e9ada71dbefb7276e0fc1aaf1';
}
1;