* LDAP/ActiveDirectory Sync: permanently store DN+entryUUID and use the latter to detect renamed user or accounts

This commit is contained in:
ralf 2024-08-12 14:32:14 +02:00
parent 10a7a4bd7e
commit 9888a681e7
6 changed files with 73 additions and 31 deletions

View File

@ -11,7 +11,7 @@
/* Basic information about this app */
$setup_info['api']['name'] = 'api';
$setup_info['api']['title'] = 'EGroupware API';
$setup_info['api']['version'] = '23.1.007';
$setup_info['api']['version'] = '23.1.008';
$setup_info['api']['versions']['current_header'] = '1.29';
// maintenance release in sync with changelog in doc/rpm-build/debian.changes
$setup_info['api']['versions']['maintenance_release'] = '23.1.20240624';

View File

@ -64,7 +64,9 @@ $phpgw_baseline = array(
'account_expires' => array('type' => 'int','precision' => '4'),
'account_type' => array('type' => 'char','precision' => '1'),
'account_primary_group' => array('type' => 'int','meta' => 'group','precision' => '4','nullable' => False,'default' => '0'),
'account_description' => array('type' => 'varchar','precision' => '255','comment' => 'group description')
'account_description' => array('type' => 'varchar','precision' => '255','comment' => 'group description'),
'account_uuid' => array('type' => 'ascii','precision' => '64','comment' => 'UUID of synced (LDAP) entries'),
'account_dn' => array('type' => 'varchar','precision' => '255','comment' => 'DN or container')
),
'pk' => array('account_id'),
'fk' => array(),
@ -553,4 +555,4 @@ $phpgw_baseline = array(
'ix' => array('account_id'),
'uc' => array()
)
);
);

View File

@ -976,4 +976,20 @@ function api_upgrade23_1_006()
));
return $GLOBALS['setup_info']['api']['currentver'] = '23.1.007';
}
function api_upgrade23_1_007()
{
$GLOBALS['egw_setup']->oProc->AddColumn('egw_accounts','account_uuid',array(
'type' => 'ascii',
'precision' => '64',
'comment' => 'UUID of synced (LDAP) entries'
));
$GLOBALS['egw_setup']->oProc->AddColumn('egw_accounts','account_dn',array(
'type' => 'varchar',
'precision' => '255',
'comment' => 'DN or container'
));
return $GLOBALS['setup_info']['api']['currentver'] = '23.1.008';
}

View File

