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