Now let’s put the pieces together. Example 12.4 shows how an authorizing version of a docbase viewer, such as the doc-view.pl script we saw in Chapter 7, can restrict access to a docbase based on a combination of group membership and attribute-based subscription. It enforces the following requirements:
The user can be authenticated to a directory server.
The user is a member of the subscribers group.
The company
field of the requested document
matches one of the subscribed-to companies listed for that user in
the subscription database.
In addition to illustrating these mechanisms, Example 12.4 shows how it’s possible to integrate the Perl technologies we’ve seen already into the Active Server Pages environment.
Example 12-4. An ASP Version of the Authorizing Viewer
<%@ language = PerlScript%> <% use Group::LdapGroup; use MIME::Base64; use SHA; use Docbase::Docbase; my $g = Group::LdapGroup->new("ldap.roninhouse.com",389,"o=RoninHouse.com", "ProductAnalysisSubscribers","uid=admin,o=RoninHouse.com","admin_passwd"); my $doc = $Request->{doc}->{Item}; # retrieve CGI var # for document my $dbh = $Application->Contents->dbhandle; # acquire db handle my $db = Docbase::Docbase->new('ProductAnalysis'), # initialize docbase my $metarecord = $db->getMetadata("$docroot/$doc"); # look up metadata for doc my $company = $metarecord->{company}; # extract company field my $deny_message = ""; my $basic_auth_obj = # get basic auth header object $Request->ServerVariables(HTTP_AUTHORIZATION); my $basic_auth = $basic_auth_obj->{Item}; # extract header from object if ( ! isBasicAuthUserForCompany($company) ) # can't authorize user { # for company $Response->{Status} ="401 $deny_message"; # say why not in status header $Response->AddHeader("WWW-Authenticate", # and issue auth challenge "Basic realm=ProductAnalysisSubscribers"); } else { $Response->write("Authorized"); # success #--code to display record goes here-- # show the authorized record } sub isBasicAuthUserForCompany { my ($company) = @_; if ( $basic_auth eq '' ) # if no auth credentials { $deny_message = "NoCredentials "; # fail for that reason return 0; } $basic_auth =~ m/Basic (.+)/i; # isolate MIME-encoded credentials # in $1 my ($user, $typed_password) = # extract user:password split(':',decode_base64($1)); if (! isAuthenticated ($g, $user, $typed_password) ) # if password bad { $deny_message = "BadPassword"; # fail for that reason return 0; } if (! $g->isMember($user) ) # if user not member of this group { $deny_message = "NotMember"; # fail for that reason return 0 ; } return isSubscribedToCompany ($user, $company); # test subscription } sub isAuthenticated { my ($g,$user, $typed_password) = @_; my $sha = new SHA; # create new SHA object $sha->add($typed_password); # load in password typed by user my $digest = $sha->digest(); # hash it my $computed_encrypted_password = # MIME-encode it encode_base64($digest); chomp $computed_encrypted_password; # trim newline added by encoder my ($stored_encrypted_password) = # look up password $g->getProperty($user,"userpassword"); $stored_encrypted_password =~ s/{SHA}//; # isolate password return # return comparison ( $computed_encrypted_password eq $stored_encrypted_password ); } sub isSubscribedToCompany { my ($user, $company) = @_; if ( $company eq '' ) # no company found in { # requested doc $deny_message = "NoCompanySpecified"; # fail for that reason return 0; } $user = alltrim($user); my $st = "select count(*) from cmp_users where cmp = '$company' and user = '$user'"; my $rs = $dbh->Execute($st); # execute sql my $count = $rs->Fields(0)->value; # extract company name if (! $count) # user not subscribed to company { $deny_message = "NotSubscribedToCompany"; # fail for that reason return 0; } return 1; } %>
This example expands on the fragment shown in Example 12.3, combining database-oriented subscription lookup with directory-oriented user lookup. And, just to show that it’s possible, I’ve switched from a portable Perl implementation to an ASP-specific setup. Although Example 12.4 is written in Perl, it runs in the ASP environment under IIS or another NT server that supports Active Server Pages. Where the earlier example used DBI to connect to a database, this example uses an ActiveX Data Objects database (ADODB) object.
Where does that ADODB object come from? It’s not shown in Example 12.4 because it’s part of the Active Server
Pages environment. The global.asa
file that
governs the authorizing script’s /cgi-bin
directory contains the following setup code:
<SCRIPT LANGUAGE=VBScript RUNAT=Server> sub Application_OnStart Set Conn = Server.CreateObject("ADODB.Connection") Conn.Open "Subscribers" Set Application("dbhandle") = Conn end sub </SCRIPT>
In addition to its method of attribute-based authentication, this
script illustrates one way to cache a database connection. The
Server.CreateObject( )
call happens just once,
producing a database connection that’s tucked into a slot of
the ASP Application object. Each time the authorizing script runs, it
fetches a handle to that connection from the persistent Application
object. Could we cache a connection to the LDAP directory as well?
Not with the current version of Group::LdapGroup.
But a version of that module that used ADSI could do so by stashing
an ADSI handle into the ASP Application object, just as we’re
doing with the ADOBD object shown here.
It’s fascinating to see how
PerlScript, the
version of Win32 Perl that plugs into the ASP environment, can wield
all the Perl modules we’ve seen so far while simultaneously
tapping into the facilities of the ASP environment. What would Example 12.4 look like if written in the generic Perl style
of the earlier Example 12.1? Not much different.
I’d have used TinyCGI to fetch the CGI
variables instead of ASP’s Request object, and
DBI to connect to the database. The resulting
script would be a more general solution that could run under NT/IIS
or Unix/Apache. However, you’d need to cache the database
connection differently in each of these environments.
ActiveState’s PerlEx (http://www.activestate.com/) is one way to
do that for NT/IIS; mod_ perl
(http://perl.apache.org/) and
Apache::DBI (a CPAN module) combine to create one
solution for Unix/Apache; the dhttp system
we’ll explore in Chapter 15, solves the
problem in another way for either environment. For the kinds of
scripts that you need to write to support custom groupware,
portability often has little to do with the script language itself.
What matters more is the environment in which the script runs and the
components available to it.
There’s no file-oriented access control here; our authorizing script is the sole arbiter of access to the docbase. It follows that you can’t locate the docbase in a public web directory, since users then can bypass the access script and scoop up the files directly. If you locate the docbase somewhere that’s script accessible but not web-visible, you can guarantee that the script is the only way to reach it and that the script’s access-control logic will always apply.
3.137.172.68