From 961a30a46fd13721e432ec447452b765bb9d1982 Mon Sep 17 00:00:00 2001 From: Ioannis Igoumenos Date: Sun, 15 Mar 2026 15:41:01 +0000 Subject: [PATCH] Add upgrade task --- .../EnvSource/templates/EnvSources/fields.inc | 2 +- app/resources/locales/en_US/information.po | 6 + app/src/Command/UpgradeCommand.php | 107 +++++++++++++++++- 3 files changed, 111 insertions(+), 4 deletions(-) diff --git a/app/plugins/EnvSource/templates/EnvSources/fields.inc b/app/plugins/EnvSource/templates/EnvSources/fields.inc index f0e468457..5bcbf00bc 100644 --- a/app/plugins/EnvSource/templates/EnvSources/fields.inc +++ b/app/plugins/EnvSource/templates/EnvSources/fields.inc @@ -43,7 +43,7 @@ foreach ($spModes as $key => $label) { } } elseif ($key === EnvSourceSpModeEnum::Other) { $phpDelimiterMap[$key] = ''; - if ($vv_obj?->mva_delimiter != '') { + if (!in_array($vv_obj?->mva_delimiter, ['', ',', ';'])) { $phpDelimiterMap[$key] = $vv_obj?->mva_delimiter; $selectedValue = EnvSourceSpModeEnum::Other; } diff --git a/app/resources/locales/en_US/information.po b/app/resources/locales/en_US/information.po index 9d2120a65..ff4d639f9 100644 --- a/app/resources/locales/en_US/information.po +++ b/app/resources/locales/en_US/information.po @@ -183,12 +183,18 @@ msgstr "Executing post-database tasks for version {0}" msgid "ug.tasks.pre" msgstr "Executing pre-database tasks for version {0}" +msgid "ug.tasks.schema.reload" +msgstr "Force a global schema reload so that post-upgrade tasks are aware of the new schema" + msgid "ug.version.current" msgstr "Current version: {0}" msgid "ug.version.target" msgstr "Target version: {0}" +msgid "ug.tasks.assignEnvSourceMvaDelimiter.co" +msgstr "Map the EnvSource SP Mode to Multi Value Delimitier for CO {0}" + msgid "ug.tasks.assignUuids" msgstr "Assigning UUIDs (this may take a while for larger deployments)" diff --git a/app/src/Command/UpgradeCommand.php b/app/src/Command/UpgradeCommand.php index 174674d0b..558056ae1 100644 --- a/app/src/Command/UpgradeCommand.php +++ b/app/src/Command/UpgradeCommand.php @@ -29,6 +29,7 @@ namespace App\Command; +use Cake\Cache\Cache; use Cake\Console\Arguments; use Cake\Console\BaseCommand; use Cake\Console\ConsoleIo; @@ -37,6 +38,8 @@ use \App\Lib\Enum\GroupTypeEnum; use \App\Lib\Util\PaginatedSqlIterator; use \App\Lib\Util\SearchUtilities; +use Cake\ORM\TableRegistry; +use EnvSource\Lib\Enum\EnvSourceSpModeEnum; class UpgradeCommand extends BaseCommand { @@ -44,6 +47,20 @@ class UpgradeCommand extends BaseCommand protected $io = null; + /** + * Cache of EnvSource SP Modes partitioned by CO ID. + * + * This array temporarily stores the `sp_mode` values (e.g., 'SH', 'SS', 'O') + * from the database prior to schema migration. It allows post-migration tasks + * to accurately map the deprecated `sp_mode` column data into the new + * `mva_delimiter` column structure. + * + * Format: `[ coId => [ envSourceId => 'sp_mode_value' ] ]` + * + * @var array> + */ + private array $EnvSourceSpMode = []; + // A list of known versions, must be semantic versioning compliant. The value // is a "blocker" if it is a version that prevents an upgrade from happening. // For example, if a user attempts to upgrade from 1.0.0 to 1.2.0, and 1.1.0 @@ -76,13 +93,15 @@ class UpgradeCommand extends BaseCommand "5.2.0" => [ 'block' => false, 'pre' => [ - 'checkGroupNames' + 'checkGroupNames', + 'cacheEnvSourcespMode' ], 'post' => [ 'assignUuids', 'buildGroupTree', 'createDefaultGroups', - 'installMostlyStaticPages' + 'installMostlyStaticPages', + 'assignEnvSourceMvaDelimiter', ] ] ]; @@ -95,7 +114,9 @@ class UpgradeCommand extends BaseCommand 'buildGroupTree' => ['global' => true], 'checkGroupNames' => ['global' => true], 'createDefaultGroups' => ['perCO' => true, 'perCOU' => true], - 'installMostlyStaticPages' => ['perCO' => true] + 'installMostlyStaticPages' => ['perCO' => true], + 'cacheEnvSourcespMode' => ['perCO' => true], + 'assignEnvSourceMvaDelimiter' => ['perCO' => true] ]; /** @@ -235,6 +256,19 @@ public function execute(Arguments $args, ConsoleIo $io) if(!$args->getOption('skipdatabase')) { // Call database command $this->executeCommand(DatabaseCommand::class); + + // Force a global schema reload so that post-upgrade tasks are aware of the new schema + $this->io->out(__d('information', 'ug.tasks.schema.reload')); + + // Clear all loaded table instances so they are re-instantiated with the new schema + TableRegistry::getTableLocator()->clear(); + + // Clear the CakePHP file/redis cache for models (this holds the schema arrays) + Cache::clear('_cake_model_'); + + // Disable schema caching for this specific process going forward + $connection = ConnectionManager::get('default'); + $connection->cacheMetadata(false); } // Run appropriate post-database steps @@ -378,6 +412,73 @@ protected function assignUuids() { } } + + /** + * Cache the SP Mode for EnvSource instances associated with a given CO. + * This is used prior to database migrations where the sp_mode column is + * removed or altered, allowing post-migration tasks to map the old value + * to the new schema structure (e.g., mva_delimiter). + * + * @since COmanage Registry v5.2.0 + * @param int $coId CO ID to cache the configurations for + * @return void + */ + protected function cacheEnvSourcespMode(int $coId): void + { + $MspsTable = $this->getTableLocator()->get('EnvSource.EnvSources'); + + $modes = $MspsTable->find('list', [ + 'keyField' => 'id', + 'valueField' => 'sp_mode' + ]) + ->innerJoinWith('ExternalIdentitySources') + ->where(['ExternalIdentitySources.co_id' => $coId]) + ->applyOptions(['archived' => true]) + ->toArray(); + + $this->EnvSourceSpMode[$coId] = $modes; + } + + /** + * Cache the SP Mode for EnvSource instances associated with a given CO. + * This is used prior to database migrations where the sp_mode column is + * removed or altered, allowing post-migration tasks to map the old value + * to the new schema structure (e.g., mva_delimiter). + * + * @param int $coId CO ID to cache the configurations for + * @return void + * @throws \Exception + * @since COmanage Registry v5.2.0 + */ + protected function assignEnvSourceMvaDelimiter(int $coId): void + { + if (empty($this->EnvSourceSpMode[$coId])) { + // Nothing to migrate for this CO + return; + } + + $EnvSourcesTable = $this->getTableLocator()->get('EnvSource.EnvSources'); + + // Iterate over the cached id => sp_mode array for this CO + foreach ($this->EnvSourceSpMode[$coId] as $envSourceId => $spMode) { + $delimiter = ';'; + + // Map the old SP Mode to the new multi-value delimiter + if ($spMode === EnvSourceSpModeEnum::Shibboleth) { + $delimiter = ';'; + } elseif ($spMode === EnvSourceSpModeEnum::SimpleSamlPhp) { + $delimiter = ','; + } + + // Use updateAll to execute a raw SQL UPDATE. + // This bypasses the ORM's beforeSave event and ChangelogBehavior's lock. + $EnvSourcesTable->updateAll( + ['mva_delimiter' => $delimiter], // Fields to update + ['id' => $envSourceId] // Conditions + ); + } + } + /** * Establish tree metadata for Groups. *