1477 lines
47 KiB
PHP
1477 lines
47 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Zend Framework
|
||
|
*
|
||
|
* LICENSE
|
||
|
*
|
||
|
* This source file is subject to the new BSD license that is bundled
|
||
|
* with this package in the file LICENSE.txt.
|
||
|
* It is also available through the world-wide-web at this URL:
|
||
|
* http://framework.zend.com/license/new-bsd
|
||
|
* If you did not receive a copy of the license and are unable to
|
||
|
* obtain it through the world-wide-web, please send an email
|
||
|
* to license@zend.com so we can send you a copy immediately.
|
||
|
*
|
||
|
* @category Zend
|
||
|
* @package Zend_Ldap
|
||
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
* @version $Id: Ldap.php 18250 2009-09-18 15:53:49Z sgehrig $
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* @category Zend
|
||
|
* @package Zend_Ldap
|
||
|
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
|
||
|
* @license http://framework.zend.com/license/new-bsd New BSD License
|
||
|
*/
|
||
|
class Zend_Ldap
|
||
|
{
|
||
|
const SEARCH_SCOPE_SUB = 1;
|
||
|
const SEARCH_SCOPE_ONE = 2;
|
||
|
const SEARCH_SCOPE_BASE = 3;
|
||
|
|
||
|
const ACCTNAME_FORM_DN = 1;
|
||
|
const ACCTNAME_FORM_USERNAME = 2;
|
||
|
const ACCTNAME_FORM_BACKSLASH = 3;
|
||
|
const ACCTNAME_FORM_PRINCIPAL = 4;
|
||
|
|
||
|
/**
|
||
|
* String used with ldap_connect for error handling purposes.
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
private $_connectString;
|
||
|
|
||
|
/**
|
||
|
* The options used in connecting, binding, etc.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
protected $_options = null;
|
||
|
|
||
|
/**
|
||
|
* The raw LDAP extension resource.
|
||
|
*
|
||
|
* @var resource
|
||
|
*/
|
||
|
protected $_resource = null;
|
||
|
|
||
|
/**
|
||
|
* Caches the RootDSE
|
||
|
*
|
||
|
* @var Zend_Ldap_Node
|
||
|
*/
|
||
|
protected $_rootDse = null;
|
||
|
|
||
|
/**
|
||
|
* Caches the schema
|
||
|
*
|
||
|
* @var Zend_Ldap_Node
|
||
|
*/
|
||
|
protected $_schema = null;
|
||
|
|
||
|
/**
|
||
|
* @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()}
|
||
|
* @param string $str The string to escape.
|
||
|
* @return string The escaped string
|
||
|
*/
|
||
|
public static function filterEscape($str)
|
||
|
{
|
||
|
/**
|
||
|
* @see Zend_Ldap_Filter_Abstract
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Filter/Abstract.php';
|
||
|
return Zend_Ldap_Filter_Abstract::escapeValue($str);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()}
|
||
|
* @param string $dn The DN to parse
|
||
|
* @param array $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
|
||
|
* @param array $vals An optional array to receive DN values
|
||
|
* @return boolean True if the DN was successfully parsed or false if the string is
|
||
|
* not a valid DN.
|
||
|
*/
|
||
|
public static function explodeDn($dn, array &$keys = null, array &$vals = null)
|
||
|
{
|
||
|
/**
|
||
|
* @see Zend_Ldap_Dn
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Dn.php';
|
||
|
return Zend_Ldap_Dn::checkDn($dn, $keys, $vals);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*
|
||
|
* @param array|Zend_Config $options Options used in connecting, binding, etc.
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __construct($options = array())
|
||
|
{
|
||
|
$this->setOptions($options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destructor.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __destruct()
|
||
|
{
|
||
|
$this->disconnect();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return resource The raw LDAP extension resource.
|
||
|
*/
|
||
|
public function getResource()
|
||
|
{
|
||
|
return $this->_resource;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the LDAP error number of the last LDAP command
|
||
|
*
|
||
|
* @return int
|
||
|
*/
|
||
|
public function getLastErrorCode()
|
||
|
{
|
||
|
$ret = @ldap_get_option($this->getResource(), LDAP_OPT_ERROR_NUMBER, $err);
|
||
|
if ($ret === true) {
|
||
|
if ($err <= -1 && $err >= -17) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
/* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error
|
||
|
* codes in OpenLDAP are negative values from -1 to -17.
|
||
|
*/
|
||
|
$err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1);
|
||
|
}
|
||
|
return $err;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the LDAP error message of the last LDAP command
|
||
|
*
|
||
|
* @param int $errorCode
|
||
|
* @param array $errorMessages
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getLastError(&$errorCode = null, array &$errorMessages = null)
|
||
|
{
|
||
|
$errorCode = $this->getLastErrorCode();
|
||
|
$errorMessages = array();
|
||
|
|
||
|
/* The various error retrieval functions can return
|
||
|
* different things so we just try to collect what we
|
||
|
* can and eliminate dupes.
|
||
|
*/
|
||
|
$estr1 = @ldap_error($this->getResource());
|
||
|
if ($errorCode !== 0 && $estr1 === 'Success') {
|
||
|
$estr1 = @ldap_err2str($errorCode);
|
||
|
}
|
||
|
if (!empty($estr1)) {
|
||
|
$errorMessages[] = $estr1;
|
||
|
}
|
||
|
|
||
|
@ldap_get_option($this->getResource(), LDAP_OPT_ERROR_STRING, $estr2);
|
||
|
if (!empty($estr2) && !in_array($estr2, $errorMessages)) {
|
||
|
$errorMessages[] = $estr2;
|
||
|
}
|
||
|
|
||
|
$message = '';
|
||
|
if ($errorCode > 0) {
|
||
|
$message = '0x' . dechex($errorCode) . ' ';
|
||
|
} else {
|
||
|
$message = '';
|
||
|
}
|
||
|
if (count($errorMessages) > 0) {
|
||
|
$message .= '(' . implode('; ', $errorMessages) . ')';
|
||
|
} else {
|
||
|
$message .= '(no error message from LDAP)';
|
||
|
}
|
||
|
return $message;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the options used in connecting, binding, etc.
|
||
|
*
|
||
|
* Valid option keys:
|
||
|
* host
|
||
|
* port
|
||
|
* useSsl
|
||
|
* username
|
||
|
* password
|
||
|
* bindRequiresDn
|
||
|
* baseDn
|
||
|
* accountCanonicalForm
|
||
|
* accountDomainName
|
||
|
* accountDomainNameShort
|
||
|
* accountFilterFormat
|
||
|
* allowEmptyPassword
|
||
|
* useStartTls
|
||
|
* optRefferals
|
||
|
* tryUsernameSplit
|
||
|
*
|
||
|
* @param array|Zend_Config $options Options used in connecting, binding, etc.
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function setOptions($options)
|
||
|
{
|
||
|
if ($options instanceof Zend_Config) {
|
||
|
$options = $options->toArray();
|
||
|
}
|
||
|
|
||
|
$permittedOptions = array(
|
||
|
'host' => null,
|
||
|
'port' => 0,
|
||
|
'useSsl' => false,
|
||
|
'username' => null,
|
||
|
'password' => null,
|
||
|
'bindRequiresDn' => false,
|
||
|
'baseDn' => null,
|
||
|
'accountCanonicalForm' => null,
|
||
|
'accountDomainName' => null,
|
||
|
'accountDomainNameShort' => null,
|
||
|
'accountFilterFormat' => null,
|
||
|
'allowEmptyPassword' => false,
|
||
|
'useStartTls' => false,
|
||
|
'optReferrals' => false,
|
||
|
'tryUsernameSplit' => true,
|
||
|
);
|
||
|
|
||
|
foreach ($permittedOptions as $key => $val) {
|
||
|
if (array_key_exists($key, $options)) {
|
||
|
$val = $options[$key];
|
||
|
unset($options[$key]);
|
||
|
/* Enforce typing. This eliminates issues like Zend_Config_Ini
|
||
|
* returning '1' as a string (ZF-3163).
|
||
|
*/
|
||
|
switch ($key) {
|
||
|
case 'port':
|
||
|
case 'accountCanonicalForm':
|
||
|
$permittedOptions[$key] = (int)$val;
|
||
|
break;
|
||
|
case 'useSsl':
|
||
|
case 'bindRequiresDn':
|
||
|
case 'allowEmptyPassword':
|
||
|
case 'useStartTls':
|
||
|
case 'optReferrals':
|
||
|
case 'tryUsernameSplit':
|
||
|
$permittedOptions[$key] = ($val === true ||
|
||
|
$val === '1' || strcasecmp($val, 'true') == 0);
|
||
|
break;
|
||
|
default:
|
||
|
$permittedOptions[$key] = trim($val);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (count($options) > 0) {
|
||
|
$key = key($options);
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
|
||
|
}
|
||
|
$this->_options = $permittedOptions;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return array The current options.
|
||
|
*/
|
||
|
public function getOptions()
|
||
|
{
|
||
|
return $this->_options;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The hostname of the LDAP server being used to authenticate accounts
|
||
|
*/
|
||
|
protected function _getHost()
|
||
|
{
|
||
|
return $this->_options['host'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return int The port of the LDAP server or 0 to indicate that no port value is set
|
||
|
*/
|
||
|
protected function _getPort()
|
||
|
{
|
||
|
return $this->_options['port'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean The default SSL / TLS encrypted transport control
|
||
|
*/
|
||
|
protected function _getUseSsl()
|
||
|
{
|
||
|
return $this->_options['useSsl'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The default acctname for binding
|
||
|
*/
|
||
|
protected function _getUsername()
|
||
|
{
|
||
|
return $this->_options['username'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The default password for binding
|
||
|
*/
|
||
|
protected function _getPassword()
|
||
|
{
|
||
|
return $this->_options['password'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean Bind requires DN
|
||
|
*/
|
||
|
protected function _getBindRequiresDn()
|
||
|
{
|
||
|
return $this->_options['bindRequiresDn'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Gets the base DN under which objects of interest are located
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function getBaseDn()
|
||
|
{
|
||
|
return $this->_options['baseDn'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or
|
||
|
* ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
|
||
|
*/
|
||
|
protected function _getAccountCanonicalForm()
|
||
|
{
|
||
|
/* Account names should always be qualified with a domain. In some scenarios
|
||
|
* using non-qualified account names can lead to security vulnerabilities. If
|
||
|
* no account canonical form is specified, we guess based in what domain
|
||
|
* names have been supplied.
|
||
|
*/
|
||
|
|
||
|
$accountCanonicalForm = $this->_options['accountCanonicalForm'];
|
||
|
if (!$accountCanonicalForm) {
|
||
|
$accountDomainName = $this->_getAccountDomainName();
|
||
|
$accountDomainNameShort = $this->_getAccountDomainNameShort();
|
||
|
if ($accountDomainNameShort) {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
|
||
|
} else if ($accountDomainName) {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
|
||
|
} else {
|
||
|
$accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return $accountCanonicalForm;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The account domain name
|
||
|
*/
|
||
|
protected function _getAccountDomainName()
|
||
|
{
|
||
|
return $this->_options['accountDomainName'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The short account domain name
|
||
|
*/
|
||
|
protected function _getAccountDomainNameShort()
|
||
|
{
|
||
|
return $this->_options['accountDomainNameShort'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string A format string for building an LDAP search filter to match
|
||
|
* an account
|
||
|
*/
|
||
|
protected function _getAccountFilterFormat()
|
||
|
{
|
||
|
return $this->_options['accountFilterFormat'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean Allow empty passwords
|
||
|
*/
|
||
|
protected function _getAllowEmptyPassword()
|
||
|
{
|
||
|
return $this->_options['allowEmptyPassword'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean The default SSL / TLS encrypted transport control
|
||
|
*/
|
||
|
protected function _getUseStartTls()
|
||
|
{
|
||
|
return $this->_options['useStartTls'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean Opt. Referrals
|
||
|
*/
|
||
|
protected function _getOptReferrals()
|
||
|
{
|
||
|
return $this->_options['optReferrals'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return boolean Try splitting the username into username and domain
|
||
|
*/
|
||
|
protected function _getTryUsernameSplit()
|
||
|
{
|
||
|
return $this->_options['tryUsernameSplit'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return string The LDAP search filter for matching directory accounts
|
||
|
*/
|
||
|
protected function _getAccountFilter($acctname)
|
||
|
{
|
||
|
/**
|
||
|
* @see Zend_Ldap_Filter_Abstract
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Filter/Abstract.php';
|
||
|
$this->_splitName($acctname, $dname, $aname);
|
||
|
$accountFilterFormat = $this->_getAccountFilterFormat();
|
||
|
$aname = Zend_Ldap_Filter_Abstract::escapeValue($aname);
|
||
|
if ($accountFilterFormat) {
|
||
|
return sprintf($accountFilterFormat, $aname);
|
||
|
}
|
||
|
if (!$this->_getBindRequiresDn()) {
|
||
|
// is there a better way to detect this?
|
||
|
return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
|
||
|
}
|
||
|
return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $name The name to split
|
||
|
* @param string $dname The resulting domain name (this is an out parameter)
|
||
|
* @param string $aname The resulting account name (this is an out parameter)
|
||
|
* @return void
|
||
|
*/
|
||
|
protected function _splitName($name, &$dname, &$aname)
|
||
|
{
|
||
|
$dname = null;
|
||
|
$aname = $name;
|
||
|
|
||
|
if (!$this->_getTryUsernameSplit()) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
$pos = strpos($name, '@');
|
||
|
if ($pos) {
|
||
|
$dname = substr($name, $pos + 1);
|
||
|
$aname = substr($name, 0, $pos);
|
||
|
} else {
|
||
|
$pos = strpos($name, '\\');
|
||
|
if ($pos) {
|
||
|
$dname = substr($name, 0, $pos);
|
||
|
$aname = substr($name, $pos + 1);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $acctname The name of the account
|
||
|
* @return string The DN of the specified account
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
protected function _getAccountDn($acctname)
|
||
|
{
|
||
|
/**
|
||
|
* @see Zend_Ldap_Dn
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Dn.php';
|
||
|
if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname;
|
||
|
$acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
|
||
|
$acct = $this->_getAccount($acctname, array('dn'));
|
||
|
return $acct['dn'];
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $dname The domain name to check
|
||
|
* @return boolean
|
||
|
*/
|
||
|
protected function _isPossibleAuthority($dname)
|
||
|
{
|
||
|
if ($dname === null) {
|
||
|
return true;
|
||
|
}
|
||
|
$accountDomainName = $this->_getAccountDomainName();
|
||
|
$accountDomainNameShort = $this->_getAccountDomainNameShort();
|
||
|
if ($accountDomainName === null && $accountDomainNameShort === null) {
|
||
|
return true;
|
||
|
}
|
||
|
if (strcasecmp($dname, $accountDomainName) == 0) {
|
||
|
return true;
|
||
|
}
|
||
|
if (strcasecmp($dname, $accountDomainNameShort) == 0) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $acctname The name to canonicalize
|
||
|
* @param int $type The desired form of canonicalization
|
||
|
* @return string The canonicalized name in the desired form
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getCanonicalAccountName($acctname, $form = 0)
|
||
|
{
|
||
|
$this->_splitName($acctname, $dname, $uname);
|
||
|
|
||
|
if (!$this->_isPossibleAuthority($dname)) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
"Binding domain is not an authority for user: $acctname",
|
||
|
Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
|
||
|
}
|
||
|
|
||
|
if (!$uname) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
|
||
|
}
|
||
|
|
||
|
if (function_exists('mb_strtolower')) {
|
||
|
$uname = mb_strtolower($uname, 'UTF-8');
|
||
|
} else {
|
||
|
$uname = strtolower($uname);
|
||
|
}
|
||
|
|
||
|
if ($form === 0) {
|
||
|
$form = $this->_getAccountCanonicalForm();
|
||
|
}
|
||
|
|
||
|
switch ($form) {
|
||
|
case Zend_Ldap::ACCTNAME_FORM_DN:
|
||
|
return $this->_getAccountDn($acctname);
|
||
|
case Zend_Ldap::ACCTNAME_FORM_USERNAME:
|
||
|
return $uname;
|
||
|
case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
|
||
|
$accountDomainNameShort = $this->_getAccountDomainNameShort();
|
||
|
if (!$accountDomainNameShort) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
|
||
|
}
|
||
|
return "$accountDomainNameShort\\$uname";
|
||
|
case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
|
||
|
$accountDomainName = $this->_getAccountDomainName();
|
||
|
if (!$accountDomainName) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
|
||
|
}
|
||
|
return "$uname@$accountDomainName";
|
||
|
default:
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param array $attrs An array of names of desired attributes
|
||
|
* @return array An array of the attributes representing the account
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
protected function _getAccount($acctname, array $attrs = null)
|
||
|
{
|
||
|
$baseDn = $this->getBaseDn();
|
||
|
if (!$baseDn) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Base DN not set');
|
||
|
}
|
||
|
|
||
|
$accountFilter = $this->_getAccountFilter($acctname);
|
||
|
if (!$accountFilter) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Invalid account filter');
|
||
|
}
|
||
|
|
||
|
if (!is_resource($this->getResource())) {
|
||
|
$this->bind();
|
||
|
}
|
||
|
|
||
|
$accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
|
||
|
$count = $accounts->count();
|
||
|
if ($count === 1) {
|
||
|
$acct = $accounts->getFirst();
|
||
|
$accounts->close();
|
||
|
return $acct;
|
||
|
} else if ($count === 0) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
|
||
|
$str = "No object found for: $accountFilter";
|
||
|
} else {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
|
||
|
$str = "Unexpected result count ($count) for: $accountFilter";
|
||
|
}
|
||
|
$accounts->close();
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, $str, $code);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
*/
|
||
|
public function disconnect()
|
||
|
{
|
||
|
if (is_resource($this->getResource())) {
|
||
|
if (!extension_loaded('ldap')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
|
||
|
Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
|
||
|
}
|
||
|
@ldap_unbind($this->getResource());
|
||
|
}
|
||
|
$this->_resource = null;
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $host The hostname of the LDAP server to connect to
|
||
|
* @param int $port The port number of the LDAP server to connect to
|
||
|
* @param boolean $useSsl Use SSL
|
||
|
* @param boolean $useStartTls Use STARTTLS
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
|
||
|
{
|
||
|
if ($host === null) {
|
||
|
$host = $this->_getHost();
|
||
|
}
|
||
|
if ($port === null) {
|
||
|
$port = $this->_getPort();
|
||
|
} else {
|
||
|
$port = (int)$port;
|
||
|
}
|
||
|
if ($useSsl === null) {
|
||
|
$useSsl = $this->_getUseSsl();
|
||
|
} else {
|
||
|
$useSsl = (bool)$useSsl;
|
||
|
}
|
||
|
if ($useStartTls === null) {
|
||
|
$useStartTls = $this->_getUseStartTls();
|
||
|
} else {
|
||
|
$useStartTls = (bool)$useStartTls;
|
||
|
}
|
||
|
|
||
|
if (!$host) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'A host parameter is required');
|
||
|
}
|
||
|
|
||
|
/* To connect using SSL it seems the client tries to verify the server
|
||
|
* certificate by default. One way to disable this behavior is to set
|
||
|
* 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
|
||
|
* if you really care about the server's cert you can put a cert on the
|
||
|
* web server.
|
||
|
*/
|
||
|
$url = ($useSsl) ? "ldaps://$host" : "ldap://$host";
|
||
|
if ($port) {
|
||
|
$url .= ":$port";
|
||
|
}
|
||
|
|
||
|
/* Because ldap_connect doesn't really try to connect, any connect error
|
||
|
* will actually occur during the ldap_bind call. Therefore, we save the
|
||
|
* connect string here for reporting it in error handling in bind().
|
||
|
*/
|
||
|
$this->_connectString = $url;
|
||
|
|
||
|
$this->disconnect();
|
||
|
|
||
|
if (!extension_loaded('ldap')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
|
||
|
Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
|
||
|
}
|
||
|
|
||
|
/* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
|
||
|
* use the old form.
|
||
|
*/
|
||
|
$resource = ($useSsl) ? @ldap_connect($url) : @ldap_connect($host, $port);
|
||
|
|
||
|
if (is_resource($resource) === true) {
|
||
|
$this->_resource = $resource;
|
||
|
|
||
|
$optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
|
||
|
if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
|
||
|
@ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
|
||
|
if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
|
||
|
return $this;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$zle = new Zend_Ldap_Exception($this, "$host:$port");
|
||
|
$this->disconnect();
|
||
|
throw $zle;
|
||
|
}
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param string $username The username for authenticating the bind
|
||
|
* @param string $password The password for authenticating the bind
|
||
|
* @return Zend_Ldap Provides a fluent interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function bind($username = null, $password = null)
|
||
|
{
|
||
|
$moreCreds = true;
|
||
|
|
||
|
if ($username === null) {
|
||
|
$username = $this->_getUsername();
|
||
|
$password = $this->_getPassword();
|
||
|
$moreCreds = false;
|
||
|
}
|
||
|
|
||
|
if ($username === null) {
|
||
|
/* Perform anonymous bind
|
||
|
*/
|
||
|
$password = null;
|
||
|
} else {
|
||
|
/* Check to make sure the username is in DN form.
|
||
|
*/
|
||
|
/**
|
||
|
* @see Zend_Ldap_Dn
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Dn.php';
|
||
|
if (!Zend_Ldap_Dn::checkDn($username)) {
|
||
|
if ($this->_getBindRequiresDn()) {
|
||
|
/* moreCreds stops an infinite loop if _getUsername does not
|
||
|
* return a DN and the bind requires it
|
||
|
*/
|
||
|
if ($moreCreds) {
|
||
|
try {
|
||
|
$username = $this->_getAccountDn($username);
|
||
|
} catch (Zend_Ldap_Exception $zle) {
|
||
|
switch ($zle->getCode()) {
|
||
|
case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
|
||
|
case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
|
||
|
case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
|
||
|
throw $zle;
|
||
|
}
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
'Failed to retrieve DN for account: ' . $username .
|
||
|
' [' . $zle->getMessage() . ']',
|
||
|
Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
|
||
|
}
|
||
|
} else {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
|
||
|
}
|
||
|
} else {
|
||
|
$username = $this->getCanonicalAccountName($username,
|
||
|
$this->_getAccountCanonicalForm());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!is_resource($this->getResource())) {
|
||
|
$this->connect();
|
||
|
}
|
||
|
|
||
|
if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
$zle = new Zend_Ldap_Exception(null,
|
||
|
'Empty password not allowed - see allowEmptyPassword option.');
|
||
|
} else {
|
||
|
if (@ldap_bind($this->getResource(), $username, $password)) {
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
$message = ($username === null) ? $this->_connectString : $username;
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
switch ($this->getLastErrorCode()) {
|
||
|
case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
|
||
|
/* If the error is related to establishing a connection rather than binding,
|
||
|
* the connect string is more informative than the username.
|
||
|
*/
|
||
|
$message = $this->_connectString;
|
||
|
}
|
||
|
|
||
|
$zle = new Zend_Ldap_Exception($this, $message);
|
||
|
}
|
||
|
$this->disconnect();
|
||
|
throw $zle;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A global LDAP search routine for finding information.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Filter_Abstract $filter
|
||
|
* @param string|Zend_Ldap_Dn|null $basedn
|
||
|
* @param integer $scope
|
||
|
* @param array $attributes
|
||
|
* @param string|null $sort
|
||
|
* @param string|null $collectionClass
|
||
|
* @return Zend_Ldap_Collection
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
|
||
|
array $attributes = array(), $sort = null, $collectionClass = null)
|
||
|
{
|
||
|
if ($basedn === null) {
|
||
|
$basedn = $this->getBaseDn();
|
||
|
}
|
||
|
else if ($basedn instanceof Zend_Ldap_Dn) {
|
||
|
$basedn = $basedn->toString();
|
||
|
}
|
||
|
|
||
|
if ($filter instanceof Zend_Ldap_Filter_Abstract) {
|
||
|
$filter = $filter->toString();
|
||
|
}
|
||
|
|
||
|
switch ($scope) {
|
||
|
case self::SEARCH_SCOPE_ONE:
|
||
|
$search = @ldap_list($this->getResource(), $basedn, $filter, $attributes);
|
||
|
break;
|
||
|
case self::SEARCH_SCOPE_BASE:
|
||
|
$search = @ldap_read($this->getResource(), $basedn, $filter, $attributes);
|
||
|
break;
|
||
|
case self::SEARCH_SCOPE_SUB:
|
||
|
default:
|
||
|
$search = @ldap_search($this->getResource(), $basedn, $filter, $attributes);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if($search === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
|
||
|
}
|
||
|
if (!is_null($sort) && is_string($sort)) {
|
||
|
$isSorted = @ldap_sort($this->getResource(), $search, $sort);
|
||
|
if($search === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Zend_Ldap_Collection_Iterator_Default
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Collection/Iterator/Default.php';
|
||
|
$iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
|
||
|
if ($collectionClass === null) {
|
||
|
/**
|
||
|
* Zend_Ldap_Collection
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Collection.php';
|
||
|
return new Zend_Ldap_Collection($iterator);
|
||
|
} else {
|
||
|
$collectionClass = (string)$collectionClass;
|
||
|
if (!class_exists($collectionClass)) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
"Class '$collectionClass' can not be found");
|
||
|
}
|
||
|
if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception(null,
|
||
|
"Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
|
||
|
}
|
||
|
return new $collectionClass($iterator);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Count items found by given filter.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Filter_Abstract $filter
|
||
|
* @param string|Zend_Ldap_Dn|null $basedn
|
||
|
* @param integer $scope
|
||
|
* @return integer
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
|
||
|
{
|
||
|
try {
|
||
|
$result = $this->search($filter, $basedn, $scope, array('dn'), null);
|
||
|
} catch (Zend_Ldap_Exception $e) {
|
||
|
if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
|
||
|
else throw $e;
|
||
|
}
|
||
|
return $result->count();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Count children for a given DN.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @return integer
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function countChildren($dn)
|
||
|
{
|
||
|
return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check if a given DN exists.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @return boolean
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function exists($dn)
|
||
|
{
|
||
|
return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Search LDAP registry for entries matching filter and optional attributes
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Filter_Abstract $filter
|
||
|
* @param string|Zend_Ldap_Dn|null $basedn
|
||
|
* @param integer $scope
|
||
|
* @param array $attributes
|
||
|
* @param string|null $sort
|
||
|
* @return array
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
|
||
|
array $attributes = array(), $sort = null)
|
||
|
{
|
||
|
$result = $this->search($filter, $basedn, $scope, $attributes, $sort);
|
||
|
return $result->toArray();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get LDAP entry by DN
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @param array $attributes
|
||
|
* @param boolean $throwOnNotFound
|
||
|
* @return array
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
|
||
|
{
|
||
|
try {
|
||
|
$result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
|
||
|
$attributes, null);
|
||
|
return $result->getFirst();
|
||
|
} catch (Zend_Ldap_Exception $e){
|
||
|
if ($throwOnNotFound !== false) throw $e;
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Prepares an ldap data entry array for insert/update operation
|
||
|
*
|
||
|
* @param array $entry
|
||
|
* @return void
|
||
|
* @throws InvalidArgumentException
|
||
|
*/
|
||
|
public static function prepareLdapEntryArray(array &$entry)
|
||
|
{
|
||
|
if (array_key_exists('dn', $entry)) unset($entry['dn']);
|
||
|
foreach ($entry as $key => $value) {
|
||
|
if (is_array($value)) {
|
||
|
foreach ($value as $i => $v) {
|
||
|
if (is_null($v)) unset($value[$i]);
|
||
|
else if (!is_scalar($v)) {
|
||
|
throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
|
||
|
} else {
|
||
|
$v = (string)$v;
|
||
|
if (strlen($v) == 0) {
|
||
|
unset($value[$i]);
|
||
|
} else {
|
||
|
$value[$i] = $v;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$entry[$key] = array_values($value);
|
||
|
} else {
|
||
|
if (is_null($value)) $entry[$key] = array();
|
||
|
else if (!is_scalar($value)) {
|
||
|
throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
|
||
|
} else {
|
||
|
$value = (string)$value;
|
||
|
if (strlen($value) == 0) {
|
||
|
$entry[$key] = array();
|
||
|
} else {
|
||
|
$entry[$key] = array($value);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$entry = array_change_key_case($entry, CASE_LOWER);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add new information to the LDAP repository
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @param array $entry
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function add($dn, array $entry)
|
||
|
{
|
||
|
if (!($dn instanceof Zend_Ldap_Dn)) {
|
||
|
$dn = Zend_Ldap_Dn::factory($dn, null);
|
||
|
}
|
||
|
self::prepareLdapEntryArray($entry);
|
||
|
foreach ($entry as $key => $value) {
|
||
|
if (is_array($value) && count($value) === 0) {
|
||
|
unset($entry[$key]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
|
||
|
foreach ($rdnParts as $key => $value) {
|
||
|
$value = Zend_Ldap_Dn::unescapeValue($value);
|
||
|
if (!array_key_exists($key, $entry) ||
|
||
|
!in_array($value, $entry[$key]) ||
|
||
|
count($entry[$key]) !== 1) {
|
||
|
$entry[$key] = array($value);
|
||
|
}
|
||
|
}
|
||
|
$adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
|
||
|
'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
|
||
|
foreach ($adAttributes as $attr) {
|
||
|
if (array_key_exists($attr, $entry)) {
|
||
|
unset($entry[$attr]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
$isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
|
||
|
if($isAdded === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Update LDAP registry
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @param array $entry
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function update($dn, array $entry)
|
||
|
{
|
||
|
if (!($dn instanceof Zend_Ldap_Dn)) {
|
||
|
$dn = Zend_Ldap_Dn::factory($dn, null);
|
||
|
}
|
||
|
self::prepareLdapEntryArray($entry);
|
||
|
|
||
|
$rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
|
||
|
$adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
|
||
|
'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
|
||
|
$stripAttributes = array_merge(array_keys($rdnParts), $adAttributes);
|
||
|
foreach ($stripAttributes as $attr) {
|
||
|
if (array_key_exists($attr, $entry)) {
|
||
|
unset($entry[$attr]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (count($entry) > 0) {
|
||
|
$isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
|
||
|
if($isModified === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
|
||
|
}
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Save entry to LDAP registry.
|
||
|
*
|
||
|
* Internally decides if entry will be updated to added by calling
|
||
|
* {@link exists()}.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @param array $entry
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function save($dn, array $entry)
|
||
|
{
|
||
|
if ($dn instanceof Zend_Ldap_Dn) {
|
||
|
$dn = $dn->toString();
|
||
|
}
|
||
|
if ($this->exists($dn)) $this->update($dn, $entry);
|
||
|
else $this->add($dn, $entry);
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Delete an LDAP entry
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @param boolean $recursively
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function delete($dn, $recursively = false)
|
||
|
{
|
||
|
if ($dn instanceof Zend_Ldap_Dn) {
|
||
|
$dn = $dn->toString();
|
||
|
}
|
||
|
if ($recursively === true) {
|
||
|
if ($this->countChildren($dn)>0) {
|
||
|
$children = $this->_getChildrenDns($dn);
|
||
|
foreach ($children as $c) {
|
||
|
$this->delete($c, true);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
$isDeleted = @ldap_delete($this->getResource(), $dn);
|
||
|
if($isDeleted === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retrieve the immediate children DNs of the given $parentDn
|
||
|
*
|
||
|
* This method is used in recursive methods like {@see delete()}
|
||
|
* or {@see copy()}
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $parentDn
|
||
|
* @return array of DNs
|
||
|
*/
|
||
|
protected function _getChildrenDns($parentDn)
|
||
|
{
|
||
|
if ($parentDn instanceof Zend_Ldap_Dn) {
|
||
|
$parentDn = $parentDn->toString();
|
||
|
}
|
||
|
$children = array();
|
||
|
$search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
|
||
|
for ($entry = @ldap_first_entry($this->getResource(), $search);
|
||
|
$entry !== false;
|
||
|
$entry = @ldap_next_entry($this->getResource(), $entry)) {
|
||
|
$childDn = @ldap_get_dn($this->getResource(), $entry);
|
||
|
if ($childDn === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'getting dn');
|
||
|
}
|
||
|
$children[] = $childDn;
|
||
|
}
|
||
|
@ldap_free_result($search);
|
||
|
return $children;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Moves a LDAP entry from one DN to another subtree.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $from
|
||
|
* @param string|Zend_Ldap_Dn $to
|
||
|
* @param boolean $recursively
|
||
|
* @param boolean $alwaysEmulate
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
|
||
|
{
|
||
|
if ($from instanceof Zend_Ldap_Dn) {
|
||
|
$orgDnParts = $from->toArray();
|
||
|
} else {
|
||
|
$orgDnParts = Zend_Ldap_Dn::explodeDn($from);
|
||
|
}
|
||
|
|
||
|
if ($to instanceof Zend_Ldap_Dn) {
|
||
|
$newParentDnParts = $to->toArray();
|
||
|
} else {
|
||
|
$newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
|
||
|
}
|
||
|
|
||
|
$newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
|
||
|
$newDn = Zend_Ldap_Dn::fromArray($newDnParts);
|
||
|
return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Moves a LDAP entry from one DN to another DN.
|
||
|
*
|
||
|
* This is an alias for {@link rename()}
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $from
|
||
|
* @param string|Zend_Ldap_Dn $to
|
||
|
* @param boolean $recursively
|
||
|
* @param boolean $alwaysEmulate
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function move($from, $to, $recursively = false, $alwaysEmulate = false)
|
||
|
{
|
||
|
return $this->rename($from, $to, $recursively, $alwaysEmulate);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Renames a LDAP entry from one DN to another DN.
|
||
|
*
|
||
|
* This method implicitely moves the entry to another location within the tree.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $from
|
||
|
* @param string|Zend_Ldap_Dn $to
|
||
|
* @param boolean $recursively
|
||
|
* @param boolean $alwaysEmulate
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
|
||
|
{
|
||
|
$emulate = (bool)$alwaysEmulate;
|
||
|
if (!function_exists('ldap_rename')) $emulate = true;
|
||
|
else if ($recursively) $emulate = true;
|
||
|
|
||
|
if ($emulate === false) {
|
||
|
if ($from instanceof Zend_Ldap_Dn) {
|
||
|
$from = $from->toString();
|
||
|
}
|
||
|
|
||
|
if ($to instanceof Zend_Ldap_Dn) {
|
||
|
$newDnParts = $to->toArray();
|
||
|
} else {
|
||
|
$newDnParts = Zend_Ldap_Dn::explodeDn($to);
|
||
|
}
|
||
|
|
||
|
$newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
|
||
|
$newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
|
||
|
$isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
|
||
|
if($isOK === false) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Exception
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Exception.php';
|
||
|
throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
|
||
|
}
|
||
|
else if (!$this->exists($to)) $emulate = true;
|
||
|
}
|
||
|
if ($emulate) {
|
||
|
$this->copy($from, $to, $recursively);
|
||
|
$this->delete($from, $recursively);
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copies a LDAP entry from one DN to another subtree.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $from
|
||
|
* @param string|Zend_Ldap_Dn $to
|
||
|
* @param boolean $recursively
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function copyToSubtree($from, $to, $recursively = false)
|
||
|
{
|
||
|
if ($from instanceof Zend_Ldap_Dn) {
|
||
|
$orgDnParts = $from->toArray();
|
||
|
} else {
|
||
|
$orgDnParts = Zend_Ldap_Dn::explodeDn($from);
|
||
|
}
|
||
|
|
||
|
if ($to instanceof Zend_Ldap_Dn) {
|
||
|
$newParentDnParts = $to->toArray();
|
||
|
} else {
|
||
|
$newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
|
||
|
}
|
||
|
|
||
|
$newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
|
||
|
$newDn = Zend_Ldap_Dn::fromArray($newDnParts);
|
||
|
return $this->copy($from, $newDn, $recursively);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Copies a LDAP entry from one DN to another DN.
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $from
|
||
|
* @param string|Zend_Ldap_Dn $to
|
||
|
* @param boolean $recursively
|
||
|
* @return Zend_Ldap Provides a fluid interface
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function copy($from, $to, $recursively = false)
|
||
|
{
|
||
|
$entry = $this->getEntry($from, array(), true);
|
||
|
|
||
|
if ($to instanceof Zend_Ldap_Dn) {
|
||
|
$toDnParts = $to->toArray();
|
||
|
} else {
|
||
|
$toDnParts = Zend_Ldap_Dn::explodeDn($to);
|
||
|
}
|
||
|
$this->add($to, $entry);
|
||
|
|
||
|
if ($recursively === true && $this->countChildren($from)>0) {
|
||
|
$children = $this->_getChildrenDns($from);
|
||
|
foreach ($children as $c) {
|
||
|
$cDnParts = Zend_Ldap_Dn::explodeDn($c);
|
||
|
$newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
|
||
|
$newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
|
||
|
$this->copy($c, $newChild, true);
|
||
|
}
|
||
|
}
|
||
|
return $this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the specified DN as a Zend_Ldap_Node
|
||
|
*
|
||
|
* @param string|Zend_Ldap_Dn $dn
|
||
|
* @return Zend_Ldap_Node
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getNode($dn)
|
||
|
{
|
||
|
/**
|
||
|
* Zend_Ldap_Node
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Node.php';
|
||
|
return Zend_Ldap_Node::fromLdap($dn, $this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the base node as a Zend_Ldap_Node
|
||
|
*
|
||
|
* @return Zend_Ldap_Node
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getBaseNode()
|
||
|
{
|
||
|
return $this->getNode($this->getBaseDn(), $this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the RootDSE
|
||
|
*
|
||
|
* @return Zend_Ldap_Node_RootDse
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getRootDse()
|
||
|
{
|
||
|
if ($this->_rootDse === null) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Node_Schema
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Node/RootDse.php';
|
||
|
$this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
|
||
|
}
|
||
|
return $this->_rootDse;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the schema
|
||
|
*
|
||
|
* @return Zend_Ldap_Node_Schema
|
||
|
* @throws Zend_Ldap_Exception
|
||
|
*/
|
||
|
public function getSchema()
|
||
|
{
|
||
|
if ($this->_schema === null) {
|
||
|
/**
|
||
|
* @see Zend_Ldap_Node_Schema
|
||
|
*/
|
||
|
require_once 'Zend/Ldap/Node/Schema.php';
|
||
|
$this->_schema = Zend_Ldap_Node_Schema::create($this);
|
||
|
}
|
||
|
return $this->_schema;
|
||
|
}
|
||
|
}
|