Projet

Général

Profil

Paste
Télécharger (51 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / ldap / ldap_user / LdapUserConf.class.php @ 91af538d

1
<?php
2

    
3
/**
4
 * @file
5
 * This class represents a ldap_user module's configuration
6
 * It is extended by LdapUserConfAdmin for configuration and other admin functions.
7
 */
8

    
9
require_once 'ldap_user.module';
10
/**
11
 *
12
 */
13
class LdapUserConf {
14

    
15
  /**
16
   * Server providing Drupal account provisioning.
17
   *
18
   * @var string
19
   *
20
   * @see LdapServer::sid
21
   */
22
  public $drupalAcctProvisionServer = LDAP_USER_NO_SERVER_SID;
23

    
24
  /**
25
   * Server providing LDAP entry provisioning.
26
   *
27
   * @var string
28
   *
29
   * @see LdapServer::sid
30
   */
31
  public $ldapEntryProvisionServer = LDAP_USER_NO_SERVER_SID;
32

    
33
  /**
34
   * Associative array mapping synch directions to ldap server instances.
35
   *
36
   * @var array
37
   */
38
  public $provisionSidFromDirection = [
39
    LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER => LDAP_USER_NO_SERVER_SID,
40
    LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => LDAP_USER_NO_SERVER_SID,
41
  ];
42

    
43
  /**
44
   * Array of events that trigger provisioning of Drupal Accounts
45
   * Valid constants are:
46
   *   LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE
47
   *   LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE
48
   *   LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE.
49
   *
50
   * @var array
51
   */
52
  public $drupalAcctProvisionTriggers = [LDAP_USER_DRUPAL_USER_PROV_ON_AUTHENTICATE, LDAP_USER_DRUPAL_USER_PROV_ON_USER_UPDATE_CREATE, LDAP_USER_DRUPAL_USER_PROV_ON_ALLOW_MANUAL_CREATE];
53

    
54
  /**
55
   * Array of events that trigger provisioning of LDAP Entries
56
   * Valid constants are:
57
   *   LDAP_USER_LDAP_ENTRY_PROV_ON_USER_UPDATE_CREATE
58
   *   LDAP_USER_LDAP_ENTRY_PROV_ON_AUTHENTICATE
59
   *   LDAP_USER_LDAP_ENTRY_DELETE_ON_USER_DELETE.
60
   *
61
   * @var array
62
   */
63
  public $ldapEntryProvisionTriggers = [];
64

    
65
  /**
66
   * Server providing LDAP entry provisioning.
67
   *
68
   * @var string
69
   *
70
   * @see LdapServer::sid
71
   */
72
  public $userConflictResolve = LDAP_USER_CONFLICT_RESOLVE_DEFAULT;
73

    
74
  /**
75
   * Whether to allow/disallow provisioning accounts that have the same email.
76
   * Depending on whether the "sharedemail" module is enabled, this variable
77
   * will (by default) be set accordingly.  It can be overridden by an admin.
78
   *
79
   * @var int
80
   *    LDAP_USER_ACCOUNTS_WITH_SAME_EMAIL_DISABLED (0)
81
   *    LDAP_USER_ACCOUNTS_WITH_SAME_EMAIL_ENABLED (1)
82
   */
83
  public $accountsWithSameEmail = LDAP_USER_ACCOUNTS_WITH_SAME_EMAIL_DISABLED;
84

    
85
  /**
86
   * Drupal account creation model.
87
   *
88
   * @var int
89
   *   LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR   /admin/config/people/accounts/settings do not affect "LDAP Associated" Drupal accounts.
90
   *   LDAP_USER_ACCT_CREATION_USER_SETTINGS_FOR_LDAP  use Account creation settings at /admin/config/people/accounts/settings
91
   */
92
  public $acctCreation = LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT;
93

    
94
  /**
95
   * Has current object been saved to the database?
96
   *
97
   * @var bool
98
   */
99
  public $inDatabase = FALSE;
100

    
101
  /**
102
   * What to do when an ldap provisioned username conflicts with existing drupal user?
103
   *
104
   * @var int
105
   *   LDAP_USER_CONFLICT_LOG - log the conflict
106
   *   LDAP_USER_CONFLICT_RESOLVE - LDAP associate the existing drupal user
107
   */
108
  public $manualAccountConflict = LDAP_USER_MANUAL_ACCT_CONFLICT_REJECT;
109

    
110
  /**
111
   * @todo default to FALSE and check for mapping to set to true
112
   */
113
  public $setsLdapPassword = TRUE;
114

    
115
  public $loginConflictResolve = FALSE;
116

    
117
  public $disableAdminPasswordField = FALSE;
118
  /**
119
   * Array of field synch mappings provided by all modules (via hook_ldap_user_attrs_list_alter())
120
   * array of the form: array(
121
   * LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
122
   *   <server_id> => array(
123
   *     'sid' => <server_id> (redundant)
124
   *     'ldap_attr' => e.g. [sn]
125
   *     'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
126
   *     'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
127
   *     'convert' => 1|0 boolean indicating need to covert from binary
128
   *     'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
129
   *     'config_module' => 'ldap_user'
130
   *     'prov_module' => 'ldap_user'
131
   *     'enabled' => 1|0 boolean
132
   *      prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
133
   *         - four permutations available
134
   *            to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
135
   *            to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
136
   *    )
137
   *  )
138
   */
139
  /**
140
   * Array of field synching directions for each operation.  should include ldapUserSynchMappings.
141
   */
142
  public $synchMapping = NULL;
143
  // Keyed on direction => property, ldap, or field token such as '[field.field_lname] with brackets in them.
144
  /**
145
   * Synch mappings configured in ldap user module (not in other modules)
146
   *   array of the form: array(
147
   * LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY => array(
148
   * 'sid' => <server_id> (redundant)
149
   * 'ldap_attr' => e.g. [sn]
150
   * 'user_attr'  => e.g. [field.field_user_lname] (when this value is set to 'user_tokens', 'user_tokens' value is used.)
151
   * 'user_tokens' => e.g. [field.field_user_lname], [field.field_user_fname]
152
   * 'convert' => 1|0 boolean indicating need to covert from binary
153
   * 'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER | LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY (redundant)
154
   * 'config_module' => 'ldap_user'
155
   * 'prov_module' => 'ldap_user'
156
   * 'enabled' => 1|0 boolean
157
   * prov_events' => array( of LDAP_USER_EVENT_* constants indicating during which synch actions field should be synched)
158
   * - four permutations available
159
   * to ldap:   LDAP_USER_EVENT_CREATE_LDAP_ENTRY,  LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY,
160
   * to drupal: LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER
161
   * )
162
   * )
163
   * )
164
   */
165
  public $ldapUserSynchMappings = NULL;
166
  /**
167
   * Keyed on property, ldap, or field token such as '[field.field_lname] with brackets in them.
168
   */
169
  public $detailedWatchdog = FALSE;
170
  public $provisionsDrupalAccountsFromLdap = FALSE;
171
  public $provisionsLdapEntriesFromDrupalUsers = FALSE;
172

    
173
  /**
174
   * What should be done with ldap provisioned accounts that no longer have associated drupal accounts.
175
   */
176
  public $orphanedDrupalAcctBehavior = 'ldap_user_orphan_email';
177
  /**
178
   * Options are partially derived from user module account cancel options:.
179
   *
180
   * 'ldap_user_orphan_do_not_check' => Do not check for orphaned Drupal accounts.)
181
   * 'ldap_user_orphan_email' => Perform no action, but email list of orphaned accounts. (All the other options will send email summaries also.)
182
   * 'user_cancel_block' => Disable the account and keep its content.
183
   * 'user_cancel_block_unpublish' => Disable the account and unpublish its content.
184
   * 'user_cancel_reassign' => Delete the account and make its content belong to the Anonymous user.
185
   * 'user_cancel_delete' => Delete the account and its content.
186
   */
187

    
188
  public $orphanedCheckQty = 100;
189

    
190
  public $provisionsLdapEvents = [];
191
  public $provisionsDrupalEvents = [];
192

    
193
  public $saveable = [
194
    'drupalAcctProvisionServer',
195
    'ldapEntryProvisionServer',
196
    'drupalAcctProvisionTriggers',
197
    'ldapEntryProvisionTriggers',
198
    'orphanedDrupalAcctBehavior',
199
    'orphanedCheckQty',
200
    'userConflictResolve',
201
    'accountsWithSameEmail',
202
    'manualAccountConflict',
203
    'acctCreation',
204
    'ldapUserSynchMappings',
205
    'disableAdminPasswordField',
206
  ];
207

    
208
  /**
209
   *
210
   */
211
  public function __construct() {
212
    $this->load();
213

    
214
    $this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER] = $this->drupalAcctProvisionServer;
215
    $this->provisionSidFromDirection[LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY] = $this->ldapEntryProvisionServer;
216

    
217
    $this->provisionsLdapEvents = [
218
      LDAP_USER_EVENT_CREATE_LDAP_ENTRY => t('On LDAP Entry Creation'),
219
      LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY => t('On Synch to LDAP Entry'),
220
    ];
221

    
222
    $this->provisionsDrupalEvents = [
223
      LDAP_USER_EVENT_CREATE_DRUPAL_USER => t('On Drupal User Creation'),
224
      LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER => t('On Synch to Drupal User'),
225
    ];
226

    
227
    $this->provisionsDrupalAccountsFromLdap = (
228
      $this->drupalAcctProvisionServer &&
229
      $this->drupalAcctProvisionServer &&
230
      (count(array_filter(array_values($this->drupalAcctProvisionTriggers))) > 0)
231
    );
232

    
233
    $this->provisionsLdapEntriesFromDrupalUsers = (
234
      $this->ldapEntryProvisionServer
235
      && $this->ldapEntryProvisionServer
236
      && (count(array_filter(array_values($this->ldapEntryProvisionTriggers))) > 0)
237
      );
238

    
239
    $this->setSynchMapping(TRUE);
240
    $this->detailedWatchdog = variable_get('ldap_help_watchdog_detail', 0);
241
  }
242

    
243
  /**
244
   *
245
   */
246
  public function load() {
247

    
248
    if ($saved = variable_get("ldap_user_conf", FALSE)) {
249
      $this->inDatabase = TRUE;
250
      foreach ($this->saveable as $property) {
251
        if (isset($saved[$property])) {
252
          $this->{$property} = $saved[$property];
253
        }
254
      }
255
    }
256
    else {
257
      $this->inDatabase = FALSE;
258
      // By default this variable should be 0 if the "sharedemail" module
259
      // is not enabled, or 1 if the module is.
260
      $this->accountsWithSameEmail = (int) module_exists('sharedemail');
261
    }
262
    // Determine account creation configuration.
263
    $user_register = variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL);
264
    if ($this->acctCreation == LDAP_USER_ACCT_CREATION_LDAP_BEHAVIOR_DEFAULT || $user_register == USER_REGISTER_VISITORS) {
265
      $this->createLDAPAccounts = TRUE;
266
      $this->createLDAPAccountsAdminApproval = FALSE;
267
    }
268
    elseif ($user_register == USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL) {
269
      $this->createLDAPAccounts = FALSE;
270
      $this->createLDAPAccountsAdminApproval = TRUE;
271
    }
272
    else {
273
      $this->createLDAPAccounts = FALSE;
274
      $this->createLDAPAccountsAdminApproval = FALSE;
275
    }
276
  }
