From 11d83d30eb6b896e2ec44e3fef5621e4610f9f68 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 24 Aug 2013 14:28:26 +0200 Subject: [PATCH 1/4] enhancements required by EGroupware: - support for utf-8 (and others supported by mbstring) internal encoding - Win2008r2/Samba4 CN=Users container can now be specified - Win2008r2 required creation order for users: 1. deactivated, 2. password added, 3. activated - Win2008r2 password reset requires ldap_mod_replace --- egw.diff | 260 +++++++++++++++++++++++++++++++++++ src/adLDAP.php | 19 ++- src/classes/adLDAPGroups.php | 13 +- src/classes/adLDAPUsers.php | 74 ++++++++-- src/classes/adLDAPUtils.php | 12 +- 5 files changed, 351 insertions(+), 27 deletions(-) create mode 100644 egw.diff diff --git a/egw.diff b/egw.diff new file mode 100644 index 0000000..4b4f3d6 --- /dev/null +++ b/egw.diff @@ -0,0 +1,260 @@ +diff --git a/src/adLDAP.php b/src/adLDAP.php +index 593ebbd..ef8aed4 100644 +--- a/src/adLDAP.php ++++ b/src/adLDAP.php +@@ -163,6 +163,13 @@ class adLDAP { + */ + protected $recursiveGroups = true; + ++ /** ++ * Charset used for internal encoding ++ * ++ * @var string ++ */ ++ public $charset = 'iso-8859-1'; ++ + // You should not need to edit anything below this line + //****************************************************************************************** + +@@ -605,6 +612,9 @@ class adLDAP { + $this->setUseSSO(false); + } + } ++ if (array_key_exists("charset", $options)) { ++ $this->charset = strtolower($options["charset"]); ++ } + } + + if ($this->ldapSupported() === false) { +@@ -949,15 +959,18 @@ class adLDAP { + */ + protected function encode8Bit(&$item, $key) { + $encode = false; +- if (is_string($item)) { ++ if (is_string($item) && $key != 'password' && $this->charset != 'utf-8') { + for ($i=0; $i> 7) { + $encode = true; ++ break; + } + } + } +- if ($encode === true && $key != 'password') { +- $item = utf8_encode($item); ++ if ($encode === true) { ++ $item = function_exists('mb_convert_encoding') ? ++ mb_convert_encoding($item, 'utf-8', $this->charset) : ++ utf8_encode($item); + } + } + +diff --git a/src/classes/adLDAPGroups.php b/src/classes/adLDAPGroups.php +index bc3def1..4460ab0 100644 +--- a/src/classes/adLDAPGroups.php ++++ b/src/classes/adLDAPGroups.php +@@ -153,6 +153,8 @@ class adLDAPGroups { + /** + * Create a group + * ++ * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" ++ * + * @param array $attributes Default attributes of the group + * @return bool + */ +@@ -162,8 +164,7 @@ class adLDAPGroups { + if (!array_key_exists("group_name", $attributes)){ return "Missing compulsory field [group_name]"; } + if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } + if (!array_key_exists("description", $attributes)){ return "Missing compulsory field [description]"; } +- if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } +- $attributes["container"] = array_reverse($attributes["container"]); ++ if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } + + //$member_array = array(); + //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; +@@ -176,8 +177,12 @@ class adLDAPGroups { + $add["description"] = $attributes["description"]; + //$add["member"] = $member_array; UNTESTED + +- $container = "OU=" . implode(",OU=", $attributes["container"]); +- $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); ++ // Determine the container ++ if (is_array($attributes['container'])) { ++ $attributes["container"] = array_reverse($attributes["container"]); ++ $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); ++ } ++ $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); + if ($result != true) { + return false; + } +diff --git a/src/classes/adLDAPUsers.php b/src/classes/adLDAPUsers.php +index 96a93b5..7184a55 100644 +--- a/src/classes/adLDAPUsers.php ++++ b/src/classes/adLDAPUsers.php +@@ -69,6 +69,13 @@ class adLDAPUsers { + * + * If you specify a password here, this can only be performed over SSL + * ++ * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" ++ * ++ * Extended to ensure following creating order required by at least win2008r2: ++ * - new user without password and deactivated ++ * - add password, see new method setPassword ++ * - activate user ++ * + * @param array $attributes The attributes to set to the user account + * @return bool + */ +@@ -80,7 +87,7 @@ class adLDAPUsers { + if (!array_key_exists("surname", $attributes)){ return "Missing compulsory field [surname]"; } + if (!array_key_exists("email", $attributes)){ return "Missing compulsory field [email]"; } + if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } +- if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } ++ if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } + + if (array_key_exists("password",$attributes) && (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS())){ + throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); +@@ -94,8 +101,9 @@ class adLDAPUsers { + $add = $this->adldap->adldap_schema($attributes); + + // Additional stuff only used for adding accounts +- $add["cn"][0] = $attributes["display_name"]; ++ $add["cn"][0] = $attributes["username"]; // win2008r2 uses username, not displayname + $add["samaccountname"][0] = $attributes["username"]; ++ $add["userPrincipalName"][0] = $attributes["username"].$this->adldap->getAccountSuffix(); + $add["objectclass"][0] = "top"; + $add["objectclass"][1] = "person"; + $add["objectclass"][2] = "organizationalPerson"; +@@ -103,22 +111,37 @@ class adLDAPUsers { + //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; + + // Set the account control attribute +- $control_options = array("NORMAL_ACCOUNT"); +- if (!$attributes["enabled"]) { +- $control_options[] = "ACCOUNTDISABLE"; +- } ++ $control_options = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); + $add["userAccountControl"][0] = $this->accountControl($control_options); + + // Determine the container ++ if (is_array($attributes['container'])) { + $attributes["container"] = array_reverse($attributes["container"]); +- $container = "OU=" . implode(", OU=",$attributes["container"]); ++ $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); ++ } ++ // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 ++ unset($add['unicodePwd']); + + // Add the entry +- $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); ++ $result = @ldap_add($ds=$this->adldap->getLdapConnection(), $dn="CN=" . $add["cn"][0] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); + if ($result != true) { + return false; + } + ++ // now password can be added to still disabled account ++ if (array_key_exists("password",$attributes)) ++ { ++ if (!$this->setPassword($dn, $attributes['password'])) return false; ++ ++ // now account can be enabled ++ if ($attributes["enabled"]) ++ { ++ $control_options = array("NORMAL_ACCOUNT"); ++ $mod = array("userAccountControl" => $this->accountControl($control_options)); ++ $result = @ldap_modify($ds, $dn, $mod); ++ } ++ } ++ + return true; + } + +@@ -420,13 +443,21 @@ class adLDAPUsers { + } + $mod["userAccountControl"][0] = $this->accountControl($controlOptions); + } ++ // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 ++ unset($mod['unicodePwd']); + ++ if ($mod) ++ { + // Do the update + $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); + if ($result == false) { + return false; + } +- ++ } ++ if (array_key_exists("password",$attributes) && !$this->setPassword($userDn, $attributes['password'])) ++ { ++ return false; ++ } + return true; + } + +@@ -510,18 +541,43 @@ class adLDAPUsers { + /** + * Encode a password for transmission over LDAP + * ++ * Extended to use mbstring to convert from arbitrary charset to UTF-16LE ++ * + * @param string $password The password to encode + * @return string + */ + public function encodePassword($password) + { + $password="\"".$password."\""; ++ if (function_exists('mb_convert_encoding')) ++ { ++ return mb_convert_encoding($password, 'UTF-16LE', $this->adldap->charset); ++ } + $encoded=""; + for ($i=0; $i adldap->getLdapConnection(), $dn, array( ++ 'unicodePwd' => $this->encodePassword($password), ++ )); ++ return $result; ++ } ++ ++ /** + * Obtain the user's distinguished name based on their userid + * + * +diff --git a/src/classes/adLDAPUtils.php b/src/classes/adLDAPUtils.php +index f039a42..bd5f650 100644 +--- a/src/classes/adLDAPUtils.php ++++ b/src/classes/adLDAPUtils.php +@@ -227,17 +227,7 @@ class adLDAPUtils { + * Convert 8bit characters e.g. accented characters to UTF8 encoded characters + */ + public function encode8Bit(&$item, $key) { +- $encode = false; +- if (is_string($item)) { +- for ($i=0; $i> 7) { +- $encode = true; +- } +- } +- } +- if ($encode === true && $key != 'password') { +- $item = utf8_encode($item); +- } ++ return $this->adldap->encode8bit($item, $key); + } + + /** diff --git a/src/adLDAP.php b/src/adLDAP.php index 593ebbd..04c5c22 100644 --- a/src/adLDAP.php +++ b/src/adLDAP.php @@ -163,6 +163,13 @@ class adLDAP { */ protected $recursiveGroups = true; + /** + * Charset used for internal encoding + * + * @var string + */ + public $charset = 'iso-8859-1'; + // You should not need to edit anything below this line //****************************************************************************************** @@ -605,6 +612,9 @@ function __construct($options = array()) $this->setUseSSO(false); } } + if (array_key_exists("charset", $options)) { + $this->charset = strtolower($options["charset"]); + } } if ($this->ldapSupported() === false) { @@ -949,15 +959,18 @@ public function adldap_schema($attributes){ */ protected function encode8Bit(&$item, $key) { $encode = false; - if (is_string($item)) { + if (is_string($item) && $key != 'password' && $this->charset != 'utf-8') { for ($i=0; $i> 7) { $encode = true; + break; } } } - if ($encode === true && $key != 'password') { - $item = utf8_encode($item); + if ($encode === true) { + $item = function_exists('mb_convert_encoding') ? + mb_convert_encoding($item, 'utf-8', $this->charset) : + utf8_encode($item); } } diff --git a/src/classes/adLDAPGroups.php b/src/classes/adLDAPGroups.php index bc3def1..b8b9c66 100644 --- a/src/classes/adLDAPGroups.php +++ b/src/classes/adLDAPGroups.php @@ -153,6 +153,8 @@ public function addContact($group, $contactDn) /** * Create a group * + * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" + * * @param array $attributes Default attributes of the group * @return bool */ @@ -162,8 +164,7 @@ public function create($attributes) if (!array_key_exists("group_name", $attributes)){ return "Missing compulsory field [group_name]"; } if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } if (!array_key_exists("description", $attributes)){ return "Missing compulsory field [description]"; } - if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } - $attributes["container"] = array_reverse($attributes["container"]); + if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } //$member_array = array(); //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; @@ -176,8 +177,12 @@ public function create($attributes) $add["description"] = $attributes["description"]; //$add["member"] = $member_array; UNTESTED - $container = "OU=" . implode(",OU=", $attributes["container"]); - $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); + // Determine the container + if (is_array($attributes['container'])) { + $attributes["container"] = array_reverse($attributes["container"]); + $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); + } + $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); if ($result != true) { return false; } diff --git a/src/classes/adLDAPUsers.php b/src/classes/adLDAPUsers.php index 96a93b5..54c37ac 100644 --- a/src/classes/adLDAPUsers.php +++ b/src/classes/adLDAPUsers.php @@ -69,6 +69,13 @@ public function authenticate($username, $password, $preventRebind = false) { * * If you specify a password here, this can only be performed over SSL * + * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" + * + * Extended to ensure following creating order required by at least win2008r2: + * - new user without password and deactivated + * - add password, see new method setPassword + * - activate user + * * @param array $attributes The attributes to set to the user account * @return bool */ @@ -80,7 +87,7 @@ public function create($attributes) if (!array_key_exists("surname", $attributes)){ return "Missing compulsory field [surname]"; } if (!array_key_exists("email", $attributes)){ return "Missing compulsory field [email]"; } if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } - if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } + if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } if (array_key_exists("password",$attributes) && (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS())){ throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); @@ -94,8 +101,9 @@ public function create($attributes) $add = $this->adldap->adldap_schema($attributes); // Additional stuff only used for adding accounts - $add["cn"][0] = $attributes["display_name"]; + $add["cn"][0] = $attributes["username"]; // win2008r2 uses username, not displayname $add["samaccountname"][0] = $attributes["username"]; + $add["userPrincipalName"][0] = $attributes["username"].$this->adldap->getAccountSuffix(); $add["objectclass"][0] = "top"; $add["objectclass"][1] = "person"; $add["objectclass"][2] = "organizationalPerson"; @@ -103,22 +111,37 @@ public function create($attributes) //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; // Set the account control attribute - $control_options = array("NORMAL_ACCOUNT"); - if (!$attributes["enabled"]) { - $control_options[] = "ACCOUNTDISABLE"; - } + $control_options = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); $add["userAccountControl"][0] = $this->accountControl($control_options); // Determine the container + if (is_array($attributes['container'])) { $attributes["container"] = array_reverse($attributes["container"]); - $container = "OU=" . implode(", OU=",$attributes["container"]); + $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); + } + // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 + unset($add['unicodePwd']); // Add the entry - $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); + $result = @ldap_add($ds=$this->adldap->getLdapConnection(), $dn="CN=" . $add["cn"][0] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); if ($result != true) { return false; } + // now password can be added to still disabled account + if (array_key_exists("password",$attributes)) + { + if (!$this->setPassword($dn, $attributes['password'])) return false; + + // now account can be enabled + if ($attributes["enabled"]) + { + $control_options = array("NORMAL_ACCOUNT"); + $mod = array("userAccountControl" => $this->accountControl($control_options)); + $result = @ldap_modify($ds, $dn, $mod); + } + } + return true; } @@ -420,13 +443,21 @@ public function modify($username, $attributes, $isGUID = false) } $mod["userAccountControl"][0] = $this->accountControl($controlOptions); } + // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 + unset($mod['unicodePwd']); + if ($mod) + { // Do the update $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); if ($result == false) { return false; } - + } + if (array_key_exists("password",$attributes) && !$this->setPassword($userDn, $attributes['password'])) + { + return false; + } return true; } @@ -510,17 +541,42 @@ public function password($username, $password, $isGUID = false) /** * Encode a password for transmission over LDAP * + * Extended to use mbstring to convert from arbitrary charset to UTF-16LE + * * @param string $password The password to encode * @return string */ public function encodePassword($password) { $password="\"".$password."\""; + if (function_exists('mb_convert_encoding')) + { + return mb_convert_encoding($password, 'UTF-16LE', $this->adldap->charset); + } $encoded=""; for ($i=0; $i adldap->getLdapConnection(), $dn, array( + 'unicodePwd' => $this->encodePassword($password), + )); + return $result; + } + /** * Obtain the user's distinguished name based on their userid * diff --git a/src/classes/adLDAPUtils.php b/src/classes/adLDAPUtils.php index f039a42..08a3ecc 100644 --- a/src/classes/adLDAPUtils.php +++ b/src/classes/adLDAPUtils.php @@ -227,17 +227,7 @@ public function boolToStr($bool) * Convert 8bit characters e.g. accented characters to UTF8 encoded characters */ public function encode8Bit(&$item, $key) { - $encode = false; - if (is_string($item)) { - for ($i=0; $i> 7) { - $encode = true; - } - } - } - if ($encode === true && $key != 'password') { - $item = utf8_encode($item); - } + return $this->adldap->encode8bit($item, $key); } /** From cd6dbd124e6cf11b961269d67f9328d1494b4af7 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 24 Aug 2013 14:33:34 +0200 Subject: [PATCH 2/4] remove accidently commited diff --- egw.diff | 260 ------------------------------------------------------- 1 file changed, 260 deletions(-) delete mode 100644 egw.diff diff --git a/egw.diff b/egw.diff deleted file mode 100644 index 4b4f3d6..0000000 --- a/egw.diff +++ /dev/null @@ -1,260 +0,0 @@ -diff --git a/src/adLDAP.php b/src/adLDAP.php -index 593ebbd..ef8aed4 100644 ---- a/src/adLDAP.php -+++ b/src/adLDAP.php -@@ -163,6 +163,13 @@ class adLDAP { - */ - protected $recursiveGroups = true; - -+ /** -+ * Charset used for internal encoding -+ * -+ * @var string -+ */ -+ public $charset = 'iso-8859-1'; -+ - // You should not need to edit anything below this line - //****************************************************************************************** - -@@ -605,6 +612,9 @@ class adLDAP { - $this->setUseSSO(false); - } - } -+ if (array_key_exists("charset", $options)) { -+ $this->charset = strtolower($options["charset"]); -+ } - } - - if ($this->ldapSupported() === false) { -@@ -949,15 +959,18 @@ class adLDAP { - */ - protected function encode8Bit(&$item, $key) { - $encode = false; -- if (is_string($item)) { -+ if (is_string($item) && $key != 'password' && $this->charset != 'utf-8') { - for ($i=0; $i> 7) { - $encode = true; -+ break; - } - } - } -- if ($encode === true && $key != 'password') { -- $item = utf8_encode($item); -+ if ($encode === true) { -+ $item = function_exists('mb_convert_encoding') ? -+ mb_convert_encoding($item, 'utf-8', $this->charset) : -+ utf8_encode($item); - } - } - -diff --git a/src/classes/adLDAPGroups.php b/src/classes/adLDAPGroups.php -index bc3def1..4460ab0 100644 ---- a/src/classes/adLDAPGroups.php -+++ b/src/classes/adLDAPGroups.php -@@ -153,6 +153,8 @@ class adLDAPGroups { - /** - * Create a group - * -+ * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" -+ * - * @param array $attributes Default attributes of the group - * @return bool - */ -@@ -162,8 +164,7 @@ class adLDAPGroups { - if (!array_key_exists("group_name", $attributes)){ return "Missing compulsory field [group_name]"; } - if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } - if (!array_key_exists("description", $attributes)){ return "Missing compulsory field [description]"; } -- if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } -- $attributes["container"] = array_reverse($attributes["container"]); -+ if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } - - //$member_array = array(); - //$member_array[0] = "cn=user1,cn=Users,dc=yourdomain,dc=com"; -@@ -176,8 +177,12 @@ class adLDAPGroups { - $add["description"] = $attributes["description"]; - //$add["member"] = $member_array; UNTESTED - -- $container = "OU=" . implode(",OU=", $attributes["container"]); -- $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); -+ // Determine the container -+ if (is_array($attributes['container'])) { -+ $attributes["container"] = array_reverse($attributes["container"]); -+ $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); -+ } -+ $result = ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); - if ($result != true) { - return false; - } -diff --git a/src/classes/adLDAPUsers.php b/src/classes/adLDAPUsers.php -index 96a93b5..7184a55 100644 ---- a/src/classes/adLDAPUsers.php -+++ b/src/classes/adLDAPUsers.php -@@ -69,6 +69,13 @@ class adLDAPUsers { - * - * If you specify a password here, this can only be performed over SSL - * -+ * Extended to allow to specify $attribute["container"] as string, because array hardcodes "OU=", while Samba4 and win2008r2 uses "CN=Users" -+ * -+ * Extended to ensure following creating order required by at least win2008r2: -+ * - new user without password and deactivated -+ * - add password, see new method setPassword -+ * - activate user -+ * - * @param array $attributes The attributes to set to the user account - * @return bool - */ -@@ -80,7 +87,7 @@ class adLDAPUsers { - if (!array_key_exists("surname", $attributes)){ return "Missing compulsory field [surname]"; } - if (!array_key_exists("email", $attributes)){ return "Missing compulsory field [email]"; } - if (!array_key_exists("container", $attributes)){ return "Missing compulsory field [container]"; } -- if (!is_array($attributes["container"])){ return "Container attribute must be an array."; } -+ if (empty($attributes["container"])){ return "Container attribute must be an array or string."; } - - if (array_key_exists("password",$attributes) && (!$this->adldap->getUseSSL() && !$this->adldap->getUseTLS())){ - throw new adLDAPException('SSL must be configured on your webserver and enabled in the class to set passwords.'); -@@ -94,8 +101,9 @@ class adLDAPUsers { - $add = $this->adldap->adldap_schema($attributes); - - // Additional stuff only used for adding accounts -- $add["cn"][0] = $attributes["display_name"]; -+ $add["cn"][0] = $attributes["username"]; // win2008r2 uses username, not displayname - $add["samaccountname"][0] = $attributes["username"]; -+ $add["userPrincipalName"][0] = $attributes["username"].$this->adldap->getAccountSuffix(); - $add["objectclass"][0] = "top"; - $add["objectclass"][1] = "person"; - $add["objectclass"][2] = "organizationalPerson"; -@@ -103,22 +111,37 @@ class adLDAPUsers { - //$add["name"][0]=$attributes["firstname"]." ".$attributes["surname"]; - - // Set the account control attribute -- $control_options = array("NORMAL_ACCOUNT"); -- if (!$attributes["enabled"]) { -- $control_options[] = "ACCOUNTDISABLE"; -- } -+ $control_options = array("NORMAL_ACCOUNT", "ACCOUNTDISABLE"); - $add["userAccountControl"][0] = $this->accountControl($control_options); - - // Determine the container -+ if (is_array($attributes['container'])) { - $attributes["container"] = array_reverse($attributes["container"]); -- $container = "OU=" . implode(", OU=",$attributes["container"]); -+ $attributes["container"] = "OU=" . implode(",OU=",$attributes["container"]); -+ } -+ // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 -+ unset($add['unicodePwd']); - - // Add the entry -- $result = @ldap_add($this->adldap->getLdapConnection(), "CN=" . $add["cn"][0] . ", " . $container . "," . $this->adldap->getBaseDn(), $add); -+ $result = @ldap_add($ds=$this->adldap->getLdapConnection(), $dn="CN=" . $add["cn"][0] . "," . $attributes["container"] . "," . $this->adldap->getBaseDn(), $add); - if ($result != true) { - return false; - } - -+ // now password can be added to still disabled account -+ if (array_key_exists("password",$attributes)) -+ { -+ if (!$this->setPassword($dn, $attributes['password'])) return false; -+ -+ // now account can be enabled -+ if ($attributes["enabled"]) -+ { -+ $control_options = array("NORMAL_ACCOUNT"); -+ $mod = array("userAccountControl" => $this->accountControl($control_options)); -+ $result = @ldap_modify($ds, $dn, $mod); -+ } -+ } -+ - return true; - } - -@@ -420,13 +443,21 @@ class adLDAPUsers { - } - $mod["userAccountControl"][0] = $this->accountControl($controlOptions); - } -+ // we can NOT set password with ldap_add or ldap_modify, it needs ldap_mod_replace, at least under Win2008r2 -+ unset($mod['unicodePwd']); - -+ if ($mod) -+ { - // Do the update - $result = @ldap_modify($this->adldap->getLdapConnection(), $userDn, $mod); - if ($result == false) { - return false; - } -- -+ } -+ if (array_key_exists("password",$attributes) && !$this->setPassword($userDn, $attributes['password'])) -+ { -+ return false; -+ } - return true; - } - -@@ -510,18 +541,43 @@ class adLDAPUsers { - /** - * Encode a password for transmission over LDAP - * -+ * Extended to use mbstring to convert from arbitrary charset to UTF-16LE -+ * - * @param string $password The password to encode - * @return string - */ - public function encodePassword($password) - { - $password="\"".$password."\""; -+ if (function_exists('mb_convert_encoding')) -+ { -+ return mb_convert_encoding($password, 'UTF-16LE', $this->adldap->charset); -+ } - $encoded=""; - for ($i=0; $i adldap->getLdapConnection(), $dn, array( -+ 'unicodePwd' => $this->encodePassword($password), -+ )); -+ return $result; -+ } -+ -+ /** - * Obtain the user's distinguished name based on their userid - * - * -diff --git a/src/classes/adLDAPUtils.php b/src/classes/adLDAPUtils.php -index f039a42..bd5f650 100644 ---- a/src/classes/adLDAPUtils.php -+++ b/src/classes/adLDAPUtils.php -@@ -227,17 +227,7 @@ class adLDAPUtils { - * Convert 8bit characters e.g. accented characters to UTF8 encoded characters - */ - public function encode8Bit(&$item, $key) { -- $encode = false; -- if (is_string($item)) { -- for ($i=0; $i> 7) { -- $encode = true; -- } -- } -- } -- if ($encode === true && $key != 'password') { -- $item = utf8_encode($item); -- } -+ return $this->adldap->encode8bit($item, $key); - } - - /** From 9ded26ae9260c6edfa354bf5bc99410183c5d35d Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sat, 24 Aug 2013 15:07:32 +0200 Subject: [PATCH 3/4] make most utility methods static and add new method to convert unix to windows time --- src/classes/adLDAPUtils.php | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/classes/adLDAPUtils.php b/src/classes/adLDAPUtils.php index 08a3ecc..9c469f1 100644 --- a/src/classes/adLDAPUtils.php +++ b/src/classes/adLDAPUtils.php @@ -60,7 +60,7 @@ public function __construct(adLDAP $adldap) { * @param array $groups * @return array */ - public function niceNames($groups) + public static function niceNames($groups) { $groupArray = array(); @@ -84,7 +84,7 @@ public function niceNames($groups) * @param string $str * @return string */ - public function escapeCharacters($str) { + public static function escapeCharacters($str) { $str = str_replace(",", "\,", $str); return $str; } @@ -99,7 +99,7 @@ public function escapeCharacters($str) { * @author Port by Andreas Gohr * @return string */ - public function ldapSlashes($str){ + public static function ldapSlashes($str){ return preg_replace('/([\x00-\x1F\*\(\)\\\\])/e', '"\\\\\".join("",unpack("H2","$1"))', $str); @@ -111,7 +111,7 @@ public function ldapSlashes($str){ * @param string $strGUID A string representation of a GUID * @return string */ - public function strGuidToHex($strGUID) + public static function strGuidToHex($strGUID) { $strGUID = str_replace('-', '', $strGUID); @@ -139,7 +139,7 @@ public function strGuidToHex($strGUID) * @param string $binsid A Binary SID * @return string */ - public function getTextSID($binsid) { + public static function getTextSID($binsid) { $hex_sid = bin2hex($binsid); $rev = hexdec(substr($hex_sid, 0, 2)); $subcount = hexdec(substr($hex_sid, 2, 2)); @@ -148,7 +148,7 @@ public function getTextSID($binsid) { for ($x=0;$x < $subcount; $x++) { $subauth[$x] = - hexdec($this->littleEndian(substr($hex_sid, 16 + ($x * 8), 8))); + hexdec(self::littleEndian(substr($hex_sid, 16 + ($x * 8), 8))); $result .= "-" . $subauth[$x]; } @@ -177,7 +177,7 @@ public function littleEndian($hex) * @param string $bin A binary LDAP attribute * @return string */ - public function binaryToText($bin) + public static function binaryToText($bin) { $hex_guid = bin2hex($bin); $hex_guid_to_guid_str = ''; @@ -203,11 +203,11 @@ public function binaryToText($bin) * @param string $binaryGuid The binary GUID attribute to convert * @return string */ - public function decodeGuid($binaryGuid) + public static function decodeGuid($binaryGuid) { if ($binaryGuid === null){ return "Missing compulsory field [binaryGuid]"; } - $strGUID = $this->binaryToText($binaryGuid); + $strGUID = self::binaryToText($binaryGuid); return $strGUID; } @@ -249,6 +249,16 @@ public static function convertWindowsTimeToUnixTime($windowsTime) { $unixTime = round($windowsTime / 10000000) - 11644477200; return $unixTime; } + + /** + * Add seconds between 1601-01-01 and 1970-01-01 and multiply by 10000000 + * + * @param long $unixTime + * @return long windowsTime + */ + public static function convertUnixTimeToWindowsTime($unixTime) { + return ($unixTime + 11644477200) * 10000000; + } } ?> \ No newline at end of file From 21c4680b4393ea35ea82b6d80a40c561ec742606 Mon Sep 17 00:00:00 2001 From: Ralf Becker Date: Sun, 29 Mar 2015 16:37:17 +0200 Subject: [PATCH 4/4] Real AD password change, if ldap_modify_batch is available PHP 5.4 >= 5.4.26, 5.5 >= 5.5.10 or 5.6+ Password change without password-reset priveledge is subject to minimum password age policy (default: 1day). To disable that run on your AD: net accounts /MINPWAGE:0 --- src/classes/adLDAPUsers.php | 43 +++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/src/classes/adLDAPUsers.php b/src/classes/adLDAPUsers.php index d511f09..8490974 100644 --- a/src/classes/adLDAPUsers.php +++ b/src/classes/adLDAPUsers.php @@ -479,15 +479,29 @@ public function enable($username, $isGUID = false) { return true; } + /** + * Check if we can to a real password change, not just a password reset + * + * Requires PHP 5.4 >= 5.4.26, PHP 5.5 >= 5.5.10 or PHP 5.6 >= 5.6.0 + * + * @return boolean + */ + public static function changePasswordSupported() + { + return function_exists('ldap_modify_batch'); + } + /** * Set the password of a user - This must be performed over SSL * * @param string $username The username to modify * @param string $password The new password * @param bool $isGUID Is the username passed a GUID or a samAccountName + * @param string $old_password old password for password change, if supported * @return bool */ - public function password($username, $password, $isGUID = false) { + public function password($username, $password, $isGUID = false, $old_password=null) + { if ($username === NULL) { return false; } if ($password === NULL) { return false; } if (!$this->adldap->getLdapBind()) { return false; } @@ -501,10 +515,30 @@ public function password($username, $password, $isGUID = false) { } $add=array(); - $add["unicodePwd"][0] = $this->encodePassword($password); - $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add); - if ($result === false) { + if (empty($old_password) || !self::changePasswordSupported()) + { + $add["unicodePwd"][0] = $this->encodePassword($password); + + $result = @ldap_mod_replace($this->adldap->getLdapConnection(), $userDn, $add); + } + else + { + $mods = array( + array( + "attrib" => "unicodePwd", + "modtype" => LDAP_MODIFY_BATCH_REMOVE, + "values" => array($this->encodePassword($old_password)), + ), + array( + "attrib" => "unicodePwd", + "modtype" => LDAP_MODIFY_BATCH_ADD, + "values" => array($this->encodePassword($password)), + ), + ); + $result = ldap_modify_batch($this->adldap->getLdapConnection(), $userDn, $mods); + } + if ($result === false){ $err = ldap_errno($this->adldap->getLdapConnection()); if ($err) { $msg = 'Error ' . $err . ': ' . ldap_err2str($err) . '.'; @@ -517,6 +551,7 @@ public function password($username, $password, $isGUID = false) { return false; } } + return true; }