Skip to content

CO-2962_EnvSource_configuration_should_not_use_Shibboleth_SimpleSAMLphp #369

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/plugins/EnvSource/config/plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"id": {},
"external_identity_source_id": {},
"redirect_on_duplicate": { "type": "url" },
"sp_mode": { "type": "enum" },
"mva_delimiter": { "type": "string", "size": "1" },
"sync_on_login": { "type": "boolean"},
"default_affiliation_type_id": { "type": "integer", "foreignkey": { "table": "types", "column": "id" } },
"address_type_id": { "type": "integer", "foreignkey": { "table": "types", "column": "id" } },
Expand Down
7 changes: 5 additions & 2 deletions app/plugins/EnvSource/resources/locales/en_US/env_source.po
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ msgstr "Source Key {0} is already attached to External Identity {1}"
msgid "field.EnvSources.address_type_id"
msgstr "Address Type"

msgid "field.EnvSources.mva_delimiter_common"
msgstr "Common Delimiters"

msgid "field.EnvSources.default_affiliation_type_id"
msgstr "Default Affiliation Type"

Expand Down Expand Up @@ -145,8 +148,8 @@ msgstr "Path to lookaside file, intended for testing only"
msgid "field.EnvSources.redirect_on_duplicate"
msgstr "Redirect on Duplicate"

msgid "field.EnvSources.sp_mode"
msgstr "Web Server Service Provider"
msgid "field.EnvSources.mva_delimiter"
msgstr "Service Provider Multi Value Delimiter"