277

    
278
  /**
279
   * Destructor Method.
280
   */
281
  public function __destruct() {}
282

    
283
  /**
284
   * Util to fetch mappings for a given direction.
285
   *
286
   * @param string $sid
287
   *   The server id.
288
   * @param string $direction
289
   *   LDAP_USER_PROV_DIRECTION_* constant.
290
   * @param array $prov_events
291
   *
292
   * @return arraybool
293
   *   Array of mappings (may be empty array)
294
   */
295
  public function getSynchMappings($direction = LDAP_USER_PROV_DIRECTION_ALL, $prov_events = NULL) {
296
    if (!$prov_events) {
297
      $prov_events = ldap_user_all_events();
298
    }
299

    
300
    $mappings = [];
301
    if ($direction == LDAP_USER_PROV_DIRECTION_ALL) {
302
      $directions = [LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY];
303
    }
304
    else {
305
      $directions = [$direction];
306
    }
307
    foreach ($directions as $direction) {
308
      if (!empty($this->ldapUserSynchMappings[$direction])) {
309
        foreach ($this->ldapUserSynchMappings[$direction] as $attribute => $mapping) {
310
          if (!empty($mapping['prov_events'])) {
311
            $result = count(array_intersect($prov_events, $mapping['prov_events']));
312
            if ($result) {
313
              $mappings[$attribute] = $mapping;
314
            }
315
          }
316
        }
317
      }
318
    }
319
    return $mappings;
320
  }
321

    
322
  /**
323
   *
324
   */
325
  public function isDrupalAcctProvisionServer($sid) {
326
    if (!$sid || !$this->drupalAcctProvisionServer) {
327
      return FALSE;
328
    }
329
    elseif ($this->drupalAcctProvisionServer == $sid) {
330
      return TRUE;
331
    }
332
    else {
333
      return FALSE;
334
    }
335
  }
336

    
337
  /**
338
   *
339
   */
340
  public function isLdapEntryProvisionServer($sid) {
341
    if (!$sid || !$this->ldapEntryProvisionServer) {
342
      return FALSE;
343
    }
344
    elseif ($this->ldapEntryProvisionServer == $sid) {
345
      return TRUE;
346
    }
347
    else {
348
      return FALSE;
349
    }
350
  }
351

    
352
  /**
353
   * Util to fetch attributes required for this user conf, not other modules.
354
   *
355
   * @param enum $direction
356
   *   LDAP_USER_PROV_DIRECTION_* constants.
357
   * @param string $ldap_context
358
   */
359
  public function getLdapUserRequiredAttributes($direction = LDAP_USER_PROV_DIRECTION_ALL, $ldap_context = NULL) {
360

    
361
    $attributes_map = [];
362
    $required_attributes = [];
363
    if ($this->drupalAcctProvisionServer) {
364
      $prov_events = $this->ldapContextToProvEvents($ldap_context);
365
      $attributes_map = $this->getSynchMappings($direction, $prov_events);
366
      $required_attributes = [];
367
      foreach ($attributes_map as $detail) {
368
        if (count(array_intersect($prov_events, $detail['prov_events']))) {
369
          // Add the attribute to our array.
370
          if ($detail['ldap_attr']) {
371
            ldap_servers_token_extract_attributes($required_attributes, $detail['ldap_attr']);
372
          }
373
        }
374
      }
375
    }
376
    return $required_attributes;
377
  }
