Hello

The problem

After struggling a lot I came to wonder if Drupal => ldap provisioning is working for anyone in 7.x.2.x version or if somehow my configuration and PhP natural weirdness conspire to induce this behavior.

When creating a new user and asking for Drupal-ldap to provision it in the LDAP directory I end up with a

"Error Server ID = cas.techartserv.com, LDAP Err No: 21 LDAP Err Message: Invalid syntax '
in LdapServer->createLdapEntry()" .. line 446 in ... ldap/ldap_servers/LdapServer.class.php

(by the way using @token in the token error array would be nice ;) )

I dived into the code to add the display of the $attribute array and it is formatted like this :

        $attribute = array (
          'cn' => array (
            0 => 'yyy',
          ),
          'userPassword' => array (
            0 => 'yy',
          ),
          'objectclass' => array (
            1 => '"inetOrgPerson"',
            2 => '"top"',
          ),
          'sn' => array (
            0 => 'yyy',
          ),
          'uid' => array (
            0 => '70',
          ),
          'mail' => array (
            0 => 'y@x.com',
          ),
        );

And that is wrong.

it should be like this

attribute : array (
  'cn' => 'xxx',
  'userPassword' => 'pwd',
  'objectclass' =>
  array (
    0 => 'inetOrgPerson',
    1 => 'top',
  ),
  'sn' => 'yyy',
  'uid' => '61',
  'mail' => 'y@x.com',
)

The ldap_add function does not expect an array of arrays.
I wrote a php script that tests this and it only works in the second case (as it is also demonstrated in the php_ldap documentation)

Questions

  • Has somebody be able to make drupal => ldap provisioning work in the 7x.2x branch ?
  • If yes I am curious about the settings used in the user mapping and in the "LDAP User to Drupal User Relationship" in the server declaration.

Comments

Annakan’s picture

I can confirm this is the problem

I traced the building of the "attribute" array to the "drupalUserToLdapEntry" method of the 'LdapUserConf' class.