msgid "field.EnvSources.sync_on_login"
msgstr "Sync on Login"
Expand Down
27 changes: 9 additions & 18 deletions app/plugins/EnvSource/src/Model/Table/EnvSourcesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -280,21 +280,15 @@ protected function resultToEntityData(

// Email Address
if(!empty($result['env_mail'])) {
$mails = [];

// We accept multiple values if supported by the configured SP software.

switch($EnvSource->sp_mode) {
case EnvSourceSpModeEnum::Shibboleth:
$mails = explode(";", $result['env_mail']);
break;
case EnvSourceSpModeEnum::SimpleSamlPhp:
$mails = explode(",", $result['env_mail']);
break;
default:
// We dont' try to tokenize the string
$mails = [ $result['env_mail' ]];
break;
$delimiter = $EnvSource->mva_delimiter;
$stringValue = $result['env_mail'];

// Check if the delimiter is provided and not an empty string
if (!empty($delimiter)) {
$mails = explode($delimiter, $stringValue);
} else {
$mails = [$stringValue];
}

foreach($mails as $m) {
Expand Down Expand Up @@ -443,10 +437,7 @@ public function validationDefault(Validator $validator): Validator {
]);
$validator->allowEmptyString('redirect_on_duplicate');

$validator->add('sp_mode', [
'content' => ['rule' => ['inList', EnvSourceSpModeEnum::getConstValues()]]
]);
$validator->notEmptyString('sp_mode');
$this->registerStringValidation($validator, $schema, 'mva_delimiter', true);

$validator->add('sync_on_login', [
'content' => ['rule' => 'boolean']
Expand Down
91 changes: 90 additions & 1 deletion app/plugins/EnvSource/templates/EnvSources/fields.inc
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,98 @@
* @license Apache License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0)
*/

use EnvSource\Lib\Enum\EnvSourceSpModeEnum;

// Build the delimiter map dynamically from your $spModes array
$phpDelimiterMap = [];
$selectedValue = '';
foreach ($spModes as $key => $label) {
if ($key === EnvSourceSpModeEnum::Shibboleth) {
$phpDelimiterMap[$key] = ';';
if ($vv_obj?->mva_delimiter == ';') {
$selectedValue = EnvSourceSpModeEnum::Shibboleth;
}
} elseif ($key === EnvSourceSpModeEnum::SimpleSamlPhp) {
$phpDelimiterMap[$key] = ',';
if ($vv_obj?->mva_delimiter == ',') {
$selectedValue = EnvSourceSpModeEnum::SimpleSamlPhp;
}
} elseif ($key === EnvSourceSpModeEnum::Other) {
$phpDelimiterMap[$key] = '';
if (!in_array($vv_obj?->mva_delimiter, ['', ',', ';'])) {
$phpDelimiterMap[$key] = $vv_obj?->mva_delimiter;
$selectedValue = EnvSourceSpModeEnum::Other;
}
}
}

// Encode the array to JSON before inserting it into the Javascript block
$jsonDelimiterMap = json_encode($phpDelimiterMap);


$afterField = <<<JS
<script>
document.addEventListener('DOMContentLoaded', function() {
var select = document.getElementById('mva_delimiter_default');
var textbox = document.getElementById('mva_delimiter');
// Map the dropdown values to actual delimiter characters
var delimiterMap = $jsonDelimiterMap;
select.addEventListener('change', function() {
var selectedValue = select.value;
// Update the textbox with the corresponding delimiter
if (delimiterMap[selectedValue] !== undefined) {
textbox.value = delimiterMap[selectedValue];
} else {
// Fallback in case of unexpected values
textbox.value = selectedValue;
}
// Focus the textbox if "Other" is selected so we can immediately type
if (selectedValue === 'O') {
textbox.focus();
}
});
if (!textbox.value && select.value) {
textbox.value = delimiterMap[select.value] || '';
}
});
</script>
JS;


$fields = [
// 'duplicate_mode',
'sp_mode',
'mva_delimiter' => [
'fieldLabel' => __d('env_source','field.EnvSources.mva_delimiter'),
'groupedControls' => [
'mva_delimiter' => [
'type' => 'text',
'id' => 'mva_delimiter',
'label' => false,
'required' => true,
'class' => 'form-control mb-1',
'singleRowItem' => true,
],
'mva_delimiter_default' => [
'id' => 'mva_delimiter_default',
'label' => [
'text' => __d('env_source','field.EnvSources.mva_delimiter_common'),
'class' => 'mb-2'
],
'name' => 'mva_delimiter_default',
'required' => false,
'empty' => false,
'type' => 'select',
'value' => $selectedValue,
'options' => $spModes,
],
],
'afterField' => $afterField
],
'sync_on_login',
'address_type_id',
'default_affiliation_type_id',
Expand Down
12 changes: 12 additions & 0 deletions app/resources/locales/en_US/information.po
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,24 @@ msgstr "Executing post-database tasks for version {0}"
msgid "ug.tasks.pre"
msgstr "Executing pre-database tasks for version {0}"

msgid "ug.tasks.cacheEnvSourcespMode"
msgstr "Caching EnvSource SP modes"

msgid "ug.tasks.assignEnvSourceMvaDelimiter"
msgstr "Mapping EnvSource SP modes to multi-value delimiters"

msgid "ug.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)"

Expand Down
99 changes: 96 additions & 3 deletions app/src/Command/UpgradeCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

namespace App\Command;

use Cake\Cache\Cache;
use Cake\Console\Arguments;
use Cake\Console\BaseCommand;
use Cake\Console\ConsoleIo;
Expand All @@ -37,13 +38,30 @@
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
{
use \Cake\ORM\Locator\LocatorAwareTrait;

protected $io = null;

/**
* Cache of EnvSource SP Modes.
*
* 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: `[ envSourceId => 'sp_mode_value' ]`
*
* @var array<int, string>
*/
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
Expand Down Expand Up @@ -76,13 +94,15 @@ class UpgradeCommand extends BaseCommand
"5.2.0" => [
'block' => false,
'pre' => [
'checkGroupNames'
'checkGroupNames',
'cacheEnvSourcespMode'
],
'post' => [
'assignUuids',
'buildGroupTree',
'createDefaultGroups',
'installMostlyStaticPages'
'installMostlyStaticPages',
'assignEnvSourceMvaDelimiter',
]
]
];
Expand All @@ -95,7 +115,9 @@ class UpgradeCommand extends BaseCommand
'buildGroupTree' => ['global' => true],
'checkGroupNames' => ['global' => true],
'createDefaultGroups' => ['perCO' => true, 'perCOU' => true],
'installMostlyStaticPages' => ['perCO' => true]
'installMostlyStaticPages' => ['perCO' => true],
'cacheEnvSourcespMode' => ['global' => true],
'assignEnvSourceMvaDelimiter' => ['global' => true]
];

/**
Expand Down Expand Up @@ -235,6 +257,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.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
Expand Down Expand Up @@ -378,6 +413,64 @@ protected function assignUuids() {
}
}


/**
* Cache the SP Mode for all EnvSource instances.
* 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
* @return void
*/
protected function cacheEnvSourcespMode(): void
{
$EnvSourcesTable = $this->getTableLocator()->get('EnvSource.EnvSources');

$this->EnvSourceSpMode = $EnvSourcesTable->find('list', [
'keyField' => 'id',
'valueField' => 'sp_mode'
])
->applyOptions(['archived' => true])
->toArray();
}

/**
* Map cached EnvSource SP Modes to the new mva_delimiter column.
*
* @return void
* @throws \Exception
* @since COmanage Registry v5.2.0
*/
protected function assignEnvSourceMvaDelimiter(): void
{
if (empty($this->EnvSourceSpMode)) {
// Nothing to migrate
return;
}

$EnvSourcesTable = $this->getTableLocator()->get('EnvSource.EnvSources');

// Iterate over the cached id => sp_mode array
foreach ($this->EnvSourceSpMode 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.
*
Expand Down
5 changes: 3 additions & 2 deletions app/src/View/Helper/FieldHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -420,7 +420,7 @@ public function formField(string $fieldName,
string $fieldType = null,
array $fieldSelectOptions = null,
string $fieldNameAlias = null,
bool $labelIsTextOnly = null): string
bool $labelIsTextOnly = null): string
{
$fieldArgs = $fieldOptions ?? [];
$fieldArgs['label'] = $fieldOptions['label'] ?? false;
Expand Down Expand Up @@ -758,7 +758,8 @@ public function restructureFieldArguments(array $fieldArgs) {
'id',
'style',
'checked',
'label'
'label',
'name',
];

// Remove the top-level field options that are intended for Cake, and
Expand Down