378

    
379
  /**
380
   * Converts the more general ldap_context string to its associated ldap user event.
381
   */
382
  public function ldapContextToProvEvents($ldap_context = NULL) {
383

    
384
    switch ($ldap_context) {
385

    
386
      case 'ldap_user_prov_to_drupal':
387
        $result = [LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, LDAP_USER_EVENT_CREATE_DRUPAL_USER, LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT];
388
        break;
389

    
390
      case 'ldap_user_prov_to_ldap':
391
        $result = [LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY, LDAP_USER_EVENT_CREATE_LDAP_ENTRY];
392
        break;
393

    
394
      default:
395
        $result = ldap_user_all_events();
396

    
397
    }
398

    
399
    return $result;
400

    
401
  }
402

    
403
  /**
404
   * Converts the more general ldap_context string to its associated ldap user prov direction.
405
   */
406
  public function ldapContextToProvDirection($ldap_context = NULL) {
407

    
408
    switch ($ldap_context) {
409

    
410
      case 'ldap_user_prov_to_drupal':
411
        $result = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER;
412
        break;
413

    
414
      case 'ldap_user_prov_to_ldap':
415
      case 'ldap_user_delete_drupal_user':
416
        $result = LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY;
417
        break;
418

    
419
      // Provisioning is can hapen in both directions in most contexts.
420
      case 'ldap_user_insert_drupal_user':
421
      case 'ldap_user_update_drupal_user':
422
      case 'ldap_authentication_authenticate':
423
      case 'ldap_user_insert_drupal_user':
424
      case 'ldap_user_disable_drupal_user':
425
        $result = LDAP_USER_PROV_DIRECTION_ALL;
426
        break;
427

    
428
      default:
429
        $result = LDAP_USER_PROV_DIRECTION_ALL;
430

    
431
    }
432

    
433
    return $result;
434
  }
435

    
436
  /**
437
   * Derive mapping array from ldap user configuration and other configurations.
438
   * if this becomes a resource hungry function should be moved to ldap_user functions
439
   * and stored with static variable. should be cached also.
440
   *
441
   * This should be cached and modules implementing ldap_user_synch_mapping_alter
442
   * should know when to invalidate cache.
443
   */
444

    
445
  /**
446
   * @todo change default to false after development
447
   */
448
  public function setSynchMapping($reset = TRUE) {
449

    
450
    $synch_mapping_cache = cache_get('ldap_user_synch_mapping');
451
    if (!$reset && $synch_mapping_cache) {
452
      $this->synchMapping = $synch_mapping_cache->data;
453
    }
454
    else {
455
      $available_user_attrs = [];
456
      foreach ([LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY] as $direction) {
457
        $sid = $this->provisionSidFromDirection[$direction];
458
        $available_user_attrs[$direction] = [];
459
        $ldap_server = ($sid) ? ldap_servers_get_servers($sid, NULL, TRUE) : FALSE;
460

    
461
        $params = [
462
          'ldap_server' => $ldap_server,
463
          'ldap_user_conf' => $this,
464
          'direction' => $direction,
465
        ];
466

    
467
        drupal_alter('ldap_user_attrs_list', $available_user_attrs[$direction], $params);
468
      }
469
    }
470
    $this->synchMapping = $available_user_attrs;
471

    
472
    cache_set('ldap_user_synch_mapping', $this->synchMapping);
473
  }
474

    
475
  /**
476
   * Given a $prov_event determine if ldap user configuration supports it.
477
   *   this is overall, not per field synching configuration.
478
   *
479
   * @param enum $direction
480
   *   LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY.
481
   *
482
   * @param enum $prov_event
483
   *   LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, LDAP_USER_EVENT_CREATE_DRUPAL_USER
484
   *   LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY LDAP_USER_EVENT_CREATE_LDAP_ENTRY
485
   *   LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT
486
   *   LDAP_USER_EVENT_ALL.
487
   *
488
   * @param enum $action
489
   *   'synch', 'provision', 'delete_ldap_entry', 'delete_drupal_entry', 'cancel_drupal_entry'.
490
   *
491
   * @return bool
492
   */
493
  public function provisionEnabled($direction, $provision_trigger) {
494
    $result = FALSE;
495

    
496
    if ($direction == LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY) {
497

    
498
      if (!$this->ldapEntryProvisionServer) {
499
        $result = FALSE;
500
      }
501
      else {
502
        $result = in_array($provision_trigger, $this->ldapEntryProvisionTriggers);
503
      }
504

    
505
    }
506
    elseif ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER) {
507
      if (!$this->drupalAcctProvisionServer) {
508
        $result = FALSE;
509
      }
510
      else {
511
        $result = in_array($provision_trigger, $this->drupalAcctProvisionTriggers);
512
      }
513
    }
514

    
515
    return $result;
516
  }
517

    
518
  /**
519
   * Given a drupal account, provision an ldap entry if none exists.  if one exists do nothing.
520
   *
521
   * @param object $account
522
   *   drupal account object with minimum of name property.
523
   * @param array $ldap_user
524
   *   as prepopulated ldap entry.  usually not provided.
525
   *
526
   * @return array of form:
527
   *   array('status' => 'success', 'fail', or 'conflict'),
528
   *     array('ldap_server' => ldap server object),
529
   *     array('proposed' => proposed ldap entry),
530
   *     array('existing' => existing ldap entry),
531
   *     array('description' = > blah blah)
532
   */
533
  public function provisionLdapEntry($account, $ldap_user = NULL, $test_query = FALSE) {
534
    $watchdog_tokens = [];
535
    $result = [
536
      'status' => NULL,
537
      'ldap_server' => NULL,
538
      'proposed' => NULL,
539
      'existing' => NULL,
540
      'description' => NULL,
541
    ];
542

    
543
    if (is_scalar($account)) {
544
      $username = $account;
545
      $account = new stdClass();
546
      $account->name = $username;
547
    }
548

    
549
    list($account, $user_entity) = ldap_user_load_user_acct_and_entity($account->name);
550

    
551
    if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
552
      $result['status'] = 'fail';
553
      $result['error_description'] = 'can not provision drupal user 1';
554
      // Do not provision or synch user 1.
555
      return $result;
556
    }
557

    
558
    if ($account == FALSE || $account->uid == 0) {
559
      $result['status'] = 'fail';
560
      $result['error_description'] = 'can not provision ldap user unless corresponding drupal account exists first.';
561
      return $result;
562
    }
563

    
564
    if (!$this->ldapEntryProvisionServer || !$this->ldapEntryProvisionServer) {
565
      $result['status'] = 'fail';
566
      $result['error_description'] = 'no provisioning server enabled';
567
      return $result;
568
    }
569

    
570
    $ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
571
    $params = [
572
      'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
573
      'prov_events' => [LDAP_USER_EVENT_CREATE_LDAP_ENTRY],
574
      'module' => 'ldap_user',
575
      'function' => 'provisionLdapEntry',
576
      'include_count' => FALSE,
577
    ];
