Sign In/My Account | View Cart  
advertisement


Listen Print

Building a Bridge to the Active Directory
by Kelvin Param | Pages: 1, 2

Since we have used the user ID instead of the full userPrincipalName, we need to append @somedomain to the user ID. Hence we get,

    if ($strAttributeName eq "userPrincipalName") { 
        $strAttributeValue = $strAttributeValue . $strDomain; 
    }

The remainder of the above variable declarations with the exception of %hashAdRecord are required to enable ADO to bind with ADSI. I'll try to explain what's happening there. First we create the following objects:

a. ADODB connection object

b. ADODB Recordset object

c. ADODB Command object

Once these objects have been created, we assign values to selected properties of the ADODB Connection object.

    $objConnection->{Provider} = ("ADsDSOObject");
    $objConnection->{ConnectionString} = ($strConnectionString);
    $objConnection->Open();
    $objCommand->{ActiveConnection} = ($objConnection);

We open the ADODB connection, and assign the ADODB Connection object to the ActiveConnection property of the ADODB Command object.

    $objCommand->{CommandText} = ($strCommandText);
    $objRecordset = $objCommand->Execute($strCommandText) 
        or die ("Cannot execute!");

We assign $strCommandText to the CommandText property of the ADODB Command object, and next, we run the Execute method of the ADODB Command object.The $strCommandText variable is the argument of the Execute method. The value of the $strCommandText is the Active Directory analog of an SQL statement use to query an relational database. $strCommandText comprises four elements:

a. $strADsPath

b. $strFilter

c. $strAttribs

d. $strScope

$strADsPath contains the address of the Active Directory server, and the subset of the total set of records one wishes to search through. The address of the Active Directory server is specified by splitting the domain name up. And the result is DC=someuniversity,DC=edu. The subset of the records in the Active Directory is defined by listing the hierarchy of OUs (Organizational Units) e.g. OU=somedivision,OU=somedepartment. The syntax of the entire $strADsPath string complies with the LDAP RFC 2307 syntax. However, many programming or scripting languages do not seem to support this syntax as yet. Instead, the LDAP RFC 2251 syntax is widely supported.

$strFilter specificies the Active Directory records that we are interested in. In this instance, we are interested in the record where userPrincipalName is equal to someuserid@someuniversity.edu. Hence, this relationship is expressed as

    $strFilter="(" . $strAttributeName . "=" . $strAttributeValue . ")".

$strAttribs contains the list of fields that we want to retrieve from the query. If the query is successful, the result will be returned in ADODB Recordset object, and we can use the methods in the ADODB Recordset objects to retrieve the data.

$strScope="subtree" indicates that the scope of the search is limited to the subtree that has been specified in $strADsPath.

Finally, we turn our attention to getting a list of a user's superiors. This is possible because each user's record in the Active Directory has a manager field that contains the canonical name or CN (amongst other bits of data) of the user's immediate superior. The GetCommandChain subroutine returns an array of a user's superiors. This subroutine takes two arguments:

a. $refAdRecord

b. $strApexTitle

    sub GetManagerString {
        my %hashAdRecord = %$refAdRecord;
        my $strManagerString=$hashAdRecord{"manager"};
        return $strManagerString;
    }

$refAdRecord is the reference of the user's data returned by GetUserData. $strApexTitle is the highest office (e.g. ``Division Manager'') relevant to the search. The logic within this subroutine is quite simple. We make use of the GetManagerString subroutine to retrieve the data that was obtained from the Manager field in the Active Directory.

    sub GetManagerCN {
        my $strManagerString=shift;
        $strManagerString =~ /\bCN=([-_#@%\&\$\*\.a-zA-Z0-9\s]+)/;
        return $1;
    }

Next we use the GetManagerCN subroutine to extract the canonical name from the string returned by the GetManagerString subroutine.

    do {
        $refAdRecord =
        <GetUserData>("cn",$strManagerCN,"LDAP://OU=somedivision,
        OU=somedepartment,DC=someuniversity,DC=edu",
        "\@someuniversity.edu");
        %hashAdRecord=%$refAdRecord;
        $strTitle = $hashAdRecord{"title"};
        push @arrCommandChain, {%hashAdRecord};
        $strManagerString = GetManagerString($refAdRecord);
        $strManagerCN = GetManagerCN($strManagerString); 
    } until ($strTitle eq $strApexTitle);
    $refCommandChain = \@arrCommandChain;
    return $refCommandChain;

A do loop is used to traverse up the management hierarchy till we reach the user record that has a title value which matches the value in $strApexTitle. Over each iteration, we store the hash of the data retreived from the Active Directory into the array @arrCommandChain. Finally, we return the reference to this array as $refCommandChain.

In the next section we will examine how the client uses the XML-RPC mechanism to make requests of the daemon.

The Client in Perl

Since most of the work is done by the active directory daemon, the active directory client in Perl is relatively simple. Because we've used the XML-RPC protocol, the client can be written in various languages, e.g. PHP 4, Python, Java, etc. However we shall continue with Perl to be consistent with the theme of this web site.

As with the daemon, we invoke the strict pragma, and load the Frontier::Client module.

    $strDaemon_url = "http://www.someuniversity.edu:8080/RPC2";

To make use of the subroutines in the Active Directory daemon, we need to provide a url to the daemon. In our case we've assigned the url to the $strDaemon_url variable. As discussed in the Daemon section of this article, we've assigned port 8080 for our daemon's use. The Frontier::Daemon package has also assigned a default virtual directory, RPC2.

Next, we create a new instance of the XML RPC client by passing $strDaemon_url as the value part of the key-value pair of a hash entry.

    $objServer = Frontier::Client->new(url => $strDaemon_url);

Now we can utilize the published subroutines in the Active Directory daemon.

$refAdRecord=$objServer->call('activedirectory_daemon.AuthenticateUser','someuserid',
'Somepassword1','LDAP://OU=somedivision,OU=somedepartment,DC=someuniversity,DC=edu',
"\@someuniversity.edu");

The first argument in the $objServer->call subroutine is the qualified daemon subroutine name. The rest of the arguments are the arguments of the daemon subroutine.

As mentioned earlier, the XML-RPC daemon, can only return references. As such, we need to convert references to hashes, or arrays as appropriate. For example, %hashAdRecord = %$refAdRecord.

Conclusion

XML-RPC is a much more than an effective mechanism to enable distributed computing. We can use it to provide access to platform specific services. In our case, we used XML-RPC to enable a non-Windows host to access data and services in the Active Directory. Furthermore, XML-RPC is simple to implement. I've made forays into distributed computing several years ago by way of Java's RMI and Microsoft's DCOM. In my experience, XML-RPC is by far the cleanest and most fuss-free mechanism of the three.

So, if you're a Perl programmer, and are looking to leveraging off a service that only runs on the Windows platform, give Active Perl and XML-RPC a go. You'll be pleasantly surprised.

Resources

http://www.cpan.org

http://www.activestate.com

http://xmlrpc-c.sourceforge.net/xmlrpc-howto/xmlrpc-howto.html