package.xml100644 1750 1750 52037 11223350110 6430 Net_LDAP pear.php.net Object oriented interface for searching and manipulating LDAP-entries Net_LDAP is a clone of Perls Net::LDAP object interface to directory servers. It does contain most of Net::LDAPs features but has some own too. With Net_LDAP you have: * A simple object-oriented interface to connections, searches entries and filters. * Support for tls and ldap v3. * Simple modification, deletion and creation of ldap entries. * Support for schema handling. Net_LDAP layers itself on top of PHP's existing ldap extensions. Tarjei Huse tarjei tarjei@nu.no no Jan Wagner jw wagner@netsols.de no Benedikt Hallinger beni beni@php.net yes 2009-07-03 1.1.5 1.1.2 stable stable LGPL License * Ported the fix (RFC-1777) in connect() from Net_LDAP2 v2.0.3 4.2 1.5.0 0.1 0.1 beta beta 2003-06-23 LGPL License Initial release 0.2 0.2 beta beta 2003-08-23 LGPL License Fixed a lot of bugs that jumped in during the pearification process 0.3 0.3 beta beta 2003-09-21 LGPL License More bug squashing! Much better errorhandling in the -search() function. Also, all errors that create a Pear_error now includes the errornumber if appropriate (i.e. it was an ldap generated error). 0.4 0.4 beta beta 2003-10-01 LGPL License Many more bugfixes. Jan Wagner fixed the shift_entry function. Also a new Net_LDAP_Entry::modify function has been added that goes far making a simple way to modify entries. 0.5 0.5 beta beta 2003-10-11 LGPL License Jan Wagner Contributed a new RootDSE object and a Schema object and some fixes to the Net_LDAP::search() method The new Net_ldap_entry::modify() method seems to work very nice now. 0.6 0.6 beta beta 2003-10-17 LGPL License New Net_LDAP::ArrayUTF8Decode and Net_LDAP::ArrayUTF8Encode functions. These are used by the Net_LDAP::Entry objects to ensure that things work ok. 0.6.3 0.6.3 beta beta 2003-11-12 LGPL License It seems that 0.6.2 was out too fast. So this is mainly a bugfix release: - Removed remaining Net_LDAP::UTF8Encode and Net_LDAP::UTF8Decode calls in Net_LDAP_Entry, which stopped attributes() and get_entry() from working - The UTF8 functions somehow got outside the Net_LDAP class ... FIXED. - The usuage example of the last release was wrong. We decided to move UTF8 handling into Net_LDAP. Handling should be done this way: $attr = $ldap-utf8Encode($attr); $entry-modify($attr); $attr = $ldap-utf8Decode( $entry-attributes() ); - This means Net_LDAP_Util is useless right now, but will be extended in the future. - Jan did a complete overhaul of the phpdoc stuff. Everything seems to be fine now with phpDocumentor. 2007-02-05 0.7.0 0.7.0 beta beta LGPL License This long awaited release of Net_LDAP features more stability and new functionality. The main changes are: - Rewrite of much of the code (including some api changes!) - LOTS of fixed bugs! - New class for easy filter handling (Net_LDAP_Filter) - Sorting support for searchresults (including multivalued sorting!) - Searched Entries can now be fetched as_struct() (array)! - Some memory optimizations Please note also that Net_LDAPs configuration changed slightly. Please see $_config in LDAP.php for the new parameters. 2007-02-23 0.7.1 0.7.0 beta beta LGPL License This is not just a bugfix release of 0.7.0 but also introduces some internal optimisations: - Fixed a connection bug whith LDAP V3 only servers - clearer sanitizing of the host config parameter 2007-05-07 0.7.2 0.7.2 beta beta LGPL License This release features some internal code movements to be more compatible to PERL::Net_LDAP. The movements include: * Removed UTF8 en-/decoding stuff from Net_LDAP_Utils class since this was moved to Net_LDAP class in 0.6.6 * Moved Filter encoding from Net_LDAP_Filter to Net_LDAP_Util * Moved ldap_explode_dn_escaped() from Net_LDAP_Entry to Net_LDAP_Util * Added perls functions from Net_LDAP::Util to our Util class, but they still need some work Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Entry anymore. Additionally some new functionality has been introduced: * You can now apply regular expressions directly to a entrys attributes and don't need to fetch the attribute values manually. * Net_LDAP_Schema can check if a attributes syntax is binary The following bugs have been resolved: * Connections to LDAP servers that forbid anonymous binds are possible again * The JPEG attribute is now properly returned as binary value instead of string * If the array describing selected attributes in searches didn't contain consecutive keys, there was a problem sometimes * Some PHP5 return issues 2007-06-12 0.7.3 0.7.2 beta beta LGPL License This release introduces some example files showing you in detail how to work with Net_LDAP. Additionally, a bug at recursive deletion of an entry is fixed and the Net_LDAP_Filter class is slightly optimized. 2007-06-20 1.0.0RC1 1.0.0RC1 beta beta LGPL License Again some small Bugfixes, most notably a bug within $ldap->modify() that occured when using the combined 'changes' array. Besides that, $search->popEntry() and the corresponding alias pop_entry() has been implemented. Net_LDAP_Util::unescape_filter_value() is available too now and Net_LDAP_Util::escape_filter_value() can handle ASCII chars smaller than 32. Above that, Net_LDAP_Util::canonical_dn() has been fully implemented. A new method createFresh() was added to Net_LDAP_Entry, so creation of initial entries is more standardized and clearer. A new example is available, describing the $ldap->modify() method. The add_entry.php example was updated, it shows the use of Net_LDAP_Entry::createFresh(). $ldap->add() links unlinked entries now to the connection used for the add. Some new additional utility functions are available in Net_LDAP_Util to assist you in handling attributes and dns. The LDAP-Rename command now uses this functions to deal with DN escaping issues. Please note that ldap_explode_dn_escaped() is not available from Net_LDAP_Util anymore; it got superseeded by Net_LDAP_Util::ldap_explode_dn(). 2007-06-28 1.0.0RC2 1.0.0RC2 beta beta LGPL License Net_LDAP->dnExists() uses the Util class now, which makes it safer. A new move() method is available from Net_LDAP. Please note, that the copy() method was removed from the Net_LDAP_Entry class since people would expect attribute moving because of the overall API of Net_LDAP. Instead use the more failsafer copy() from Net_LDAP. 2007-07-24 1.0.0RC3 1.0.0RC3 beta beta LGPL License Fixed a bug with dnExists() that was caused mainly by bad behavior of Net_LDAP_UTIL::ldap_explode_dn(). Fixed a bug with call time pass-by-reference if calling $entry->update(); however this inflicted a API change: The parameter $ldap is not available anymore, you need to use $entry->setLDAP() prior update now if you want to change the LDAP object. This brought us a more logical API now, since Entry operations should be performed by the Net_LDAP object. 2007-09-18 1.0.0RC4 1.0.0RC4 beta beta LGPL License - Fixed some minor bugs of RC3 - Reintroduced $ldap parameter for $entry-update(), but it is not prefferred to use this way. The Parameter is there for perl interface compatibility 2007-10-29 1.0.0 1.0.0 stable stable LGPL License After more than four years of development, we are very proud to announce the ~ FIRST STABLE Net_LDAP RELEASE 1.0.0 ~ Net_LDAP ist tested now and should be stable enough for production use. The API is finished so far, no changes should be neccessary in the future. Changes to Release candidate 4: - Implemented PHPUnit tests - Fixed some minor bugs of RC4 (including the schema loading warning-generation) - Fixed several bugs in Net_LDAP_Util - Improved Net_LDAP_Filter and Net_LDAP_Util error handling and code cleanness - Completely implemented Net_LDAP_Filter perl interface - Improved several doc comments and fixed some spelling errors 2008-01-14 1.1.0a1 1.1.0a1 beta beta LGPL License * Added LDIF reading and writing support * Fixed minor issues of 1.0.0 release 2008-01-21 1.1.0a2 1.1.0a2 beta beta LGPL License * Added parseLines() to Net_LDAP_LDIF for more convinience * Added some handy methods to Net_LDAP_Entry * Enhanced tests 2008-02-27 1.1.0 1.1.0 stable stable LGPL License * Fixed a little bug at cross directory move * Fixed a bug when deleting a subtree containing several subentries that failed if one called dnExists() prior calling delete() * Fixed some minor bugs at NeT_LDAP->move() and Net_LDAP->dnExists() * Added Net_LDAP tests * Changed API of Net_LDAP->copy() to only accept Net_LDAP_Entry objects, because with DNs Attribute values will be lost /!\ This is the last release of Net_LDAP supporting PHP4 /!\ 2008-03-19 1.1.1 1.1.1 stable stable LGPL License This is just a bugfix release. Net_LDAP is superseeded by Net_LDAP2, please use Net_LDAP2 unless you are forced to use PHP4. * Fixed a problem with Net_LDAP_LDIF and files with DOS line endings 2008-06-04 1.1.2 1.1.2 stable stable LGPL License * Backportet a patch from Net_LDAP2. Schema->isBinary() did not checked attribute supertypes * Backported a patch for Schema->isBinary() bug if unknown attribute type is requested * Bugfix in filter class for approx matching 2008-10-23 1.1.3 1.1.2 stable stable LGPL License * Fixed a little issue with repetive adding the same attribute value (backported from Net_LDAP2) * Fixed issue with repetitve adding or deleting values causing Net_LDAP to send the same change multiple times 2009-04-23 1.1.4 1.1.2 stable stable LGPL License * Ported the connect() method from Net_LDAP2 since it fixes some connection issues Net_LDAP-1.1.5/LDAP/Entry.php100644 1750 1750 72132 11223350110 10631 * @author Jan Wagner * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Entry.php,v 1.58 2008/11/03 14:06:27 beni Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; require_once 'Util.php'; /** * Object representation of a directory entry * * This class represents a directory entry. You can add, delete, replace * attributes and their values, rename the entry, delete the entry. * * @category Net * @package Net_LDAP * @author Jan Wagner * @author Tarjej Huse * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Entry extends PEAR { /** * Entry ressource identifier * * @access private * @var ressourcee */ var $_entry = null; /** * LDAP ressource identifier * * @access private * @var ressource */ var $_link = null; /** * Net_LDAP object * * This object will be used for updating and schema checking * * @access private * @var object Net_LDAP */ var $_ldap = null; /** * Distinguished name of the entry * * @access private * @var string */ var $_dn = null; /** * Attributes * * @access private * @var array */ var $_attributes = array(); /** * Original attributes before any modification * * @access private * @var array */ var $_original = array(); /** * Map of attribute names * * @access private * @var array */ var $_map = array(); /** * Is this a new entry? * * @access private * @var boolean */ var $_new = true; /** * New distinguished name * * @access private * @var string */ var $_newdn = null; /** * Shall the entry be deleted? * * @access private * @var boolean */ var $_delete = false; /** * Map with changes to the entry * * @access private * @var array */ var $_changes = array("add" => array(), "delete" => array(), "replace" => array() ); /** * Internal Constructor * * Constructor of the entry. Sets up the distinguished name and the entries * attributes. * You should not call this method manually! Use {@link Net_LDAP_Entry::createFresh()} instead! * * @param Net_LDAP|ressource|array &$ldap Net_LDAP object, ldap-link ressource or array of attributes * @param string|ressource $entry Either a DN or a LDAP-Entry ressource * * @access protected * @return none */ function Net_LDAP_Entry(&$ldap, $entry = null) { $this->PEAR('Net_LDAP_Error'); if (is_resource($entry)) { $this->_entry = &$entry; } else { $this->_dn = $entry; } if (is_a($ldap, 'Net_LDAP')) { $this->_ldap = &$ldap; $this->_link = $ldap->getLink(); } elseif (is_resource($ldap)) { $this->_link = $ldap; } elseif (is_array($ldap)) { $this->_setAttributes($ldap); // setup attrs manually } if (is_resource($this->_entry) && is_resource($this->_link)) { $this->_new = false; $this->_dn = @ldap_get_dn($this->_link, $this->_entry); $this->_setAttributes(); // fetch attributes from server } } /** * Creates a fresh entry that may be added to the directory later on * * Use this method, if you want to initialize a fresh entry. * * The method should be called statically: $entry = Net_LDAP_Entry::createFresh(); * You should put a 'objectClass' attribute into the $attrs so the directory server * knows which object you want to create. However, you may omit this in case you * don't want to add this entry to a directory server. * * The attributes parameter is as following: * * $attrs = array( 'attribute1' => array('value1', 'value2'), * 'attribute2' => 'single value' * ); * * * @param string $dn DN of the Entry * @param array $attrs Attributes of the entry * * @static * @return Net_LDAP_Entry */ function createFresh($dn, $attrs = array()) { if (!is_array($attrs)) { return PEAR::raiseError("Unable to create fresh entry: Parameter \$attrs needs to be an array!"); } $entry = new Net_LDAP_Entry($attrs, $dn); return $entry; } /** * Get or set the distinguished name of the entry * * If called without an argument the current (or the new DN if set) DN gets returned. * If you provide an DN, this entry is moved to the new location specified if a DN existed. * If the DN was not set, the DN gets initialized. Call {@link update()} to actually create * the new Entry in the directory. * To fetch the current active DN after setting a new DN but before an update(), you can use * {@link currentDN()} to retrieve the DN that is currently active. * * Please note that special characters (eg german umlauts) should be encoded using utf8_encode(). * You may use {@link Net_LDAP_Util::canonical_dn()} for properly encoding of the DN. * * @param string $dn New distinguished name * * @access public * @return string|true Distinguished name (or true if a new DN was provided) */ function dn($dn = null) { if (false == is_null($dn)) { if (is_null($this->_dn)) { $this->_dn = $dn; } else { $this->_newdn = $dn; } return true; } return (isset($this->_newdn) ? $this->_newdn : $this->currentDN()); } /** * Renames or moves the entry * * This is just a convinience alias to {@link dn()} * to make your code more meaningful. * * @param string $newdn The new DN * @return true */ function move($newdn) { return $this->dn($newdn); } /** * Sets the internal attributes array * * This fetches the values for the attributes from the server. * The attribute Syntax will be checked so binary attributes will be returned * as binary values. * * Attributes may be passed directly via the $attributes parameter to setup this * entry manually. This overrides attribute fetching from the server. * * @param array $attributes Attributes to set for this entry * * @access private * @return void */ function _setAttributes($attributes = null) { /* * fetch attributes from the server */ if (is_null($attributes) && is_resource($this->_entry) && is_resource($this->_link)) { // fetch schema if (is_a($this->_ldap, 'Net_LDAP')) { $schema =& $this->_ldap->schema(); } // fetch attributes $attributes = array(); do { if (empty($attr)) { $ber = null; $attr = @ldap_first_attribute($this->_link, $this->_entry, $ber); } else { $attr = @ldap_next_attribute($this->_link, $this->_entry, $ber); } if ($attr) { $func = 'ldap_get_values'; // standard function to fetch value // Try to get binary values as binary data if (is_a($schema, 'Net_LDAP_Schema')) { if ($schema->isBinary($attr)) { $func = 'ldap_get_values_len'; } } // fetch attribute value (needs error checking?) $attributes[$attr] = $func($this->_link, $this->_entry, $attr); } } while ($attr); } /* * set attribute data directly, if passed */ if (is_array($attributes) && count($attributes) > 0) { if (isset($attributes["count"]) && is_numeric($attributes["count"])) { unset($attributes["count"]); } foreach ($attributes as $k => $v) { // attribute names should not be numeric if (is_numeric($k)) { continue; } // map generic attribute name to real one $this->_map[strtolower($k)] = $k; // attribute values should be in an array if (false == is_array($v)) { $v = array($v); } // remove the value count (comes from ldap server) if (isset($v["count"])) { unset($v["count"]); } $this->_attributes[$k] = $v; } } // save a copy for later use $this->_original = $this->_attributes; } /** * Get the values of all attributes in a hash * * The returned hash has the form * array('attributename' => 'single value', * 'attributename' => array('value1', value2', value3')) * * @access public * @return array Hash of all attributes with their values */ function getValues() { $attrs = array(); foreach ($this->_attributes as $attr => $value) { $attrs[$attr] = $this->getValue($attr); } return $attrs; } /** * Get the value of a specific attribute * * The first parameter is the name of the attribute * The second parameter influences the way the value is returned: * 'single': only the first value is returned as string * 'all': all values including the value count are returned in an * array * 'default': in all other cases an attribute value with a single value is * returned as string, if it has multiple values it is returned * as an array (without value count) * * @param string $attr Attribute name * @param string $option Option * * @access public * @return string|array|PEAR_Error string, array or PEAR_Error */ function getValue($attr, $option = null) { $attr = $this->_getAttrName($attr); if (false == array_key_exists($attr, $this->_attributes)) { return PEAR::raiseError("Unknown attribute ($attr) requested"); } $value = $this->_attributes[$attr]; if ($option == "single" || (count($value) == 1 && $option != 'all')) { $value = array_shift($value); } return $value; } /** * Alias function of getValue for perl-ldap interface * * @see getValue() * @return string|array|PEAR_Error */ function get_value() { $args = func_get_args(); return call_user_func_array(array( &$this, 'getValue' ), $args); } /** * Returns an array of attributes names * * @access public * @return array Array of attribute names */ function attributes() { return array_keys($this->_attributes); } /** * Returns whether an attribute exists or not * * @param string $attr Attribute name * * @access public * @return boolean */ function exists($attr) { $attr = $this->_getAttrName($attr); return array_key_exists($attr, $this->_attributes); } /** * Adds a new attribute or a new value to an existing attribute * * The paramter has to be an array of the form: * array('attributename' => 'single value', * 'attributename' => array('value1', 'value2)) * When the attribute already exists the values will be added, else the * attribute will be created. These changes are local to the entry and do * not affect the entry on the server until update() is called. * * Note, that you can add values of attributes that you haven't selected, but if * you do so, {@link getValue()} and {@link getValues()} will only return the * values you added, _NOT_ all values present on the server. To avoid this, just refetch * the entry after calling {@link update()} or select the attribute. * * @param array $attr Attributes to add * * @access public * @return true|Net_LDAP_Error */ function add($attr = array()) { if (false == is_array($attr)) { return PEAR::raiseError("Parameter must be an array"); } foreach ($attr as $k => $v) { $k = $this->_getAttrName($k); if (false == is_array($v)) { // Do not add empty values if ($v == null) { continue; } else { $v = array($v); } } // add new values to existing attribute or add new attribute if ($this->exists($k)) { $this->_attributes[$k] = array_unique(array_merge($this->_attributes[$k], $v)); } else { $this->_map[strtolower($k)] = $k; $this->_attributes[$k] = $v; } // save changes for update() if (empty($this->_changes["add"][$k])) { $this->_changes["add"][$k] = array(); } $this->_changes["add"][$k] = array_unique(array_merge($this->_changes["add"][$k], $v)); } $return = true; return $return; } /** * Deletes an whole attribute or a value or the whole entry * * The parameter can be one of the following: * * "attributename" - The attribute as a whole will be deleted * array("attributename1", "attributename2) - All given attributes will be * deleted * array("attributename" => "value") - The value will be deleted * array("attributename" => array("value1", "value2") - The given values * will be deleted * If $attr is null or omitted , then the whole Entry will be deleted! * * These changes are local to the entry and do * not affect the entry on the server until {@link update()} is called. * * Please note that you must select the attribute (at $ldap->search() for example) * to be able to delete values of it, Otherwise {@link update()} will silently fail * and remove nothing. * * @param string|array $attr Attributes to delete (NULL or missing to delete whole entry) * * @access public * @return true */ function delete($attr = null) { if (is_null($attr)) { $this->_delete = true; return true; } if (is_string($attr)) { $attr = array($attr); } // Make the assumption that attribute names cannot be numeric, // therefore this has to be a simple list of attribute names to delete if (is_numeric(key($attr))) { foreach ($attr as $name) { if (is_array($name)) { // someone mixed modes (list mode but specific values given!) $del_attr_name = array_search($name, $attr); $this->delete(array($del_attr_name => $name)); } else { $name = $this->_getAttrName($name); if ($this->exists($name)) { $this->_changes["delete"][$name] = null; unset($this->_attributes[$name]); } } } } else { // Here we have a hash with "attributename" => "value to delete" foreach ($attr as $name => $values) { if (is_int($name)) { // someone mixed modes and gave us just an attribute name $this->delete($values); } else { // get the correct attribute name $name = $this->_getAttrName($name); if ($this->exists($name)) { if (false == is_array($values)) { $values = array($values); } // save values to be deleted if (empty($this->_changes["delete"][$name])) { $this->_changes["delete"][$name] = array(); } $this->_changes["delete"][$name] = array_unique(array_merge($this->_changes["delete"][$name], $values)); foreach ($values as $value) { // find the key for the value that should be deleted $key = array_search($value, $this->_attributes[$name]); if (false !== $key) { // delete the value unset($this->_attributes[$name][$key]); } } } } } } $return = true; return $return; } /** * Replaces attributes or its values * * The parameter has to an array of the following form: * array("attributename" => "single value", * "attribute2name" => array("value1", "value2")) * If the attribute does not yet exist it will be added instead. * If the attribue value is null, the attribute will de deleted * * These changes are local to the entry and do * not affect the entry on the server until {@link update()} is called. * * @param array $attr Attributes to replace * * @access public * @return true|Net_LDAP_Error */ function replace($attr = array()) { if (false == is_array($attr)) { return PEAR::raiseError("Parameter must be an array"); } foreach ($attr as $k => $v) { $k = $this->_getAttrName($k); if (false == is_array($v)) { // delete attributes with empty values if ($v == null) { $this->delete($k); continue; } else { $v = array($v); } } // existing attributes will get replaced if ($this->exists($k)) { $this->_changes["replace"][$k] = $v; $this->_attributes[$k] = $v; } else { // new ones just get added $this->add(array($k => $v)); } } $return = true; return $return; } /** * Update the entry on the directory server * * @param Net_LDAP $ldap If passed, a call to setLDAP() is issued prior update, thus switching the LDAP-server. This is for perl-ldap interface compliance * * @access public * @return true|Net_LDAP_Error * @todo Entry rename with a DN containing special characters needs testing! */ function update($ldap = null) { if ($ldap) { $msg = $this->setLDAP($ldap); if (Net_LDAP::isError($msg)) { return PEAR::raiseError('You passed an invalid $ldap variable to update()'); } } // ensure we have a valid LDAP object $ldap =& $this->getLDAP(); if (!is_a($ldap, 'Net_LDAP')) { return PEAR::raiseError("The entries LDAP object is not valid"); } // Get and check link $link = $ldap->getLink(); if (!is_resource($link)) { return PEAR::raiseError("Could not update entry: internal LDAP link is invalid"); } /* * Delete the entry */ if (true === $this->_delete) { return $ldap->delete($this); } /* * New entry */ if (true === $this->_new) { $msg = $ldap->add($this); if (Net_LDAP::isError($msg)) { return $msg; } $this->_new = false; $this->_changes['add'] = array(); $this->_changes['delete'] = array(); $this->_changes['replace'] = array(); $this->_original = $this->_attributes; $return = true; return $return; } /* * Rename/move entry */ if (false == is_null($this->_newdn)) { if ($ldap->getLDAPVersion() !== 3) { return PEAR::raiseError("Renaming/Moving an entry is only supported in LDAPv3"); } // make dn relative to parent (needed for ldap rename) $parent = Net_LDAP_Util::ldap_explode_dn($this->_newdn, array('casefolding' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (Net_LDAP::isError($parent)) { return $parent; } $child = array_shift($parent); // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $child-RDN is an array! if (is_array($child)) { $child = Net_LDAP_Util::canonical_dn($child); } $parent = Net_LDAP_Util::canonical_dn($parent); // rename/move if (false == @ldap_rename($link, $this->_dn, $child, $parent, true)) { return PEAR::raiseError("Entry not renamed: " . @ldap_error($link), @ldap_errno($link)); } // reflect changes to local copy $this->_dn = $this->_newdn; $this->_newdn = null; } /* * Carry out modifications to the entry */ // ADD foreach ($this->_changes["add"] as $attr => $value) { // if attribute exists, add new values if ($this->exists($attr)) { if (false === @ldap_mod_add($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not add new values to attribute $attr: " . @ldap_error($link), @ldap_errno($link)); } } else { // new attribute if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not add new attribute $attr: " . @ldap_error($link), @ldap_errno($link)); } } // all went well here, I guess unset($this->_changes["add"][$attr]); } // DELETE foreach ($this->_changes["delete"] as $attr => $value) { // In LDAPv3 you need to specify the old values for deleting if (is_null($value) && $ldap->getLDAPVersion() === 3) { $value = $this->_original[$attr]; } if (false === @ldap_mod_del($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not delete attribute $attr: " . @ldap_error($link), @ldap_errno($link)); } unset($this->_changes["delete"][$attr]); } // REPLACE foreach ($this->_changes["replace"] as $attr => $value) { if (false === @ldap_modify($link, $this->dn(), array($attr => $value))) { return PEAR::raiseError("Could not replace attribute $attr values: " . @ldap_error($link), @ldap_errno($link)); } unset($this->_changes["replace"][$attr]); } // all went well, so _original (server) becomes _attributes (local copy) $this->_original = $this->_attributes; $return = true; return $return; } /** * Returns the right attribute name * * @param string $attr Name of attribute * * @access private * @return string The right name of the attribute */ function _getAttrName($attr) { $name = strtolower($attr); if (array_key_exists($name, $this->_map)) { $attr = $this->_map[$name]; } return $attr; } /** * Returns a reference to the LDAP-Object of this entry * * @access public * @return Net_LDAP|Net_LDAP_Error Reference to the Net_LDAP Object (the connection) or Net_LDAP_Error */ function &getLDAP() { if (!is_a($this->_ldap, 'Net_LDAP')) { $err = new PEAR_Error('LDAP is not a valid Net_LDAP object'); return $err; } else { return $this->_ldap; } } /** * Sets a reference to the LDAP-Object of this entry * * After setting a Net_LDAP object, calling update() will use that object for * updating directory contents. Use this to dynamicly switch directorys. * * @param Net_LDAP &$ldap Net_LDAP object that this entry should be connected to * * @access public * @return true|Net_LDAP_Error */ function setLDAP(&$ldap) { if (!is_a($ldap, 'Net_LDAP')) { return PEAR::raiseError("LDAP is not a valid Net_LDAP object"); } else { $this->_ldap =& $ldap; return true; } } /** * Marks the entry as new. * * If an Entry is marked as new, it will be added to the directory when * calling {@link update()}. This method is mainly intendet for internal * Net_LDAP package usage, so if you use it, use it with care. * * @access private * @param boolean $mark Value to set, defaults to "true" */ function _markAsNew($mark = true) { $this->_new = ($mark)? true : false; } /** * Applies a regular expression onto a single- or multivalued attribute (like preg_match()) * * This method behaves like PHPs preg_match() but with some exceptions. * If you want to retrieve match information, then you MUST pass the * $matches parameter via reference! otherwise you will get no matches. * Since it is possible to have multi valued attributes the $matches * array will have a additionally numerical dimension (one for each value): * * $matches = array( * 0 => array (usual preg_match() returnarray), * 1 => array (usual preg_match() returnarray) * ) * * Please note, that $matches will be initialized to an empty array inside. * * Usage example: * * $result = $entry->preg_match('/089(\d+)/', 'telephoneNumber', &$matches); * if ( $result === true ){ * echo "First match: ".$matches[0][1]; // Match of value 1, content of first bracket * } else { * if ( Net_LDAP::isError($result) ) { * echo "Error: ".$result->getMessage(); * } else { * echo "No match found."; * } * } * * * Please note that it is important to test for an Net_LDAP_Error, because objects are * evaluating to true by default, thus if a error occured, and you only check using "==" then * you get misleading results. Use the "identical" (===) operator to test for matches to * avoid this as shown above. * * @param string $regex The regular expression * @param string $attr_name The attribute to search in * @param array $matches (optional, PASS BY REFERENCE!) Array to store matches in * * @return boolean|Net_LDAP_Error TRUE, if we had a match in one of the values, otherwise false. Net_LDAP_Error in case something went wrong */ function preg_match($regex, $attr_name, $matches = array()) { $matches = array(); // fetch attribute values $attr = $this->getValue($attr_name, 'all'); if (Net_LDAP::isError($attr)) { return $attr; } else { unset($attr['count']); } // perform preg_match() on all values $match = false; foreach ($attr as $thisvalue) { $matches_int = array(); if (preg_match($regex, $thisvalue, $matches_int)) { $match = true; array_push($matches, $matches_int); // store matches in reference } } return $match; } /** * Is this entry going to be deleted once update() is called? * * @return boolean */ function willBeDeleted() { return $this->_delete; } /** * Is this entry going to be moved once update() is called? * * @return boolean */ function willBeMoved() { return ($this->dn() !== $this->currentDN()); } /** * Returns always the original DN * * If an entry will be moved but {@link update()} was not called, * {@link dn()} will return the new DN. This method however, returns * always the current active DN. * * @return string */ function currentDN() { return $this->_dn; } /** * Returns the attribute changes to be carried out once update() is called * * @return array */ function getChanges() { return $this->_changes; } } ?> Net_LDAP-1.1.5/LDAP/Filter.php100644 1750 1750 41624 11223350110 10757 * $filter0 = Net_LDAP_Filter::create('stars', 'equals', '***'); * $filter_not0 = Net_LDAP_Filter::combine('not', $filter0); * * $filter1 = Net_LDAP_Filter::create('gn', 'begins', 'bar'); * $filter2 = Net_LDAP_Filter::create('gn', 'ends', 'baz'); * $filter_comp = Net_LDAP_Filter::combine('or',array($filter_not0, $filter1, $filter2)); * * echo $filter_comp->asString(); * // This will output: (|(!(stars=\0x5c0x2a\0x5c0x2a\0x5c0x2a))(gn=bar*)(gn=*baz)) * // The stars in $filter0 are treaten as real stars unless you disable escaping. * * * @category Net * @package Net_LDAP * @author Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Filter.php,v 1.27 2008/06/04 06:12:04 beni Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Filter extends PEAR { /** * Storage for combination of filters * * This variable holds a array of filter objects * that should be combined by this filter object. * * @access private * @var array */ var $_subfilters = array(); /** * Match of this filter * * If this is a leaf filter, then a matching rule is stored, * if it is a container, then it is a logical operator * * @access private * @var string */ var $_match; /** * Single filter * * If we operate in leaf filter mode, * then the constructing method stores * the filter representation here * * @acces private * @var string */ var $_filter; /** * Create a new Net_LDAP_Filter object and parse $filter. * * This is for PERL Net::LDAP interface. * Construction of Net_LDAP_Filter objects should happen through either * {@link create()} or {@link combine()} which give you more control. * However, you may use the perl iterface if you already have generated filters. * * @param string $filter LDAP filter string * * @see parse() */ function Net_LDAP_Filter($filter = false) { // The optional parameter must remain here, because otherwise create() crashes if (false !== $filter) { $filter_o = Net_LDAP_Filter::parse($filter); if (PEAR::isError($filter_o)) { $this->_filter = $filter_o; // assign error, so asString() can report it } else { $this->_filter = $filter_o->asString(); } } } /** * Constructor of a new part of a LDAP filter. * * The following matching rules exists: * - equals: One of the attributes values is exactly $value * Please note that case sensitiviness is depends on the * attributes syntax configured in the server. * - begins: One of the attributes values must begin with $value * - ends: One of the attributes values must end with $value * - contains: One of the attributes values must contain $value * - any: The attribute can contain any value but must be existent * - greater: The attributes value is greater than $value * - less: The attributes value is less than $value * - greaterOrEqual: The attributes value is greater or equal than $value * - lessOrEqual: The attributes value is less or equal than $value * - approx: One of the attributes values is similar to $value * * If $escape is set to true (default) then $value will be escaped * properly. If it is set to false then $value will be treaten as raw value. * * Examples: * * // This will find entries that contain an attribute "sn" that ends with "foobar": * $filter = new Net_LDAP_Filter('sn', 'ends', 'foobar'); * * // This will find entries that contain an attribute "sn" that has any value set: * $filter = new Net_LDAP_Filter('sn', 'any'); * * * @param string $attr_name Name of the attribute the filter should apply to * @param string $match Matching rule (equals, begins, ends, contains, greater, less, greaterOrEqual, lessOrEqual, approx, any) * @param string $value (optional) if given, then this is used as a filter * @param boolean $escape Should $value be escaped? (default: yes, see {@link Net_LDAP_Util::escape_filter_value()} for detailed information) * * @return Net_LDAP_Filter|Net_LDAP_Error */ function &create($attr_name, $match, $value = '', $escape = true) { $leaf_filter = new Net_LDAP_Filter(); if ($escape) { $array = Net_LDAP_Util::escape_filter_value(array($value)); $value = $array[0]; } switch (strtolower($match)) { case 'equals': $leaf_filter->_filter = '(' . $attr_name . '=' . $value . ')'; break; case 'begins': $leaf_filter->_filter = '(' . $attr_name . '=' . $value . '*)'; break; case 'ends': $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . ')'; break; case 'contains': $leaf_filter->_filter = '(' . $attr_name . '=*' . $value . '*)'; break; case 'greater': $leaf_filter->_filter = '(' . $attr_name . '>' . $value . ')'; break; case 'less': $leaf_filter->_filter = '(' . $attr_name . '<' . $value . ')'; break; case 'greaterorequal': $leaf_filter->_filter = '(' . $attr_name . '>=' . $value . ')'; break; case 'lessorequal': $leaf_filter->_filter = '(' . $attr_name . '<=' . $value . ')'; break; case 'approx': $leaf_filter->_filter = '(' . $attr_name . '~=' . $value . ')'; break; case 'any': $leaf_filter->_filter = '(' . $attr_name . '=*)'; break; default: return PEAR::raiseError('Net_LDAP_Filter create error: matching rule "' . $match . '" not known!'); } return $leaf_filter; } /** * Combine two or more filter objects using a logical operator * * This static method combines two or more filter objects and returns one single * filter object that contains all the others. * Call this method statically: $filter =& Net_LDAP_Filter('or', array($filter1, $filter2)) * If the array contains filter strings instead of filter objects, we will try to parse them. * * @param string $log_op The locicall operator. May be "and", "or", "not" or the subsequent logical equivalents "&", "|", "!" * @param array|Net_LDAP_Filter $filters array with Net_LDAP_Filter objects * * @return Net_LDAP_Filter|Net_LDAP_Error * @static */ function &combine($log_op, $filters) { if (PEAR::isError($filters)) { return $filters; } // substitude named operators to logical operators if ($log_op == 'and') $log_op = '&'; if ($log_op == 'or') $log_op = '|'; if ($log_op == 'not') $log_op = '!'; // tests for sane operation if ($log_op == '!') { // Not-combination, here we also accept one filter object or filter string if (!is_array($filters) && is_a($filters, 'Net_LDAP_Filter')) { $filters = array($filters); // force array } elseif (is_string($filters)) { $filter_o = Net_LDAP_Filter::parse($filters); if (PEAR::isError($filter_o)) { $err = PEAR::raiseError('Net_LDAP_Filter combine error: '.$filter_o->getMessage()); return $err; } else { $filters = array($filter_o); } } else { $err = PEAR::raiseError('Net_LDAP_Filter combine error: operator is "not" but $filter is not a valid Net_LDAP_Filter nor an array nor a filter string!'); return $err; } } elseif ($log_op == '&' || $log_op == '|') { if (!is_array($filters) || count($filters) < 2) { $err = PEAR::raiseError('Net_LDAP_Filter combine error: parameter $filters is not an array or contains less than two Net_LDAP_Filter objects!'); return $err; } } else { $err = PEAR::raiseError('Net_LDAP_Filter combine error: logical operator is not known!'); return $err; } $combined_filter = new Net_LDAP_Filter(); foreach ($filters as $key => $testfilter) { // check for errors if (PEAR::isError($testfilter)) { return $testfilter; } elseif (is_string($testfilter)) { // string found, try to parse into an filter object $filter_o = Net_LDAP_Filter::parse($testfilter); if (PEAR::isError($filter_o)) { return $filter_o; } else { $filters[$key] = $filter_o; } } elseif (!is_a($testfilter, 'Net_LDAP_Filter')) { $err = PEAR::raiseError('Net_LDAP_Filter combine error: invalid object passed in array $filters!'); return $err; } } $combined_filter->_subfilters = $filters; $combined_filter->_match = $log_op; return $combined_filter; } /** * Parse FILTER into a Net_LDAP_Filter object * * This parses an filter string into Net_LDAP_Filter objects. * * @param string $FILTER The filter string * * @access static * @return Net_LDAP_Filter|Net_LDAP_Error * @todo Leaf-mode: Do we need to escape at all? what about *-chars?check for the need of encoding values, tackle problems (see code comments) */ function parse($FILTER) { if (preg_match('/^\((.+?)\)$/', $FILTER, $matches)) { if (in_array(substr($matches[1], 0, 1), array('!', '|', '&'))) { // Subfilter processing: pass subfilters to parse() and combine // the objects using the logical operator detected // we have now something like "(...)(...)(...)" but at least one part ("(...)"). // extract logical operator and subfilters $log_op = substr($matches[1], 0, 1); $remaining_component = substr($matches[1], 1); // bite off the next filter part and parse $subfilters = array(); while (preg_match('/^(\(.+?\))(.*)/', $remaining_component, $matches)) { $remaining_component = $matches[2]; $filter_o = Net_LDAP_Filter::parse($matches[1]); if (PEAR::isError($filter_o)) { return $filter_o; } array_push($subfilters, $filter_o); } // combine subfilters using the logical operator $filter_o = Net_LDAP_Filter::combine($log_op, $subfilters); return $filter_o; } else { // This is one leaf filter component, do some syntax checks, then escape and build filter_o // $matches[1] should be now something like "foo=bar" // detect multiple leaf components // [TODO] Maybe this will make problems with filters containing brackets inside the value if (stristr($matches[1], ')(')) { return PEAR::raiseError("Filter parsing error: invalid filter syntax - multiple leaf components detected!"); } else { $filter_parts = preg_split('/(?|<|>=|<=)/', $matches[1], 2, PREG_SPLIT_DELIM_CAPTURE); if (count($filter_parts) != 3) { return PEAR::raiseError("Filter parsing error: invalid filter syntax - unknown matching rule used"); } else { $filter_o = new Net_LDAP_Filter(); // [TODO]: Do we need to escape at all? what about *-chars user provide and that should remain special? // I think, those prevent escaping! We need to check against PERL Net::LDAP! // $value_arr = Net_LDAP_Util::escape_filter_value(array($filter_parts[2])); // $value = $value_arr[0]; $value = $filter_parts[2]; $filter_o->_filter = '('.$filter_parts[0].$filter_parts[1].$value.')'; return $filter_o; } } } } else { // ERROR: Filter components must be enclosed in round brackets return PEAR::raiseError("Filter parsing error: invalid filter syntax - filter components must be enclosed in round brackets"); } } /** * Get the string representation of this filter * * This method runs through all filter objects and creates * the string representation of the filter. If this * filter object is a leaf filter, then it will return * the string representation of this filter. * * @return string|Net_LDAP_Error */ function asString() { if ($this->_isLeaf()) { $return = $this->_filter; } else { $return = ''; foreach ($this->_subfilters as $filter) { $return = $return.$filter->asString(); } $return = '(' . $this->_match . $return . ')'; } return $return; } /** * Alias for perl interface as_string() * * @see asString() */ function as_string() { return $this->asString(); } /** * Print the text representation of the filter to FH, or the currently selected output handle if FH is not given * * This method is only for compatibility to the perl interface. * However, the original method was called "print" but due to PHP language restrictions, * we can't have a print() method. * * @param resource $FH (optional) A filehandle resource * * @return true|Net_LDAP_Error */ function printMe($FH = false) { if (!is_resource($FH)) { if (PEAR::isError($FH)) { return $FH; } $filter_str = $this->asString(); if (PEAR::isError($filter_str)) { return $filter_str; } else { print($filter_str); } } else { $filter_str = $this->asString(); if (PEAR::isError($filter_str)) { return $filter_str; } else { $res = @fwrite($FH, $this->asString()); if ($res == false) { return PEAR::raiseError("Unable to write filter string to filehandle \$FH!"); } } } return true; } /** * This can be used to escape a string to provide a valid LDAP-Filter. * * LDAP will only recognise certain characters as the * character istself if they are properly escaped. This is * what this method does. * The method can be called statically, so you can use it outside * for your own purposes (eg for escaping only parts of strings) * * In fact, this is just a shorthand to {@link Net_LDAP_Util::escape_filter_value()}. * For upward compatibiliy reasons you are strongly encouraged to use the escape * methods provided by the Net_LDAP_Util class. * * @param string $value Any string who should be escaped * * @static * @return string The string $string, but escaped * @deprecated Do not use this method anymore, instead use Net_LDAP_Util::escape_filter_value() directly */ function escape($value) { $return = Net_LDAP_Util::escape_filter_value(array($value)); return $return[0]; } /** * Is this a container or a leaf filter object? * * @access private * @return boolean */ function _isLeaf() { if (count($this->_subfilters) > 0) { return false; // Container! } else { return true; // Leaf! } } } ?> Net_LDAP-1.1.5/LDAP/RootDSE.php100644 1750 1750 11062 11223350110 11002 * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: RootDSE.php,v 1.12 2008/10/26 15:31:06 clockwerx Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; /** * Getting the rootDSE entry of a LDAP server * * @category Net * @package Net_LDAP * @author Jan Wagner * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_RootDSE extends PEAR { /** * @access private * @var object Net_LDAP_Entry **/ var $_entry; /** * Class constructor * * @param Net_LDAP_Entry &$entry Net_LDAP_Entry object */ function Net_LDAP_RootDSE(&$entry) { $this->_entry = $entry; } /** * Gets the requested attribute value * * Same usuage as {@link Net_LDAP_Entry::getValue()} * * @param string $attr Attribute name * @param array $options Array of options * * @access public * @return mixed Net_LDAP_Error object or attribute values * @see Net_LDAP_Entry::get_value() */ function getValue($attr = '', $options = '') { return $this->_entry->get_value($attr, $options); } /** * Alias function of getValue() for perl-ldap interface * * @see getValue() */ function get_value() { $args = func_get_args(); return call_user_func_array(array( &$this, 'getValue' ), $args); } /** * Determines if the extension is supported * * @param array $oids Array of oids to check * * @access public * @return boolean */ function supportedExtension($oids) { return $this->_checkAttr($oids, 'supportedExtension'); } /** * Alias function of supportedExtension() for perl-ldap interface * * @see supportedExtension() */ function supported_extension() { $args = func_get_args(); return call_user_func_array(array( &$this, 'supportedExtension'), $args); } /** * Determines if the version is supported * * @param array $versions Versions to check * * @access public * @return boolean */ function supportedVersion($versions) { return $this->_checkAttr($versions, 'supportedLDAPVersion'); } /** * Alias function of supportedVersion() for perl-ldap interface * * @see supportedVersion() */ function supported_version() { $args = func_get_args(); return call_user_func_array(array(&$this, 'supportedVersion'), $args); } /** * Determines if the control is supported * * @param array $oids Control oids to check * * @access public * @return boolean */ function supportedControl($oids) { return $this->_checkAttr($oids, 'supportedControl'); } /** * Alias function of supportedControl() for perl-ldap interface * * @see supportedControl() */ function supported_control() { $args = func_get_args(); return call_user_func_array(array(&$this, 'supportedControl' ), $args); } /** * Determines if the sasl mechanism is supported * * @param array $mechlist SASL mechanisms to check * * @access public * @return boolean */ function supportedSASLMechanism($mechlist) { return $this->_checkAttr($mechlist, 'supportedSASLMechanisms'); } /** * Alias function of supportedSASLMechanism() for perl-ldap interface * * @see supportedSASLMechanism() */ function supported_sasl_mechanism() { $args = func_get_args(); return call_user_func_array(array(&$this, 'supportedSASLMechanism'), $args); } /** * Checks for existance of value in attribute * * @param array $values values to check * @param string $attr attribute name * * @access private * @return boolean */ function _checkAttr($values, $attr) { if (!is_array($values)) $values = array($values); foreach ($values as $value) { if (!@in_array($value, $this->get_value($attr, 'all'))) { return false; } } return true; } } ?> Net_LDAP-1.1.5/LDAP/Schema.php100644 1750 1750 35166 11223350110 10736 * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Schema.php,v 1.23 2008/10/26 15:31:06 clockwerx Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; /** * Syntax definitions * * Please don't forget to add binary attributes to isBinary() below * to support proper value fetching from Net_LDAP_Entry */ define('NET_LDAP_SYNTAX_BOOLEAN', '1.3.6.1.4.1.1466.115.121.1.7'); define('NET_LDAP_SYNTAX_DIRECTORY_STRING', '1.3.6.1.4.1.1466.115.121.1.15'); define('NET_LDAP_SYNTAX_DISTINGUISHED_NAME', '1.3.6.1.4.1.1466.115.121.1.12'); define('NET_LDAP_SYNTAX_INTEGER', '1.3.6.1.4.1.1466.115.121.1.27'); define('NET_LDAP_SYNTAX_JPEG', '1.3.6.1.4.1.1466.115.121.1.28'); define('NET_LDAP_SYNTAX_NUMERIC_STRING', '1.3.6.1.4.1.1466.115.121.1.36'); define('NET_LDAP_SYNTAX_OID', '1.3.6.1.4.1.1466.115.121.1.38'); define('NET_LDAP_SYNTAX_OCTET_STRING', '1.3.6.1.4.1.1466.115.121.1.40'); /** * Load an LDAP Schema and provide information * * This class takes a Subschema entry, parses this information * and makes it available in an array. Most of the code has been * inspired by perl-ldap( http://perl-ldap.sourceforge.net). * You will find portions of their implementation in here. * * @category Net * @package Net_LDAP * @author Jan Wagner * @author Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Schema extends PEAR { /** * Map of entry types to ldap attributes of subschema entry * * @access public * @var array */ var $types = array('attribute' => 'attributeTypes', 'ditcontentrule' => 'dITContentRules', 'ditstructurerule' => 'dITStructureRules', 'matchingrule' => 'matchingRules', 'matchingruleuse' => 'matchingRuleUse', 'nameform' => 'nameForms', 'objectclass' => 'objectClasses', 'syntax' => 'ldapSyntaxes'); /** * Array of entries belonging to this type * * @access private * @var array */ var $_attributeTypes = array(); var $_matchingRules = array(); var $_matchingRuleUse = array(); var $_ldapSyntaxes = array(); var $_objectClasses = array(); var $_dITContentRules = array(); var $_dITStructureRules = array(); var $_nameForms = array(); /** * hash of all fetched oids * * @access private * @var array */ var $_oids = array(); /** * Tells if the schema is initialized * * @access private * @var boolean * @see parse(), get() */ var $_initialized = false; /** * constructor of the class * * @access protected */ function Net_LDAP_Schema() { $this->PEAR('Net_LDAP_Error'); // default error class } /** * Return a hash of entries for the given type * * Returns a hash of entry for th givene type. Types may be: * objectclasses, attributes, ditcontentrules, ditstructurerules, matchingrules, * matchingruleuses, nameforms, syntaxes * * @param string $type Type to fetch * * @access public * @return array|Net_LDAP_Error Array or Net_LDAP_Error */ function &getAll($type) { $map = array('objectclasses' => &$this->_objectClasses, 'attributes' => &$this->_attributeTypes, 'ditcontentrules' => &$this->_dITContentRules, 'ditstructurerules' => &$this->_dITStructureRules, 'matchingrules' => &$this->_matchingRules, 'matchingruleuses' => &$this->_matchingRuleUse, 'nameforms' => &$this->_nameForms, 'syntaxes' => &$this->_ldapSyntaxes ); $key = strtolower($type); $ret = ((key_exists($key, $map)) ? $map[$key] : PEAR::raiseError("Unknown type $type")); return $ret; } /** * Return a specific entry * * @param string $type Type of name * @param string $name Name or OID to fetch * * @access public * @return mixed Entry or Net_LDAP_Error */ function &get($type, $name) { if ($this->_initialized) { $type = strtolower($type); if (false == key_exists($type, $this->types)) { return PEAR::raiseError("No such type $type"); } $name = strtolower($name); $type_var = &$this->{'_' . $this->types[$type]}; if (key_exists($name, $type_var)) { return $type_var[$name]; } elseif (key_exists($name, $this->_oids) && $this->_oids[$name]['type'] == $type) { return $this->_oids[$name]; } else { return PEAR::raiseError("Could not find $type $name"); } } else { $return = null; return $return; } } /** * Fetches attributes that MAY be present in the given objectclass * * @param string $oc Name or OID of objectclass * * @access public * @return array|Net_LDAP_Error Array with attributes or Net_LDAP_Error */ function may($oc) { return $this->_getAttr($oc, 'may'); } /** * Fetches attributes that MUST be present in the given objectclass * * @param string $oc Name or OID of objectclass * * @access public * @return array|Net_LDAP_Error Array with attributes or Net_LDAP_Error */ function must($oc) { return $this->_getAttr($oc, 'must'); } /** * Fetches the given attribute from the given objectclass * * @param string $oc Name or OID of objectclass * @param string $attr Name of attribute to fetch * * @access private * @return array|Net_LDAP_Error The attribute or Net_LDAP_Error */ function _getAttr($oc, $attr) { $oc = strtolower($oc); if (key_exists($oc, $this->_objectClasses) && key_exists($attr, $this->_objectClasses[$oc])) { return $this->_objectClasses[$oc][$attr]; } elseif (key_exists($oc, $this->_oids) && $this->_oids[$oc]['type'] == 'objectclass' && key_exists($attr, $this->_oids[$oc])) { return $this->_oids[$oc][$attr]; } else { return PEAR::raiseError("Could not find $attr attributes for $oc "); } } /** * Returns the name(s) of the immediate superclass(es) * * @param string $oc Name or OID of objectclass * * @return array|Net_LDAP_Error Array of names or Net_LDAP_Error */ function superclass($oc) { $o = $this->get('objectclass', $oc); if (Net_LDAP::isError($o)) { return $o; } return (key_exists('sup', $o) ? $o['sup'] : array()); } /** * Parses the schema of the given Subschema entry * * @param Net_LDAP_Entry &$entry Subschema entry * * @access public */ function parse(&$entry) { foreach ($this->types as $type => $attr) { // initialize map type to entry $type_var = '_' . $attr; $this->{$type_var} = array(); // get values for this type if ($entry->exists($attr)) { $values = $entry->getValue($attr); if (is_array($values)) { foreach ($values as $value) { unset($schema_entry); // this was a real mess without it // get the schema entry $schema_entry = $this->_parse_entry($value); // set the type $schema_entry['type'] = $type; // save a ref in $_oids $this->_oids[$schema_entry['oid']] = &$schema_entry; // save refs for all names in type map $names = $schema_entry['aliases']; array_push($names, $schema_entry['name']); foreach ($names as $name) { $this->{$type_var}[strtolower($name)] = &$schema_entry; } } } } } $this->_initialized = true; } /** * parses an attribute value into a schema entry * * @param string $value Attribute value * * @access private * @return array|false Schema entry array or false */ function &_parse_entry($value) { // tokens that have no value associated $noValue = array('single-value', 'obsolete', 'collective', 'no-user-modification', 'abstract', 'structural', 'auxiliary'); // tokens that can have multiple values $multiValue = array('must', 'may', 'sup'); $schema_entry = array('aliases' => array()); // initilization $tokens = $this->_tokenize($value); // get an array of tokens // remove surrounding brackets if ($tokens[0] == '(') array_shift($tokens); if ($tokens[count($tokens) - 1] == ')') array_pop($tokens); // -1 doesnt work on arrays :-( $schema_entry['oid'] = array_shift($tokens); // first token is the oid // cycle over the tokens until none are left while (count($tokens) > 0) { $token = strtolower(array_shift($tokens)); if (in_array($token, $noValue)) { $schema_entry[$token] = 1; // single value token } else { // this one follows a string or a list if it is multivalued if (($schema_entry[$token] = array_shift($tokens)) == '(') { // this creates the list of values and cycles through the tokens // until the end of the list is reached ')' $schema_entry[$token] = array(); while ($tmp = array_shift($tokens)) { if ($tmp == ')') break; if ($tmp != '$') array_push($schema_entry[$token], $tmp); } } // create a array if the value should be multivalued but was not if (in_array($token, $multiValue) && !is_array($schema_entry[$token])) { $schema_entry[$token] = array($schema_entry[$token]); } } } // get max length from syntax if (key_exists('syntax', $schema_entry)) { if (preg_match('/{(\d+)}/', $schema_entry['syntax'], $matches)) { $schema_entry['max_length'] = $matches[1]; } } // force a name if (empty($schema_entry['name'])) { $schema_entry['name'] = $schema_entry['oid']; } // make one name the default and put the other ones into aliases if (is_array($schema_entry['name'])) { $aliases = $schema_entry['name']; $schema_entry['name'] = array_shift($aliases); $schema_entry['aliases'] = $aliases; } return $schema_entry; } /** * tokenizes the given value into an array of tokens * * @param string $value String to parse * * @access private * @return array Array of tokens */ function _tokenize($value) { $tokens = array(); // array of tokens $matches = array(); // matches[0] full pattern match, [1,2,3] subpatterns // this one is taken from perl-ldap, modified for php $pattern = "/\s* (?:([()]) | ([^'\s()]+) | '((?:[^']+|'[^\s)])*)') \s*/x"; /** * This one matches one big pattern wherin only one of the three subpatterns matched * We are interested in the subpatterns that matched. If it matched its value will be * non-empty and so it is a token. Tokens may be round brackets, a string, or a string * enclosed by ' */ preg_match_all($pattern, $value, $matches); for ($i = 0; $i < count($matches[0]); $i++) { // number of tokens (full pattern match) for ($j = 1; $j < 4; $j++) { // each subpattern if (null != trim($matches[$j][$i])) { // pattern match in this subpattern $tokens[$i] = trim($matches[$j][$i]); // this is the token } } } return $tokens; } /** * Returns wether a attribute syntax is binary or not * * This method gets used by Net_LDAP_Entry to decide which * PHP function needs to be used to fetch the value in the * proper format (e.g. binary or string) * * @param string $attribute The name of the attribute (eg.: 'sn') * * @access public * @return boolean */ function isBinary($attribute) { $return = false; // default to false // This list contains all syntax that should be treaten as // containing binary values // The Syntax Definitons go into constants at the top of this page $syntax_binary = array( NET_LDAP_SYNTAX_OCTET_STRING, NET_LDAP_SYNTAX_JPEG ); // Check Syntax $attr_s = $this->get('attribute', $attribute); if (Net_LDAP::isError($attr_s)) { // Attribute not found in schema $return = false; // consider attr not binary } elseif (isset($attr_s['syntax']) && in_array($attr_s['syntax'], $syntax_binary)) { // Syntax is defined as binary in schema $return = true; } else { // Syntax not defined as binary, or not found // if attribute is a subtype, check superior attribute syntaxes if (isset($attr_s['sup'])) { foreach ($attr_s['sup'] as $superattr) { $return = $this->isBinary($superattr); if ($return) { break; // stop checking parents since we are binary } } } } return $return; } } ?> Net_LDAP-1.1.5/LDAP/Search.php100644 1750 1750 34623 11223350110 10740 * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Search.php,v 1.32 2008/10/26 15:31:06 clockwerx Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; /** * Result set of an LDAP search * * @category Net * @package Net_LDAP * @author Tarjej Huse * @author Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Search extends PEAR { /** * Search result identifier * * @access private * @var resource */ var $_search; /** * LDAP resource link * * @access private * @var resource */ var $_link; /** * Net_LDAP object * * A reference of the Net_LDAP object for passing to Net_LDAP_Entry * * @access private * @var object Net_LDAP */ var $_ldap; /** * Result entry identifier * * @access private * @var resource */ var $_entry = null; /** * The errorcode the search got * * Some errorcodes might be of interest, but might not be best handled as errors. * examples: 4 - LDAP_SIZELIMIT_EXCEEDED - indicates a huge search. * Incomplete results are returned. If you just want to check if there's anything in the search. * than this is a point to handle. * 32 - no such object - search here returns a count of 0. * * @access private * @var int */ var $_errorCode = 0; // if not set - sucess! /** * What attributes we searched for * * The $attributes array contains the names of the searched attributes and gets * passed from $Net_LDAP->search() so the Net_LDAP_Search object can tell * what attributes was searched for ({@link _searchedAttrs()) * * This variable gets set from the constructor and returned * from {@link _searchedAttrs()} * * @access private * @var array */ var $_searchedAttrs = array(); /** * Cache variable for storing entries fetched internally * * This currently is only used by {@link pop_entry()} * * @access private * @var array */ var $_entry_cache = false; /** * Constructor * * @param resource &$search Search result identifier * @param Net_LDAP|resource &$ldap Net_LDAP object or just a LDAP-Link resource * @param array $attributes (optional) Array with searched attribute names. (see {@link $_searchedAttrs}) * * @access protected */ function Net_LDAP_Search(&$search, &$ldap, $attributes = array()) { $this->PEAR('Net_LDAP_Error'); $this->setSearch($search); if (is_a($ldap, 'Net_LDAP')) { $this->_ldap =& $ldap; $this->setLink($this->_ldap->getLink()); } else { $this->setLink($ldap); } $this->_errorCode = @ldap_errno($this->_link); if (is_array($attributes) && !empty($attributes)) { $this->_searchedAttrs = $attributes; } } /** * Returns an array of entry objects * * @return array Array of entry objects. */ function entries() { $entries = array(); while ($entry = $this->shiftEntry()) { $entries[] = $entry; } return $entries; } /** * Get the next entry in the searchresult. * * This will return a valid Net_LDAP_Entry object or false, so * you can use this method to easily iterate over the entries inside * a while loop. * * @return Net_LDAP_Entry|false Reference to Net_LDAP_Entry object or false */ function &shiftEntry() { if ($this->count() == 0 ) { $false = false; return $false; } if (is_null($this->_entry)) { $this->_entry = @ldap_first_entry($this->_link, $this->_search); $entry = new Net_LDAP_Entry($this->_ldap, $this->_entry); } else { if (!$this->_entry = @ldap_next_entry($this->_link, $this->_entry)) { $false = false; return $false; } $entry = new Net_LDAP_Entry($this->_ldap, $this->_entry); } return $entry; } /** * Alias function of shiftEntry() for perl-ldap interface * * @see shiftEntry() * @return Net_LDAP_Entry|false Reference to Net_LDAP_Entry object or false */ function shift_entry() { $args = func_get_args(); return call_user_func_array(array( &$this, 'shiftEntry' ), $args); } /** * Retrieve the next entry in the searchresult, but starting from last entry * * This is the opposite to {@link shiftEntry()} and is also very useful * to be used inside a while loop. * * @return Net_LDAP_Entry|false */ function popEntry() { if (false === $this->_entry_cache) { // fetch entries into cache if not done so far $this->_entry_cache = $this->entries(); } $return = array_pop($this->_entry_cache); return (null === $return)? false : $return; } /** * Alias function of popEntry() for perl-ldap interface * * @see popEntry() * @return Net_LDAP_Entry|false */ function pop_entry() { $args = func_get_args(); return call_user_func_array(array( &$this, 'popEntry' ), $args); } /** * Return entries sorted as array * * This returns a array with sorted entries and the values. * Sorting is done with PHPs {@link array_multisort()}. * This method relies on {@link as_struct()} to fetch the raw data of the entries. * * Please note that attribute names are case sensitive! * * Usage example: * * // to sort entries first by location, then by surename, but descending: * $entries = $search->sorted_as_struct(array('locality','sn'), SORT_DESC); * * * @param array $attrs Array of attribute names to sort; order from left to right. * @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC * * @return array|Net_LDAP_Error Array with sorted entries or error */ function sorted_as_struct($attrs = array('cn'), $order = SORT_ASC) { /* * Old Code, suitable and fast for single valued sorting * This code should be used if we know that single valued sorting is desired, * but we need some method to get that knowledge... */ /* $attrs = array_reverse($attrs); foreach ($attrs as $attribute) { if (!ldap_sort($this->_link, $this->_search, $attribute)){ $this->raiseError("Sorting failed for Attribute " . $attribute); } } $results = ldap_get_entries($this->_link, $this->_search); unset($results['count']); //for tidier output if ($order) { return array_reverse($results); } else { return $results; }*/ /* * New code: complete "client side" sorting */ // first some parameterchecks if (!is_array($attrs)) { return PEAR::raiseError("Sorting failed: Parameterlist must be an array!"); } if ($order != SORT_ASC && $order != SORT_DESC) { return PEAR::raiseError("Sorting failed: sorting direction not understood! (neither constant SORT_ASC nor SORT_DESC)"); } // fetch the entries data $entries = $this->as_struct(); // now sort each entries attribute values // this is neccessary because later we can only sort by one value, // so we need the highest or lowest attribute now, depending on the // selected ordering for that specific attribute foreach ($entries as $dn => $entry) { foreach ($entry as $attr_name => $attr_values) { sort($entries[$dn][$attr_name]); if ($order == SORT_DESC) { array_reverse($entries[$dn][$attr_name]); } } } // reformat entrys array for later use with array_multisort() $to_sort = array(); // <- will be a numeric array similar to ldap_get_entries foreach ($entries as $dn => $entry_attr) { $row = array(); $row['dn'] = $dn; foreach ($entry_attr as $attr_name => $attr_values) { $row[$attr_name] = $attr_values; } $to_sort[] = $row; } // Build columns for array_multisort() // each requested attribute is one row $columns = array(); foreach ($attrs as $attr_name) { foreach ($to_sort as $key => $row) { $columns[$attr_name][$key] =& $to_sort[$key][$attr_name][0]; } } // sort the colums with array_multisort, if there is something // to sort and if we have requested sort columns if (!empty($to_sort) && !empty($columns)) { $sort_params = ''; foreach ($attrs as $attr_name) { $sort_params .= '$columns[\''.$attr_name.'\'], '.$order.', '; } eval("array_multisort($sort_params \$to_sort);"); // perform sorting } return $to_sort; } /** * Return entries sorted as objects * * This returns a array with sorted Net_LDAP_Entry objects. * The sorting is actually done with {@link sorted_as_struct()}. * * Please note that attribute names are case sensitive! * * Usage example: * * // to sort entries first by location, then by surename, but descending: * $entries = $search->sorted(array('locality','sn'), SORT_DESC); * * * @param array $attrs Array of sort attributes to sort; order from left to right. * @param int $order Ordering direction, either constant SORT_ASC or SORT_DESC * * @return array|Net_LDAP_Error Array with sorted Net_LDAP_Entries or error */ function sorted($attrs = array('cn'), $order = SORT_ASC) { $return = array(); $sorted = $this->sorted_as_struct($attrs, $order); if (PEAR::isError($sorted)) { return $sorted; } foreach ($sorted as $key => $row) { $entry = $this->_ldap->getEntry($row['dn'], $this->_searchedAttrs()); if (!PEAR::isError($entry)) { array_push($return, $entry); } else { return $entry; } } return $return; } /** * Return entries as array * * This method returns the entries and the selected attributes values as * array. * The first array level contains all found entries where the keys are the * DNs of the entries. The second level arrays contian the entries attributes * such that the keys is the lowercased name of the attribute and the values * are stored in another indexed array. Note that the attribute values are stored * in an array even if there is no or just one value. * * The array has the following structure: * * $return = array( * 'cn=foo,dc=example,dc=com' => array( * 'sn' => array('foo'), * 'multival' => array('val1', 'val2', 'valN') * ) * 'cn=bar,dc=example,dc=com' => array( * 'sn' => array('bar'), * 'multival' => array('val1', 'valN') * ) * ) * * * @return array associative result array as described above */ function as_struct() { $return = array(); $entries = $this->entries(); foreach ($entries as $entry) { $attrs = array(); $entry_attributes = $entry->attributes(); foreach ($entry_attributes as $attr_name) { $attr_values = $entry->getValue($attr_name, 'all'); if (!is_array($attr_values)) { $attr_values = array($attr_values); } $attrs[$attr_name] = $attr_values; } $return[$entry->dn()] = $attrs; } return $return; } /** * Set the search objects resource link * * @param resource &$search Search result identifier * * @access public * @return void */ function setSearch(&$search) { $this->_search = $search; } /** * Set the ldap ressource link * * @param resource &$link Link identifier * * @access public * @return void */ function setLink(&$link) { $this->_link = $link; } /** * Returns the number of entries in the searchresult * * @return int Number of entries in search. */ function count() { // this catches the situation where OL returned errno 32 = no such object! if (!$this->_search) { return 0; } return @ldap_count_entries($this->_link, $this->_search); } /** * Get the errorcode the object got in its search. * * @return int The ldap error number. */ function getErrorCode() { return $this->_errorCode; } /** * Destructor * * @access protected */ function _Net_LDAP_Search() { @ldap_free_result($this->_search); } /** * Closes search result * * @return void */ function done() { $this->_Net_LDAP_Search(); } /** * Return the attribute names this search selected * * @return array * @see $_searchedAttrs * @access private */ function _searchedAttrs() { return $this->_searchedAttrs; } /** * Tells if this search exceeds a sizelimit * * @return boolean */ function sizeLimitExceeded() { return ($this->getErrorCode() == 4); } } ?> Net_LDAP-1.1.5/LDAP/Util.php100644 1750 1750 56415 11223350110 10453 * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: Util.php,v 1.29 2008/10/26 15:31:06 clockwerx Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; /** * Utility Class for Net_LDAP * * This class servers some functionality to the other classes of Net_LDAP but most of * the methods can be used separately as well. * * @category Net * @package Net_LDAP * @author Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Util extends PEAR { /** * Private empty Constructur * * @access private */ function Net_LDAP_Util() { // We do nothing here, since all methods can be called statically. // In Net_LDAP <= 0.7, we needed a instance of Util, because // it was possible to do utf8 encoding and decoding, but this // has been moved to the LDAP class. The constructor remains only // here to document the downward compatibility of creating a instance. } /** * Explodes the given DN into its elements * * {@link http://www.ietf.org/rfc/rfc2253.txt RFC 2253} says, a Distinguished Name is a sequence * of Relative Distinguished Names (RDNs), which themselves * are sets of Attributes. For each RDN a array is constructed where the RDN part is stored. * * For example, the DN 'OU=Sales+CN=J. Smith,DC=example,DC=net' is exploded to: * array( [0] => array([0] => 'OU=Sales', [1] => 'CN=J. Smith'), [2] => 'DC=example', [3] => 'DC=net' ) * * [NOT IMPLEMENTED] DNs might also contain values, which are the bytes of the BER encoding of * the X.500 AttributeValue rather than some LDAP string syntax. These values are hex-encoded * and prefixed with a #. To distinguish such BER values, ldap_explode_dn uses references to * the actual values, e.g. '1.3.6.1.4.1.1466.0=#04024869,DC=example,DC=com' is exploded to: * [ { '1.3.6.1.4.1.1466.0' => "\004\002Hi" }, { 'DC' => 'example' }, { 'DC' => 'com' } ]; * See {@link http://www.vijaymukhi.com/vmis/berldap.htm} for more information on BER. * * It also performs the following operations on the given DN: * - Unescape "\" followed by ",", "+", """, "\", "<", ">", ";", "#", "=", " ", or a hexpair * and strings beginning with "#". * - Removes the leading 'OID.' characters if the type is an OID instead of a name. * - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order. * * OPTIONS is a list of name/value pairs, valid options are: * casefold Controls case folding of attribute types names. * Attribute values are not affected by this option. * The default is to uppercase. Valid values are: * lower Lowercase attribute types names. * upper Uppercase attribute type names. This is the default. * none Do not change attribute type names. * reverse If TRUE, the RDN sequence is reversed. * onlyvalues If TRUE, then only attributes values are returned ('foo' instead of 'cn=foo') * * @param string $dn The DN that should be exploded * @param array $options Options to use * * @static * @return array Parts of the exploded DN * @todo implement BER */ function ldap_explode_dn($dn, $options = array('casefold' => 'upper')) { if (!isset($options['onlyvalues'])) { $options['onlyvalues'] = false; } if (!isset($options['reverse'])) { $options['reverse'] = false; } if (!isset($options['casefold'])) { $options['casefold'] = 'upper'; } // Escaping of DN and stripping of "OID." $dn = Net_LDAP_Util::canonical_dn($dn, array('casefold' => $options['casefold'])); // splitting the DN $dn_array = preg_split('/(?<=[^\\\\]),/', $dn); // construct subarrays for multivalued RDNs and unescape DN value // also convert to output format and apply casefolding foreach ($dn_array as $key => $value) { $value_u = Net_LDAP_Util::unescape_dn_value($value); $rdns = Net_LDAP_Util::split_rdn_multival($value_u[0]); if (count($rdns) > 1) { // MV RDN! foreach ($rdns as $subrdn_k => $subrdn_v) { // Casefolding if ($options['casefold'] == 'upper') { $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtoupper('\\1').''", $subrdn_v); } if ($options['casefold'] == 'lower') { $subrdn_v = preg_replace("/^(\w+=)/e", "''.strtolower('\\1').''", $subrdn_v); } if ($options['onlyvalues']) { preg_match('/(.+?)(?", ";", "#", "=" with a special meaning in RFC 2252 * are preceeded by ba backslash. Control characters with an ASCII code < 32 are represented as \hexpair. * Finally all leading and trailing spaces are converted to sequences of \20. * * @param array $values An array containing the DN values that should be escaped * * @static * @return array The array $values, but escaped */ function escape_dn_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters $val = str_replace('\\', '\\\\', $val); $val = str_replace(',', '\,', $val); $val = str_replace('+', '\+', $val); $val = str_replace('"', '\"', $val); $val = str_replace('<', '\<', $val); $val = str_replace('>', '\>', $val); $val = str_replace(';', '\;', $val); $val = str_replace('#', '\#', $val); $val = str_replace('=', '\=', $val); // ASCII < 32 escaping $val = Net_LDAP_Util::asc2hex32($val); // Convert all leading and trailing spaces to sequences of \20. if (preg_match('/^(\s*)(.+?)(\s*)$/', $val, $matches)) { $val = $matches[2]; for ($i = 0; $i < strlen($matches[1]); $i++) { $val = '\20'.$val; } for ($i = 0; $i < strlen($matches[3]); $i++) { $val = $val.'\20'; } } if (null === $val) { $val = '\0'; // apply escaped "null" if string is empty } $values[$key] = $val; } return $values; } /** * Undoes the conversion done by escape_dn_value(). * * Any escape sequence starting with a baskslash - hexpair or special character - * will be transformed back to the corresponding character. * * @param array $values Array of DN Values * * @return array Same as $values, but unescaped * @static */ function unescape_dn_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // strip slashes from special chars $val = str_replace('\\\\', '\\', $val); $val = str_replace('\,', ',', $val); $val = str_replace('\+', '+', $val); $val = str_replace('\"', '"', $val); $val = str_replace('\<', '<', $val); $val = str_replace('\>', '>', $val); $val = str_replace('\;', ';', $val); $val = str_replace('\#', '#', $val); $val = str_replace('\=', '=', $val); // Translate hex code into ascii $values[$key] = Net_LDAP_Util::hex2asc($val); } return $values; } /** * Returns the given DN in a canonical form * * Returns false if DN is not a valid Distinguished Name. * DN can either be a string or an array * as returned by ldap_explode_dn, which is useful when constructing a DN. * The DN array may have be indexed (each array value is a OCL=VALUE pair) * or associative (array key is OCL and value is VALUE). * * It performs the following operations on the given DN: * - Removes the leading 'OID.' characters if the type is an OID instead of a name. * - Escapes all RFC 2253 special characters (",", "+", """, "\", "<", ">", ";", "#", "="), slashes ("/"), and any other character where the ASCII code is < 32 as \hexpair. * - Converts all leading and trailing spaces in values to be \20. * - If an RDN contains multiple parts, the parts are re-ordered so that the attribute type names are in alphabetical order. * * OPTIONS is a list of name/value pairs, valid options are: * casefold Controls case folding of attribute type names. * Attribute values are not affected by this option. The default is to uppercase. * Valid values are: * lower Lowercase attribute type names. * upper Uppercase attribute type names. This is the default. * none Do not change attribute type names. * [NOT IMPLEMENTED] mbcescape If TRUE, characters that are encoded as a multi-octet UTF-8 sequence will be escaped as \(hexpair){2,*}. * reverse If TRUE, the RDN sequence is reversed. * separator Separator to use between RDNs. Defaults to comma (','). * * Note: The empty string "" is a valid DN, so be sure not to do a "$can_dn == false" test, * because an empty string evaluates to false. Use the "===" operator instead. * * @param array|string $dn The DN * @param array $options Options to use * * @static * @return false|string The canonical DN or FALSE * @todo implement option mbcescape */ function canonical_dn($dn, $options = array('casefold' => 'upper', 'separator' => ',')) { if ($dn === '') { return $dn; // empty DN is valid! } // options check if (!isset($options['reverse'])) { $options['reverse'] = false; } else { $options['reverse'] = true; } if (!isset($options['casefold'])) { $options['casefold'] = 'upper'; } if (!isset($options['separator'])) { $options['separator'] = ','; } if (!is_array($dn)) { // It is not clear to me if the perl implementation splits by the user defined // separator or if it just uses this separator to construct the new DN $dn = preg_split('/(?<=[^\\\\])'.$options['separator'].'/', $dn); // clear wrong splitting (possibly we have split too much) $dn = Net_LDAP_Util::_correct_dn_splitting($dn, $options['separator']); } else { // Is array, check, if the array is indexed or associative $assoc = false; foreach ($dn as $dn_key => $dn_part) { if (!is_int($dn_key)) { $assoc = true; } } // convert to indexed, if associative array detected if ($assoc) { $newdn = array(); foreach ($dn as $dn_key => $dn_part) { if (is_array($dn_part)) { ksort($dn_part, SORT_STRING); // we assume here, that the rdn parts are also associative $newdn[] = $dn_part; // copy array as-is, so we can resolve it later } else { $newdn[] = $dn_key.'='.$dn_part; } } $dn =& $newdn; } } // Escaping and casefolding foreach ($dn as $pos => $dnval) { if (is_array($dnval)) { // subarray detected, this means very surely, that we had // a multivalued dn part, which must be resolved $dnval_new = ''; foreach ($dnval as $subkey => $subval) { // build RDN part if (!is_int($subkey)) { $subval = $subkey.'='.$subval; } $subval_processed = Net_LDAP_Util::canonical_dn($subval); if (false === $subval_processed) { return false; } $dnval_new .= $subval_processed.'+'; } $dn[$pos] = substr($dnval_new, 0, -1); // store RDN part, strip last plus } else { // try to split multivalued RDNS into array $rdns = Net_LDAP_Util::split_rdn_multival($dnval); if (count($rdns) > 1) { // Multivalued RDN was detected! // The RDN value is expected to be correctly split by split_rdn_multival(). // It's time to sort the RDN and build the DN! $rdn_string = ''; sort($rdns, SORT_STRING); // Sort RDN keys alphabetically foreach ($rdns as $rdn) { $subval_processed = Net_LDAP_Util::canonical_dn($rdn); if (false === $subval_processed) { return false; } $rdn_string .= $subval_processed.'+'; } $dn[$pos] = substr($rdn_string, 0, -1); // store RDN part, strip last plus } else { // no multivalued RDN! // split at first unescaped "=" $dn_comp = preg_split('/(?<=[^\\\\])=/', $rdns[0], 2); $ocl = ltrim($dn_comp[0]); // trim left whitespaces 'cause of "cn=foo, l=bar" syntax (whitespace after comma) $val = $dn_comp[1]; // strip 'OID.', otherwise apply casefolding and escaping if (substr(strtolower($ocl), 0, 4) == 'oid.') { $ocl = substr($ocl, 4); } else { if ($options['casefold'] == 'upper') { $ocl = strtoupper($ocl); } if ($options['casefold'] == 'lower') { $ocl = strtolower($ocl); } $ocl = Net_LDAP_Util::escape_dn_value(array($ocl)); $ocl = $ocl[0]; } // escaping of dn-value $val = Net_LDAP_Util::escape_dn_value(array($val)); $val = str_replace('/', '\/', $val[0]); $dn[$pos] = $ocl.'='.$val; } } } if ($options['reverse']) { $dn = array_reverse($dn); } return implode($options['separator'], $dn); } /** * Escapes the given VALUES according to RFC 2254 so that they can be safely used in LDAP filters. * * Any control characters with an ACII code < 32 as well as the characters with special meaning in * LDAP filters "*", "(", ")", and "\" (the backslash) are converted into the representation of a * backslash followed by two hex digits representing the hexadecimal value of the character. * * @param array $values Array of values to escape * * @static * @return array Array $values, but escaped */ function escape_filter_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $val) { // Escaping of filter meta characters $val = str_replace('\\', '\5c', $val); $val = str_replace('*', '\2a', $val); $val = str_replace('(', '\28', $val); $val = str_replace(')', '\29', $val); // ASCII < 32 escaping $val = Net_LDAP_Util::asc2hex32($val); if (null === $val) { $val = '\0'; // apply escaped "null" if string is empty } $values[$key] = $val; } return $values; } /** * Undoes the conversion done by {@link escape_filter_value()}. * * Converts any sequences of a backslash followed by two * hex digits into the corresponding character. * * @param array $values Array of values to escape * * @static * @return array Array $values, but unescaped */ function unescape_filter_value($values = array()) { // Parameter validation if (!is_array($values)) { $values = array($values); } foreach ($values as $key => $value) { // Translate hex code into ascii $values[$key] = Net_LDAP_Util::hex2asc($value); } return $values; } /** * Converts all ASCII chars < 32 to "\HEX" * * @param string $string String to convert * * @static * @return string */ function asc2hex32($string) { for ($i = 0; $i < strlen($string); $i++) { $char = substr($string, $i, 1); if (ord($char) < 32) { $hex = dechex(ord($char)); if (strlen($hex) == 1) { $hex = '0'.$hex; } $string = str_replace($char, '\\'.$hex, $string); } } return $string; } /** * Converts all Hex expressions ("\HEX") to their original ASCII characters * * @param string $string String to convert * * @static * @author beni@php.net, heavily based on work from DavidSmith@byu.net * @return string */ function hex2asc($string) { $string = preg_replace("/\\\([0-9A-Fa-f]{2})/e", "''.chr(hexdec('\\1')).''", $string); return $string; } /** * Split an multivalued RDN value into an Array * * A RDN can contain multiple values, spearated by a plus sign. * This function returns each separate ocl=value pair of the RDN part. * * If no multivalued RDN is detected, a array containing only * the original rdn part is returned. * * For example, the multivalued RDN 'OU=Sales+CN=J. Smith' is exploded to: * array([0] => 'OU=Sales', [1] => 'CN=J. Smith') * * The method trys to be smart if it encounters unescaped "+" characters, but may fail, * so ensure escaped "+"es in attr names and attr values. * * [BUG] If you use string mode and have a multivalued RDN with unescaped plus characters * and there is a unescaped plus sign at the end of an value followed by an * attribute name containing an unescaped plus, then you will get wrong splitting: * $rdn = 'OU=Sales+C+N=J. Smith'; * returns: * array('OU=Sales+C', 'N=J. Smith'); * The "C+" is treaten as value of the first pair instead as attr name of the second pair. * To prevent this, escape correctly. * * @param string $rdn Part of an (multivalued) escaped RDN (eg. ou=foo OR ou=foo+cn=bar) * * @static * @return array Array with the components of the multivalued RDN or Error */ function split_rdn_multival($rdn) { $rdns = preg_split('/(? $dn_value) { $dn_value = $dn[$key]; // refresh value (foreach caches!) // if the dn_value is not in attr=value format, then we had an // unescaped separator character inside the attr name or the value. // We assume, that it was the attribute value. // [TODO] To solve this, we might ask the schema. Keep in mind, that UTIL class // must remain independent from the other classes or connections. if (!preg_match('/.+(? Net_LDAP-1.1.5/LDAP/LDIF.php100644 1750 1750 101446 11223350110 10267 * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: LDIF.php,v 1.33 2008/10/26 15:31:06 clockwerx Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ require_once 'PEAR.php'; require_once 'Net/LDAP.php'; require_once 'Net/LDAP/Entry.php'; require_once 'Net/LDAP/Util.php'; /** * LDIF capabilitys for Net_LDAP, closely taken from PERLs Net::LDAP * * It provides a means to convert between Net_LDAP_Entry objects and LDAP entries * represented in LDIF format files. Reading and writing are supported and may * manipulate single entries or lists of entries. * * Usage example: * * // Read and parse an ldif-file into Net_LDAP_Entry objects * // and print out the DNs. Store the entries for later use. * require 'Net/LDAP/LDIF.php'; * $options = array( * 'onerror' => 'die' * ); * $entries = array(); * $ldif = new Net_LDAP_LDIF('test.ldif', 'r', $options); * do { * $entry = $ldif->read_entry(); * $dn = $entry->dn(); * echo " done building entry: $dn\n"; * array_push($entries, $entry); * } while (!$ldif->eof()); * $ldif->done(); * * * // write those entries to another file * $ldif = new Net_LDAP_LDIF('test.out.ldif', 'w', $options); * $ldif->write_entry($entries); * $ldif->done(); * * * @category Net * @package Net_LDAP * @author Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ * @see http://www.ietf.org/rfc/rfc2849.txt * @todo Error handling should be PEARified * @todo LDAPv3 controls are not implemented yet */ class Net_LDAP_LDIF extends PEAR { /** * Options * * @access private * @var array */ var $_options = array( 'encode' => 'base64', 'onerror' => 'undef', 'change' => 0, 'lowercase' => 0, 'sort' => 0, 'version' => 1, 'wrap' => 78, 'raw' => '' ); /** * Errorcache * * @access private * @var array */ var $_error = array( 'error' => null, 'line' => 0 ); /** * Filehandle for read/write * * @access private * @var array */ var $_FH = null; /** * Says, if we opened the filehandle ourselves * * @access private * @var array */ var $_FH_opened = false; /** * Linecounter for input file handle * * @access private * @var array */ var $_input_line = 0; /** * counter for processed entries * * @access private * @var int */ var $_entrynum = 0; /** * Mode we are working in * * Either 'r', 'a' or 'w' * * @access private * @var string */ var $_mode = false; /** * Tells, if the LDIF version string was already written * * @access private * @var boolean */ var $_version_written = false; /** * Cache for lines that have build the current entry * * @access private * @var boolean */ var $_lines_cur = array(); /** * Cache for lines that will build the next entry * * @access private * @var boolean */ var $_lines_next = array(); /** * Open LDIF file for reading or for writing * * new (FILE): * Open the file read-only. FILE may be the name of a file * or an already open filehandle. * If the file doesn't exist, it will be created if in write mode. * * new (FILE, MODE, OPTIONS): * Open the file with the given MODE (see PHPs fopen()), eg "w" or "a". * FILE may be the name of a file or an already open filehandle. * PERLs Net_LDAP "FILE|" mode does not work curently. * * OPTIONS is an associative array and may contain: * encode => 'none' | 'canonical' | 'base64' * Some DN values in LDIF cannot be written verbatim and have to be encoded in some way: * 'none' No encoding. * 'canonical' See "canonical_dn()" in Net::LDAP::Util. * 'base64' Use base64. (default, this differs from the Perl interface. * The perl default is "none"!) * * onerror => 'die' | 'warn' | undef * Specify what happens when an error is detected. * 'die' Net_LDAP_LDIF will croak with an appropriate message. * 'warn' Net_LDAP_LDIF will warn (echo) with an appropriate message. * undef Net_LDAP_LDIF will not warn (default), use error(). * * change => 1 * Write entry changes to the LDIF file instead of the entries itself. I.e. write LDAP * operations acting on the entries to the file instead of the entries contents. * This writes the changes usually carried out by an update() to the LDIF file. * * lowercase => 1 * Convert attribute names to lowercase when writing. * * sort => 1 * Sort attribute names when writing entries according to the rule: * objectclass first then all other attributes alphabetically sorted by attribute name * * version => '1' * Set the LDIF version to write to the resulting LDIF file. * According to RFC 2849 currently the only legal value for this option is 1. * When this option is set Net_LDAP_LDIF tries to adhere more strictly to * the LDIF specification in RFC2489 in a few places. * The default is undef meaning no version information is written to the LDIF file. * * wrap => 78 * Number of columns where output line wrapping shall occur. * Default is 78. Setting it to 40 or lower inhibits wrapping. * * [NOT IMPLEMENTED] raw => REGEX * Use REGEX to denote the names of attributes that are to be * considered binary in search results if writing entries. * Example: raw => "/(?i:^jpegPhoto|;binary)/i" * * @param string|ressource $file Filename or filehandle * @param string $mode Mode to open filename * @param array $options Options like described above */ function Net_LDAP_LDIF($file, $mode = 'r', $options = array()) { $this->PEAR('Net_LDAP_Error'); // default error class // First, parse options // todo: maybe implement further checks on possible values foreach ($options as $option => $value) { if (!array_key_exists($option, $this->_options)) { $this->_dropError('Net_LDAP_LDIF error: option '.$option.' not known!'); return; } else { $this->_options[$option] = strtolower($value); } } // setup LDIF class $this->version($this->_options['version']); // setup file mode if (!preg_match('/^[rwa]\+?$/', $mode)) { $this->_dropError('Net_LDAP_LDIF error: file mode '.$mode.' not supported!'); } else { $this->_mode = $mode; // setup filehandle if (is_resource($file)) { // TODO: checks on mode possible? $this->_FH =& $file; } else { $imode = substr($this->_mode, 0, 1); if ($imode == 'r') { if (!file_exists($file)) { $this->_dropError('Unable to open '.$file.' for read: file not found'); $this->_mode = false; } if (!is_readable($file)) { $this->_dropError('Unable to open '.$file.' for read: permission denied'); $this->_mode = false; } } if (($imode == 'w' || $imode == 'a')) { if (file_exists($file)) { if (!is_writable($file)) { $this->_dropError('Unable to open '.$file.' for write: permission denied'); $this->_mode = false; } } else { if (!@touch($file)) { $this->_dropError('Unable to create '.$file.' for write: permission denied'); $this->_mode = false; } } } if ($this->_mode) { $this->_FH = @fopen($file, $this->_mode); if (false === $this->_FH) { // Fallback; should never be reached if tests above are good enough! $this->_dropError('Net_LDAP_LDIF error: Could not open file '.$file); } else { $this->_FH_opened = true; } } } } } /** * Read one entry from the file and return it as a Net::LDAP::Entry object. * * @return Net_LDAP_Entry */ function read_entry() { // read fresh lines, set them as current lines and create the entry $attrs = $this->next_lines(true); if (count($attrs) > 0) { $this->_lines_cur = $attrs; } return $this->current_entry(); } /** * Returns true when the end of the file is reached. * * @return boolean */ function eof() { return feof($this->_FH); } /** * Write the entry or entries to the LDIF file. * * If you want to build an LDIF file containing several entries AND * you want to call write_entry() several times, you must open the filehandle * in append mode ("a"), otherwise you will always get the last entry only. * * @param Net_LDAP_Entry|array $entries Entry or array of entries * * @return void * @todo implement operations on whole entries (adding a whole entry) */ function write_entry($entries) { if (!is_array($entries)) { $entries = array($entries); } foreach ($entries as $entry) { $this->_entrynum++; if (!is_a($entry, 'Net_LDAP_Entry')) { $this->_dropError('Net_LDAP_LDIF error: entry '.$this->_entrynum.' is not an Net_LDAP_Entry object'); } else { if ($this->_options['change']) { // LDIF change mode // fetch change information from entry $entry_attrs_changes = $entry->getChanges(); $num_of_changes = count($entry_attrs_changes['add']) + count($entry_attrs_changes['replace']) + count($entry_attrs_changes['delete']); $is_changed = ($num_of_changes > 0 || $entry->willBeDeleted() || $entry->willBeMoved()); // write version if not done yet // also write DN of entry if ($is_changed) { if (!$this->_version_written) { $this->write_version(); } $this->_writeDN($entry->currentDN()); } // process changes // TODO: consider DN add! if ($entry->willBeDeleted()) { $this->_writeLine("changetype: delete".PHP_EOL); } elseif ($entry->willBeMoved()) { $this->_writeLine("changetype: modrdn".PHP_EOL); $olddn = Net_LDAP_Util::ldap_explode_dn($entry->currentDN(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs $oldrdn = array_shift($olddn); $oldparent = implode(',', $olddn); $newdn = Net_LDAP_Util::ldap_explode_dn($entry->dn(), array('casefold' => 'none')); // maybe gives a bug if using multivalued RDNs $rdn = array_shift($newdn); $parent = implode(',', $newdn); $this->_writeLine("newrdn: ".$rdn.PHP_EOL); $this->_writeLine("deleteoldrdn: 1".PHP_EOL); if ($parent !== $oldparent) { $this->_writeLine("newsuperior: ".$parent.PHP_EOL); } // TODO: What if the entry has attribute changes as well? // I think we should check for that and make a dummy // entry with the changes that is written to the LDIF file } elseif ($num_of_changes > 0) { // write attribute change data $this->_writeLine("changetype: modify".PHP_EOL); foreach ($entry_attrs_changes as $changetype => $entry_attrs) { foreach ($entry_attrs as $attr_name => $attr_values) { $this->_writeLine("$changetype: $attr_name".PHP_EOL); if ($attr_values !== null) { $this->_writeAttribute($attr_name, $attr_values, $changetype); } $this->_writeLine("-".PHP_EOL); } } } // finish this entrys data if we had changes if ($is_changed) { $this->_finishEntry(); } } else { // LDIF-content mode // fetch attributes for further processing $entry_attrs = $entry->getValues(); // sort and put objectclass-attrs to first position if ($this->_options['sort']) { ksort($entry_attrs); if (array_key_exists('objectclass', $entry_attrs)) { $oc = $entry_attrs['objectclass']; unset($entry_attrs['objectclass']); $entry_attrs = array_merge(array('objectclass' => $oc), $entry_attrs); } } // write data if (!$this->_version_written) { $this->write_version(); } $this->_writeDN($entry->dn()); foreach ($entry_attrs as $attr_name => $attr_values) { $this->_writeAttribute($attr_name, $attr_values); } $this->_finishEntry(); } } } } /** * Write version to LDIF * * If the object's version is defined, this method allows to explicitely write the version before an entry is written. * If not called explicitely, it gets called automatically when writing the first entry. * * @return void */ function write_version() { $this->_version_written = true; return $this->_writeLine('version: '.$this->version().PHP_EOL, 'Net_LDAP_LDIF error: unable to write version'); } /** * Get or set LDIF version * * If called without arguments it returns the version of the LDIF file or undef if no version has been set. * If called with an argument it sets the LDIF version to VERSION. * According to RFC 2849 currently the only legal value for VERSION is 1. * * @param int $version (optional) LDIF version to set * * @return int */ function version($version = null) { if ($version !== null) { if ($version != 1) { $this->_dropError('Net_LDAP_LDIF error: illegal LDIF version set'); } else { $this->_version = $version; } } return $this->_version; } /** * Returns the file handle the Net_LDAP_LDIF object reads from or writes to. * * You can, for example, use this to fetch the content of the LDIF file yourself * * @return null|resource */ function &handle() { if (!is_resource($this->_FH)) { $this->_dropError('Net_LDAP_LDIF error: invalid file resource'); $null = null; return $null; } else { return $this->_FH; } } /** * Clean up * * This method signals that the LDIF object is no longer needed. * You can use this to free up some memory and close the file handle. * The file handle is only closed, if it was opened from Net_LDAP_LDIF. * * @return void */ function done() { // close FH if we opened it if ($this->_FH_opened) { fclose($this->handle()); } // free variables foreach (get_object_vars($this) as $name => $value) { unset($this->$name); } } /** * Returns last error message if error was found. * * Example: * * $ldif->someAction(); * if ($ldif->error()) { * echo "Error: ".$ldif->error()." at input line: ".$ldif->error_lines(); * } * * * @param boolean $as_string If set to true, only the message is returned * * @return false|Net_LDAP_Error */ function error($as_string = false) { if (Net_LDAP::isError($this->_error['error'])) { return ($as_string)? $this->_error['error']->getMessage() : $this->_error['error']; } else { return false; } } /** * Returns lines that resulted in error. * * Perl returns an array of faulty lines in list context, * but we always just return an int because of PHPs language. * * @return int */ function error_lines() { return $this->_error['line']; } /** * Returns the current Net::LDAP::Entry object. * * @return Net_LDAP_Entry|false */ function current_entry() { return $this->parseLines($this->current_lines()); } /** * Parse LDIF lines of one entry into an Net_LDAP_Entry object * * @param array $lines LDIF lines for one entry * * @return Net_LDAP_Entry|false Net_LDAP_Entry object for those lines * @todo what about file inclusions and urls? "jpegphoto:< file:///usr/local/directory/photos/fiona.jpg" */ function parseLines($lines) { // parse lines into an array of attributes and build the entry $attributes = array(); $dn = false; foreach ($lines as $line) { if (preg_match('/^(\w+)(:|::|:<)\s(.+)$/', $line, $matches)) { $attr =& $matches[1]; $delim =& $matches[2]; $data =& $matches[3]; if ($delim == ':') { // normal data $attributes[$attr][] = $data; } elseif ($delim == '::') { // base64 data $attributes[$attr][] = base64_decode($data); } elseif ($delim == ':<') { // file inclusion // TODO: Is this the job of the LDAP-client or the server? $this->_dropError('File inclusions are currently not supported'); //$attributes[$attr][] = ...; } else { // since the pattern above, the delimeter cannot be something else. $this->_dropError('Net_LDAP_LDIF parsing error: invalid syntax at parsing entry line: '.$line); continue; } if (strtolower($attr) == 'dn') { // DN line detected $dn = $attributes[$attr][0]; // save possibly decoded DN unset($attributes[$attr]); // remove wrongly added "dn: " attribute } } else { // line not in "attr: value" format -> ignore // maybe we should rise an error here, but this should be covered by // next_lines() already. A problem arises, if users try to feed data of // several entries to this method - the resulting entry will // get wrong attributes. However, this is already mentioned in the // methods documentation above. } } if (false === $dn) { $this->_dropError('Net_LDAP_LDIF parsing error: unable to detect DN for entry'); return false; } else { $newentry = Net_LDAP_Entry::createFresh($dn, $attributes); return $newentry; } } /** * Returns the lines that generated the current Net::LDAP::Entry object. * * Note that this returns an empty array if no lines have been read so far. * * @return array Array of lines */ function current_lines() { return $this->_lines_cur; } /** * Returns the lines that will generate the next Net::LDAP::Entry object. * * If you set $force to TRUE then you can iterate over the lines that build * up entries manually. Otherwise, iterating is done using {@link read_entry()}. * Force will move the file pointer forward, thus returning the next entries lines. * * Wrapped lines will be unwrapped. Comments are stripped. * * @param boolean $force Set this to true if you want to iterate over the lines manually * * @return array */ function next_lines($force = false) { // if we already have those lines, just return them, otherwise read if (count($this->_lines_next) == 0 || $force) { $this->_lines_next = array(); // empty in case something was left (if used $force) $entry_done = false; $fh = &$this->handle(); $commentmode = false; // if we are in an comment, for wrapping purposes $datalines_read = 0; // how many lines with data we have read while (!$entry_done && !$this->eof()) { $this->_input_line++; // Read line. Remove line endings, we want only data; // this is okay since ending spaces should be encoded $data = rtrim(fgets($fh)); if ($data === false) { // error only, if EOF not reached after fgets() call if (!$this->eof()) { $this->_dropError('Net_LDAP_LDIF error: error reading from file at input line '.$this->_input_line, $this->_input_line); } break; } else { if (count($this->_lines_next) > 0 && preg_match('/^$/', $data)) { // Entry is finished if we have an empty line after we had data $entry_done = true; // Look ahead if the next EOF is nearby. Comments and empty // lines at the file end may cause problems otherwise $current_pos = ftell($fh); $data = fgets($fh); while (!feof($fh)) { if (preg_match('/^\s*$/', $data) || preg_match('/^#/', $data)) { // only empty lines or comments, continue to seek // TODO: Known bug: Wrappings for comments are okay but are treaten as // error, since we do not honor comment mode here. // This should be a very theoretically case, however // i am willing to fix this if really necessary. $this->_input_line++; $current_pos = ftell($fh); $data = fgets($fh); } else { // Data found if non emtpy line and not a comment!! // Rewind to position prior last read and stop lookahead fseek($fh, $current_pos); break; } } // now we have either the file pointer at the beginning of // a new data position or at the end of file causing feof() to return true } else { // build lines if (preg_match('/^version:\s(.+)$/', $data, $match)) { // version statement, set version $this->version($match[1]); } elseif (preg_match('/^\w+::?\s.+$/', $data)) { // normal attribute: add line $commentmode = false; $this->_lines_next[] = trim($data); $datalines_read++; } elseif (preg_match('/^\s(.+)$/', $data, $matches)) { // wrapped data: unwrap if not in comment mode if (!$commentmode) { if ($datalines_read == 0) { // first line of entry: wrapped data is illegal $this->_dropError('Net_LDAP_LDIF error: illegal wrapping at input line '.$this->_input_line, $this->_input_line); } else { $last = array_pop($this->_lines_next); $last = $last.trim($matches[1]); $this->_lines_next[] = $last; $datalines_read++; } } } elseif (preg_match('/^#/', $data)) { // LDIF comments $commentmode = true; } elseif (preg_match('/^\s*$/', $data)) { // empty line but we had no data for this // entry, so just ignore this line $commentmode = false; } else { $this->_dropError('Net_LDAP_LDIF error: invalid syntax at input line '.$this->_input_line, $this->_input_line); continue; } } } } } return $this->_lines_next; } /** * Convert an attribute and value to LDIF string representation * * It honors correct encoding of values according to RFC 2849. * Line wrapping will occur at the configured maximum but only if * the value is greater than 40 chars. * * @param string $attr_name Name of the attribute * @param string $attr_value Value of the attribute * * @access private * @return string LDIF string for that attribute and value */ function _convertAttribute($attr_name, $attr_value) { // Handle empty attribute or process if (strlen($attr_value) == 0) { $attr_value = " "; } else { $base64 = false; // ASCII-chars that are NOT safe for the // start and for being inside the value. // These are the int values of those chars. $unsafe_init = array(0, 10, 13, 32, 58, 60); $unsafe = array(0, 10, 13); // Test for illegal init char $init_ord = ord(substr($attr_value, 0, 1)); if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) { $base64 = true; } // Test for illegal content char for ($i = 0; $i < strlen($attr_value); $i++) { $char = substr($attr_value, $i, 1); if (ord($char) >= 127 || in_array($init_ord, $unsafe)) { $base64 = true; } } // Test for ending space if (substr($attr_value, -1) == ' ') { $base64 = true; } // If converting is needed, do it if ($base64 && !($this->_options['raw'] && preg_match($this->_options['raw'], $attr_name))) { $attr_name .= ':'; $attr_value = base64_encode($attr_value); } // Lowercase attr names if requested if ($this->_options['lowercase']) { $attr_name = strtolower($attr_name); } // Handle line wrapping if ($this->_options['wrap'] > 40 && strlen($attr_value) > $this->_options['wrap']) { $attr_value = wordwrap($attr_value, $this->_options['wrap'], PHP_EOL." ", true); } } return $attr_name.': '.$attr_value; } /** * Convert an entries DN to LDIF string representation * * It honors correct encoding of values according to RFC 2849. * * @param string $dn UTF8-Encoded DN * * @access private * @return string LDIF string for that DN * @todo I am not sure, if the UTF8 stuff is correctly handled right now */ function _convertDN($dn) { $base64 = false; // ASCII-chars that are NOT safe for the // start and for being inside the dn. // These are the int values of those chars. $unsafe_init = array(0, 10, 13, 32, 58, 60); $unsafe = array(0, 10, 13); // Test for illegal init char $init_ord = ord(substr($dn, 0, 1)); if ($init_ord >= 127 || in_array($init_ord, $unsafe_init)) { $base64 = true; } // Test for illegal content char for ($i = 0; $i < strlen($dn); $i++) { $char = substr($dn, $i, 1); if (ord($char) >= 127 || in_array($init_ord, $unsafe)) { $base64 = true; } } // Test for ending space if (substr($dn, -1) == ' ') { $base64 = true; } // if converting is needed, do it return ($base64)? 'dn:: '.base64_encode($dn) : 'dn: '.$dn; } /** * Writes an attribute to the filehandle * * @param string $attr_name Name of the attribute * @param string|array $attr_values Single attribute value or array with attribute values * * @access private * @return void */ function _writeAttribute($attr_name, $attr_values) { // write out attribute content if (!is_array($attr_values)) { $attr_values = array($attr_values); } foreach ($attr_values as $attr_val) { $line = $this->_convertAttribute($attr_name, $attr_val).PHP_EOL; $this->_writeLine($line, 'Net_LDAP_LDIF error: unable to write attribute '.$attr_name.' of entry '.$this->_entrynum); } } /** * Writes a DN to the filehandle * * @param string $dn DN to write * * @access private * @return void */ function _writeDN($dn) { // prepare DN if ($this->_options['encode'] == 'base64') { $dn = $this->_convertDN($dn).PHP_EOL; } elseif ($this->_options['encode'] == 'canonical') { $dn = Net_LDAP_Util::canonical_dn($dn, array('casefold' => 'none')).PHP_EOL; } else { $dn = $dn.PHP_EOL; } $this->_writeLine($dn, 'Net_LDAP_LDIF error: unable to write DN of entry '.$this->_entrynum); } /** * Finishes an LDIF entry * * @access private * @return void */ function _finishEntry() { $this->_writeLine(PHP_EOL, 'Net_LDAP_LDIF error: unable to close entry '.$this->_entrynum); } /** * Just write an arbitary line to the filehandle * * @param string $line Content to write * @param string $error If error occurs, drop this message * * @access private * @return true|false */ function _writeLine($line, $error = 'Net_LDAP_LDIF error: unable to write to filehandle') { if (is_resource($this->handle()) && fwrite($this->handle(), $line, strlen($line)) === false) { $this->_dropError($error); return false; } else { return true; } } /** * Optionally raises an error and pushes the error on the error cache * * @param string $msg Errortext * @param int $line Line in the LDIF that caused the error * * @access private * @return void */ function _dropError($msg, $line = null) { $this->_error['error'] = new Net_LDAP_Error($msg); if ($line !== null) { $this->_error['line'] = $line; } if ($this->_options['onerror'] == 'die') { die($msg.PHP_EOL); } elseif ($this->_options['onerror'] == 'warn') { echo $msg.PHP_EOL; } } } ?> Net_LDAP-1.1.5/doc/manual.html100644 1750 1750 21135 11223350110 11204 Net_LDAP Manual

Net_LDAP Manual

Welcome to the Net_LDAP user manual! here you have a quick introduction on how to use Net_LDAP to acces your directory server with php.

First step: Connect

To do this, use the Net_LDAP::connect function like this:

require_once('Net_LDAP/LDAP.php');

$config = array (
            'binddn'   => 'uid=tarjei,dc=php,dc=net',
            'bindpw' => 'secret',
            'basedn'   => dc=php,dc=net
          );

$ldap = Net_LDAP::connect($config);

But what are valid values in the config array?

    Here's a quick table: (defaults)
  • binddn = the DN to bind as. (none)
  • bindpw = Password of the user specified by 'binddn' (none)
  • host = the ldap host to connect to (localhost)
  • base = ldap base, this is usually the Entry point of your directory (none)
  • port = the server port (389)
  • starttls = when set, ldap_start_tls() is run after connecting. (false)
  • version = ldap version (defaults to v 3)
  • filter = default search filter (objectclass=*)
  • scope = default search scope (sub)
We'll get back to these later.

Errorhandling

Now you should have the base ldapobject stored in the variable "$ldap". But, what if it is an error? Net_LDAP returns a Net_LDAP_error object (basicly a pear_error object) when an error occurs. So wherever you need to check an error, do like this:

$ldap = Net_LDAP::connect($config); // copied from above! 

if (Net_LDAP::isError($ldap)) {
   print $ldap->getMessage(); // this will tell you what went wrong!
}

Two things to note:
1) The function is_a() might be faster:

if (is_a($ldap,'net_ldap_error')) {
// do the same as above
}

In PHP5 you must use the instanceof operator instead of is_a().
2) Net_LDAP_Error can also return an errornumber. These numbers are standardized. A good description of what they mean is found there: http://www.directory-info.com/LDAP/LDAPErrorCodes.html

