diff --git a/module.combodo-webhook-integration.php b/module.combodo-webhook-integration.php index 8215cae..512faca 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 @@ -72,6 +73,104 @@ 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 AttributeEncryptedString 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; + $aUpdatedValues = []; + $aFailedIds = []; + + foreach ($aRows as $aRow) { + $iId = (int) $aRow['id']; + $sEncryptedValue = $aRow[$sColumn]; + + // 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; + } + + $aUpdatedValues[$iId] = $sDecryptedValue; + } + + // 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."); } } }