578

    
579
    list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
580
    $proposed_dn = (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn']) ? $proposed_ldap_entry['dn'] : NULL;
581
    $proposed_dn_lcase = drupal_strtolower($proposed_dn);
582
    $existing_ldap_entry = ($proposed_dn) ? $ldap_server->dnExists($proposed_dn, 'ldap_entry') : NULL;
583

    
584
    if ($error == LDAP_USER_PROV_RESULT_NO_PWD) {
585
      $result['status'] = 'fail';
586
      $result['description'] = 'Can not provision ldap account without user provided password.';
587
      $result['existing'] = $existing_ldap_entry;
588
      $result['proposed'] = $proposed_ldap_entry;
589
      $result['ldap_server'] = $ldap_server;
590
    }
591
    elseif (!$proposed_dn) {
592
      $result['status'] = 'fail';
593
      $result['description'] = t('failed to derive dn and or mappings');
594
      return $result;
595
    }
596
    elseif ($existing_ldap_entry) {
597
      $result['status'] = 'conflict';
598
      $result['description'] = 'can not provision ldap entry because exists already';
599
      $result['existing'] = $existing_ldap_entry;
600
      $result['proposed'] = $proposed_ldap_entry;
601
      $result['ldap_server'] = $ldap_server;
602
    }
603
    elseif ($test_query) {
604
      $result['status'] = 'fail';
605
      $result['description'] = 'not created because flagged as test query';
606
      $result['proposed'] = $proposed_ldap_entry;
607
      $result['ldap_server'] = $ldap_server;
608
    }
609
    else {
610
      // Stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call.
611
      $ldap_entries = [$proposed_dn_lcase => $proposed_ldap_entry];
612
      $context = [
613
        'action' => 'add',
614
        'corresponding_drupal_data' => [$proposed_dn_lcase => $account],
615
        'corresponding_drupal_data_type' => 'user',
616
      ];
617
      drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
618
      // Remove altered $proposed_ldap_entry from $ldap_entries array.
619
      $proposed_ldap_entry = $ldap_entries[$proposed_dn_lcase];
620

    
621
      $ldap_entry_created = $ldap_server->createLdapEntry($proposed_ldap_entry, $proposed_dn);
622
      if ($ldap_entry_created) {
623
        module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
624
        $result['status'] = 'success';
625
        $result['description'] = 'ldap account created';
626
        $result['proposed'] = $proposed_ldap_entry;
627
        $result['created'] = $ldap_entry_created;
628
        $result['ldap_server'] = $ldap_server;
629

    
630
        // Need to store <sid>|<dn> in ldap_user_prov_entries field, which may contain more than one.
631
        $ldap_user_prov_entry = $ldap_server->sid . '|' . $proposed_ldap_entry['dn'];
632
        if (!isset($user_entity->ldap_user_prov_entries[LANGUAGE_NONE])) {
633
          $user_entity->ldap_user_prov_entries = [LANGUAGE_NONE => []];
634
        }
635
        $ldap_user_prov_entry_exists = FALSE;
636
        foreach ($user_entity->ldap_user_prov_entries[LANGUAGE_NONE] as $i => $field_value_instance) {
637
          if ($field_value_instance == $ldap_user_prov_entry) {
638
            $ldap_user_prov_entry_exists = TRUE;
639
          }
640
        }
641
        if (!$ldap_user_prov_entry_exists) {
642
          $user_entity->ldap_user_prov_entries[LANGUAGE_NONE][] = [
643
            'value' => $ldap_user_prov_entry,
644
          ];
645

    
646
          // Save the field without calling user_save()
647
          field_attach_presave('user', $user_entity);
648
          field_attach_update('user', $user_entity);
649
        }
650

    
651
      }
652
      else {
653
        $result['status'] = 'fail';
654
        $result['proposed'] = $proposed_ldap_entry;
655
        $result['created'] = $ldap_entry_created;
656
        $result['ldap_server'] = $ldap_server;
657
        $result['existing'] = NULL;
658
      }
659
    }
660

    
661
    $tokens = [
662
      '%dn' => isset($result['proposed']['dn']) ? $result['proposed']['dn'] : NULL,
663
      '%sid' => (isset($result['ldap_server']) && $result['ldap_server']) ? $result['ldap_server']->sid : 0,
664
      '%username' => @$account->name,
665
      '%uid' => @$account->uid,
666
      '%description' => @$result['description'],
667
    ];
668
    if (!$test_query && isset($result['status'])) {
669
      if ($result['status'] == 'success') {
670
        if ($this->detailedWatchdog) {
671
          watchdog('ldap_user', 'LDAP entry on server %sid created dn=%dn.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
672
        }
673
      }
674
      elseif ($result['status'] == 'conflict') {
675
        if ($this->detailedWatchdog) {
676
          watchdog('ldap_user', 'LDAP entry on server %sid not created because of existing ldap entry. %description. username=%username, uid=%uid', $tokens, WATCHDOG_WARNING);
677
        }
678
      }
679
      elseif ($result['status'] == 'fail') {
680
        watchdog('ldap_user', 'LDAP entry on server %sid not created because error.  %description. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
681
      }
682
    }
683
    return $result;
684
  }
685

    
686
  /**
687
   * Given a drupal account, synch to related ldap entry.
688
   *
689
   * @param drupal user object $account
690
   *   Drupal user object.
691
   * @param array $user_edit
692
   *   Edit array for user_save.  generally null unless user account is being created or modified in same synching.
693
   * @param array $ldap_user
694
   *   current ldap data of user. @see README.developers.txt for structure.
695
   *
696
   * @return TRUE on success or FALSE on fail.
697
   */
698
  public function synchToLdapEntry($account, $user_edit = NULL, $ldap_user = [], $test_query = FALSE) {
699

    
700
    if (is_object($account) && property_exists($account, 'uid') && $account->uid == 1) {
701
      // Do not provision or synch user 1.
702
      return FALSE;
703
    }
704

    
705
    $watchdog_tokens = [];
706
    $result = FALSE;
707
    $proposed_ldap_entry = FALSE;
708

    
709
    if ($this->ldapEntryProvisionServer) {
710
      $ldap_server = ldap_servers_get_servers($this->ldapEntryProvisionServer, NULL, TRUE);
711

    
712
      $params = [
713
        'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
714
        'prov_events' => [LDAP_USER_EVENT_SYNCH_TO_LDAP_ENTRY],
715
        'module' => 'ldap_user',
716
        'function' => 'synchToLdapEntry',
717
        'include_count' => FALSE,
718
      ];
719

    
720
      list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user);
721
      if ($error != LDAP_USER_PROV_RESULT_NO_ERROR) {
722
        $result = FALSE;
723
      }
724
      elseif (is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn'])) {
725
        $existing_ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry');
726
        // This array represents attributes to be modified; not comprehensive list of attributes.
727
        $attributes = [];
728
        foreach ($proposed_ldap_entry as $attr_name => $attr_values) {
729
          if ($attr_name != 'dn') {
730
            if (isset($attr_values['count'])) {
731
              unset($attr_values['count']);
732
            }
733
            if (count($attr_values) == 1) {
734
              $attributes[$attr_name] = $attr_values[0];
735
            }
736
            else {
737
              $attributes[$attr_name] = $attr_values;
738
            }
739
          }
740
        }
741

    
742
        if ($test_query) {
743
          $proposed_ldap_entry = $attributes;
744
          $result = [
745
            'proposed' => $proposed_ldap_entry,
746
            'server' => $ldap_server,
747
          ];
748
        }
749
        else {
750
          // Stick $proposed_ldap_entry in $ldap_entries array for drupal_alter call.
751
          $proposed_dn_lcase = drupal_strtolower($proposed_ldap_entry['dn']);
752
          $ldap_entries = [$proposed_dn_lcase => $attributes];
753
          $context = [
754
            'action' => 'update',
755
            'corresponding_drupal_data' => [$proposed_dn_lcase => $attributes],
756
            'corresponding_drupal_data_type' => 'user',
757
          ];
758
          drupal_alter('ldap_entry_pre_provision', $ldap_entries, $ldap_server, $context);
759
          // Remove altered $proposed_ldap_entry from $ldap_entries array.
760
          $attributes = $ldap_entries[$proposed_dn_lcase];
761
          $result = $ldap_server->modifyLdapEntry($proposed_ldap_entry['dn'], $attributes);
762
          // Success.
763
          if ($result) {
764
            module_invoke_all('ldap_entry_post_provision', $ldap_entries, $ldap_server, $context);
765
          }
766
        }
767
      }
768
      // Failed to get acceptable proposed ldap entry.
769
      else {
770
        $result = FALSE;
771
      }
772
    }
773

    
774
    $tokens = [
775
      '%dn' => isset($proposed_ldap_entry['dn']) ? $proposed_ldap_entry['dn'] : NULL,
776
      '%sid' => $this->ldapEntryProvisionServer,
777
      '%username' => $account->name,
778
      '%uid' => ($test_query || !property_exists($account, 'uid')) ? '' : $account->uid,
779
    ];
780

    
781
    if ($result) {
782
      watchdog('ldap_user', 'LDAP entry on server %sid synched dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
783
    }
784
    else {
785
      watchdog('ldap_user', 'LDAP entry on server %sid not synched because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
786
    }
787

    
788
    return $result;
789

    
790
  }
791

    
792
  /**
793
   * Given a drupal account, query ldap and get all user fields and create user account.
794
   *
795
   * @param array $account
796
   *   drupal account array with minimum of name.
797
   * @param array $user_edit
798
   *   drupal edit array in form user_save($account, $user_edit) would take,
799
   *   generally empty unless overriding synchToDrupalAccount derived values.
800
   * @param array $ldap_user
801
   *   as user's ldap entry.  passed to avoid requerying ldap in cases where already present.
802
   * @param bool $save
803
   *   indicating if drupal user should be saved.  generally depends on where function is called from.
804
   *
805
   * @return bool|object
806
   *   Result of user_save() function is $save is true, otherwise return TRUE
807
   *   $user_edit data returned by reference
808
   */
809
  public function synchToDrupalAccount($drupal_user, &$user_edit, $prov_event = LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user = NULL, $save = FALSE) {
810

    
811
    $debug = [
812
      'account' => $drupal_user,
813
      'user_edit' => $user_edit,
814
      'ldap_user' => $ldap_user,
815
    ];
816

    
817
    if (
818
        (!$ldap_user  && !isset($drupal_user->name)) ||
819
        (!$drupal_user && $save) ||
820
        ($ldap_user && !isset($ldap_user['sid']))
821
    ) {
822
      // Should throw watchdog error also.
823
      return FALSE;
824
    }
825

    
826
    if (!$ldap_user && $this->drupalAcctProvisionServer) {
827
      $ldap_user = ldap_servers_get_user_ldap_data($drupal_user->name, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
828
    }
829

    
830
    if (!$ldap_user) {
831
      return FALSE;
832
    }
833

    
834
    if ($this->drupalAcctProvisionServer) {
835
      $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, NULL, TRUE);
836
      $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, [$prov_event]);
837
    }
838

    
839
    if ($save) {
840
      $account = user_load($drupal_user->uid);
841
      $result = user_save($account, $user_edit, 'ldap_user');
842
      return $result;
843
    }
844
    else {
845
      return TRUE;
846
    }
847
  }
848

    
849
  /**
850
   * Given a drupal account, delete user account.
851
   *
852
   * @param string $username
853
   *   drupal account name.
854
   *
855
   * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
856
   */
857
  public function deleteDrupalAccount($username) {
858
    $user = user_load_by_name($username);
859
    if (is_object($user)) {
860
      user_delete($user->uid);
861
      return TRUE;
862
    }
863
    else {
864
      return FALSE;
865
    }
866
  }
867

    
868
  /**
869
   * Given a drupal account, find the related ldap entry.
870
   *
871
   * @param drupal user object $account
872
   *
873
   * @return FALSE or ldap entry
874
   */
875
  public function getProvisionRelatedLdapEntry($account, $prov_events = NULL) {
876
    if (!$prov_events) {
877
      $prov_events = ldap_user_all_events();
878
    }
879
    $sid = $this->ldapEntryProvisionServer;
880
    if (!$sid) {
881
      return FALSE;
882
    }
883
    // $user_entity->ldap_user_prov_entries,.
884
    $ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
885
    $params = [
886
      'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY,
887
      'prov_events' => $prov_events,
888
      'module' => 'ldap_user',
889
      'function' => 'getProvisionRelatedLdapEntry',
890
      'include_count' => FALSE,
891
    ];
892
    list($proposed_ldap_entry, $error) = $this->drupalUserToLdapEntry($account, $ldap_server, $params);
893
    if (!(is_array($proposed_ldap_entry) && isset($proposed_ldap_entry['dn']) && $proposed_ldap_entry['dn'])) {
894
      return FALSE;
895
    }
896
    $ldap_entry = $ldap_server->dnExists($proposed_ldap_entry['dn'], 'ldap_entry', []);
897
    return $ldap_entry;
898

    
899
  }
900

    
901
  /**
902
   * Given a drupal account, delete ldap entry that was provisioned based on it
903
   *   normally this will be 0 or 1 entry, but the ldap_user_provisioned_ldap_entries
904
   *   field attached to the user entity track each ldap entry provisioned.
905
   *
906
   * @param object $account
907
   *   drupal account.
908
   *
909
   * @return TRUE or FALSE.  FALSE indicates failed or action not enabled in ldap user configuration
910
   */
911
  public function deleteProvisionedLdapEntries($account) {
912
    // Determine server that is associated with user.
913
    $boolean_result = FALSE;
914
    if (isset($account->ldap_user_prov_entries[LANGUAGE_NONE][0])) {
915
      foreach ($account->ldap_user_prov_entries[LANGUAGE_NONE] as $i => $field_instance) {
916
        $parts = explode('|', $field_instance['value']);
917
        if (count($parts) == 2) {
918

    
919
          list($sid, $dn) = $parts;
920
          $ldap_server = ldap_servers_get_servers($sid, NULL, TRUE);
921
          if (is_object($ldap_server) && $dn) {
922
            $boolean_result = $ldap_server->delete($dn);
923
            $tokens = ['%sid' => $sid, '%dn' => $dn, '%username' => $account->name, '%uid' => $account->uid];
924
            if ($boolean_result) {
925
              watchdog('ldap_user', 'LDAP entry on server %sid deleted dn=%dn. username=%username, uid=%uid', $tokens, WATCHDOG_INFO);
926
            }
927
            else {
928
              watchdog('ldap_user', 'LDAP entry on server %sid not deleted because error. username=%username, uid=%uid', $tokens, WATCHDOG_ERROR);
929
            }
930
          }
931
          else {
932
            $boolean_result = FALSE;
933
          }
934
        }
935
      }
936
    }
937
    return $boolean_result;
938

    
939
  }
940

    
941
  /**
942
   * Populate ldap entry array for provisioning.
943
   *
944
   * @param array $account
945
   *   drupal account.
946
   * @param \LdapServer $ldap_server
947
   * @param array $ldap_user
948
   *   ldap entry of user, returned by reference.
949
   * @param array $params
950
   *   with the following key values:
951
   *   'ldap_context' =>
952
   *   'module' => module calling function, e.g. 'ldap_user'
953
   *   'function' => function calling function, e.g. 'provisionLdapEntry'
954
   *   'include_count' => should 'count' array key be included
955
   *   'direction' => LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY || LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER.
956
   *
957
   * @return array(ldap entry, $result) in ldap extension array format.!THIS IS NOT THE ACTUAL LDAP ENTRY
958
   */
959
  public function drupalUserToLdapEntry($account, $ldap_server, $params, $ldap_user_entry = NULL) {
960
    $provision = (isset($params['function']) && $params['function'] == 'provisionLdapEntry');
961
    $result = LDAP_USER_PROV_RESULT_NO_ERROR;
962
    if (!$ldap_user_entry) {
963
      $ldap_user_entry = [];
964
    }
965

    
966
    if (!is_object($account) || !is_object($ldap_server)) {
967
      return [NULL, LDAP_USER_PROV_RESULT_BAD_PARAMS];
968
    }
969
    $watchdog_tokens = [
970
      '%drupal_username' => $account->name,
971
    ];
972
    $include_count = (isset($params['include_count']) && $params['include_count']);
973

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

    
977
    $mappings = $this->getSynchMappings($direction, $prov_events);
978
    foreach ($mappings as $field_key => $field_detail) {
979
      list($ldap_attr_name, $ordinal, $conversion) = ldap_servers_token_extract_parts($field_key, TRUE);
980
      $ordinal = (!$ordinal) ? 0 : $ordinal;
981
      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])) {
982
        // don't override values passed in.
983
        continue;
984
      }
985

    
986
      $synched = $this->isSynched($field_key, $params['prov_events'], LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY);
987
      if ($synched) {
988
        $token = ($field_detail['user_attr'] == 'user_tokens') ? $field_detail['user_tokens'] : $field_detail['user_attr'];
989
        $value = ldap_servers_token_replace($account, $token, 'user_account');
990

    
991
        // Deal with empty/unresolved password.
992
        if (substr($token, 0, 10) == '[password.' && (!$value || $value == $token)) {
993
          if (!$provision) {
994
            // don't overwrite password on synch if no value provided.
995
            continue;
996
          }
997
        }
998

    
999
        if ($ldap_attr_name == 'dn' && $value) {
1000
          $ldap_user_entry['dn'] = $value;
1001
        }
1002
        elseif ($value) {
1003
          if (!isset($ldap_user_entry[$ldap_attr_name]) || !is_array($ldap_user_entry[$ldap_attr_name])) {
1004
            $ldap_user_entry[$ldap_attr_name] = [];
1005
          }
1006
          $ldap_user_entry[$ldap_attr_name][$ordinal] = $value;
1007
          if ($include_count) {
1008
            $ldap_user_entry[$ldap_attr_name]['count'] = count($ldap_user_entry[$ldap_attr_name]);
1009
          }
1010

    
1011
        }
1012

    
1013
      }
1014

    
1015
    }