Searching (basics)

Most of the work you do on an ldapserver is in searching, for example, you search for your boss's password or his wife's phonenumber.
Searching an ldapserver is a bit like doing SQL and a lot not like it at all.
Think of the directory as some sort of "telephone book". Basically, searches are performed by applying a "filter" to objects under a specific "base" in the directory. Additionally, there is a "scope" applied to the search, so you can specify the recursion level in the directory tree.

Base:

The "base" is the point under the directory where you want to search under. To search for all people under php.net, you may use: "ou=People,dc=php,dc=net". But if you want just to search the devs, you can use "ou=dev,ou=People,dc=php,dc=net".

Filter:

Filters define what you are looking for. They "filter out" unwanted entries.
Filters start with a ( and end with a ). There is a lot to be said about filters, most is better said by examples:

(&(objectclass=posixAccount)(uid=boss)) : The object has to satisfy both filters. I.e. an object that is both boss and an posixAccount. If you had another object with uid=boss but that wasn't an postixaccount it would be excluded.
(|(uid=boss)(uid=secretary)) : Either the boss or the secretary. Note that both logical operators are placed before the filters not between the two conditions as you might used to from sql.
(&(objectclass=posixAccount)(|(uid=boss)(uid=secretary))) : Here they must have the posixAccount objectclass as well. (objectclass=*) : All objects must have an objectclass, so this is the simplest way of saying everything. (uid=t*) : With the right indexes on the server, you may search the substring of an attriute. Here; all users with the first name beginning with a "T".
Please note, that Net_LDAP provides a filter class for simplier generation and combination of filters. You should use that class unless you know how filters work. This will save you a lot of trouble, since there are some encoding issues with ldap-filters. If you want to provide the filter yourself, you should also have a look to RFC #1558 defining LDAP-Filters.

