DAViCal
Loading...
Searching...
No Matches
drivers_ldap.php
1<?php
13
14require_once("auth-functions.php");
15
20{
24
29
31
32
38 function __construct($config)
39 {
40 global $c;
41 $host=$config['host'];
42 $port=$config['port'];
43 if(!function_exists('ldap_connect')){
44 $c->messages[] = i18n("drivers_ldap : function ldap_connect not defined, check your php_ldap module");
45 $this->valid=false;
46 return ;
47 }
48
49 //Set LDAP protocol version
50 if (isset($config['protocolVersion']))
51 ldap_set_option($this->connect, LDAP_OPT_PROTOCOL_VERSION, $config['protocolVersion']);
52 if (isset($config['optReferrals']))
53 ldap_set_option($this->connect, LDAP_OPT_REFERRALS, $config['optReferrals']);
54 if (isset($config['networkTimeout']))
55 ldap_set_option($this->connect, LDAP_OPT_NETWORK_TIMEOUT, $config['networkTimeout']);
56
57 // If we are given a URI (or multiple) to connect to, use them. This allows support for LDAPS connections
58 // as well as redundant ldap servers to connect to
59 // Otherwise default to host and port
60 if (isset($config['uri']))
61 $this->connect=ldap_connect($config['uri']);
62 elseif ($port)
63 $this->connect=ldap_connect($host, $port);
64 else
65 $this->connect=ldap_connect($host);
66
67 if (! $this->connect){
68 if (isset($config['uri'])) {
69 $c->messages[] = sprintf(translate( 'drivers_ldap : Unable to connect to LDAP with URI: %s'), $config['uri'] );
70 } else {
71 if (! $port) $port = 'default';
72 $c->messages[] = sprintf(translate( 'drivers_ldap : Unable to connect to LDAP with port %s on host %s'), $port, $host );
73 }
74 $this->valid=false;
75 return ;
76 } else {
77 if (isset($config['uri']))
78 dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s", $config['uri'] );
79 elseif ($port)
80 dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s, port %s", $host, $port );
81 else
82 dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s", $host );
83 }
84
85 // Start TLS if desired (requires protocol version 3)
86 if (isset($config['startTLS'])) {
87 if (!ldap_set_option($this->connect, LDAP_OPT_PROTOCOL_VERSION, 3)) {
88 $c->messages[] = i18n('drivers_ldap : Failed to set LDAP to use protocol version 3, TLS not supported');
89 $this->valid=false;
90 return;
91 }
92 if (!ldap_start_tls($this->connect)) {
93 $c->messages[] = i18n('drivers_ldap : Could not start TLS: ldap_start_tls() failed');
94 $this->valid=false;
95 return;
96 }
97 }
98
99 //Set the search scope to be used, default to subtree. This sets the functions to be called later.
100 if (!isset($config['scope'])) $config['scope'] = 'subtree';
101 switch (strtolower($config['scope'])) {
102 case "base":
103 $this->ldap_query_one = 'ldap_read';
104 $this->ldap_query_all = 'ldap_read';
105 break;
106 case "onelevel":
107 $this->ldap_query_one = 'ldap_list';
108 $this->ldap_query_all = 'ldap_list';
109 break;
110 default:
111 $this->ldap_query_one = 'ldap_search';
112 $this->ldap_query_all = 'ldap_search';
113 break;
114 }
115
116 // This is useful to see what is happening at a low level. I also
117 // recommend tcpdump...
118 //ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
119
120 //connect as root
121 if (isset($config['sasl'])){
122 $bind_result = ldap_sasl_bind(
123 $this->connect,
124 (isset($config['bindDN']) ? $config['bindDN'] : null),
125 (isset($config['passDN']) ? $config['passDN'] : null),
126 (isset($config['sasl_mech']) ? $config['sasl_mech'] : null),
127 (isset($config['sasl_realm']) ? $config['sasl_realm'] : null),
128 (isset($config['sasl_authc_id']) ? $config['sasl_authc_id'] : null),
129 (isset($config['sasl_authz_id']) ? $config['sasl_authz_id'] : null),
130 (isset($config['sasl_props']) ? $config['sasl_props'] : null) );
131 } else {
132 $bind_result = ldap_bind($this->connect, (isset($config['bindDN']) ? $config['bindDN'] : null), (isset($config['passDN']) ? $config['passDN'] : null) );
133 }
134 if (!$bind_result){
135 $bindDN = isset($config['bindDN']) ? $config['bindDN'] : 'anonymous';
136 $passDN = isset($config['passDN']) ? $config['passDN'] : 'anonymous';
137
138 dbg_error_log( "LDAP", i18n('drivers_ldap : Failed to bind to host %1$s on port %2$s with bindDN of %3$s'), $host, $port, $bindDN );
139 $c->messages[] = i18n( 'drivers_ldap : Unable to bind to LDAP - check your configuration for bindDN and passDN, and that your LDAP server is reachable');
140 $this->valid=false;
141 return ;
142 }
143 $this->valid = true;
144
145 // root to start search
146 $this->baseDNUsers =
147 is_string($config['baseDNUsers']) ?
148 array($config['baseDNUsers']) : $config['baseDNUsers'];
149 $this->filterUsers =
150 (isset($config['filterUsers']) ?
151 $config['filterUsers'] : '(objectclass=*)');
152 $this->baseDNGroups =
153 (isset($config['baseDNGroups']) ?
154 (is_string($config['baseDNGroups']) ?
155 array($config['baseDNGroups']) : $config['baseDNGroups']) : null);
156 $this->filterGroups =
157 (isset($config['filterGroups']) ?
158 $config['filterGroups'] : '(objectclass=*)');
159 }
160
164 function getAllUsers($attributes){
165 global $c;
166
167 $query = $this->ldap_query_all;
168 $ret = array();
169
170 foreach($this->baseDNUsers as $baseDNUsers) {
171 $entry = $query($this->connect, $baseDNUsers, $this->filterUsers, $attributes);
172
173 if (! $entry) {
174 $c->messages[] = sprintf(translate('Error NoUserFound with filter >%s<, attributes >%s< , baseDN >%s<'),
175 $this->filterUsers,
176 join(', ', $attributes),
177 $baseDNUsers);
178 dbg_error_log('LDAP', 'Error NoUserFound with filter >%s<, attributes >%s< , baseDN >%s<',
179 $this->filterUsers,
180 join(', ', $attributes),
181 $baseDNUsers);
182 return $ret;
183 }
184
185 if (! ldap_first_entry($this->connect, $entry)) {
186 $c->messages[] = sprintf(translate('Error NoUserFound with filter >%s<, attributes >%s< , dn >%s<'),
187 $this->filterUsers,
188 join(', ', $attributes),
189 $baseDNUsers);
190 return $ret;
191 }
192
193 $row = array();
194 for ($i = ldap_first_entry($this->connect,$entry);
195 $i && $arr = ldap_get_attributes($this->connect,$i);
196 $i = ldap_next_entry($this->connect,$i) ) {
197 $row = array();
198
199 for ($j=0; $j < $arr['count']; $j++) {
200 $row[$arr[$j]] = $arr[$arr[$j]][0];
201 }
202
203 $ret[]=$row;
204 }
205 }
206 return $ret;
207 }
208
212 function getAllGroups($attributes){
213 global $c;
214
215 $query = $this->ldap_query_all;
216 $ret = array();
217
218 foreach($this->baseDNGroups as $baseDNGroups) {
219 $entry = $query($this->connect,$baseDNGroups,$this->filterGroups,$attributes);
220
221 if (!ldap_first_entry($this->connect,$entry)) {
222 $c->messages[] = sprintf(translate('Error NoGroupFound with filter >%s<, attributes >%s< , dn >%s<'),
223 $this->filterGroups,
224 join(', ', $attributes),
225 $baseDNGroups);
226 }
227 $row = array();
228 for($i = ldap_first_entry($this->connect,$entry);
229 $i && $arr = ldap_get_attributes($this->connect,$i);
230 $i = ldap_next_entry($this->connect,$i) ) {
231 for ($j=0; $j < $arr['count']; $j++) {
232 $row[$arr[$j]] = count($arr[$arr[$j]])>2?$arr[$arr[$j]]:$arr[$arr[$j]][0];
233 }
234 $ret[]=$row;
235 unset($row);
236 }
237 }
238 return $ret;
239 }
240
251 function requestUser( $filter, $attributes, $username, $passwd) {
252 global $c;
253
254 $entry=NULL;
255 // We get the DN of the USER
256 $query = $this->ldap_query_one;
257 # ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
258
259 foreach($this->baseDNUsers as $baseDNUsers) {
260 $entry = $query($this->connect, $baseDNUsers, $filter, $attributes);
261
262 if (ldap_first_entry($this->connect,$entry) )
263 break;
264
265 dbg_error_log( "LDAP", "drivers_ldap : Failed to find user with baseDN: %s", $baseDNUsers );
266 }
267
268 if ( !ldap_first_entry($this->connect, $entry) ){
269 dbg_error_log( "ERROR", "drivers_ldap : Unable to find the user with filter %s",$filter );
270 return false;
271 } else {
272 dbg_error_log( "LDAP", "drivers_ldap : Found a user using filter %s",$filter );
273 }
274
275 $dnUser = ldap_get_dn($this->connect, ldap_first_entry($this->connect,$entry));
276
277 $authenticated = false;
278 $use_kerberos_only = isset($c->authenticate_hook['config']['i_use_mode_kerberos']) && $c->authenticate_hook['config']['i_use_mode_kerberos'] == "i_know_what_i_am_doing";
279 $use_kerberos_with_fallback = isset($c->authenticate_hook['config']['i_use_mode_kerberos']) && $c->authenticate_hook['config']['i_use_mode_kerberos'] == "allow_fallback_to_ldap_auth";
280
281 if ($use_kerberos_only or $use_kerberos_with_fallback) {
282 if (isset($_SERVER["REMOTE_USER"])) {
283 dbg_error_log( "LOG", "drivers_ldap : Skipping password Check for user %s which should be the same as %s",$username , $_SERVER["REMOTE_USER"]);
284 if ($username == $_SERVER["REMOTE_USER"]) {
285 $authenticated = true;
286 } else {
287 return false;
288 }
289 } elseif (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
290 dbg_error_log( "LOG", "drivers_ldap : Skipping password Check for user %s which should be the same as %s",$username , $_SERVER["REDIRECT_REMOTE_USER"]);
291 if ($username == $_SERVER["REDIRECT_REMOTE_USER"]) {
292 $authenticated = true;
293 } else {
294 return false;
295 }
296 } elseif ($use_kerberos_only) {
297 return false;
298 }
299 }
300
301 if (!$authenticated) {
302 if ( empty($passwd) || preg_match('/[\x00-\x19]/',$passwd) ) {
303 // See http://www.php.net/manual/en/function.ldap-bind.php#73718 for more background
304 dbg_error_log( 'LDAP', 'drivers_ldap : user %s supplied empty or invalid password: login rejected', $dnUser );
305 return false;
306 }
307 else {
308 if ( !@ldap_bind($this->connect, $dnUser, $passwd) ) {
309 dbg_error_log( "LDAP", "drivers_ldap : Failed to bind to user %s ", $dnUser );
310 return false;
311 }
312 }
313 }
314
315 dbg_error_log( "LDAP", "drivers_ldap : Bound to user %s using password %s", $dnUser,
316 (isset($c->dbg['password']) && $c->dbg['password'] ? $passwd : 'another delicious password for the debugging monster!') );
317
318 $i = ldap_first_entry($this->connect,$entry);
319 $arr = ldap_get_attributes($this->connect,$i);
320 for( $i=0; $i<$arr['count']; $i++ ) {
321 $ret[$arr[$i]]=$arr[$arr[$i]][0];
322 }
323 return $ret;
324
325 }
326}
327
328
332function getStaticLdap() {
333 global $c;
334 // Declare a static variable to hold the object instance
335 static $instance;
336
337 // If the instance is not there, create one
338 if(!isset($instance)) {
339 $ldapDriver = new ldapDriver($c->authenticate_hook['config']);
340
341 if ($ldapDriver->valid) {
342 $instance = $ldapDriver;
343 }
344 }
345 else {
346 $ldapDriver = $instance;
347 }
348 return $ldapDriver;
349}
350
351
356function sync_user_from_LDAP( Principal &$principal, $mapping, $ldap_values ) {
357 global $c;
358
359 dbg_error_log( "LDAP", "Going to sync the user from LDAP" );
360
361 $fields_to_set = array();
362 $updateable_fields = Principal::updateableFields();
363 foreach( $updateable_fields AS $field ) {
364 if ( isset($mapping[$field]) ) {
365 $tab_part_fields = explode(',',$mapping[$field]);
366 foreach( $tab_part_fields as $part_field ) {
367 if ( isset($ldap_values[$part_field]) ) {
368 if (isset($fields_to_set[$field]) ) {
369 $fields_to_set[$field] .= ' '.$ldap_values[$part_field];
370 }
371 else {
372 $fields_to_set[$field] = $ldap_values[$part_field];
373 }
374 }
375 }
376 dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $fields_to_set[$field], $mapping[$field] );
377 }
378 else if ( isset($c->authenticate_hook['config']['default_value']) && is_array($c->authenticate_hook['config']['default_value'])
379 && isset($c->authenticate_hook['config']['default_value'][$field] ) ) {
380 $fields_to_set[$field] = $c->authenticate_hook['config']['default_value'][$field];
381 dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $c->authenticate_hook['config']['default_value'][$field] );
382 }
383 }
384
385 if ( $principal->Exists() ) {
386 $principal->Update($fields_to_set);
387 }
388 else {
389 $principal->Create($fields_to_set);
390 CreateHomeCollections($principal->username());
391 CreateDefaultRelationships($principal->username());
392 }
393}
394
398function array_values_mapping($mapping){
399 $attributes=array();
400 foreach ( $mapping as $field ) {
401 $tab_part_field = explode(",",$field);
402 foreach( $tab_part_field as $part_field ) {
403 $attributes[] = $part_field;
404 }
405 }
406 return $attributes;
407}
408
412function LDAP_check($username, $password ){
413 global $c;
414
415 $ldapDriver = getStaticLdap();
416 if ( !$ldapDriver->valid ) {
417 sleep(1); // Sleep very briefly to try and survive intermittent issues
418 $ldapDriver = getStaticLdap();
419 if ( !$ldapDriver->valid ) {
420 dbg_error_log( "ERROR", "Couldn't contact LDAP server for authentication" );
421 foreach($c->messages as $msg) {
422 dbg_error_log( "ERROR", "-> ".$msg );
423 }
424 header( sprintf("HTTP/1.1 %d %s", 503, translate("Authentication server unavailable.")) );
425 exit(0);
426 }
427 }
428
429 $mapping = $c->authenticate_hook['config']['mapping_field'];
430 if ( isset($mapping['active']) && !isset($mapping['user_active']) ) {
431 // Backward compatibility: now 'user_active'
432 $mapping['user_active'] = $mapping['active'];
433 unset($mapping['active']);
434 }
435 if ( isset($mapping['updated']) && !isset($mapping['modified']) ) {
436 // Backward compatibility: now 'modified'
437 $mapping['modified'] = $mapping['updated'];
438 unset($mapping['updated']);
439 }
440 $attributes = array_values_mapping($mapping);
441
446 $filter_munge = "";
447 if ( preg_match( '/^\‍(/', $ldapDriver->filterUsers ) ) {
448 $filter_munge = $ldapDriver->filterUsers;
449 }
450 else if ( isset($ldapDriver->filterUsers) && $ldapDriver->filterUsers != '' ) {
451 $filter_munge = "($ldapDriver->filterUsers)";
452 }
453
454 $filter = "(&$filter_munge(".$mapping['username']."=$username))";
455 $valid = $ldapDriver->requestUser( $filter, $attributes, $username, $password );
456
457 // is a valid user or not
458 if ( !$valid ) {
459 dbg_error_log( "LDAP", "user %s is not a valid user",$username );
460 return false;
461 }
462
463 if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
464 $ldap_timestamp = $valid[$mapping['modified']];
465 } else {
466 $ldap_timestamp = '19700101000000';
467 }
468
472 foreach($c->authenticate_hook['config']['format_updated'] as $k => $v)
473 $$k = substr($ldap_timestamp,$v[0],$v[1]);
474
475 $ldap_timestamp = "$Y"."$m"."$d"."$H"."$M"."$S";
476 if ($mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
477 $valid[$mapping['modified']] = "$Y-$m-$d $H:$M:$S";
478 }
479
480 $principal = new Principal('username',$username);
481 if ( $principal->Exists() ) {
482 // should we update it ?
483 $db_timestamp = $principal->modified;
484 $db_timestamp = substr(strtr($db_timestamp, array(':' => '',' '=>'','-'=>'')),0,14);
485 if( $ldap_timestamp <= $db_timestamp ) {
486 return $principal; // no need to update
487 }
488
489 // we will need to update the user record
490 dbg_error_log( "LDAP", "user %s has been modified in LDAP, we need to update our DB", $username );
491 }
492 else {
493 dbg_error_log( "LDAP", "user %s doesn't exist in local DB, we need to create it", $username );
494 }
495 $principal->setUsername($username);
496
497 // The local cached user doesn't exist, or is older, so we create/update their details
498 sync_user_from_LDAP( $principal, $mapping, $valid );
499
500 return $principal;
501
502}
503
507function fix_unique_member($list) {
508 $fixed_list = array();
509 foreach ( $list as $member ){
510 array_unshift( $fixed_list, ldap_explode_dn($member,1)[0]);
511 }
512 return $fixed_list;
513}
514
518function sync_LDAP_groups(){
519 global $c;
520
521 // Can only sync if we know where to find the groups.
522 if (! isset($c->authenticate_hook['config']['baseDNGroups']) )
523 return;
524
525 $ldapDriver = getStaticLdap();
526 if ( ! $ldapDriver->valid ) return;
527
528 $mapping = $c->authenticate_hook['config']['group_mapping_field'];
529
530 // Old config (pre 1.1.13) used "username" as the group name, if that is in
531 // use, copy it to the new name: name.
532 if ( ! isset($mapping['name']) && isset($mapping['username']) ) {
533 $mapping['name'] = $mapping['username'];
534 unset($mapping['username']);
535 }
536
537 // Set sane defaults.
538 if (! isset($mapping['name']))
539 $mapping['name'] = 'cn';
540 if (! isset($mapping['members']))
541 $mapping['members'] = 'member';
542
543 $attributes = array_values_mapping($mapping);
544 $ldap_groups_tmp = $ldapDriver->getAllGroups($attributes);
545
546 if ( sizeof($ldap_groups_tmp) == 0 ) return;
547
548 $member_field = $mapping['members'];
549
550 $dnfix = isset($c->authenticate_hook['config']['group_member_dnfix'])
551 && $c->authenticate_hook['config']['group_member_dnfix'];
552
553 foreach ($ldap_groups_tmp as $key => $ldap_group) {
554 $group_name = $ldap_group[$mapping['name']];
555
556 if ( isset($ldap_group[$member_field]) ) {
557 if ( is_array($ldap_group[$member_field]) ) {
558 unset( $ldap_group[$member_field]['count'] );
559 } else {
560 $ldap_group[$member_field] = array($ldap_group[$member_field]);
561 }
562 }
563
564 $ldap_groups_info[$group_name] = $ldap_group;
565 unset($ldap_groups_tmp[$key]);
566 }
567
568 $db_groups = array();
569 $db_group_members = array();
570
571 $qry = new AwlQuery( "
572 SELECT g.username AS group_name, member.username AS member_name
573 FROM dav_principal g
574 LEFT JOIN group_member ON (g.principal_id = group_member.group_id)
575 LEFT JOIN dav_principal member ON
576 (member.principal_id = group_member.member_id)
577 WHERE g.type_id = 3
578 ");
579
580 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
581
582 while ($db_group = $qry->Fetch()) {
583 $db_groups[$db_group->group_name] = $db_group->group_name;
584 $db_group_members[$db_group->group_name][] = $db_group->member_name;
585 }
586
587 $ldap_groups = array_keys($ldap_groups_info);
588
589 // Groups only in LDAP or in both LDAP and DB. To be created or updated.
590 $groups_to_create = array_merge(
591 array_diff($ldap_groups, $db_groups),
592 array_intersect($db_groups, $ldap_groups));
593
594 // Groups only in DB. To be disabled..
595 $groups_to_deactivate = array_diff($db_groups, $ldap_groups);
596
597 // Groups where nothing was done
598 $groups_nothing_done[] = null;
599
600 if ( sizeof ( $groups_to_create ) ) {
601 $validUserFields = awl_get_fields('usr');
602
603 foreach ( $groups_to_create as $k => $group ){
604 if ( isset($c->do_not_sync_group_from_ldap)
605 && isset($c->do_not_sync_group_from_ldap[$group]) ) {
606 unset($groups_to_create[$k]);
607 $groups_nothing_done[] = $group;
608
609 continue;
610 }
611
612 dbg_error_log( "LDAP", "Syncing group %s", $group );
613 $user = (object) array();
614
615 if ( isset($c->authenticate_hook['config']['default_value'])
616 && is_array($c->authenticate_hook['config']['default_value']) ) {
617 foreach ( $c->authenticate_hook['config']['default_value'] as $field => $value ) {
618 if ( isset($validUserFields[$field]) ) {
619 $user->{$field} = $value;
620 dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $value );
621 }
622 }
623 }
624
625 $user->user_no = 0;
626 $ldap_values = $ldap_groups_info[$group];
627
628 foreach ( $mapping as $field => $value ) {
629 dbg_error_log( "LDAP", "Considering copying %s", $field );
630 if ( isset($validUserFields[$field]) && isset($ldap_values[$value]) ) {
631 $user->{$field} = $ldap_values[$value];
632 dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value );
633 }
634 }
635
636 // A sane default for the fullname is the group name.
637 if (! isset($user->fullname) || $user->fullname == "") {
638 $user->fullname = $group;
639 }
640
641 // A sane default for the displayname is the fullname (which might be
642 // the group name).
643 if (! isset($user->displayname) || $user->displayname == "") {
644 $user->displayname = $user->fullname;
645 }
646
647 $user->username = $group;
648 $user->updated = "now";
649
650 $principal = new Principal('username', $group);
651 if ( $principal->Exists() ) {
652 $principal->Update($user);
653 }
654 else {
655 $principal->Create($user);
656 }
657
658 $qry = new AwlQuery( "
659 UPDATE dav_principal
660 SET type_id = 3
661 WHERE username = :group",
662 array(':group' => $group) );
663 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
664
665 Principal::cacheDelete('username', $group);
666
667 // mark group for sync'ing the members
668 $groups_to_sync_members[] = $group;
669 }
670
671 $c->messages[] = sprintf( i18n('- creating groups : %s'), join(', ', $groups_to_create) );
672 }
673
674 if ( sizeof ( $groups_to_sync_members ) ){
675 $c->messages[] = sprintf(i18n('- updating groups : %s'), join(', ', $groups_to_sync_members));
676
677 // Support the old name of user_mapping_field..
678 $user_mapping = NULL;
679 if (isset($c->authenticate_hook['config']['user_mapping_field'])) {
680 $user_mapping = $c->authenticate_hook['config']['user_mapping_field'];
681 } else if (isset($c->authenticate_hook['config']['mapping_field'])) {
682 $user_mapping = $c->authenticate_hook['config']['mapping_field'];
683 }
684
685 // Used if the group members are DNs and we need to do LDAP lookups.
686 $query = $ldapDriver->ldap_query_one;
687 $username_ldap_attribute = $user_mapping['username'];
688 $filter = $ldapDriver->filterUsers;
689
690 foreach ( $groups_to_sync_members as $group ) {
691 $db_members = isset($db_group_members[$group])
692 && is_array($db_group_members[$group])
693 ? array_values( $db_group_members[$group] )
694 : array();
695 $ldap_members = isset ( $ldap_groups_info[$group][$member_field] )
696 ? array_values ( $ldap_groups_info[$group][$member_field] )
697 : array();
698
699 $ldap_members_tmp = array();
700
701 foreach ( $ldap_members as $member ) {
702 dbg_error_log( "LDAP", "Considering adding %s to group %s", $member, $group );
703
704 if (isset($user_mapping['username'])
705 && preg_match('/=/', $member)) {
706 // The member from LDAP has an = in it, assume it is a DN.
707
708 if (preg_match("/^$username_ldap_attribute=/", $member)) {
709 // If the member record starts with the mapping field, we
710 // don't need to fetch from LDAP. Win.
711
712 $ldap_members_tmp[] = ldap_explode_dn($member,1)[0];
713
714 } else {
715 // The attribute isn't at the start of the member name,
716 // off to LDAP we go.
717 // NOTE: We could cache all the person records fetched
718 // in sync_LDAP and use more memory but not need
719 // anymore trips to LDAP.
720
721 $entry = $query($ldapDriver->connect, $member, $filter,
722 array($username_ldap_attribute));
723 $ldap_user_entry
724 = ldap_first_entry($ldapDriver->connect, $entry);
725
726 if (! $ldap_user_entry) {
727 dbg_error_log( "ERROR", "%s not found in LDAP, not adding to group %s", $member, $group );
728 } else {
729 $ldap_user_attributes
730 = ldap_get_attributes($ldapDriver->connect,
731 $ldap_user_entry);
732
733 $ldap_members_tmp[]
734 = $ldap_user_attributes[$username_ldap_attribute][0];
735 }
736 }
737 } else {
738 // No need to rewrite.
739 $ldap_members_tmp[] = $member;
740 }
741
742 $ldap_members = $ldap_members_tmp;
743 }
744
745 $add_users = array_diff ( $ldap_members, $db_members );
746 if ( sizeof ( $add_users ) ){
747 $c->messages[] = sprintf(i18n('- adding %s to group : %s'),join(', ', $add_users ), $group);
748
749 foreach ( $add_users as $member ) {
750 if ( isset($c->do_not_sync_from_ldap)
751 && isset($c->do_not_sync_from_ldap[$member]) ) {
752 dbg_error_log( "ERROR", "drivers_ldap : Skipped adding %s to %s", $member, $group);
753
754 continue;
755 }
756
757 $qry = new AwlQuery( "
758 INSERT INTO group_member
759 SELECT g.principal_id AS group_id, u.principal_id AS member_id
760 FROM dav_principal g, dav_principal u
761 WHERE g.username = :group
762 AND u.username = :member",
763 array (':group' => $group, ':member' => $member) );
764
765 $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__);
766 Principal::cacheDelete('username', $member);
767 }
768 }
769
770 $remove_users = @array_flip( @array_flip( array_diff( $db_members,
771 $ldap_members )));
772 if ( sizeof ( $remove_users ) ) {
773 $c->messages[] = sprintf(i18n('- removing %s from group : %s'),join(', ', $remove_users ), $group);
774
775 foreach ( $remove_users as $member ) {
776 $qry = new AwlQuery( "
777 DELETE FROM group_member
778 USING dav_principal g,dav_principal m
779 WHERE group_id = g.principal_id
780 AND member_id = m.principal_id
781 AND g.username = :group
782 AND m.username=:member",
783 array (':group' => $group , ':member' => $member) );
784
785 $qry->Exec('sync_LDAP_groups',__LINE__,__FILE__);
786 Principal::cacheDelete('username', $member);
787 }
788 }
789 }
790 }
791
792 if ( sizeof ( $groups_to_deactivate ) ) {
793 foreach ( $groups_to_deactivate as $k => $group ) {
794 if ( isset($c->do_not_sync_group_from_ldap)
795 && isset($c->do_not_sync_group_from_ldap[$group]) ) {
796 unset($groups_to_deactivate[$k]);
797 $groups_nothing_done[] = $group;
798
799 } else {
800 $qry = new AwlQuery( '
801 UPDATE dav_principal
802 SET user_active = FALSE
803 WHERE username = :group
804 AND type_id = 3',
805 array(':group' => $group) );
806 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
807
808 Principal::cacheFlush('username=:group AND type_id = 3',
809 array(':group' => $group) );
810 }
811 }
812
813 if ( sizeof($groups_to_deactivate) )
814 $c->messages[] = sprintf(i18n('- deactivated groups : %s'), join(', ',$groups_to_deactivate));
815 }
816
817 if ( sizeof($groups_nothing_done) )
818 $c->messages[] = sprintf( i18n('- nothing done on : %s'), join(', ',$groups_nothing_done) );
819
820}
821
825function sync_LDAP(){
826 global $c;
827 $ldapDriver = getStaticLdap();
828 if ( ! $ldapDriver->valid ) return;
829
830 // Support the old name of user_mapping_field.
831 $mapping = NULL;
832 if (isset($c->authenticate_hook['config']['user_mapping_field'])) {
833 $mapping = $c->authenticate_hook['config']['user_mapping_field'];
834 } else if (isset($c->authenticate_hook['config']['mapping_field'])) {
835 $mapping = $c->authenticate_hook['config']['mapping_field'];
836 }
837
838 $attributes = array_values_mapping($mapping);
839 $ldap_users_tmp = $ldapDriver->getAllUsers($attributes);
840
841 if ( sizeof($ldap_users_tmp) == 0 ) return;
842
843 foreach($ldap_users_tmp as $key => $ldap_user){
844 if(!isset($ldap_user[$mapping['username']])) continue;
845 $ldap_users_info[$ldap_user[$mapping['username']]] = $ldap_user;
846 unset($ldap_users_tmp[$key]);
847 }
848 $qry = new AwlQuery( "SELECT username, user_no, modified as updated , user_active FROM dav_principal where type_id=1");
849 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
850 while($db_user = $qry->Fetch()) {
851 $db_users[] = $db_user->username;
852 $db_users_info[$db_user->username] = array('user_no' => $db_user->user_no, 'updated' => $db_user->updated, 'user_active' => $db_user->user_active);
853 }
854
855 // all users from ldap
856 $ldap_users = array_keys($ldap_users_info);
857 // users only in ldap
858 $users_to_create = array_diff($ldap_users,$db_users);
859 // users only in db
860 $users_to_deactivate = array_diff($db_users,$ldap_users);
861 // users present in ldap and in the db
862 $users_to_update = array_intersect($db_users,$ldap_users);
863 // users not modified
864 $users_nothing_done = array();
865
866 // creation of all users;
867 if ( sizeof($users_to_create) ) {
868 foreach( $users_to_create as $k => $username ) {
869 if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$username]) ) {
870 unset( $users_to_create[$k] );
871 $users_nothing_done[] = $username;
872 continue;
873 }
874 $principal = new Principal( 'username', $username );
875 $valid = $ldap_users_info[$username];
876 if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
877 $ldap_timestamp = $valid[$mapping['modified']];
878 } else {
879 $ldap_timestamp = '19700101000000';
880 }
881
882 if ( !empty($c->authenticate_hook['config']['format_updated']) ) {
886 foreach($c->authenticate_hook['config']['format_updated'] as $k => $v)
887 $$k = substr($ldap_timestamp,$v[0],$v[1]);
888 $ldap_timestamp = $Y.$m.$d.$H.$M.$S;
889 }
890 else if ( preg_match('{^(\d{8})(\d{6})(Z)?$}', $ldap_timestamp, $matches ) ) {
891 $ldap_timestamp = $matches[1].'T'.$matches[2].$matches[3];
892 }
893 else if ( empty($ldap_timestamp) ) {
894 $ldap_timestamp = date('c');
895 }
896 if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
897 $valid[$mapping['modified']] = $ldap_timestamp;
898 }
899
900 sync_user_from_LDAP( $principal, $mapping, $valid );
901 }
902 $c->messages[] = sprintf( i18n('- creating record for users : %s'), join(', ',$users_to_create) );
903 }
904
905 // deactivating all users
906 $params = array();
907 $i = 0;
908 $paramstring = '';
909 foreach( $users_to_deactivate as $k => $v ) {
910 if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$v]) ) {
911 unset($users_to_deactivate[$k]);
912 $users_nothing_done[] = $v;
913 continue;
914 }
915 if ( $i > 0 ) $paramstring .= ',';
916 $paramstring .= ':u'.$i.'::text';
917 $params[':u'.$i++] = strtolower($v);
918 }
919 if ( count($params) > 0 ) {
920 $c->messages[] = sprintf(i18n('- deactivating users : %s'),join(', ',$users_to_deactivate));
921 $qry = new AwlQuery( 'UPDATE usr SET active = FALSE WHERE lower(username) IN ('.$paramstring.')', $params);
922 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
923
924 Principal::cacheFlush('lower(username) IN ('.$paramstring.')', $params);
925 }
926
927 // updating all users
928 if ( sizeof($users_to_update) ) {
929 foreach ( $users_to_update as $key=> $username ) {
930 $principal = new Principal( 'username', $username );
931 $valid=$ldap_users_info[$username];
932 if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
933 $ldap_timestamp = $valid[$mapping['modified']];
934 } else {
935 $ldap_timestamp = '19700101000000';
936 }
937
938 $valid['user_no'] = $db_users_info[$username]['user_no'];
939 $mapping['user_no'] = 'user_no';
940
944 foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) {
945 $$k = substr($ldap_timestamp,$v[0],$v[1]);
946 }
947 $ldap_timestamp = $Y.$m.$d.$H.$M.$S;
948 $valid[$mapping['modified']] = "$Y-$m-$d $H:$M:$S";
949
950 $db_timestamp = substr(strtr($db_users_info[$username]['updated'], array(':' => '',' '=>'','-'=>'')),0,14);
951 if ( $ldap_timestamp > $db_timestamp || !$db_users_info[$username]['user_active']) {
952 $principal->user_active = true;
953 sync_user_from_LDAP($principal, $mapping, $valid);
954 }
955 else {
956 unset($users_to_update[$key]);
957 $users_nothing_done[] = $username;
958 }
959 }
960 if ( sizeof($users_to_update) )
961 $c->messages[] = sprintf(i18n('- updating user records : %s'),join(', ',$users_to_update));
962 }
963 if ( sizeof($users_nothing_done) )
964 $c->messages[] = sprintf( i18n('- nothing done on : %s'), join(', ',$users_nothing_done) );
965
966 // check for remaining admins
967 $admins = 0;
968 $qry = new AwlQuery( "SELECT count(*) AS admins FROM usr JOIN role_member USING ( user_no ) JOIN roles USING (role_no) WHERE usr.active=TRUE AND role_name='Admin'");
969 $qry->Exec('sync_LDAP',__LINE__,__FILE__);
970 while ( $db_user = $qry->Fetch() ) {
971 $admins = $db_user->admins;
972 }
973 if ( $admins == 0 ) {
974 $c->messages[] = sprintf(i18n('Warning: there are no active admin users! You should fix this before logging out. Consider using the $c->do_not_sync_from_ldap configuration setting.'));
975 }
976}
requestUser( $filter, $attributes, $username, $passwd)
getAllUsers($attributes)
getAllGroups($attributes)
__construct($config)