in local/o365/classes/feature/usersync/main.php [962:1182]
public function sync_users(array $aadusers = array()) {
global $DB, $CFG;
$aadsync = $this->get_sync_options();
$switchauthminupnsplit0 = get_config('local_o365', 'switchauthminupnsplit0');
if (empty($switchauthminupnsplit0)) {
$switchauthminupnsplit0 = 10;
}
$usernames = [];
$upns = [];
$guestsync = array_key_exists('guestsync', $aadsync);
foreach ($aadusers as $i => $user) {
if (!isset($user['userPrincipalName'])) {
// User doesn't have userPrincipalName, should be deleted users.
unset($aadusers[$i]);
continue;
}
if (!$guestsync) {
if (strpos($user['userPrincipalName'], '#EXT#') !== false) {
// The user is a guest user, and the guest sync option is disabled. Skip processing the user.
unset($aadusers[$i]);
continue;
}
}
$upnlower = \core_text::strtolower($user['userPrincipalName']);
$aadusers[$i]['upnlower'] = $upnlower;
$usernames[] = $upnlower;
$upns[] = $upnlower;
$upnsplit = explode('@', $upnlower);
if (!empty($upnsplit[0])) {
$aadusers[$i]['upnsplit0'] = $upnsplit[0];
$usernames[] = $upnsplit[0];
}
// Convert upn for guest users.
if (stripos($upnlower, '#ext#') !== false) {
$upnlower = \core_text::strtolower($user['mail']);
$usernames[] = $upnlower;
$upns[] = $upnlower;
$upnsplit = explode('@', $upnlower);
if (!empty($upnsplit[0])) {
$usernames[] = $upnsplit[0];
}
}
}
if (!$aadusers) {
return true;
}
$select = 'SELECT LOWER(u.username) AS username,';
if (isset($aadsync['emailsync'])) {
$select .= ' LOWER(u.email) AS email,';
}
$sql = "$select
u.id as muserid,
u.auth,
u.suspended,
tok.id as tokid,
conn.id as existingconnectionid,
assign.assigned assigned,
assign.photoid photoid,
assign.photoupdated photoupdated,
obj.id AS objectid
FROM {user} u
LEFT JOIN {auth_oidc_token} tok ON tok.userid = u.id
LEFT JOIN {local_o365_connections} conn ON conn.muserid = u.id
LEFT JOIN {local_o365_appassign} assign ON assign.muserid = u.id
LEFT JOIN {local_o365_objects} obj ON obj.type = ? AND obj.moodleid = u.id
WHERE u.mnethostid = ? AND u.deleted = ?
ORDER BY CONCAT(u.username, '~')"; // Sort john.smith@email.com before john.smith.
$params = array_merge(['user'], [$CFG->mnet_localhost_id, '0']);
$existingusers = $DB->get_records_sql($sql, $params);
foreach ($existingusers as $id => $existinguser) {
if (isset($aadsync['emailsync'])) {
if (!in_array($existinguser->email, $usernames)) {
unset($existingusers[$id]);
}
} else {
if (!in_array($existinguser->username, $usernames)) {
unset($existingusers[$id]);
}
}
}
// Fetch linked AAD user accounts.
[$upnsql, $upnparams] = $DB->get_in_or_equal($upns);
[$usernamesql, $usernameparams] = $DB->get_in_or_equal($usernames, SQL_PARAMS_QM, 'param', false);
$sql = 'SELECT tok.oidcusername,
u.username as username,
u.id as muserid,
u.auth,
tok.id as tokid,
conn.id as existingconnectionid,
assign.assigned assigned,
assign.photoid photoid,
assign.photoupdated photoupdated,
obj.id AS objectid
FROM {user} u
LEFT JOIN {auth_oidc_token} tok ON tok.userid = u.id
LEFT JOIN {local_o365_connections} conn ON conn.muserid = u.id
LEFT JOIN {local_o365_appassign} assign ON assign.muserid = u.id
LEFT JOIN {local_o365_objects} obj ON obj.type = ? AND obj.moodleid = u.id
WHERE tok.oidcusername '.$upnsql.' AND u.username '.$usernamesql.' AND u.mnethostid = ? AND u.deleted = ? ';
$params = array_merge(['user'], $upnparams, $usernameparams, [$CFG->mnet_localhost_id, '0']);
$linkedexistingusers = $DB->get_records_sql($sql, $params);
$existingusers = $existingusers + $linkedexistingusers;
$processedusers = [];
foreach ($aadusers as $aaduser) {
$this->mtrace(' ');
if (unified::is_configured()) {
$userobjectid = $aaduser['id'];
} else {
$userobjectid = $aaduser['objectId'];
}
if (empty($aaduser['upnlower'])) {
$this->mtrace('Azure AD user missing UPN (' . $userobjectid . '); skipping...');
continue;
}
$this->mtrace('Syncing user '.$aaduser['upnlower']);
// Process guest users.
$aaduser['convertedupn'] = $aaduser['upnlower'];
if (stripos($aaduser['userPrincipalName'], '#EXT#') !== false) {
$aaduser['convertedupn'] = strtolower($aaduser['mail']);
}
if (in_array($aaduser['convertedupn'], $processedusers)) {
$this->mtrace('User already processed; skipping...');
continue;
} else {
$processedusers[] = $aaduser['convertedupn'];
}
if (!isset($existingusers[$aaduser['upnlower']]) && !isset($existingusers[$aaduser['upnsplit0']]) &&
!isset($existingusers[$aaduser['convertedupn']])) {
$this->sync_new_user($aadsync, $aaduser, isset($aadsync['guestsync']));
} else {
$existinguser = null;
if (isset($existingusers[$aaduser['upnlower']])) {
$existinguser = $existingusers[$aaduser['upnlower']];
$exactmatch = true;
} else if (isset($existingusers[$aaduser['upnsplit0']])) {
$existinguser = $existingusers[$aaduser['upnsplit0']];
$exactmatch = strlen($aaduser['upnsplit0']) >= $switchauthminupnsplit0;
} else if (isset($existingusers[$aaduser['convertedupn']])) {
$existinguser = $existingusers[$aaduser['convertedupn']];
$exactmatch = true;
}
// Process guest users.
if (stripos($aaduser['upnlower'], '_ext_') !== false) {
$this->mtrace('The user is a guest user.');
if (!isset($aadsync['guestsync'])) {
$this->mtrace('The option to sync guest users is turned off.');
$this->mtrace('User is already synced, but not updated.');
continue;
}
}
$this->sync_existing_user($aadsync, $aaduser, $existinguser, $exactmatch);
if ($existinguser->auth === 'oidc' || empty($existinguser->tokid)) {
// Create userobject if it does not exist.
if (empty($existinguser->objectid)) {
$this->mtrace('Adding o365 object record for user.');
$now = time();
$userobjectdata = (object)[
'type' => 'user',
'subtype' => '',
'objectid' => $userobjectid,
'o365name' => $aaduser['userPrincipalName'],
'moodleid' => $existinguser->muserid,
'tenant' => '',
'timecreated' => $now,
'timemodified' => $now,
];
$userobjectdata->id = $DB->insert_record('local_o365_objects', $userobjectdata);
}
// User already connected.
$this->mtrace('User is now synced.');
}
// Update existing user on moodle from AD.
if ($existinguser->auth === 'oidc') {
if (isset($aadsync['update'])) {
$this->mtrace('Updating Moodle user data from Azure AD user data.');
$fullexistinguser = get_complete_user_data('username', $existinguser->username);
if ($fullexistinguser) {
$existingusercopy = \core_user::get_user_by_username($existinguser->username);
$fullexistinguser->description = $existingusercopy->description;
$this->update_user_from_aaddata($aaduser, $fullexistinguser);
$this->mtrace('User is now updated.');
} else {
$this->mtrace('Update failed for user with username "' . $existinguser->username . '".');
}
}
}
}
}
return true;
}