Merge pull request #5763 from AngelFQC/BT21930
Plugin: Azure: Improvements for existing user verification and scripts to sync userspull/5823/head
commit
52afec09f3
@ -0,0 +1,188 @@ |
||||
<?php |
||||
|
||||
/* For license terms, see /license.txt */ |
||||
|
||||
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; |
||||
use League\OAuth2\Client\Token\AccessTokenInterface; |
||||
use TheNetworg\OAuth2\Client\Provider\Azure; |
||||
|
||||
abstract class AzureCommand |
||||
{ |
||||
/** |
||||
* @var AzureActiveDirectory |
||||
*/ |
||||
protected $plugin; |
||||
/** |
||||
* @var Azure |
||||
*/ |
||||
protected $provider; |
||||
|
||||
public function __construct() |
||||
{ |
||||
$this->plugin = AzureActiveDirectory::create(); |
||||
$this->plugin->get_settings(true); |
||||
$this->provider = $this->plugin->getProviderForApiGraph(); |
||||
} |
||||
|
||||
/** |
||||
* @throws IdentityProviderException |
||||
*/ |
||||
protected function generateOrRefreshToken(?AccessTokenInterface &$token) |
||||
{ |
||||
if (!$token || ($token->getExpires() && !$token->getRefreshToken())) { |
||||
$token = $this->provider->getAccessToken( |
||||
'client_credentials', |
||||
['resource' => $this->provider->resource] |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @throws Exception |
||||
* |
||||
* @return Generator<int, array<string, string>> |
||||
*/ |
||||
protected function getAzureUsers(): Generator |
||||
{ |
||||
$userFields = [ |
||||
'givenName', |
||||
'surname', |
||||
'mail', |
||||
'userPrincipalName', |
||||
'businessPhones', |
||||
'mobilePhone', |
||||
'accountEnabled', |
||||
'mailNickname', |
||||
'id', |
||||
]; |
||||
|
||||
$query = sprintf( |
||||
'$top=%d&$select=%s', |
||||
AzureActiveDirectory::API_PAGE_SIZE, |
||||
implode(',', $userFields) |
||||
); |
||||
|
||||
$token = null; |
||||
|
||||
do { |
||||
$this->generateOrRefreshToken($token); |
||||
|
||||
try { |
||||
$azureUsersRequest = $this->provider->request( |
||||
'get', |
||||
"users?$query", |
||||
$token |
||||
); |
||||
} catch (Exception $e) { |
||||
throw new Exception('Exception when requesting users from Azure: '.$e->getMessage()); |
||||
} |
||||
|
||||
$azureUsersInfo = $azureUsersRequest['value'] ?? []; |
||||
|
||||
foreach ($azureUsersInfo as $azureUserInfo) { |
||||
yield $azureUserInfo; |
||||
} |
||||
|
||||
$hasNextLink = false; |
||||
|
||||
if (!empty($azureUsersRequest['@odata.nextLink'])) { |
||||
$hasNextLink = true; |
||||
$query = parse_url($azureUsersRequest['@odata.nextLink'], PHP_URL_QUERY); |
||||
} |
||||
} while ($hasNextLink); |
||||
} |
||||
|
||||
/** |
||||
* @throws Exception |
||||
* |
||||
* @return Generator<int, array<string, string>> |
||||
*/ |
||||
protected function getAzureGroups(): Generator |
||||
{ |
||||
$groupFields = [ |
||||
'id', |
||||
'displayName', |
||||
'description', |
||||
]; |
||||
|
||||
$query = sprintf( |
||||
'$top=%d&$select=%s', |
||||
AzureActiveDirectory::API_PAGE_SIZE, |
||||
implode(',', $groupFields) |
||||
); |
||||
|
||||
$token = null; |
||||
|
||||
do { |
||||
$this->generateOrRefreshToken($token); |
||||
|
||||
try { |
||||
$azureGroupsRequest = $this->provider->request('get', "groups?$query", $token); |
||||
} catch (Exception $e) { |
||||
throw new Exception('Exception when requesting groups from Azure: '.$e->getMessage()); |
||||
} |
||||
|
||||
$azureGroupsInfo = $azureGroupsRequest['value'] ?? []; |
||||
|
||||
foreach ($azureGroupsInfo as $azureGroupInfo) { |
||||
yield $azureGroupInfo; |
||||
} |
||||
|
||||
$hasNextLink = false; |
||||
|
||||
if (!empty($azureGroupsRequest['@odata.nextLink'])) { |
||||
$hasNextLink = true; |
||||
$query = parse_url($azureGroupsRequest['@odata.nextLink'], PHP_URL_QUERY); |
||||
} |
||||
} while ($hasNextLink); |
||||
} |
||||
|
||||
/** |
||||
* @throws Exception |
||||
* |
||||
* @return Generator<int, array<string, string>> |
||||
*/ |
||||
protected function getAzureGroupMembers(string $groupUid): Generator |
||||
{ |
||||
$userFields = [ |
||||
'mail', |
||||
'mailNickname', |
||||
'id', |
||||
]; |
||||
|
||||
$query = sprintf( |
||||
'$top=%d&$select=%s', |
||||
AzureActiveDirectory::API_PAGE_SIZE, |
||||
implode(',', $userFields) |
||||
); |
||||
|
||||
$token = null; |
||||
|
||||
do { |
||||
$this->generateOrRefreshToken($token); |
||||
|
||||
try { |
||||
$azureGroupMembersRequest = $this->provider->request( |
||||
'get', |
||||
"groups/$groupUid/members?$query", |
||||
$token |
||||
); |
||||
} catch (Exception $e) { |
||||
throw new Exception('Exception when requesting group members from Azure: '.$e->getMessage()); |
||||
} |
||||
|
||||
$azureGroupMembers = $azureGroupMembersRequest['value'] ?? []; |
||||
|
||||
foreach ($azureGroupMembers as $azureGroupMember) { |
||||
yield $azureGroupMember; |
||||
} |
||||
|
||||
$hasNextLink = false; |
||||
|
||||
if (!empty($azureGroupMembersRequest['@odata.nextLink'])) { |
||||
$hasNextLink = true; |
||||
$query = parse_url($azureGroupMembersRequest['@odata.nextLink'], PHP_URL_QUERY); |
||||
} |
||||
} while ($hasNextLink); |
||||
} |
||||
} |
||||
@ -0,0 +1,74 @@ |
||||
<?php |
||||
|
||||
/* For license terms, see /license.txt */ |
||||
|
||||
class AzureSyncUsergroupsCommand extends AzureCommand |
||||
{ |
||||
/** |
||||
* @throws Exception |
||||
* |
||||
* @return Generator<int, string> |
||||
*/ |
||||
public function __invoke(): Generator |
||||
{ |
||||
yield 'Synchronizing groups from Azure.'; |
||||
|
||||
$usergroup = new UserGroup(); |
||||
|
||||
$groupIdByUid = []; |
||||
|
||||
foreach ($this->getAzureGroups() as $azureGroupInfo) { |
||||
if ($usergroup->usergroup_exists($azureGroupInfo['displayName'])) { |
||||
$groupId = $usergroup->getIdByName($azureGroupInfo['displayName']); |
||||
|
||||
if ($groupId) { |
||||
$usergroup->subscribe_users_to_usergroup($groupId, []); |
||||
|
||||
yield sprintf('Class exists, all users unsubscribed: %s', $azureGroupInfo['displayName']); |
||||
} |
||||
} else { |
||||
$groupId = $usergroup->save([ |
||||
'name' => $azureGroupInfo['displayName'], |
||||
'description' => $azureGroupInfo['description'], |
||||
]); |
||||
|
||||
if ($groupId) { |
||||
yield sprintf('Class created: %s', $azureGroupInfo['displayName']); |
||||
} |
||||
} |
||||
|
||||
$groupIdByUid[$azureGroupInfo['id']] = $groupId; |
||||
} |
||||
|
||||
yield '----------------'; |
||||
yield 'Subscribing users to groups'; |
||||
|
||||
foreach ($groupIdByUid as $azureGroupUid => $groupId) { |
||||
$newGroupMembers = []; |
||||
|
||||
yield sprintf('Obtaining members for group (ID %d)', $groupId); |
||||
|
||||
try { |
||||
foreach ($this->getAzureGroupMembers($azureGroupUid) as $azureGroupMember) { |
||||
if ($userId = $this->plugin->getUserIdByVerificationOrder($azureGroupMember, 'id')) { |
||||
$newGroupMembers[] = $userId; |
||||
} |
||||
} |
||||
} catch (Exception $e) { |
||||
yield $e->getMessage(); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
if ($newGroupMembers) { |
||||
$usergroup->subscribe_users_to_usergroup($groupId, $newGroupMembers); |
||||
|
||||
yield sprintf( |
||||
'User IDs subscribed in class (ID %d): %s', |
||||
$groupId, |
||||
implode(', ', $newGroupMembers) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,98 @@ |
||||
<?php |
||||
|
||||
/* For license terms, see /license.txt */ |
||||
|
||||
use Chamilo\UserBundle\Entity\User; |
||||
|
||||
class AzureSyncUsersCommand extends AzureCommand |
||||
{ |
||||
/** |
||||
* @throws Exception |
||||
* |
||||
* @return Generator<int, string> |
||||
*/ |
||||
public function __invoke(): Generator |
||||
{ |
||||
yield 'Synchronizing users from Azure.'; |
||||
|
||||
/** @var array<string, int> $existingUsers */ |
||||
$existingUsers = []; |
||||
|
||||
foreach ($this->getAzureUsers() as $azureUserInfo) { |
||||
try { |
||||
$userId = $this->plugin->registerUser($azureUserInfo, 'id'); |
||||
} catch (Exception $e) { |
||||
yield $e->getMessage(); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
$existingUsers[$azureUserInfo['id']] = $userId; |
||||
|
||||
yield sprintf('User (ID %d) with received info: %s ', $userId, serialize($azureUserInfo)); |
||||
} |
||||
|
||||
yield '----------------'; |
||||
yield 'Updating users status'; |
||||
|
||||
$roleGroups = $this->plugin->getGroupUidByRole(); |
||||
$roleActions = $this->plugin->getUpdateActionByRole(); |
||||
|
||||
$userManager = UserManager::getManager(); |
||||
$em = Database::getManager(); |
||||
|
||||
foreach ($roleGroups as $userRole => $groupUid) { |
||||
try { |
||||
$azureGroupMembersInfo = iterator_to_array($this->getAzureGroupMembers($groupUid)); |
||||
} catch (Exception $e) { |
||||
yield $e->getMessage(); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
$azureGroupMembersUids = array_column($azureGroupMembersInfo, 'id'); |
||||
|
||||
foreach ($azureGroupMembersUids as $azureGroupMembersUid) { |
||||
$userId = $existingUsers[$azureGroupMembersUid] ?? null; |
||||
|
||||
if (!$userId) { |
||||
continue; |
||||
} |
||||
|
||||
if (isset($roleActions[$userRole])) { |
||||
/** @var User $user */ |
||||
$user = $userManager->find($userId); |
||||
|
||||
$roleActions[$userRole]($user); |
||||
|
||||
yield sprintf('User (ID %d) status %s', $userId, $userRole); |
||||
} |
||||
} |
||||
|
||||
$em->flush(); |
||||
} |
||||
|
||||
if ('true' === $this->plugin->get(AzureActiveDirectory::SETTING_DEACTIVATE_NONEXISTING_USERS)) { |
||||
yield '----------------'; |
||||
|
||||
yield 'Trying deactivate non-existing users in Azure'; |
||||
|
||||
$users = UserManager::getRepository()->findByAuthSource('azure'); |
||||
$userIdList = array_map( |
||||
function ($user) { |
||||
return $user->getId(); |
||||
}, |
||||
$users |
||||
); |
||||
|
||||
$nonExistingUsers = array_diff($userIdList, $existingUsers); |
||||
|
||||
UserManager::deactivate_users($nonExistingUsers); |
||||
|
||||
yield sprintf( |
||||
'Deactivated users IDs: %s', |
||||
implode(', ', $nonExistingUsers) |
||||
); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
/* For license terms, see /license.txt */ |
||||
|
||||
require __DIR__.'/../../../../main/inc/global.inc.php'; |
||||
|
||||
if (PHP_SAPI !== 'cli') { |
||||
exit('Run this script through the command line or comment this line in the code'); |
||||
} |
||||
|
||||
// Uncomment to indicate the access url to get the plugin settings when using multi-url |
||||
//$_configuration['access_url'] = 1; |
||||
|
||||
$command = new AzureSyncUsergroupsCommand(); |
||||
|
||||
try { |
||||
foreach ($command() as $str) { |
||||
printf("%d - %s".PHP_EOL, time(), $str); |
||||
} |
||||
} catch (Exception $e) { |
||||
printf('%s - Exception: %s'.PHP_EOL, time(), $e->getMessage()); |
||||
} |
||||
@ -0,0 +1,21 @@ |
||||
<?php |
||||
/* For license terms, see /license.txt */ |
||||
|
||||
require __DIR__.'/../../../../main/inc/global.inc.php'; |
||||
|
||||
if (PHP_SAPI !== 'cli') { |
||||
exit('Run this script through the command line or comment this line in the code'); |
||||
} |
||||
|
||||
// Uncomment to indicate the access url to get the plugin settings when using multi-url |
||||
//$_configuration['access_url'] = 1; |
||||
|
||||
$command = new AzureSyncUsersCommand(); |
||||
|
||||
try { |
||||
foreach ($command() as $str) { |
||||
printf("%d - %s".PHP_EOL, time(), $str); |
||||
} |
||||
} catch (Exception $e) { |
||||
printf('%s - Exception: %s'.PHP_EOL, time(), $e->getMessage()); |
||||
} |
||||
Loading…
Reference in new issue