Since this method seems hell bend to put arrays in every position in the "ldap_user_entry" array it returns , checking constantly "is_array" and replacing at times a previous value if it is not an array (

      if ($ldap_user_entry && isset($ldap_user_entry[$ldap_attr_name]) && is_array($ldap_user_entry[$ldap_attr_name]) && isset($ldap_user_entry[$ldap_attr_name][$ordinal]) ) {
        continue; // don't override values passed in;

in the foreach ($mappings as $field_key => $field_detail) loop

or

         if (!isset($ldap_user_entry[$ldap_attr_name]) || !is_array($ldap_user_entry[$ldap_attr_name])) {
            $ldap_user_entry[$ldap_attr_name] = array();

a few lines below, I assumed It was on intend.

Either because some version of ldap or php_ldap behaves like this or because some other places in the code requires this format.
Since I don't have a proper test bed I hacked quickly a nasty function that does some clean up job on the array before returning it and added

$ldap_user_entry = _cleanup_ldap_entry_descriptor($ldap_user_entry);

before the drupal_alter('ldap_entry', $ldap_user_entry, $params); call and it solved the problem.
The function :

/**
    Hack fucntion that clean up the ldap_entry_desc array  to avoid arrays where not needed i.e. when the element is not multi valued.
    see : http://drupal.org/node/1989018
    **/
    function _cleanup_ldap_entry_descriptor($ldap_entry_descs){
      foreach ($ldap_entry_descs as $field_key => $field_detail) {
        if (is_array($field_detail) && count($field_detail)==1) {
            $ldap_entry_descs[$field_key]=$field_detail[0];
        }
      }
      return $ldap_entry_descs;
    }

My final drupalUserToLdapEntry method looks like this

  function drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user_entry = NULL) {
  //debug('call to drupalUserToLdapEntry, account:'); //debug($account); //debug('ldap_server'); //debug($ldap_server);
  //debug('params'); //debug($params); //debug('ldap_user_entry');//debug($ldap_user_entry);
    $provision = (isset($params['function']) && $params['function'] == 'provisionLdapEntry');
    $result = LDAP_USER_PROV_RESULT_NO_ERROR;
    if (!$ldap_user_entry) {
      $ldap_user_entry = array();
    }

    if (!is_object($account) || !is_object($ldap_server)) {
      return array(NULL, LDAP_USER_PROV_RESULT_BAD_PARAMS);
    }
    $watchdog_tokens = array(
      '%drupal_username' => $account->name,
    );
    $include_count = (isset($params['include_count']) && $params['include_count']);

    $direction = isset($params['direction']) ? $params['direction'] : LDAP_USER_PROV_DIRECTION_ALL;
    $prov_events = empty($params['prov_events']) ? ldap_user_all_events() : $params['prov_events'];

    $mappings = $this->getSynchMappings($direction, $prov_events);
     //debug('prov_events'); //debug(join(",",$prov_events));
  //  debug('mappings'); debug($mappings);
      // Loop over the mappings.
    foreach ($mappings as $field_key => $field_detail) {
      list($ldap_attr_name, $ordinal, $conversion) = ldap_servers_token_extract_parts($field_key, TRUE);  //trim($field_key, '[]');
      $ordinal = (!$ordinal) ? 0 : $ordinal;
      if ($ldap_user_entry && isset($ldap_user_entry[$ldap_attr_name]) && is_array($ldap_user_entry[$ldap_attr_name]) && isset($ldap_user_entry[$ldap_attr_name][$ordinal]) ) {
        continue; // don't override values passed in;
      }

      $synched = $this->isSynched($field_key, $params['prov_events'], LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
    //  debug("isSynched $field_key: $synched");
      if ($synched) {
        $token = ($field_detail['user_attr'] == 'user_tokens') ? $field_detail['user_tokens'] : $field_detail['user_attr'];
        $value = ldap_servers_token_replace($account, $token, 'user_account');

        if (substr($token, 0, 10) == '[password.' && (!$value || $value == $token)) { // deal with empty/unresolved password
          if (!$provision) {
            continue; //don't overwrite password on synch if no value provided
          }
        }

        if ($ldap_attr_name == 'dn' && $value) {
          $ldap_user_entry['dn'] = $value;
        }
        elseif ($value) {
          if (!isset($ldap_user_entry[$ldap_attr_name]) || !is_array($ldap_user_entry[$ldap_attr_name])) {
            $ldap_user_entry[$ldap_attr_name] = array();
          }
          $ldap_user_entry[$ldap_attr_name][$ordinal] = $value;
          if ($include_count) {
            $ldap_user_entry[$ldap_attr_name]['count'] = count($ldap_user_entry[$ldap_attr_name]);
          }

        }

      }

    }

    $ldap_user_entry = _cleanup_ldap_entry_descriptor($ldap_user_entry);
    /**
     * 4. call drupal_alter() to allow other modules to alter $ldap_user
     */

    drupal_alter('ldap_entry', $ldap_user_entry, $params);

    return array($ldap_user_entry, $result);

  }

This is not a proper solution, but I need some other people input or the maintainer input to understand the why of format the function was returning and the implication of rewriting the drupalUserToLdapEntry method before doing it.

I will try to see if I have side effects in the next few days.

johnbarclay’s picture

Title: Drupal => LDAP provisionning not working (invalid syntax) because of bad formated attribute array » LDAP User: Drupal to LDAP provisionning not working (invalid syntax) because of bad formated attribute array

The array needs to be in the correct format for the ldap_add and other ldap_* functions. Any patches in this direction are welcomed. Please be sure to consider different versions of PHP if there are variations in ldap_* functions. I did minimal testing of adds and updates against one type of ldap (Active Directory). Since then its all been tested against the mock ldap server in the simpletests.

Annakan’s picture

Ok
I can confirm that the proper format for OpenLDAP seems to not have "array" everywhere but only on multivalue attributes.

My hack has ugly sides effects (if you put only one value in a multivalued property like objectClasss, it will remove the sub array, incorrectly that time) and I'll "rewrite" the drupalUserToLdapEntry method to that effect soon.
My fear is that different LDAP server (AD comes to my mind) expect different formats ...

Before doing that, I try to have something working and I am banging my head on understanding the magical first line in the "provisioning from drupal to LDAP mapping" of the user tabs

"cn=[property.name],ou=users,dc=xxx,dc=com"

WHAT is "property.name" the name of WHAT property, this is driving me insane at the moment.

Edit

through trial and errors I figured that "property" was the user object in that case and a way to access its fields, property.name is thus the user.name, property.uuid is the user.uuid if you have the uuid module (and you definitely should), etc ... Not sure what the difference is with the "pre constructed" tokens (like "property : email") though and if "property" can represent some other entity in some other cases.

My current trouble is that each time I rename a user in Drupal a new one is created in the LDAP, I tried to give the "Persistent and Unique User ID Attribute" a value ([employeeNumber] seemed perfect) but this does not seem to be used to fetch the user.

I suspect I must change my directory structure so that the dn contains the userID but honestly this is becoming a crazy chase for sanity ;)

The goal seemed simple : declare an attribute to contains a unique identifier, use it to fetch and check existence of a record under the given branch, retrieve, update or delete in sync with Drupal that record .... but well I can't get it to work ATM.

Annakan’s picture

StatusFileSize
new5.46 KB

ok ugly patching is a bad habit.

I made the modifications to not generate arrays on every attributes but only in entries that maps to multivalued attributes has hinted by the fact that they have a :number syntax has in [objectclass:0]

It should by the way be noted that such an attribute must have zero based consecutive index in it or the mapping will not work, i.e . [objectclass:0] and [objectclass:1] but not [objectclass:1] and [objectclass:2] or
[objectclass:0] and [objectclass:3], order of declaration in the mapping does not matter though.

The patch is attached (I am new to this and did not have the drupal_ldap directory under version control since those code modifications where not planned, so I made the diff "manually" with diff utility, not git).
Tell me is it is not correct.

Adding is correct now on OpenLdap, further testing is needed to see if it does not break with other LDAP servers.

I still have troubles with Drupal account renaming, the requested dn is the new one, it fails to find a match (obviously) and fallback into creating a new ldap entry with the new dn ...

Annakan’s picture

StatusFileSize
new6.57 KB

The method synchToLdapEntry of the ldap_user_conf class was tweaking the attribute array to account for the old attribute structure (with arrays everywhere) and undo that.
Since the attribute array is now "properly" (as far as tests shows) formatted I removed that code from the method.

I also removed an error message that was logged when trying to read a non existent dn since it is not an error to do so and in fact the only way to know if an entry is present or not. (class LdapServer, read method, case LDAP_SCOPE_BASE).

A new patch is attached.
There are still a few (commented) "dpm" scattered around because I'm still trying to make this to work :

  • On user account changes, currently the modules creates a new one first and then update the old entry.
  • And I can't sync group membership / make work automatic group creation at the moment either
johnbarclay’s picture

could someone test this?

kenorb’s picture

Status: Active » Needs review
kenorb’s picture

Status: Needs review » Needs work

Please remove dpm().

acrosman’s picture

I couldn't get the patch to apply cleanly (although it seemed to be white space related, so I'm not sure why it was giving me trouble).

I had noticed this issue after I started to work on a patch to resolve the slightly related issue I discovered when working around this problem using hook_ldap_entry_pre_provision_alter().

Currently when you attempt to alter the DN during the user provision process the LDAPUser module skips over the dn field, using instead the dn it cached before calling the hook. I've added my fix for that to this patch and re-rolled.

acrosman’s picture

Status: Needs work » Needs review
t0xicCode’s picture

Status: Needs review » Needs work
+++ b/ldap_servers/LdapServer.class.php
@@ -463,11 +463,13 @@ class LdapServer {
+    //dpm($attributes,"attributes createLdapEntry");
...
+      //dpm(debug_backtrace());

@@ -532,6 +534,11 @@ class LdapServer {
+    /*dpm($dn,"Method  modifyLdapEntry DN");
+    dpm($attributes,"Method  modifyLdapEntry attributes");
+    dpm(debug_backtrace());*/

@@ -891,12 +898,15 @@ class LdapServer {
+        //dpm($params,"case LDAP_SCOPE_BASE read");
...
+          //dpm( $this->errorMsg('ldap'));

+++ b/ldap_user/LdapUserConf.class.php
@@ -684,6 +685,7 @@ class LdapUserConf {
+      //dpm($proposed_ldap_entry,"synchToLdapEntry proposed_ldap_entry ");

You should remove, not just comment out, the calls to dpm.

haggins’s picture

Status: Needs work » Needs review
StatusFileSize
new7.43 KB

Need this asap, so here's a reworked patch based on #9.

grahl’s picture

Status: Needs review » Needs work
grahl’s picture

Status: Needs work » Needs review

The last submitted patch, 4: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 4: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 4: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 5: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 5: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 5: ldap_no_array_in_every_attributes.patch, failed testing.

The last submitted patch, 9: LDAP-no-array-in-every-attributes-1989018-9.patch, failed testing.

The last submitted patch, 9: LDAP-no-array-in-every-attributes-1989018-9.patch, failed testing.

The last submitted patch, 9: LDAP-no-array-in-every-attributes-1989018-9.patch, failed testing.

Status: Needs review » Needs work

The last submitted patch, 12: LDAP-no-array-in-every-attribute-1989018-12.patch, failed testing.

grahl’s picture

Does someone have an up-to-date patch?

caiobianchi’s picture

Is there an 8.x equivalent issue?

minoroffense’s picture

Here's an updated patch without whitespace errors. I've also renamed a defined constant to have the module name prefixed to avoid collisions.

minoroffense’s picture

Status: Needs work » Needs review
minoroffense’s picture

I can confirm this patch resolves the issue for us with regards to provisioning to an LDAP using OpenLDAP.

Status: Needs review » Needs work

The last submitted patch, 27: LDAP-no-array-in-every-attribute-1989018-15.patch, failed testing.

minoroffense’s picture

Looks like the tests need to be updated with the new array structure.

grahl’s picture

Hi minorOffense

Thanks for your patch, I've removed all cosmetic changes to make reviewing easier. I've globally removed all commented out debug messages in dev.

I have three questions:

  • "XM flag non multivalued attributes with LDAP_ATTRIBUTE_WITHOUT_ORDINAL (-1) instead of forcing 0" is very cryptic to me, isn't it the same as saying "Flag item with an invalid ordinal number (-1) in cases where there is no ordinal."?
  • Can someone please step up and update the tests?
  • Why is a check to LDAP_NO_SUCH_OBJECT introduced for LDAP_SCOPE_BASE. This seems to be an unrelated issue and should not necessarily be silently ignored. Or at the very least the comment explaining the false positive is no longer fully correct, isn't it?

@caiobianchi: You are seeing this on 8.x, too, I presume?

minoroffense’s picture

I wasn't the original patch author so I don't have answers for you sadly.

grahl’s picture

grahl’s picture

Status: Needs work » Closed (outdated)
Issue tags: -D8 stable release blocker

Closing issue as outdated due to no further development on 7.x, if you feel this issue is still relevant and you are willing to work on a patch and/or debug the problem, please reopen.