Searchscope

The scope of an search may be three things:
  • 'base' = Just the entry in question.
  • 'sub' = All subentries.
  • 'one' = All entries just below the searchbase.
Searching with scope 'base' may be handy for getting just one entry. But then again, that's what the getEntry function does.

Searching some entries

We know now, how to search, so we will test out our new knowledge. We want to search all person entries whose second name starts with "Ha", but only developers. Later we want to know the name and the telephone number of the persons.
$filter = '(&(objectclass=person)(sn=Ha*))';
$searchbase = 'ou=dev,ou=People,dc=php,dc=net';
$options = array(
               'scope' => 'sub',        // all entries below the searchbase (recursive all subtrees from there)
               'attributes' => array('sn','gn','telephonenumber')  // what attributes to select
           );
$search = $ldap->search($searchbase, $filter, $options);
$search should now be an Net_LDAP_Search object.
Okay, now we assume that everything was fine (in production, test for error!). We have several options now. We can fetch the found entries at once sorted ($search->sorted()) or unsorted ($search->entries()), or we can read the objects one by one inside a loop using $search->shiftEntry(). See the class documentation of Net_LDAP_Search for more details.

Entries

This describes how to get an entry and modifying it. If we just want one single entry, it may be useful to directly fetch that entry instead of searching it manually. To do this you can use Net_LDAPs "getEntry()" method:

