Improved enterprise plugin rollouts and protected deployed plugins against changes (#938)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-08-28 14:06:24 +02:00
1 parent a4c35fc2f2
commit 3940ad7f29
11 files changed
+232 -54

No files matched your search

@@ -176,13 +176,22 @@ public sealed partial class PluginInstallService
return TB("The plugin has no local directory.");
//
// We decide by the plugin path, not by what a plugin declares about itself. Both
// DEPLOYED_USING_CONFIG_SERVER and the Assistant Builder metadata are self-declared: a
// locally placed plugin could claim to be deployed by an organization, or simply omit the
// builder metadata, and would then be impossible to remove through the user interface, which
// is exactly the situation this deletion is meant to resolve.
// Nothing an organization rolled out belongs to the user, so none of it may be removed here.
// The plugin path is the primary criterion and covers every plugin type: a deployed
// configuration, a test configuration staged for it, and every plugin an organization ships
// alongside them in a subdirectory.
//
if (PluginFactory.IsEnterpriseConfigurationPath(plugin.LocalPath))
if (PluginFactory.IsOrganizationConfigurationPath(plugin.LocalPath))
return TB("Plugins deployed by your organization cannot be deleted.");
//
// Organizations also roll plugins out past these directories, e.g. through their MDM
// solution. DEPLOYED_USING_CONFIG_SERVER is the only marker such a plugin has, so we honor
// it here just like sharing, replacing, and revising already do. A plugin cannot acquire the
// flag by accident: an archive declaring it is refused on import, which leaves deliberate
// manual placement as the only way in, and the file system as the way back out.
//
if (plugin.IsManagedByConfigServer)
return TB("Plugins deployed by your organization cannot be deleted.");
if (!PluginFactory.IsInsidePluginsRoot(plugin.LocalPath) || PluginFactory.IsPluginsRoot(plugin.LocalPath))
@@ -21,6 +21,14 @@ public sealed partial class PluginInstallService
if (plugin.IsInternal)
return CheckError(TB("Internal assistant plugins cannot be edited."));
//
// An assistant an organization rolled out is theirs to change, not the user's. Editing it
// would also change its content hash, which is what an enterprise approval is based on: the
// assistant would lose its approval and suddenly demand a security audit.
//
if (plugin.IsManagedByConfigServer)
return CheckError(TB("Only locally managed assistant plugins can be edited."));
if (string.IsNullOrWhiteSpace(plugin.LocalPath))
return CheckError(TB("The assistant plugin has no local directory."));
@@ -76,6 +84,11 @@ public sealed partial class PluginInstallService
if (plugin.IsInternal)
return UpdateError(plugin, plugin.LocalPath, TB("Internal assistant plugins cannot be edited."));
// See CheckInstalledAssistantUpdateAsync: an organization's assistant must keep the content
// its enterprise approval was granted for.
if (plugin.IsManagedByConfigServer)
return UpdateError(plugin, plugin.LocalPath, TB("Only locally managed assistant plugins can be edited."));
if (string.IsNullOrWhiteSpace(plugin.LocalPath))
return UpdateError(plugin, string.Empty, TB("The assistant plugin has no local directory."));