1016

    
1017
    /**
1018
     * 4. call drupal_alter() to allow other modules to alter $ldap_user
1019
     */
1020
    $params['account'] = $account;
1021
    drupal_alter('ldap_entry', $ldap_user_entry, $params);
1022

    
1023
    return [$ldap_user_entry, $result];
1024

    
1025
  }
1026

    
1027
  /**
1028
   * Given a drupal account, query ldap and get all user fields and save user account
1029
   * (note: parameters are in odd order to match synchDrupalAccount handle)
1030
   *
1031
   * @param array $account
1032
   *   drupal account object or null.
1033
   * @param array $user_edit
1034
   *   drupal edit array in form user_save($account, $user_edit) would take.
1035
   * @param array $ldap_user
1036
   *   as user's ldap entry.  passed to avoid requerying ldap in cases where already present.
1037
   * @param bool $save
1038
   *   indicating if drupal user should be saved.  generally depends on where function is called from and if the.
1039
   *
1040
   * @return bool
1041
   *   Resultof user_save() function is $save is true, otherwise return TRUE on
1042
   *   success or FALSE on any problem
1043
   *
1044
   *   $user_edit data returned by reference
1045
   */
1046
  public function provisionDrupalAccount($account = FALSE, &$user_edit, $ldap_user = NULL, $save = TRUE) {
1047

    
1048
    $watchdog_tokens = [];
1049
    /**
1050
     * @todo
1051
     * -- add error catching for conflicts, conflicts should be checked before calling this function.
1052
     *
1053
     */
1054

    
1055
    if (!$account) {
1056
      $account = new stdClass();
1057
    }
1058
    $account->is_new = TRUE;
1059

    
1060
    if (!$ldap_user && !isset($user_edit['name'])) {
1061
      return FALSE;
1062
    }
1063

    
1064
    if (!$ldap_user) {
1065
      $watchdog_tokens['%username'] = $user_edit['name'];
1066
      if ($this->drupalAcctProvisionServer) {
1067
        $ldap_user = ldap_servers_get_user_ldap_data($user_edit['name'], $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
1068
      }
1069
      if (!$ldap_user) {
1070
        if ($this->detailedWatchdog) {
1071
          watchdog('ldap_user', '%username : failed to find associated ldap entry for username in provision.', $watchdog_tokens, WATCHDOG_DEBUG);
1072
        }
1073
        return FALSE;
1074
      }
1075
    }
1076

    
1077
    if (!isset($user_edit['name']) && isset($account->name)) {
1078
      $user_edit['name'] = $account->name;
1079
      $watchdog_tokens['%username'] = $user_edit['name'];
1080
    }
1081
    // When using the multi-domain last authentication option
1082
    // $ldap_server breaks beacause $this->drupalAcctProvisionServer is set on LDAP_USER_AUTH_SERVER_SID
1083
    // So we need to check it's not the case before using ldap_servers_get_servers.
1084
    if ($this->drupalAcctProvisionServer && $this->drupalAcctProvisionServer != LDAP_USER_AUTH_SERVER_SID) {
1085

    
1086
      /** @var \LdapServer $ldap_server */
1087
      // $ldap_user['sid'].
1088
      $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);
1089

    
1090
      $params = [
1091
        'account' => $account,
1092
        'user_edit' => $user_edit,
1093
        'prov_event' => LDAP_USER_EVENT_CREATE_DRUPAL_USER,
1094
        'module' => 'ldap_user',
1095
        'function' => 'provisionDrupalAccount',
1096
        'direction' => LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER,
1097
      ];
1098

    
1099
      drupal_alter('ldap_entry', $ldap_user, $params);
1100

    
1101
      // Look for existing drupal account with same puid.  if so update username and attempt to synch in current context.
1102
      $puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
1103
      $existing_account_from_puid = ($puid) ? $ldap_server->userUserEntityFromPuid($puid) : FALSE;
1104

    
1105
      // Synch drupal account, since drupal account exists.
1106
      if ($existing_account_from_puid) {
1107
        // 1. correct username and authmap.
1108
        $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, [LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER]);
1109
        $account = user_save($existing_account_from_puid, $user_edit, 'ldap_user');
1110
        user_set_authmaps($account, ["authname_ldap_user" => $user_edit['name']]);
1111
        // 2. attempt synch if appropriate for current context.
1112
        if ($account) {
1113
          $account = $this->synchToDrupalAccount($account, $user_edit, LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $ldap_user, TRUE);
1114
        }
1115
        return $account;
1116
      }
1117
      // Create drupal account.
1118
      else {
1119
        $this->entryToUserEdit($ldap_user, $user_edit, $ldap_server, LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, [LDAP_USER_EVENT_CREATE_DRUPAL_USER]);
1120
        if ($save) {
1121
          $watchdog_tokens = ['%drupal_username' => $user_edit['name']];
1122
          if (empty($user_edit['name'])) {
1123
            drupal_set_message(t('User account creation failed because of invalid, empty derived Drupal username.'), 'error');
1124
            watchdog('ldap_user',
1125
              'Failed to create Drupal account %drupal_username because drupal username could not be derived.',
1126
              $watchdog_tokens,
1127
              WATCHDOG_ERROR
1128
            );
1129
            return FALSE;
1130
          }
1131
          if (!isset($user_edit['mail']) || !$user_edit['mail']) {
1132
            drupal_set_message(t('User account creation failed because of invalid, empty derived email address.'), 'error');
1133
            watchdog('ldap_user',
1134
              'Failed to create Drupal account %drupal_username because email address could not be derived by LDAP User module',
1135
              $watchdog_tokens,
1136
              WATCHDOG_ERROR
1137
            );
1138
            return FALSE;
1139
          }
1140
          if (($this->accountsWithSameEmail == LDAP_USER_ACCOUNTS_WITH_SAME_EMAIL_DISABLED) && ($account_with_same_email = user_load_by_mail($user_edit['mail']))) {
1141
            $watchdog_tokens['%email'] = $user_edit['mail'];
1142
            $watchdog_tokens['%duplicate_name'] = $account_with_same_email->name;
1143
            watchdog('ldap_user', 'LDAP user %drupal_username has email address
1144
              (%email) conflict with a drupal user %duplicate_name', $watchdog_tokens, WATCHDOG_ERROR);
1145
            drupal_set_message(t('Another user already exists in the system with the same email address. You should contact the system administrator in order to solve this conflict.'), 'error');
1146
            return FALSE;
1147
          }
1148
          $account = user_save(NULL, $user_edit, 'ldap_user');
1149
          if (!$account) {
1150
            drupal_set_message(t('User account creation failed because of system problems.'), 'error');
1151
          }
1152
          else {
1153
            user_set_authmaps($account, ['authname_ldap_user' => $account->name]);
1154
            ldap_user_ldap_provision_semaphore('drupal_created', 'set', $account->name);
1155
          }
1156
          return $account;
1157
        }
1158
        return TRUE;
1159
      }
1160
    }
1161
  }
