PHP – LDAP Authentication and Getting LDAP User Groups for user

Some pretty useful functions to check if LDAP Username/Password entered are valid. And to get LDAP Groups that a user belongs to so group based access can be added.

$host = 'ldap-server.domain.com';
$port = '636';
$protocol = 'ldaps';
$base_dn = 'ou=corp,dc=domain,dc=pvt';
$domain = "@domain.pvt";
                
function validAdminUser($uname) {
    // Returns true if user belongs in group support/it/systems
    $validAdminUser = false;
    $arr_usr_groups = get_groups($uname);
    foreach($arr_usr_groups as $group) {
        if(strpos(strtolower($group), "cn=support,") !== false ||
           strpos(strtolower($group), "cn=it,") !== false ||
           strpos(strtolower($group), "cn=systems,") !== false) {
            $validAdminUser = true;
            break;
        }
    }
    return $validAdminUser;
}

function ldap_login($username, $password) {
    global $host, $port, $protocol, $base_dn, $domain;
    if ($username && $password) {
        $connection_string = "$protocol://$host:$port";
        $conn = @ldap_connect($connection_string) or $msg = "Could not connect: $connection_string";
        ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
        ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
    
        $ldaprdn = $username.$domain;
        $ldapbind = @ldap_bind($conn, $ldaprdn, $password);
        if ($ldapbind) {
            $search = ldap_search($conn, $base_dn, "(samaccountname=$username)");
            if ($search) {
                $result = ldap_get_entries($conn, $search);
                if ($result['count'] > 0) {
                    $returnval = 1; // "Success"
                }
                else {
                    $returnval = -1; // "User not found"
                }
            }
        }
        else {
            $returnval = 0; // "Incorrect username/password"
        }    
    }
    else {
        $returnval = -1; // "Please enter username/password"
    }
	
    return $returnval;    
}

function get_groups($user) {
    global $host, $port, $protocol, $base_dn, $domain;

    // Use admin user in LDAP to query
    $username = "admin_username";
    $password = "password";
    
	// Active Directory server
	$connection_string = "$protocol://$host:$port";
 
	// Active Directory DN, base path for our querying user
	$ldap_dn = $base_dn;
 
	// Active Directory user for querying
	$query_user = $username."$domain";
	$password = $password;
 
	// Connect to AD
	$ldap = ldap_connect($connection_string) or die("Could not connect to LDAP");
	ldap_bind($ldap,$query_user,$password) or die("Could not bind to LDAP");
 
	// Search AD
	$results = ldap_search($ldap,$ldap_dn,"(samaccountname=$user)",array("memberof","primarygroupid"));
	$entries = ldap_get_entries($ldap, $results);
	
	// No information found, bad user
	if($entries['count'] == 0) return false;
	
	// Get groups and primary group token
	$output = $entries[0]['memberof'];
	$token = $entries[0]['primarygroupid'][0];
	
	// Remove extraneous first entry i.e. the count of the groups the user belongs to
	array_shift($output);
	
	// We need to look up the primary group, get list of all groups
	$results2 = ldap_search($ldap,$ldap_dn,"(objectcategory=group)",array("distinguishedname","primarygrouptoken"));
	$entries2 = ldap_get_entries($ldap, $results2);
	
	// Remove extraneous first entry
	array_shift($entries2);
	
	// Loop through and find group with a matching primary group token
	foreach($entries2 as $e) {
		if($e['primarygrouptoken'][0] == $token) {
			// Primary group found, add it to output array
			$output[] = $e['distinguishedname'][0];
			// Break loop
			break;
		}
	}
 
	return $output;
}

If you’re running a script on the server to test this, may not be recommended to store your username and password in the script to test this. You could alternatively run the test by doing an anonymous bind.

        $ldapbind = @ldap_bind($conn);
        if ($ldapbind) {
           echo "LDAP bind anonymous successful...";
        } else {
            echo "LDAP bind anonymous failed...";
        }

If for any reason you are unable to bind, here are a few things you could do to debug further.
1. Set debug level to 7 with the ldap_set_option. Please note that the connection parameter is not required in this function.

ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);

2. Make sure the following libraries are installed on the system: php-ldap, libldap-common, libaprutil1-ldap, libldap-2.4-2

3. If you want to do SSL or TLS, you should know that the default behavior is for ldap clients to verify certificates, and give misleading bind errors if they can’t validate them. This means:

  • if you’re using self-signed certificates, add TLS_REQCERT allow to /etc/ldap/ldap.conf on your clients, which means allow certificates the clients can’t validate. TLS_REQCERT can take the following values:

    never – The client will not request or check any server certificate.
    allow – The server certificate is requested. If no certificate is provided, the session proceeds normally. If a bad certificate is provided, it will be ignored and the session proceeds normally.
    try – The server certificate is requested. If no certificate is provided, the session proceeds normally. If a bad certificate is provided, the session is immediately terminated.
    demand | hard – This is the default. The server certificate is requested and a valid certificate must be provided, otherwise the session is immediately terminated.

  • if you’re using CA-signed certificates, and want to verify them, add your CA PEM certificate to a directory of your choice (e.g. /etc/ldap/certs, or /etc/pki/tls/certs, for instance), and point to it using TLA_CACERT in /etc/ldap/ldap.conf, and tla_cacertfile in /etc/ldap.conf.
