diff --git a/plugin/azure_active_directory/lang/dutch.php b/plugin/azure_active_directory/lang/dutch.php
index 32885f3f68..48a8049ec3 100644
--- a/plugin/azure_active_directory/lang/dutch.php
+++ b/plugin/azure_active_directory/lang/dutch.php
@@ -46,3 +46,7 @@ $strings['tenant_id'] = 'Mandanten-ID';
$strings['tenant_id_help'] = 'Required to run scripts.';
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
+$strings['script_users_delta'] = 'Delta query for users';
+$strings['script_users_delta_help'] = 'Get newly created, updated, or deleted users without having to perform a full read of the entire user collection. By default, is No
.';
+$strings['script_usergroups_delta'] = 'Delta query for usergroups';
+$strings['script_usergroups_delta_help'] = 'Get newly created, updated, or deleted groups, including group membership changes, without having to perform a full read of the entire group collection. By default, is No
.';
diff --git a/plugin/azure_active_directory/lang/english.php b/plugin/azure_active_directory/lang/english.php
index 0d000a7d79..defb392d1e 100644
--- a/plugin/azure_active_directory/lang/english.php
+++ b/plugin/azure_active_directory/lang/english.php
@@ -46,3 +46,7 @@ $strings['tenant_id'] = 'Tenant ID';
$strings['tenant_id_help'] = 'Required to run scripts.';
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
+$strings['script_users_delta'] = 'Delta query for users';
+$strings['script_users_delta_help'] = 'Get newly created, updated, or deleted users without having to perform a full read of the entire user collection. By default, is No
.';
+$strings['script_usergroups_delta'] = 'Delta query for usergroups';
+$strings['script_usergroups_delta_help'] = 'Get newly created, updated, or deleted groups, including group membership changes, without having to perform a full read of the entire group collection. By default, is No
.';
diff --git a/plugin/azure_active_directory/lang/french.php b/plugin/azure_active_directory/lang/french.php
index e699c6d91d..3707e64de8 100644
--- a/plugin/azure_active_directory/lang/french.php
+++ b/plugin/azure_active_directory/lang/french.php
@@ -46,3 +46,7 @@ $strings['tenant_id'] = 'ID du client';
$strings['tenant_id_help'] = 'Nécessaire pour exécuter des scripts.';
$strings['deactivate_nonexisting_users'] = 'Deactivate non-existing users';
$strings['deactivate_nonexisting_users_help'] = 'Compare registered users in Chamilo with those in Azure and deactivate accounts in Chamilo that do not exist in Azure.';
+$strings['script_users_delta'] = 'Requête delta pour les utilisateurs';
+$strings['script_users_delta_help'] = 'Get newly created, updated, or deleted users without having to perform a full read of the entire user collection. By default, is No
.';
+$strings['script_usergroups_delta'] = 'Requête delta pour les groupes d\'utilisateurs';
+$strings['script_usergroups_delta_help'] = 'Get newly created, updated, or deleted groups, including group membership changes, without having to perform a full read of the entire group collection. By default, is No
.';
diff --git a/plugin/azure_active_directory/lang/spanish.php b/plugin/azure_active_directory/lang/spanish.php
index 389a4912e4..4885c5df06 100644
--- a/plugin/azure_active_directory/lang/spanish.php
+++ b/plugin/azure_active_directory/lang/spanish.php
@@ -46,3 +46,7 @@ $strings['tenant_id'] = 'Id. del inquilino';
$strings['tenant_id_help'] = 'Necesario para ejecutar scripts.';
$strings['deactivate_nonexisting_users'] = 'Desactivar usuarios no existentes';
$strings['deactivate_nonexisting_users_help'] = 'Compara los usuarios registrados en Chamilo con los de Azure y desactiva las cuentas en Chamilo que no existan en Azure.';
+$strings['script_users_delta'] = 'Consula delta para usuarios';
+$strings['script_users_delta_help'] = 'Obtiene usuarios recién creados, actualizados o eliminados sin tener que realizar una lectura completa de toda la colección de usuarios. De forma predeterminada, es No
.';
+$strings['script_usergroups_delta'] = 'Consulta delta para grupos de usuarios';
+$strings['script_usergroups_delta_help'] = 'Obtiene grupos recién creados, actualizados o eliminados, incluidos los cambios de membresía del grupo, sin tener que realizar una lectura completa de toda la colección de grupos. De forma predeterminada, es No
';
diff --git a/plugin/azure_active_directory/src/AzureActiveDirectory.php b/plugin/azure_active_directory/src/AzureActiveDirectory.php
index fce9cf9b68..d2493f11be 100644
--- a/plugin/azure_active_directory/src/AzureActiveDirectory.php
+++ b/plugin/azure_active_directory/src/AzureActiveDirectory.php
@@ -1,7 +1,10 @@
'text',
self::SETTING_TENANT_ID => 'text',
self::SETTING_DEACTIVATE_NONEXISTING_USERS => 'boolean',
+ self::SETTING_GET_USERS_DELTA => 'boolean',
+ self::SETTING_GET_USERGROUPS_DELTA => 'boolean',
];
parent::__construct('2.4', 'Angel Fernando Quiroz Campos, Yannick Warnier', $settings);
@@ -131,6 +138,8 @@ class AzureActiveDirectory extends Plugin
/**
* Create extra fields for user when installing.
+ *
+ * @throws ToolsException
*/
public function install()
{
@@ -152,6 +161,35 @@ class AzureActiveDirectory extends Plugin
$this->get_lang('AzureUid'),
''
);
+
+ $em = Database::getManager();
+
+ if ($em->getConnection()->getSchemaManager()->tablesExist(['course_home_notify_notification'])) {
+ return;
+ }
+
+ $schemaTool = new SchemaTool($em);
+ $schemaTool->createSchema(
+ [
+ $em->getClassMetadata(AzureSyncState::class),
+ ]
+ );
+ }
+
+ public function uninstall()
+ {
+ $em = Database::getManager();
+
+ if (!$em->getConnection()->getSchemaManager()->tablesExist(['course_home_notify_notification'])) {
+ return;
+ }
+
+ $schemaTool = new SchemaTool($em);
+ $schemaTool->dropSchema(
+ [
+ $em->getClassMetadata(AzureSyncState::class),
+ ]
+ );
}
public function getExistingUserVerificationOrder(): array
@@ -385,4 +423,27 @@ class AzureActiveDirectory extends Plugin
$extra,
];
}
+
+ public function getSyncState(string $title): ?AzureSyncState
+ {
+ $stateRepo = Database::getManager()->getRepository(AzureSyncState::class);
+
+ return $stateRepo->findOneBy(['title' => $title]);
+ }
+
+ public function saveSyncState(string $title, $value)
+ {
+ $state = $this->getSyncState($title);
+
+ if (!$state) {
+ $state = new AzureSyncState();
+ $state->setTitle($title);
+
+ Database::getManager()->persist($state);
+ }
+
+ $state->setValue($value);
+
+ Database::getManager()->flush();
+ }
}
diff --git a/plugin/azure_active_directory/src/AzureCommand.php b/plugin/azure_active_directory/src/AzureCommand.php
index ce79c45ca2..b07dd79a7f 100644
--- a/plugin/azure_active_directory/src/AzureCommand.php
+++ b/plugin/azure_active_directory/src/AzureCommand.php
@@ -2,6 +2,7 @@
/* For license terms, see /license.txt */
+use Chamilo\PluginBundle\Entity\AzureActiveDirectory\AzureSyncState;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessTokenInterface;
use TheNetworg\OAuth2\Client\Provider\Azure;
@@ -56,11 +57,21 @@ abstract class AzureCommand
'id',
];
- $query = sprintf(
- '$top=%d&$select=%s',
- AzureActiveDirectory::API_PAGE_SIZE,
- implode(',', $userFields)
- );
+ $getUsersDelta = 'true' === $this->plugin->get(AzureActiveDirectory::SETTING_GET_USERS_DELTA);
+
+ if ($getUsersDelta) {
+ $usersDeltaLink = $this->plugin->getSyncState(AzureSyncState::USERS_DATALINK);
+
+ $query = $usersDeltaLink
+ ? $usersDeltaLink->getValue()
+ : sprintf('$select=%s', implode(',', $userFields));
+ } else {
+ $query = sprintf(
+ '$top=%d&$select=%s',
+ AzureActiveDirectory::API_PAGE_SIZE,
+ implode(',', $userFields)
+ );
+ }
$token = null;
@@ -70,7 +81,7 @@ abstract class AzureCommand
try {
$azureUsersRequest = $this->provider->request(
'get',
- "users?$query",
+ $getUsersDelta ? "users/delta?$query" : "users?$query",
$token
);
} catch (Exception $e) {
@@ -80,6 +91,10 @@ abstract class AzureCommand
$azureUsersInfo = $azureUsersRequest['value'] ?? [];
foreach ($azureUsersInfo as $azureUserInfo) {
+ $azureUserInfo['mail'] = $azureUserInfo['mail'] ?? null;
+ $azureUserInfo['surname'] = $azureUserInfo['surname'] ?? null;
+ $azureUserInfo['givenName'] = $azureUserInfo['givenName'] ?? null;
+
yield $azureUserInfo;
}
@@ -89,6 +104,13 @@ abstract class AzureCommand
$hasNextLink = true;
$query = parse_url($azureUsersRequest['@odata.nextLink'], PHP_URL_QUERY);
}
+
+ if ($getUsersDelta && !empty($azureUsersRequest['@odata.deltaLink'])) {
+ $this->plugin->saveSyncState(
+ AzureSyncState::USERS_DATALINK,
+ parse_url($azureUsersRequest['@odata.deltaLink'], PHP_URL_QUERY),
+ );
+ }
} while ($hasNextLink);
}
@@ -105,11 +127,21 @@ abstract class AzureCommand
'description',
];
- $query = sprintf(
- '$top=%d&$select=%s',
- AzureActiveDirectory::API_PAGE_SIZE,
- implode(',', $groupFields)
- );
+ $getUsergroupsDelta = 'true' === $this->plugin->get(AzureActiveDirectory::SETTING_GET_USERGROUPS_DELTA);
+
+ if ($getUsergroupsDelta) {
+ $usergroupsDeltaLink = $this->plugin->getSyncState(AzureSyncState::USERGROUPS_DATALINK);
+
+ $query = $usergroupsDeltaLink
+ ? $usergroupsDeltaLink->getValue()
+ : sprintf('$select=%s', implode(',', $groupFields));
+ } else {
+ $query = sprintf(
+ '$top=%d&$select=%s',
+ AzureActiveDirectory::API_PAGE_SIZE,
+ implode(',', $groupFields)
+ );
+ }
$token = null;
@@ -117,7 +149,11 @@ abstract class AzureCommand
$this->generateOrRefreshToken($token);
try {
- $azureGroupsRequest = $this->provider->request('get', "groups?$query", $token);
+ $azureGroupsRequest = $this->provider->request(
+ 'get',
+ $getUsergroupsDelta ? "groups/delta?$query" : "groups?$query",
+ $token
+ );
} catch (Exception $e) {
throw new Exception('Exception when requesting groups from Azure: '.$e->getMessage());
}
@@ -134,6 +170,13 @@ abstract class AzureCommand
$hasNextLink = true;
$query = parse_url($azureGroupsRequest['@odata.nextLink'], PHP_URL_QUERY);
}
+
+ if ($getUsergroupsDelta && !empty($azureGroupsRequest['@odata.deltaLink'])) {
+ $this->plugin->saveSyncState(
+ AzureSyncState::USERGROUPS_DATALINK,
+ parse_url($azureGroupsRequest['@odata.deltaLink'], PHP_URL_QUERY),
+ );
+ }
} while ($hasNextLink);
}
diff --git a/plugin/azure_active_directory/src/AzureSyncUsersCommand.php b/plugin/azure_active_directory/src/AzureSyncUsersCommand.php
index 36b60f9a2f..72c198ee77 100644
--- a/plugin/azure_active_directory/src/AzureSyncUsersCommand.php
+++ b/plugin/azure_active_directory/src/AzureSyncUsersCommand.php
@@ -15,8 +15,8 @@ class AzureSyncUsersCommand extends AzureCommand
{
yield 'Synchronizing users from Azure.';
- /** @var array $existingUsers */
- $existingUsers = [];
+ /** @var array $azureCreatedUserIdList */
+ $azureCreatedUserIdList = [];
foreach ($this->getAzureUsers() as $azureUserInfo) {
try {
@@ -27,7 +27,7 @@ class AzureSyncUsersCommand extends AzureCommand
continue;
}
- $existingUsers[$azureUserInfo['id']] = $userId;
+ $azureCreatedUserIdList[$azureUserInfo['id']] = $userId;
yield sprintf('User (ID %d) with received info: %s ', $userId, serialize($azureUserInfo));
}
@@ -53,7 +53,7 @@ class AzureSyncUsersCommand extends AzureCommand
$azureGroupMembersUids = array_column($azureGroupMembersInfo, 'id');
foreach ($azureGroupMembersUids as $azureGroupMembersUid) {
- $userId = $existingUsers[$azureGroupMembersUid] ?? null;
+ $userId = $azureCreatedUserIdList[$azureGroupMembersUid] ?? null;
if (!$userId) {
continue;
@@ -72,20 +72,22 @@ class AzureSyncUsersCommand extends AzureCommand
$em->flush();
}
- if ('true' === $this->plugin->get(AzureActiveDirectory::SETTING_DEACTIVATE_NONEXISTING_USERS)) {
+ if ('true' === $this->plugin->get(AzureActiveDirectory::SETTING_DEACTIVATE_NONEXISTING_USERS)
+ && 'true' !== $this->plugin->get(AzureActiveDirectory::SETTING_GET_USERS_DELTA)
+ ) {
yield '----------------';
yield 'Trying deactivate non-existing users in Azure';
$users = UserManager::getRepository()->findByAuthSource('azure');
- $userIdList = array_map(
+ $chamiloUserIdList = array_map(
function ($user) {
return $user->getId();
},
$users
);
- $nonExistingUsers = array_diff($userIdList, $existingUsers);
+ $nonExistingUsers = array_diff($chamiloUserIdList, $azureCreatedUserIdList);
UserManager::deactivate_users($nonExistingUsers);
diff --git a/plugin/azure_active_directory/src/Entity/AzureSyncState.php b/plugin/azure_active_directory/src/Entity/AzureSyncState.php
new file mode 100644
index 0000000000..a61016770b
--- /dev/null
+++ b/plugin/azure_active_directory/src/Entity/AzureSyncState.php
@@ -0,0 +1,74 @@
+id;
+ }
+
+ public function getTitle(): string
+ {
+ return $this->title;
+ }
+
+ public function setTitle(string $title): AzureSyncState
+ {
+ $this->title = $title;
+
+ return $this;
+ }
+
+ public function getValue(): string
+ {
+ return $this->value;
+ }
+
+ public function setValue(string $value): AzureSyncState
+ {
+ $this->value = $value;
+
+ return $this;
+ }
+}
diff --git a/plugin/azure_active_directory/uninstall.php b/plugin/azure_active_directory/uninstall.php
new file mode 100644
index 0000000000..43a1f4fa73
--- /dev/null
+++ b/plugin/azure_active_directory/uninstall.php
@@ -0,0 +1,9 @@
+uninstall();