mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-25 09:23:28 +01:00
WIP ADS/LDAP account import:
- implement import from groups and memberships --> ToDo: deleting of accounts and testing with LDAP
This commit is contained in:
parent
2cd5c861aa
commit
2dd3a25b35
@ -893,6 +893,7 @@ class Ads
|
||||
* @param $param['offset'] int - number of matches to return if start given, default use the value in the prefs
|
||||
* @param $param['objectclass'] boolean return objectclass(es) under key 'objectclass' in each account
|
||||
* @param $param['active'] boolean true: only return active / not expired accounts
|
||||
* @param $param['modified'] int if given minimum modification time
|
||||
* @return array with account_id => data pairs, data is an array with account_id, account_lid, account_firstname,
|
||||
* account_lastname, person_id (id of the linked addressbook entry), account_status, account_expires, account_primary_group
|
||||
*/
|
||||
@ -942,6 +943,10 @@ class Ads
|
||||
$membership_filter = '(|(memberOf='.$this->id2name((int)$param['type'], 'account_dn').')(PrimaryGroupId='.abs($param['type']).'))';
|
||||
$filter = $filter ? "(&$membership_filter$filter)" : $membership_filter;
|
||||
}
|
||||
if (!empty($param['modified']))
|
||||
{
|
||||
$filter = "(&(whenChanged>=".gmdate('YmdHis', $param['modified']).".0Z)$filter)";
|
||||
}
|
||||
foreach($this->filter($filter, 'u', self::$user_attributes, [], $param['active'], $param['order'].' '.$param['sort'], $start, $offset, $this->total) as $account_id => $data)
|
||||
{
|
||||
$account = $this->_ldap2user($data);
|
||||
@ -970,6 +975,10 @@ class Ads
|
||||
}
|
||||
$filter = "(|(cn=$query)(description=$query))";
|
||||
}
|
||||
if (!empty($param['modified']))
|
||||
{
|
||||
$filter = "(&(whenChanged>=".gmdate('YmdHis', $param['modified']).".0Z)$filter)";
|
||||
}
|
||||
foreach($this->filter($filter, 'g', self::$group_attributes) as $account_id => $data)
|
||||
{
|
||||
$accounts[$account_id] = $this->_ldap2group($data);
|
||||
|
@ -12,6 +12,11 @@ namespace EGroupware\Api\Accounts;
|
||||
|
||||
use EGroupware\Api;
|
||||
|
||||
/**
|
||||
* Account import from LDAP (incl. ADS) to SQL
|
||||
*
|
||||
* @todo check that ADS and LDAP update modification time of account, if memberships change
|
||||
*/
|
||||
class Import
|
||||
{
|
||||
public function __construct()
|
||||
@ -71,6 +76,14 @@ class Import
|
||||
$accounts_sql = new Api\Accounts\Sql(new Api\Accounts(['account_repository' => 'sql']+$GLOBALS['egw_info']['server']));
|
||||
Api\Accounts::cache_invalidate(); // to not get any cached data eg. from the wrong backend
|
||||
|
||||
$created = $updated = $uptodate = $errors = $deleted = 0;
|
||||
if (in_array('groups', explode('_', $type)))
|
||||
{
|
||||
[$created, $updated, $uptodate, $errors, $deleted] = $this->groups(
|
||||
$initial_import ? null : $GLOBALS['egw_info']['server']['account_import_lastrun'],
|
||||
$accounts, $accounts_sql, $logger, $delete, $groups);
|
||||
}
|
||||
|
||||
$filter = [
|
||||
'owner' => '0',
|
||||
];
|
||||
@ -80,7 +93,6 @@ class Import
|
||||
}
|
||||
$last_modified = null;
|
||||
$start_import = time();
|
||||
$created = $updated = $uptodate = $errors = 0;
|
||||
$cookie = '';
|
||||
$start = ['', 5, &$cookie]; // cookie must be a reference!
|
||||
do
|
||||
@ -98,13 +110,19 @@ class Import
|
||||
if (!($account_id = $accounts_sql->name2id($account['account_lid'])))
|
||||
{
|
||||
$sql_account = $account;
|
||||
if ($accounts_sql->save($sql_account, true) > 0)
|
||||
// check if account_id is not yet taken by another user or group --> unset it to let DB assign a new one
|
||||
if ($accounts_sql->read($account['account_id']))
|
||||
{
|
||||
$logger("Successful created user '$account[account_lid]' (#$account_id)", 'detail');
|
||||
unset($sql_account['account_id']);
|
||||
}
|
||||
if (($account_id = $accounts_sql->save($sql_account, true)) > 0)
|
||||
{
|
||||
$logger("Successful created user '$account[account_lid]' (#$account[account_id]".
|
||||
($account['account_id'] != $account_id ? " as #$account_id" : '').')', 'detail');
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger("Error creaing user '$account[account_lid]' (#$account_id)", 'error');
|
||||
$logger("Error creaing user '$account[account_lid]' (#$account[account_id])", 'error');
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
@ -193,6 +211,15 @@ class Import
|
||||
$logger("Contact data of '$account[account_lid]' (#$account_id) already up to date", 'debug');
|
||||
}
|
||||
}
|
||||
// if requested, also set memberships
|
||||
if ($type === 'users_groups')
|
||||
{
|
||||
// we need to convert the account_id's of memberships, in case we use different ones in SQL
|
||||
$accounts_sql->set_memberships(array_filter(array_map(static function($account_lid) use ($groups)
|
||||
{
|
||||
return array_search($account_lid, $groups);
|
||||
}, $account['memberships'])), $account_id);
|
||||
}
|
||||
if ($new)
|
||||
{
|
||||
++$created;
|
||||
@ -216,19 +243,19 @@ class Import
|
||||
{
|
||||
$logger("Setting new incremental import time to: $str ($last_run)", 'detail');
|
||||
}
|
||||
if ($created || $updated || $errors)
|
||||
if ($created || $updated || $errors || $deleted)
|
||||
{
|
||||
$result = "Created $created and updated $updated users, with $errors errors.";
|
||||
$result = "Created $created, updated $updated and deleted $deleted accounts, with $errors errors.";
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = "All users are up-to-date.";
|
||||
$result = "All accounts are up-to-date.";
|
||||
}
|
||||
$logger($result, 'info');
|
||||
|
||||
if ($initial_import && self::installAsyncJob())
|
||||
{
|
||||
$logger('Aync job for periodic import installed', 'info');
|
||||
$logger('Async job for periodic import installed', 'info');
|
||||
}
|
||||
}
|
||||
catch(\Exception $e) {
|
||||
@ -240,10 +267,147 @@ class Import
|
||||
'updated' => $updated,
|
||||
'uptodate' => $uptodate,
|
||||
'errors' => $errors,
|
||||
'deleted' => $deleted,
|
||||
'result' => $result,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Import all groups
|
||||
*
|
||||
* We assume we can list all groups without running into memory or timeout issues.
|
||||
* Groups with identical names as users are skipped, but logged as error.
|
||||
*
|
||||
* We can only delete no longer existing groups, if we query all groups!
|
||||
* So $delete !== 'no', requires $modified === null.
|
||||
*
|
||||
* @param Ldap|Ads $accounts
|
||||
* @param Sql $accounts_sql
|
||||
* @param callable $logger function($str, $level) level: "debug", "detail", "info", "error" or "fatal"
|
||||
* @param string $delete what to do with no longer existing groups: "yes": delete incl. data, "deactivate": delete group, "no": do nothing
|
||||
* @param int|null $modified null: initial import, int: timestamp of last import
|
||||
* @param array|null &$groups on return all current groups as account_id => account_lid pairs
|
||||
* @return array with int values [$created, $updated, $uptodate, $errors, $deleted]
|
||||
*/
|
||||
protected function groups($modified, object $accounts, Sql $accounts_sql, callable $logger, string $delete, array &$groups=null)
|
||||
{
|
||||
// to delete no longer existing groups, we have to query all groups!
|
||||
if ($delete !== 'no')
|
||||
{
|
||||
$modified = null;
|
||||
}
|
||||
|
||||
// query all groups in SQL
|
||||
$sql_groups = $groups = [];
|
||||
foreach($GLOBALS['egw']->db->select(Sql::TABLE, 'account_id,account_lid', ['account_type' => 'g'], __LINE__, __FILE__) as $row)
|
||||
{
|
||||
$sql_groups[-$row['account_id']] = $row['account_lid'];
|
||||
}
|
||||
// fill groups with existing ones, for incremental sync, as we need to return all groups
|
||||
if (!empty($modified))
|
||||
{
|
||||
$groups = $sql_groups;
|
||||
}
|
||||
|
||||
$created = $updated = $uptodate = $errors = $deleted = 0;
|
||||
foreach($accounts->search(['type' => 'groups', 'modified' => $modified]) as $account_id => $group)
|
||||
{
|
||||
$logger(json_encode($group, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), 'debug');
|
||||
|
||||
if (!($sql_id = array_search($group['account_lid'], $sql_groups)))
|
||||
{
|
||||
if ($accounts_sql->name2id($group['account_lid']) > 0)
|
||||
{
|
||||
$logger("Group '$group[account_lid]' already exists as user --> skipped!", 'error');
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
// check if the numeric account_id is not yet taken --> unset account_id and let DB create a new one
|
||||
if ($accounts_sql->read($account_id))
|
||||
{
|
||||
unset($group['account_id']);
|
||||
}
|
||||
if (($sql_id = $accounts_sql->save($group, true)) < 0)
|
||||
{
|
||||
$logger("Successful created group '$group[account_lid]' (#$account_id".($sql_id != $account_id ? " as #$sql_id" : '').')', 'detail');
|
||||
$created++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger("Error creating group '$group[account_lid]' (#$account_id)", 'error');
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
elseif (!($sql_group = $accounts_sql->read($sql_id)))
|
||||
{
|
||||
throw new \Exception("Group '$group[account_lid]' (#$sql_id) should exist, but not found!");
|
||||
}
|
||||
else
|
||||
{
|
||||
$group['account_id'] = $sql_id;
|
||||
unset($sql_group['account_fullname'], $sql_group['account_firstname'], $sql_group['account_lastname']); // not stored anywhere
|
||||
// ignore LDAP specific fields, and empty fields
|
||||
$relevant = array_filter(array_intersect_key($group, $sql_group), static function ($attr) {
|
||||
return $attr !== null && $attr !== '';
|
||||
});
|
||||
$to_update = $relevant + $sql_group;
|
||||
if (($diff = array_diff_assoc($to_update, $sql_group)))
|
||||
{
|
||||
if ($accounts_sql->save($group, true) > 0)
|
||||
{
|
||||
$logger("Successful updated group '$group[account_lid]' (#$sql_id): " .
|
||||
json_encode($diff, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 'detail');
|
||||
$updated++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger("Error updating group '$group[account_lid]' (#$sql_id)", 'error');
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$logger("Group '$group[account_lid]' (#$sql_id) already up to date", 'debug');
|
||||
$uptodate++;
|
||||
}
|
||||
// unset the updated groups, so we can delete the ones not returned from LDAP
|
||||
unset($sql_groups[$sql_id]);
|
||||
}
|
||||
$groups[$sql_id] = $group['account_lid'];
|
||||
}
|
||||
|
||||
// delete the groups not returned from LDAP, groups can NOT be deactivated, we just delete them in the DB
|
||||
foreach($delete !== 'no' ? $sql_groups : [] as $account_id => $account_lid)
|
||||
{
|
||||
static $acl=null;
|
||||
if ($delete === 'yes')
|
||||
{
|
||||
try {
|
||||
$cmd = new \admin_cmd_delete_account($account_id);
|
||||
$cmd->run();
|
||||
}
|
||||
catch (\Exception $e) {
|
||||
$logger("Error deleting no longer existing group '$account_lid' (#$account_id).", 'error');
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
// still run the SQL commands, as an LDAP/ADS system will not run them
|
||||
if ($accounts_sql->delete($account_id))
|
||||
{
|
||||
if (!isset($acl)) $acl = new Api\Acl();
|
||||
$acl->delete_account($account_id);
|
||||
$logger("Successful deleted no longer existing group '$account_lid' (#$account_id).", 'detail');
|
||||
$deleted++;
|
||||
}
|
||||
elseif (!isset($e))
|
||||
{
|
||||
$logger("Error deleting no longer existing group '$account_lid' (#$account_id).", 'error');
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
return [$created, $updated, $uptodate, $errors, $deleted];
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook called when setup configuration is being stored:
|
||||
* - install/removing cron job to periodic import accounts from LDAP/ADS
|
||||
|
@ -269,7 +269,10 @@ class Sql
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete one account, deletes also all acl-entries for that account
|
||||
* Delete one account
|
||||
*
|
||||
* Does NOT delete acl-entries and memberships, use Acl::delete_account($account_id) for that!
|
||||
* Users need to be deleted via admin_cmd_delete_account, to ensure proper data removal.
|
||||
*
|
||||
* @param int $account_id numeric account_id
|
||||
* @return boolean true on success, false otherwise
|
||||
@ -278,13 +281,11 @@ class Sql
|
||||
{
|
||||
if (!(int)$account_id) return false;
|
||||
|
||||
$contact_id = $this->id2name($account_id,'person_id');
|
||||
|
||||
if (!$this->db->delete($this->table,array('account_id' => abs($account_id)),__LINE__,__FILE__))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if ($contact_id)
|
||||
if ($account_id > 0 && ($contact_id = $this->id2name($account_id,'person_id')))
|
||||
{
|
||||
if (!isset($this->contacts)) $this->contacts = new Api\Contacts();
|
||||
$this->contacts->delete($contact_id,false); // false = allow to delete accounts (!)
|
||||
|
@ -467,7 +467,7 @@
|
||||
<td>
|
||||
<select name="newsettings[account_import_type]">
|
||||
<option value="users" {selected_account_import_source_user}>{lang_just_users}</option>
|
||||
<option value="users_groups" disabled {selected_account_import_source_users_groups}>{lang_users,_groups_and_memberships}</option>
|
||||
<option value="users_groups" {selected_account_import_source_users_groups}>{lang_users,_groups_and_memberships}</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
Loading…
Reference in New Issue
Block a user