diff --git a/README.md b/README.md index 13b24f78bf84b83994dc016cdd0f83e718be95b7..cb431f67aeb2436bdd046e1cf54d8947681c2f12 100644 --- a/README.md +++ b/README.md @@ -5,8 +5,7 @@ A PHP class that I use * for authentication of user logins * CRUD actions on ldap nodes -Institute for Medical Education; University of Bern - -License: GNU GPL 3 - -see [docs](docs/) \ No newline at end of file +👤 Author: Axel Hahn; Institute for Medical Education; University of Bern +📄 Source: https://git-repo.iml.unibe.ch/iml-open-source/ldap-php-class +📜 License: GNU GPL 3.0 +📗 Docs: https://os-docs.iml.unibe.ch/ldap-php-class/ diff --git a/docs/20_Usage.md b/docs/20_Usage.md index 595e75933ccab564e3067ad5ce21e4d25a0c3161..6f6c14f70aa820fb7bc5a65d0c0576592ae12a80 100644 --- a/docs/20_Usage.md +++ b/docs/20_Usage.md @@ -11,12 +11,11 @@ require_once '[APPROOT]/classes/ldap.class.php'; As an example I create a hash named $aConfig and save it as "inc_config.php". ```php -$aConfig=[ +return [ ... 'ldap' => [ 'server' => 'ldaps://ldap.example.com', - 'port' => 636, 'DnLdapUser' => 'cn=Lookup,ou=Service,dc=some,dc=example.com', 'PwLdapUser' => 'PasswordOfLookupUser', @@ -32,16 +31,69 @@ $aConfig=[ ]; ``` -## Example: verify login data +## initialize connection ```php -require_once('inc_config.php'); +$aConfig = require_once('inc_config.php'); require_once '[APPROOT]/classes/ldap.class.php'; -oLdap=new imlldap($aConfig['ldap']); +$oLdap=new imlldap($aConfig['ldap']); +``` + +## Methods + +### Object handling + +* objAdd(string $sDn, array $aItem): bool +* objGet(string $sDn, string $sSearchFilter = '(objectclass=*)', array $aAttributesToGet = ["*"]): bool|array +* objUpdate(string $sDn, array $aItem): bool +* objDelete(string $sDn): bool + +### Attributes + +* objAddAttr(string $sDn, array $aItem): bool +* objDeleteAttr(string $sDn, array $aItem): bool +* objectAttributeExists(string $sDn, string $sAttribute): bool +* objectAttributeAndValueExist(string $sDn, string $sAttribute, string $sAttrValue): bool - check only +* objectAttributeAndValueMustExist(string $sDn, string $sAttribute, string $sAttrValue): bool - force the existence of attribute and value + +### User functions + +* userAdd(array $aItem, string $sDn = "") +* getUserInfo(string $sUser, array $aAttributesToGet = ["*"]): bool|array +* userDelete(string $sUserDn) +* userUpdate(array $aItem) +* setPassword(string $sUser, string $sPW): bool +* verifyPassword(string $sUser, string $sPW): bool + +### Debugging + +Turn debugging on or off -// set values from $_FORM or $_POST data of your login form here -// The variable $bAuthenticated is true if authentication of the user was successful. -$bAuthenticated=oLdap->verifyPassword($sUser, $sPassword); +* debugOff() +* debugOn() +## Examples + +### read user attributes + +Use the username or an email address to get user data. The 2nd parameter defines the attributes to fetch (`["*"]` is default). + +```php +$aUser = $oLdap->getUserInfo("john@example.com", []); +$aUser = $oLdap->getUserInfo("john@example.com", ["memberof", "uid"]); + +// simplify result array: +print_r($oLdap->normalizeSearchentry($aUser)); +``` + +### Example: search + +When using special chars in search then you can sanitize the search string. + +```php +$sCn = 'John Smith (john)'; +$sSearchFilter = '(cn='.$oLdap->sanitizeFilter($sCn).')'; +$aResults = $oLdap->searchDn("<DN here>", $sSearchFilter, ["*"]); +$oLdap->close(); ``` diff --git a/docs/30_Methods.md b/docs/30_Methods.md index 8fd210c8a505867f4d0ab2dbcca910dbddb347fa..86962bdc4e3e9a69283624b89a521e901e85bea3 100644 --- a/docs/30_Methods.md +++ b/docs/30_Methods.md @@ -5,19 +5,28 @@ --- ## `class imlldap` -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> +IML LDAP CONNECTOR + +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 --- -## `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 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, ]` --- -## `private $_ldapConn = false` +## `private object|bool $_ldapConn = false` --- -## `private $_ldapBind = false` +## `private object|bool $_ldapBind = false` + +ldap bind object - bind was done? --- -## `public function __construct($aConfig = array())` +## `var bool $bDebug = false` + +Flag if debug mode is on + +--- +## `public function __construct(array $aConfig = [])` constructor **Parameters:** @@ -27,17 +36,17 @@ Var | Type | Desciption $aConfig | array | optional set ldap connection --- -## `public function debugOn()` +## `public function debugOn(): void` turn debug messages on; if this detail level is not enough, set a value with key debugLevel in ldap config array --- -## `public function debugOff()` +## `public function debugOff(): void` turn debug messages off --- -## `private function _w($sText)` +## `private function _w(string $sText): bool` write debug message if denugOn() was fired. @@ -52,7 +61,7 @@ $sText | string | message text boolean --- -## `private function _wLdaperror($sText = '')` +## `private function _wLdaperror(string $sText = ''): bool` write last ldap error as debug @@ -67,7 +76,7 @@ $sText | string | message text boolean --- -## `public function setConfig($aConfig = array())` +## `public function setConfig(array $aConfig = []): void` set a ldap config @@ -87,17 +96,17 @@ Var | Type | Desciption $aConfig | array | new config items --- -## `public function close()` +## `public function close(): void` close an existing ldap connection --- -## `public function connect()` +## `public function connect(): void` connect to ldap --- -## `public function bind($sUser = '', $sPw = '')` +## `public function bind(string $sUser = '', string $sPw = ''): bool` 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 @@ -109,12 +118,12 @@ $sUser | string | optional: username (overrides _aLdap['DnLdapUser']) $sPw | string | optional: password (overrides _aLdap['PwLdapUser']) --- -## `public function unbind()` +## `public function unbind(): void` ldap unbind ... if a bind exists --- -## `public function DnExists($sDn)` +## `public function DnExists(string $sDn): bool` check if a DN already exists; return is true/ false **Parameters:** @@ -128,37 +137,22 @@ $sDn | string | DN to check boolean --- -## `public function normalizeSearchentry($aRecord)` +## `public function normalizeSearchentry(array $aRecord): bool|array` -get simpler array from ldap_get_entries after ldap_search +get simpler array from ldap_get_entries after ldap_search If the given array doesn't contain the key "dn" it returns "false" **Parameters:** Var | Type | Desciption -- |-- |-- -$aRecord | array | singel result item +$aRecord | array | single result item **Return:** array --- -## `public function normalizeSearchresult($aLdapSearchresult)` - -get simpler array from ldap_get_entries after ldap_search - -**Parameters:** - -Var | Type | Desciption --- |-- |-- -$aRecord | array | singel result item - -**Return:** - - array - ---- -## `static public function sanitizeFilter($s)` +## `static public function sanitizeFilter(string $s): string` sanitize value to put into a search filter WARNING: the implementation is incomplete! I replaces the first N ascii chars only @@ -176,9 +170,9 @@ $s | string | value to sanitize string --- -## `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` -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 **Parameters:** @@ -191,10 +185,10 @@ $bRecursive | boolean | recusrive (uses ldap_search) or not (ldap_list) **Return:** - array + boolean|array --- -## `public function searchUser($sSearchFilter='', $aAttributesToGet = array("*"), $bRecursive=true)` +## `public function searchUser(string $sSearchFilter = '', array $aAttributesToGet = ["*"], bool $bRecursive = true): bool|array` search for entries in in ldap user node and get result as array @@ -208,10 +202,10 @@ $bRecursive | bool | flag: recursive search? default: true (=yes, recursive) **Return:** - array + boolean|array --- -## `public function getUserInfo($sUser, $aAttributesToGet = array("*"))` +## `public function getUserInfo(string $sUser, array $aAttributesToGet = ["*"]): bool|array` 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). @@ -219,15 +213,15 @@ search user by a given username or email address. It returns false if the user d Var | Type | Desciption -- |-- |-- -$sUser | type | user id (uid) or email (mail) to search -$aAttributesToGet | type | i.e. array("ou", "sn", "vorname", "mail", "uid", "memberOf") +$sUser | string | user id (uid) or email (mail) to search +$aAttributesToGet | array | i.e. ["ou", "sn", "vorname", "mail", "uid", "memberOf"] **Return:** boolean|array --- -## `public function getUserDn($sUser)` +## `public function getUserDn(string $sUser): bool|string` 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). @@ -235,14 +229,14 @@ search for a DN entry with the lookup user by a given username or email address. Var | Type | Desciption -- |-- |-- -$sUser | type | %s +$sUser | string | %s **Return:** string --- -## `public function setPassword($sUser, $sPW)` +## `public function setPassword(string $sUser, string $sPW): bool` set a password for a given user; this requires a ldap bind with master/ admin account @@ -258,7 +252,7 @@ $sPW | string | password boolean --- -## `private function _getNTLMHash($Input)` +## `private function _getNTLMHash(string $Input): string` get NTLM hash from a string taken from https://secure.php.net/manual/en/ref.hash.php @@ -273,7 +267,7 @@ $Input | string | %s string --- -## `public function setPasswordSamba($sUser, $sPW)` +## `public function setPasswordSamba(string $sUser, string $sPW): bool` 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 @@ -289,23 +283,23 @@ $sPW | string | password boolean --- -## `public function objAdd($sDn, $aItem)` +## `public function objAdd(string $sDn, array $aItem): bool` -update an ldap object this requires a ldap bind with master/ admin account +update an ldap object this requires a ldap bind with master/ admin account It returns true if the action was successful **Parameters:** Var | Type | Desciption -- |-- |-- $sDn | string | dn to update -$aItem | string | array of new ldap properties +$aItem | array | array of new ldap properties **Return:** boolean --- -## `public function objAddAttr($sDn, $aItem)` +## `public function objAddAttr(string $sDn, array $aItem): bool` update an ldap attribute this requires a ldap bind with master/ admin account @@ -314,16 +308,16 @@ update an ldap attribute this requires a ldap bind with master/ admin account Var | Type | Desciption -- |-- |-- $sDn | string | dn to update -$aItem | string | array of new ldap properties +$aItem | array | array of new ldap properties **Return:** boolean --- -## `public function objGet($sDn, $sSearchFilter='(objectclass=*)', $aAttributesToGet = array("*"))` +## `public function objGet(string $sDn, string $sSearchFilter = '(objectclass=*)', array $aAttributesToGet = ["*"]): bool|array` -read attributes from ldap node with given DN (using ldap_read) +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 **Parameters:** @@ -335,12 +329,12 @@ $aAttributesToGet | array | flat array of attributes to fetch **Return:** - array + boolean|array --- -## `public function objUpdate($sDn, $aItem)` +## `public function objUpdate(string $sDn, array $aItem): bool` -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 +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 **Parameters:** @@ -354,9 +348,9 @@ $aItem | array | updated entry boolean --- -## `public function objDelete($sDn)` +## `public function objDelete(string $sDn): bool` -delete an ldap object this requires a ldap bind with master/ admin account +delete an ldap object this requires a ldap bind with master/ admin account It returns "false" if the action failed **Parameters:** @@ -369,25 +363,24 @@ $sDn | string | full DN to remove boolean --- -## `public function objDeleteAttr($sDn, $aItem)` - -delete attributes of an ldap object this requires a ldap bind with master/ admin account +## `public function objDeleteAttr(string $sDn, array $aItem): bool` -TODO: Test me +delete attributes of an ldap object this requires a ldap bind with master/ admin account It returns "false" if the action failed +remove attribute "userPassword" of user $sUserDn: <code>$oLdap->objDeleteAttr($sUserDn, ['userPassword'=>[]]</code> **Parameters:** Var | Type | Desciption -- |-- |-- $sDn | string | DN -$aItem | string | item to remove +$aItem | array | item to remove **Return:** boolean --- -## `public function objectAttributeExists($sDn, $sAttribute)` +## `public function objectAttributeExists(string $sDn, string $sAttribute): bool` check if an attribute exists in a DN @@ -404,7 +397,7 @@ $sAttrValue | string | value to check boolean --- -## `public function objectAttributeAndValueExist($sDn, $sAttribute, $sAttrValue)` +## `public function objectAttributeAndValueExist(string $sDn, string $sAttribute, string $sAttrValue): bool` check if an attribute and value exist in a DN @@ -421,7 +414,7 @@ $sAttrValue | string | value to check boolean --- -## `public function objectAttributeAndValueMustExist($sDn, $sAttribute, $sAttrValue)` +## `public function objectAttributeAndValueMustExist(string $sDn, string $sAttribute, string $sAttrValue): bool` check an attribute and value; it will be created if it does not exist this requires a ldap bind with master/ admin account @@ -438,7 +431,7 @@ $sAttrValue | string | value to check boolean --- -## `public function userAdd($aItem, $sDn = false)` +## `public function userAdd(array $aItem, string $sDn = ""): bool` create a new user item this requires a ldap bind with master/ admin account @@ -454,7 +447,7 @@ $sDn | string | optional DN where to create the user boolean --- -## `public function userDelete($sUserDn)` +## `public function userDelete(string $sUserDn): bool` delete a user this requires a ldap bind with master/ admin account @@ -470,7 +463,7 @@ $sPW | string | new password to set boolean --- -## `public function userUpdate($aItem)` +## `public function userUpdate(array $aItem): bool` update an ldap object this requires a ldap bind with master/ admin account @@ -485,7 +478,7 @@ $aItem | array | new user data to update boolean --- -## `public function verifyPassword($sUser, $sPW)` +## `public function verifyPassword(string $sUser, string $sPW): bool` verify user and password **Parameters:** diff --git a/docs/_index.md b/docs/_index.md index 074f0b26c1b2f2ed37a9648729d13c0cac2d8d16..2f382b7453ad16188037524529a4197f82838d93 100644 --- a/docs/_index.md +++ b/docs/_index.md @@ -5,12 +5,13 @@ A PHP class that I use * for authentication of user logins * CRUD actions on ldap nodes -Institute for Medical Education; University of Bern - -License: GNU GPL 3 +👤 Author: Axel Hahn; Institute for Medical Education; University of Bern +📄 Source: https://git-repo.iml.unibe.ch/iml-open-source/ldap-php-class +📜 License: GNU GPL 3.0 +📗 Docs: https://os-docs.iml.unibe.ch/ldap-php-class/ ## Requirements -* PHP 7+ +* PHP 8 * Php Ldap module -* OpenLdap server to connect +* OpenLdap server / Active Directory to connect diff --git a/src/ldap.class.php b/src/ldap.class.php index 82e4a57c387cfced3423599f0cb00f6763289b5d..67babe5eb26008d8c3f053d153183a48fcb3ef87 100755 --- a/src/ldap.class.php +++ b/src/ldap.class.php @@ -3,24 +3,26 @@ /** * * 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 + * + * 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 + * + * @author axel.hahn@unibe.ch */ -class imlldap { +class imlldap +{ // ---------------------------------------------------------------------- // vars // ---------------------------------------------------------------------- - + /** * @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 @@ -29,27 +31,34 @@ class imlldap { 'DnAppNode' => false, // cn=AppGroup... 'protoVersion' => 3, 'debugLevel' => 0, - ); + ]; /** * @var object current ldap connection */ - private $_ldapConn = false; + private object|bool $_ldapConn = false; + + /** + * ldap bind object - bind was done? + * @var object|bool + */ + private object|bool $_ldapBind = false; /** - * @var bool bind was done? + * Flag if debug mode is on + * @var bool */ - private $_ldapBind = false; - var $bDebug = false; + var bool $bDebug = false; // ---------------------------------------------------------------------- // functions // ---------------------------------------------------------------------- - + /** * 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 +67,8 @@ class imlldap { } } - public function __destruct() { + public function __destruct() + { $this->close(); } @@ -72,7 +82,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 +94,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 +106,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,8 +121,9 @@ class imlldap { * @param string $sText message text * @return boolean */ - private function _wLdaperror($sText = '') { - $this->_w(($sText ? $sText . ' - ' : '' ) . 'last LDAP-ERROR: ' . ldap_error($this->_ldapConn)); + private function _wLdaperror(string $sText = ''): bool + { + $this->_w(($sText ? $sText . ' - ' : '') . 'last LDAP-ERROR: ' . ldap_error($this->_ldapConn)); return true; } @@ -130,7 +144,8 @@ class imlldap { * 'protoVersion' => 3 * 'debugLevel' => 0 // for debugging set higher 0 AND call 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 +163,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 +178,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."); @@ -173,7 +190,7 @@ class imlldap { } $this->_w(__FUNCTION__ . ' connect to ' . $this->_aLdap['server'] . ':' . $this->_aLdap['port']); - $this->_ldapConn = ldap_connect($this->_aLdap['server'], $this->_aLdap['port']); + $this->_ldapConn = ldap_connect($this->_aLdap['server']); if (!$this->_ldapConn) { $this->_wLdaperror(__FUNCTION__); die(__CLASS__ . " ERROR: ldap connect failed."); @@ -201,10 +218,11 @@ class imlldap { * @param string $sUser optional: username (overrides _aLdap['DnLdapUser']) * @param string $sPw optional: password (overrides _aLdap['PwLdapUser']) */ - public function bind($sUser = '', $sPw = '') { - if(!$sUser){ + public function bind(string $sUser = '', string $sPw = ''): bool + { + if (!$sUser) { $sUser = $this->_aLdap['DnLdapUser']; - $sPw = $this->_aLdap['PwLdapUser']; + $sPw = $this->_aLdap['PwLdapUser']; } if (!$this->_ldapConn) { @@ -218,7 +236,7 @@ class imlldap { $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 ' . substr($sPw,0,4).'**********'); + $this->_w(__FUNCTION__ . ' with user ' . $sUser . ' PW ' . substr($sPw, 0, 4) . '**********'); $this->_ldapBind = @ldap_bind($this->_ldapConn, $sUser, $sPw); if (!$this->_ldapBind) { @@ -232,7 +250,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,30 +270,33 @@ 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) { - if (!is_array($aRecord) || !isset($aRecord['dn'])){ + 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; - if($bUseArray){ + $bUseArray = count($aData) > 1 || array_search($sAttr, ['hieradata', 'member', 'memberof', 'objectclass']) !== false; + if ($bUseArray) { sort($aData); } $value = $bUseArray ? $aData : $aData[0]; @@ -284,23 +306,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,51 +320,56 @@ 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(); - for($i=0; $i<65; $i++){ - $val=dechex($i); - if ($val<10){ - $val="0$val"; + $aReplace = []; + for ($i = 0; $i < 65; $i++) { + $val = dechex($i); + if ($val < 10) { + $val = "0$val"; } - $aReplace[chr($i)]='\\'.$val; + $aReplace[chr($i)] = '\\' . $val; } - $sReturn=$s; - $sReturn=str_replace(array_keys($aReplace), array_values($aReplace), $sReturn); - + $sReturn = $s; + $sReturn = str_replace(array_keys($aReplace), array_values($aReplace), $sReturn); + 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } - $this->_w(__FUNCTION__ . ' DN = ' . $sDn . ' filter = ' . $sSearchFilter . ' attributes = ' . print_r($aAttributesToGet, 1).' recursive = '.($bRecursive ? 'yes' : 'no' )); + $this->_w(__FUNCTION__ . ' DN = ' . $sDn . ' filter = ' . $sSearchFilter . ' attributes = ' . print_r($aAttributesToGet, 1) . ' recursive = ' . ($bRecursive ? 'yes' : 'no')); $oLdapSearch = $bRecursive - ? ldap_search($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet) - : ldap_list ($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet) - ; + ? 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); - $this->_w(__FUNCTION__ . " count of returned items: ".count($aItems)); + $this->_w(__FUNCTION__ . " count of returned items: " . count($aItems)); // $this->_w(__FUNCTION__ . " <pre>".print_r($aItems,1).'</pre>'); return $aItems; } @@ -371,9 +381,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) { @@ -396,13 +407,14 @@ class imlldap { * It returns false if the user does not exist or is * 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 string $sUser user id (uid) or email (mail) to search + * @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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -430,12 +442,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,15 +465,16 @@ 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'])){ + 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)))))) { + if (!ldap_mod_replace($this->_ldapConn, $sDn, ['userpassword' => "{MD5}" . base64_encode(pack("H*", md5($sPW)))])) { $this->_wLdaperror(__FUNCTION__); return false; } else { @@ -478,17 +492,18 @@ 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); // Encrypt it with the MD4 hash - $MD4Hash=hash('md4',$Input); + $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); + return ($NTLMHash); } /** @@ -502,15 +517,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( - $sPwField => $sPwValue, - 'SambaPwdLastSet' => date('U'), - )); + 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,15 +538,17 @@ 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) { - $this->_w(__FUNCTION__ . '("' . $sDn . '", <pre>['.print_r($aItem, 1).']</pre>)'); + 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -543,13 +564,14 @@ 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -567,23 +589,27 @@ class imlldap { return false; } - /** + /** * 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).' )'); + $this->_w(__FUNCTION__ . '("' . $sDn . '", filter = ' . $sSearchFilter . ', atttr= ' . print_r($aAttributesToGet, 1) . ' )'); if (!$this->_ldapBind) { - if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } - + $oLdapResult = ldap_read($this->_ldapConn, $sDn, $sSearchFilter, $aAttributesToGet); if (!$oLdapResult) { @@ -597,15 +623,17 @@ 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -624,14 +652,16 @@ 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -640,7 +670,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,17 +680,21 @@ 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'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { return false; } } @@ -669,7 +704,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,15 +719,16 @@ 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) { - if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { 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,15 +742,16 @@ 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) { - if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])){ + if (!$this->bind($this->_aLdap['DnLdapUser'], $this->_aLdap['PwLdapUser'])) { 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 +766,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 +776,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 +788,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 +809,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 +822,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 +843,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);