Advertisements

Convert Array to CSV string for SQL operations

The PHP function implode() can be used to convert an Array to a comma separated string. But if you’d like the string to be a part of an SQL WHERE IN statement, they would need to be enclosed within single quotes. Here’s what you can use to accomplish a comma separated string enclosed within single quotes.

$statusToProcess = array('Pending','Approved');
print implode(",", $statusToProcess); \\ Outputs: Pending,Approved
print "\n";
print "'".implode("','", $statusToProcess)."'"; \\ Outputs: 'Pending','Approved'
print "\n";

Installing / Re-Installing Apache, PHP, libapache2-mod-php

The following is done on an Ubuntu machine but hoping it should cover other Debian instances.

I accidentally deleted php7.0.conf and php7.0.load from the mods-available folder. These files come with libapache2-mod-php. I uninstalled the module and installed it again – but the files didn’t appear. A lot of blogs suggested to remove Apache, PHP and libapache2-mod-php altogether and installing it again and it didn’t work. I used remove, purge and everything suggested but didn’t help. Eventually, I had to search for the installed module by:

dpkg --get-selections | grep libapache2-mod-php

I installed the module above as is but the result showed me libapache2-mod-php7.0. So I went ahead and removed and purged the module listed after which I installed the module again (without the 7.0 suffix) and I was able to recover the files again.

apt-get remove --purge libapache2-mod-php7.0
apt-get install libapache2-mod-php

Since I am covering this topic, let’s talk about installing Apache on Debian and the supported PHP packages along with SSL support.

apt-get install apache2
apt-get install php
apt-get install php-curl
apt-get install libapache2-mod-php

Enable SSL on the site:

cd /etc/apache2/sites-enabled
ln -fs ../sites-available/default-ssl.conf 

Finally, add the necessary modules to support PHP/SSL on Apache

cd /etc/apache2/mods-enabled
ln -fs ../mods-available/ssl.conf
ln -fs ../mods-available/ssl.load 
ln -fs ../mods-available/socache_shmcb.load
ln -fs ../mods-available/php7.0.conf
ln -fs ../mods-available/php7.0.load

Do not forget to restart apache with

service apache2 restart

PHP – return multiple values in Array

PHP doesn’t allow return of multiple values – no way. I typically would create an array and set key value pairs and return the array. Until I found this much easier way of handling the situation:

function getXYZ()
{
    return array(4,5,6);
}

list($x,$y,$z) = getXYZ();

// Afterwards: $x == 4 && $y == 5 && $z == 6
// (This will hold for all samples unless otherwise noted)

Technically, you’re returning an array and using list to store the elements of that array in different values instead of storing the actual array. Using this technique will make it feel most like returning multiple values.

LDAP authentication using PHP

A simple script to test out LDAP authentication via PHP.

<?php

$host = 'ldap-host.domain.pvt';
$port = '636';
$protocol = 'ldaps';
$base_dn = 'ou=corp,dc=domain,dc=pvt';
$domain   = "@domain.pvt";

$username = 'username';
$password = 'Password';
$connection_string = "$protocol://$host:$port";
print "$connection_string\n";
$conn = ldap_connect($connection_string) or die("Could not connect: $connection_string");
print $conn."\n";
ldap_set_option($conn, LDAP_OPT_PROTOCOL_VERSION, 3);
ldap_set_option($conn, LDAP_OPT_REFERRALS, 0);
$ldaprdn = $username.$domain;
$ldapbind = ldap_bind($conn, $ldaprdn, $password);
print $ldapbind."\n";
$search = ldap_search($conn, $base_dn, "(samaccountname=$username)");
var_dump($search);
$result = ldap_get_entries($conn, $search);
print $result['count']."\n";
print "End\n\n";

?>

PHP Warning – date(): It is not safe to rely on the system’s timezone settings

Ever encountered this warning while executing PHP code?

PHP Warning:  date(): It is not safe to rely on the system's timezone settings. You are *required* to use the date.timezone setting 
or the date_default_timezone_set() function. In case you used any of those methods and you are still getting this warning, you most likely 
misspelled the timezone identifier. We selected the timezone 'UTC' for now, but please set date.timezone to select your timezone. 
in/path/file.php on line 10

Solution – explicitly add a timezone to the php.ini file

date.timezone = "America/Los_Angeles"

A list of timezones is available here – http://php.net/manual/en/timezones.php