From c17b489326cb5d532c96454eec3a99a96288378f Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Mon, 13 Jul 2026 11:20:41 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=20N=C2=B09772=20-=20Migrating=20token's=20?= =?UTF-8?q?data=20in=20webhooks=20to=20avoid=20setup=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- module.combodo-webhook-integration.php | 85 ++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/module.combodo-webhook-integration.php b/module.combodo-webhook-integration.php index 8215cae..2a85f87 100644 --- a/module.combodo-webhook-integration.php +++ b/module.combodo-webhook-integration.php @@ -72,6 +72,91 @@ public static function BeforeDatabaseCreation(Config $oConfiguration, $sPrevious self::MoveColumnInDB($sTableToRead, 'language', $sTableToSet, 'language', true); SetupLog::Info("| ActionWebhook migration done."); } + + // In combodo-webhook-integration v1.4.2, bug N°7875 led to token format change + if (version_compare($sPreviousVersion, '1.4.2', '<')) { + SetupLog::Info("| Migrate RemoteiTopConnectionToken token format."); + self::MigrateTokenFromEncryptedBlobToString(); + SetupLog::Info("| RemoteiTopConnectionToken migration done."); + } + } + + /** + * Convert token encrypted binary format into clear token strings to prevent new column format from rejecting the data. + * + * @throws Exception When at least one token cannot be migrated safely. + */ + private static function MigrateTokenFromEncryptedBlobToString() + { + if (!MetaModel::DBExists(false)) { + return; + } + + // Note: There is an issue when upgrading, encryption values cannot be retrieved from the passed configuration, we have to read it from the disk + $oDiskConfig = utils::GetConfig(true); + + $sClass = 'RemoteiTopConnectionToken'; + $sTable = MetaModel::DBGetTable($sClass); + $sColumn = MetaModel::GetAttributeDef($sClass, 'token')->Get('sql'); + + if (!CMDBSource::IsTable($sTable) || !CMDBSource::IsField($sTable, $sColumn)) { + SetupLog::Info("| Token migration skipped: table/column not found."); + return; + } + + $sFieldType = strtolower((string) CMDBSource::GetFieldType($sTable, $sColumn)); + // Check if we're dealing with a *blob (tinyblob is expected from the AttibuteEncryptedString definition) + if (strpos($sFieldType, 'blob') === false) { + SetupLog::Info("| Token migration skipped: column already non-binary ({$sFieldType})."); + return; + } + + SetupLog::Info("| Migrating encrypted token payloads from binary column {$sTable}.{$sColumn}."); + $aRows = CMDBSource::QueryToArray("SELECT `id`, `{$sColumn}` FROM `{$sTable}` WHERE `{$sColumn}` IS NOT NULL AND LENGTH(`{$sColumn}`) > 0"); + if (empty($aRows)) { + SetupLog::Info("| Token migration skipped: no non-empty values found."); + return; + } + + // Load SimpleCrypt object to decrypt values + $oSimpleCrypt = new SimpleCrypt($oDiskConfig->GetEncryptionLibrary()); + $sEncryptionKey = $oDiskConfig->GetEncryptionKey(); + + $sDecryptError = Dict::S('Core:AttributeEncryptFailedToDecrypt'); + $iMigrated = 0; + $aFailedIds = []; + + foreach ($aRows as $aRow) { + $iId = (int) $aRow['id']; + $sEncryptedValue = $aRow['token']; + + // Try to decrypt token raw value + try { + $sDecryptedValue = $oSimpleCrypt->Decrypt($sEncryptionKey, $sEncryptedValue); + } catch (Exception $e) { + $aFailedIds[] = $iId; + SetupLog::Error("| Token migration failed for id={$iId}: {$e->getMessage()}"); + continue; + } + + // If the decrypted value equals common error message, consider something went wrong and skip this line + if ($sDecryptedValue === $sDecryptError) { + $aFailedIds[] = $iId; + SetupLog::Error("| Token migration failed for id={$iId}: decryption error."); + continue; + } + + $sUpdateQuery = "UPDATE `{$sTable}` SET `{$sColumn}` = ".CMDBSource::Quote($sDecryptedValue)." WHERE `id` = {$iId}"; + CMDBSource::Query($sUpdateQuery); + $iMigrated++; + } + + // Throw an exception before trying to go forward with the database if we couldn't decipher all values, avoiding MySQL errors + if (!empty($aFailedIds)) { + throw new Exception('| Token migration aborted: unable to decrypt token values for ids '.implode(', ', $aFailedIds).'.'); + } + + SetupLog::Info("| Token migration done: {$iMigrated} row(s) decrypted to clear string."); } } } From 169c7e5c9a065fdd327f71c5d18124fd01c96671 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Wed, 15 Jul 2026 09:32:30 +0200 Subject: [PATCH 2/4] Fix typo --- module.combodo-webhook-integration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.combodo-webhook-integration.php b/module.combodo-webhook-integration.php index 2a85f87..a3138a7 100644 --- a/module.combodo-webhook-integration.php +++ b/module.combodo-webhook-integration.php @@ -105,7 +105,7 @@ private static function MigrateTokenFromEncryptedBlobToString() } $sFieldType = strtolower((string) CMDBSource::GetFieldType($sTable, $sColumn)); - // Check if we're dealing with a *blob (tinyblob is expected from the AttibuteEncryptedString definition) + // Check if we're dealing with a *blob (tinyblob is expected from the AttributeEncryptedString definition) if (strpos($sFieldType, 'blob') === false) { SetupLog::Info("| Token migration skipped: column already non-binary ({$sFieldType})."); return; From ec9978bef507905d8396c3f0e0377b548e04b199 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Wed, 15 Jul 2026 10:10:33 +0200 Subject: [PATCH 3/4] Commit changes after the decryption has been done and validated. Rollback on fail --- module.combodo-webhook-integration.php | 78 +++++++++++++++----------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/module.combodo-webhook-integration.php b/module.combodo-webhook-integration.php index a3138a7..863fa85 100644 --- a/module.combodo-webhook-integration.php +++ b/module.combodo-webhook-integration.php @@ -1,41 +1,43 @@ 'Webhook integrations', 'category' => 'business', // Setup // - 'dependencies' => array( + 'dependencies' => [ 'itop-structure/3.2.0', 'itop-attribute-encrypted-password/1.0.0', 'combodo-oauth2-client/1.0.0', - ), + ], 'mandatory' => false, 'visible' => true, 'installer' => 'WebhookIntegrationInstaller', // Components // - 'datamodel' => array( + 'datamodel' => [ // Extension autoloader 'vendor/autoload.php', // Explicitly load DM classes 'SendWebRequest.php', 'model.combodo-webhook-integration.php', - ), - 'webservice' => array(), - 'data.struct' => array( + ], + 'webservice' => [], + 'data.struct' => [ 'data.struct.remote_application_type.xml', - ), - 'data.sample' => array(), + ], + 'data.sample' => [], // Documentation // @@ -44,12 +46,11 @@ // Default settings // - 'settings' => array(), - ) + 'settings' => [], + ] ); -if (!class_exists('WebhookIntegrationInstaller')) -{ +if (!class_exists('WebhookIntegrationInstaller')) { // Module installation handler // class WebhookIntegrationInstaller extends ModuleInstallerAPI @@ -73,16 +74,16 @@ public static function BeforeDatabaseCreation(Config $oConfiguration, $sPrevious SetupLog::Info("| ActionWebhook migration done."); } - // In combodo-webhook-integration v1.4.2, bug N°7875 led to token format change - if (version_compare($sPreviousVersion, '1.4.2', '<')) { - SetupLog::Info("| Migrate RemoteiTopConnectionToken token format."); - self::MigrateTokenFromEncryptedBlobToString(); - SetupLog::Info("| RemoteiTopConnectionToken migration done."); - } + // In combodo-webhook-integration v1.4.2, bug N°7875 led to token format change + if (version_compare($sPreviousVersion, '1.4.2', '<')) { + SetupLog::Info("| Migrate RemoteiTopConnectionToken token format."); + self::MigrateTokenFromEncryptedBlobToString(); + SetupLog::Info("| RemoteiTopConnectionToken migration done."); + } } /** - * Convert token encrypted binary format into clear token strings to prevent new column format from rejecting the data. + * Convert token encrypted binary format into clear token strings to prevent new column format from rejecting the data. * * @throws Exception When at least one token cannot be migrated safely. */ @@ -92,10 +93,10 @@ private static function MigrateTokenFromEncryptedBlobToString() return; } - // Note: There is an issue when upgrading, encryption values cannot be retrieved from the passed configuration, we have to read it from the disk - $oDiskConfig = utils::GetConfig(true); + // Note: There is an issue when upgrading, encryption values cannot be retrieved from the passed configuration, we have to read it from the disk + $oDiskConfig = utils::GetConfig(true); - $sClass = 'RemoteiTopConnectionToken'; + $sClass = 'RemoteiTopConnectionToken'; $sTable = MetaModel::DBGetTable($sClass); $sColumn = MetaModel::GetAttributeDef($sClass, 'token')->Get('sql'); @@ -105,7 +106,7 @@ private static function MigrateTokenFromEncryptedBlobToString() } $sFieldType = strtolower((string) CMDBSource::GetFieldType($sTable, $sColumn)); - // Check if we're dealing with a *blob (tinyblob is expected from the AttributeEncryptedString definition) + // Check if we're dealing with a *blob (tinyblob is expected from the AttributeEncryptedString definition) if (strpos($sFieldType, 'blob') === false) { SetupLog::Info("| Token migration skipped: column already non-binary ({$sFieldType})."); return; @@ -118,19 +119,20 @@ private static function MigrateTokenFromEncryptedBlobToString() return; } - // Load SimpleCrypt object to decrypt values + // Load SimpleCrypt object to decrypt values $oSimpleCrypt = new SimpleCrypt($oDiskConfig->GetEncryptionLibrary()); $sEncryptionKey = $oDiskConfig->GetEncryptionKey(); $sDecryptError = Dict::S('Core:AttributeEncryptFailedToDecrypt'); $iMigrated = 0; + $aUpdatedValues = []; $aFailedIds = []; foreach ($aRows as $aRow) { $iId = (int) $aRow['id']; $sEncryptedValue = $aRow['token']; - // Try to decrypt token raw value + // Try to decrypt token raw value try { $sDecryptedValue = $oSimpleCrypt->Decrypt($sEncryptionKey, $sEncryptedValue); } catch (Exception $e) { @@ -139,23 +141,35 @@ private static function MigrateTokenFromEncryptedBlobToString() continue; } - // If the decrypted value equals common error message, consider something went wrong and skip this line + // If the decrypted value equals common error message, consider something went wrong and skip this line if ($sDecryptedValue === $sDecryptError) { $aFailedIds[] = $iId; SetupLog::Error("| Token migration failed for id={$iId}: decryption error."); continue; } - $sUpdateQuery = "UPDATE `{$sTable}` SET `{$sColumn}` = ".CMDBSource::Quote($sDecryptedValue)." WHERE `id` = {$iId}"; - CMDBSource::Query($sUpdateQuery); - $iMigrated++; + $aUpdatedValues[$iId] = $sDecryptedValue; } - // Throw an exception before trying to go forward with the database if we couldn't decipher all values, avoiding MySQL errors + // Throw an exception before trying to go forward with the database if we couldn't decipher all values, avoiding MySQL errors if (!empty($aFailedIds)) { throw new Exception('| Token migration aborted: unable to decrypt token values for ids '.implode(', ', $aFailedIds).'.'); } + CMDBSource::Query('START TRANSACTION'); + try { + foreach ($aUpdatedValues as $iId => $sDecryptedValue) { + CMDBSource::Query( + "UPDATE `{$sTable}` SET `{$sColumn}` = ".CMDBSource::Quote($sDecryptedValue)." WHERE `id` = {$iId}" + ); + $iMigrated++; + } + CMDBSource::Query('COMMIT'); + } catch (Exception $e) { + CMDBSource::Query('ROLLBACK'); + throw new Exception('| Token migration aborted: unable to insert updated data into database column: '.$e->getMessage().'.'); + } + SetupLog::Info("| Token migration done: {$iMigrated} row(s) decrypted to clear string."); } } From 2d18cdbf6be884a9b3eac50ad7ed141a62db75f9 Mon Sep 17 00:00:00 2001 From: Stephen Abello Date: Wed, 15 Jul 2026 11:25:35 +0200 Subject: [PATCH 4/4] Use variable instead of hardcoded value --- module.combodo-webhook-integration.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/module.combodo-webhook-integration.php b/module.combodo-webhook-integration.php index 863fa85..512faca 100644 --- a/module.combodo-webhook-integration.php +++ b/module.combodo-webhook-integration.php @@ -130,7 +130,7 @@ private static function MigrateTokenFromEncryptedBlobToString() foreach ($aRows as $aRow) { $iId = (int) $aRow['id']; - $sEncryptedValue = $aRow['token']; + $sEncryptedValue = $aRow[$sColumn]; // Try to decrypt token raw value try {