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