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

update vendor class for ldap

parent cf32d6ec
No related branches found
No related tags found
1 merge request!66php8 only; added variable types; short array syntax; remove glyphicons
This commit is part of merge request !66. Comments created here will be created in the context of that merge request.
......@@ -3,15 +3,22 @@
/**
*
* IML LDAP CONNECTOR
*<pre>
* 2022-02-22 ah added objGet(), sanitizeFilter() <br>
* 2022-08-18 ah mask password (showing 4 chars only) <br>
* 2022-08-22 ah mhash is deprecated <br>
* 2022-08-26 ah fix verifyPassword <br>
* </pre>
* @author axel.hahn@iml.unibe.ch
*
* @author axel.hahn@unibe.ch
* @license GNU GPL v3
*
* SOURCE: <https://git-repo.iml.unibe.ch/iml-open-source/ldap-php-class/>
* DOCS: <https://os-docs.iml.unibe.ch/ldap-php-class/index.html>
*
* 2022-02-22 ah added objGet(), sanitizeFilter()
* 2022-08-18 ah mask password (showing 4 chars only)
* 2022-08-22 ah mhash is deprecated
* 2022-08-26 ah fix verifyPassword
* 2024-07-11 ah php8 only: use variable types; update phpdocs
* 2024-07-12 ah remove connection port (use server value "ldaps://<host>:<port>" if needed)
*/
class imlldap {
class imlldap
{
// ----------------------------------------------------------------------
// vars
......@@ -20,26 +27,31 @@ class imlldap {
/**
* @var array options array for an ldap connection including some base settings and DNs
*/
private $_aLdap = array(
private array $_aLdap = [
'server' => false,
'port' => false,
'DnLdapUser' => false, // ldap rdn oder dn
'PwLdapUser' => false,
'DnUserNode' => false, // ou=People...
'DnAppNode' => false, // cn=AppGroup...
'protoVersion' => 3,
'debugLevel' => 0,
);
];
/**
* @var object current ldap connection
*/
private $_ldapConn = false;
private object|bool $_ldapConn = false;
/**
* @var bool bind was done?
* ldap bind object - bind was done?
* @var object|bool
*/
private $_ldapBind = false;
var $bDebug = false;
private object|bool $_ldapBind = false;
/**
* Flag if debug mode is on
* @var bool
*/
var bool $bDebug = false;
// ----------------------------------------------------------------------
// functions
......@@ -49,7 +61,8 @@ class imlldap {
* constructor
* @param array $aConfig optional set ldap connection
*/
public function __construct($aConfig = array()) {
public function __construct(array $aConfig = [])
{
if (!function_exists("ldap_connect")) {
die(__CLASS__ . " ERROR: php-ldap module is not installed on this server.");
}
......@@ -58,7 +71,8 @@ class imlldap {
}
}
public function __destruct() {
public function __destruct()
{
$this->close();
}
......@@ -72,7 +86,8 @@ class imlldap {
* ldap config array
* @see setConfig()
*/
public function debugOn() {
public function debugOn(): void
{
$this->bDebug = true;
if ($this->_aLdap['debugLevel']) {
$this->_w(__FUNCTION__ . ' setting debug level ' . $this->_aLdap['debugLevel']);
......@@ -83,7 +98,8 @@ class imlldap {
/**
* turn debug messages off
*/
public function debugOff() {
public function debugOff(): void
{
$this->bDebug = false;
ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 0);
}
......@@ -94,7 +110,8 @@ class imlldap {
* @param string $sText message text
* @return boolean
*/
private function _w($sText) {
private function _w(string $sText): bool
{
if (!$this->bDebug) {
return false;
}
......@@ -108,7 +125,8 @@ class imlldap {
* @param string $sText message text
* @return boolean
*/
private function _wLdaperror($sText = '') {
private function _wLdaperror(string $sText = ''): bool
{
$this->_w(($sText ? $sText . ' - ' : '') . 'last LDAP-ERROR: ' . ldap_error($this->_ldapConn));
return true;
}
......@@ -118,19 +136,18 @@ class imlldap {
// ----------------------------------------------------------------------
/**
* set a ldap config
* set a ldap config or modify existing value
*
* @param array $aConfig new config items
* @param array $aConfig new config items with these keys
* '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
* 'PwLdapUser' => 'PasswordOfLookupUser', // 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()
* 'debugLevel' => 0 // value for LDAP_OPT_DEBUG_LEVEL in debugOn()
*/
public function setConfig($aConfig = array()) {
public function setConfig(array $aConfig = []): void
{
if (is_array($aConfig)) {
foreach (array_keys($this->_aLdap) as $sKey) {
if (array_key_exists($sKey, $aConfig)) {
......@@ -148,7 +165,8 @@ class imlldap {
/**
* close an existing ldap connection
*/
public function close() {
public function close(): void
{
if ($this->_ldapConn) {
$this->_w(__FUNCTION__ . ' closing connection.');
ldap_close($this->_ldapConn);
......@@ -162,7 +180,8 @@ class imlldap {
/**
* connect to ldap
*/
public function connect() {
public function connect(): void
{
if (!array_key_exists('server', $this->_aLdap) || !$this->_aLdap['server']) {
die(__CLASS__ . " ERROR: no ldap server was setup set. Use setConfig() first.");
......@@ -172,8 +191,8 @@ class imlldap {
$this->close();
}
$this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server'] . ':' . $this->_aLdap['port']);
$this->_ldapConn = ldap_connect($this->_aLdap['server'], $this->_aLdap['port']);
$this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server']);
$this->_ldapConn = ldap_connect($this->_aLdap['server']);
if (!$this->_ldapConn) {
$this->_wLdaperror(__FUNCTION__);
die(__CLASS__ . " ERROR: ldap connect failed.");
......@@ -201,7 +220,8 @@ class imlldap {
* @param string $sUser optional: username (overrides _aLdap['DnLdapUser'])
* @param string $sPw optional: password (overrides _aLdap['PwLdapUser'])
*/
public function bind($sUser = '', $sPw = '') {
public function bind(string $sUser = '', string $sPw = ''): bool
{
if (!$sUser) {
$sUser = $this->_aLdap['DnLdapUser'];
$sPw = $this->_aLdap['PwLdapUser'];
......@@ -232,7 +252,8 @@ class imlldap {
/**
* ldap unbind ... if a bind exists
*/
public function unbind() {
public function unbind(): void
{
if ($this->_ldapBind && !is_bool($this->_ldapBind)) {
$this->_w(__FUNCTION__ . ' ...');
ldap_unbind($this->_ldapBind);
......@@ -251,29 +272,32 @@ class imlldap {
* @param string $sDn DN to check
* @return boolean
*/
public function DnExists($sDn) {
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array("*"));
public function DnExists(string $sDn): bool
{
$aData = $this->searchDn($sDn, '(&(objectclass=top))', ["*"]);
return is_array($aData);
}
/**
* get simpler array from ldap_get_entries after ldap_search
* If the given array doesn't contain the key "dn" it returns "false"
*
* @param array $aRecord singel result item
* @param array $aRecord single result item
* @return array
*/
public function normalizeSearchentry($aRecord) {
public function normalizeSearchentry(array $aRecord): bool|array
{
if (!is_array($aRecord) || !isset($aRecord['dn'])) {
return false;
}
$aItem = array();
$aItem = [];
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;
$bUseArray = count($aData) > 1 || array_search($sAttr, ['hieradata', 'member', 'memberof', 'objectclass']) !== false;
if ($bUseArray) {
sort($aData);
}
......@@ -284,23 +308,6 @@ class imlldap {
}
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;
}
/**
* sanitize value to put into a search filter
......@@ -315,10 +322,11 @@ class imlldap {
* @param string $s value to sanitize
* @return string
*/
static public function sanitizeFilter($s){
static public function sanitizeFilter(string $s): string
{
// helper array to replace special chars
$aReplace=array();
$aReplace = [];
for ($i = 0; $i < 65; $i++) {
$val = dechex($i);
if ($val < 10) {
......@@ -333,15 +341,19 @@ class imlldap {
return $sReturn;
}
/**
* search in ldap directory and get result as array
* search in ldap directory and get result as array.
* It returns "false" on error:
* - no ldap connection
* - search failed
*
* @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
* @return boolean|array
*/
public function searchDn($sDn, $sSearchFilter='(objectclass=*)', $aAttributesToGet = array("*"), $bRecursive=true) {
public function searchDn(string $sDn, string $sSearchFilter = '(objectclass=*)', array $aAttributesToGet = ["*"], bool $bRecursive = true): bool|array
{
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
return false;
......@@ -371,9 +383,10 @@ class imlldap {
* @param array $aAttributesToGet flat array of attributes to fetch
* @param bool $bRecursive flag: recursive search? default: true (=yes, recursive)
*
* @return array
* @return boolean|array
*/
public function searchUser($sSearchFilter='', $aAttributesToGet = array("*"), $bRecursive=true) {
public function searchUser(string $sSearchFilter = '', array $aAttributesToGet = ["*"], bool $bRecursive = true): bool|array
{
return $this->searchDn($this->_aLdap['DnUserNode'], $sSearchFilter, $aAttributesToGet, $bRecursive);
/*
if (!$this->_ldapBind) {
......@@ -397,10 +410,11 @@ class imlldap {
* not member of the group 'DnAppNode' (if it was set).
*
* @param string $sUser user id (uid) or email (mail) to search
* @param array $aAttributesToGet i.e. array("ou", "sn", "vorname", "mail", "uid", "memberOf")
* @param array $aAttributesToGet i.e. ["ou", "sn", "vorname", "mail", "uid", "memberOf"]
* @return boolean|array
*/
public function getUserInfo($sUser, $aAttributesToGet = array("*")) {
public function getUserInfo(string $sUser, array $aAttributesToGet = ["*"]): bool|array
{
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
return false;
......@@ -430,12 +444,13 @@ class imlldap {
* 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
* @param string $sUser
* @return string
*/
public function getUserDn($sUser) {
public function getUserDn(string $sUser): bool|string
{
$this->_w(__FUNCTION__ . '(' . $sUser . ')');
$aItem = $this->getUserInfo($sUser, array("dn"));
$aItem = $this->getUserInfo($sUser, ["dn"]);
if (is_array($aItem) && array_key_exists('dn', $aItem)) {
$this->_w(__FUNCTION__ . ' OK: dn was found ' . $aItem['dn']);
return $aItem['dn'];
......@@ -452,7 +467,8 @@ class imlldap {
* @param string $sPW password
* @return boolean
*/
public function setPassword($sUser, $sPW) {
public function setPassword(string $sUser, string $sPW): bool
{
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
return false;
......@@ -460,7 +476,7 @@ class imlldap {
}
$sDn = $this->getUserDn($sUser);
if ($sDn) {
if (!ldap_mod_replace($this->_ldapConn, $sDn, array('userpassword' => "{MD5}" . base64_encode(pack("H*", md5($sPW)))))) {
if (!ldap_mod_replace($this->_ldapConn, $sDn, ['userpassword' => "{MD5}" . base64_encode(pack("H*", md5($sPW)))])) {
$this->_wLdaperror(__FUNCTION__);
return false;
} else {
......@@ -478,7 +494,8 @@ class imlldap {
* @param string $Input
* @return string
*/
private function _getNTLMHash($Input) {
private function _getNTLMHash(string $Input): string
{
// Convert the password from UTF8 to UTF16 (little endian)
$Input = iconv('UTF-8', 'UTF-16LE', $Input);
......@@ -502,15 +519,19 @@ class imlldap {
* @param string $sPW password
* @return boolean
*/
public function setPasswordSamba($sUser, $sPW) {
public function setPasswordSamba(string $sUser, string $sPW): bool
{
$sDn = $this->getUserDn($sUser);
if ($sDn) {
$sPwField = 'sambaNTPassword';
$sPwValue = $this->_getNTLMHash($sPW);
return $this->objUpdate($sDn, array(
return $this->objUpdate(
$sDn,
[
$sPwField => $sPwValue,
'SambaPwdLastSet' => date('U'),
));
]
);
}
$this->_w(__FUNCTION__ . ' dn not found (user does not exist in ldap) ' . $sUser);
return false;
......@@ -519,12 +540,14 @@ class imlldap {
/**
* update an ldap object
* this requires a ldap bind with master/ admin account
* It returns true if the action was successful
*
* @param string $sDn dn to update
* @param array $aItem array of new ldap properties
* @return boolean
*/
public function objAdd($sDn, $aItem) {
public function objAdd(string $sDn, array $aItem): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", <pre>[' . print_r($aItem, 1) . ']</pre>)');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
......@@ -543,10 +566,11 @@ class imlldap {
* this requires a ldap bind with master/ admin account
*
* @param string $sDn dn to update
* @param string $aItem array of new ldap properties
* @param array $aItem array of new ldap properties
* @return boolean
*/
public function objAddAttr($sDn, $aItem) {
public function objAddAttr(string $sDn, array $aItem): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", [array])');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
......@@ -569,13 +593,17 @@ class imlldap {
/**
* read attributes from ldap node with given DN (using ldap_read)
* It returns "false" if the action was not successful
* - no ldap connection
* - DN or filter didn't match
*
* @param string $sDn DN to search for
* @param string $sSearchFilter filter in ldap filter syntax
* @param array $aAttributesToGet flat array of attributes to fetch
* @return array
* @return boolean|array
*/
public function objGet($sDn, $sSearchFilter='(objectclass=*)', $aAttributesToGet = array("*")) {
public function objGet(string $sDn, string $sSearchFilter = '(objectclass=*)', array $aAttributesToGet = ["*"]): bool|array
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", filter = ' . $sSearchFilter . ', atttr= ' . print_r($aAttributesToGet, 1) . ' )');
if (!$this->_ldapBind) {
......@@ -597,12 +625,14 @@ class imlldap {
* 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
* It returns "false" if the action failed
*
* @param string $sDn full DN where to update the item
* @param array $aItem updated entry
* @return boolean
*/
public function objUpdate($sDn, $aItem) {
public function objUpdate(string $sDn, array $aItem): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", ' . print_r($aItem, 1) . ')');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
......@@ -624,11 +654,13 @@ class imlldap {
/**
* delete an ldap object
* this requires a ldap bind with master/ admin account
* It returns "false" if the action failed
*
* @param string $sDn full DN to remove
* @return boolean
*/
public function objDelete($sDn) {
public function objDelete(string $sDn): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '")');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
......@@ -640,7 +672,8 @@ class imlldap {
if (!ldap_delete($this->_ldapConn, $sDn)) {
$this->_wLdaperror(__FUNCTION__);
return false;
} return true;
}
return true;
}
$this->_w(__FUNCTION__ . ' missing parameter for DN');
return false;
......@@ -649,14 +682,18 @@ class imlldap {
/**
* delete attributes of an ldap object
* this requires a ldap bind with master/ admin account
* It returns "false" if the action failed
*
* TODO: Test me
* @example:
* remove attribute "userPassword" of user $sUserDn:
* <code>$oLdap->objDeleteAttr($sUserDn, ['userPassword'=>[]]</code>
*
* @param string $sDn DN
* @param string $aItem item to remove
* @param array $aItem item to remove
* @return boolean
*/
public function objDeleteAttr($sDn, $aItem) {
public function objDeleteAttr(string $sDn, array $aItem): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", [array])');
if (!$this->_ldapBind) {
if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) {
......@@ -669,7 +706,8 @@ class imlldap {
if (!ldap_mod_del($this->_ldapConn, $sDn, $aItem)) {
$this->_wLdaperror(__FUNCTION__);
return false;
} return true;
}
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;
......@@ -683,7 +721,8 @@ class imlldap {
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeExists($sDn, $sAttribute) {
public function objectAttributeExists(string $sDn, string $sAttribute): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '")');
if (!$this->_ldapBind) {
......@@ -691,7 +730,7 @@ class imlldap {
return false;
}
}
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute));
$aData = $this->searchDn($sDn, '(&(objectclass=top))', [$sAttribute]);
$return = (is_array($aData) && isset($aData[0][strtolower($sAttribute)]));
$this->_w(__FUNCTION__ . '(...) returns ' . ($return ? 'true' : 'false'));
return $return;
......@@ -705,7 +744,8 @@ class imlldap {
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue) {
public function objectAttributeAndValueExist(string $sDn, string $sAttribute, string $sAttrValue): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")');
if (!$this->_ldapBind) {
......@@ -713,7 +753,7 @@ class imlldap {
return false;
}
}
$aData = $this->searchDn($sDn, '(&(objectclass=top))', array($sAttribute));
$aData = $this->searchDn($sDn, '(&(objectclass=top))', [$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;
......@@ -728,7 +768,8 @@ class imlldap {
* @param string $sAttrValue value to check
* @return boolean
*/
public function objectAttributeAndValueMustExist($sDn, $sAttribute, $sAttrValue) {
public function objectAttributeAndValueMustExist(string $sDn, string $sAttribute, string $sAttrValue): bool
{
$this->_w(__FUNCTION__ . '("' . $sDn . '", "' . $sAttribute . '", "' . $sAttrValue . '")');
// return if it already exists
if ($this->objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue)) {
......@@ -737,7 +778,7 @@ class imlldap {
// create it
$this->_w(__FUNCTION__ . " create $sAttribute = $sAttrValue");
$return = $this->objAddAttr($sDn, array($sAttribute => $sAttrValue));
$return = $this->objAddAttr($sDn, [$sAttribute => $sAttrValue]);
return $return;
}
......@@ -749,7 +790,8 @@ class imlldap {
* @param string $sDn optional DN where to create the user
* @return boolean
*/
public function userAdd($aItem, $sDn = false) {
public function userAdd(array $aItem, string $sDn = ""): bool
{
if (!$sDn) {
$sDn = 'cn=' . $aItem['cn'] . ',' . $this->_aLdap['DnUserNode'];
}
......@@ -769,7 +811,8 @@ class imlldap {
* @param string $sPW new password to set
* @return boolean
*/
public function userDelete($sUserDn) {
public function userDelete(string $sUserDn): bool
{
$this->_w(__FUNCTION__ . '(' . $sUserDn . ')');
return $this->objDelete($sUserDn);
}
......@@ -781,7 +824,8 @@ class imlldap {
* @param array $aItem new user data to update
* @return boolean
*/
public function userUpdate($aItem) {
public function userUpdate(array $aItem): bool
{
$this->_w(__FUNCTION__ . '([array])');
$sDn = $this->getUserDn($aItem['uid']);
if ($sDn) {
......@@ -801,7 +845,8 @@ class imlldap {
* @param string $sPW password
* @return boolean
*/
public function verifyPassword($sUser, $sPW) {
public function verifyPassword(string $sUser, string $sPW): bool
{
$sDn = $this->getUserDn($sUser);
if ($sDn) {
return $this->bind($sDn, $sPW);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment