Sign In/My Account | View Cart  
advertisement


Listen Print

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.

Pages: 1, 2, 3

Next Pagearrow