1162

    
1163
  /**
1164
   * Set ldap associations of a drupal account by altering user fields.
1165
   *
1166
   * @param string $drupal_username
1167
   *
1168
   * @return bool
1169
   *   TRUE on success, FALSE on error or failure because of invalid user or ldap accounts
1170
   */
1171
  public function ldapAssociateDrupalAccount($drupal_username) {
1172

    
1173
    if ($this->drupalAcctProvisionServer) {
1174
      $prov_events = [LDAP_USER_EVENT_LDAP_ASSOCIATE_DRUPAL_ACCT];
1175
      // $ldap_user['sid'].
1176
      $ldap_server = ldap_servers_get_servers($this->drupalAcctProvisionServer, 'enabled', TRUE);
1177
      $account = user_load_by_name($drupal_username);
1178
      $ldap_user = ldap_servers_get_user_ldap_data($drupal_username, $this->drupalAcctProvisionServer, 'ldap_user_prov_to_drupal');
1179
      if (!$account) {
1180
        watchdog(
1181
          'ldap_user',
1182
          'Failed to LDAP associate drupal account %drupal_username because account not found',
1183
          ['%drupal_username' => $drupal_username],
1184
          WATCHDOG_ERROR
1185
        );
1186
        return FALSE;
1187
      }
1188
      elseif (!$ldap_user) {
1189
        watchdog(
1190
          'ldap_user',
1191
          'Failed to LDAP associate drupal account %drupal_username because corresponding LDAP entry not found',
1192
          ['%drupal_username' => $drupal_username],
1193
          WATCHDOG_ERROR
1194
        );
1195
        return FALSE;
1196
      }
1197
      else {
1198
        $user_edit = [];
1199
        $user_edit['data']['ldap_user']['init'] = [
1200
          'sid'  => $ldap_user['sid'],
1201
          'dn'   => $ldap_user['dn'],
1202
          'mail'   => $account->mail,
1203
        ];
1204
        $ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
1205
        if ($ldap_user_puid) {
1206
          $user_edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid;
1207
        }
1208
        $user_edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
1209
        $user_edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
1210
        $user_edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
1211
        $account = user_save($account, $user_edit, 'ldap_user');
1212
        return (boolean) $account;
1213
      }
1214
    }
1215
    else {
1216
      return FALSE;
1217
    }
1218
  }
