mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-03-01 06:03:46 +00:00
This tickles a bug as reported by Thorsten Marquardt in https://sourceforge.net/p/davical/mailman/message/58766531/ with this error message reported: Exception [0] ldap_get_attributes(): Argument #2 ($entry) must be of type LDAP\ResultEntry, false given At line 718 of /usr/share/davical-master/inc/drivers_ldap.php You need to look in 3da860e5d5e732c2dedb62a73c76608a2b7098a4 for that line number as I've just made some improvements to the code the last few commits.
977 lines
35 KiB
PHP
977 lines
35 KiB
PHP
<?php
|
|
/**
|
|
* Manages LDAP repository connection
|
|
*
|
|
* @package davical
|
|
* @category Technical
|
|
* @subpackage authentication/drivers
|
|
* @author Maxime Delorme <mdelorme@tennaxia.net>,
|
|
* Andrew McMillan <andrew@mcmillan.net.nz>
|
|
* @copyright Maxime Delorme
|
|
* @license http://gnu.org/copyleft/gpl.html GNU GPL v2 or later
|
|
*/
|
|
|
|
require_once("auth-functions.php");
|
|
|
|
/**
|
|
* Plugin to authenticate and sync with LDAP
|
|
*/
|
|
class ldapDriver
|
|
{
|
|
/**#@+
|
|
* @access private
|
|
*/
|
|
|
|
/**
|
|
* Holds the LDAP connection parameters
|
|
*/
|
|
var $connect;
|
|
|
|
/**#@-*/
|
|
|
|
|
|
/**
|
|
* Initializes the LDAP connection
|
|
*
|
|
* @param array $config The configuration data
|
|
*/
|
|
function __construct($config)
|
|
{
|
|
global $c;
|
|
$host=$config['host'];
|
|
$port=$config['port'];
|
|
if(!function_exists('ldap_connect')){
|
|
$c->messages[] = i18n("drivers_ldap : function ldap_connect not defined, check your php_ldap module");
|
|
$this->valid=false;
|
|
return ;
|
|
}
|
|
|
|
//Set LDAP protocol version
|
|
if (isset($config['protocolVersion']))
|
|
ldap_set_option($this->connect, LDAP_OPT_PROTOCOL_VERSION, $config['protocolVersion']);
|
|
if (isset($config['optReferrals']))
|
|
ldap_set_option($this->connect, LDAP_OPT_REFERRALS, $config['optReferrals']);
|
|
if (isset($config['networkTimeout']))
|
|
ldap_set_option($this->connect, LDAP_OPT_NETWORK_TIMEOUT, $config['networkTimeout']);
|
|
|
|
// If we are given a URI (or multiple) to connect to, use them. This allows support for LDAPS connections
|
|
// as well as redundant ldap servers to connect to
|
|
// Otherwise default to host and port
|
|
if (isset($config['uri']))
|
|
$this->connect=ldap_connect($config['uri']);
|
|
elseif ($port)
|
|
$this->connect=ldap_connect($host, $port);
|
|
else
|
|
$this->connect=ldap_connect($host);
|
|
|
|
if (! $this->connect){
|
|
if (isset($config['uri'])) {
|
|
$c->messages[] = sprintf(translate( 'drivers_ldap : Unable to connect to LDAP with URI: %s'), $config['uri'] );
|
|
} else {
|
|
if (! $port) $port = 'default';
|
|
$c->messages[] = sprintf(translate( 'drivers_ldap : Unable to connect to LDAP with port %s on host %s'), $port, $host );
|
|
}
|
|
$this->valid=false;
|
|
return ;
|
|
} else {
|
|
if (isset($config['uri']))
|
|
dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s", $config['uri'] );
|
|
elseif ($port)
|
|
dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s, port %s", $host, $port );
|
|
else
|
|
dbg_error_log( "LDAP", "drivers_ldap : Connected to LDAP server %s", $host );
|
|
}
|
|
|
|
// Start TLS if desired (requires protocol version 3)
|
|
if (isset($config['startTLS'])) {
|
|
if (!ldap_set_option($this->connect, LDAP_OPT_PROTOCOL_VERSION, 3)) {
|
|
$c->messages[] = i18n('drivers_ldap : Failed to set LDAP to use protocol version 3, TLS not supported');
|
|
$this->valid=false;
|
|
return;
|
|
}
|
|
if (!ldap_start_tls($this->connect)) {
|
|
$c->messages[] = i18n('drivers_ldap : Could not start TLS: ldap_start_tls() failed');
|
|
$this->valid=false;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//Set the search scope to be used, default to subtree. This sets the functions to be called later.
|
|
if (!isset($config['scope'])) $config['scope'] = 'subtree';
|
|
switch (strtolower($config['scope'])) {
|
|
case "base":
|
|
$this->ldap_query_one = 'ldap_read';
|
|
$this->ldap_query_all = 'ldap_read';
|
|
break;
|
|
case "onelevel":
|
|
$this->ldap_query_one = 'ldap_list';
|
|
$this->ldap_query_all = 'ldap_list';
|
|
break;
|
|
default:
|
|
$this->ldap_query_one = 'ldap_search';
|
|
$this->ldap_query_all = 'ldap_search';
|
|
break;
|
|
}
|
|
|
|
// This is useful to see what is happening at a low level. I also
|
|
// recommend tcpdump...
|
|
//ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
|
|
|
|
//connect as root
|
|
if (isset($config['sasl'])){
|
|
$bind_result = ldap_sasl_bind(
|
|
$this->connect,
|
|
(isset($config['bindDN']) ? $config['bindDN'] : null),
|
|
(isset($config['passDN']) ? $config['passDN'] : null),
|
|
(isset($config['sasl_mech']) ? $config['sasl_mech'] : null),
|
|
(isset($config['sasl_realm']) ? $config['sasl_realm'] : null),
|
|
(isset($config['sasl_authc_id']) ? $config['sasl_authc_id'] : null),
|
|
(isset($config['sasl_authz_id']) ? $config['sasl_authz_id'] : null),
|
|
(isset($config['sasl_props']) ? $config['sasl_props'] : null) );
|
|
} else {
|
|
$bind_result = ldap_bind($this->connect, (isset($config['bindDN']) ? $config['bindDN'] : null), (isset($config['passDN']) ? $config['passDN'] : null) );
|
|
}
|
|
if (!$bind_result){
|
|
$bindDN = isset($config['bindDN']) ? $config['bindDN'] : 'anonymous';
|
|
$passDN = isset($config['passDN']) ? $config['passDN'] : 'anonymous';
|
|
|
|
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 );
|
|
$c->messages[] = i18n( 'drivers_ldap : Unable to bind to LDAP - check your configuration for bindDN and passDN, and that your LDAP server is reachable');
|
|
$this->valid=false;
|
|
return ;
|
|
}
|
|
$this->valid = true;
|
|
|
|
// root to start search
|
|
$this->baseDNUsers =
|
|
is_string($config['baseDNUsers']) ?
|
|
array($config['baseDNUsers']) : $config['baseDNUsers'];
|
|
$this->filterUsers =
|
|
(isset($config['filterUsers']) ?
|
|
$config['filterUsers'] : '(objectclass=*)');
|
|
$this->baseDNGroups =
|
|
(isset($config['baseDNGroups']) ?
|
|
(is_string($config['baseDNGroups']) ?
|
|
array($config['baseDNGroups']) : $config['baseDNGroups']) : null);
|
|
$this->filterGroups =
|
|
(isset($config['filterGroups']) ?
|
|
$config['filterGroups'] : '(objectclass=*)');
|
|
}
|
|
|
|
/**
|
|
* Retrieve all users from the LDAP directory
|
|
*/
|
|
function getAllUsers($attributes){
|
|
global $c;
|
|
|
|
$query = $this->ldap_query_all;
|
|
$ret = array();
|
|
|
|
foreach($this->baseDNUsers as $baseDNUsers) {
|
|
$entry = $query($this->connect, $baseDNUsers, $this->filterUsers, $attributes);
|
|
|
|
if (! $entry) {
|
|
$c->messages[] = sprintf(translate('Error NoUserFound with filter >%s<, attributes >%s< , baseDN >%s<'),
|
|
$this->filterUsers,
|
|
join(', ', $attributes),
|
|
$baseDNUsers);
|
|
dbg_error_log('LDAP', 'Error NoUserFound with filter >%s<, attributes >%s< , baseDN >%s<',
|
|
$this->filterUsers,
|
|
join(', ', $attributes),
|
|
$baseDNUsers);
|
|
return $ret;
|
|
}
|
|
|
|
if (! ldap_first_entry($this->connect, $entry)) {
|
|
$c->messages[] = sprintf(translate('Error NoUserFound with filter >%s<, attributes >%s< , dn >%s<'),
|
|
$this->filterUsers,
|
|
join(', ', $attributes),
|
|
$baseDNUsers);
|
|
return $ret;
|
|
}
|
|
|
|
$row = array();
|
|
for ($i = ldap_first_entry($this->connect,$entry);
|
|
$i && $arr = ldap_get_attributes($this->connect,$i);
|
|
$i = ldap_next_entry($this->connect,$i) ) {
|
|
$row = array();
|
|
|
|
for ($j=0; $j < $arr['count']; $j++) {
|
|
$row[$arr[$j]] = $arr[$arr[$j]][0];
|
|
}
|
|
|
|
$ret[]=$row;
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Retrieve all groups from the LDAP directory
|
|
*/
|
|
function getAllGroups($attributes){
|
|
global $c;
|
|
|
|
$query = $this->ldap_query_all;
|
|
$ret = array();
|
|
|
|
foreach($this->baseDNGroups as $baseDNGroups) {
|
|
$entry = $query($this->connect,$baseDNGroups,$this->filterGroups,$attributes);
|
|
|
|
if (!ldap_first_entry($this->connect,$entry)) {
|
|
$c->messages[] = sprintf(translate('Error NoGroupFound with filter >%s<, attributes >%s< , dn >%s<'),
|
|
$this->filterGroups,
|
|
join(', ', $attributes),
|
|
$baseDNGroups);
|
|
}
|
|
$row = array();
|
|
for($i = ldap_first_entry($this->connect,$entry);
|
|
$i && $arr = ldap_get_attributes($this->connect,$i);
|
|
$i = ldap_next_entry($this->connect,$i) ) {
|
|
for ($j=0; $j < $arr['count']; $j++) {
|
|
$row[$arr[$j]] = count($arr[$arr[$j]])>2?$arr[$arr[$j]]:$arr[$arr[$j]][0];
|
|
}
|
|
$ret[]=$row;
|
|
unset($row);
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Actually look up a user in the LDAP directory
|
|
* (this is the LDAP part of LDAP_check() below)
|
|
*
|
|
* @param string $filter The filter used to search entries
|
|
* @param array $attributes Attributes to be returned
|
|
* @param string $username username to check
|
|
* @param string $passwd password to check
|
|
* @return array Contains selected attributes from all entries corresponding to the given filter
|
|
*/
|
|
function requestUser( $filter, $attributes, $username, $passwd) {
|
|
global $c;
|
|
|
|
$entry=NULL;
|
|
// We get the DN of the USER
|
|
$query = $this->ldap_query_one;
|
|
# ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, 7);
|
|
|
|
foreach($this->baseDNUsers as $baseDNUsers) {
|
|
$entry = $query($this->connect, $baseDNUsers, $filter, $attributes);
|
|
|
|
if (ldap_first_entry($this->connect,$entry) )
|
|
break;
|
|
|
|
dbg_error_log( "LDAP", "drivers_ldap : Failed to find user with baseDN: %s", $baseDNUsers );
|
|
}
|
|
|
|
if ( !ldap_first_entry($this->connect, $entry) ){
|
|
dbg_error_log( "ERROR", "drivers_ldap : Unable to find the user with filter %s",$filter );
|
|
return false;
|
|
} else {
|
|
dbg_error_log( "LDAP", "drivers_ldap : Found a user using filter %s",$filter );
|
|
}
|
|
|
|
$dnUser = ldap_get_dn($this->connect, ldap_first_entry($this->connect,$entry));
|
|
|
|
$authenticated = false;
|
|
$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";
|
|
$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";
|
|
|
|
if ($use_kerberos_only or $use_kerberos_with_fallback) {
|
|
if (isset($_SERVER["REMOTE_USER"])) {
|
|
dbg_error_log( "LOG", "drivers_ldap : Skipping password Check for user %s which should be the same as %s",$username , $_SERVER["REMOTE_USER"]);
|
|
if ($username == $_SERVER["REMOTE_USER"]) {
|
|
$authenticated = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} elseif (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
|
|
dbg_error_log( "LOG", "drivers_ldap : Skipping password Check for user %s which should be the same as %s",$username , $_SERVER["REDIRECT_REMOTE_USER"]);
|
|
if ($username == $_SERVER["REDIRECT_REMOTE_USER"]) {
|
|
$authenticated = true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} elseif ($use_kerberos_only) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!$authenticated) {
|
|
if ( empty($passwd) || preg_match('/[\x00-\x19]/',$passwd) ) {
|
|
// See http://www.php.net/manual/en/function.ldap-bind.php#73718 for more background
|
|
dbg_error_log( 'LDAP', 'drivers_ldap : user %s supplied empty or invalid password: login rejected', $dnUser );
|
|
return false;
|
|
}
|
|
else {
|
|
if ( !@ldap_bind($this->connect, $dnUser, $passwd) ) {
|
|
dbg_error_log( "LDAP", "drivers_ldap : Failed to bind to user %s ", $dnUser );
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
dbg_error_log( "LDAP", "drivers_ldap : Bound to user %s using password %s", $dnUser,
|
|
(isset($c->dbg['password']) && $c->dbg['password'] ? $passwd : 'another delicious password for the debugging monster!') );
|
|
|
|
$i = ldap_first_entry($this->connect,$entry);
|
|
$arr = ldap_get_attributes($this->connect,$i);
|
|
for( $i=0; $i<$arr['count']; $i++ ) {
|
|
$ret[$arr[$i]]=$arr[$arr[$i]][0];
|
|
}
|
|
return $ret;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* A generic function to create and fetch static objects
|
|
*/
|
|
function getStaticLdap() {
|
|
global $c;
|
|
// Declare a static variable to hold the object instance
|
|
static $instance;
|
|
|
|
// If the instance is not there, create one
|
|
if(!isset($instance)) {
|
|
$ldapDriver = new ldapDriver($c->authenticate_hook['config']);
|
|
|
|
if ($ldapDriver->valid) {
|
|
$instance = $ldapDriver;
|
|
}
|
|
}
|
|
else {
|
|
$ldapDriver = $instance;
|
|
}
|
|
return $ldapDriver;
|
|
}
|
|
|
|
|
|
/**
|
|
* Synchronise a cached user with one from LDAP
|
|
* @param object $principal A Principal object to be updated (or created)
|
|
*/
|
|
function sync_user_from_LDAP( Principal &$principal, $mapping, $ldap_values ) {
|
|
global $c;
|
|
|
|
dbg_error_log( "LDAP", "Going to sync the user from LDAP" );
|
|
|
|
$fields_to_set = array();
|
|
$updateable_fields = Principal::updateableFields();
|
|
foreach( $updateable_fields AS $field ) {
|
|
if ( isset($mapping[$field]) ) {
|
|
$tab_part_fields = explode(',',$mapping[$field]);
|
|
foreach( $tab_part_fields as $part_field ) {
|
|
if ( isset($ldap_values[$part_field]) ) {
|
|
if (isset($fields_to_set[$field]) ) {
|
|
$fields_to_set[$field] .= ' '.$ldap_values[$part_field];
|
|
}
|
|
else {
|
|
$fields_to_set[$field] = $ldap_values[$part_field];
|
|
}
|
|
}
|
|
}
|
|
dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $fields_to_set[$field], $mapping[$field] );
|
|
}
|
|
else if ( isset($c->authenticate_hook['config']['default_value']) && is_array($c->authenticate_hook['config']['default_value'])
|
|
&& isset($c->authenticate_hook['config']['default_value'][$field] ) ) {
|
|
$fields_to_set[$field] = $c->authenticate_hook['config']['default_value'][$field];
|
|
dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $c->authenticate_hook['config']['default_value'][$field] );
|
|
}
|
|
}
|
|
|
|
if ( $principal->Exists() ) {
|
|
$principal->Update($fields_to_set);
|
|
}
|
|
else {
|
|
$principal->Create($fields_to_set);
|
|
CreateHomeCollections($principal->username());
|
|
CreateDefaultRelationships($principal->username());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* explode the multipart mapping
|
|
*/
|
|
function array_values_mapping($mapping){
|
|
$attributes=array();
|
|
foreach ( $mapping as $field ) {
|
|
$tab_part_field = explode(",",$field);
|
|
foreach( $tab_part_field as $part_field ) {
|
|
$attributes[] = $part_field;
|
|
}
|
|
}
|
|
return $attributes;
|
|
}
|
|
|
|
/**
|
|
* Check the username / password against the LDAP server
|
|
*/
|
|
function LDAP_check($username, $password ){
|
|
global $c;
|
|
|
|
$ldapDriver = getStaticLdap();
|
|
if ( !$ldapDriver->valid ) {
|
|
sleep(1); // Sleep very briefly to try and survive intermittent issues
|
|
$ldapDriver = getStaticLdap();
|
|
if ( !$ldapDriver->valid ) {
|
|
dbg_error_log( "ERROR", "Couldn't contact LDAP server for authentication" );
|
|
foreach($c->messages as $msg) {
|
|
dbg_error_log( "ERROR", "-> ".$msg );
|
|
}
|
|
header( sprintf("HTTP/1.1 %d %s", 503, translate("Authentication server unavailable.")) );
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
$mapping = $c->authenticate_hook['config']['mapping_field'];
|
|
if ( isset($mapping['active']) && !isset($mapping['user_active']) ) {
|
|
// Backward compatibility: now 'user_active'
|
|
$mapping['user_active'] = $mapping['active'];
|
|
unset($mapping['active']);
|
|
}
|
|
if ( isset($mapping['updated']) && !isset($mapping['modified']) ) {
|
|
// Backward compatibility: now 'modified'
|
|
$mapping['modified'] = $mapping['updated'];
|
|
unset($mapping['updated']);
|
|
}
|
|
$attributes = array_values_mapping($mapping);
|
|
|
|
/**
|
|
* If the config contains a filter that starts with a ( then believe
|
|
* them and don't modify it, otherwise wrap the filter.
|
|
*/
|
|
$filter_munge = "";
|
|
if ( preg_match( '/^\(/', $ldapDriver->filterUsers ) ) {
|
|
$filter_munge = $ldapDriver->filterUsers;
|
|
}
|
|
else if ( isset($ldapDriver->filterUsers) && $ldapDriver->filterUsers != '' ) {
|
|
$filter_munge = "($ldapDriver->filterUsers)";
|
|
}
|
|
|
|
$filter = "(&$filter_munge(".$mapping['username']."=$username))";
|
|
$valid = $ldapDriver->requestUser( $filter, $attributes, $username, $password );
|
|
|
|
// is a valid user or not
|
|
if ( !$valid ) {
|
|
dbg_error_log( "LDAP", "user %s is not a valid user",$username );
|
|
return false;
|
|
}
|
|
|
|
if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
|
|
$ldap_timestamp = $valid[$mapping['modified']];
|
|
} else {
|
|
$ldap_timestamp = '19700101000000';
|
|
}
|
|
|
|
/**
|
|
* This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S
|
|
*/
|
|
foreach($c->authenticate_hook['config']['format_updated'] as $k => $v)
|
|
$$k = substr($ldap_timestamp,$v[0],$v[1]);
|
|
|
|
$ldap_timestamp = "$Y"."$m"."$d"."$H"."$M"."$S";
|
|
if ($mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
|
|
$valid[$mapping['modified']] = "$Y-$m-$d $H:$M:$S";
|
|
}
|
|
|
|
$principal = new Principal('username',$username);
|
|
if ( $principal->Exists() ) {
|
|
// should we update it ?
|
|
$db_timestamp = $principal->modified;
|
|
$db_timestamp = substr(strtr($db_timestamp, array(':' => '',' '=>'','-'=>'')),0,14);
|
|
if( $ldap_timestamp <= $db_timestamp ) {
|
|
return $principal; // no need to update
|
|
}
|
|
|
|
// we will need to update the user record
|
|
dbg_error_log( "LDAP", "user %s has been modified in LDAP, we need to update our DB", $username );
|
|
}
|
|
else {
|
|
dbg_error_log( "LDAP", "user %s doesn't exist in local DB, we need to create it", $username );
|
|
}
|
|
$principal->setUsername($username);
|
|
|
|
// The local cached user doesn't exist, or is older, so we create/update their details
|
|
sync_user_from_LDAP( $principal, $mapping, $valid );
|
|
|
|
return $principal;
|
|
|
|
}
|
|
|
|
/**
|
|
* turn a list of uniqueMember into member strings
|
|
*/
|
|
function fix_unique_member($list) {
|
|
$fixed_list = array();
|
|
foreach ( $list as $member ){
|
|
array_unshift( $fixed_list, ldap_explode_dn($member,1)[0]);
|
|
}
|
|
return $fixed_list;
|
|
}
|
|
|
|
/**
|
|
* sync LDAP Groups against the DB
|
|
*/
|
|
function sync_LDAP_groups(){
|
|
global $c;
|
|
|
|
// Can only sync if we know where to find the groups.
|
|
if (! isset($c->authenticate_hook['config']['baseDNGroups']) )
|
|
return;
|
|
|
|
$ldapDriver = getStaticLdap();
|
|
if ( ! $ldapDriver->valid ) return;
|
|
|
|
$mapping = $c->authenticate_hook['config']['group_mapping_field'];
|
|
|
|
// Old config (pre 1.1.13) used "username" as the group name, if that is in
|
|
// use, copy it to the new name: name.
|
|
if ( ! isset($mapping['name']) && isset($mapping['username']) ) {
|
|
$mapping['name'] = $mapping['username'];
|
|
unset($mapping['username']);
|
|
}
|
|
|
|
// Set sane defaults.
|
|
if (! isset($mapping['name']))
|
|
$mapping['name'] = 'cn';
|
|
if (! isset($mapping['members']))
|
|
$mapping['members'] = 'member';
|
|
|
|
$attributes = array_values_mapping($mapping);
|
|
$ldap_groups_tmp = $ldapDriver->getAllGroups($attributes);
|
|
|
|
if ( sizeof($ldap_groups_tmp) == 0 ) return;
|
|
|
|
$member_field = $mapping['members'];
|
|
|
|
$dnfix = isset($c->authenticate_hook['config']['group_member_dnfix'])
|
|
&& $c->authenticate_hook['config']['group_member_dnfix'];
|
|
|
|
foreach ($ldap_groups_tmp as $key => $ldap_group) {
|
|
$group_name = $ldap_group[$mapping['name']];
|
|
|
|
if ( isset($ldap_group[$member_field]) ) {
|
|
if ( is_array($ldap_group[$member_field]) ) {
|
|
unset( $ldap_group[$member_field]['count'] );
|
|
} else {
|
|
$ldap_group[$member_field] = array($ldap_group[$member_field]);
|
|
}
|
|
}
|
|
|
|
$ldap_groups_info[$group_name] = $ldap_group;
|
|
unset($ldap_groups_tmp[$key]);
|
|
}
|
|
|
|
$db_groups = array();
|
|
$db_group_members = array();
|
|
|
|
$qry = new AwlQuery( "
|
|
SELECT g.username AS group_name, member.username AS member_name
|
|
FROM dav_principal g
|
|
LEFT JOIN group_member ON (g.principal_id = group_member.group_id)
|
|
LEFT JOIN dav_principal member ON
|
|
(member.principal_id = group_member.member_id)
|
|
WHERE g.type_id = 3
|
|
");
|
|
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
|
|
while ($db_group = $qry->Fetch()) {
|
|
$db_groups[$db_group->group_name] = $db_group->group_name;
|
|
$db_group_members[$db_group->group_name][] = $db_group->member_name;
|
|
}
|
|
|
|
$ldap_groups = array_keys($ldap_groups_info);
|
|
|
|
// Groups only in LDAP or in both LDAP and DB. To be created or updated.
|
|
$groups_to_create = array_merge(
|
|
array_diff($ldap_groups, $db_groups),
|
|
array_intersect($db_groups, $ldap_groups));
|
|
|
|
// Groups only in DB. To be disabled..
|
|
$groups_to_deactivate = array_diff($db_groups, $ldap_groups);
|
|
|
|
// Groups where nothing was done
|
|
$groups_nothing_done[] = null;
|
|
|
|
if ( sizeof ( $groups_to_create ) ) {
|
|
$validUserFields = awl_get_fields('usr');
|
|
|
|
foreach ( $groups_to_create as $k => $group ){
|
|
if ( isset($c->do_not_sync_group_from_ldap)
|
|
&& isset($c->do_not_sync_group_from_ldap[$group]) ) {
|
|
unset($groups_to_create[$k]);
|
|
$groups_nothing_done[] = $group;
|
|
|
|
continue;
|
|
}
|
|
|
|
dbg_error_log( "LDAP", "Syncing group %s", $group );
|
|
$user = (object) array();
|
|
|
|
if ( isset($c->authenticate_hook['config']['default_value'])
|
|
&& is_array($c->authenticate_hook['config']['default_value']) ) {
|
|
foreach ( $c->authenticate_hook['config']['default_value'] as $field => $value ) {
|
|
if ( isset($validUserFields[$field]) ) {
|
|
$user->{$field} = $value;
|
|
dbg_error_log( "LDAP", "Setting usr->%s to %s from configured defaults", $field, $value );
|
|
}
|
|
}
|
|
}
|
|
|
|
$user->user_no = 0;
|
|
$ldap_values = $ldap_groups_info[$group];
|
|
|
|
foreach ( $mapping as $field => $value ) {
|
|
dbg_error_log( "LDAP", "Considering copying %s", $field );
|
|
if ( isset($validUserFields[$field]) && isset($ldap_values[$value]) ) {
|
|
$user->{$field} = $ldap_values[$value];
|
|
dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value );
|
|
}
|
|
}
|
|
|
|
// A sane default for the fullname is the group name.
|
|
if (! isset($user->fullname) || $user->fullname == "") {
|
|
$user->fullname = $group;
|
|
}
|
|
|
|
// A sane default for the displayname is the fullname (which might be
|
|
// the group name).
|
|
if (! isset($user->displayname) || $user->displayname == "") {
|
|
$user->displayname = $user->fullname;
|
|
}
|
|
|
|
$user->username = $group;
|
|
$user->updated = "now"; /** @todo Use the 'updated' timestamp from LDAP for groups too */
|
|
|
|
$principal = new Principal('username', $group);
|
|
if ( $principal->Exists() ) {
|
|
$principal->Update($user);
|
|
}
|
|
else {
|
|
$principal->Create($user);
|
|
}
|
|
|
|
$qry = new AwlQuery( "
|
|
UPDATE dav_principal
|
|
SET type_id = 3
|
|
WHERE username = :group",
|
|
array(':group' => $group) );
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
|
|
Principal::cacheDelete('username', $group);
|
|
|
|
// mark group for sync'ing the members
|
|
$groups_to_sync_members[] = $group;
|
|
}
|
|
|
|
$c->messages[] = sprintf( i18n('- creating groups : %s'), join(', ', $groups_to_create) );
|
|
}
|
|
|
|
if ( sizeof ( $groups_to_sync_members ) ){
|
|
$c->messages[] = sprintf(i18n('- updating groups : %s'), join(', ', $groups_to_sync_members));
|
|
|
|
// Support the old name of user_mapping_field..
|
|
$user_mapping = NULL;
|
|
if (isset($c->authenticate_hook['config']['user_mapping_field'])) {
|
|
$user_mapping = $c->authenticate_hook['config']['user_mapping_field'];
|
|
} else if (isset($c->authenticate_hook['config']['mapping_field'])) {
|
|
$user_mapping = $c->authenticate_hook['config']['mapping_field'];
|
|
}
|
|
|
|
// Used if the group members are DNs and we need to do LDAP lookups.
|
|
$query = $ldapDriver->ldap_query_one;
|
|
$username_ldap_attribute = $user_mapping['username'];
|
|
$filter = $ldapDriver->filterUsers;
|
|
|
|
foreach ( $groups_to_sync_members as $group ) {
|
|
$db_members = isset($db_group_members[$group])
|
|
&& is_array($db_group_members[$group])
|
|
? array_values( $db_group_members[$group] )
|
|
: array();
|
|
$ldap_members = isset ( $ldap_groups_info[$group][$member_field] )
|
|
? array_values ( $ldap_groups_info[$group][$member_field] )
|
|
: array();
|
|
|
|
$ldap_members_tmp = array();
|
|
|
|
foreach ( $ldap_members as $member ) {
|
|
dbg_error_log( "LDAP", "Considering adding %s to group %s", $member, $group );
|
|
|
|
if (isset($user_mapping['username'])
|
|
&& preg_match('/=/', $member)) {
|
|
// The member from LDAP has an = in it, assume it is a DN.
|
|
|
|
if (preg_match("/^$username_ldap_attribute=/", $member)) {
|
|
// If the member record starts with the mapping field, we
|
|
// don't need to fetch from LDAP. Win.
|
|
|
|
$ldap_members_tmp[] = ldap_explode_dn($member,1)[0];
|
|
|
|
} else {
|
|
// The attribute isn't at the start of the member name,
|
|
// off to LDAP we go.
|
|
// NOTE: We could cache all the person records fetched
|
|
// in sync_LDAP and use more memory but not need
|
|
// anymore trips to LDAP.
|
|
|
|
$entry = $query($ldapDriver->connect, $member, $filter,
|
|
array($username_ldap_attribute));
|
|
$ldap_user_entry
|
|
= ldap_first_entry($ldapDriver->connect, $entry);
|
|
|
|
if (! $ldap_user_entry) {
|
|
dbg_error_log( "ERROR", "%s not found in LDAP, not adding to group %s", $member, $group );
|
|
} else {
|
|
$ldap_user_attributes
|
|
= ldap_get_attributes($ldapDriver->connect,
|
|
$ldap_user_entry);
|
|
|
|
$ldap_members_tmp[]
|
|
= $ldap_user_attributes[$username_ldap_attribute][0];
|
|
}
|
|
}
|
|
} else {
|
|
// No need to rewrite.
|
|
$ldap_members_tmp[] = $member;
|
|
}
|
|
|
|
$ldap_members = $ldap_members_tmp;
|
|
}
|
|
|
|
$add_users = array_diff ( $ldap_members, $db_members );
|
|
if ( sizeof ( $add_users ) ){
|
|
$c->messages[] = sprintf(i18n('- adding %s to group : %s'),join(', ', $add_users ), $group);
|
|
|
|
foreach ( $add_users as $member ) {
|
|
if ( isset($c->do_not_sync_from_ldap)
|
|
&& isset($c->do_not_sync_from_ldap[$member]) ) {
|
|
dbg_error_log( "ERROR", "drivers_ldap : Skipped adding %s to %s", $member, $group);
|
|
|
|
continue;
|
|
}
|
|
|
|
$qry = new AwlQuery( "
|
|
INSERT INTO group_member
|
|
SELECT g.principal_id AS group_id, u.principal_id AS member_id
|
|
FROM dav_principal g, dav_principal u
|
|
WHERE g.username = :group
|
|
AND u.username = :member",
|
|
array (':group' => $group, ':member' => $member) );
|
|
|
|
$qry->Exec('sync_LDAP_groups',__LINE__,__FILE__);
|
|
Principal::cacheDelete('username', $member);
|
|
}
|
|
}
|
|
|
|
$remove_users = @array_flip( @array_flip( array_diff( $db_members,
|
|
$ldap_members )));
|
|
if ( sizeof ( $remove_users ) ) {
|
|
$c->messages[] = sprintf(i18n('- removing %s from group : %s'),join(', ', $remove_users ), $group);
|
|
|
|
foreach ( $remove_users as $member ) {
|
|
$qry = new AwlQuery( "
|
|
DELETE FROM group_member
|
|
USING dav_principal g,dav_principal m
|
|
WHERE group_id = g.principal_id
|
|
AND member_id = m.principal_id
|
|
AND g.username = :group
|
|
AND m.username=:member",
|
|
array (':group' => $group , ':member' => $member) );
|
|
|
|
$qry->Exec('sync_LDAP_groups',__LINE__,__FILE__);
|
|
Principal::cacheDelete('username', $member);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( sizeof ( $groups_to_deactivate ) ) {
|
|
foreach ( $groups_to_deactivate as $k => $group ) {
|
|
if ( isset($c->do_not_sync_group_from_ldap)
|
|
&& isset($c->do_not_sync_group_from_ldap[$group]) ) {
|
|
unset($groups_to_deactivate[$k]);
|
|
$groups_nothing_done[] = $group;
|
|
|
|
} else {
|
|
$qry = new AwlQuery( '
|
|
UPDATE dav_principal
|
|
SET user_active = FALSE
|
|
WHERE username = :group
|
|
AND type_id = 3',
|
|
array(':group' => $group) );
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
|
|
Principal::cacheFlush('username=:group AND type_id = 3',
|
|
array(':group' => $group) );
|
|
}
|
|
}
|
|
|
|
if ( sizeof($groups_to_deactivate) )
|
|
$c->messages[] = sprintf(i18n('- deactivated groups : %s'), join(', ',$groups_to_deactivate));
|
|
}
|
|
|
|
if ( sizeof($groups_nothing_done) )
|
|
$c->messages[] = sprintf( i18n('- nothing done on : %s'), join(', ',$groups_nothing_done) );
|
|
|
|
}
|
|
|
|
/**
|
|
* sync LDAP against the DB
|
|
*/
|
|
function sync_LDAP(){
|
|
global $c;
|
|
$ldapDriver = getStaticLdap();
|
|
if ( ! $ldapDriver->valid ) return;
|
|
|
|
// Support the old name of user_mapping_field.
|
|
$mapping = NULL;
|
|
if (isset($c->authenticate_hook['config']['user_mapping_field'])) {
|
|
$mapping = $c->authenticate_hook['config']['user_mapping_field'];
|
|
} else if (isset($c->authenticate_hook['config']['mapping_field'])) {
|
|
$mapping = $c->authenticate_hook['config']['mapping_field'];
|
|
}
|
|
|
|
$attributes = array_values_mapping($mapping);
|
|
$ldap_users_tmp = $ldapDriver->getAllUsers($attributes);
|
|
|
|
if ( sizeof($ldap_users_tmp) == 0 ) return;
|
|
|
|
foreach($ldap_users_tmp as $key => $ldap_user){
|
|
if(!isset($ldap_user[$mapping['username']])) continue;
|
|
$ldap_users_info[$ldap_user[$mapping['username']]] = $ldap_user;
|
|
unset($ldap_users_tmp[$key]);
|
|
}
|
|
$qry = new AwlQuery( "SELECT username, user_no, modified as updated , user_active FROM dav_principal where type_id=1");
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
while($db_user = $qry->Fetch()) {
|
|
$db_users[] = $db_user->username;
|
|
$db_users_info[$db_user->username] = array('user_no' => $db_user->user_no, 'updated' => $db_user->updated, 'user_active' => $db_user->user_active);
|
|
}
|
|
|
|
// all users from ldap
|
|
$ldap_users = array_keys($ldap_users_info);
|
|
// users only in ldap
|
|
$users_to_create = array_diff($ldap_users,$db_users);
|
|
// users only in db
|
|
$users_to_deactivate = array_diff($db_users,$ldap_users);
|
|
// users present in ldap and in the db
|
|
$users_to_update = array_intersect($db_users,$ldap_users);
|
|
// users not modified
|
|
$users_nothing_done = array();
|
|
|
|
// creation of all users;
|
|
if ( sizeof($users_to_create) ) {
|
|
foreach( $users_to_create as $k => $username ) {
|
|
if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$username]) ) {
|
|
unset( $users_to_create[$k] );
|
|
$users_nothing_done[] = $username;
|
|
continue;
|
|
}
|
|
$principal = new Principal( 'username', $username );
|
|
$valid = $ldap_users_info[$username];
|
|
if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
|
|
$ldap_timestamp = $valid[$mapping['modified']];
|
|
} else {
|
|
$ldap_timestamp = '19700101000000';
|
|
}
|
|
|
|
if ( !empty($c->authenticate_hook['config']['format_updated']) ) {
|
|
/**
|
|
* This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S
|
|
*/
|
|
foreach($c->authenticate_hook['config']['format_updated'] as $k => $v)
|
|
$$k = substr($ldap_timestamp,$v[0],$v[1]);
|
|
$ldap_timestamp = $Y.$m.$d.$H.$M.$S;
|
|
}
|
|
else if ( preg_match('{^(\d{8})(\d{6})(Z)?$', $ldap_timestamp, $matches ) ) {
|
|
$ldap_timestamp = $matches[1].'T'.$matches[2].$matches[3];
|
|
}
|
|
else if ( empty($ldap_timestamp) ) {
|
|
$ldap_timestamp = date('c');
|
|
}
|
|
if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
|
|
$valid[$mapping['modified']] = $ldap_timestamp;
|
|
}
|
|
|
|
sync_user_from_LDAP( $principal, $mapping, $valid );
|
|
}
|
|
$c->messages[] = sprintf( i18n('- creating record for users : %s'), join(', ',$users_to_create) );
|
|
}
|
|
|
|
// deactivating all users
|
|
$params = array();
|
|
$i = 0;
|
|
$paramstring = '';
|
|
foreach( $users_to_deactivate as $k => $v ) {
|
|
if ( isset($c->do_not_sync_from_ldap) && isset($c->do_not_sync_from_ldap[$v]) ) {
|
|
unset($users_to_deactivate[$k]);
|
|
$users_nothing_done[] = $v;
|
|
continue;
|
|
}
|
|
if ( $i > 0 ) $paramstring .= ',';
|
|
$paramstring .= ':u'.$i.'::text';
|
|
$params[':u'.$i++] = strtolower($v);
|
|
}
|
|
if ( count($params) > 0 ) {
|
|
$c->messages[] = sprintf(i18n('- deactivating users : %s'),join(', ',$users_to_deactivate));
|
|
$qry = new AwlQuery( 'UPDATE usr SET active = FALSE WHERE lower(username) IN ('.$paramstring.')', $params);
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
|
|
Principal::cacheFlush('lower(username) IN ('.$paramstring.')', $params);
|
|
}
|
|
|
|
// updating all users
|
|
if ( sizeof($users_to_update) ) {
|
|
foreach ( $users_to_update as $key=> $username ) {
|
|
$principal = new Principal( 'username', $username );
|
|
$valid=$ldap_users_info[$username];
|
|
if ( $mapping['modified'] != "" && array_key_exists($mapping['modified'], $valid)) {
|
|
$ldap_timestamp = $valid[$mapping['modified']];
|
|
} else {
|
|
$ldap_timestamp = '19700101000000';
|
|
}
|
|
|
|
$valid['user_no'] = $db_users_info[$username]['user_no'];
|
|
$mapping['user_no'] = 'user_no';
|
|
|
|
/**
|
|
* This splits the LDAP timestamp apart and assigns values to $Y $m $d $H $M and $S
|
|
*/
|
|
foreach($c->authenticate_hook['config']['format_updated'] as $k => $v) {
|
|
$$k = substr($ldap_timestamp,$v[0],$v[1]);
|
|
}
|
|
$ldap_timestamp = $Y.$m.$d.$H.$M.$S;
|
|
$valid[$mapping['modified']] = "$Y-$m-$d $H:$M:$S";
|
|
|
|
$db_timestamp = substr(strtr($db_users_info[$username]['updated'], array(':' => '',' '=>'','-'=>'')),0,14);
|
|
if ( $ldap_timestamp > $db_timestamp || !$db_users_info[$username]['user_active']) {
|
|
$principal->user_active = true;
|
|
sync_user_from_LDAP($principal, $mapping, $valid);
|
|
}
|
|
else {
|
|
unset($users_to_update[$key]);
|
|
$users_nothing_done[] = $username;
|
|
}
|
|
}
|
|
if ( sizeof($users_to_update) )
|
|
$c->messages[] = sprintf(i18n('- updating user records : %s'),join(', ',$users_to_update));
|
|
}
|
|
if ( sizeof($users_nothing_done) )
|
|
$c->messages[] = sprintf( i18n('- nothing done on : %s'), join(', ',$users_nothing_done) );
|
|
|
|
// check for remaining admins
|
|
$admins = 0;
|
|
$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'");
|
|
$qry->Exec('sync_LDAP',__LINE__,__FILE__);
|
|
while ( $db_user = $qry->Fetch() ) {
|
|
$admins = $db_user->admins;
|
|
}
|
|
if ( $admins == 0 ) {
|
|
$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.'));
|
|
}
|
|
}
|