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.");
@@ -3,6 +3,8 @@
- Improved the documentation for IT departments, which now lives in our [wiki](https://github.com/MindWorkAI/AI-Studio/wiki/Enterprise-IT). It is sorted by task, explains the setup on Windows, Linux, and macOS, and offers complete configurations for typical needs, such as using only local models. The link in the app settings leads there, and the wiki also holds the installation guide in English and German.
- Changed the size of AI Studio on laptop screens: it now appears in the same size on every laptop, whatever the scaling of your operating system. If you prefer the previous size, choose "System scaling" for the interface size in the app settings. Contributed by Paul Koudelka.
- Fixed AI Studio becoming unusable when an update failed after its download. The app now restarts with the version you have installed, so you can simply try the update again.
- Fixed configuration plugins you installed yourself being able to change the tool settings of your organization, such as the minimum provider confidence of a tool. These settings now follow the same rules as all other settings, which also gives you back your own values once your organization removes its configuration.
- Fixed the log of AI Studio reporting that a configuration plugin you installed yourself tried to take over a setting of your organization, even when the plugin does not contain that setting.
- Upgraded several libraries to improve security.
- Upgraded Tauri to v2.12.5.
- Upgraded MudBlazor to v9.12.0.