$dn = 'cn=Foo Bar,ou=dev,ou=People,dc=php,dc=net';
$entry =& $ldap->getEntry($dn, array('sn','gn','telephonenumber'));

With this entry object you now can perform some actions like fetching the contents of attributes:
$telephonenumber =    $entry->getValue('telephonenumber','single');
Or you can modify a attribute:
$entry->replace("telephonenumber" => "0123456789");   // replace the attributes values with the new number
$entry->update();  // update temporarily modified entry on the server
Of course there are much more other possibilitys. Please note that adding and deleting whole entrys is performed through the Net_LDAP class and not with the Net_LDAP_Entry class.

Schemas

You may also use Net_LDAP to find out what schemas your ldap-server supports. Here's an example of how:
$schema = $ldap->schema();
Now you got a schemaobject. To read from this schemaobject, you have several methods defined in the class Net_LDAP_Schema.
For example, to find out which attributes are required for inetOrgPerson, you do this:
$required = $schema->must( 'inetOrgUser' );

print_r($required);
/* The output of this will be:
Array
(
     [0] => sn
     [1] => cn
)
*/
Ok, but what kind of attribute is sn? Let's check:
$att = $schema->get('attribute','sn');

print_r($att);
/* The output of this will be:
Array
(
     [aliases] => Array
             (
              [0] => surname
             )

     [oid] => 2.5.4.4
     [name] => sn
     [desc] => RFC2256: last (family) name(s) for which the entity is known by
     [sup] => Array
     (
        [0] => name
     )
     [type] => attribute
)
*/
Hmm, ok, the sup part is important. It means that surname derives it's syntax from another attribute, the name attribute. So , we need to check that as well.
We do:
$att_dep = $schema->get('attribute',$att['sup'][0]);

