An Authorizing Docbase Viewer

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.172.68