Fixed local configuration plugins overriding the tool settings of an organization (#1139)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-10-31 13:50:00 +01:00
1 parent eef475581c
commit 2c2ad0dfde
5 files changed
+579 -80

No files matched your search

@@ -830,7 +830,7 @@ public static partial class ManagedConfiguration
var settingName = SettingName(propertyExpression);
var managedMode = ReadManagedConfigurationMode(propertyExpression, settings);
return HandleParsedDictionaryValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
return HandleParsedScalarValue(configPluginId, dryRun, successful, configMeta, configuredValue, managedMode, settingName);
}
/// <summary>
@@ -1080,9 +1080,14 @@ public static partial class ManagedConfiguration
if(dryRun)
return successful;
//
// The setting might belong to the IT department of an organization. In that case, no local
// configuration plugin may touch it, no matter what it declares:
if (!MayManageSetting(configPluginId, configMeta))
// configuration plugin may touch it, no matter what it declares. A plugin which does not
// declare the setting touches nothing below unless it owns the setting, so we ask only when it
// declares the setting. Otherwise, the log would report every local plugin for every setting
// of the organization:
//
if (successful && !MayManageSetting(configPluginId, configMeta))
return false;
//
@@ -1141,9 +1146,14 @@ public static partial class ManagedConfiguration
if (dryRun)
return successful;
//
// The setting might belong to the IT department of an organization. In that case, no local
// configuration plugin may touch it, no matter what it declares:
if (!MayManageSetting(configPluginId, configMeta))
// configuration plugin may touch it, no matter what it declares. A plugin which does not
// declare the setting touches nothing below unless it owns the setting, so we ask only when it
// declares the setting. Otherwise, the log would report every local plugin for every setting
// of the organization:
//
if (successful && !MayManageSetting(configPluginId, configMeta))
return false;
//
@@ -1204,67 +1214,6 @@ public static partial class ManagedConfiguration
return successful;
}
private static bool HandleParsedDictionaryValue<TClass>(
Guid configPluginId,
bool dryRun,
bool successful,
ConfigMeta<TClass, IDictionary<string, string>> configMeta,
IDictionary<string, string> configuredValue,
ManagedConfigurationMode managedMode,
string settingName)
{
if (dryRun)
return successful;
switch (successful)
{
case true when managedMode is ManagedConfigurationMode.LOCKED:
ClearEditableDefaultState(settingName);
configMeta.ClearEditableDefaultConfiguration();
configMeta.SetValue(CloneStringDictionary(configuredValue));
configMeta.LockConfiguration(configPluginId);
break;
case true when managedMode is ManagedConfigurationMode.EDITABLE_DEFAULT:
var currentValueSerialized = SerializeManagedStringDictionaryValue(configMeta.GetValue());
var configuredValueSerialized = SerializeManagedStringDictionaryValue(configuredValue);
string lastAppliedValue;
if (!TryGetEditableDefaultState(settingName, out var editableDefaultState))
{
configMeta.SetValue(CloneStringDictionary(configuredValue));
lastAppliedValue = configuredValueSerialized;
}
else
{
lastAppliedValue = editableDefaultState.LastAppliedValue;
if (string.Equals(currentValueSerialized, lastAppliedValue, StringComparison.Ordinal))
{
configMeta.SetValue(CloneStringDictionary(configuredValue));
lastAppliedValue = configuredValueSerialized;
}
}
SetEditableDefaultState(settingName, configPluginId, lastAppliedValue);
configMeta.UnlockConfiguration();
configMeta.SetEditableDefaultConfiguration(configPluginId);
break;
case false when configMeta.IsLocked && configMeta.LockedByConfigPluginId == configPluginId:
configMeta.ResetLockedConfiguration();
break;
case false when configMeta.ManagedMode is ManagedConfigurationMode.EDITABLE_DEFAULT
&& TryGetEditableDefaultState(settingName, out var editableDefaultStateToRemove)
&& editableDefaultStateToRemove.ConfigPluginId == configPluginId:
configMeta.ClearEditableDefaultConfiguration();
ClearEditableDefaultState(settingName);
break;
}
return successful;
}
private static ManagedConfigurationMode ReadManagedConfigurationMode<TClass, TValue>(
Expression<Func<TClass, TValue>> propertyExpression,
LuaTable settings)
@@ -1286,6 +1235,17 @@ public static partial class ManagedConfiguration
{
null => string.Empty,
string text => text,
//
// Tables of texts, i.e., the tool tables, keep the form they were stored in before they shared
// this method with the other settings. The settings of installed versions hold the last applied
// editable default in that form, and any other form would make each of them look as if the user
// had changed it, so that the organization could never update it again:
//
IDictionary<string, string> texts => string.Join("\n", texts
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => $"{pair.Key}={pair.Value}")),
System.Collections.IDictionary dictionary => string.Join(";", dictionary.Keys
.Cast<object>()
.OrderBy(key => key.ToString(), StringComparer.Ordinal)
@@ -1300,10 +1260,4 @@ public static partial class ManagedConfiguration
};
private static Dictionary<string, string> CloneStringDictionary(IDictionary<string, string> values) => new(values, StringComparer.Ordinal);
private static string SerializeManagedStringDictionaryValue(IDictionary<string, string> values) => string.Join(
"\n",
values
.OrderBy(pair => pair.Key, StringComparer.Ordinal)
.Select(pair => $"{pair.Key}={pair.Value}"));
}
@@ -14,6 +14,17 @@ public static partial class ManagedConfiguration
private static ILogger Log => Program.LOGGER_FACTORY.CreateLogger(nameof(ManagedConfiguration));
/// <summary>
/// Decides whether a configuration plugin speaks for an organization.
/// </summary>
/// <remarks>
/// The plugin factory decides this by the directories of a running app, which a test does not
/// have. Tests therefore replace this for their duration and restore it afterward, so that they
/// can check what an organization owns against a local configuration plugin. The app itself
/// never replaces it.
/// </remarks>
internal static Func<Guid, bool> IsOrganizationPlugin { get; set; } = PluginFactory.IsOrganizationConfigurationPlugin;
/// <summary>
/// All registered settings, in no particular order.
/// </summary>
@@ -354,10 +365,10 @@ public static partial class ManagedConfiguration
if (owningConfigPluginId == Guid.Empty || owningConfigPluginId == configPluginId)
return true;
if (!PluginFactory.IsOrganizationConfigurationPlugin(owningConfigPluginId))
if (!IsOrganizationPlugin(owningConfigPluginId))
return true;
if (PluginFactory.IsOrganizationConfigurationPlugin(configPluginId))
if (IsOrganizationPlugin(configPluginId))
return true;
Log.LogWarning($"The configuration plugin '{configPluginId}' tried to manage the setting '{configMeta.SettingName}', which is managed by the configuration plugin '{owningConfigPluginId}' of your organization. Ignoring the attempt: configurations deployed by your organization's IT take precedence.");