Show Contents Previous Page Next Page
Chapter 6 - Authentication and Authorization / Authorization Handlers Advanced Gender-Based Authorization A dissatisfying feature of Apache::AuthzGender is that when an unauthorized user finally gives up and presses the cancel button, Apache displays the generic "Unauthorized" error page without providing any indication of why the user was refused access. Fortunately this is easy to fix with a custom error response. We can call the request object's custom_response() method to display a custom error message, an HTML page, or the output of a CGI script when the AUTH_REQUIRED error occurs.
Another problem with Apache::AuthzGender is that it uses a nonstandard way to configure the authorization scheme. The standard authorization schemes use a require directive as in:
require group authors
At the cost of making our module slightly more complicated, we can accommodate this too, allowing access to the protected directory to be adjusted by any of the following directives:
require gender F # allow females
require user Webmaster Jeff # allow Webmaster or Jeff
require valid-user # allow any valid user
Example 6-10 shows an improved Apache::AuthzGender
that implements these changes. The big task is to recover and process the list
of require directives. To retrieve the directives, we call the request
object's requires() method. This method returns an array reference
corresponding to all of the require directives in the current directory
and its parents. Rather than being a simple string, however, each member of
this array is actually a hash reference containing two keys: method_mask
and requirement. The requirement key is easy to understand.
It's simply all the text to the right of the require directive (excluding
comments). You'll process this text according to your own rules. There's nothing
magical about the keywords user, group, or valid-user.
The method_mask key is harder to explain. It consists of a bit mask indicating what methods the require statement should be applied to. This mask is set when there are one or more <LIMIT> sections in the directory's configuration. The GET, PUT, POST, and DELETE methods correspond to the first through fourth bits of the mask (counting from the right). For example, a require directive contained within a <LIMIT GET POST> section will have a method mask equal to binary 0101, or decimal 5. If no <LIMIT> section is present, the method mask will be -1 (all bits set, all methods restricted). You can test for particular bits using the method number constants defined in the :methods section of Apache::Constants. For example, to test whether the current mask applies to POST requests, you could write a piece of code like this one (assuming that the current requires() is in $_ ):
if ($_->{method_mask} & (1 << M_POST)) {
warn "Current requirements apply to POST";
}
In practice, you rarely have to worry about the method mask within your own authorization modules because mod_perl automatically filters out any require statement that wouldn't apply to the current transaction.
In the example given earlier, the array reference returned by requires() would look like this:
[
{
requirement => 'gender F',
method_mask => -1
},
{
requirement => 'user Webmaster Jeff',
method_mask => -1
},
{
requirement => 'valid-user',
method_mask => -1
}
]
The revised module begins by calling the request object's requires() method and storing it in a lexical variable $requires :
my $r = shift;
my $requires = $r->requires;
return DECLINED unless $requires;
If requires() returns undef, it means that no require statements were present, so we decline to handle the transaction. (This shouldn't actually happen, but it doesn't hurt to make sure.) The script then recovers the user's name and guesses his or her gender, as before.
Next we begin our custom error message:
my $explanation = <<END;
<TITLE>Unauthorized</TITLE>
<H1>You Are Not Authorized to Access This Page</H1>
Access to this page is limited to:
<OL>
END
The message will be in a text/html page, so we're free to use HTML
formatting. The error warns that the user is unauthorized, followed by a numbered
list of the requirements that the user must meet in order to gain access to
the page (Figure 6-2). This will help us confirm
that the requirement processing is working correctly.
Figure 6-2. The custom error message generated
by Apache::AuthzGender specifically lists the requirements that the user has
failed to satisfy.
Now we process the requirements one by one by looping over the array contained in $requires :
for my $entry (@$requires) {
my($requirement, @rest) = split /\s+/, $entry->{requirement};
For each requirement, we extract the text of the require directive and split it on whitespace into the requirement type and its arguments. For example, the line require gender M would result in a requirement type of gender and an argument of M. We act on any of three different requirement types. If the requirement equals user, we loop through its arguments seeing if the current user matches any of the indicated usernames. If a match is found, we exit with an OK result code:
if (lc $requirement eq 'user') {
foreach (@rest) { return OK if $user eq $_; }
$explanation .= "<LI>Users @rest.\n";
}
If the requirement equals gender, we loop through its arguments looking
to see whether the user's gender is correct and again return OK
if a match is found:8
elsif (lc $requirement eq 'gender') {
foreach (@rest) { return OK if $guessed_gender eq uc $_; }
$explanation .= "<LI>People of the @G{@rest} persuasion.\n";
}
Otherwise, if the requirement equals valid-user, then we simply return OK because the authentication module has already made sure of this for us:
elsif (lc $requirement eq 'valid-user') {
return OK;
}
}
$explanation .= "</OL>";
As we process each require directive, we add a line of explanation to the custom error string. We never use this error string if any of the requirements are satisfied, but if we fall through to the end of the loop, we complete the ordered list and set the explanation as the response for AUTH_REQUIRED errors by passing the explanation string to the request object's custom_response() method:
$r->custom_response(AUTH_REQUIRED, $explanation);
The module ends by noting and logging the failure, and returning an AUTH_REQUIRED status code as before:
$r->note_basic_auth_failure;
$r->log_reason("user $user: not authorized", $r->filename);
return AUTH_REQUIRED;
}
The logic of this module places a logical OR between the requirements. The user is allowed access to the site if any of the require statements is satisfied, which is consistent with the way Apache handles authorization in its standard modules. However, you can easily modify the logic so that all requirements must be met in order to allow the user access.
Example 6-10. An Improved Apache::AuthzGender
package Apache::AuthzGender2;
use strict;
use Text::GenderFromName qw(gender);
use Apache::Constants qw(:common);
my %G = ('M' => "male", 'F' => "female");
sub handler {
my $r = shift;
my $requires = $r->requires;
return DECLINED unless $requires;
my $user = ucfirst lc $r->connection->user;
my $guessed_gender = uc(gender($user)) || 'M';
my $explanation = <<END;
<TITLE>Unauthorized</TITLE>
<H1>You Are Not Authorized to Access This Page</H1>
Access to this page is limited to:
<OL>
END
for my $entry (@$requires) {
my($requirement, @rest) = split /\s+/, $entry->{requirement};
if (lc $requirement eq 'user') {
foreach (@rest) { return OK if $user eq $_; }
$explanation .= "<LI>Users @rest.\n";
}
elsif (lc $requirement eq 'gender') {
foreach (@rest) { return OK if $guessed_gender eq uc $_; }
$explanation .= "<LI>People of the @G{@rest} persuasion.\n"; }
elsif (lc $requirement eq 'valid-user') {
return OK;
}
}
$explanation .= "</OL>";
$r->custom_response(AUTH_REQUIRED, $explanation);
$r->note_basic_auth_failure;
$r->log_reason("user $user: not authorized", $r->filename);
return AUTH_REQUIRED;
}
1;
__END__
Show Contents Previous Page Next Page Copyright © 1999 by O'Reilly & Associates, Inc. |