Integrating mod_perl with Apache 2.1 Authentication
by Geoffrey Young
|
Pages: 1, 2, 3
Setting the Stage
Now that we have the ability to call ap_register_provider, we
need to link that into the Apache configuration process somehow. What we do not
want to do is replace current PerlAuthenHandler functionality,
since that directive is for inserting authentication handler logic in place of
Apache's defaults. In our case, we need the default modules to run so they can
call our Perl providers. Instead, we want to make it possible for Perl modules
to register themselves as authentication providers. While we could have Perl
providers call our new register_provider() function directly,
Apache::AuthenHook chose to make the process transparent, using mod_perl's
directive handler API to call register_provider() silently as
httpd.conf is parsed.
Apache::AuthenHook makes sneaky use of directive handlers to extend the
default Apache AuthBasicProvider and
AuthDigestProvider directives so they register Perl providers
on-the-fly. The net result is that Perl providers will be fully registered and
configured via standard Apache directives, similar to the following.
AuthBasicProvider My::BasicProvider file
At configuration time, Apache::AuthenHook will intercept
AuthBasicProvider and register My::BasicProvider. At request time,
mod_auth_basic will attempt to authenticate the user, first using
My::BasicProvider, followed by the default file provider if My::BasicProvider
declines the request.
A nice side effect to this is that through our implementation we will be giving mod_perl developers a feature they have never had before—the ability to interlace Perl handlers and C handlers within the same phase.
AuthDigestProvider My::DigestProvider file My::OtherDigestProvider
Exciting, no? Let's take a look at AuthenHook.pm and see how
the directive handler API works in mod_perl 2.0.
Directive Handlers with mod_perl
Directive handlers are a very powerful but little used feature of mod_perl. For the most part, their lack of use probably stems from the complex and intimidating API in mod_perl 1.0. However, in mod_perl 2.0, the API is much simpler and should lend itself to adoption by a larger audience.
The directive handler API allows mod_perl modules to define their own custom configuration directives that are understood by Apache, For example, enabling modules to make use of configuration variables like:
Foo "bar"
in httpd.conf requires only a few relatively simple settings
you can code directly in Perl.
While using directive handlers simply to replace PerlSetVar
behavior might seem a bit flashy, the techniques used by Apache::AuthenHook are
some of the most powerful mod_perl has to offer.
As previously mentioned, we will be extending the new
AuthBasicProvider and AuthDigestProvider directives
to apply to Perl providers as well, silently registering each provider as the
directive itself is parsed. To do this, we redefine these core directives,
manipulate their configuration data, then disappear and allow Apache to handle
the directives as if we were never there.
The code responsible for this is in AuthenHook.pm.
package Apache::AuthenHook;
use 5.008;
use DynaLoader ();
use mod_perl 1.99_10; # DECLINE_CMD and $parms->info support
use Apache::CmdParms (); # $parms->info
use Apache::Const -compile => qw(OK DECLINE_CMD OR_AUTHCFG RAW_ARGS);
use strict;
our @ISA = qw(DynaLoader);
our $VERSION = '2.00_01';
__PACKAGE__->bootstrap($VERSION);
our @APACHE_MODULE_COMMANDS = (
{ name => 'AuthDigestProvider',
errmsg => 'specify the auth providers for a directory or location',
args_how => Apache::RAW_ARGS,
req_override => Apache::OR_AUTHCFG,
cmd_data => 'digest' },
{ name => 'AuthBasicProvider',
errmsg => 'specify the auth providers for a directory or location',
args_how => Apache::RAW_ARGS,
req_override => Apache::OR_AUTHCFG,
func => 'AuthDigestProvider',
cmd_data => 'basic' },
);
At the top of our module we import a few required items, some that are new
and some that should already be familiar. DynaLoader and the
bootstrap() method are required to pull in the
register_provider() function from our XS implementation and,
unlike with mod_perl 1.0, have nothing to do with the actual directive handler
implementation. The Apache::CmdParms class provides the info()
method we will be illustrating shortly, while Apache::Const gives us access to
the constants we will need throughout the process.
The @APACHE_MODULE_COMMANDS array is where the real interface
for directive handlers begins. @APACHE_MODULE_COMMANDS holds an
array of hashes, each of which defines the behavior of an Apache directive.
Let's focus on the first directive our handler implements,
AuthDigestProvider, forgetting for the moment that mod_auth_digest
also defines this directive.
While it should be obvious that the name key specifies the name
of the directive, it is not so obvious that it also specifies the default Perl
subroutine to call when Apache encounters AuthDigestProvider while
parsing httpd.conf. Later on, we will need to implement the
AuthDigestProvider() subroutine, which will contain the logic for
all the activities we want to perform when Apache sees the
AuthDigestProvider directive.
The args_how and req_override are fields that tell
Apache specifically how the directive is supposed to behave in the
configuration. req_override defines how our directive will
interact with the core AllowOverride
directive, in our case allowing AuthDigestProvider in
.htaccess files only on directories governed by
AllowOverride AuthConfig. Similarly, args_how defines
how Apache should interact with our AuthDigestProvider()
subroutine when it sees our directive in httpd.conf. In the case
of RAW_ARGS, it means that Apache will pass our callback whatever
follows the directive as a single string. Other possible values for both of
these keys can be found in the documentation pointed to at the end of this
article.
The final important key in our first hash is the cmd_data key,
in which we can store a string of our choosing. This will become important in
a moment.
The second hash in @APACHE_MODULE_COMMANDS defines the behavior
of the AuthBasicProvider directive, which for the most part is
identical to AuthDigestProvider. The differences are important,
however, and begin with the addition of the func key. Although the
default Perl subroutine callback for handling directives is the same as the
name of the directive, the func key allows us to point to a
different subroutine instead. Here we will be reusing
AuthDigestProvider() to process both directives. How will we know
which directive is actually being parsed? The cmd_data slot will
contain digest when processing AuthDigestProvider and
basic when processing AuthBasicProvider.
At this point, we have defined what our directives will look like and how
they will interact with Apache in httpd.conf. What we have not
shown is the logic that sits behind our directives. As we mentioned, both of
our directives will be calling the Perl subroutine
AuthDigestProvider, defined in AuthenHook.pm.
sub AuthDigestProvider {
my ($cfg, $parms, $args) = @_;
my @providers = split ' ', $args;
foreach my $provider (@providers) {
# if the provider looks like a Perl handler...
if ($provider =~ m/::/) {
# save the config for later
push @{$cfg->{$parms->info}}, $provider;
# and register the handler as an authentication provider
register_provider($provider);
}
}
# pass the directive back to Apache "unprocessed"
return Apache::DECLINE_CMD;
}
The first argument passed to our directive handler callback,
$cfg, represents the configuration object for our module, which we
can populate with whatever data we choose and access again at request time. The
second argument is an Apache::CmdParms object, which we will use to dig out the
string we specified in the cmd_data slot of our configuration hash
using the info() method.
While the first two arguments are standard and will be there for any
directive handler you write, the third argument can vary somewhat. Because we
specified RAW_ARGS as our args_how setting in the
configuration hash, $args contains everything on the
httpd.conf line following our directive. The standard
Auth*Provider directives we are overriding can take more than one
argument, so we split on whitespace and break apart the configuration into an
array of providers, each of which we then process separately.
Each provider is examined using a cursory check to see whether the specified
provider is a Perl provider. If the provider meets our criteria, we call the
register_provider() function defined in AuthenHook.xs
and keep track of the provider by storing it in our $cfg
configuration object.
The final part of our callback brings the entire process together. The
constant DECLINE_CMD has special meaning to Apache. Just as you
might return DECLINED from a PerlTransHandler to
trick Apache into thinking no translation took place, returning
DECLINE_CMD from a directive handler tricks Apache into thinking
that the directive was unprocessed. So, after our
AuthDigestProvider() subroutine runs, Apache will continue along
until it finds mod_auth_digest, which will then process the
directive as though we were never there.
The one final piece of AuthenHook.pm that we need to discuss is
directive merging. In order to deal properly with situations when directives
meet, such as when AuthBasicProvider is specified in both an
.htaccess file as well as the <Location> that
governs the URI, we need to define DIR_CREATE() and
DIR_MERGE() subroutines.
DIR_CREATE() is called at various times in the configuration
process, including when <Location> and related directives are
parsed at configuration time, as well as whenever an .htaccess
file enters the request cycle. This is where we create the $cfg
object our callback uses to store configuration data. While it is not required,
DIR_CREATE() is a good place to initialize fields in the object as
well, which prevents accidentally dereferencing nonexistent references.
sub DIR_CREATE {
return bless { digest => [],
basic => [], }, shift;
}
DIR_MERGE, generally called at request time, defines how we
handle places where directives collide. The following code is standard for
allowing the current configuration (%$base) to inherit only
missing parameters from higher configurations (%$add), which is
the behavior you are most likely to want.
sub DIR_MERGE {
my ($base, $add) = @_;
my %new = (%$add, %$base);
return bless \%new, ref($base);
}
1;
Thus ends AuthenHook.pm.
The final result is pretty amazing. By secretly intercepting the
AuthDigestProvider directive before mod_auth_digest
has the chance to process it, we have provided an interface that makes the
presence of Apache::AuthenHook all but undetectable. To enable the new provider
mechanism for mod_perl developers, all that is required is to load
Apache::AuthenHook using the new PerlLoadModule directive
PerlLoadModule Apache::AuthenHook
and their Perl providers will be magically inserted into the authentication phase at the appropriate time.

