DocMgr with AD Authentication

Over the past couple of days I hacked an older version of DocMgr to authenticate users to Active Directory. I thought it might be interesting for some readers to see my thought process, hence this post.

All of the scripts in this post along with my test directory and my changes to DocMgr can be downloaded here.

First I started out with a simple php form for authentication. The input portion is in the download, this is the processing side. Some interesting notes about this script. At first I was using the full distinguished name (DN) of the user but that doesn’t scale. It was not until I download adLDAP that I saw you could just use username@domain.org to authenticate. I don’t know why I never tried, but that stymied me.

The reason this script looks for groups the user belongs to is because originally I figured I would limit access by group. The problem is that DocMgr has it’s own permission system and you must be in the database in order to be able to login. The script at the bottom is how I dealt with that limitation.

The ldpad_set_option and ldap_get_option calls were for testing once I began hacking DocMgr. I added them in to test an error I was getting in ldap_search.

<?php
session_start();
header("Cache-control: private"); //IE 6 Fix

$username=$_POST['username'];
$password=$_POST['password'];
$ds;
$searchbase = 'OU=Users,DC=CIRCUS,DC=ORG';
$group = 'CN=DocMgr,OU=Users,DC=CIRCUS,DC=ORG';

echo "<html> <head>  <title>AD Test</title></head> <body>";
echo "<p align=\"left\">This is testAD</p>\n"; 
echo "Username: ". $username . "<br>\n";
echo "Password: ". $password . "<br>\n";

function ad_ldap_auth()
{
	global $username, $password, $ds;
	$ldapServer = 'server.circus.org';
	$ldapPort = '389';
	$ds = ldap_connect($ldapServer, $ldapPort)
		or die("Could not connect to ldap.");

	ldap_set_option($ds, LDAP_OPT_REFERRALS, 0);

	if ($ds)
	{
		$binddn = "$username@.circus.org";
		$ldapbind = ldap_bind($ds, $binddn, $password);

		if ($ldapbind)
		{
		        echo "Bound<br>\n";                  
		}else{
		        echo "<br>Not bound<br>\n";                  
		}
	}
	return $ldapbind;
}


$test = ad_ldap_auth();

if ($test)
{
	$_SESSION['name'] = $username;
	$_SESSION['expire'] = (time() + 3600);

	$filter = "sAMAccountName=$username";
	//$filter = "sAMAccountName=*";
	$attrib = array("memberOf");
	$attrsonly = 0;
	$sizelimit = 3;
	$timelimit = 10;

	ldap_get_option($ds, LDAP_OPT_SIZELIMIT, $optVal); // returns 0
	echo "size_limit =". $optVal ."<br>";
	
	ldap_set_option($ds, LDAP_OPT_SIZELIMIT, 5); // returns TRUE
	ldap_get_option($ds, LDAP_OPT_SIZELIMIT, $optVal); // returns 0
	echo "size_limit =". $optVal ."<br>";

	$sr = ldap_search($ds, $searchbase, $filter, $attrib);

	for ($entry=ldap_first_entry($ds,$sr); $entry!=false; $entry=ldap_next_entry($ds,$entry))
	{
		$values = ldap_get_values($ds, $entry, "memberOf");

		echo $values["count"] . " groups for this entry.<br />";

		for ($i=0; $i < $values["count"]; $i++) 
		{
		    	echo $values[$i] . "<br />";
			if (strcmp($values[$i],$group) == 0)
			{
				echo "User is member.<br>";
			}
		}
	}
} else {
	echo "LDAP Failed.";
}

ldap_unbind($ds);

?>

After finishing my script it was time to move on to DocMgr. I turned on DEBUG in the config.php and began placing debug output throughout DogMgr so that I could see the flow of function calls as the program ran.

if (DEBUG)
   print("authroized == 1<br>");

I wrote down the flow of some key authentication functions and then began reading them to understand what each did and where I should start hacking. Then I went and broke the old ldap includes into open-ldap and ad-ldap includes and set up the ifdefs in the different files. Doing this I was able to still use my OpenLDAP authentication and could hack on ad-ldap.

I set up the ad-ldap-config.php file by comparing the output of ldapsearch in ldif format on both OpenLDAP and AD. We made some changes to the AD population script and moved forward using employeeNumber instead of uidNumber.