1219

    
1220
  /**
1221
   * Populate $user edit array (used in hook_user_save, hook_user_update, etc)
1222
   * ... should not assume all attribues are present in ldap entry.
1223
   *
1224
   * @param $ldap_user
1225
   * @param array $edit
1226
   *   see hook_user_save, hook_user_update, etc.
1227
   * @param \LdapServer $ldap_server
1228
   * @param int $direction
1229
   * @param array $prov_events
1230
   */
1231
  public function entryToUserEdit($ldap_user, &$edit, $ldap_server, $direction = LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER, $prov_events = NULL) {
1232

    
1233
    // Need array of user fields and which direction and when they should be synched.
1234
    if (!$prov_events) {
1235
      $prov_events = ldap_user_all_events();
1236
    }
1237
    $mail_synched = $this->isSynched('[property.mail]', $prov_events, $direction);
1238
    if (!isset($edit['mail']) && $mail_synched) {
1239
      $derived_mail = $ldap_server->userEmailFromLdapEntry($ldap_user['attr']);
1240
      if ($derived_mail) {
1241
        $edit['mail'] = $derived_mail;
1242
      }
1243
    }
1244

    
1245
    $drupal_username = $ldap_server->userUsernameFromLdapEntry($ldap_user['attr']);
1246

    
1247
    if ($this->isSynched('[property.name]', $prov_events, $direction) && !isset($edit['name']) && $drupal_username) {
1248
      $edit['name'] = $drupal_username;
1249
    }
1250

    
1251
    if ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER && in_array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, $prov_events)) {
1252
      $edit['mail'] = isset($edit['mail']) ? $edit['mail'] : $ldap_user['mail'];
1253
      if (!isset($edit['pass'])) {
1254
        $edit['pass'] = user_password(20);
1255
        watchdog('ldap_user', '20 character random password generated for the %username account that has been created.', ['%username' => $drupal_username], WATCHDOG_INFO);
1256
      }
1257
      $edit['init'] = isset($edit['init']) ? $edit['init'] : $edit['mail'];
1258
      $edit['status'] = isset($edit['status']) ? $edit['status'] : 1;
1259
      $edit['signature'] = isset($edit['signature']) ? $edit['signature'] : '';
1260

    
1261
      $edit['data']['ldap_user']['init'] = [
1262
        'sid'  => $ldap_user['sid'],
1263
        'dn'   => $ldap_user['dn'],
1264
        'mail' => $edit['mail'],
1265
      ];
1266
    }
