Ldap.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Auth
  17. * @subpackage Zend_Auth_Adapter
  18. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. /**
  23. * @see Zend_Auth_Adapter_Interface
  24. */
  25. require_once 'Zend/Auth/Adapter/Interface.php';
  26. /**
  27. * @category Zend
  28. * @package Zend_Auth
  29. * @subpackage Zend_Auth_Adapter
  30. * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
  31. * @license http://framework.zend.com/license/new-bsd New BSD License
  32. */
  33. class Zend_Auth_Adapter_Ldap implements Zend_Auth_Adapter_Interface
  34. {
  35. /**
  36. * The Zend_Ldap context.
  37. *
  38. * @var Zend_Ldap
  39. */
  40. protected $_ldap = null;
  41. /**
  42. * The array of arrays of Zend_Ldap options passed to the constructor.
  43. *
  44. * @var array
  45. */
  46. protected $_options = null;
  47. /**
  48. * The username of the account being authenticated.
  49. *
  50. * @var string
  51. */
  52. protected $_username = null;
  53. /**
  54. * The password of the account being authenticated.
  55. *
  56. * @var string
  57. */
  58. protected $_password = null;
  59. /**
  60. * Constructor
  61. *
  62. * @param array $options An array of arrays of Zend_Ldap options
  63. * @param string $username The username of the account being authenticated
  64. * @param string $password The password of the account being authenticated
  65. * @return void
  66. */
  67. public function __construct(array $options = array(), $username = null, $password = null)
  68. {
  69. $this->setOptions($options);
  70. if ($username !== null) {
  71. $this->setUsername($username);
  72. }
  73. if ($password !== null) {
  74. $this->setPassword($password);
  75. }
  76. }
  77. /**
  78. * Returns the array of arrays of Zend_Ldap options of this adapter.
  79. *
  80. * @return array|null
  81. */
  82. public function getOptions()
  83. {
  84. return $this->_options;
  85. }
  86. /**
  87. * Sets the array of arrays of Zend_Ldap options to be used by
  88. * this adapter.
  89. *
  90. * @param array $options The array of arrays of Zend_Ldap options
  91. * @return Zend_Auth_Adapter_Ldap Provides a fluent interface
  92. */
  93. public function setOptions($options)
  94. {
  95. $this->_options = is_array($options) ? $options : array();
  96. return $this;
  97. }
  98. /**
  99. * Returns the username of the account being authenticated, or
  100. * NULL if none is set.
  101. *
  102. * @return string|null
  103. */
  104. public function getUsername()
  105. {
  106. return $this->_username;
  107. }
  108. /**
  109. * Sets the username for binding
  110. *
  111. * @param string $username The username for binding
  112. * @return Zend_Auth_Adapter_Ldap Provides a fluent interface
  113. */
  114. public function setUsername($username)
  115. {
  116. $this->_username = (string) $username;
  117. return $this;
  118. }
  119. /**
  120. * Returns the password of the account being authenticated, or
  121. * NULL if none is set.
  122. *
  123. * @return string|null
  124. */
  125. public function getPassword()
  126. {
  127. return $this->_password;
  128. }
  129. /**
  130. * Sets the passwort for the account
  131. *
  132. * @param string $password The password of the account being authenticated
  133. * @return Zend_Auth_Adapter_Ldap Provides a fluent interface
  134. */
  135. public function setPassword($password)
  136. {
  137. $this->_password = (string) $password;
  138. return $this;
  139. }
  140. /**
  141. * Returns the LDAP Object
  142. *
  143. * @return Zend_Ldap The Zend_Ldap object used to authenticate the credentials
  144. */
  145. public function getLdap()
  146. {
  147. if ($this->_ldap === null) {
  148. /**
  149. * @see Zend_Ldap
  150. */
  151. require_once 'Zend/Ldap.php';
  152. $this->_ldap = new Zend_Ldap();
  153. }
  154. return $this->_ldap;
  155. }
  156. /**
  157. * Set an Ldap connection
  158. *
  159. * @param Zend_Ldap $ldap An existing Ldap object
  160. * @return Zend_Auth_Adapter_Ldap Provides a fluent interface
  161. */
  162. public function setLdap(Zend_Ldap $ldap)
  163. {
  164. $this->_ldap = $ldap;
  165. $this->setOptions(array($ldap->getOptions()));
  166. return $this;
  167. }
  168. /**
  169. * Returns a domain name for the current LDAP options. This is used
  170. * for skipping redundant operations (e.g. authentications).
  171. *
  172. * @return string
  173. */
  174. protected function _getAuthorityName()
  175. {
  176. $options = $this->getLdap()->getOptions();
  177. $name = $options['accountDomainName'];
  178. if (!$name)
  179. $name = $options['accountDomainNameShort'];
  180. return $name ? $name : '';
  181. }
  182. /**
  183. * Authenticate the user
  184. *
  185. * @throws Zend_Auth_Adapter_Exception
  186. * @return Zend_Auth_Result
  187. */
  188. public function authenticate()
  189. {
  190. /**
  191. * @see Zend_Ldap_Exception
  192. */
  193. require_once 'Zend/Ldap/Exception.php';
  194. $messages = array();
  195. $messages[0] = ''; // reserved
  196. $messages[1] = ''; // reserved
  197. $username = $this->_username;
  198. $password = $this->_password;
  199. if (!$username) {
  200. $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
  201. $messages[0] = 'A username is required';
  202. return new Zend_Auth_Result($code, '', $messages);
  203. }
  204. if (!$password) {
  205. /* A password is required because some servers will
  206. * treat an empty password as an anonymous bind.
  207. */
  208. $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
  209. $messages[0] = 'A password is required';
  210. return new Zend_Auth_Result($code, '', $messages);
  211. }
  212. $ldap = $this->getLdap();
  213. $code = Zend_Auth_Result::FAILURE;
  214. $messages[0] = "Authority not found: $username";
  215. $failedAuthorities = array();
  216. /* Iterate through each server and try to authenticate the supplied
  217. * credentials against it.
  218. */
  219. foreach ($this->_options as $name => $options) {
  220. if (!is_array($options)) {
  221. /**
  222. * @see Zend_Auth_Adapter_Exception
  223. */
  224. require_once 'Zend/Auth/Adapter/Exception.php';
  225. throw new Zend_Auth_Adapter_Exception('Adapter options array not in array');
  226. }
  227. $ldap->setOptions($options);
  228. $dname = '';
  229. try {
  230. if ($messages[1])
  231. $messages[] = $messages[1];
  232. $messages[1] = '';
  233. $messages[] = $this->_optionsToString($options);
  234. $dname = $this->_getAuthorityName();
  235. if (isset($failedAuthorities[$dname])) {
  236. /* If multiple sets of server options for the same domain
  237. * are supplied, we want to skip redundant authentications
  238. * where the identity or credentials where found to be
  239. * invalid with another server for the same domain. The
  240. * $failedAuthorities array tracks this condition (and also
  241. * serves to supply the original error message).
  242. * This fixes issue ZF-4093.
  243. */
  244. $messages[1] = $failedAuthorities[$dname];
  245. $messages[] = "Skipping previously failed authority: $dname";
  246. continue;
  247. }
  248. $canonicalName = $ldap->getCanonicalAccountName($username);
  249. $ldap->bind($canonicalName, $password);
  250. $messages[0] = '';
  251. $messages[1] = '';
  252. $messages[] = "$canonicalName authentication successful";
  253. return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $canonicalName, $messages);
  254. } catch (Zend_Ldap_Exception $zle) {
  255. /* LDAP based authentication is notoriously difficult to diagnose. Therefore
  256. * we bend over backwards to capture and record every possible bit of
  257. * information when something goes wrong.
  258. */
  259. $err = $zle->getCode();
  260. if ($err == Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH) {
  261. /* This error indicates that the domain supplied in the
  262. * username did not match the domains in the server options
  263. * and therefore we should just skip to the next set of
  264. * server options.
  265. */
  266. continue;
  267. } else if ($err == Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) {
  268. $code = Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND;
  269. $messages[0] = "Account not found: $username";
  270. $failedAuthorities[$dname] = $zle->getMessage();
  271. } else if ($err == Zend_Ldap_Exception::LDAP_INVALID_CREDENTIALS) {
  272. $code = Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID;
  273. $messages[0] = 'Invalid credentials';
  274. $failedAuthorities[$dname] = $zle->getMessage();
  275. } else {
  276. $line = $zle->getLine();
  277. $messages[] = $zle->getFile() . "($line): " . $zle->getMessage();
  278. $messages[] = str_replace($password, '*****', $zle->getTraceAsString());
  279. $messages[0] = 'An unexpected failure occurred';
  280. }
  281. $messages[1] = $zle->getMessage();
  282. }
  283. }
  284. $msg = isset($messages[1]) ? $messages[1] : $messages[0];
  285. $messages[] = "$username authentication failed: $msg";
  286. return new Zend_Auth_Result($code, $username, $messages);
  287. }
  288. /**
  289. * Converts options to string
  290. *
  291. * @param array $options
  292. * @return string
  293. */
  294. private function _optionsToString(array $options)
  295. {
  296. $str = '';
  297. foreach ($options as $key => $val) {
  298. if ($key === 'password')
  299. $val = '*****';
  300. if ($str)
  301. $str .= ',';
  302. $str .= $key . '=' . $val;
  303. }
  304. return $str;
  305. }
  306. }