After I got the correct entries in AD and OpenLDAP figured out it was just a matter of hacking my test script into the correct functions and testing. There were a couple of errors that I needed to fix. The first was caused by searching by for sAMAccountName=* in the else portion of the ldap_account_search function of docmgr/include/ad_ldap.inc.php.

This was the first error and fix:

Warning: ldap_search(): Partial search results returned: Sizelimit exceeded in /var/www/docmgr/include/ad_ldap.inc.php on line 697

ldap_set_option($ds, LDAP_OPT_SIZELIMIT, 5);

This was the second error and fix:

Warning: ldap_search(): Search: Operations error in /var/www/docmgr/include/ad_ldap.inc.php 

ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);

Once I finished making DocMgr authenticate to AD I wanted to make the integration into AD complete so that I did not have to manually add or remove users within the web interface. The following is the script that does that.

It is run every morning from cron.

# crontab -l
# m h  dom mon dow   command
# Add/remove DocMgr users according to AD group DocMgr.
16 4 * * * /usr/local/bin/ad-docmgr.pl

And the code for allowing access to DocMgr. I truncate the table and just add the users each time.

#!/usr/bin/perl

# 2010-04-13  Jud Bishop
# This script queries active directory for every member of the DocMgr group
# and adds them to the auth_accoutperm table, which gives them the ability
# to login to DocMgr after authenicationg to AD.

use strict;
use DBI;
use Net::LDAP;
use Net::LDAP::LDIF;
use Net::LDAP::Entry;

# Globals
# 1 == debug
# 0 == quiet
my $debug = 1;

sub connectLDAP {
	if($debug){print "connectLDAP\n";}

	my $ldap= Net::LDAP->new("server.circus.org", port =>"389", version =>"3" )
		or die $!;
	
	my $result = $ldap->bind( "CN=SpecialUser,DC=CIRCUS,DC=ORG", 
				password => "PassWord" );
		die $result->error() if $result->code();

	return $ldap;
}

sub queryORG {

	my $mesg;
	my ($ldap, $dbh) = @_;

	if($debug){print "queryORG\n";}

	$mesg = $ldap->search(
		base => "OU=Users,DC=CIRCUS,DC=ORG",
		scope => "sub",
		filter => "(memberOf = CN=DocMgr,OU=Groups,DC=CIRCUS,DC=ORG)");
	
	if ( $mesg->count() > 0 )
	{
       		my $max = $mesg->count;
                for ( my $j = 0 ; $j < $max ; $j++ ) 
                {
			my $entry = $mesg->entry ( $j );
			foreach my $t ( $entry->get_value( "employeeNumber" ))
	                {
				if($debug){print "employeeNumber $t\n";}
				insertQuery($dbh, $t);
			}
		}
	}
}

sub unbindLDAP {
	if($debug){print "unbindLDAP\n";}
	my $ldap = shift;
	$ldap->unbind();

}

sub connectPostgres {
	if($debug){print "connectPostgres\n";}

	my $dbh = DBI->connect('DBI:Pg:dbname=docmgr;host=127.0.0.1', 'DocMgrUserName', 'PassWord' , {
		PrintError => 0,
		RaiseError => 1 # Report errors via die(), needs less error handling because
				# DBI will check for us and die() automagically.
	});

	if ($dbh) {
		# From above we don't have to check, this is just for debugging.
		if($debug){print "connected\n";}
		return $dbh;
	}
}

# Truncate the table so we don't have to worry about who is there and who isn't.
sub deleteQuery {
	if($debug){print "deleteQuery\n";}

	my $dbh = shift;
	my $sth = $dbh->prepare("TRUNCATE auth_accountperm");
	$sth->execute;
}

sub insertQuery {
	if($debug){print "insertQuery\n";}

	my ($dbh, $employeeNumber) = @_;
	
	my $sth = $dbh->prepare("INSERT INTO auth_accountperm VALUES ($employeeNumber, 1, true)");

	$sth->execute;
	if($debug){print "INSERT INTO auth_accountperm VALUES ($employeeNumber, 1, true)\n"};
}

sub disconnectPostgres {
	if($debug){print "disconnectPostgres\n";}
	
	my $dbh = shift;
	$dbh->disconnect();
}

my $ldap = connectLDAP();

my $dbh = connectPostgres();

deleteQuery($dbh);

queryORG($ldap,$dbh);

disconnectPostgres($dbh);

unbindLDAP ($ldap);

# end main
This entry was posted in Code, Linux. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s