mirror of
https://gitlab.com/davical-project/davical.git
synced 2026-03-02 06:14:17 +00:00
The logic actually handled updated group details, but only new groups were being passed in. This will cause changes to what is being used for the fullname to flow through.
913 lines
32 KiB
PHP
913 lines
32 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_search';
|
|
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 (!ldap_bind($this->connect, (isset($config['bindDN']) ? $config['bindDN'] : null), (isset($config['passDN']) ? $config['passDN'] : null) ) ){
|
|
$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));
|
|
|
|
if ( isset($c->authenticate_hook['config']['i_use_mode_kerberos']) && $c->authenticate_hook['config']['i_use_mode_kerberos'] == "i_know_what_i_am_doing") {
|
|
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"]) {
|
|
return false;
|
|
}
|
|
} else {
|
|
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"]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else 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
|
|
}
|
|
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;
|
|
}
|
|
|
|
$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]) ) {
|
|
$user->{$field} = $ldap_values[$value];
|
|
dbg_error_log( "LDAP", "Setting usr->%s to %s from LDAP field %s", $field, $ldap_values[$value], $value );
|
|
}
|
|
}
|
|
|
|
if ($user->fullname == "") {
|
|
$user->fullname = $group;
|
|
}
|
|
|
|
if (! isset($user->displayname) || $user->displayname == "") {
|
|
$user->displayname = $group;
|
|
}
|
|
|
|
$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'];
|
|
}
|
|
|
|
foreach ( $groups_to_sync_members as $group ) {
|
|
$db_members = 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();
|
|
|
|
if (isset($c->authenticate_hook['config']['group_match_username_attr'])
|
|
&& isset($user_mapping['username'])) {
|
|
$query = $ldapDriver->ldap_query_one;
|
|
$username_ldap_attribute = $user_mapping['username'];
|
|
|
|
$filter = $ldapDriver->filterUsers();
|
|
|
|
$ldap_members_tmp = array();
|
|
|
|
foreach ( $ldap_members as $member ){
|
|
$entry = ldap_read($ldapDriver->connect, $member, $filter,
|
|
array($username_ldap_attribute));
|
|
$ldap_user_entry
|
|
= ldap_first_entry($ldapDriver->connect,$entry);
|
|
$ldap_user_attributes
|
|
= ldap_get_attributes($ldapDriver->connect, $ldap_user_entry);
|
|
|
|
array_push($ldap_members_tmp,
|
|
$ldap_user_attributes[$username_ldap_attribute][0]);
|
|
}
|
|
|
|
$ldap_members = $ldap_members_tmp;
|
|
}
|
|
else if ( $member_field == 'uniqueMember' || $dnfix ) {
|
|
$ldap_members = fix_unique_member( $ldap_members );
|
|
}
|
|
|
|
$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 ) {
|
|
$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.'));
|
|
}
|
|
}
|