@ -113,7 +113,7 @@ class Ads
* @var array
*/
protected static $default_attributes = array(
'objectsid', 'samaccounttype', 'samaccountname',
'objectsid', 'samaccounttype', 'samaccountname', 'entryuuid',
);
/**
@ -122,7 +122,7 @@ class Ads
* @var array
*/
protected static $user_attributes = array(
'objectsid', 'samaccounttype', 'samaccountname',
'objectsid', 'samaccounttype', 'samaccountname', 'entryuuid',
'primarygroupid', 'givenname', 'sn', 'mail', 'displayname', 'telephonenumber',
'objectguid', 'useraccountcontrol', 'accountexpires', 'pwdlastset', 'whencreated', 'whenchanged', 'lastlogon',
'jpegphoto',
@ -134,7 +134,7 @@ class Ads
* @var array
*/
protected static $group_attributes = array(
'objectsid', 'samaccounttype', 'samaccountname',
'objectsid', 'samaccounttype', 'samaccountname', 'entryuuid',
'objectguid', 'mail', 'whencreated', 'whenchanged', 'description',
);
@ -565,6 +565,7 @@ class Ads
$group = array(
'account_dn' => $data['dn'],
'account_uuid' => $data['entryuuid'][0],
'account_id' => $account_id,
'account_sid' => $sid,
'account_guid' => $this->adldap->utilities()->decodeGuid($data['objectguid'][0]),
@ -644,6 +645,7 @@ class Ads
$user = array(
'account_dn' => $data['dn'],
'account_uuid' => $data['entryuuid'][0],
'account_id' => $account_id,
'account_sid' => $sid,
'account_guid' => $this->adldap->utilities()->decodeGuid($data['objectguid'][0]),
@ -1327,6 +1329,8 @@ class Ads
'account_fullname' => 'cn',
'account_sid' => 'objectsid',
'account_guid' => 'objectguid',
'account_uuid' => 'entryuuid',
'account_dn' => 'dn',
);
$ret = false;
if (isset($to_ldap[$which]))

View File

@ -325,7 +325,8 @@ class Import
$hide_binary = ['jpegphoto' => $contact['jpegphoto'] ? bytes($contact['jpegphoto']).' bytes binary data' : null];
$this->logger(++$num.'. User: '.json_encode($hide_binary + $contact + $account, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE), 'debug');
// check if account exists in sql
if (!($account_id = $this->accounts_sql->name2id($account['account_lid'])))
if (!($account_id = $this->accounts_sql->name2id($account['account_uuid'], 'account_uuid')) &&
!($account_id = $this->accounts_sql->name2id($account['account_lid'])))
{
$sql_account = $account;
// if only users are imported set primary group as configured
@ -702,10 +703,14 @@ class Import
$local_groups = in_array('local', explode('+', $GLOBALS['egw_info']['server']['account_import_type']));
// query all groups in SQL
$sql_groups = $groups = $set_members = [];
foreach($GLOBALS['egw']->db->select(Sql::TABLE, 'account_id,account_lid', ['account_type' => 'g'], __LINE__, __FILE__) as $row)
$sql_groups = $uuid2account_id = $groups = $set_members = [];
foreach($GLOBALS['egw']->db->select(Sql::TABLE, 'account_id,account_lid,account_uuid', ['account_type' => 'g'], __LINE__, __FILE__) as $row)
{
$sql_groups[-$row['account_id']] = self::strtolower($row['account_lid']);
if (!empty($row['account_uuid']))
{
$uuid2account_id[$row['account_uuid']] = -$row['account_id'];
}
}
// fill groups with existing ones, for incremental sync, but only if we have NO local groups, otherwise we need to query them all
$filter = ['type' => 'groups'];
@ -733,7 +738,13 @@ class Import
}
$this->logger(++$num.'. Group: '.json_encode($group, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), 'debug');
if (!($sql_id = array_search(self::strtolower($group['account_lid']), $sql_groups)))
$sql_id = null;
// found group by uuid
if ($uuid2account_id && ($sql_id = $uuid2account_id[$group['account_uuid']] ?? false))
{
}
if (!$sql_id && !($sql_id = array_search(self::strtolower($group['account_lid']), $sql_groups)))
{
if ($this->accounts_sql->name2id($group['account_lid']) > 0)
{
@ -754,7 +765,7 @@ class Import
}
if (($sql_id = $group['account_id'] = $this->accounts_sql->save($group, true)) < 0)
{
// run addgroup hook to create eg. home-directory or mail account
// run addgroup hook to create e.g. home-directory or mail account
Api\Hooks::process($group+array(
'location' => 'addgroup'
),False,True); // called for every app now, not only enabled ones)

View File

@ -535,7 +535,7 @@ class Ldap
}
}
if (!($data = $this->_ldap_search($this->group_context,'(&(objectClass=posixGroup)(gidnumber=' . abs($account_id).'))',
array('dn', 'gidnumber', 'cn', 'objectclass', static::MAIL_ATTR, 'memberuid', 'description'))))
array('dn', 'gidnumber', 'cn', 'objectclass', static::MAIL_ATTR, 'memberuid', 'description', 'entryuuid'))))
{
return false; // group not found
}
@ -543,6 +543,7 @@ class Ldap
$group += array(
'account_dn' => $data['dn'],
'account_uuid' => $data['entryuuid'][0],
'account_id' => -$data['gidnumber'][0],
'account_lid' => $data['cn'][0],
'account_type' => 'g',
@ -585,7 +586,7 @@ class Ldap
if (!($data = $this->_ldap_search($this->user_context, '(&(objectclass=posixAccount)(uidnumber=' . (int)$account_id.")$account_filter)",
array('dn','uidnumber','uid','gidnumber','givenname','sn','cn',static::MAIL_ATTR,'userpassword','telephonenumber',
'shadowexpire','shadowlastchange','homedirectory','loginshell','createtimestamp','modifytimestamp'))))
'shadowexpire','shadowlastchange','homedirectory','loginshell','createtimestamp','modifytimestamp','entryuuid'))))
{
return false; // user not found
}
@ -593,6 +594,7 @@ class Ldap
$utc_diff = date('Z');
$user = array(
'account_dn' => $data['dn'],
'account_uuid' => $data['entryuuid'][0],
'account_id' => (int)$data['uidnumber'][0],
'account_lid' => $data['uid'][0],
'account_type' => 'u',
@ -893,7 +895,7 @@ class Ldap
{
$allValues = $this->vlvSortQuery($this->user_context, $filter,
['uid', 'uidNumber', 'givenname', 'sn', static::MAIL_ATTR, 'shadowExpire', 'createtimestamp',
'modifytimestamp', 'objectclass', 'gidNumber', 'jpegphoto'],
'modifytimestamp', 'objectclass', 'gidNumber', 'jpegphoto', 'entryuuid'],
$order_by, $start, $offset, $totalcount);
$utc_diff = date('Z');
@ -904,6 +906,7 @@ class Ldap
{
$account = array(
'account_dn' => $allVals['dn'],
'account_uuid' => $allVals['entryuuid'][0],
'account_id' => $allVals['uidnumber'][0],
'account_lid' => Api\Translation::convert($allVals['uid'][0], 'utf-8'),
'account_type' => 'u',
@ -963,7 +966,7 @@ class Ldap
$filter .= "(modifytimestamp>=".gmdate('YmdHis', $param['modified']).".0Z)";
}
$filter .= ')';
$allValues = $this->vlvSortQuery($this->group_context, $filter, ['cn', 'gidNumber'], $order_by, $start, $offset, $group_total);
$allValues = $this->vlvSortQuery($this->group_context, $filter, ['cn', 'gidNumber', 'entryuuid'], $order_by, $start, $offset, $group_total);
$totalcount += $group_total;
foreach($allValues ?: [] as $allVals)
{
@ -972,6 +975,7 @@ class Ldap
{
$accounts[(string)-$allVals['gidnumber'][0]] = Array(
'account_dn' => $allVals['dn'],
'account_uuid' => $allVals['entryuuid'][0],
'account_id' => -$allVals['gidnumber'][0],
'account_lid' => Api\Translation::convert($allVals['cn'][0],'utf-8'),
'account_type' => 'g',
@ -1085,32 +1089,38 @@ class Ldap
{
$name = Api\Ldap::quote(Api\Translation::convert($_name,Api\Translation::charset(),'utf-8'));
if (in_array($which, array('account_lid','account_email')) && $account_type !== 'u') // groups only support account_(lid|email)
$to_ldap = [
'account_lid' => 'cn',
'account_email' => static::MAIL_ATTR,
'account_uuid' => 'entryuuid',
'account_dn' => 'dn',
];
if (isset($to_ldap[$which]) && $account_type !== 'u') // groups only support account_(lid|email)
{
$attr = $which == 'account_lid' ? 'cn' : static::MAIL_ATTR;
if (($sri = ldap_search($this->ds, $this->group_context, '(&('.$attr.'=' . $name . ")(objectclass=posixgroup)$this->group_filter)", array('gidNumber'))) &&
($allValues = ldap_get_entries($this->ds, $sri)) &&
!empty($allValues[0]['gidnumber'][0]))
if (($data = ldap_search($this->group_context, '(&('.$attr.'=' . $name . ")(objectclass=posixgroup)$this->group_filter)", ['gidNumber'])) &&
!empty($data['gidnumber'][0]))
{
return -$allValues[0]['gidnumber'][0];
return -$data['gidnumber'][0];
}
}
$to_ldap = array(
$to_ldap = [
'account_lid' => 'uid',
'account_email' => static::MAIL_ATTR,
'account_fullname' => 'cn',
);
'account_uuid' => 'entryuuid',
'account_dn' => 'dn',
];
if (!isset($to_ldap[$which]) || $account_type === 'g')
{
return False;
}
if (($sri = ldap_search($this->ds, $this->user_context, '(&('.$to_ldap[$which].'=' . $name . ')(objectclass=posixaccount))', array('uidNumber'))) &&
($allValues = ldap_get_entries($this->ds, $sri)) &&
!empty($allValues[0]['uidnumber'][0]))
if (($data = $this->_ldap_search($this->user_context, '(&('.$to_ldap[$which].'=' . $name . ')(objectclass=posixaccount))', array('uidNumber'))) &&
!empty($data['uidNumber'][0]))
{
return (int)$allValues[0]['uidnumber'][0];
return (int)$data['uidnumber'][0];
}
return False;
}
@ -1181,15 +1191,14 @@ class Ldap
$gid = abs($_gid); // our gid is negative!
$sri = ldap_search($this->ds,$this->group_context,"(&(objectClass=posixGroup)(gidnumber=$gid))",array('memberuid'));
$group = ldap_get_entries($this->ds, $sri);
$group = $this->_ldap_search($this->group_context,"(&(objectClass=posixGroup)(gidnumber=$gid))", ['memberuid']);
$members = array();
if (isset($group[0]['memberuid']))
if ($group && isset($group['memberuid']))
{
unset($group[0]['memberuid']['count']);
unset($group['memberuid']['count']);
foreach($group[0]['memberuid'] as $lid)
foreach($group['memberuid'] as $lid)
{
if (($id = $this->name2id($lid, 'account_lid'))) // also return groups!
{