print_r($att_dep);
/* The output of this will be:
Array
(
    [aliases] => Array
        (
        )

    [oid] => 2.5.4.41
    [name] => name
    [desc] => RFC2256: common supertype of name attributes
    [equality] => caseIgnoreMatch
    [substr] => caseIgnoreSubstringsMatch
    [syntax] => 1.3.6.1.4.1.1466.115.121.1.15{32768}
    [max_length] => 32768
    [type] => attribute
)
*/
From this we find out that the attribute has a maxlength of 32768 characters and has the syntax 1.3.6.1.4.1.1466.115.121.1.15{32768}.Net_LDAP-1.1.5/doc/README.txt100644 1750 1750 1301 11223350110 10510 This is my own implementation of some classes for manipulation LDAP-entries in a directory. The classes methods and structure are based on Perls Net::LDAP (see perl-ldap.sf.net). The test.php file shuld provide you with enough examples to do the most basic things. The largest difference between the perl implementation and this one (apart from the fact that all array/list structures are different due to differences in the two languages) is that instead of the method new you'll have to use the method connect() instead. Patches and comments are most welcome! Please submit them via PEARS Bug tracking feature or via mail to one of Net_LDAPs developers. Use unified context diffs if possible! TarjeiNet_LDAP-1.1.5/doc/RootDSE.txt100644 1750 1750 2515 11223350110 11042 First of all connect as usual. (Some servers require authentication to get the RootDSE entry) $config = array( 'host' => 'localhost' ); $ldap = Net_LDAP::connect( $config ); if( Net_LDAP::isError( $ldap ) ) die( $ldap->getMessage() ); Now we can get the entry: $dse = $ldap->rootDSE(); if( Net_LDAP::isError( $dse ) die( $dse->getMessage() ); You can give an array of attributes to fetch as an parameter ro rootDSE(). If none are given these ones are fetched: namingContexts altServer supportedExtension supportedControl supportedSASLMechanisms supportedLDAPVersion subschemaSubentry Then you can work with the object: $basedn = $dse->getValue( 'namingContexts' ); if( $dse->supportedVersion( 3 ) == 3 ) { do_something_only_ldap_v3_can_do(); } Public functions: getValue( string ) get the value of this attribute. same syntax as Net_LDAP_Entry::get_value() supportedControl( oid ) supportedExtension( oid ) check if the given control/extension is supported by the server supportedSASLMechanism( mechanism ) check if the given sasl mechanism is supported by the server supportedVersion( version ) check if the given ldap version is supported by the serve These are alias functions of the above, to make the api perl-ldap compatible. get_value() supported_control() supported_extension() supported_sasl_mechanism() supported_version() Net_LDAP-1.1.5/doc/Schema.txt100644 1750 1750 3315 11223350110 10762 Examples: First of all connect to your server as usual. (Some servers require authentication to get the Subschema entry) $config = array( 'host' => 'localhost' ); $ldap = Net_LDAP::connect( $config ); if( Net_LDAP::isError( $ldap ) ) die ( $ldap->getMessage() ) Then we can get the schema. $schema = $ldap->schema(); if( Net_LDAP::isError( $schema ) ) die ( $schema->getMessage() ); You can give a parameter to $ldap->schema() which sets the Subschema Entry dn. If it is omitted, the entry dn will be fetched internally via rootDSE(). If that fails it will be set to "cn=Subschema". $schema = $ldap->schema( 'cn=Subschema' ); Now you can work with the schema and retrieve information: $attrs = $schema->getAll( 'attributes' ); This returns an array with all attributes and their information such as syntax, equality, max_length etc. Look at the returned array to see what information was passed. Valid options to getAll() are: objectclasses attributes ditcontentrules ditstructurerules matchingrules matchingruleuses nameforms syntaxes If you know the the name of an attribute or objectclass you can get the information directly. $attr = $schema->get('attribute', 'userPassword'); $oc = $schema->get('objectclass', 'posixAccount'); The first parameter determines the type of entry to be fetched and can be one of: attribute ditcontentrule ditstructurerule matchingrule matchingruleuse nameform objectclass syntax The second parameter can be the name or the oid of the entry. You can retrieve a list of required and optional attributes of an object class via must( $oc ) or may( $oc ). Both return a list of attributes in an array. $required = $schema->must( 'posixAccount' ); $optional = $schema->may( 'posixAccount' ); Net_LDAP-1.1.5/doc/utf8.txt100644 1750 1750 1351 11223350110 10446 UTF8 and Net_LDAP: It is hard to know exactly what entries need utf8 every time you need them, so here's the simple way to salvation: Net_LDAP will check internally if utf8 is needed. Code: // $attr is some text a user entered with funny characters in it. // If $attr should not be utfized (f.x. userPassword) then utf8Encode // will not encode the attribute. $attr = $ldap->utf8Encode($attr); // now insert the correctly encoded attribute into the directory. $entry->modify($attr); // later when you access the attributes of that user, decode the ones // that have to be decoded. $attr = $ldap->utf8Decode( $entry->attributes() ); Thanks to Jan for the code. Net_LDAP-1.1.5/doc/examples/connecting.php100644 1750 1750 3017 11223350110 13476 'ldap.example.org', 'host' => array('ldap1.example.org', 'ldap2.example.org'), // 'binddn' => 'cn=admin,o=example,dc=org', // 'bindpw' => 'your-secret-password', 'tls' => false, 'base' => 'o=example,dc=org', 'port' => 389, 'version' => 3, 'filter' => '(cn=*)', 'scope' => 'sub' ); // Connect to configured ldap server $ldap = Net_LDAP::connect($ldap_config); // It is important to check for errors. // Nearly every method of Net_LDAP returns a Net_LDAP_Error object // if something went wrong. Through this object, you can retrieve detailed // information on what exactly happened. // // Here we drop a die with the error message, so the other example // files will not be calles unless we have a valid link. if (Net_LDAP::isError($ldap)) { die('BIND FAILED: '.$ldap->getMessage()); }Net_LDAP-1.1.5/doc/examples/fetch_entry.php100644 1750 1750 2552 11223350110 13664 getEntry('cn=admin,'.$ldap_config['base'], array('gn', 'sn')); // Error checking is important! if (Net_LDAP::isError($entry)) { die('Could not fetch entry: '.$entry->getMessage()); } // Now fetch the data from the entry $surename = $entry->getValue('sn', 'single'); if (Net_LDAP::isError($surename)) { die('Unable to get surename: '.$surename->getMessage()); } $givenname = $entry->getValue('gn', 'single'); if (Net_LDAP::isError($givenname)) { die('Unable to get surename: '.$givenname->getMessage()); } // Finally output the data of the entry: // This will give something like "Name of cn=admin,o=example,dc=org: Foo Bar" echo 'Name of '.$entry->DN().': '.$givenname.' '.$surename; ?>Net_LDAP-1.1.5/doc/examples/search_entries.php100644 1750 1750 5773 11223350110 14360 search($ldap_config['base'], ... $requested_attributes = array('sn','gn','telephonenumber'); $search = $ldap->search(null, $filter, array('attributes' => $requested_attributes)); if (Net_LDAP::isError($search)) { die('LDAP search failed: '.$search->getMessage()); } // Lets see what entries we got and print the names and telephone numbers: if ($search->count() > 0) { echo "Found ".$search->count().' entries:
'; // Note, this is is only one of several ways to fetch entries! // You can also retrieve all entries in an array with // $entries = $search->entries() // or the same thing sorted: // $entries = $search->sorted() while ($entry = $search->shiftEntry()) { $surename = $entry->getValue('sn', 'single'); if (Net_LDAP::isError($surename)) { die('Unable to get surename: '.$surename->getMessage()); } $givenname = $entry->getValue('gn', 'single'); if (Net_LDAP::isError($givenname)) { die('Unable to get givenname: '.$givenname->getMessage()); } $phone = $entry->getValue('telephonenumber', 'single'); if (Net_LDAP::isError($phone)) { die('Unable to get phone number: '.$phone->getMessage()); } echo "
$givenname $surename: $phone"; } } else { die('Sorry, no entries found!'); } ?>Net_LDAP-1.1.5/doc/examples/add_entry.php100644 1750 1750 3715 11223350110 13325 dnExists($dn)) { die('Could not add entry! Entry already exists!'); } // The entry does not exist so far, we can safely add him. // But first, we must construct the entry. // This is, because Net_LDAP was build to make changes only // locally (in your script), not directly on the server. $attributes = array( 'sn' => 'Foo', 'gn' => 'Bar', 'mail' => array('foo@example.org', 'bar@example2.org'), 'employeeNumber' => 123456 ); $new_entry = Net_LDAP_Entry::createFresh($dn, $attributes); // Finally add the entry in the server: $result = $ldap->add($new_entry); if (Net_LDAP::isError($result)) { die('Unable to add entry: '.$result->getMessage()); } // The entry is now present in the directory server. // Additionally, it is linked to the $ldap connection used for the add(), // so you may call $entry->modify() (and friends) and $entry->update() // without the need for passing an $ldap object. // This is only the case if the entry was not linked to an Net_LDAP object // before, so if the entry object would be fetched from a $ldap object // and then added to $ldap_2, the link of the entry remains to $ldap, // thus any update() will be performed on directory1 ($ldap). ?>Net_LDAP-1.1.5/doc/examples/modify_entry.php100644 1750 1750 4246 11223350110 14064 add(array( 'mail' => array('foo@example.org', 'test2@example.org'), 'telephoneNumber' => '1234567890' )); if (Net_LDAP::isError($result)) { die('Unable to add attribute: '.$result->getMessage()); } // Now we modify the first value // Note, that we must give all old values, otherwise the attribute // will be deleted. We specify the new absolute attribute state $result = $entry->replace(array('mail' => array('test1@example.org', 'test2@example.org'))); if (Net_LDAP::isError($result)) { die('Unable to modify attribute: '.$result->getMessage()); } // And now we delete the second attribute value // We must provide the old value, so the ldap server knows, // which value we want to be deleted $result = $entry->delete(array('mail' => 'test2@example.org')); if (Net_LDAP::isError($result)) { die('Unable to delete attribute value: '.$result->getMessage()); } // Finally, we delete the whole attribute 'telephoneNumber': $result = $entry->delete('telephoneNumber'); if (Net_LDAP::isError($result)) { die('Unable to delete attribute: '.$result->getMessage()); } // Now it is time to transfer the changes to the ldap // directory. However, for security reasons, this line is // commented out. /* $result = $entry->update(); if (Net_LDAP::isError($result)) { die('Unable to update entry: '.$result->getMessage()); } */ ?>Net_LDAP-1.1.5/doc/examples/modify_entry2.php100644 1750 1750 4051 11223350110 14140 array( 'mail' => array('foo@example.org', 'test2@example.org'), 'telephoneNumber' => '1234567890' ), 'replace' => array( 'mail' => array('test1@example.org', 'test2@example.org') ), 'delete' => array( 'mail' => 'test2@example.org', 'telephoneNumber' => null // the null value is important here, sincearray ) // mode (indexed, associative) is needed to be homogenous ); // Now it is time to transfer the changes to the ldap // directory. However, for security reasons, these lines // are commented out. // You have two options to carry out the changes, with a small but often // very important difference: // The first call will carry out the actions in the order "add->delete->replace", // while the latter will perform the changes in the order you define. // (add->replace->delete, in our example) /* // METHOD 1: ORDER = add->delete->replace $result = $ldap->modify($entry, $changes); if (Net_LDAP::isError($result)) { die('Unable to update entry: '.$result->getMessage()); } */ /* // METHOD 2: ORDER = add->replace->delete $result = $ldap->modify($entry, array('changes' => $changes)); if (Net_LDAP::isError($result)) { die('Unable to update entry: '.$result->getMessage()); } */ ?> Net_LDAP-1.1.5/tests/AllTests.php100644 1750 1750 2555 11223350110 11667 addTestSuite('Net_LDAP_FilterTest'); $suite->addTestSuite('Net_LDAP_UtilTest'); $suite->addTestSuite('Net_LDAP_LDIFTest'); // LDAP dependent tests (require a LDAP server) $suite->addTestSuite('Net_LDAPTest'); $suite->addTestSuite('Net_LDAP_SearchTest'); $suite->addTestSuite('Net_LDAP_EntryTest'); $suite->addTestSuite('Net_LDAP_RootDSETest'); return $suite; } } // exec test suite if (PHPUnit_MAIN_METHOD == 'Net_LDAP_AllTests::main') { Net_LDAP_AllTests::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAP_EntryTest.php100644 1750 1750 13247 11223350110 13523 markTestSkipped('Test not implemented yet.'); } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. * * @access protected */ protected function tearDown() { } /** * @todo Implement testCreateFresh(). */ public function testCreateFresh() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testDn(). */ public function testDn() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement test_setAttributes(). */ public function test_setAttributes() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGetValues(). */ public function testGetValues() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGetValue(). */ public function testGetValue() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGet_value(). */ public function testGet_value() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testAttributes(). */ public function testAttributes() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testExists(). */ public function testExists() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testAdd(). */ public function testAdd() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testDelete(). */ public function testDelete() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testReplace(). */ public function testReplace() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testUpdate(). */ public function testUpdate() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement test_getAttrName(). */ public function test_getAttrName() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGetLDAP(). */ public function testGetLDAP() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSetLDAP(). */ public function testSetLDAP() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testPreg_match(). */ public function testPreg_match() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } } // Call Net_LDAP_EntryTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_EntryTest::main") { Net_LDAP_EntryTest::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAP_FilterTest.php100644 1750 1750 31112 11223350110 13636 filter_str); $this->assertType('Net_LDAP_Filter', $filter_o); $this->assertEquals($this->filter_str, $filter_o->asString()); $filter_o_err = new Net_LDAP_Filter('some bad filter'); $this->assertType('PEAR_Error', $filter_o_err->_filter); } /** * Test correct parsing of filter strings through parse() */ public function testParse() { $parsed_dmg = Net_LDAP_Filter::parse('some_damaged_filter_str'); $this->assertType('PEAR_Error', $parsed_dmg); $parsed_dmg2 = Net_LDAP_Filter::parse('(invalid=filter)(because=~no-surrounding brackets)'); $this->assertType('PEAR_Error', $parsed_dmg2); $parsed_dmg3 = Net_LDAP_Filter::parse('((invalid=filter)(because=log_op is missing))'); $this->assertType('PEAR_Error', $parsed_dmg3); $parsed_dmg4 = Net_LDAP_Filter::parse('(invalid-because-becauseinvalidoperator)'); $this->assertType('PEAR_Error', $parsed_dmg4); $parsed_dmg5 = Net_LDAP_Filter::parse('(&(filterpart>=ok)(part2=~ok)(filterpart3_notok---becauseinvalidoperator))'); $this->assertType('PEAR_Error', $parsed_dmg5); $parsed = Net_LDAP_Filter::parse($this->filter_str); $this->assertType('Net_LDAP_Filter', $parsed); $this->assertEquals($this->filter_str, $parsed->asString()); } /** * This tests the basic create() method of creating filters */ public function testCreate() { // Test values and an array containing the filter // creating methods and an regex to test the resulting filter $testattr = 'testattr'; $testval = 'testval'; $combinations = array( 'equals' => "/\($testattr=$testval\)/", 'begins' => "/\($testattr=$testval\*\)/", 'ends' => "/\($testattr=\*$testval\)/", 'contains' => "/\($testattr=\*$testval\*\)/", 'greater' => "/\($testattr>$testval\)/", 'less' => "/\($testattr<$testval\)/", 'greaterorequal' => "/\($testattr>=$testval\)/", 'lessorequal' => "/\($testattr<=$testval\)/", 'approx' => "/\($testattr~=$testval\)/", 'any' => "/\($testattr=\*\)/" ); foreach ($combinations as $match => $regex) { // escaping is tested in util class $filter = Net_LDAP_Filter::create($testattr, $match, $testval, false); $this->assertType('Net_LDAP_Filter', $filter); $this->assertRegExp($regex, $filter->asString(), "Filter generation failed for MatchType: $match"); } // test creating failure $filter = Net_LDAP_Filter::create($testattr, 'test_undefined_matchingrule', $testval); $this->assertType('PEAR_Error', $filter); } /** * Tests, if _isLeaf() works */ public function test_isLeaf() { $leaf = Net_LDAP_Filter::create('foo', 'equals', 'bar'); $this->assertType('Net_LDAP_Filter', $leaf); $this->assertTrue($leaf->_isLeaf()); $noleaf = Net_LDAP_Filter::combine('not', $leaf); $this->assertType('Net_LDAP_Filter', $noleaf); $this->assertFalse($noleaf->_isLeaf()); } /** * Tests, if asString() works */ public function testAsString() { $filter = Net_LDAP_Filter::create('foo', 'equals', 'bar'); $this->assertType('Net_LDAP_Filter', $filter); $this->assertEquals('(foo=bar)', $filter->asString()); $this->assertEquals('(foo=bar)', $filter->as_string()); } /** * Tests, if printMe() works */ public function testPrintMe() { if (substr(strtolower(PHP_OS), 0,3) == 'win') { $testfile = '/tmp/Net_LDAP_Filter_printMe-Testfile'; } else { $testfile = 'c:\Net_LDAP_Filter_printMe-Testfile'; } $filter = Net_LDAP_Filter::create('testPrintMe', 'equals', 'ok'); $this->assertType('Net_LDAP_Filter', $filter); // print success: ob_start(); $printresult = $filter->printMe(); ob_end_clean(); $this->assertTrue($printresult); // PrintMe if Filehandle is an error (e.g. if some PEAR-File db is used): $err = new PEAR_Error(); $this->assertType('PEAR_Error', $filter->printMe($err)); // PrintMe if filter is damaged, // $filter_dmg is used below too, to test printing to a file with // damaged filter $filter_dmg = new Net_LDAP_Filter('damaged_filter_string'); // write success: $file = @fopen($testfile, 'w'); if (is_writable($testfile) && $file) { $this->assertTrue($filter->printMe($file)); $this->assertType('PEAR_Error', $filter_dmg->printMe($file)); // dmg. filter @fclose($file); } else { $this->markTestSkipped("$testfile could not be opened in write mode, skipping write test"); } // write failure: $file = @fopen($testfile, 'r'); if (is_writable($testfile) && $file) { $this->assertType('PEAR_Error', $filter->printMe($file)); @fclose($file); @unlink($testfile); } else { $this->markTestSkipped("$testfile could not be opened in read mode, skipping write test"); } } /** * This tests the basic cobination of filters */ public function testCombine() { // Setup $filter0 = Net_LDAP_Filter::create('foo', 'equals', 'bar'); $this->assertType('Net_LDAP_Filter', $filter0); $filter1 = Net_LDAP_Filter::create('bar', 'equals', 'foo'); $this->assertType('Net_LDAP_Filter', $filter1); $filter2 = Net_LDAP_Filter::create('you', 'equals', 'me'); $this->assertType('Net_LDAP_Filter', $filter2); $filter3 = new Net_LDAP_Filter('(perlinterface=used)'); $this->assertType('Net_LDAP_Filter', $filter3); // Negation test $filter_not1 = Net_LDAP_Filter::combine('not', $filter0); $this->assertType('Net_LDAP_Filter', $filter_not1, 'Negation failed for literal NOT'); $this->assertEquals('(!(foo=bar))', $filter_not1->asString()); $filter_not2 = Net_LDAP_Filter::combine('!', $filter0); $this->assertType('Net_LDAP_Filter', $filter_not2, 'Negation failed for logical NOT'); $this->assertEquals('(!(foo=bar))', $filter_not2->asString()); $filter_not3 = Net_LDAP_Filter::combine('!', $filter0->asString()); $this->assertType('Net_LDAP_Filter', $filter_not3, 'Negation failed for logical NOT'); $this->assertEquals('(!'.$filter0->asString().')', $filter_not3->asString()); // Combination test: OR $filter_comb_or1 = Net_LDAP_Filter::combine('or', array($filter1, $filter2)); $this->assertType('Net_LDAP_Filter', $filter_comb_or1, 'Combination failed for literal OR'); $this->assertEquals('(|(bar=foo)(you=me))', $filter_comb_or1->asString()); $filter_comb_or2 = Net_LDAP_Filter::combine('|', array($filter1, $filter2)); $this->assertType('Net_LDAP_Filter', $filter_comb_or2, 'combination failed for logical OR'); $this->assertEquals('(|(bar=foo)(you=me))', $filter_comb_or2->asString()); // Combination test: AND $filter_comb_and1 = Net_LDAP_Filter::combine('and', array($filter1, $filter2)); $this->assertType('Net_LDAP_Filter', $filter_comb_and1, 'Combination failed for literal AND'); $this->assertEquals('(&(bar=foo)(you=me))', $filter_comb_and1->asString()); $filter_comb_and2 = Net_LDAP_Filter::combine('&', array($filter1, $filter2)); $this->assertType('Net_LDAP_Filter', $filter_comb_and2, 'combination failed for logical AND'); $this->assertEquals('(&(bar=foo)(you=me))', $filter_comb_and2->asString()); // Combination test: using filter created with perl interface $filter_comb_perl1 = Net_LDAP_Filter::combine('and', array($filter1, $filter3)); $this->assertType('Net_LDAP_Filter', $filter_comb_perl1, 'Combination failed for literal AND'); $this->assertEquals('(&(bar=foo)(perlinterface=used))', $filter_comb_perl1->asString()); $filter_comb_perl2 = Net_LDAP_Filter::combine('&', array($filter1, $filter3)); $this->assertType('Net_LDAP_Filter', $filter_comb_perl2, 'combination failed for logical AND'); $this->assertEquals('(&(bar=foo)(perlinterface=used))', $filter_comb_perl2->asString()); // Combination test: using filter_str instead of object $filter_comb_fstr1 = Net_LDAP_Filter::combine('and', array($filter1, '(filter_str=foo)')); $this->assertType('Net_LDAP_Filter', $filter_comb_fstr1, 'Combination failed for literal AND using filter_str'); $this->assertEquals('(&(bar=foo)(filter_str=foo))', $filter_comb_fstr1->asString()); // Combination test: deep combination $filter_comp_deep = Net_LDAP_Filter::combine('and',array($filter2, $filter_not1, $filter_comb_or1, $filter_comb_perl1)); $this->assertType('Net_LDAP_Filter', $filter_comp_deep, 'Deep combination failed!'); $this->assertEquals('(&(you=me)(!(foo=bar))(|(bar=foo)(you=me))(&(bar=foo)(perlinterface=used)))', $filter_comp_deep->AsString()); // Test failure in combination $damaged_filter = Net_LDAP_Filter::create('foo', 'test_undefined_matchingrule', 'bar'); $this->assertType('PEAR_Error', $damaged_filter); $filter_not_dmg0 = Net_LDAP_Filter::combine('not', $damaged_filter); $this->assertType('PEAR_Error', $filter_not_dmg0); $filter_not_dmg0s = Net_LDAP_Filter::combine('not', 'damaged_filter_str'); $this->assertType('PEAR_Error', $filter_not_dmg0s); $filter_not_dmg1 = Net_LDAP_Filter::combine('not', null); $this->assertType('PEAR_Error', $filter_not_dmg1); $filter_not_dmg2 = Net_LDAP_Filter::combine('and', $filter_not1); $this->assertType('PEAR_Error', $filter_not_dmg2); $filter_not_dmg3 = Net_LDAP_Filter::combine('and', array($filter_not1)); $this->assertType('PEAR_Error', $filter_not_dmg3); $filter_not_dmg4 = Net_LDAP_Filter::combine('and', $filter_not1); $this->assertType('PEAR_Error', $filter_not_dmg4); $filter_not_dmg5 = Net_LDAP_Filter::combine('or', array($filter_not1)); $this->assertType('PEAR_Error', $filter_not_dmg5); $filter_not_dmg5 = Net_LDAP_Filter::combine('some_unknown_method', array($filter_not1)); $this->assertType('PEAR_Error', $filter_not_dmg5); $filter_not_dmg6 = Net_LDAP_Filter::combine('and', array($filter_not1, 'some_invalid_filterstring')); $this->assertType('PEAR_Error', $filter_not_dmg6); $filter_not_dmg7 = Net_LDAP_Filter::combine('and', array($filter_not1, $damaged_filter)); $this->assertType('PEAR_Error', $filter_not_dmg7); $filter_not_dmg8 = Net_LDAP_Filter::combine('and', array($filter_not1, null)); $this->assertType('PEAR_Error', $filter_not_dmg8); } } // Call Net_LDAP_FilterTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_FilterTest::main") { Net_LDAP_FilterTest::main(); } ?>Net_LDAP-1.1.5/tests/Net_LDAP_RootDSETest.php100644 1750 1750 10750 11223350110 13675 markTestSkipped('Test not implemented yet.'); } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. * * @access protected */ protected function tearDown() { } /** * @todo Implement testGetValue(). */ public function testGetValue() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGet_value(). */ public function testGet_value() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupportedExtension(). */ public function testSupportedExtension() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupported_extension(). */ public function testSupported_extension() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupportedVersion(). */ public function testSupportedVersion() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupported_version(). */ public function testSupported_version() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupportedControl(). */ public function testSupportedControl() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupported_control(). */ public function testSupported_control() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupportedSASLMechanism(). */ public function testSupportedSASLMechanism() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSupported_sasl_mechanism(). */ public function testSupported_sasl_mechanism() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement test_checkAttr(). */ public function test_checkAttr() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } } // Call Net_LDAP_RootDSETest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_RootDSETest::main") { Net_LDAP_RootDSETest::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAP_SearchTest.php100644 1750 1750 17167 11223350110 13634 ldapcfg = $this->getTestConfig(); } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. * * @access protected */ protected function tearDown() { } /** * This checks if a valid LDAP testconfig is present and loads it. * * If so, it is loaded and returned as array. If not, false is returned. * * @return false|array */ public function getTestConfig() { $config = false; $file = dirname(__FILE__).'/ldapconfig.ini'; if (file_exists($file) && is_readable($file)) { $config = parse_ini_file($file, true); } else { return false; } // validate ini $v_error = $file.' is probably invalid. Did you quoted values correctly?'; $this->assertTrue(array_key_exists('global', $config), $v_error); $this->assertTrue(array_key_exists('test', $config), $v_error); $this->assertEquals(7, count($config['global']), $v_error); $this->assertEquals(7, count($config['test']), $v_error); // reformat things a bit, for convinience $config['global']['server_binddn'] = $config['global']['server_binddn'].','.$config['global']['server_base_dn']; $config['test']['existing_attrmv'] = explode('|', $config['test']['existing_attrmv']); return $config; } /** * Establishes a working connection * * @return Net_LDAP */ public function &connect() { // Check extension if (true !== Net_LDAP::checkLDAPExtension()) { $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.'); } // Simple working connect and privilegued bind $lcfg = array( 'host' => $this->ldapcfg['global']['server_address'], 'port' => $this->ldapcfg['global']['server_port'], 'basedn' => $this->ldapcfg['global']['server_base_dn'], 'binddn' => $this->ldapcfg['global']['server_binddn'], 'bindpw' => $this->ldapcfg['global']['server_bindpw'], 'filter' => '(ou=*)', ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!'); return $ldap; } /** * @todo Implement testEntries(). */ public function testEntries() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testShiftEntry(). */ public function testShiftEntry() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testShift_entry(). */ public function testShift_entry() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testPopEntry(). */ public function testPopEntry() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testPop_entry(). */ public function testPop_entry() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSorted_as_struct(). */ public function testSorted_as_struct() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSorted(). */ public function testSorted() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testAs_struct(). */ public function testAs_struct() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSetSearch(). */ public function testSetSearch() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testSetLink(). */ public function testSetLink() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testCount(). */ public function testCount() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testGetErrorCode(). */ public function testGetErrorCode() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement test_Net_LDAP_Search(). */ public function test_Net_LDAP_Search() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement testDone(). */ public function testDone() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } /** * @todo Implement test_searchedAttrs(). */ public function test_searchedAttrs() { // Remove the following line when you implement this test. $this->markTestIncomplete( "This test has not been implemented yet." ); } } // Call Net_LDAP_SearchTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_SearchTest::main") { Net_LDAP_SearchTest::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAPTest.php100644 1750 1750 100724 11223350110 12357 markTestSkipped("This test requires the LDAP extension"); } $this->ldapcfg = $this->getTestConfig(); } /** * Tears down the fixture, for example, close a network connection. * This method is called after a test is executed. * * @access protected */ protected function tearDown() { } /** * This checks if a valid LDAP testconfig is present and loads it. * * If so, it is loaded and returned as array. If not, false is returned. * * @return false|array */ public function getTestConfig() { $config = false; $file = dirname(__FILE__).'/ldapconfig.ini'; if (file_exists($file) && is_readable($file)) { $config = parse_ini_file($file, true); } else { return false; } // validate ini $v_error = $file.' is probably invalid. Did you quoted values correctly?'; $this->assertTrue(array_key_exists('global', $config), $v_error); $this->assertTrue(array_key_exists('test', $config), $v_error); $this->assertEquals(7, count($config['global']), $v_error); $this->assertEquals(7, count($config['test']), $v_error); // reformat things a bit, for convinience $config['global']['server_binddn'] = $config['global']['server_binddn'].','.$config['global']['server_base_dn']; $config['test']['existing_attrmv'] = explode('|', $config['test']['existing_attrmv']); return $config; } /** * Establishes a working connection * * @return Net_LDAP */ public function &connect() { // Check extension if (true !== Net_LDAP::checkLDAPExtension()) { $this->markTestSkipped('PHP LDAP extension not found or not loadable. Skipped Test.'); } // Simple working connect and privilegued bind $lcfg = array( 'host' => $this->ldapcfg['global']['server_address'], 'port' => $this->ldapcfg['global']['server_port'], 'basedn' => $this->ldapcfg['global']['server_base_dn'], 'binddn' => $this->ldapcfg['global']['server_binddn'], 'bindpw' => $this->ldapcfg['global']['server_bindpw'], 'filter' => '(ou=*)', ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!'); return $ldap; } /* ---------- TESTS ---------- */ /** * testCheckLDAPExtension(). * * @todo can we unload modules at runtime?? */ public function testCheckLDAPExtension() { if (extension_loaded('ldap')) { // If extension is already loaded, then we must get true. $this->assertTrue(Net_LDAP::checkLDAPExtension()); } else { // If not, we should be able to load it - but may fail $this->assertThat(Net_LDAP::checkLDAPExtension(), $this->logicalOr($this->isInstanceOf('Net_LDAP_Error'), $this->equalTo(true))); } } /** * Tests if getVersion() works correctly */ public function testGetVersion() { $this->assertTrue(defined('NET_LDAP_VERSION')); $this->assertEquals(NET_LDAP_VERSION, Net_LDAP::getVersion()); } /** * Tests if the server can connect and bind correctly */ public function testConnectAndPrivileguedBind() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { // This connect is supposed to fail $lcfg = array( 'host' => 'pear.net-ldap.test.hostunknown.cno', ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP_Error', $ldap, 'Connect succeeded but was supposed to fail!'); // Failing with multiple hosts $lcfg = array( 'host' => array('pear.net-ldap.test.hostunknown1.cno', 'pear.net-ldap.test.hostunknown2.cno'), ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP_Error', $ldap, 'Connect succeeded but was supposed to fail!'); // Simple working connect and privilegued bind $ldap =& $this->connect(); // Working connect and privilegued bind with first host down $lcfg = array( 'host' => array( 'pear.net-ldap.test.hostunknown1.cno', $this->ldapcfg['global']['server_address'] ), 'port' => $this->ldapcfg['global']['server_port'], 'binddn' => $this->ldapcfg['global']['server_binddn'], 'bindpw' => $this->ldapcfg['global']['server_bindpw'], ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!'); } } /** * Tests if the server can connect and bind anonymously, if supported (->cfg and ldap mode) */ public function testConnectAndAnonymousBind() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } elseif ($this->ldapcfg['global']['server_cap_anonymous'] == true) { // Simple working connect and anonymous bind $lcfg = array( 'host' => $this->ldapcfg['global']['server_address'], 'port' => $this->ldapcfg['global']['server_port'], ); $ldap = Net_LDAP::connect($lcfg); $this->assertType('Net_LDAP', $ldap, 'Connect failed but was supposed to work. Check address and if server supports anonymous bind. If those are correct, file a bug!'); } else { $this->markTestSkipped('Server does not support anonymous bind (see ldapconfig.ini). Skipping test.'); } } /** * testStartTLS() if server supports it */ public function testStartTLS() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } elseif ($this->ldapcfg['global']['server_cap_tls'] == true) { // Simple working connect and privilegued bind $lcfg = array( 'host' => $this->ldapcfg['global']['server_address'], 'port' => $this->ldapcfg['global']['server_port'], 'binddn' => $this->ldapcfg['global']['server_binddn'].','.$this->ldapcfg['global']['server_binddn'], 'bindpw' => $this->ldapcfg['global']['server_bindpw'], 'starttls' => true ); $ldap = Net_LDAP::connect(); $this->assertType('Net_LDAP', $ldap, 'Connect failed but was supposed to work. Check credentials and host address. If those are correct, file a bug!'); } else { $this->markTestSkipped('Server does not support TLS (see ldapconfig.ini). Skipping test.'); } } /** * Test if adding and deleting a fresh entry works */ public function testAdd() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // Adding a fresh entry $cn = 'Net-LDAP-TestEntry'; $dn = 'cn='.$cn.','.$this->ldapcfg['global']['server_base_dn']; $fresh_entry = Net_LDAP_Entry::createFresh($dn, array( 'objectClass' => array('top','person'), 'cn' => $cn, 'sn' => 'TestEntry' ) ); $this->assertType('Net_LDAP_Entry', $fresh_entry); $this->assertTrue($ldap->add($fresh_entry)); // Deleting this Entry $this->assertTrue($ldap->delete($fresh_entry), 'Deletion of entry failed!'); } } /** * testDelete(). * * Basic deletion is tested in testAdd(), so here we just test if * advanced deletion tasks work properly. */ public function testDelete() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // some parameter checks $this->assertType('Net_LDAP_Error', $ldap->delete(1234)); $this->assertType('Net_LDAP_Error', $ldap->delete($ldap)); // in order to test subtree deletion, we need some little tree // which we need to establish first $base = $this->ldapcfg['global']['server_base_dn']; $testdn = 'ou=Net_LDAP_Test_subdelete,'.$base; $ou = Net_LDAP_Entry::createFresh($testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_subdelete' )); $ou_1 = Net_LDAP_Entry::createFresh('ou=test1,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'test1' )); $ou_1_l1 = Net_LDAP_Entry::createFresh('l=subtest,ou=test1,'.$testdn, array( 'objectClass' => array('top','locality'), 'l' => 'test1' )); $ou_2 = Net_LDAP_Entry::createFresh('ou=test2,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'test2' )); $ou_3 = Net_LDAP_Entry::createFresh('ou=test3,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'test3' )); $this->assertTrue($ldap->add($ou)); $this->assertTrue($ldap->add($ou_1)); $this->assertTrue($ldap->add($ou_1_l1)); $this->assertTrue($ldap->add($ou_2)); $this->assertTrue($ldap->add($ou_3)); $this->assertTrue($ldap->dnExists($ou->dn())); $this->assertTrue($ldap->dnExists($ou_1->dn())); $this->assertTrue($ldap->dnExists($ou_1_l1->dn())); $this->assertTrue($ldap->dnExists($ou_2->dn())); $this->assertTrue($ldap->dnExists($ou_3->dn())); // Tree established now. We can run some tests now :D // Try to delete some non existent entry inside that subtree (fails) $this->assertType('Net_LDAP_Error', $ldap->delete( 'cn=not_existent,ou=test1,'.$testdn)); // Try to delete main test ou without recursive set (fails too) $this->assertType('Net_LDAP_Error', $ldap->delete($testdn)); // Retry with subtree delete, this should work $this->assertTrue($ldap->delete($testdn, true)); // The DN is not allowed to exist anymore $this->assertFalse($ldap->dnExists($testdn)); } } /** * testModify(). */ public function testModify() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // We need a test entry: $local_entry = Net_LDAP_Entry::createFresh( 'ou=Net_LDAP_Test_modify,'.$this->ldapcfg['global']['server_base_dn'], array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_modify', 'street' => 'Beniroad', 'telephoneNumber' => array('1234', '5678'), 'postalcode' => '12345', 'postalAddress' => 'someAddress', 'facsimileTelephoneNumber' => array('123','456') )); $this->assertTrue($ldap->add($local_entry)); $this->assertTrue($ldap->dnExists($local_entry->dn())); // Prepare some changes $changes = array( 'add' => array( 'businessCategory' => array('foocat', 'barcat'), 'description' => 'testval' ), 'delete' => array('postalAddress'), 'replace' => array('telephoneNumber' => array('345', '567')), 'changes' => array( 'replace' => array('street' => 'Highway to Hell'), 'add' => array('l' => 'someLocality'), 'delete' => array( 'postalcode', 'facsimileTelephoneNumber' => array('123')) ) ); // run them $this->assertTrue($ldap->modify($local_entry, $changes)); // verify correct attribute changes $actual_entry = $ldap->getEntry($local_entry->dn(), array( 'objectClass', 'ou','postalAddress', 'street', 'telephoneNumber', 'postalcode', 'facsimileTelephoneNumber', 'l', 'businessCategory', 'description')); $this->assertType('Net_LDAP_Entry', $actual_entry); $expected_attributes = array( 'objectClass' => array('top', 'organizationalUnit'), 'ou' => 'Net_LDAP_Test_modify', 'street' => 'Highway to Hell', 'l' => 'someLocality', 'telephoneNumber' => array('345', '567'), 'businessCategory' => array('foocat', 'barcat'), 'description' => 'testval', 'facsimileTelephoneNumber' => '456' ); $local_attributes = $local_entry->getValues(); $actual_attributes = $actual_entry->getValues(); // to enable easy check, we need to sort the // values of the remaining multival attrs as // well as the attribute names ksort($expected_attributes); ksort($local_attributes); ksort($actual_attributes); sort($expected_attributes['businessCategory']); sort($local_attributes['businessCategory']); sort($actual_attributes['businessCategory']); // cleanup directory $this->assertTrue($ldap->delete($actual_entry), 'Cleanup of test entry failed. Please remove manually: '.$local_entry->dn()); // The attributes must match the expected values. // Both, the entry inside the directory and our // apps local copy must reflect the same values $this->assertEquals($expected_attributes, $actual_attributes, 'The directory entries attributes are not OK!'); $this->assertEquals($expected_attributes, $local_attributes, 'The local entries attributes are not OK!'); } } /** * testSearch(). */ public function testSearch() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // some testdata, so we can test sizelimit $base = $this->ldapcfg['global']['server_base_dn']; $ou1 = Net_LDAP_Entry::createFresh('ou=Net_LDAP_Test_search1,'.$base, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_search1' )); $ou2 = Net_LDAP_Entry::createFresh('ou=Net_LDAP_Test_search2,'.$base, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_search2' )); $this->assertTrue($ldap->add($ou1)); $this->assertTrue($ldap->dnExists($ou1->dn())); $this->assertTrue($ldap->add($ou2)); $this->assertTrue($ldap->dnExists($ou2->dn())); // Search for testfilter, should at least return our two test entries $res = $ldap->search(null, '(ou=Net_LDAP*)', array('attributes' => '1.1') ); $this->assertType('Net_LDAP_Search', $res); $this->assertThat($res->count(), $this->greaterThanOrEqual(2)); // Same, but with Net_LDAP_Filter object $filtero = Net_LDAP_Filter::create('ou', 'begins', 'Net_LDAP'); $this->assertType('Net_LDAP_Filter', $filtero); $res = $ldap->search(null, $filtero, array('attributes' => '1.1') ); $this->assertType('Net_LDAP_Search', $res); $this->assertThat($res->count(), $this->greaterThanOrEqual(2)); // Search using default filter for base-onelevel scope // should at least return our two test entries $res = $ldap->search(null, null, array('scope' => 'one', 'attributes' => '1.1') ); $this->assertType('Net_LDAP_Search', $res); $this->assertThat($res->count(), $this->greaterThanOrEqual(2)); // Search using default filter for base-onelevel scope with sizelimit // should of course return more than one entry, // but not more than sizelimit $res = $ldap->search(null, null, array('scope' => 'one', 'sizelimit' => 1, 'attributes' => '1.1') ); $this->assertType('Net_LDAP_Search', $res); $this->assertEquals(1, $res->count()); $this->assertTrue($res->sizeLimitExceeded()); // sizelimit should be exceeded now // Bad filter $res = $ldap->search(null, 'somebadfilter', array('attributes' => '1.1') ); $this->assertType('Net_LDAP_Error', $res); // Bad base $res = $ldap->search('badbase', null, array('attributes' => '1.1') ); $this->assertType('Net_LDAP_Error', $res); // Nullresult $res = $ldap->search(null, '(cn=nevermatching_filter)', array('scope' => 'base', 'attributes' => '1.1') ); $this->assertType('Net_LDAP_Search', $res); $this->assertEquals(0, $res->count()); // cleanup $this->assertTrue($ldap->delete($ou1), 'Cleanup failed, please delete manually'); $this->assertTrue($ldap->delete($ou2), 'Cleanup failed, please delete manually'); } } /** * @todo Implement testSetOption(). */ public function testSetOption() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $this->markTestIncomplete("This test has not been implemented yet."); } } /** * @todo Implement testGetOption(). */ public function testGetOption() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $this->markTestIncomplete("This test has not been implemented yet."); } } /** * @todo Implement testGetLDAPVersion(). */ public function testGetLDAPVersion() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $this->markTestIncomplete("This test has not been implemented yet."); } } /** * @todo Implement testSetLDAPVersion(). */ public function testSetLDAPVersion() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $this->markTestIncomplete("This test has not been implemented yet."); } } /** * testDnExists(). */ public function testDnExists() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $dn = $this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn']; // Testing existing and not existing DN; neither should produce an error $this->assertTrue($ldap->dnExists($dn)); $this->assertFalse($ldap->dnExists('cn=not_existent,'.$dn)); } } /** * testGetEntry(). */ public function testGetEntry() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $dn = $this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn']; // existing DN $this->assertType('Net_LDAP_Entry', $ldap->getEntry($dn), "$dn was supposed to be found. Please check your ldapconfig.ini!"); // Not existing DN $this->assertType('Net_LDAP_Error', $ldap->getEntry('cn=notexistent,'.$this->ldapcfg['global']['server_base_dn'])); } } /** * testMove(). */ public function testMove() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // For Moving tests, we need some little tree again $base = $this->ldapcfg['global']['server_base_dn']; $testdn = 'ou=Net_LDAP_Test_moves,'.$base; $ou = Net_LDAP_Entry::createFresh($testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_moves' )); $ou_1 = Net_LDAP_Entry::createFresh('ou=source,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'source' )); $ou_1_l1 = Net_LDAP_Entry::createFresh('l=moveitem,ou=source,'.$testdn, array( 'objectClass' => array('top','locality'), 'l' => 'moveitem', 'description' => 'movetest' )); $ou_2 = Net_LDAP_Entry::createFresh('ou=target,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'target' )); $ou_3 = Net_LDAP_Entry::createFresh('ou=target_otherdir,'.$testdn, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'target_otherdir' )); $this->assertTrue($ldap->add($ou)); $this->assertTrue($ldap->add($ou_1)); $this->assertTrue($ldap->add($ou_1_l1)); $this->assertTrue($ldap->add($ou_2)); $this->assertTrue($ldap->add($ou_3)); $this->assertTrue($ldap->dnExists($ou->dn())); $this->assertTrue($ldap->dnExists($ou_1->dn())); $this->assertTrue($ldap->dnExists($ou_1_l1->dn())); $this->assertTrue($ldap->dnExists($ou_2->dn())); $this->assertTrue($ldap->dnExists($ou_3->dn())); // Tree established // Local rename $olddn = $ou_1_l1->currentDN(); $this->assertTrue($ldap->move($ou_1_l1, str_replace('moveitem', 'move_item', $ou_1_l1->dn()))); $this->assertTrue($ldap->dnExists($ou_1_l1->dn())); $this->assertFalse($ldap->dnExists($olddn)); // Local move $olddn = $ou_1_l1->currentDN(); $this->assertTrue($ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn())); $this->assertTrue($ldap->dnExists($ou_1_l1->dn())); $this->assertFalse($ldap->dnExists($olddn)); // Local move backward, with rename // Here we use the DN of the object, to test DN conversion. // Note that this will outdate the object since it does not // has knowledge about the move. $olddn = $ou_1_l1->currentDN(); $newdn = 'l=moveditem,'.$ou_2->dn(); $this->assertTrue($ldap->move($olddn, $newdn)); $this->assertTrue($ldap->dnExists($newdn)); $this->assertFalse($ldap->dnExists($olddn)); $ou_1_l1 = $ldap->getEntry($newdn); // Refetch since the objects DN was outdated // Fake-cross directory move using two separate // links to the same directory. // This other directory is represented by ou=target_otherdir $ldap2 = $this->connect(); $olddn = $ou_1_l1->currentDN(); $this->assertTrue($ldap->move($ou_1_l1, 'l=movedcrossdir,'.$ou_3->dn(), $ldap2)); $this->assertFalse($ldap->dnExists($olddn)); $this->assertTrue($ldap2->dnExists($ou_1_l1->dn())); // Try to move over an existing entry $this->assertType('Net_LDAP_Error', $ldap->move($ou_2, $ou_3->dn(), $ldap2)); // Try cross directory move without providing an valid entry but a DN $this->assertType('Net_LDAP_Error', $ldap->move($ou_1_l1->dn(), 'l=movedcrossdir2,'.$ou_2->dn(), $ldap2)); // Try passing an invalid entry object $this->assertType('Net_LDAP_Error', $ldap->move($ldap, 'l=move_item,'.$ou_2->dn())); // Try passing an invalid ldap object $this->assertType('Net_LDAP_Error', $ldap->move($ou_1_l1, 'l=move_item,'.$ou_2->dn(), $ou_1)); // cleanup test tree $this->assertTrue($ldap->delete($testdn, true), "Could not delete $testdn, please cleanup manually"); } } /** * testCopy(). */ public function testCopy() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); // some testdata... $base = $this->ldapcfg['global']['server_base_dn']; $ou1 = Net_LDAP_Entry::createFresh('ou=Net_LDAP_Test_pool,'.$base, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_copy' )); $ou2 = Net_LDAP_Entry::createFresh('ou=Net_LDAP_Test_tgt,'.$base, array( 'objectClass' => array('top','organizationalUnit'), 'ou' => 'Net_LDAP_Test_copy' )); $this->assertTrue($ldap->add($ou1)); $this->assertTrue($ldap->dnExists($ou1->dn())); $this->assertTrue($ldap->add($ou2)); $this->assertTrue($ldap->dnExists($ou2->dn())); $entry = Net_LDAP_Entry::createFresh('l=cptest,'.$ou1->dn(), array( 'objectClass' => array('top','locality'), 'l' => 'cptest' )); $this->assertTrue($ldap->add($entry)); $this->assertTrue($ldap->dnExists($entry->dn())); // copy over the entry to another tree with rename $entrycp = $ldap->copy($entry, 'l=test_copied,'.$ou2->dn()); $this->assertType('Net_LDAP_Entry', $entrycp); $this->assertNotEquals($entry->dn(), $entrycp->dn()); $this->assertTrue($ldap->dnExists($entrycp->dn())); // copy same again (fails, entry exists) $entrycp_f = $ldap->copy($entry, 'l=test_copied,'.$ou2->dn()); $this->assertType('Net_LDAP_Error', $entrycp_f); // use only DNs to copy (fails) $entrycp = $ldap->copy($entry->dn(), 'l=test_copied2,'.$ou2->dn()); //cleanup $this->assertTrue($ldap->delete($ou1, true)); $this->assertTrue($ldap->delete($ou2, true)); } } /** * testIsError(). */ public function testIsError() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $error = PEAR::raiseError('TestError'); $this->assertTrue(Net_LDAP::isError($error)); $this->assertFalse(Net_LDAP::isError('noerror')); } } /** * checks retrival of RootDSE object */ public function testRootDse() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $this->assertType('Net_LDAP_RootDSE', $ldap->rootDSE()); } } /** * Checks retrival of schema through LDAP object */ public function testSchema() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $this->assertType('Net_LDAP_Schema', $ldap->schema()); } } /** * testUtf8Encode() */ public function testUtf8Encode() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $utf8 = array('cn' => 'this needs utf8: öäü'); $no_utf8 = array('cn' => 'this needs no utf8'); $this->assertNotEquals($utf8, $ldap->utf8Encode($utf8)); $this->assertEquals($no_utf8, $ldap->utf8Encode($no_utf8)); // wrong parameter $this->assertType('Net_LDAP_Error', $ldap->utf8Encode('foobar')); $this->assertType('Net_LDAP_Error', $ldap->utf8Encode(array('foobar'))); } } /** * testUtf8Decode(). */ public function testUtf8Decode() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $entry = $ldap->getEntry($this->ldapcfg['test']['existing_entry'].','.$this->ldapcfg['global']['server_base_dn'], array($this->ldapcfg['test']['utf8_attr'], $this->ldapcfg['test']['noutf8_attr'])); $this->assertType('Net_LDAP_Entry', $entry, 'Unable to fetch test entry, check ldapconfig.ini'); $raw_utf8 = array($this->ldapcfg['test']['utf8_attr'] => $entry->getValue($this->ldapcfg['test']['utf8_attr'], 'single')); $this->assertTrue(is_string($raw_utf8[$this->ldapcfg['test']['utf8_attr']])); $no_utf8 = array($this->ldapcfg['test']['noutf8_attr'] => $entry->getValue($this->ldapcfg['test']['noutf8_attr'], 'single')); $this->assertTrue(is_string($no_utf8[$this->ldapcfg['test']['noutf8_attr']])); $this->assertNotEquals($raw_utf8, $ldap->utf8Decode($raw_utf8)); $this->assertEquals($no_utf8, $ldap->utf8Decode($no_utf8)); // wrong parameter $this->assertType('Net_LDAP_Error', $ldap->utf8Decode('foobar')); $this->assertType('Net_LDAP_Error', $ldap->utf8Decode(array('foobar'))); } } /** * testGetLink(). */ public function testGetLink() { if (!$this->ldapcfg) { $this->markTestSkipped('No ldapconfig.ini found. Skipping test!'); } else { $ldap =& $this->connect(); $this->assertTrue(is_resource($ldap->getLink())); } } } // Call Net_LDAPTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAPTest::main") { Net_LDAPTest::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAP_UtilTest.php100644 1750 1750 34723 11223350110 13341 l;u#e=! '; $expected = '\20\20\16 t\,e\+s\"t\,\\\\v\l\;u\#e\=!\20\20\20\20'; // string call $this->assertEquals(array($expected), Net_LDAP_Util::escape_dn_value($dnval)); // array call $this->assertEquals(array($expected), Net_LDAP_Util::escape_dn_value(array($dnval))); // multiple arrays $this->assertEquals(array($expected, $expected, $expected), Net_LDAP_Util::escape_dn_value(array($dnval,$dnval,$dnval))); } /** * Test unescape_dn_value() */ public function testUnescape_dn_value() { $dnval = '\\20\\20\\16\\20t\\,e\\+s \\"t\\,\\\\v\\l\\;u\\#e\\=!\\20\\20\\20\\20'; $expected = ' '.chr(22).' t,e+s "t,\\vl;u#e=! '; // string call $this->assertEquals(array($expected), Net_LDAP_Util::unescape_dn_value($dnval)); // array call $this->assertEquals(array($expected), Net_LDAP_Util::unescape_dn_value(array($dnval))); // multiple arrays $this->assertEquals(array($expected, $expected, $expected), Net_LDAP_Util::unescape_dn_value(array($dnval,$dnval,$dnval))); } /** * Test escaping of filter values */ public function testEscape_filter_value() { $expected = 't\28e,s\29t\2av\5cal\1eue'; $filterval = 't(e,s)t*v\\al'.chr(30).'ue'; // string call $this->assertEquals(array($expected), Net_LDAP_Util::escape_filter_value($filterval)); // array call $this->assertEquals(array($expected), Net_LDAP_Util::escape_filter_value(array($filterval))); // multiple arrays $this->assertEquals(array($expected, $expected, $expected), Net_LDAP_Util::escape_filter_value(array($filterval,$filterval,$filterval))); } /** * Test unescaping of filter values */ public function testUnescape_filter_value() { $expected = 't(e,s)t*v\\al'.chr(30).'ue'; $filterval = 't\28e,s\29t\2av\5cal\1eue'; // string call $this->assertEquals(array($expected), Net_LDAP_Util::unescape_filter_value($filterval)); // array call $this->assertEquals(array($expected), Net_LDAP_Util::unescape_filter_value(array($filterval))); // multiple arrays $this->assertEquals(array($expected, $expected, $expected), Net_LDAP_Util::unescape_filter_value(array($filterval,$filterval,$filterval))); } /** * Test asc2hex32() */ public function testAsc2hex32() { $expected = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $str = ''; for ($i = 0; $i < 127; $i++) { $str .= chr($i); } $this->assertEquals($expected, Net_LDAP_Util::asc2hex32($str)); } /** * Test HEX unescaping */ public function testHex2asc() { $expected = ''; for ($i = 0; $i < 127; $i++) { $expected .= chr($i); } $str = '\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; $this->assertEquals($expected, Net_LDAP_Util::hex2asc($str)); } /** * Test _correct_dn_splitting() */ public function test_correct_dn_splitting() { // normal DN $dn_array = array('cn=John','dc=php','c=net'); $expected = array('cn=John','dc=php','c=net'); $this->assertEquals($expected, Net_LDAP_Util::_correct_dn_splitting($dn_array, ',')); // wrong split at attr name $dn_array = array('c','n=John','dc=php','c=net'); $expected = array('c,n=John','dc=php','c=net'); $this->assertEquals($expected, Net_LDAP_Util::_correct_dn_splitting($dn_array, ',')); // wrong split at attr value $dn_array = array('cn=John','doe','dc=php','c=net'); $expected = array('cn=John,doe','dc=php','c=net'); $this->assertEquals($expected, Net_LDAP_Util::_correct_dn_splitting($dn_array, ',')); // wrong split at both $dn_array = array('cn=John','doe','d', 'c=php','c=net'); $expected = array('cn=John,doe','d,c=php','c=net'); $this->assertEquals($expected, Net_LDAP_Util::_correct_dn_splitting($dn_array, ',')); } /** * Tests split_rdn_multival() */ public function testSplit_rdn_multival() { // One value $rdn = 'CN=J. Smith'; $expected = array('CN=J. Smith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Two values $rdn = 'OU=Sales+CN=J. Smith'; $expected = array('OU=Sales', 'CN=J. Smith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Several multivals $rdn = 'OU=Sales+CN=J. Smith+L=London+C=England'; $expected = array('OU=Sales', 'CN=J. Smith'); $expected = array('OU=Sales', 'CN=J. Smith', 'L=London', 'C=England'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Unescaped "+" in value $rdn = 'OU=Sa+les+CN=J. Smith'; $expected = array('OU=Sa+les', 'CN=J. Smith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Unescaped "+" in attr name $rdn = 'O+U=Sales+CN=J. Smith'; $expected = array('O+U=Sales', 'CN=J. Smith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Unescaped "+" in attr name + value $rdn = 'O+U=Sales+CN=J. Sm+ith'; $expected = array('O+U=Sales', 'CN=J. Sm+ith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Unescaped "+" in attr name, but not first attr // this documents a known bug. However, unfortunately we cant // know wether the "C+" belongs to value "Sales" or attribute "C+N". // To solve this, we must ask the schema which we do not right now. // The problem is located in _correct_dn_splitting() $rdn = 'OU=Sales+C+N=J. Smith'; $expected = array('OU=Sales+C', 'N=J. Smith'); // The "C+" is treaten as value of "OU" $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); // Escaped "+" in attr name and value $rdn = 'O\+U=Sales+CN=J. Sm\+ith'; $expected = array('O\+U=Sales', 'CN=J. Sm\+ith'); $split = Net_LDAP_Util::split_rdn_multival($rdn); $this->assertEquals($expected, $split); } /** * Tests attribute splitting ('foo=bar' => array('foo', 'bar')) */ public function testSplit_attribute_string() { $attr_str = "foo=bar"; // properly $expected = array('foo', 'bar'); $split = Net_LDAP_Util::split_attribute_string($attr_str); $this->assertEquals($expected, $split); // escaped "=" $attr_str = "fo\=o=b\=ar"; $expected = array('fo\=o', 'b\=ar'); $split = Net_LDAP_Util::split_attribute_string($attr_str); $this->assertEquals($expected, $split); // escaped "=" and unescaped = later on $attr_str = "fo\=o=b=ar"; $expected = array('fo\=o', 'b=ar'); $split = Net_LDAP_Util::split_attribute_string($attr_str); $this->assertEquals($expected, $split); } /** * Tests Ldap_explode_dn() */ public function testLdap_explode_dn() { $dn = 'OU=Sales+CN=J. Smith,dc=example,dc=net'; $expected_casefold_none = array( array('CN=J. Smith', 'OU=Sales'), 'dc=example', 'dc=net' ); $expected_casefold_upper = array( array('CN=J. Smith', 'OU=Sales'), 'DC=example', 'DC=net' ); $expected_casefold_lower = array( array('cn=J. Smith', 'ou=Sales'), 'dc=example', 'dc=net' ); $expected_onlyvalues = array( array( 'J. Smith', 'Sales'), 'example', 'net' ); $expected_reverse = array_reverse($expected_casefold_upper); $dn_exploded_cnone = Net_LDAP_Util::ldap_explode_dn($dn, array('casefold' => 'none')); $this->assertEquals($expected_casefold_none, $dn_exploded_cnone, 'Option casefold none failed'); $dn_exploded_cupper = Net_LDAP_Util::ldap_explode_dn($dn, array('casefold' => 'upper')); $this->assertEquals($expected_casefold_upper, $dn_exploded_cupper, 'Option casefold upper failed'); $dn_exploded_clower = Net_LDAP_Util::ldap_explode_dn($dn, array('casefold' => 'lower')); $this->assertEquals($expected_casefold_lower, $dn_exploded_clower, 'Option casefold lower failed'); $dn_exploded_onlyval = Net_LDAP_Util::ldap_explode_dn($dn, array('onlyvalues' => true)); $this->assertEquals($expected_onlyvalues, $dn_exploded_onlyval, 'Option onlyval failed'); $dn_exploded_reverse = Net_LDAP_Util::ldap_explode_dn($dn, array('reverse' => true)); $this->assertEquals($expected_reverse, $dn_exploded_reverse, 'Option reverse failed'); } /** * Tests if canonical_dn() works * * Note: This tests depend on the default options of canonical_dn(). */ public function testCanonical_dn() { // test empty dn (is valid according to rfc) $this->assertEquals('', Net_LDAP_Util::canonical_dn('')); // default options with common dn $testdn = 'cn=beni,DC=php,c=net'; $expected = 'CN=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($testdn)); // casefold tests with common dn $expected_up = 'CN=beni,DC=php,C=net'; $expected_lo = 'cn=beni,dc=php,c=net'; $expected_no = 'cn=beni,DC=php,c=net'; $this->assertEquals($expected_up, Net_LDAP_Util::canonical_dn($testdn, array('casefold' => 'upper'))); $this->assertEquals($expected_lo, Net_LDAP_Util::canonical_dn($testdn, array('casefold' => 'lower'))); $this->assertEquals($expected_no, Net_LDAP_Util::canonical_dn($testdn, array('casefold' => 'none'))); // reverse $expected_rev = 'C=net,DC=php,CN=beni'; $this->assertEquals($expected_rev, Net_LDAP_Util::canonical_dn($testdn, array('reverse' => true)), 'Option reverse failed'); // DN as arrays $dn_index = array('cn=beni', 'dc=php', 'c=net'); $dn_assoc = array('cn' => 'beni', 'dc' => 'php', 'c' => 'net'); $expected = 'CN=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($dn_index)); $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($dn_assoc)); // DN with multiple rdn value $testdn = 'ou=dev+cn=beni,DC=php,c=net'; $testdn_index = array(array('ou=dev','cn=beni'),'DC=php','c=net'); $testdn_assoc = array(array('ou' => 'dev','cn' => 'beni'),'DC' => 'php','c' => 'net'); $expected = 'CN=beni+OU=dev,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($testdn)); $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($testdn_assoc)); $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($expected)); // test DN with OID $testdn = 'OID.2.5.4.3=beni,dc=php,c=net'; $expected = '2.5.4.3=beni,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($testdn)); // test with leading and ending spaces $testdn = 'cn= beni ,DC=php,c=net'; $expected = 'CN=\20\20beni\20\20,DC=php,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($testdn)); // test with to-be escaped characters in attr value $specialchars = array( ',' => '\,', '+' => '\+', '"' => '\"', '\\' => '\\\\', '<' => '\<', '>' => '\>', ';' => '\;', '#' => '\#', '=' => '\=', chr(18) => '\12', '/' => '\/' ); foreach ($specialchars as $char => $escape) { $test_string = 'CN=be'.$char.'ni,DC=ph'.$char.'p,C=net'; $test_index = array('CN=be'.$char.'ni', 'DC=ph'.$char.'p', 'C=net'); $test_assoc = array('CN' => 'be'.$char.'ni', 'DC' => 'ph'.$char.'p', 'C' => 'net'); $expected = 'CN=be'.$escape.'ni,DC=ph'.$escape.'p,C=net'; $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($test_string), 'String escaping test ('.$char.') failed'); $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($test_index), 'Indexed array escaping test ('.$char.') failed'); $this->assertEquals($expected, Net_LDAP_Util::canonical_dn($test_assoc), 'Associative array encoding test ('.$char.') failed'); } } } // Call Net_LDAP_UtilTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_UtilTest::main") { Net_LDAP_UtilTest::main(); } ?> Net_LDAP-1.1.5/tests/Net_LDAP_LDIFTest.php100644 1750 1750 60777 11223350110 13152 'undef', 'encode' => 'base64', 'wrap' => 50, 'change' => 0, 'sort' => 0, ); /** * Test entries data * * Please do not just modify these values, they * are closely related to the LDIF test data. * * @var string */ var $testentries_data = array( 'cn=test1,ou=example,dc=cno' => array( 'cn' => 'test1', 'attr3' => array('foo', 'bar'), 'attr1' => 12345, 'attr4' => 'brrrzztt', 'objectclass' => 'oc1', 'attr2' => array('1234', 'baz')), 'cn=test blabla,ou=example,dc=cno' => array( 'cn' => 'test blabla', 'attr3' => array('foo', 'bar'), 'attr1' => 12345, 'attr4' => 'blablaöäü', 'objectclass' => 'oc2', 'attr2' => array('1234', 'baz'), 'verylong' => 'fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738'), 'cn=test öäü,ou=example,dc=cno' => array( 'cn' => 'test öäü', 'attr3' => array('foo', 'bar'), 'attr1' => 12345, 'attr4' => 'blablaöäü', 'objectclass' => 'oc3', 'attr2' => array('1234', 'baz'), 'attr5' => 'endspace ', 'attr6' => ':badinitchar'), ':cn=endspace,dc=cno ' => array( 'cn' => 'endspace') ); /** * Test file written to * * @var string */ var $outfile = 'test.out.ldif'; /** * Test entries * * They will be created in main() * * @var array */ var $testentries = array(); /** * Runs the test methods of this class. * * @access public * @static */ public static function main() { require_once "PHPUnit/TextUI/TestRunner.php"; $suite = new PHPUnit_Framework_TestSuite("Net_LDAP_LDIFTest"); $result = PHPUnit_TextUI_TestRunner::run($suite); } /** * Open some outfile and ensure correct rights * * @access protected */ protected function setUp() { // initialize test entries $this->testentries = array(); foreach ($this->testentries_data as $dn => $attrs) { $entry = Net_LDAP_Entry::createfresh($dn, $attrs); $this->assertType('Net_LDAP_Entry', $entry, 'ERROR inittializing test entries'); array_push($this->testentries, $entry); } // create outfile if not exists and enforce proper access rights if (!file_exists($this->outfile)) { if (!touch($this->outfile)) { $this->markTestSkipped('Unable to create '.$this->outfile.', skipping test'); } } if (!chmod($this->outfile, 0644)) { $this->markTestSkipped('Unable to chmod(0644) '.$this->outfile.', skipping test'); } } /** * Remove the outfile * * @access protected */ protected function tearDown() { @unlink($this->outfile); } /** * Construction tests * * Construct LDIF object and see if we can get a handle */ public function testConstruction() { $supported_modes = array('r', 'w', 'a'); $plus = array('', '+'); // Test all open modes, // all of them should return a correct handle foreach ($supported_modes as $mode) { foreach ($plus as $p) { $ldif = new Net_LDAP_LDIF($this->outfile, $mode, $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); } } // Test illegal option passing $ldif = new Net_LDAP_LDIF($this->outfile, $mode, array('somebad' => 'option')); $this->assertType('Net_LDAP_Error', $ldif->error()); // Test passing custom handle $handle = fopen($this->outfile, 'r'); $ldif = new Net_LDAP_LDIF($handle, $mode, $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); // Reading test with invalid file mode $ldif = new Net_LDAP_LDIF($this->outfile, 'y', $this->defaultConfig); $this->assertNull($ldif->handle()); $this->assertType('Net_LDAP_Error', $ldif->error()); // Reading test with nonexistent file $ldif = new Net_LDAP_LDIF('some/nonexistent/file_for_net_ldap_ldif', 'r', $this->defaultConfig); $this->assertNull($ldif->handle()); $this->assertType('Net_LDAP_Error', $ldif->error()); // writing to nonexistent file $ldif = new Net_LDAP_LDIF('testfile_for_net_ldap_ldif', 'w', $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); @unlink('testfile_for_net_ldap_ldif'); // writing to nonexistent path $ldif = new Net_LDAP_LDIF('some/nonexistent/file_for_net_ldap_ldif', 'w', $this->defaultConfig); $this->assertNull($ldif->handle()); $this->assertType('Net_LDAP_Error', $ldif->error()); // writing to existing file but without permission // note: chmod should succeed since we do that in setUp() if (chmod($this->outfile, 0444)) { $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $this->defaultConfig); $this->assertNull($ldif->handle()); $this->assertType('Net_LDAP_Error', $ldif->error()); } else { $this->markTestSkipped("Could not chmod ".$this->outfile.", write test without permission skipped"); } } /** * Tests if entries from an LDIF file are correctly constructed */ public function testRead_entry() { /* * UNIX line endings */ $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif', 'r', $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); $entries = array(); do { $entry = $ldif->read_entry(); $this->assertFalse((boolean)$ldif->error(), 'failed building entry from LDIF: '.$ldif->error(true)); $this->assertType('Net_LDAP_Entry', $entry); array_push($entries, $entry); } while (!$ldif->eof()); $this->compareEntries($this->testentries, $entries); /* * Windows line endings */ $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50_WIN.ldif', 'r', $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); $entries = array(); do { $entry = $ldif->read_entry(); $this->assertFalse((boolean)$ldif->error(), 'failed building entry from LDIF: '.$ldif->error(true)); $this->assertType('Net_LDAP_Entry', $entry); array_push($entries, $entry); } while (!$ldif->eof()); $this->compareEntries($this->testentries, $entries); } /** * Tests if entries are correctly written * * This tests converting entries to LDIF lines, wrapping, encoding, etc */ public function testWrite_entry() { $testconf = $this->defaultConfig; /* * test wrapped operation */ $testconf['wrap'] = 50; $testconf['sort'] = 0; $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($this->testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files $this->assertEquals($expected, file($this->outfile)); $testconf['wrap'] = 30; $testconf['sort'] = 0; $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/unsorted_w30.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($this->testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files $this->assertEquals($expected, file($this->outfile)); /* * Test unwrapped operation */ $testconf['wrap'] = 40; $testconf['sort'] = 1; $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/sorted_w40.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($this->testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files $this->assertEquals($expected, file($this->outfile)); $testconf['wrap'] = 50; $testconf['sort'] = 1; $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/sorted_w50.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($this->testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files $this->assertEquals($expected, file($this->outfile)); /* * Test raw option */ $testconf['wrap'] = 50; $testconf['sort'] = 1; $testconf['raw'] = '/attr6/'; $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/sorted_w50.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($this->testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files, with expected attr adjusted $expected[33] = preg_replace('/attr6:: OmJhZGluaXRjaGFy/', 'attr6: :badinitchar', $expected[33]); $this->assertEquals($expected, file($this->outfile)); /* * Test writing with non entry as parameter */ $ldif = new Net_LDAP_LDIF($this->outfile, 'w'); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry('malformed_parameter'); $this->assertTrue((boolean)$ldif->error()); } /** * Round trip test: Read LDIF, parse to entries, write that to LDIF and compare both files */ public function testReadWriteRead() { $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif', 'r', $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); // Read LDIF $entries = array(); do { $entry = $ldif->read_entry(); $this->assertFalse((boolean)$ldif->error(), 'failed building entry from LDIF: '.$ldif->error(true)); $this->assertType('Net_LDAP_Entry', $entry); array_push($entries, $entry); } while (!$ldif->eof()); $ldif->done(); // Write LDIF $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $this->defaultConfig); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($entries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); // Compare files $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); $this->assertEquals($expected, file($this->outfile)); } /** * Tests if entriy changes are correctly written */ public function testWrite_entryChanges() { $testentries = $this->testentries; $testentries[] = Net_LDAP_Entry::createFresh('cn=foo,ou=example,dc=cno', array('cn' => 'foo')); $testentries[] = Net_LDAP_Entry::createFresh('cn=footest,ou=example,dc=cno', array('cn' => 'foo')); $testconf = $this->defaultConfig; $testconf['change'] = 1; /* * no changes should produce empty file */ $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); $this->assertEquals(array(), file($this->outfile)); /* * changes test */ //prepare some changes $testentries[0]->delete('attr1'); // del whole attr $testentries[0]->delete(array('attr2' => 'baz')); // del spec. value $testentries[0]->delete(array('attr4', 'attr3' => 'bar')); // del mixed // prepare some replaces and adds $testentries[2]->replace(array('attr1' => 'newvaluefor1')); $testentries[2]->replace(array('attr2' => array('newvalue1for2', 'newvalue2for2'))); $testentries[2]->replace(array('attr3' => '')); // will result in delete $testentries[2]->replace(array('newattr' => 'foo')); // will result in add // delete whole entry $testentries[3]->delete(); // rename and move $testentries[4]->dn('cn=Bar,ou=example,dc=cno'); $testentries[5]->dn('cn=foobartest,ou=newexample,dc=cno'); // carry out write $ldif = new Net_LDAP_LDIF($this->outfile, 'w', $testconf); $this->assertTrue(is_resource($ldif->handle())); $ldif->write_entry($testentries); $this->assertFalse((boolean)$ldif->error(), 'Failed writing entry to '.$this->outfile.': '.$ldif->error(true)); $ldif->done(); //compare results $expected = array_map('conv_lineend', file(dirname(__FILE__).'/ldif_data/changes.ldif')); // strip 4 starting lines because of comments in the file header: array_shift($expected);array_shift($expected); array_shift($expected);array_shift($expected); $this->assertEquals($expected, file($this->outfile)); } /** * Tests if syntax errors are detected * * The used LDIF files have several damaged entries but always one * correct too, to test if Net_LDAP_LDIF is continue reading as it should * Each Entry must have 2 correct attributes. */ public function testSyntaxerrors() { // Test malformed encoding // I think we can ignore this test, because if the LDIF is not encoded properly, we // might be able to successfully fetch the entries data. However, it is possible // that it will be corrupted, but thats not our fault then. // If we should catch that error, we must adjust Net_LDAP_LDIF::next_lines(). /* $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/malformed_encoding.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); $entries = array(); do { $entry = $ldif->read_entry(); if ($entry) { // the correct attributes need to be parsed $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2)); $entries[] = $entry; } } while (!$ldif->eof()); $this->assertTrue((boolean)$ldif->error()); $this->assertThat($ldif->error_lines(), $this->greaterThan(1)); $this->assertThat(count($entries), $this->equalTo(1)); */ // Test malformed syntax $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/malformed_syntax.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); $entries = array(); do { $entry = $ldif->read_entry(); if ($entry) { // the correct attributes need to be parsed $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2)); $entries[] = $entry; } } while (!$ldif->eof()); $this->assertTrue((boolean)$ldif->error()); $this->assertThat($ldif->error_lines(), $this->greaterThan(1)); $this->assertThat(count($entries), $this->equalTo(2)); // test bad wrapping $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/malformed_wrapping.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); $entries = array(); do { $entry = $ldif->read_entry(); if ($entry) { // the correct attributes need to be parsed $this->assertThat(count(array_keys($entry->getValues())), $this->equalTo(2)); $entries[] = $entry; } } while (!$ldif->eof()); $this->assertTrue((boolean)$ldif->error()); $this->assertThat($ldif->error_lines(), $this->greaterThan(1)); $this->assertThat(count($entries), $this->equalTo(2)); } /** * Test error dropping functionality */ public function testError() { // NO error: $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); // Error giving error msg and line number: $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/some_not_existing/path/for/net_ldap_ldif', 'r', $this->defaultConfig); $this->assertTrue((boolean)$ldif->error()); $this->assertType('Net_LDAP_Error', $ldif->error()); $this->assertType('string', $ldif->error(true)); $this->assertType('int', $ldif->error_lines()); $this->assertThat(strlen($ldif->error(true)), $this->greaterThan(0)); // Test for line number reporting $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/malformed_syntax.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); do { $entry = $ldif->read_entry(); } while (!$ldif->eof()); $this->assertTrue((boolean)$ldif->error()); $this->assertThat($ldif->error_lines(), $this->greaterThan(1)); } /** * Tests current_lines() and next_lines(). * * This should always return the same lines unless forced */ public function testLineMethods() { $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); $this->assertEquals(array(), $ldif->current_lines(), 'Net_LDAP_LDIF initialization error!'); // read first lines $lines = $ldif->next_lines(); $this->assertFalse((boolean)$ldif->error(), 'unable to read first lines'); // read the first lines several times and test for ($i = 0; $i <= 10; $i++) { $r_lines = $ldif->next_lines(); $this->assertFalse((boolean)$ldif->error()); $this->assertEquals($lines, $r_lines); } // now force to iterate and see if the content changes $r_lines = $ldif->next_lines(true); $this->assertFalse((boolean)$ldif->error()); $this->assertNotEquals($lines, $r_lines); // it could be confusing to some people, but calling // current_entry would not work now, like the description // of the method says. $no_entry = $ldif->current_lines(); $this->assertEquals(array(), $no_entry); } /** * Tests current_entry(). This should always return the same object */ public function testCurrent_entry() { $ldif = new Net_LDAP_LDIF(dirname(__FILE__).'/ldif_data/unsorted_w50.ldif', 'r', $this->defaultConfig); $this->assertFalse((boolean)$ldif->error()); // read first entry $entry = $ldif->read_entry(); $this->assertFalse((boolean)$ldif->error(), 'First entry failed: '.$ldif->error(true)); // test if current_Entry remains the first one for ($i = 0; $i <= 10; $i++) { $e = $ldif->current_entry(); $this->assertFalse((boolean)$ldif->error(), $ldif->error(true)); $this->assertEquals($entry, $e); } } /** * Compare Net_LDAP_Entries * * This helper function compares two entries (or array of entries) * and tells if they are equal. They are equal if all DNs from * the first crowd exist in the second AND each attribute is present * and equal at the respicitve entry. * The search is case sensitive. * * @param array|Net_LDAP_Entry $entry1 * @param array|Net_LDAP_Entry $entry2 * @return true|false */ function compareEntries($entry1, $entry2) { if (!is_array($entry1)) $entry1 = array($entry1); if (!is_array($entry2)) $entry2 = array($entry2); $entries_data1 = array(); $entries_data2 = array(); // step 1: extract and sort data foreach ($entry1 as $e) { $values = $e->getValues(); foreach ($values as $attr_name => $attr_values) { if (!is_array($attr_values)) $attr_values = array($attr_values); $values[$attr_name] = $attr_values; } $entries_data1[$e->dn()] = $values; } foreach ($entry2 as $e) { $values = $e->getValues(); foreach ($values as $attr_name => $attr_values) { if (!is_array($attr_values)) $attr_values = array($attr_values); $values[$attr_name] = $attr_values; } $entries_data2[$e->dn()] = $values; } // step 2: compare DNs (entries) $this->assertEquals(array_keys($entries_data1), array_keys($entries_data2), 'Entries DNs not equal! (missing entry or wrong DN)'); // step 3: look for attribute existence and compare values foreach ($entries_data1 as $dn => $attributes) { $this->assertEquals($entries_data1[$dn], $entries_data2[$dn], 'Entries '.$dn.' attributes are not equal'); foreach ($attributes as $attr_name => $attr_values) { $this->assertEquals(0, count(array_diff($entries_data1[$dn][$attr_name], $entries_data2[$dn][$attr_name])), 'Entries '.$dn.' attribute '.$attr_name.' values are not equal'); } } return true; } } // Call Net_LDAP_LDIFTest::main() if this source file is executed directly. if (PHPUnit_MAIN_METHOD == "Net_LDAP_LDIFTest::main") { Net_LDAP_LDIFTest::main(); } if (!function_exists('conv_lineend')) { /** * Function transfers line endings to current OS * * This is neccessary to make write tests platform indendent. * * @param string $line Line * @return string */ function conv_lineend($line) { return rtrim($line).PHP_EOL; } } ?>Net_LDAP-1.1.5/tests/ldapconfig.ini.dist100644 1750 1750 5050 11223350110 13165 ; +---------------------------------------------------------------------------+ ; | This is a configuration file for Net_LDAPs tests. | ; | To activate the tests, you need to adjust the configuration to | ; | your needs and copy it over to "ldapconfig.conf". Place the | ; | file inside the directory holding all the test programs. | ; | | ; | Note, that this tests assume a working LDAP server. | ; | If you don't heave such a server, you may use the | ; | LDIF based mock server. Head to 'ldapldifconfig.ini.dist'. | ; | | ; | The syntax of this file is really easy and similar to other .ini-files. | ; | Remember to quote strings containing non-alphanumeric characters. | ; +---------------------------------------------------------------------------+ ; Global section ; server_cap_tls: is the server ssl capable? ; server_cap_anonymous: is the server allowing anonymous access? ; server_base_dn: BaseDN at which your DIT starts. Ensure that the ; binding user has all rights to add, delete and ; modifying entries as well as creating and deleting ; some subtree (ou=...) below the base. ; server_*: I think those following are self-explanatory ; server_binddn: Relative to base_dn below [global] server_cap_tls = false server_cap_anonymous = true server_base_dn = "ou=example,dc=cno" server_address = localhost server_port = 389 server_binddn = "cn=testuser" server_bindpw = testpass ; Definitions for the tests. Adjust to suit the needs of your test server. ; existing_entry: RDN for an entry that must exist (relative to base). ; The following attribute tests are performed on this entry. ; existing_attrs: Name of an existing attribute (single valued) ; existing_attrsv: Value of this attribute ; existing_attrm: Name of an existing attribute (multi valued) ; existing_attrmv: Values of this attribute, "|" delimited ; utf8_attr: Name of attribute with UTF8 value ; noutf8_attr: Name of attribute without UTF8 value [test] existing_entry = "cn=existing" existing_attrs = attr1 existing_attrsv = 12345 existing_attrm = attr2 existing_attrmv = "1234|baz" utf8_attr = attr4 noutf8_attr = attr1 Net_LDAP-1.1.5/tests/ldapldifconfig.ini.dist100644 1750 1750 2216 11223350110 14025 ; +---------------------------------------------------------------------------+ ; | This is a configuration file for Net_LDAPLDIFs tests. | ; | To activate the tests, you need to adjust the configuration to | ; | your needs and copy it over to "ldapldifconfig.conf". Place the | ; | file inside the directory holding all the test programs. | ; | | ; | Note, that this tests assumes a correct LDIF file. | ; | By default, one of the test sets will be used. | ; | | ; | The syntax of this file is really easy and similar to other .ini-files. | ; | Remember to quote strings containing non-alphanumeric characters. | ; +---------------------------------------------------------------------------+ ; /!\ IMOPORTANT NOTE: THE LDIF BASED MOCK SERVER IS NOT IMPLEMENTED SO FAR! /!\ ; This file is just here for later use and a reminder. Just ignore it so far ; unless you are implementing tests :)Net_LDAP-1.1.5/tests/ldif_data/malformed_encoding.ldif100644 1750 1750 447 11223350110 15764 # # This is a LDIF file to test encoding failure # # unencoded DN version: 1 dn: cn=testöäü,ou=example,dc=cno objectclass: oc1 # unencoded attr value version: 1 dn: cn=test2,ou=example,dc=cno objectclass: testöäü cn: test2 # entry ok version: 1 dn: cn=test,ou=example,dc=cno objectclass: oc1 Net_LDAP-1.1.5/tests/ldif_data/malformed_syntax.ldif100644 1750 1750 506 11223350110 15520 # # This is a LDIF file to test syntax error # # wrong syntax (space too less at val of objectclass) dn: cn=test1,ou=example,dc=cno objectclass:oc1 cn: test1 attr3: foo # wrong syntax (no DN given) objectclass:oc1 cn: test_invalid attr3: foo # entry ok version: 1 dn: cn=test3,ou=example,dc=cno objectclass: oc1 attr3: foo Net_LDAP-1.1.5/tests/ldif_data/malformed_wrapping.ldif100644 1750 1750 1000 11223350110 16027 # # This is a LDIF file to test wrapping failure # # wrong wrapping (entry must fail because DN is damaged): # (note, that there must eb an empty line below this comment, otherwise # the DN line is treaten as wrapped comment) dn: cn=test1,ou=example,dc=cno objectclass: oc1 cn: test1 # wrong syntax (literal line but no wrapped content) dn: cn=test2,ou=example,dc=cno objectclass:oc1 cn: test2 some_wrong_literal_line attr3: foo # entry ok version: 1 dn: cn=test,ou=example,dc=cno objectclass: oc1 cn: test Net_LDAP-1.1.5/tests/ldif_data/sorted_w40.ldif100644 1750 1750 1417 11223350110 14160 # # This is a LDIF file to test reading capabilitys # It was created using options: sort=1, wrap=40 # version: 1 dn: cn=test1,ou=example,dc=cno objectclass: oc1 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4: brrrzztt cn: test1 dn: cn=test blabla,ou=example,dc=cno objectclass: oc2 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4:: YmxhYmxh9uT8 cn: test blabla verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738 dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= objectclass: oc3 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4:: YmxhYmxh9uT8 attr5:: ZW5kc3BhY2Ug attr6:: OmJhZGluaXRjaGFy cn:: dGVzdCD25Pw= dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= cn: endspace Net_LDAP-1.1.5/tests/ldif_data/sorted_w50.ldif100644 1750 1750 1423 11223350110 14156 # # This is a LDIF file to test reading capabilitys # It was created using options: sort=1, wrap=50 # version: 1 dn: cn=test1,ou=example,dc=cno objectclass: oc1 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4: brrrzztt cn: test1 dn: cn=test blabla,ou=example,dc=cno objectclass: oc2 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4:: YmxhYmxh9uT8 cn: test blabla verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8 h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb 5789thvngwr789cghm738 dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= objectclass: oc3 attr1: 12345 attr2: 1234 attr2: baz attr3: foo attr3: bar attr4:: YmxhYmxh9uT8 attr5:: ZW5kc3BhY2Ug attr6:: OmJhZGluaXRjaGFy cn:: dGVzdCD25Pw= dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= cn: endspace Net_LDAP-1.1.5/tests/ldif_data/unsorted_w30.ldif100644 1750 1750 1417 11223350110 14522 # # This is a LDIF file to test reading capabilitys # It was created using options: sort=0, wrap=30 # version: 1 dn: cn=test1,ou=example,dc=cno cn: test1 attr3: foo attr3: bar attr1: 12345 attr4: brrrzztt objectclass: oc1 attr2: 1234 attr2: baz dn: cn=test blabla,ou=example,dc=cno cn: test blabla attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc2 attr2: 1234 attr2: baz verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb5789thvngwr789cghm738 dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= cn:: dGVzdCD25Pw= attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc3 attr2: 1234 attr2: baz attr5:: ZW5kc3BhY2Ug attr6:: OmJhZGluaXRjaGFy dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= cn: endspace Net_LDAP-1.1.5/tests/ldif_data/unsorted_w50.ldif100644 1750 1750 1423 11223350110 14521 # # This is a LDIF file to test reading capabilitys # It was created using options: sort=0, wrap=50 # version: 1 dn: cn=test1,ou=example,dc=cno cn: test1 attr3: foo attr3: bar attr1: 12345 attr4: brrrzztt objectclass: oc1 attr2: 1234 attr2: baz dn: cn=test blabla,ou=example,dc=cno cn: test blabla attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc2 attr2: 1234 attr2: baz verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8 h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb 5789thvngwr789cghm738 dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= cn:: dGVzdCD25Pw= attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc3 attr2: 1234 attr2: baz attr5:: ZW5kc3BhY2Ug attr6:: OmJhZGluaXRjaGFy dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= cn: endspace Net_LDAP-1.1.5/tests/ldif_data/unsorted_w50_WIN.ldif100644 1750 1750 1530 11223350110 15235 # # This is a LDIF file to test reading capabilitys with WINDOWS line endings # It was created using options: sort=0, wrap=50 # version: 1 dn: cn=test1,ou=example,dc=cno cn: test1 attr3: foo attr3: bar attr1: 12345 attr4: brrrzztt objectclass: oc1 attr2: 1234 attr2: baz dn: cn=test blabla,ou=example,dc=cno cn: test blabla attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc2 attr2: 1234 attr2: baz verylong: fhu08rhvt7b478vt5hv78h45nfgt45h78t34hhhhhhhhhv5bg8 h6ttttttttt3489t57nhvgh4788trhg8999vnhtgthgui65hgb 5789thvngwr789cghm738 dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= cn:: dGVzdCD25Pw= attr3: foo attr3: bar attr1: 12345 attr4:: YmxhYmxh9uT8 objectclass: oc3 attr2: 1234 attr2: baz attr5:: ZW5kc3BhY2Ug attr6:: OmJhZGluaXRjaGFy dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= cn: endspace Net_LDAP-1.1.5/tests/ldif_data/changes.ldif100644 1750 1750 1211 11223350110 13566 # # This is a LDIF file to test writing changes of entries # # version: 1 dn: cn=test1,ou=example,dc=cno changetype: modify delete: attr1 - delete: attr2 attr2: baz - delete: attr4 - dn:: Y249dGVzdCD25Pwsb3U9ZXhhbXBsZSxkYz1jbm8= changetype: modify add: newattr newattr: foo - delete: attr3 - replace: attr1 attr1: newvaluefor1 - replace: attr2 attr2: newvalue1for2 attr2: newvalue2for2 - dn:: OmNuPWVuZHNwYWNlLGRjPWNubyA= changetype: delete dn: cn=foo,ou=example,dc=cno changetype: modrdn newrdn: cn=Bar deleteoldrdn: 1 dn: cn=footest,ou=example,dc=cno changetype: modrdn newrdn: cn=foobartest deleteoldrdn: 1 newsuperior: ou=newexample,dc=cno Net_LDAP-1.1.5/LDAP.php100644 1750 1750 141206 11223350110 7547 * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @version CVS: $Id: LDAP.php,v 1.95 2009/07/03 09:32:25 beni Exp $ * @link http://pear.php.net/package/Net_LDAP/ */ /** * Package includes. */ require_once 'PEAR.php'; require_once 'LDAP/RootDSE.php'; require_once 'Net/LDAP/Schema.php'; require_once 'LDAP/Entry.php'; require_once 'LDAP/Search.php'; require_once 'LDAP/Util.php'; require_once 'LDAP/Filter.php'; require_once 'LDAP/LDIF.php'; /** * Error constants for errors that are not LDAP errors. */ define('NET_LDAP_ERROR', 1000); /** * Net_LDAP Version */ define('NET_LDAP_VERSION', '1.1.5'); /** * Net_LDAP - manipulate LDAP servers the right way! * * @category Net * @package Net_LDAP * @author Tarjej Huse * @author Jan Wagner * @author Del * @author Benedikt Hallinger * @copyright 2003-2007 Tarjej Huse, Jan Wagner, Del Elson, Benedikt Hallinger * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP extends PEAR { /** * Class configuration array * * host = the ldap host to connect to (may be an array of several hosts * to try) * port = the server port * version = ldap version (defaults to v 3) * starttls = when set, ldap_start_tls() is run after connecting. * bindpw = no explanation needed * binddn = the DN to bind as. * basedn = ldap base * options = hash of ldap options to set (opt => val) * filter = default search filter * scope = default search scope * * @access private * @var array */ var $_config = array('host' => 'localhost', 'port' => 389, 'version' => 3, 'starttls' => false, 'binddn' => '', 'bindpw' => '', 'basedn' => '', 'options' => array(), 'filter' => '(objectClass=*)', 'scope' => 'sub'); /** * List of hosts we try to establish a connection to * * @access private * @var array */ var $_host_list = array(); /** * List of hosts that are known to be down. * * @access private * @var array */ var $_down_host_list = array(); /** * LDAP resource link. * * @access private * @var resource */ var $_link = false; /** * Net_LDAP_Schema object * * This gets set and returned by {@link schema()} * * @access private * @var object Net_LDAP_Schema */ var $_schema = null; /** * Cache for attribute encoding checks * * @access private * @var array Hash with attribute names as key and boolean value * to determine whether they should be utf8 encoded or not. */ var $_schemaAttrs = array(); /** * Returns the Net_LDAP Release version, may be called statically * * @static * @return string Net_LDAP version */ function getVersion() { return NET_LDAP_VERSION; } /** * Creates the initial ldap-object * * Static function that returns either an error object or the new Net_LDAP * object. Something like a factory. Takes a config array with the needed * parameters. * * @param array $config Configuration array * * @access public * @return Net_LDAP_Error|Net_LDAP Net_LDAP_Error or Net_LDAP object */ function &connect($config = array()) { $ldap_check = Net_LDAP::checkLDAPExtension(); if (Net_LDAP::iserror($ldap_check)) { return $ldap_check; } @$obj = & new Net_LDAP($config); // todo? better errorhandling for setConfig()? // connect and bind with credentials in config $err = $obj->bind(); if (Net_LDAP::isError($err)) { return $err; } return $obj; } /** * Net_LDAP constructor * * Sets the config array * * Please note that the usual way of getting Net_LDAP to work is * to call something like: * $ldap = Net_LDAP::connect($ldap_config); * * @param array $config Configuration array * * @access protected * @return void * @see $_config */ function Net_LDAP($config = array()) { $this->PEAR('Net_LDAP_Error'); $this->_setConfig($config); } /** * Sets the internal configuration array * * @param array $config Configuration array * * @access private * @return void */ function _setConfig($config) { // // Parameter check -- probably should raise an error here if config // is not an array. // if (! is_array($config)) { return; } foreach ($config as $k => $v) { if (isset($this->_config[$k])) { $this->_config[$k] = $v; } else { // map old (Net_LDAP) parms to new ones switch($k) { case "dn": $this->_config["binddn"] = $v; break; case "password": $this->_config["bindpw"] = $v; break; case "tls": $this->_config["starttls"] = $v; break; case "base": $this->_config["basedn"] = $v; break; } } } // // Ensure the host list is an array. // if (is_array($this->_config['host'])) { $this->_host_list = $this->_config['host']; } else { if (strlen($this->_config['host']) > 0) { $this->_host_list = array($this->_config['host']); } else { // this will cause an error in _connect(), so the user is notified $this->_host_list = array(); } } // // Reset the down host list, which seems like a sensible thing to do // if the config is being reset for some reason. // $this->_down_host_list = array(); } /** * Bind or rebind to the ldap-server * * This function binds with the given dn and password to the server. In case * no connection has been made yet, it will be startet and startTLS issued * if appropiate. * * The internal bind configuration is not being updated, so if you call * bind() without parameters, you can rebind with the credentials * provided at first connecting to the server. * * @param string $dn Distinguished name for binding * @param string $password Password for binding * * @access public * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function bind($dn = null, $password = null) { // fetch current bind credentials if (is_null($dn)) { $dn = $this->_config["binddn"]; } if (is_null($password)) { $password = $this->_config["bindpw"]; } // Connect first, if we haven't so far. // This will also bind us to the server. if ($this->_link === false) { // store old credentials so we can revert them later // then overwrite config with new bind credentials $olddn = $this->_config["binddn"]; $oldpw = $this->_config["bindpw"]; // overwrite bind credentials in config // so _connect() knows about them $this->_config["binddn"] = $dn; $this->_config["bindpw"] = $password; // try to connect with provided credentials $msg = $this->_connect(); // reset to previous config $this->_config["binddn"] = $olddn; $this->_config["bindpw"] = $oldpw; // see if bind worked if (Net_LDAP::isError($msg)) { return $msg; } } else { // do the requested bind as we are // asked to bind manually if (is_null($dn)) { $msg = @ldap_bind($this->_link); // anonymous bind } else { $msg = @ldap_bind($this->_link, $dn, $password); // privilegued bind } if (false === $msg) { return PEAR::raiseError("Bind failed: " . @ldap_error($this->_link), @ldap_errno($this->_link)); } } return true; } /** * Connect to the ldap-server * * This function connects to the given LDAP server. * * @access private * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function _connect() { // // Return true if we are already connected. // if ($this->_link !== false) { return true; } // // Connnect to the LDAP server if we are not connected. Note that // with some LDAP clients, ldap_connect returns a link value even // if no connection is made. We need to do at least one anonymous // bind to ensure that a connection is actually valid. // // Ref: http://www.php.net/manual/en/function.ldap-connect.php // // // Default error message in case all connection attempts fail but no message is set // $current_error = new PEAR_Error('Unknown connection error'); // // Catch empty $_host_list arrays. // if (!is_array($this->_host_list) || count($this->_host_list) == 0) { $current_error = PEAR::raiseError('No Servers configured! Please pass in an array of servers to Net_LDAP2'); return $current_error; } // // Cycle through the host list. // foreach ($this->_host_list as $host) { // // Ensure we have a valid string for host name // if (is_array($host)) { $current_error = PEAR::raiseError('No Servers configured! Please pass in an one dimensional array of servers to Net_LDAP2! (multidimensional array detected!)'); continue; } // // Skip this host if it is known to be down. // if (in_array($host, $this->_down_host_list)) { continue; } // // Record the host that we are actually connecting to in case // we need it later. // $this->_config['host'] = $host; // // Attempt a connection. // $this->_link = @ldap_connect($host, $this->_config['port']); if (false === $this->_link) { $current_error = PEAR::raiseError('Could not connect to ' . $host . ':' . $this->_config['port']); $this->_down_host_list[] = $host; continue; } // // If we're supposed to use TLS, do so before we try to bind. // if ($this->_config["starttls"] === true) { if (self::isError($msg = $this->startTLS())) { $current_error = $msg; $this->_link = false; $this->_down_host_list[] = $host; continue; } } // // Attempt to bind to the server. If we have credentials configured, // we try to use them, otherwise its an anonymous bind. // $msg = $this->bind(); if (self::isError($msg)) { // The bind failed, discard link and save error msg. // Then record the host as down and try next one $this->_link = false; $current_error = $msg; $this->_down_host_list[] = $host; continue; } // // Set LDAP version after we have a bind. // if (self::isError($msg = $this->setLDAPVersion())) { $current_error = $msg; $this->_link = false; $this->_down_host_list[] = $host; continue; } // // Set LDAP parameters, now we know we have a valid connection. // if (isset($this->_config['options']) && is_array($this->_config['options']) && count($this->_config['options'])) { foreach ($this->_config['options'] as $opt => $val) { $err = $this->setOption($opt, $val); if (self::isError($err)) { $current_error = $err; $this->_link = false; $this->_down_host_list[] = $host; continue 2; } } } // // At this stage we have connected, bound, and set up options, // so we have a known good LDAP server. Time to go home. // return true; } // // All connection attempts have failed, return the last error. // return $current_error; } /** * Starts an encrypted session * * @access public * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function startTLS() { if (false === @ldap_start_tls($this->_link)) { return $this->raiseError("TLS not started: " . @ldap_error($this->_link), @ldap_errno($this->_link)); } return true; } /** * alias function of startTLS() for perl-ldap interface * * @return void * @see startTLS() */ function start_tls() { $args = func_get_args(); return call_user_func_array(array( &$this, 'startTLS' ), $args); } /** * Close LDAP connection. * * Closes the connection. Use this when the session is over. * * @return void */ function done() { $this->_Net_LDAP(); } /** * Destructor * * @access private */ function _Net_LDAP() { @ldap_close($this->_link); } /** * Add a new entryobject to a directory. * * Use add to add a new Net_LDAP_Entry object to the directory. * This also links the entry to the connection used for the add, * if it was a fresh entry ({@link Net_LDAP_Entry::createFresh()}) * * @param Net_LDAP_Entry &$entry Net_LDAP_Entry * * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function add(&$entry) { if (false === is_a($entry, 'Net_LDAP_Entry')) { return PEAR::raiseError('Parameter to Net_LDAP::add() must be a Net_LDAP_Entry object.'); } if (@ldap_add($this->_link, $entry->dn(), $entry->getValues())) { // entry successfully added, we should update its $ldap reference // in case it is not set so far (fresh entry) if (!is_a($entry->getLDAP(), 'Net_LDAP')) { $entry->setLDAP($this); } // store, that the entry is present inside the directory $entry->_markAsNew(false); return true; } else { return PEAR::raiseError("Could not add entry " . $entry->dn() . " " . @ldap_error($this->_link), @ldap_errno($this->_link)); } } /** * Delete an entry from the directory * * The object may either be a string representing the dn or a Net_LDAP_Entry * object. When the boolean paramter recursive is set, all subentries of the * entry will be deleted as well. * * @param string|Net_LDAP_Entry $dn DN-string or Net_LDAP_Entry * @param boolean $recursive Should we delete all children recursive as well? * * @access public * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function delete($dn, $recursive = false) { if (is_a($dn, 'Net_LDAP_Entry')) { $dn = $dn->dn(); } if (false === is_string($dn)) { return PEAR::raiseError("Parameter is not a string nor an entry object!"); } // Recursive delete searches for children and calls delete for them if ($recursive) { $result = @ldap_list($this->_link, $dn, '(objectClass=*)', array(null), 0, 0); if (@ldap_count_entries($this->_link, $result)) { $subentry = @ldap_first_entry($this->_link, $result); $this->delete(@ldap_get_dn($this->_link, $subentry), true); while ($subentry = @ldap_next_entry($this->_link, $subentry)) { $this->delete(@ldap_get_dn($this->_link, $subentry), true); } } } // Delete the DN if (false == @ldap_delete($this->_link, $dn)) { $error = @ldap_errno($this->_link); if ($error == 66) { return PEAR::raiseError("Could not delete entry $dn because of subentries. Use the recursive param to delete them."); } else { return PEAR::raiseError("Could not delete entry $dn: " . $this->errorMessage($error), $error); } } return true; } /** * Modify an ldapentry directly on the server * * This one takes the DN or a Net_LDAP_Entry object and an array of actions. * This array should be something like this: * * array('add' => array('attribute1' => array('val1', 'val2'), * 'attribute2' => array('val1')), * 'delete' => array('attribute1'), * 'replace' => array('attribute1' => array('val1')), * 'changes' => array('add' => ..., * 'replace' => ..., * 'delete' => array('attribute1', 'attribute2' => array('val1'))) * * The changes array is there so the order of operations can be influenced * (the operations are done in order of appearance). * The order of execution is as following: * 1. adds from 'add' array * 2. deletes from 'delete' array * 3. replaces from 'replace' array * 4. changes (add, replace, delete) in order of appearance * All subarrays (add, replace, delete, changes) may be given at the same time. * * The function calls the corresponding functions of an Net_LDAP_Entry * object. A detailed description of array structures can be found there. * * Unlike the modification methods provided by the Net_LDAP_Entry object, * this method will instantly carry out an update() after each operation, * thus modifying "directly" on the server. * * @param string|Net_LDAP_Entry &$entry DN-string or Net_LDAP_Entry * @param array $parms Array of changes * * @access public * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function modify(&$entry , $parms = array()) { if (is_string($entry)) { $entry = $this->getEntry($entry); if (Net_LDAP::isError($entry)) { return $entry; } } if (!is_a($entry, 'Net_LDAP_Entry')) { return PEAR::raiseError("Parameter is not a string nor an entry object!"); } foreach (array('add', 'delete', 'replace') as $action) { if (isset($parms[$action])) { $msg = $entry->$action($parms[$action]); if (Net_LDAP::isError($msg)) { return $msg; } $entry->setLDAP($this); $msg = $entry->update(); if (Net_LDAP::isError($msg)) { return PEAR::raiseError("Could not modify entry: ".$msg->getMessage()); } } } if (isset($parms['changes']) && is_array($parms['changes'])) { foreach ($parms['changes'] as $action => $value) { $msg = $this->modify($entry, array($action => $value)); if (Net_LDAP::isError($msg)) { return $msg; } } } return true; } /** * Run a ldap query * * Search is used to query the ldap-database. * $base and $filter may be ommitted.The one from config will then be used. * Params may contain: * * scope: The scope which will be used for searching * base - Just one entry * sub - The whole tree * one - Immediately below $base * sizelimit: Limit the number of entries returned (default: 0 = unlimited), * timelimit: Limit the time spent for searching (default: 0 = unlimited), * attrsonly: If true, the search will only return the attribute names, * attributes: Array of attribute names, which the entry should contain. * It is good practice to limit this to just the ones you need. * [NOT IMPLEMENTED] * deref: By default aliases are dereferenced to locate the base object for the search, but not when * searching subordinates of the base object. This may be changed by specifying one of the * following values: * * never - Do not dereference aliases in searching or in locating the base object of the search. * search - Dereference aliases in subordinates of the base object in searching, but not in * locating the base object of the search. * find * always * * Please note, that you cannot override server side limitations to sizelimit * and timelimit: You can always only lower a given limit. * * @param string $base LDAP searchbase * @param string|Net_LDAP_Filter $filter LDAP search filter or a Net_LDAP_Filter object * @param array $params Array of options * * @access public * @return Net_LDAP_Search|Net_LDAP_Error Net_LDAP_Search object or Net_LDAP_Error object */ function search($base = null, $filter = null, $params = array()) { if (is_null($base)) { $base = $this->_config['basedn']; } if (is_null($filter)) { $filter = $this->_config['filter']; } if (is_a($filter, 'Net_LDAP_Filter')) { $filter = $filter->asString(); // convert Net_LDAP_Filter to string representation } if (PEAR::isError($filter)) { return $filter; } /* setting searchparameters */ $sizelimit = isset($params['sizelimit']) ? $params['sizelimit'] : 0; $timelimit = isset($params['timelimit']) ? $params['timelimit'] : 0; $attrsonly = isset($params['attrsonly']) ? $params['attrsonly'] : 0; $attributes = isset($params['attributes'])? $params['attributes'] : array(); // Ensure $attributes to be an array in case only one // attribute name was given as string if (!is_array($attributes)) { $attributes = array($attributes); } // reorganize the $attributes array index keys // sometimes there are problems with not consecutive indexes $attributes = array_values($attributes); // scoping makes searches faster! $scope = (isset($params['scope']) ? $params['scope'] : $this->_config['scope']); switch ($scope) { case 'one': $search_function = 'ldap_list'; break; case 'base': $search_function = 'ldap_read'; break; default: $search_function = 'ldap_search'; } $search = @call_user_func($search_function, $this->_link, $base, $filter, $attributes, $attrsonly, $sizelimit, $timelimit); if ($err = @ldap_errno($this->_link)) { if ($err == 32) { // Errorcode 32 = no such object, i.e. a nullresult. return $obj = & new Net_LDAP_Search ($search, $this, $attributes); } elseif ($err == 4) { // Errorcode 4 = sizelimit exeeded. return $obj = & new Net_LDAP_Search ($search, $this, $attributes); } elseif ($err == 87) { // bad search filter return $this->raiseError($this->errorMessage($err) . "($filter)", $err); } else { $msg = "\nParameters:\nBase: $base\nFilter: $filter\nScope: $scope"; return $this->raiseError($this->errorMessage($err) . $msg, $err); } } else { return $obj = & new Net_LDAP_Search($search, $this, $attributes); } } /** * Set an LDAP option * * @param string $option Option to set * @param mixed $value Value to set Option to * * @access public * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function setOption($option, $value) { if ($this->_link) { if (defined($option)) { if (@ldap_set_option($this->_link, constant($option), $value)) { return true; } else { $err = @ldap_errno($this->_link); if ($err) { $msg = @ldap_err2str($err); } else { $err = NET_LDAP_ERROR; $msg = $this->errorMessage($err); } return $this->raiseError($msg, $err); } } else { return $this->raiseError("Unkown Option requested"); } } else { return $this->raiseError("Could not set LDAP option: No LDAP connection"); } } /** * Get an LDAP option value * * @param string $option Option to get * * @access public * @return Net_LDAP_Error|string Net_LDAP_Error or option value */ function getOption($option) { if ($this->_link) { if (defined($option)) { if (@ldap_get_option($this->_link, constant($option), $value)) { return $value; } else { $err = @ldap_errno($this->_link); if ($err) { $msg = @ldap_err2str($err); } else { $err = NET_LDAP_ERROR; $msg = $this->errorMessage($err); } return $this->raiseError($msg, $err); } } else { $this->raiseError("Unkown Option requested"); } } else { $this->raiseError("No LDAP connection"); } } /** * Get the LDAP_PROTOCOL_VERSION that is used on the connection. * * A lot of ldap functionality is defined by what protocol version the ldap server speaks. * This might be 2 or 3. * * @return int */ function getLDAPVersion() { if ($this->_link) { $version = $this->getOption("LDAP_OPT_PROTOCOL_VERSION"); } else { $version = $this->_config['version']; } return $version; } /** * Set the LDAP_PROTOCOL_VERSION that is used on the connection. * * @param int $version LDAP-version that should be used * * @return Net_LDAP_Error|true Net_LDAP_Error object or true */ function setLDAPVersion($version = 0) { if (!$version) { $version = $this->_config['version']; } return $this->setOption("LDAP_OPT_PROTOCOL_VERSION", $version); } /** * Tell if a DN does exist in the directory * * @param string $dn The DN of the object to test * * @return boolean|Net_LDAP_Error */ function dnExists($dn) { if (!is_string($dn)) { return PEAR::raiseError('$dn is expected to be a string but is '.gettype($dn).' '.get_class($dn)); } // make dn relative to parent $base = Net_LDAP_Util::ldap_explode_dn($dn, array('casefold' => 'none', 'reverse' => false, 'onlyvalues' => false)); if (Net_LDAP::isError($base)) { return $base; } $entry_rdn = array_shift($base); if (is_array($entry_rdn)) { // maybe the dn consist of a multivalued RDN, we must build the dn in this case // because the $entry_rdn is an array! $filter_dn = Net_LDAP_Util::canonical_dn($entry_rdn); } $base = Net_LDAP_Util::canonical_dn($base); $result = @ldap_list($this->_link, $base, $entry_rdn, array(), 1, 1); if (@ldap_count_entries($this->_link, $result)) { return true; } if (ldap_errno($this->_link) == 32) { return false; } if (ldap_errno($this->_link) != 0) { return PEAR::raiseError(ldap_error($this->_link), ldap_errno($this->_link)); } return false; } /** * Get a specific entry based on the DN * * @param string $dn DN of the entry that should be fetched * @param array $attr Array of Attributes to select * * @return Net_LDAP_Entry|Net_LDAP_Error Reference to a Net_LDAP_Entry object or Net_LDAP_Error object * @todo Maybe check against the shema should be done to be sure the attribute type exists */ function &getEntry($dn, $attr = array()) { if (!is_array($attr)) { $attr = array($attr); } $result = $this->search($dn, '(objectClass=*)', array('scope' => 'base', 'attributes' => $attr)); if (Net_LDAP::isError($result)) { return $result; } elseif ($result->count() == 0) { return PEAR::raiseError('Could not fetch entry '.$dn.': no entry found'); } $entry = $result->shiftEntry(); if (false == $entry) { return PEAR::raiseError('Could not fetch entry (error retrieving entry from search result)'); } return $entry; } /** * Rename or move an entry * * This method will instantly carry out an update() after the move, * so the entry is moved instantly. * You can pass an optional Net_LDAP object. In this case, a cross directory * move will be performed which deletes the entry in the source (THIS) directory * and adds it in the directory $target_ldap. * A cross directory move will switch the Entrys internal LDAP reference so * updates to the entry will go to the new directory. * * Note that if you want to do a cross directory move, you need to * pass an Net_LDAP_Entry object, otherwise the attributes will be empty. * * @param string|Net_LDAP_Entry &$entry Entry DN or Entry object * @param string $newdn New location * @param Net_LDAP $target_ldap (optional) Target directory for cross server move; should be passed via reference * * @return Net_LDAP_Error|true */ function move(&$entry, $newdn, $target_ldap = null) { if (is_string($entry)) { $entry_o = $this->getEntry($entry); } else { $entry_o =& $entry; } if (!is_a($entry_o, 'Net_LDAP_Entry')) { return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP_Entry object! (If DN was passed, conversion failed)'); } if (null !== $target_ldap && !is_a($target_ldap, 'Net_LDAP')) { return PEAR::raiseError('Parameter $target_ldap is expected to be a Net_LDAP object!'); } if ($target_ldap && $target_ldap !== $this) { // cross directory move if (is_string($entry)) { return PEAR::raiseError('Unable to perform cross directory move: operation requires a Net_LDAP_Entry object'); } if ($target_ldap->dnExists($newdn)) { return PEAR::raiseError('Unable to perform cross directory move: entry does exist in target directory'); } $entry_o->dn($newdn); $res = $target_ldap->add($entry_o); if (Net_LDAP::isError($res)) { return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in target directory'); } $res = $this->delete($entry_o->currentDN()); if (Net_LDAP::isError($res)) { $res2 = $target_ldap->delete($entry_o); // undo add if (Net_LDAP::isError($res2)) { $add_error_string = 'Additionally, the deletion (undo add) of $entry in target directory failed.'; } return PEAR::raiseError('Unable to perform cross directory move: '.$res->getMessage().' in source directory. '.$add_error_string); } $entry_o->setLDAP($target_ldap); return true; } else { // local move $entry_o->dn($newdn); $entry_o->setLDAP($this); return $entry_o->update(); } } /** * Copy an entry to a new location * * The entry will be immediately copied. * Please note that only attributes you have * selected will be copied. * * @param Net_LDAP_Entry &$entry Entry object * @param string $newdn New FQF-DN of the entry * * @return Net_LDAP_Error|Net_LDAP_Entry Error Message or reference to the copied entry */ function ©(&$entry, $newdn) { if (!is_a($entry, 'Net_LDAP_Entry')) { return PEAR::raiseError('Parameter $entry is expected to be a Net_LDAP_Entry object!'); } $newentry = Net_LDAP_Entry::createFresh($newdn, $entry->getValues()); $result = $this->add($newentry); if (is_a($result, 'Net_LDAP_Error')) { return $result; } else { return $newentry; } } /** * Returns the string for an ldap errorcode. * * Made to be able to make better errorhandling * Function based on DB::errorMessage() * Tip: The best description of the errorcodes is found here: * http://www.directory-info.com/LDAP/LDAPErrorCodes.html * * @param int $errorcode Error code * * @return string The errorstring for the error. */ function errorMessage($errorcode) { $errorMessages = array( 0x00 => "LDAP_SUCCESS", 0x01 => "LDAP_OPERATIONS_ERROR", 0x02 => "LDAP_PROTOCOL_ERROR", 0x03 => "LDAP_TIMELIMIT_EXCEEDED", 0x04 => "LDAP_SIZELIMIT_EXCEEDED", 0x05 => "LDAP_COMPARE_FALSE", 0x06 => "LDAP_COMPARE_TRUE", 0x07 => "LDAP_AUTH_METHOD_NOT_SUPPORTED", 0x08 => "LDAP_STRONG_AUTH_REQUIRED", 0x09 => "LDAP_PARTIAL_RESULTS", 0x0a => "LDAP_REFERRAL", 0x0b => "LDAP_ADMINLIMIT_EXCEEDED", 0x0c => "LDAP_UNAVAILABLE_CRITICAL_EXTENSION", 0x0d => "LDAP_CONFIDENTIALITY_REQUIRED", 0x0e => "LDAP_SASL_BIND_INPROGRESS", 0x10 => "LDAP_NO_SUCH_ATTRIBUTE", 0x11 => "LDAP_UNDEFINED_TYPE", 0x12 => "LDAP_INAPPROPRIATE_MATCHING", 0x13 => "LDAP_CONSTRAINT_VIOLATION", 0x14 => "LDAP_TYPE_OR_VALUE_EXISTS", 0x15 => "LDAP_INVALID_SYNTAX", 0x20 => "LDAP_NO_SUCH_OBJECT", 0x21 => "LDAP_ALIAS_PROBLEM", 0x22 => "LDAP_INVALID_DN_SYNTAX", 0x23 => "LDAP_IS_LEAF", 0x24 => "LDAP_ALIAS_DEREF_PROBLEM", 0x30 => "LDAP_INAPPROPRIATE_AUTH", 0x31 => "LDAP_INVALID_CREDENTIALS", 0x32 => "LDAP_INSUFFICIENT_ACCESS", 0x33 => "LDAP_BUSY", 0x34 => "LDAP_UNAVAILABLE", 0x35 => "LDAP_UNWILLING_TO_PERFORM", 0x36 => "LDAP_LOOP_DETECT", 0x3C => "LDAP_SORT_CONTROL_MISSING", 0x3D => "LDAP_INDEX_RANGE_ERROR", 0x40 => "LDAP_NAMING_VIOLATION", 0x41 => "LDAP_OBJECT_CLASS_VIOLATION", 0x42 => "LDAP_NOT_ALLOWED_ON_NONLEAF", 0x43 => "LDAP_NOT_ALLOWED_ON_RDN", 0x44 => "LDAP_ALREADY_EXISTS", 0x45 => "LDAP_NO_OBJECT_CLASS_MODS", 0x46 => "LDAP_RESULTS_TOO_LARGE", 0x47 => "LDAP_AFFECTS_MULTIPLE_DSAS", 0x50 => "LDAP_OTHER", 0x51 => "LDAP_SERVER_DOWN", 0x52 => "LDAP_LOCAL_ERROR", 0x53 => "LDAP_ENCODING_ERROR", 0x54 => "LDAP_DECODING_ERROR", 0x55 => "LDAP_TIMEOUT", 0x56 => "LDAP_AUTH_UNKNOWN", 0x57 => "LDAP_FILTER_ERROR", 0x58 => "LDAP_USER_CANCELLED", 0x59 => "LDAP_PARAM_ERROR", 0x5a => "LDAP_NO_MEMORY", 0x5b => "LDAP_CONNECT_ERROR", 0x5c => "LDAP_NOT_SUPPORTED", 0x5d => "LDAP_CONTROL_NOT_FOUND", 0x5e => "LDAP_NO_RESULTS_RETURNED", 0x5f => "LDAP_MORE_RESULTS_TO_RETURN", 0x60 => "LDAP_CLIENT_LOOP", 0x61 => "LDAP_REFERRAL_LIMIT_EXCEEDED", 1000 => "Unknown Net_LDAP Error" ); return isset($errorMessages[$errorcode]) ? $errorMessages[$errorcode] : $errorMessages[NET_LDAP_ERROR] . ' (' . $errorcode . ')'; } /** * Tell whether variable is a Net_LDAP_Error or not * * @param mixed $var A variable, most commonly some Net_LDAP* object * * @access public * @return boolean */ function isError($var) { return (is_a($var, "Net_LDAP_Error") || parent::isError($var)); } /** * Gets a rootDSE object * * @param array $attrs Array of attributes to search for * * @access public * @author Jan Wagner * @return Net_LDAP_Error|Net_LDAP_RootDSE Net_LDAP_Error or Net_LDAP_RootDSE object */ function &rootDse($attrs = null) { if (is_array($attrs) && count($attrs) > 0 ) { $attributes = $attrs; } else { $attributes = array('namingContexts', 'altServer', 'supportedExtension', 'supportedControl', 'supportedSASLMechanisms', 'supportedLDAPVersion', 'subschemaSubentry' ); } $result = $this->search('', '(objectClass=*)', array('attributes' => $attributes, 'scope' => 'base')); if (Net_LDAP::isError($result)) { return $result; } $entry = $result->shiftEntry(); if (false === $entry) { return PEAR::raiseError('Could not fetch RootDSE entry'); } $ret = new Net_LDAP_RootDSE($entry); return $ret; } /** * Alias function of rootDse() for perl-ldap interface * * @access public * @see rootDse() * @return Net_LDAP_Error|Net_LDAP_RootDSE */ function &root_dse() { $args = func_get_args(); return call_user_func_array(array(&$this, 'rootDse'), $args); } /** * Get a schema object * * @param string $dn Subschema entry dn * * @access public * @author Jan Wagner * @return Net_LDAP_Schema|Net_LDAP_Error Net_LDAP_Schema or Net_LDAP_Error object */ function &schema($dn = null) { if (false == is_a($this->_schema, 'Net_LDAP_Schema')) { $this->_schema = & new Net_LDAP_Schema(); if (is_null($dn)) { // get the subschema entry via root dse $dse = $this->rootDSE(array('subschemaSubentry')); if (false == Net_LDAP::isError($dse)) { $base = $dse->getValue('subschemaSubentry', 'single'); if (!Net_LDAP::isError($base)) { $dn = $base; } } } // // Support for buggy LDAP servers (e.g. Siemens DirX 6.x) that incorrectly // call this entry subSchemaSubentry instead of subschemaSubentry. // Note the correct case/spelling as per RFC 2251. // if (is_null($dn)) { // get the subschema entry via root dse $dse = $this->rootDSE(array('subSchemaSubentry')); if (false == Net_LDAP::isError($dse)) { $base = $dse->getValue('subSchemaSubentry', 'single'); if (!Net_LDAP::isError($base)) { $dn = $base; } } } // // Final fallback case where there is no subschemaSubentry attribute // in the root DSE (this is a bug for an LDAP v3 server so report this // to your LDAP vendor if you get this far). // if (is_null($dn)) { $dn = 'cn=Subschema'; } // fetch the subschema entry $result = $this->search($dn, '(objectClass=*)', array('attributes' => array_values($this->_schema->types), 'scope' => 'base')); if (Net_LDAP::isError($result)) { return $result; } $entry = $result->shiftEntry(); if (false === $entry) { return PEAR::raiseError('Could not fetch Subschema entry'); } $this->_schema->parse($entry); } return $this->_schema; } /** * Checks if phps ldap-extension is loaded * * If it is not loaded, it tries to load it manually using PHPs dl(). * It knows both windows-dll and *nix-so. * * @static * @return Net_LDAP_Error|true */ function checkLDAPExtension() { if (!extension_loaded('ldap') && !@dl('ldap.' . PHP_SHLIB_SUFFIX)) { $msg = "It seems that you do not have the ldap-extension installed."; $msg .= "Please install it before using the Net_LDAP package."; return PEAR::raiseError($msg); } else { return true; } } /** * Encodes given attributes to UTF8 if needed by schema * * This function takes attributes in an array and then checks against the schema * if they need UTF8 encoding. If that is so, they will be encoded. An encoded * array will be returned and can be used for adding or modifying. * * $attributes is expected to be an array with keys describing * the attribute names and the values as the value of this attribute: * * $attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2')); * * * @param array $attributes Array of attributes * * @access public * @return array|Net_LDAP_Error Array of UTF8 encoded attributes or Error */ function utf8Encode($attributes) { return $this->_utf8($attributes, 'utf8_encode'); } /** * Decodes the given attribute values if needed by schema * * $attributes is expected to be an array with keys describing * the attribute names and the values as the value of this attribute: * * $attributes = array('cn' => 'foo', 'attr2' => array('mv1', 'mv2')); * * * @param array $attributes Array of attributes * * @access public * @see utf8Encode() * @return array|Net_LDAP_Error Array with decoded attribute values or Error */ function utf8Decode($attributes) { return $this->_utf8($attributes, 'utf8_decode'); } /** * Encodes or decodes attribute values if needed * * @param array $attributes Array of attributes * @param array $function Function to apply to attribute values * * @access private * @return array|Net_LDAP_Error Array of attributes with function * applied to values or Error */ function _utf8($attributes, $function) { if (!is_array($attributes) || array_key_exists(0, $attributes)) { $msg = 'Parameter $attributes is expected to be an associative array'; return PEAR::raiseError($msg); } if (!$this->_schema) { $this->_schema = $this->schema(); } if (!$this->_link || Net_LDAP::isError($this->_schema) || !function_exists($function)) { return $attributes; } if (is_array($attributes) && count($attributes) > 0) { foreach ($attributes as $k => $v) { if (!isset($this->_schemaAttrs[$k])) { $attr = $this->_schema->get('attribute', $k); if (Net_LDAP::isError($attr)) { continue; } $haystack = '1.3.6.1.4.1.1466.115.121.1.15'; if (false !== strpos($attr['syntax'], $haystack)) { $encode = true; } else { $encode = false; } $this->_schemaAttrs[$k] = $encode; } else { $encode = $this->_schemaAttrs[$k]; } if ($encode) { if (is_array($v)) { foreach ($v as $ak => $av) { $v[$ak] = call_user_func($function, $av); } } else { $v = call_user_func($function, $v); } } $attributes[$k] = $v; } } return $attributes; } /** * Get the LDAP link * * @access public * @return resource LDAP link */ function &getLink() { return $this->_link; } } /** * Net_LDAP_Error implements a class for reporting portable LDAP error messages. * * @category Net * @package Net_LDAP * @author Tarjej Huse * @license http://www.gnu.org/copyleft/lesser.html LGPL * @link http://pear.php.net/package/Net_LDAP/ */ class Net_LDAP_Error extends PEAR_Error { /** * Net_LDAP_Error constructor. * * @param string $message String with error message. * @param integer $code Net_LDAP error code * @param integer $mode what "error mode" to operate in * @param mixed $level what error level to use for $mode & * PEAR_ERROR_TRIGGER * @param mixed $debuginfo additional debug info, such as the last query * * @access public * @see PEAR_Error */ function Net_LDAP_Error($message = 'Net_LDAP_Error', $code = NET_LDAP_ERROR, $mode = PEAR_ERROR_RETURN, $level = E_USER_NOTICE, $debuginfo = null) { $error_code = NET_LDAP_ERROR; $msg = "$message: $code"; if (is_int($code)) { $msg = $message . ': ' . Net_LDAP::errorMessage($code); $error_code = $code; } $this->PEAR_Error($msg, $error_code, $mode, $level, $debuginfo); } } ?>