Apache-AuthenHook-2.00_04/0040755000076400007640000000000010226762636014146 5ustar geoffgeoffApache-AuthenHook-2.00_04/AuthenHook.pm0100755000076400007640000001757010226762567016566 0ustar geoffgeoffpackage 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.xs0100755000076400007640000002124610226755607016574 0ustar geoffgeoff#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/Changes0100755000076400007640000000066010226762557015445 0ustar geoffgeoffRevision 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/0040755000076400007640000000000010226762636014411 5ustar geoffgeoffApache-AuthenHook-2.00_04/t/My/0040755000076400007640000000000010226762636014776 5ustar geoffgeoffApache-AuthenHook-2.00_04/t/My/TestUser.pm0100644000076400007640000000135210226761063017101 0ustar geoffgeoffpackage 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.pm0100644000076400007640000000167610226551405016632 0ustar geoffgeoffpackage 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.pm0100644000076400007640000000034710226551405017064 0ustar geoffgeoffpackage 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.pm0100644000076400007640000000117210226551405016422 0ustar geoffgeoffpackage 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.pm0100644000076400007640000000130610226551405016617 0ustar geoffgeoffpackage 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.pm0100644000076400007640000000151010226551405016417 0ustar geoffgeoffpackage 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.t0100755000076400007640000000236410226762370016040 0ustar geoffgeoffuse 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.t0100755000076400007640000000417410226756524016025 0ustar geoffgeoffuse 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.t0100755000076400007640000000425210226757205016216 0ustar geoffgeoffuse 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/0040755000076400007640000000000010226762636015675 5ustar geoffgeoffApache-AuthenHook-2.00_04/t/htdocs/index.html0100755000076400007640000000002010144007014017637 0ustar geoffgeoffbase index.html Apache-AuthenHook-2.00_04/t/htdocs/merge/0040755000076400007640000000000010226762636016774 5ustar geoffgeoffApache-AuthenHook-2.00_04/t/htdocs/merge/htaccess0100755000076400007640000000007710226760010020500 0ustar geoffgeoffAuthDigestProvider My::TestUser AuthBasicProvider My::TestUser Apache-AuthenHook-2.00_04/t/htdocs/merge/index.html0100755000076400007640000000002110144007014020737 0ustar geoffgeoffmerge index.html Apache-AuthenHook-2.00_04/t/04redirect.t0100755000076400007640000000432310226757126016543 0ustar geoffgeoffuse 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.t0100755000076400007640000000150210226757135015536 0ustar geoffgeoffuse 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/0040755000076400007640000000000010226762636015336 5ustar geoffgeoffApache-AuthenHook-2.00_04/t/conf/extra.last.conf.in0100755000076400007640000000164710144007014020664 0ustar geoffgeoffLogLevel 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/realm10100755000076400007640000000010210144007014015463 0ustar geoffgeoffuser5:mENThp/zs3fy. user5:realm1:5242966e36c83e80ed9aee2dc40d2da4 Apache-AuthenHook-2.00_04/MANIFEST0100755000076400007640000000064310144007014015257 0ustar geoffgeoffAuthenHook.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/ToDo0100755000076400007640000000016310144007014014713 0ustar geoffgeoffgrant 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.yml0100755000076400007640000000053310226762635015417 0ustar geoffgeoff# 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/INSTALL0100755000076400007640000000330710144007014015157 0ustar geoffgeoffNOTE: 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/README0100755000076400007640000001425010226762502015020 0ustar geoffgeoffNAME 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.PL0100755000076400007640000000110110226551026016076 0ustar geoffgeoff#!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 }, );