1267

    
1268
    /*
1269
     * Make sure the user account has the latest ldap_user settings
1270
     * when syncing the profile.
1271
     */
1272
    if ($direction == LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER && in_array(LDAP_USER_EVENT_SYNCH_TO_DRUPAL_USER, $prov_events)) {
1273
      $edit['data']['ldap_user']['init'] = [
1274
        'sid'  => $ldap_user['sid'],
1275
        'dn'   => $ldap_user['dn'],
1276
        'mail' => isset($edit['mail']) && !empty($edit['mail']) ? $edit['mail'] : $ldap_user['mail'],
1277
      ];
1278
    }
1279

    
1280
    if ($this->isSynched('[property.picture]', $prov_events, $direction)) {
1281
      $picture = $ldap_server->userPictureFromLdapEntry($ldap_user['attr'], $drupal_username);
1282
      if ($picture) {
1283
        if (in_array(LDAP_USER_EVENT_CREATE_DRUPAL_USER, $prov_events)) {
1284
          $edit['picture'] = $picture->fid;
1285
        }
1286
        else {
1287
          $edit['picture'] = $picture;
1288
        }
1289
        $edit['data']['ldap_user']['init']['thumb5md'] = $picture->md5Sum;
1290
      }
1291
    }
1292

    
1293
    /**
1294
     * basic $user ldap fields
1295
     */
1296
    if ($this->isSynched('[field.ldap_user_puid]', $prov_events, $direction)) {
1297
      $ldap_user_puid = $ldap_server->userPuidFromLdapEntry($ldap_user['attr']);
1298
      if ($ldap_user_puid) {
1299
        $edit['ldap_user_puid'][LANGUAGE_NONE][0]['value'] = $ldap_user_puid;
1300
      }
1301
    }
1302
    if ($this->isSynched('[field.ldap_user_puid_property]', $prov_events, $direction)) {
1303
      $edit['ldap_user_puid_property'][LANGUAGE_NONE][0]['value'] = $ldap_server->unique_persistent_attr;
1304
    }
1305
    if ($this->isSynched('[field.ldap_user_puid_sid]', $prov_events, $direction)) {
1306
      $edit['ldap_user_puid_sid'][LANGUAGE_NONE][0]['value'] = $ldap_server->sid;
1307
    }
1308
    if ($this->isSynched('[field.ldap_user_current_dn]', $prov_events, $direction)) {
1309
      $edit['ldap_user_current_dn'][LANGUAGE_NONE][0]['value'] = $ldap_user['dn'];
1310
    }
1311

    
1312
    // Get any additional mappings.
1313
    $mappings = $this->getSynchMappings($direction, $prov_events);
1314

    
1315
    // Loop over the mappings.
1316
    foreach ($mappings as $user_attr_key => $field_detail) {
1317

    
1318
      // Make sure this mapping is relevant to the sync context.
1319
      if (!$this->isSynched($user_attr_key, $prov_events, $direction)) {
1320
        continue;
1321
      }
1322
      /**
1323
        * if "convert from binary is selected" and no particular method is in token,
1324
        * default to ldap_servers_binary() function
1325
        */
1326
      if ($field_detail['convert'] && strpos($field_detail['ldap_attr'], ';') === FALSE) {
1327
        $field_detail['ldap_attr'] = str_replace(']', ';binary]', $field_detail['ldap_attr']);
1328
      }
1329
      $value = ldap_servers_token_replace($ldap_user['attr'], $field_detail['ldap_attr'], 'ldap_entry');
1330
      list($value_type, $value_name, $value_instance) = ldap_servers_parse_user_attr_name($user_attr_key);
1331

    
1332
      // $value_instance not used, may have future use case.
1333
      // Are we dealing with a field?
1334
      if ($value_type == 'field') {
1335
        // Field api field - first we get the field.
1336
        $field = field_info_field($value_name);
1337
        // Then the columns for the field in the schema.
1338
        $columns = array_keys($field['columns']);
1339
        // Then we convert the value into an array if it's scalar.
1340
        $values = $field['cardinality'] == 1 ? [$value] : (array) $value;
1341

    
1342
        $items = [];
1343
        // Loop over the values and set them in our $items array.
1344
        foreach ($values as $delta => $value) {
1345
          if (isset($value)) {
1346
            // We set the first column value only, this is consistent with
1347
            // the Entity Api (@see entity_metadata_field_property_set).
1348
            $items[$delta][$columns[0]] = $value;
1349
          }
1350
        }
1351
        // Add them to our edited item.
1352
        $edit[$value_name][LANGUAGE_NONE] = $items;
1353
      }
1354
      elseif ($value_type == 'property') {
1355
        // Straight property.
1356
        $edit[$value_name] = $value;
1357
      }
1358
    }
1359

    
1360
    // Allow other modules to have a say.
1361
    drupal_alter('ldap_user_edit_user', $edit, $ldap_user, $ldap_server, $prov_events);
1362
    // don't let empty 'name' value pass for user.
1363
    if (isset($edit['name']) && $edit['name'] == '') {
1364
      unset($edit['name']);
1365
    }
1366

    
1367
  }
1368

    
1369
  /**
1370
   * Given configuration of synching, determine is a given synch should occur.
1371
   *
1372
   * @param string $attr_token
1373
   *   e.g. [property.mail], [field.ldap_user_puid_property].
1374
   * @param array $prov_events
1375
   *   e.g. array(LDAP_USER_EVENT_CREATE_DRUPAL_USER).  typically array with 1
1376
   *   element.
1377
   * @param scalar $direction
1378
   *   LDAP_USER_PROV_DIRECTION_TO_DRUPAL_USER or
1379
   *   LDAP_USER_PROV_DIRECTION_TO_LDAP_ENTRY.
1380
   *
1381
   * @return bool
1382
   */
1383
  public function isSynched($attr_token, $prov_events, $direction) {
1384
    $result = (boolean) (
1385
      isset($this->synchMapping[$direction][$attr_token]['prov_events']) &&
1386
      count(array_intersect($prov_events, $this->synchMapping[$direction][$attr_token]['prov_events']))
1387
    );
1388
    if (!$result) {
1389
      if (isset($this->synchMapping[$direction][$attr_token])) {
1390
      }
1391
      else {
1392
      }
1393
    }
1394
    return $result;
1395
  }
1396

    
1397
}