Apache-AuthenHook-2.00_04/ 0040755 0000764 0000764 00000000000 10226762636 014146 5 ustar geoff geoff Apache-AuthenHook-2.00_04/AuthenHook.pm 0100755 0000764 0000764 00000017570 10226762567 016566 0 ustar geoff geoff package Apache::AuthenHook;
use 5.008;
use DynaLoader ();
use Apache2::Module (); # add()
use Apache2::CmdParms (); # $parms->info
use Apache2::Const -compile => qw(OK DECLINE_CMD OR_AUTHCFG ITERATE);
use strict;
our @ISA = qw(DynaLoader);
our $VERSION = '2.00_04';
__PACKAGE__->bootstrap($VERSION);
my @directives = (
{ name => 'AuthDigestProvider',
errmsg => 'specify the auth providers for a directory or location',
args_how => Apache2::Const::ITERATE,
req_override => Apache2::Const::OR_AUTHCFG,
cmd_data => 'digest' },
{ name => 'AuthBasicProvider',
errmsg => 'specify the auth providers for a directory or location',
args_how => Apache2::Const::ITERATE,
req_override => Apache2::Const::OR_AUTHCFG,
func => 'AuthDigestProvider',
cmd_data => 'basic' },
);
Apache2::Module::add(__PACKAGE__, \@directives);
sub AuthDigestProvider {
my ($cfg, $parms, $args) = @_;
my @providers = split ' ', $args;
foreach my $provider (@providers) {
if ($provider =~ m/::/) {
# if the provider looks like a Perl handler...
# save the config for later
push @{$cfg->{$parms->info}}, $provider;
# and register the handler as an authentication provider
register_provider($provider);
}
# yeah, that's not a perfect check, but...
}
# pass the directive back to Apache "unprocessed"
return Apache2::Const::DECLINE_CMD;
}
sub DIR_CREATE {
return bless { digest => [],
basic => [], }, shift;
}
sub DIR_MERGE {
my ($base, $add) = @_;
my %new = (%$add, %$base);
return bless \%new, ref($base);
}
1;
__END__
=head1 NAME
Apache::AuthenHook - Perl API for Apache 2.1 authentication
=head1 SYNOPSIS
PerlLoadModule Apache::AuthenHook
PerlModule My::OtherProvider
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::DigestProvider file My::OtherProvider::digest
AuthUserFile realm1
Require valid-user
AuthType Basic
AuthName foorealm
AuthBasicProvider My::OtherProvider::basic file My::BasicProvider
AuthUserFile realm1
=head1 DESCRIPTION
Apache::AuthenHook offers access to the 2.1 Apache authentication
API in Perl. This is different than the authentication API
from Apache 1.3 or even Apache 2.0, but in its differences lies
strength.
For a full description of how authentication works in 2.1, see
http://www.serverwatch.com/tutorials/article.php/2202671
Basically, the difference between 2.0 and 2.1 is that authentication
is now delegated to providers, and each provider has a specific
purpose. For instance, mod_authn_file covers gleaning the password
from an .htpasswd or .htdigest file, while mod_auth_basic covers
the Basic dialogue between the client and server, regardless
of the source of the password. The best part of all this (to me)
is that Digest authentication is also delegated out -
mod_auth_digest now handles all the intricacies of Digest
authentication (including the elusive MSIE support) which
means you don't need to worry about them (and neither do I).
All that Digest authentication requires is *some* authentication
provider to provide user credentials - this can be via
mod_authn_file or another mechanism of your choosing.
Apache::AuthenHook registers and coordinates the use of Perl
handlers as authentication providers.
How does this affect you? Read on...
=head1 EXAMPLE
Say you want to enable Digest authentication in your Apache 2.1 server...
PerlLoadModule Apache::AuthenHook
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::DigestProvider file
AuthUserFile realm1
This configuration means that My::DigestProvider will be
responsible for providing user credentials for requests to
/digest. if My::DigestProvider finds a suitable user,
mod_auth_digest will verify those credentials and take care of
setting all the proper headers, set the proper HTTP response
status, and so on. If My::DigestProvider cannot find a matching
user it can decide what to do next - either pass the user to
the next provider (in this case the default file provider,
which will use the flat file "realm1") or decide that no user
means no access.
Here is a simple My::DigestProvider
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);
sub handler {
my ($r, $user, $realm, $hash) = @_;
# user1 at realm1 is found - pass to mod_auth_digest
if ($user eq 'user1' && $realm eq 'realm1') {
$$hash = 'eee52b97527306e9e8c4613b7fa800eb';
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2' && $realm eq 'realm1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along to the next provider
return Apache2::Const::DECLINED;
}
isn't that easy?
the only thing that is a bit tricky here is $$hash. the fourth
argument passed to your handler, $hash, is a reference to
to a simple scalar that needs to be populated with the MD5 hash of
the user:realm:password combination you determine for the
incoming user. this may seem a bit strange, but it is actually
exactly how things work over in Apache C land, so I guess that
makes it ok.
as you can see, returning OK means "user found" and requires
that $$hash be populated - mod_auth_digest will take care
of determining whether the hash matches the incoming Digest
criteria. returning HTTP_UNAUTHORIZED (which is the same
as the former and still available AUTH_REQUIRED constant)
means "no access." returning DECLINED means "some other provider
can try."
The steps are remarkably similar for Basic authentication, first
Require valid-user
AuthType Basic
AuthName foorealm
AuthBasicProvider My::BasicProvider file
AuthUserFile realm1
then
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);
sub handler {
my ($r, $user, $password) = @_;
# user1/basic1 is ok
if ($user eq 'user1' && $password eq 'basic1') {
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along to the next provider
return Apache2::Const::DECLINED;
}
In the case of Basic authentication, the return codes mean
essentially the same thing. The one exception is that OK
means that you have checked the user against the password
and have found that they match (as opposed to with Digest,
where the actual verification is not done by you).
These explanations should be enough to get you going -
see the files in the test suite for more examples.
=head1 NOTES
This has been tested under the prefork MPM only, using
mostly Perl 5.9.0 (as well as some 5.8.0). It will not
work under threaded MPMs - soon, just not yet.
=head1 FEATURES/BUGS
This is very much so alphaware, so beware - bugs may lurk
in unexpected places. there is one bug that is outside
of my control, though, and concerns MSIE and Digest
authentication for URIs that include query strings. see
http://httpd.apache.org/docs-2.0/mod/mod_auth_digest.html
one workaround for this issue is is to use POST instead of
GET for your forms.
A limitation of this interface is that you can't use Perl
providers that are not at least two levels deep -
the criterion for registering a Perl
provider is a simple check for a double-colon.
for example, My::Provider will work while Provider won't
(although Provider::handler will). anyway, single
level handlers are rare, so fixing it would be a lot
of trouble for little benefit.
=head1 AUTHOR
Geoffrey Young Egeoff@modperlcookbook.orgE
=head1 COPYRIGHT
Copyright (c) 2003, Geoffrey Young
All rights reserved.
This module is free software. It may be used, redistributed
and/or modified under the same terms as Perl itself.
=cut
Apache-AuthenHook-2.00_04/AuthenHook.xs 0100755 0000764 0000764 00000021246 10226755607 016574 0 ustar geoff geoff #include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "mod_perl.h"
#include "ap_provider.h"
#include "mod_auth.h"
static AV *providers = Nullav; /* @{$cfg->{basic}}, cached per-request */
static int call_provider(request_rec *r, const char *type,
const char *user, const char *cred, char **rethash)
{
SV *provider = Nullsv; /* the name of the Perl provider to call */
modperl_handler_t *handler; /* the actual provider as a Perl handler */
AV *args = Nullav; /* @_ to Perl handler */
SV *hash = newSV(0); /* scalar reference in @_ for Digest auth */
SV *cfg; /* $cfg from directive handlers */
SV **svp; /* hash within $cfg, %{$cfg} */
int status; /* the status this function returns */
if (! apr_table_get(r->notes, "AUTHEN_HOOK_INIT_REQUEST")) {
/* initialize a clean copy of the Perl provider array
* (eg, @{$cfg->{basic}}) on each request. because this function is
* the sole function registered for all Perl providers, it is
* called multiple times per request - caching the provider array is
* the only way we can keep track of which Perl callbacks we've processed
* for this request.
*
* oh, and using r->notes instead of per-request cleanups helps us with
* internal redirects, which is a nice bonus. */
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - initializing request\n");
/* first, get the config object ($cfg) populated by the Perl
* directive handlers. This returns the same object as
* Apache2::Module->get_config().
*/
cfg = modperl_module_config_get_obj(aTHX_ newSVpvn("Apache::AuthenHook", 18),
r->server, r->per_dir_config);
if (! cfg) {
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - config object not found\n");
return AUTH_GENERAL_ERROR;
}
/* isolate $cfg->{basic} */
svp = hv_fetch((HV *)SvRV(cfg), type, strlen(type), FALSE);
if (SvROK(*svp) && (SvTYPE(SvRV(*svp)) == SVt_PVAV)) {
/* if $cfg->{basic} holds a reference to an array, dereference
* and initialize provider array for this request */
AV *av = (AV *)SvRV(*svp); /* temporary storage */
providers = av_make(av_len(av)+1, AvARRAY(av));
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - found %d providers\n",
av_len(av)+1);
}
else {
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - provider array not found\n");
return AUTH_GENERAL_ERROR;
}
/* set the note so we don't initialize again for this (sub)request */
apr_table_setn(r->notes, "AUTHEN_HOOK_INIT_REQUEST", "1");
} /* end once per-request processing */
/* shift off the next provider from the provider array */
provider = av_shift(providers);
if (! SvOK(provider)) {
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - provider not found\n");
return AUTH_GENERAL_ERROR;
}
/* populate @_ for the callback, starting with $r */
modperl_handler_make_args(aTHX_ &args,
"Apache2::RequestRec", r, NULL);
/* now for the username and password(Basic) or realm(Digest) */
av_push(args, newSVpv(user, 0));
av_push(args, newSVpv(cred, 0));
/* Digest authentication requires an extra argument -
* the scalar reference to be populated with the lookup hash */
if (! strcmp(type, "digest")) {
av_push(args, newRV_inc(hash));
}
/* at this point, we know which provider we're supposed to
* be calling and have populated the argument list. now,
* issue the callback using native mod_perl routines. */
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - trying provider %s for %s",
SvPVX(provider), r->uri);
handler = modperl_handler_new(r->pool, SvPV_nolen(provider));
status = modperl_callback(aTHX_ handler, r->pool, r, r->server, args);
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - provider returned %d\n",
status);
/* unfortunately, modperl_callback forces the status into an HTTP_
* status or OK, so I can't give Perl handlers meaningful access to
* the AUTH_ constants without implementing my own callback routines.
* this is the only real difference between the Perl and C API.
*
* while the switch statment in the C callback function takes care of
* the translation back to AUTH_ constants for us, there is still
* a little more work left */
if (status == OK && ! strcmp(type, "digest")) {
/* for Digest we need to send the hash back for verification
* via mod_auth_digest */
/* sanity checking - make sure that the scalar references a string */
if (SvTYPE(hash) == SVt_PV) {
*rethash = SvPV_nolen(hash);
}
else {
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - returned hash not a string\n");
status = AUTH_GENERAL_ERROR;
}
}
else if (status == HTTP_INTERNAL_SERVER_ERROR) {
/* write $@ to the error_log in the case of an error */
modperl_errsv(aTHX_ status, r, NULL);
}
/* decrement args, as required by modperl_callback */
SvREFCNT_dec((SV*)args);
/* return whatever status the Perl provider returned */
return status;
}
static authn_status check_password(request_rec *r, const char *user,
const char *password)
{
int status;
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - calling Basic handler\n");
status = call_provider(r, "basic", user, password, NULL);
/* determine which AUTH_ status we should return based on
* what HTTP_ status the Perl provider returned. and yes,
* this routine could be reused if I wanted to be less specific
* with the log messages... */
switch(status) {
case AUTH_GENERAL_ERROR:
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Apache::AuthenHook - yikes! something bad happened!");
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - try running with PerlTrace a");
/* status is already AUTH_GENERAL_ERROR */
break;
case OK:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - user '%s', password '%s' verified",
user, password);
status = AUTH_GRANTED;
break;
case DECLINED:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - passing user '%s' to next provider",
user);
status = AUTH_USER_NOT_FOUND;
break;
default:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - user '%s', password '%s' denied",
user, password);
status = AUTH_DENIED;
};
return status;
}
static authn_status get_realm_hash(request_rec *r, const char *user,
const char *realm, char **rethash)
{
int status;
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - calling Digest handler\n");
status = call_provider(r, "digest", user, realm, &*rethash);
switch(status) {
case AUTH_GENERAL_ERROR:
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
"Apache::AuthenHook - yikes! something bad happened!");
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - try running with PerlTrace a");
break;
case OK:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - user '%s', hash '%s' found in realm '%s'",
user, *rethash, realm);
status = AUTH_USER_FOUND;
break;
case DECLINED:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - passing user '%s' to next provider",
user);
status = AUTH_USER_NOT_FOUND;
break;
default:
ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
"Apache::AuthenHook - user '%s' in realm '%s' denied",
user, realm);
status = AUTH_DENIED;
};
return status;
}
static const authn_provider authn_AAH_provider =
{
&check_password,
&get_realm_hash,
};
MODULE = Apache::AuthenHook PACKAGE = Apache::AuthenHook
PROTOTYPES: DISABLE
void
register_provider(provider)
SV *provider
CODE:
MP_TRACE_a(MP_FUNC,
"Apache::AuthenHook - registering provider %s\n",
SvPV_nolen(provider));
ap_register_provider(modperl_global_get_pconf(),
AUTHN_PROVIDER_GROUP,
SvPV_nolen(newSVsv(provider)), "0",
&authn_AAH_provider);
Apache-AuthenHook-2.00_04/Changes 0100755 0000764 0000764 00000000660 10226762557 015445 0 ustar geoff geoff Revision history for Perl extension Apache::AuthenHook
2.00_01 05.22.2003
- original version
2.00_02 06.25.2003
- minor doc changes (note prefork only)
2.00_03 04.05.2004
- minor test format changes
- use MP_TRACE_a for internal trace routines
- bump minimum mod_perl version to 1.99_11 in preparation for
future access to Apache AUTH_* constants
2.00_04 04.13.2005
- updates for mod_perl 2.0-RC5 (1.999_22)
Apache-AuthenHook-2.00_04/t/ 0040755 0000764 0000764 00000000000 10226762636 014411 5 ustar geoff geoff Apache-AuthenHook-2.00_04/t/My/ 0040755 0000764 0000764 00000000000 10226762636 014776 5 ustar geoff geoff Apache-AuthenHook-2.00_04/t/My/TestUser.pm 0100644 0000764 0000764 00000001352 10226761063 017101 0 ustar geoff geoff package My::TestUser;
use Apache2::RequestRec;
use Apache2::RequestUtil;
use Apache2::Access;
use Apache2::Const -compile => qw(OK HTTP_UNAUTHORIZED);
use strict;
sub handler {
my $r = shift;
warn "trying ", $r->uri;
warn "auth type ", $r->ap_auth_type;
warn "auth type 2", $r->auth_type;
if ($r->ap_auth_type eq 'Digest') {
my ($user, $realm, $hash) = @_;
warn "inside";
if ($user eq 'testuser' && $realm eq 'realm1') {
$$hash = '0a2e8a13afd0ea7b7e78cc22725bf06a';
warn "ok";
return Apache2::Const::OK;
}
}
else {
my ($user, $password) = @_;
if ($user eq 'testuser' && $password eq 'testpass') {
return Apache2::Const::OK;
}
}
return Apache2::Const::HTTP_UNAUTHORIZED;
}
1;
Apache-AuthenHook-2.00_04/t/My/Digest2.pm 0100644 0000764 0000764 00000001676 10226551405 016632 0 ustar geoff geoff package My::Digest2;
use Apache2::Log;
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR);
use strict;
sub digest {
my ($r, $user, $realm, $hash) = @_;
$r->log->info('called ', join ' : ', __PACKAGE__, @_);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR unless $r->isa('Apache2::RequestRec');
# user1 was ok in My::Digest1
if ($user eq 'user1' && $realm eq 'realm1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# user2 was denied in My::Digest1
if ($user eq 'user2' && $realm eq 'realm1') {
return Apache2::Const::OK;
}
# user3/digest3 is ok
if ($user eq 'user3' && $realm eq 'realm1') {
$$hash = '00f4ee3be98d1c8d83bc526a9bad5308';
return Apache2::Const::OK;
}
# decline user4 outright
if ($user eq 'user4' && $realm eq 'realm1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along
return Apache2::Const::DECLINED;
}
1;
Apache-AuthenHook-2.00_04/t/My/Redirect.pm 0100644 0000764 0000764 00000000347 10226551405 017064 0 ustar geoff geoff package My::Redirect;
use Apache2::RequestRec ();
use Apache2::SubRequest ();
use Apache2::Const -compile => qw(OK);
use strict;
sub handler {
shift->internal_redirect('/digest/index.html');
return Apache2::Const::OK;
}
1;
Apache-AuthenHook-2.00_04/t/My/Basic1.pm 0100644 0000764 0000764 00000001172 10226551405 016422 0 ustar geoff geoff package My::Basic1;
use Apache2::Log;
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR);
use strict;
sub basic {
my ($r, $user, $password) = @_;
$r->log->info('called ', join ' : ', __PACKAGE__, @_);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR unless $r->isa('Apache2::RequestRec');
# user1/basic1 is ok
if ($user eq 'user1' && $password eq 'basic1') {
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along
return Apache2::Const::DECLINED;
}
1;
Apache-AuthenHook-2.00_04/t/My/Digest1.pm 0100644 0000764 0000764 00000001306 10226551405 016617 0 ustar geoff geoff package My::Digest1;
use Apache2::Log;
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR);
use strict;
sub handler {
my ($r, $user, $realm, $hash) = @_;
$r->log->info('called ', join ' : ', __PACKAGE__, @_);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR unless $r->isa('Apache2::RequestRec');
# user1/digest1 is ok
if ($user eq 'user1' && $realm eq 'realm1') {
$$hash = 'eee52b97527306e9e8c4613b7fa800eb';
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2' && $realm eq 'realm1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along
return Apache2::Const::DECLINED;
}
1;
Apache-AuthenHook-2.00_04/t/My/Basic2.pm 0100644 0000764 0000764 00000001510 10226551405 016417 0 ustar geoff geoff package My::Basic2;
use Apache2::Log;
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED HTTP_INTERNAL_SERVER_ERROR);
use strict;
sub handler {
my ($r, $user, $password) = @_;
$r->log->info('called ', join ' : ', __PACKAGE__, @_);
return Apache2::Const::HTTP_INTERNAL_SERVER_ERROR unless $r->isa('Apache2::RequestRec');
# user1 was ok in My::Basic1
if ($user eq 'user1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# user2 was denied in My::Basic1
if ($user eq 'user2') {
return Apache2::Const::OK;
}
# user3/digest3 is ok
if ($user eq 'user3' && $password eq 'basic3') {
return Apache2::Const::OK;
}
# decline user4 outright
if ($user eq 'user4') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along
return Apache2::Const::DECLINED;
}
1;
Apache-AuthenHook-2.00_04/t/03merge.t 0100755 0000764 0000764 00000002364 10226762370 016040 0 ustar geoff geoff use strict;
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestRequest;
use Apache::TestUtil;
# test DIR_MERGE logic
plan tests => 6, (need_lwp &&
need_module('mod_auth_digest') &&
need_module('mod_auth_basic'));
my $url = '/digest/merge/index.html';
my $response = GET $url;
ok t_cmp($response->code,
401,
"GET $url");
# authenticated
$response = GET $url, username => 'testuser', password => 'testpass';
ok t_cmp($response->code,
200,
"GET $url, username => 'testuser', password => 'testpass'");
# bad pass
$response = GET $url, username => 'testuser', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'testuser', password => 'foo'");
$url = '/basic/merge/index.html';
$response = GET $url;
ok t_cmp($response->code,
401,
"GET $url");
# authenticated
$response = GET $url, username => 'testuser', password => 'testpass';
ok t_cmp($response->code,
200,
"GET $url, username => 'testuser', password => 'testpass'");
# bad pass
$response = GET $url, username => 'testuser', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'testuser', password => 'foo'");
Apache-AuthenHook-2.00_04/t/01basic.t 0100755 0000764 0000764 00000004174 10226756524 016025 0 ustar geoff geoff use strict;
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestRequest;
use Apache::TestUtil;
# Basic auth tests
plan tests => 12, (need_lwp &&
need_module('mod_auth_basic'));
my $url = '/basic/index.html';
my $response = GET $url;
ok $response->code == 401;
# bad pass
$response = GET $url, username => 'user1', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'user1', password => 'foo'");
# authenticated in the first provider
$response = GET $url, username => 'user1', password => 'basic1';
ok t_cmp($response->code,
200,
"GET $url, username => 'user1', password => 'basic1'");
# declined in the first provider
$response = GET $url, username => 'user2', password => 'basic2';
ok t_cmp($response->code,
401,
"GET $url, username => 'user2', password => 'basic2'");
# bad pass
$response = GET $url, username => 'user3', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'user3', password => 'foo'");
# authenticated in the second provider
$response = GET $url, username => 'user3', password => 'basic3';
ok t_cmp($response->code,
200,
"GET $url, username => 'user3', password => 'basic3'");
# declined in the second provider
$response = GET $url, username => 'user4', password => 'basic4';
ok t_cmp($response->code,
401,
"GET $url, username => 'user4', password => 'basic4'");
# bad pass
$response = GET $url, username => 'user5', password => 'foo';
skip(! need_module('mod_authn_file'),
ok t_cmp($response->code,
401,
"GET $url, username => 'user5', password => 'foo'")
);
# authenticated in the file provider
$response = GET $url, username => 'user5', password => 'basic5';
skip(! have_module('mod_authn_file'),
ok t_cmp($response->code,
200,
"GET $url, username => 'user5', password => 'basic5'")
);
# non-existent anywhere
$response = GET $url, username => 'user6', password => 'basic6';
ok t_cmp($response->code,
401,
"GET $url, username => 'user6', password => 'basic6'");
Apache-AuthenHook-2.00_04/t/02digest.t 0100755 0000764 0000764 00000004252 10226757205 016216 0 ustar geoff geoff use strict;
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestRequest;
use Apache::TestUtil;
# Digest auth tests
plan tests => 12, (need_lwp &&
need_module('mod_auth_digest'));
my $url = '/digest/index.html';
my $response = GET $url;
ok t_cmp($response->code,
401,
"GET $url");
# bad pass
$response = GET $url, username => 'user1', password => 'foo';
ok t_cmp($response->code,
401,
GET $url, username => 'user1', password => 'foo');
# authenticated in the first provider
$response = GET $url, username => 'user1', password => 'digest1';
ok t_cmp($response->code,
200,
GET $url, username => 'user1', password => 'digest1');
# declined in the first provider
$response = GET $url, username => 'user2', password => 'digest2';
ok t_cmp($response->code,
401,
"GET $url, username => 'user2', password => 'digest2'");
# bad pass
$response = GET $url, username => 'user3', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'user3', password => 'foo'");
# authenticated in the second provider
$response = GET $url, username => 'user3', password => 'digest3';
ok t_cmp($response->code,
200,
"GET $url, username => 'user3', password => 'digest3'");
# declined in the second provider
$response = GET $url, username => 'user4', password => 'digest4';
ok t_cmp($response->code,
401,
"GET $url, username => 'user4', password => 'digest4'");
# bad pass
$response = GET $url, username => 'user5', password => 'foo';
skip(! need_module('mod_authn_file'),
ok t_cmp($response->code,
401,
"GET $url, username => 'user5', password => 'foo'")
);
# authenticated in the file provider
$response = GET $url, username => 'user5', password => 'digest5';
skip(! have_module('mod_authn_file'),
ok t_cmp($response->code,
200,
"GET $url, username => 'user5', password => 'digest5'")
);
# non-existent anywhere
$response = GET $url, username => 'user6', password => 'digest6';
ok t_cmp($response->code,
401,
"GET $url, username => 'user6', password => 'digest6'");
Apache-AuthenHook-2.00_04/t/htdocs/ 0040755 0000764 0000764 00000000000 10226762636 015675 5 ustar geoff geoff Apache-AuthenHook-2.00_04/t/htdocs/index.html 0100755 0000764 0000764 00000000020 10144007014 017637 0 ustar geoff geoff base index.html
Apache-AuthenHook-2.00_04/t/htdocs/merge/ 0040755 0000764 0000764 00000000000 10226762636 016774 5 ustar geoff geoff Apache-AuthenHook-2.00_04/t/htdocs/merge/htaccess 0100755 0000764 0000764 00000000077 10226760010 020500 0 ustar geoff geoff AuthDigestProvider My::TestUser
AuthBasicProvider My::TestUser
Apache-AuthenHook-2.00_04/t/htdocs/merge/index.html 0100755 0000764 0000764 00000000021 10144007014 020737 0 ustar geoff geoff merge index.html
Apache-AuthenHook-2.00_04/t/04redirect.t 0100755 0000764 0000764 00000004323 10226757126 016543 0 ustar geoff geoff use strict;
use warnings FATAL => 'all';
use Apache::Test;
use Apache::TestRequest;
use Apache::TestUtil;
# make sure that internal redirects are handled properly
plan tests => 12, (need_lwp &&
need_module('mod_auth_digest'));
my $url = '/redirect/index.html';
my $response = GET $url;
ok t_cmp($response->code,
401,
"GET $url");
# bad pass
$response = GET $url, username => 'user1', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'user1', password => 'foo'");
# authenticated in the first provider
$response = GET $url, username => 'user1', password => 'digest1';
ok t_cmp($response->code,
200,
"GET $url, username => 'user1', password => 'digest1'");
# declined in the first provider
$response = GET $url, username => 'user2', password => 'digest2';
ok t_cmp($response->code,
401,
"GET $url, username => 'user2', password => 'digest2'");
# bad pass
$response = GET $url, username => 'user3', password => 'foo';
ok t_cmp($response->code,
401,
"GET $url, username => 'user3', password => 'foo'");
# authenticated in the second provider
$response = GET $url, username => 'user3', password => 'digest3';
ok t_cmp($response->code,
200,
"GET $url, username => 'user3', password => 'digest3'");
# declined in the second provider
$response = GET $url, username => 'user4', password => 'digest4';
ok t_cmp($response->code,
401,
"GET $url, username => 'user4', password => 'digest4'");
# bad pass
$response = GET $url, username => 'user5', password => 'foo';
skip(! need_module('mod_authn_file'),
ok t_cmp($response->code,
401,
"GET $url, username => 'user5', password => 'foo'")
);
# authenticated in the file provider
$response = GET $url, username => 'user5', password => 'digest5';
skip(! have_module('mod_authn_file'),
ok t_cmp($response->code,
200,
"GET $url, username => 'user5', password => 'digest5'")
);
# non-existent anywhere
$response = GET $url, username => 'user6', password => 'digest6';
ok t_cmp($response->code,
401,
"GET $url, username => 'user6', password => 'digest6'");
Apache-AuthenHook-2.00_04/t/99pod.t 0100755 0000764 0000764 00000001502 10226757135 015536 0 ustar geoff geoff use File::Spec;
use File::Find qw(find);
use strict;
# check that documentation isn't broken
eval {
# if we have both Test::More and Test::Pod we're good to go
require Test::More;
Test::More->import;
require Test::Pod;
Test::Pod->import;
};
if ($@) {
# otherwise we need to plan accordingly - either
# using Test::More or Test.pm syntax
eval {
require Test::More;
};
if ($@) {
require Test;
Test->import;
plan(tests => 0);
}
else {
plan(skip_all => 'Test::Pod required for testing POD');
}
}
else {
my @files;
find(
sub { push @files, $File::Find::name if m!\.p(m|od|l)$! },
File::Spec->catfile(qw(.. blib lib))
);
plan(tests => scalar @files);
foreach my $file (@files) {
# use the older Test::Pod interface for maximum back compat
pod_ok($file);
}
}
Apache-AuthenHook-2.00_04/t/conf/ 0040755 0000764 0000764 00000000000 10226762636 015336 5 ustar geoff geoff Apache-AuthenHook-2.00_04/t/conf/extra.last.conf.in 0100755 0000764 0000764 00000001647 10144007014 020664 0 ustar geoff geoff LogLevel debug
# these don't default to handler(), so we need to pre-declare them
PerlModule My::Basic1
PerlModule My::Digest2
PerlModule My::Redirect
PerlLoadModule Apache::AuthenHook
Alias /digest @DocumentRoot@
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::Digest1 file My::Digest2::digest
AuthUserFile realm1
Alias /basic @DocumentRoot@
Require valid-user
AuthType Basic
AuthName foorealm
AuthBasicProvider My::Basic1::basic file My::Basic2
AuthUserFile realm1
# not .htaccess for make dist
AccessFileName htaccess
AllowOverride AuthConfig
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::Digest1 file My::Digest2::digest
AuthUserFile realm1
PerlResponseHandler My::Redirect
SetHandler modperl
Apache-AuthenHook-2.00_04/t/realm1 0100755 0000764 0000764 00000000102 10144007014 015463 0 ustar geoff geoff user5:mENThp/zs3fy.
user5:realm1:5242966e36c83e80ed9aee2dc40d2da4
Apache-AuthenHook-2.00_04/MANIFEST 0100755 0000764 0000764 00000000643 10144007014 015257 0 ustar geoff geoff AuthenHook.pm
AuthenHook.xs
Changes
MANIFEST
Makefile.PL
README
INSTALL
t/01basic.t
t/02digest.t
t/03merge.t
t/04redirect.t
t/99pod.t
t/My/Basic1.pm
t/My/Basic2.pm
t/My/Digest1.pm
t/My/Digest2.pm
t/My/TestUser.pm
t/My/Redirect.pm
t/conf/extra.last.conf.in
t/realm1
t/htdocs/index.html
t/htdocs/merge/index.html
t/htdocs/merge/htaccess
META.yml Module meta-data (added by MakeMaker)
ToDo
Apache-AuthenHook-2.00_04/ToDo 0100755 0000764 0000764 00000000163 10144007014 014713 0 ustar geoff geoff grant access to the AUTH_* contants now that mod_perl doesn't translate
handler return codes to HTTP status values
Apache-AuthenHook-2.00_04/META.yml 0100755 0000764 0000764 00000000533 10226762635 015417 0 ustar geoff geoff # http://module-build.sourceforge.net/META-spec.html
#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX#
name: Apache-AuthenHook
version: 2.00_04
version_from: AuthenHook.pm
installdirs: site
requires:
mod_perl2: 0
distribution_type: module
generated_by: ExtUtils::MakeMaker version 6.17
Apache-AuthenHook-2.00_04/INSTALL 0100755 0000764 0000764 00000003307 10144007014 015157 0 ustar geoff geoff NOTE:
mod_perl 1.99_10 (the current CVS version) is required for
this module to work as expected.
additionally, apache 2.1 (the current experimental version
from CVS, not -r APACHE_2_0_BRANCH) is also required for
this module to work as expected.
even with both of those, there are no guarantees and it
still might not work.
you can get the recent CVS sources via
$ cvs -d":pserver:anoncvs@cvs.apache.org:/home/cvspublic" checkout modperl-2.0
$ cvs -d":pserver:anoncvs@cvs.apache.org:/home/cvspublic" checkout httpd-2.0
see
http://perl.apache.org/contribute/cvs_howto.html
http://httpd.apache.org/dev/anoncvs.txt
for detailed instructions on how to use apache.org's anonymous CVS
you can also get recent CVS snapshots at
http://cvs.apache.org/snapshots/modperl-2.0/
http://cvs.apache.org/snapshots/httpd-2.0/
INSTALLATION:
this module follows the standard
$ perl Makefile.PL
$ make
$ su
# make install
routine.
if you want to run the tests, you'll need to do something similar
to the following
$ export APACHE=/usr/local/apache2/bin/httpd
$ export APXS=/usr/local/apache2/bin/apxs
$ make test
whether you choose to specify httpd or apxs depends on whether
or not your installation is has mod_so, so you may need one or
the other or both.
you can also configure the test suite when building the Makefile
$ perl Makefile.PL -apxs /usr/local/apache2/bin/apxs
run
$ t/TEST -help
or see the README in the Apache-Test distribtion for more options
of course, this module is made to run under Apache 2.1 and mod_perl
2.0 so you'll need those as well, again preferably recent copies
complied from CVS. Perl 5.8.0 is also a good idea.
have fun.
Apache-AuthenHook-2.00_04/README 0100755 0000764 0000764 00000014250 10226762502 015020 0 ustar geoff geoff NAME
Apache::AuthenHook - Perl API for Apache 2.1 authentication
SYNOPSIS
PerlLoadModule Apache::AuthenHook
PerlModule My::OtherProvider
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::DigestProvider file My::OtherProvider::digest
AuthUserFile realm1
Require valid-user
AuthType Basic
AuthName foorealm
AuthBasicProvider My::OtherProvider::basic file My::BasicProvider
AuthUserFile realm1
DESCRIPTION
Apache::AuthenHook offers access to the 2.1 Apache authentication
API in Perl. This is different than the authentication API
from Apache 1.3 or even Apache 2.0, but in its differences lies
strength.
For a full description of how authentication works in 2.1, see
http://www.serverwatch.com/tutorials/article.php/2202671
Basically, the difference between 2.0 and 2.1 is that authentication
is now delegated to providers, and each provider has a specific
purpose. For instance, mod_authn_file covers gleaning the password
from an .htpasswd or .htdigest file, while mod_auth_basic covers
the Basic dialogue between the client and server, regardless
of the source of the password. The best part of all this (to me)
is that Digest authentication is also delegated out -
mod_auth_digest now handles all the intricacies of Digest
authentication (including the elusive MSIE support) which
means you don't need to worry about them (and neither do I).
All that Digest authentication requires is *some* authentication
provider to provide user credentials - this can be via
mod_authn_file or another mechanism of your choosing.
Apache::AuthenHook registers and coordinates the use of Perl
handlers as authentication providers.
How does this affect you? Read on...
EXAMPLE
Say you want to enable Digest authentication in your Apache 2.1 server...
PerlLoadModule Apache::AuthenHook
Require valid-user
AuthType Digest
AuthName realm1
AuthDigestProvider My::DigestProvider file
AuthUserFile realm1
This configuration means that My::DigestProvider will be
responsible for providing user credentials for requests to
/digest. if My::DigestProvider finds a suitable user,
mod_auth_digest will verify those credentials and take care of
setting all the proper headers, set the proper HTTP response
status, and so on. If My::DigestProvider cannot find a matching
user it can decide what to do next - either pass the user to
the next provider (in this case the default file provider,
which will use the flat file "realm1") or decide that no user
means no access.
Here is a simple My::DigestProvider
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);
sub handler {
my ($r, $user, $realm, $hash) = @_;
# user1 at realm1 is found - pass to mod_auth_digest
if ($user eq 'user1' && $realm eq 'realm1') {
$$hash = 'eee52b97527306e9e8c4613b7fa800eb';
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2' && $realm eq 'realm1') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along to the next provider
return Apache2::Const::DECLINED;
}
isn't that easy?
the only thing that is a bit tricky here is $$hash. the fourth
argument passed to your handler, $hash, is a reference to
to a simple scalar that needs to be populated with the MD5 hash of
the user:realm:password combination you determine for the
incoming user. this may seem a bit strange, but it is actually
exactly how things work over in Apache C land, so I guess that
makes it ok.
as you can see, returning OK means "user found" and requires
that $$hash be populated - mod_auth_digest will take care
of determining whether the hash matches the incoming Digest
criteria. returning HTTP_UNAUTHORIZED (which is the same
as the former and still available AUTH_REQUIRED constant)
means "no access." returning DECLINED means "some other provider
can try."
The steps are remarkably similar for Basic authentication, first
Require valid-user
AuthType Basic
AuthName foorealm
AuthBasicProvider My::BasicProvider file
AuthUserFile realm1
then
use Apache2::Const -compile => qw(OK DECLINED HTTP_UNAUTHORIZED);
sub handler {
my ($r, $user, $password) = @_;
# user1/basic1 is ok
if ($user eq 'user1' && $password eq 'basic1') {
return Apache2::Const::OK;
}
# user2 is denied outright
if ($user eq 'user2') {
return Apache2::Const::HTTP_UNAUTHORIZED;
}
# all others are passed along to the next provider
return Apache2::Const::DECLINED;
}
In the case of Basic authentication, the return codes mean
essentially the same thing. The one exception is that OK
means that you have checked the user against the password
and have found that they match (as opposed to with Digest,
where the actual verification is not done by you).
These explanations should be enough to get you going -
see the files in the test suite for more examples.
NOTES
This has been tested under the prefork MPM only, using
mostly Perl 5.9.0 (as well as some 5.8.0). It will not
work under threaded MPMs - soon, just not yet.
FEATURES/BUGS
This is very much so alphaware, so beware - bugs may lurk
in unexpected places. there is one bug that is outside
of my control, though, and concerns MSIE and Digest
authentication for URIs that include query strings. see
http://httpd.apache.org/docs-2.0/mod/mod_auth_digest.html
one workaround for this issue is is to use POST instead of
GET for your forms.
A limitation of this interface is that you can't use Perl
providers that are not at least two levels deep -
the criterion for registering a Perl
provider is a simple check for a double-colon.
for example, My::Provider will work while Provider won't
(although Provider::handler will). anyway, single
level handlers are rare, so fixing it would be a lot
of trouble for little benefit.
AUTHOR
Geoffrey Young Egeoff@modperlcookbook.orgE
COPYRIGHT
Copyright (c) 2003, Geoffrey Young
All rights reserved.
This module is free software. It may be used, redistributed
and/or modified under the same terms as Perl itself.
Apache-AuthenHook-2.00_04/Makefile.PL 0100755 0000764 0000764 00000001101 10226551026 016076 0 ustar geoff geoff #!perl
use 5.008;
use ModPerl::MM ();
use Apache::Test;
use Apache::TestMM qw(test clean);
use Apache::TestRunPerl ();
Apache::TestMM::filter_args();
Apache::TestRunPerl->generate_script(bugreport => < 'Apache::AuthenHook',
VERSION_FROM => 'AuthenHook.pm',
PREREQ_PM => { mod_perl2 => 0 },
);