[turba] LDAP composed primary Key, WITH ATTACH
Marco Brandizi
zakmck at virgilio.it
Wed Dec 17 13:08:19 PST 2003
Hi all
I'm importing, via Turba / CSV import, in LDAP a long address list.
Since practically I don't have a primary key (all the fields are a key,
there are almost identical rows and I don't have time to check 1000
entries), I would like to use this as primary:
// Generates MD5 unique ID from record
$attrs = '';
foreach ( $attributes as $attr ) {
if ( $attrs ) $attrs .= '';
$attrs .= $attr;
}
$dn .= 'uid=' . md5 ( $attrs ) . ',';
i.e.: the primary key is an hash of the entire row.
But unfortunately Turba expects a list of attributes as key
specification, so that I've hacked lib/driver/ldap.php, in the following
way:
function makeKey($attributes)
{
$dn = '';
if (is_array($this->_params['dn'])) {
foreach ($this->_params['dn'] as $param) {
/* Marco Brandizi, Oct 2003, A VERY dirty trick to support dn dynamic */
if ( $param == 'uid' )
{
// Generates MD5 unique ID from record
$attrs = '';
foreach ( $attributes as $attr ) {
if ( $attrs ) $attrs .= '';
$attrs .= $attr;
}
$dn .= 'uid=' . md5 ( $attrs ) . ',';
}
else
// End of Brandizi Trick
if (isset($attributes[$param])) {
$dn .= $param . '=' . $attributes[$param] . ',';
}
}
}
$dn .= $this->_params['root'];
return $dn;
}
Also I need some other slight changes, see the attachement (comments
with BRANDIZI).
Is there a cleaner way to do what I want? For example, telling Turba it
should pass a parameter to eval()? It would be nice even something less
than I'm doing, for example the simple ability to specify the key is the
concatenation of several attributes.
NOTE: About this latter point, I cannot write (in config/sources.php)
...
'dn' => array( 'cn', 'sn' ),
...
Since the result would be "dn=cn=Marco,sn=Brandizi..." and with this
search string, LDAP expects to find a node whose father the node having
sn=Brandizi.
<?php
/**
* Turba directory driver implementation for PHP's LDAP extension.
*
* $Horde: turba/lib/Driver/ldap.php,v 1.43 2003/12/07 15:35:39 chuck Exp $
*
* @author Chuck Hagenbuch <chuck at horde.org>
* @author Jon Parise <jon at csh.rit.edu>
* @version $Revision: 1.43 $
* @since Turba 0.0.1
* @package Turba
*/
class Turba_Driver_ldap extends Turba_Driver {
/** Handle for the current LDAP connection. */
var $_ds = 0;
/**
* Constructs a new Turba LDAP driver object.
*
* @param $params Hash containing additional configuration
parameters.
*/
function Turba_Driver_ldap($params)
{
if (empty($params['server'])) {
$params['server'] = 'localhost';
}
if (empty($params['port'])) {
$params['port'] = 389;
}
if (empty($params['root'])) {
$params['root'] = '';
}
if (empty($params['multiple_entry_separator'])) {
$params['multiple_entry_separator'] = ', ';
}
if (empty($params['charset'])) {
$params['charset'] = '';
}
parent::Turba_Driver($params);
}
function init()
{
if (!($this->_ds = @ldap_connect($this->_params['server'],
$this->_params['port']))) {
return PEAR::raiseError(_("Connection failure"));
}
// Set the LDAP protocol version.
if (!empty($this->_params['version'])) {
@ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION,
$this->_params['version']);
}
if (isset($this->_params['bind_dn']) &&
isset($this->_params['bind_password'])) {
if (!@ldap_bind($this->_ds, $this->_params['bind_dn'],
$this->_params['bind_password'])) {
return PEAR::raiseError(sprintf(_("Bind failed: (%s)
%s"), ldap_errno($this->_ds), ldap_error($this->_ds)));
}
} else if (!(@ldap_bind($this->_ds))) {
return PEAR::raiseError(sprintf(_("Bind failed: (%s) %s"),
ldap_errno($this->_ds), ldap_error($this->_ds)));
}
}
/**
* Searches the LDAP directory with the given criteria and returns
* a filtered list of results. If no criteria are specified, all
* records are returned.
*
* @param $criteria Array containing the search criteria.
* @param $fields List of fields to return.
*
* @return array Hash containing the search results.
*/
function search($criteria, $fields)
{
/* Build the LDAP filter. */
$filter = '';
if (count($criteria)) {
foreach ($criteria as $key => $vals) {
if ($key == 'OR') {
$filter .= '(|' . $this->_buildSearchQuery($vals) .
')';
} elseif ($key == 'AND') {
$filter .= '(&' . $this->_buildSearchQuery($vals) .
')';
}
}
} else {
// Filter on objectclass.
$filter = $this->_buildObjectclassFilter();
}
/* Add source-wide filters, which are _always_ AND-ed. */
if (!empty($this->_params['filter'])) {
$filter = '(&' . '(' . $this->_params['filter'] . ')' .
$filter . ')';
}
/* Four11 (at least) doesn't seem to return 'cn' if you don't
* ask for 'sn' as well. Add 'sn' implicitly. */
$attr = $fields;
if (!in_array('sn', $attr)) {
$attr[] = 'sn';
}
/* Add a sizelimit, if specified. Default is 0, which means no
* limit. Note: You cannot override a server-side limit with
* this. */
$sizelimit = 0;
if (!empty($this->_params['sizelimit'])) {
$sizelimit = $this->_params['sizelimit'];
}
/* Log the query at a DEBUG log level. */
Horde::logMessage(sprintf('LDAP search by %s: root = %s (%s);
filter = "%s"; attributes = "%s"; sizelimit = %d',
Auth::getAuth(),
$this->_params['root'], $this->_params['server'], $filter, implode(', ',
$attr), $sizelimit),
__FILE__, __LINE__, PEAR_LOG_DEBUG);
/* Send the query to the LDAP server and fetch the matching
* entries. */
if (!($res = @ldap_search($this->_ds, $this->_params['root'],
$filter, $attr, 0, $sizelimit))) {
return PEAR::raiseError(sprintf(_("Query failed: (%s) %s"),
ldap_errno($this->_ds), ldap_error($this->_ds)));
}
return $this->getResults($fields, $res);
}
/**
* Build a piece of a search query.
*
* @param array $criteria The array of criteria.
*
* @return string An LDAP query fragment.
*/
function _buildSearchQuery($criteria)
{
require_once HORDE_BASE . '/lib/LDAP.php';
$clause = '';
foreach ($criteria as $key => $vals) {
if (!empty($vals['OR'])) {
$clause .= '(|' . $this->_buildSearchQuery($vals) . ')';
} elseif (!empty($vals['AND'])) {
$clause .= '(&' . $this->_buildSearchQuery($vals) . ')';
} else {
if (isset($vals['field'])) {
$rhs = String::convertCharset($vals['test'],
NLS::getCharset(), $this->_params['charset']);
$clause .= Horde_LDAP::buildClause($vals['field'],
$vals['op'], $rhs);
} else {
foreach ($vals as $test) {
if (!empty($test['OR'])) {
$clause .= '(|' .
$this->_buildSearchQuery($test) . ')';
} elseif (!empty($test['AND'])) {
$clause .= '(&' .
$this->_buildSearchQuery($test) . ')';
} else {
$rhs =
String::convertCharset($test['test'], NLS::getCharset(),
$this->_params['charset']);
$clause .=
Horde_LDAP::buildClause($test['field'], $test['op'], $rhs);
}
}
}
}
}
return $clause;
}
/**
* Reads the LDAP directory for a given element and returns
* the result's fields.
*
* @param string $criteria Search criteria (must be 'dn').
* @param mixed $dn The dn of the object to read.
* @param array $fields List of fields to return.
*
* @return array Hash containing the search results.
*/
function read($criteria, $dn, $fields)
{
// Only DN
if ($criteria != 'dn') {
return array();
}
$filter = $this->_buildObjectclassFilter();
/* Four11 (at least) doesn't seem to return 'cn' if you don't
* ask for 'sn' as well. Add 'sn' implicitly. */
$attr = $fields;
if (!in_array('sn', $attr)) {
$attr[] = 'sn';
}
// Handle a request for multiple records.
if (is_array($dn)) {
$results = array();
foreach ($dn as $d) {
$res = @ldap_read($this->_ds, $d, $filter, $attr);
if ($res) {
if (!is_a($result = $this->getResults($fields,
$res), 'PEAR_Error')) {
$results += $result;
} else {
return $result;
}
} else {
return PEAR::raiseError(sprintf(_("Read failed:
(%s) %s"), ldap_errno($this->_ds), ldap_error($this->_ds)));
}
}
return $results;
}
$res = @ldap_read($this->_ds, $dn, $filter, $attr);
if (!$res) {
return PEAR::raiseError(sprintf(_("Read failed: (%s) %s"),
ldap_errno($this->_ds), ldap_error($this->_ds)));
}
return $this->getResults($fields, $res);
}
/**
* Get some results from a result identifier and clean them up.
*
* @param array $fields List of fields to return.
* @param resource $res Result identifier.
*
* @return array Hash containing the results.
*/
function getResults($fields, $res)
{
if (!($entries = @ldap_get_entries($this->_ds, $res))) {
return PEAR::raiseError(sprintf(_("Read failed: (%s) %s"),
ldap_errno($this->_ds), ldap_error($this->_ds)));
}
/* Return only the requested fields (from $fields, above). */
$results = array();
for ($i = 0; $i < $entries['count']; $i++) {
$entry = $entries[$i];
$result = array();
foreach ($fields as $field) {
$field_l = String::lower($field);
if ($field == 'dn') {
$result[$field] = $entry[$field_l];
} else {
$result[$field] = '';
if (!empty($entry[$field_l])) {
for ($j = 0; $j < $entry[$field_l]['count'];
$j++) {
if (!empty($result[$field])) {
$result[$field] .=
$this->_params['multiple_entry_separator'];
}
$result[$field] .=
String::convertCharset($entry[$field_l][$j], $this->_params['charset']);
}
}
}
}
$results[] = $result;
}
return $results;
}
/**
* Adds the specified entry to the LDAP directory.
*
* @param array $attributes The initial attributes for the new object.
*/
function addObject($attributes)
{
if (empty($attributes['dn'])) {
return PEAR::raiseError('Tried to add an object with no dn:
[' . serialize($attributes) . '].');
} elseif (empty($this->_params['objectclass'])) {
return PEAR::raiseError('Tried to add an object with no
objectclass: [' . serialize($attributes) . '].');
}
// Take the DN out of the attributes array
$dn = $attributes['dn'];
unset($attributes['dn']);
// Put the Objectclass into the attributes array
if (!is_array($this->_params['objectclass'])) {
$attributes['objectclass'] = $this->_params['objectclass'];
} else {
$i = 0;
foreach ($this->_params['objectclass'] as $objectclass) {
$attributes['objectclass'][$i] = $objectclass;
$i++;
}
}
// Don't add empty attributes.
$attributes = array_filter($attributes, array($this,
'_emptyAttributeFilter'));
// Encode entries.
foreach ($attributes as $key => $val) {
if (!is_array($val)) {
$attributes[$key] = String::convertCharset($val,
NLS::getCharset(), $this->_params['charset']);
}
}
// BRANDIZI Build the cn attribute as first+last name
$attributes['cn'] =
$attributes['givenName'] . ' ' . $attributes['sn'];
if (!@ldap_add($this->_ds, $dn, $attributes)) {
return PEAR::raiseError('Failed to add an object: [' .
ldap_errno($this->_ds) . '] "' . ldap_error($this->_ds) . '"
(attributes: [' . serialize($attributes) . ']).');
} else {
return true;
}
}
/**
* Deletes the specified entry from the LDAP directory.
*/
function removeObject($object_key, $object_id)
{
if ($object_key != 'dn') {
return PEAR::raiseError(_("Invalid key specified."));
}
if (!@ldap_delete($this->_ds, $object_id)) {
return PEAR::raiseError(sprintf(_("Delete failed: (%s)
%s"), ldap_errno($this->_ds), ldap_error($this->_ds)));
} else {
return true;
}
}
/**
* Modifies the specified entry in the LDAP directory.
*/
function setObject($object_key, $object_id, $attributes)
{
// Get the old entry so that we can access the old
// values. These are needed so that we can delete any
// attributes that have been removed by using ldap_mod_del.
// BRANDIZI Old way didn't work, probably because of
// my changes, let's use this:
return $this->removeObject ( $object_key, $object_id ) &&
$this->addObject ( $attributes );
// NEVER REACHED
$filter = $this->_buildObjectclassFilter();
$oldres = @ldap_read($this->_ds, $object_id, $filter,
array_keys($attributes));
$info = ldap_get_attributes($this->_ds,
ldap_first_entry($this->_ds, $oldres));
// The attributes in the attributes array are all lower case
// while they are mixedCase in the search result. So convert
// the keys to lower.
$info = array_change_key_case($info, CASE_LOWER);
foreach ($info as $key => $value) {
$var = $info[$key];
$oldval = null;
// Check to see if the old value and the new value are
// different and that the new value is empty. If so then
// we use ldap_mod_del to delete the attribute.
if (isset($attributes[$key]) &&
($var[0] != $attributes[$key]) &&
$attributes[$key] == '') {
$oldval[$key] = $var[0];
if (!@ldap_mod_del($this->_ds, $object_id, $oldval)) {
return PEAR::raiseError(sprintf(_("Modify failed:
(%s) %s"), ldap_errno($this->_ds), ldap_error($this->_ds)));
}
unset($attributes[$key]);
}
}
// Encode entries.
foreach ($attributes as $key => $val) {
if (!is_array($val)) {
$attributes[$key] = String::convertCharset($val,
NLS::getCharset(), $this->_params['charset']);
}
}
unset($attributes[$object_key]);
$attributes = array_filter($attributes, array($this,
'_emptyAttributeFilter'));
if (!@ldap_modify($this->_ds, $object_id, $attributes)) {
return PEAR::raiseError(sprintf(_("Modify failed: (%s)
%s"), ldap_errno($this->_ds), ldap_error($this->_ds)));
} else {
return true;
}
}
/**
* Build a DN based on a set of attributes and what attributes
* make a DN for the current source.
*
* @param array $attributes The attributes (in driver keys) of the
* object being added.
*
* @return string The DN for the new object.
*/
function makeKey($attributes)
{
$dn = '';
if (is_array($this->_params['dn'])) {
foreach ($this->_params['dn'] as $param) {
/* Marco Brandizi, Oct 2003, A VERY dirty trick to support dn dynamic */
if ( $param == 'uid' )
{
// Generates MD5 unique ID from record
$attrs = '';
foreach ( $attributes as $attr ) {
if ( $attrs ) $attrs .= '';
$attrs .= $attr;
}
$dn .= 'uid=' . md5 ( $attrs ) . ',';
}
else
// End of Brandizi Trick
if (isset($attributes[$param])) {
$dn .= $param . '=' . $attributes[$param] . ',';
}
}
}
$dn .= $this->_params['root'];
// DEBUG echo ( "<H1>dn = $dn</H1>" );
return $dn;
}
/**
* Remove empty attributes from attributes array
*
* @param mixed $val Value from attributes array.
* @return bool Boolean used by array_filter.
*/
function _emptyAttributeFilter($var)
{
if (!is_array($var)) {
return ($var != '');
} else {
foreach ($var as $v) {
if ($v == '') {
return false;
}
}
return true;
}
}
/**
* Build an LDAP filter based on the objectclass parameter.
*
* @return string An LDAP filter.
*/
function _buildObjectclassFilter()
{
$filter = '';
if (!empty($this->_params['objectclass'])) {
if (!is_array($this->_params['objectclass'])) {
$filter = '(objectclass=' .
$this->_params['objectclass'] . ')';
} else {
$filter = '(|';
foreach ($this->_params['objectclass'] as $objectclass) {
$filter .= '(objectclass=' . $objectclass . ')';
}
$filter .= ')';
}
}
return $filter;
}
}
More information about the turba
mailing list