Skip to content
Snippets Groups Projects
Commit c1d474fe authored by Hahn Axel (hahn)'s avatar Hahn Axel (hahn)
Browse files

Initial commit

parents
Branches
No related tags found
No related merge requests found
# ldap.class.php
A PHP class that I use
* for authentication of user logins
* CRUD actions on ldap nodes
see [docs](docs/)
\ No newline at end of file
# ldap.class.php
A PHP class that I use
* for authentication of user logins
* CRUD actions on ldap nodes
## Requirements
* PHP 7+
* Php Ldap module
* OpenLdap
<?php
/**
* IML LDAP CLASS
*
* - ldap auth
* - CRUD actions on ldap leafs
*
* @author axel.hahn@iml.unibe.ch
*/
class imlldap {
private $_aLdap = array(
'server' => false,
'port' => false,
'DnLdapUser' => false, // ldap rdn oder dn
'PwLdapUser' => false,
'DnUserNode' => false, // ou=People...
'DnAppNode' => false, // cn=AppGroup...
'protoVersion' => 3,
'debugLevel' => 0,
);
private $_ldapConn = false;
private $_ldapBind = false;
var $bDebug = false;
/**
* constructor
* @param array $aConfig optional set ldap connection
*/
public function __construct($aConfig = array()) {
if (!function_exists("ldap_connect")) {
die(__CLASS__ . " ERROR: php-ldap module is not installed on this server.");
}
if (count($aConfig)) {
$this->setConfig($aConfig);
}
}
public function __destruct() {
$this->close();
}
// ----------------------------------------------------------------------
// write debug text
// ----------------------------------------------------------------------
/**
* turn debug messages on;
* if this detail level is not enough, set a value with key debugLevel in
* ldap config array
* @see setConfig()
*/
public function debugOn() {
$this->bDebug = true;
if ($this->_aLdap['debugLevel']) {
$this->_w(__FUNCTION__ . ' setting debug level ' . $this->_aLdap['debugLevel']);
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, $this->_aLdap['debugLevel']);
}
}
/**
* turn debug messages off
*/
public function debugOff() {
$this->bDebug = false;
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 0);
}
/**
* write debug message if denugOn() was fired.
*
* @param string $sText message text
* @return boolean
*/
private function _w($sText) {
if (!$this->bDebug) {
return false;
}
echo __CLASS__ . ' DEBUG: ' . $sText . "<br>\n";
return true;
}
/**
* write last ldap error as debug
*
* @param string $sText message text
* @return boolean
*/
private function _wLdaperror($sText = '') {
$this->_w(($sText ? $sText . ' - ' : '' ) . 'last LDAP-ERROR: ' . ldap_error($this->_ldapConn));
return true;
}
// ----------------------------------------------------------------------
// setup
// ----------------------------------------------------------------------
/**
* set a ldap config
*
* @param array $aConfig new config items
* 'server' => 'ldaps://ldap.example.com',
* 'port' => 636,
* 'DnLdapUser' => 'cn=Lookup,ou=ServiceAccounts,dc=org,dc=example.com', // ldap rdn oder dn
* 'PwLdapUser' => 'IkHEFFzlZ...99j0h8WdI0LrLhxU', // password
* 'DnUserNode' => 'ou=People,ou=ORG,dc=org,dc=example.com',
* 'DnAppNode' => '' optional dn ... if a user must be member of a given group
* 'protoVersion' => 3
* 'debugLevel' => 0 // for debugging set higher 0 AND call debugOn()
*/
public function setConfig($aConfig = array()) {
if (is_array($aConfig)) {
foreach (array_keys($this->_aLdap) as $sKey) {
if (array_key_exists($sKey, $aConfig)) {
$this->_w(__FUNCTION__ . ' setting ldap ' . $sKey . ' = ' . $aConfig[$sKey]);
$this->_aLdap[$sKey] = $aConfig[$sKey];
}
}
}
}
// ----------------------------------------------------------------------
// ldap lowlevel functions
// ----------------------------------------------------------------------
/**
* close an existing ldap connection
*/
public function close() {
if ($this->_ldapConn) {
$this->_w(__FUNCTION__ . ' closing connection.');
ldap_close($this->_ldapConn);
} else {
$this->_w(__FUNCTION__ . ' SKIP close.');
}
$this->_ldapConn = false;
}
/**
* connect to ldap
*/
public function connect() {
if (!array_key_exists('server', $this->_aLdap) || !$this->_aLdap['server']) {
die(__CLASS__ . " ERROR: no ldap server was setup set. Use setConfig() first.");
}
if ($this->_ldapConn) {
$this->close();
}
$this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server'] . ':' . $this->_aLdap['port']);
$this->_ldapConn = ldap_connect($this->_aLdap['server'], $this->_aLdap['port']);
if (!$this->_ldapConn) {
$this->_wLdaperror(__FUNCTION__);
die(__CLASS__ . " ERROR: ldap connect failed.");
}
$this->_w(__FUNCTION__ . ' OK, connected.');
ldap_set_option($this->_ldapConn, LDAP_OPT_NETWORK_TIMEOUT, 3);
ldap_set_option($this->_ldapConn, LDAP_OPT_TIMELIMIT, 3);
if ($this->_aLdap['protoVersion']) {
$this->_w(__FUNCTION__ . ' setting protocol version .' . $this->_aLdap['protoVersion']);
ldap_set_option($this->_ldapConn, LDAP_OPT_PROTOCOL_VERSION, $this->_aLdap['protoVersion']);
}
ldap_set_option($this->_ldapConn, LDAP_OPT_REFERRALS, 0); // for AD MS Windows
}
/**
* ldap bind connects with a ldap user.
* If the ldap connection was not opened yet the connection will be established.
* If a binding exists it will be unbind
*
* @see connect()
* @see unbind()
*
* @param string $sUser optional: username (overrides _aLdap['DnLdapUser'])
* @param string $sPw optional: password (overrides _aLdap['PwLdapUser'])
*/
public function bind($sUser = '', $sPw = '') {
if(!$sUser){
$sUser = $this->_aLdap['DnLdapUser'];
$sPw = $this->_aLdap['PwLdapUser'];
}
if (!$this->_ldapConn) {
$this->connect();
}
if ($this->_ldapBind) {
$this->unbind();
}
if (!$sUser) {
$this->_w(__FUNCTION__ . ' ERROR: no user was set as first param.');
die("ERROR: no user was given to connect to ldap.");
}
$this->_w(__FUNCTION__ . ' with user ' . $sUser . ' PW ' . $sPw);
$this->_ldapBind = @ldap_bind($this->_ldapConn, $sUser, $sPw);
if (!$this->_ldapBind) {
$this->_wLdaperror(__FUNCTION__);
return false;
}
$this->_w(__FUNCTION__ . ' OK, successful.');
return true;
}
/**
* ldap unbind ... if a bind exists
*/
public function unbind() {
if ($this->_ldapBind && !is_bool($this->_ldapBind)) {
$this->_w(__FUNCTION__ . ' ...');
ldap_unbind($this->_ldapBind);
} else {
$this->_w(__FUNCTION__ . ' SKIP.');
}
$this->_ldapBind = false;
}
// ----------------------------------------------------------------------
// ldap highlevel functions
// ----------------------------------------------------------------------
/**
* check if a DN already exists; return is true/ false
* @param string $sDn DN to check
* @return boolean
*/
public function DnExists($sDn) {
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array("*"));
return is_array($aData);
}
/**
* get simpler array from ldap_get_entries after ldap_search
*
* @param array $aRecord singel result item
* @return array
*/
public function normalizeSearchentry($aRecord) {
if (!is_array($aRecord) || !isset($aRecord['dn'])){
return false;
}
$aItem = array();
unset($aRecord['count']);
foreach ($aRecord as $sAttr => $aData) {
if (!is_integer($sAttr)) {
$value = $aData;
if (is_array($aData)) {
unset($aData['count']);
$bUseArray=count($aData)>1 || array_search($sAttr, array('hieradata', 'member', 'memberof', 'objectclass'))!==false;
if($bUseArray){
sort($aData);
}
$value = $bUseArray ? $aData : $aData[0];
}
$aItem[$sAttr] = $value;
}
}
return $aItem;
}
/**
* get simpler array from ldap_get_entries after ldap_search
*
* @param array $aRecord singel result item
* @return array
*/
public function normalizeSearchresult($aLdapSearchresult) {
if (!is_array($aLdapSearchresult)){
return false;
}
$aReturn = array();
unset($aRecord['count']);
foreach ($aLdapSearchresult as $aRecord) {
$aReturn[]=$this->normalizeSearchentry($aRecord);
}
return $aReturn;
}
/**
* search in ldap directory and get result as array
*
* @param string $sDn DN to search for
* @param string $sSearchFilter filter in ldap filter syntax
* @param array $aAttributesToGet flat array of attributes to fetch
* @param boolean $bRecursive recusrive (uses ldap_search) or not (ldap_list)
* @return array
*/
public function searchDn($sDn, $sSearchFilter, $aAttributesToGet = array("*"), $bRecursive=true) {
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
$this->_w(__FUNCTION__ . ' DN = ' . $sDn . ' filter = ' . $sSearchFilter . ' attributes = ' . print_r($aAttributesToGet, 1));
$oLdapSearch = $bRecursive
? ldap_search($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet)
: ldap_list ($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet)
;
if (!$oLdapSearch) {
$this->_w(__FUNCTION__ . " !!!ERROR!!! filter $sSearchFilter failed ");
return false;
}
$aItems = ldap_get_entries($this->_ldapConn, $oLdapSearch);
return $aItems;
}
/**
* search for entries in in ldap user node and get result as array
*
* @param string $sSearchFilter filter in ldap filter syntax
* @param array $aAttributesToGet flat array of attributes to fetch
* @param bool $bRecursive flag: recursive search? default: true (=yes, recursive)
*
* @return array
*/
public function searchUser($sSearchFilter, $aAttributesToGet = array("*"), $bRecursive=true) {
return $this->searchDn($this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet, $bRecursive);
/*
if (!$this->_ldapBind) {
$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser']);
}
$this->_w(__FUNCTION__ . ' DN = ' . $this->_aLdap['DnUserNode'] . ' filter = ' . $sSearchFilter);
$oLdapSearch = ldap_search(
$this->_ldapConn, $this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet
);
$aItems = $oLdapSearch ? ldap_get_entries($this->_ldapConn, $oLdapSearch) : false;
return $aItems;
*/
}
/**
* search user by a given username or email address.
* It returns false if the user does not exist or is
* not member of the group 'DnAppNode' (if it was set).
*
* @param type $sUser user id (uid) or email (mail) to search
* @param type $aAttributesToGet i.e. array("ou", "sn", "vorname", "mail", "uid", "memberOf")
* @return boolean
*/
public function getUserInfo($sUser, $aAttributesToGet = array("*")) {
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
// generate search filter
$sSearchFilter = (strpos($sUser, '@')) ? "(mail=$sUser)" : "(uid=$sUser)";
if ($this->_aLdap['DnAppNode']) {
$sSearchFilter .= '(memberof=' . $this->_aLdap['DnAppNode'] . ')';
}
// $sSearchFilter .= '(memberof=*)';
$sSearchFilter = '(&' . $sSearchFilter . ')';
$aItems = $this->searchUser($sSearchFilter, $aAttributesToGet);
if (count($aItems) == 2) {
$this->_w(__FUNCTION__ . ' OK: I got a single result: ' . print_r($aItems[0], 1));
return $aItems[0];
} else {
$this->_w(__FUNCTION__ . ' ERROR: result is: <pre>' . print_r($aItems, 1) . '</pre>');
}
return false;
}
/**
* search for a DN entry with the lookup user by a given username or
* email address. It returns false if the user does not exist or is
* not member of the group 'DnAppNode' (if it was set).
*
* @param type $sUser
* @return string
*/
public function getUserDn($sUser) {
$this->_w(__FUNCTION__ . '(' . $sUser . ')');
$aItem = $this->getUserInfo($sUser, array("dn"));
if (is_array($aItem) && array_key_exists('dn', $aItem)) {
$this->_w(__FUNCTION__ . ' OK: dn was found ' . $aItem['dn']);
return $aItem['dn'];
}
$this->_w(__FUNCTION__ . ' ERROR: dn was NOT found ' . print_r($aItem));
return false;
}
/**
* set a password for a given user;
* this requires a ldap bind with master/ admin account
*
* @param string $sUser username or email
* @param string $sPW password
* @return boolean
*/
public function setPassword($sUser, $sPW) {
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
$sDn = $this->getUserDn($sUser);
if ($sDn) {
if (!ldap_mod_replace($this->_ldapConn, $sDn, array('userpassword' => "{MD5}" . base64_encode(pack("H*", md5($sPW)))))) {
$this->_wLdaperror(__FUNCTION__);
return false;
} else {
return true;
}
}
$this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser);
return false;
}
/**
* get NTLM hash from a string
* taken from https://secure.php.net/manual/en/ref.hash.php
*
* @param string $Input
* @return string
*/
private function _getNTLMHash($Input) {
// Convert the password from UTF8 to UTF16 (little endian)
$Input = iconv('UTF-8', 'UTF-16LE', $Input);
// Encrypt it with the MD4 hash
$MD4Hash = bin2hex(mhash(MHASH_MD4, $Input));
// You could use this instead, but mhash works on PHP 4 and 5 or above
// The hash function only works on 5 or above
//$MD4Hash=hash('md4',$Input);
// Make it uppercase, not necessary, but it's common to do so with NTLM hashes
$NTLMHash = strtoupper($MD4Hash);
// Return the result
return($NTLMHash);
}
/**
* set a password for a given user for Samba
* this requires a ldap bind with master/ admin account
* see https://msdn.microsoft.com/en-us/library/cc223248.aspx
* see http://php.net/ldap-modify-batch - last examle
* see https://secure.php.net/manual/en/ref.hash.php
*
* @param string $sUser username or email
* @param string $sPW password
* @return boolean
*/
public function setPasswordSamba($sUser, $sPW) {
$sDn = $this->getUserDn($sUser);
if ($sDn) {
$sPwField = 'sambaNTPassword';
$sPwValue = $this->_getNTLMHash($sPW);
return $this->objUpdate($sDn, array(
$sPwField => $sPwValue,
'SambaPwdLastSet' => date('U'),
));
}
$this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser);
return false;
}
/**
* update an ldap object
* this requires a ldap bind with master/ admin account
*
* @param string $sDn dn to update
* @param string $aItem array of new ldap properties
* @return boolean
*/
public function objAdd($sDn, $aItem) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", <pre>['.print_r($aItem, 1).']</pre>)');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
if (!ldap_add($this->_ldapConn, $sDn, $aItem)) {
$this->_wLdaperror(__FUNCTION__);
return false;
}
return true;
}
/**
* update an ldap attribute
* this requires a ldap bind with master/ admin account
*
* @param string $sDn dn to update
* @param string $aItem array of new ldap properties
* @return boolean
*/
public function objAddAttr($sDn, $aItem) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", [array])');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
if ($sDn && is_array($aItem)) {
$this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect');
$this->_w(__FUNCTION__ . ' ldap_mod_add($this->_ldapConn, "' . $sDn . '", ' . print_r($aItem, 1) . ')');
if (!ldap_mod_add($this->_ldapConn, $sDn, $aItem)) {
$this->_w(__FUNCTION__ . ' ldap_mod_add FAILED');
$this->_wLdaperror(__FUNCTION__);
return false;
}
return true;
}
$this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not ann array ' . print_r($aItem, 1));
return false;
}
/**
* update an ldap object with given key-value array
* if the attribute (key) does not exist it will be created.
* this requires a ldap bind with master/ admin account
*
* @param string $sDn full DN where to update the item
* @param array $aItem updated entry
* @return boolean
*/
public function objUpdate($sDn, $aItem) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", ' . print_r($aItem, 1) . ')');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
if ($sDn && is_array($aItem)) {
if (!ldap_mod_replace($this->_ldapConn, $sDn, $aItem)) {
$this->_w(__FUNCTION__ . ' ldap_mod_replace FAILED');
$this->_wLdaperror(__FUNCTION__);
return false;
}
return true;
}
$this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) ' . print_r($aItem, 1));
return false;
}
/**
* delete an ldap object
* this requires a ldap bind with master/ admin account
*
* @param string $sDn full DN to remove
* @return boolean
*/
public function objDelete($sDn) {
$this->_w(__FUNCTION__ . '("' . $sDn . '")');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
if ($sDn) {
if (!ldap_delete($this->_ldapConn, $sDn)) {
$this->_wLdaperror(__FUNCTION__);
return false;
} return true;
}
$this->_w(__FUNCTION__ . ' missing parameter for DN');
return false;
}
/**
* delete attributes of an ldap object
* this requires a ldap bind with master/ admin account
*
* TODO: Test me
*
* @param string $sDn DN
* @param string $aItem item to remove
* @return boolean
*/
public function objDeleteAttr($sDn, $aItem) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", [array])');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
if ($sDn && is_array($aItem)) {
$this->_w(__FUNCTION__ . ' ' . $this->_ldapConn ? 'Verbindung da' : 'kein LDAP Connect');
$this->_w(__FUNCTION__ . ' ldap_mod_del($this->_ldapConn, "' . $sDn . '", ' . print_r($aItem, 1) . ')');
if (!ldap_mod_del($this->_ldapConn, $sDn, $aItem)) {
$this->_wLdaperror(__FUNCTION__);
return false;
} return true;
}
$this->_w(__FUNCTION__ . ' dn not found (item does not exist in ldap) or item was not an array ' . print_r($aItem, 1));
return false;
}
/**
* check if an attribute exists in a DN
*
* @param string $sDn DN
* @param string $sAttribute attribute name to check
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeExists($sDn, $sAttribute) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '")');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute));
$return = (is_array($aData) && isset($aData[0][strtolower($sAttribute)]));
$this->_w(__FUNCTION__ . '(...) returns ' . ($return ? 'true' : 'false'));
return $return;
}
/**
* check if an attribute and value exist in a DN
*
* @param string $sDn DN
* @param string $sAttribute attribute name to check
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute));
$return = (is_array($aData) && isset($aData[0][strtolower($sAttribute)]) && array_search($sAttrValue, $aData[0][strtolower($sAttribute)]) !== false);
$this->_w(__FUNCTION__ . '(...) returns ' . ($return ? 'true' : 'false'));
return $return;
}
/**
* check an attribute and value; it will be created if it does not exist
* this requires a ldap bind with master/ admin account
*
* @param string $sDn dn to update
* @param string $sAttribute attribute name to check
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeAndValueMustExist($sDn, $sAttribute, $sAttrValue) {
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")');
// return if it already exists
if ($this->objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue)) {
return true;
}
// create it
$this->_w(__FUNCTION__ . " create $sAttribute = $sAttrValue");
$return = $this->objAddAttr($sDn, array($sAttribute => $sAttrValue));
return $return;
}
/**
* create a new user item
* this requires a ldap bind with master/ admin account
*
* @param array $aItem ldap properties
* @param string $sDn optional DN where to create the user
* @return boolean
*/
public function userAdd($aItem, $sDn = false) {
if (!$sDn) {
$sDn = 'cn=' . $aItem['cn'] . ',' . $this->_aLdap['DnUserNode'];
}
$this->_w(__FUNCTION__ . '([array], "' . $sDn . '")');
if ($sDn) {
return $this->objAdd($sDn, $aItem);
}
$this->_w(__FUNCTION__ . ' node dn where to put the user was not found; set a value DnUserNode in ldap config or set it as 2nd parameter ' . print_r($aItem, 1));
return false;
}
/**
* delete a user
* this requires a ldap bind with master/ admin account
*
* @param string $sUser user to update
* @param string $sPW new password to set
* @return boolean
*/
public function userDelete($sUserDn) {
$this->_w(__FUNCTION__ . '(' . $sUserDn . ')');
return $this->objDelete($sUserDn);
}
/**
* update an ldap object
* this requires a ldap bind with master/ admin account
*
* @param array $aItem new user data to update
* @return boolean
*/
public function userUpdate($aItem) {
$this->_w(__FUNCTION__ . '([array])');
$sDn = $this->getUserDn($aItem['uid']);
if ($sDn) {
if (array_key_exists('cn', $aItem)) {
$this->_w(__FUNCTION__ . ' deleting cn entry.');
unset($aItem['cn']);
}
return $this->objUpdate($sDn, $aItem);
}
$this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sDn);
return false;
}
/**
* verify user and password
* @param string $sUser username or email
* @param string $sPW password
* @return boolean
*/
public function verifyPassword($sUser, $sPW) {
$sDn = $this->getUserDn($sUser);
if ($sDn) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){
return false;
}
}
$this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser);
return false;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment