// Copyright (C) 2026, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #nullable enable using System; namespace Duplicati.Server; /// /// Defines how the task configuration should be stored in the backup. /// public enum StoreTaskConfigMode { /// /// Automatically determine behavior based on encryption settings. /// When encryption is enabled, behaves as . /// When encryption is not enabled, behaves as . /// Auto, /// /// Do not include any task configuration. /// None, /// /// Include the current job's backup configuration without secrets. /// Self, /// /// Include all job backup configurations without secrets. /// All, /// /// Include the current job's backup configuration with all secrets included. /// SelfWithSecrets, /// /// Include all job backup configurations with all secrets included. /// AllWithSecrets, /// /// Include the current job's backup configuration with all secrets included, even if encryption is disabled. /// SelfWithUnencryptedSecrets, /// /// Include all job backup configurations with all secrets included, even if encryption is disabled. /// AllWithUnencryptedSecrets, } /// /// The resolved mode for storing task configuration. /// /// A value indicating whether to include all tasks. /// A value indicating whether to remove secrets. public record ResolvedTaskConfigMode( bool IncludeAllTasks, bool RemoveSecrets ); /// /// Extension methods for . /// public static class StoreTaskConfigModeExtensions { /// /// The log tag for this class. /// private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(typeof(StoreTaskConfigModeExtensions)); /// /// Resolves the effective mode based on the current mode and encryption settings. /// /// The mode to resolve. /// A flag indicating whether encryption is enabled. /// The effective mode public static ResolvedTaskConfigMode? ResolvedTaskConfigMode(this StoreTaskConfigMode mode, bool encryptionEnabled) { var effectiveMode = mode switch { StoreTaskConfigMode.Auto => encryptionEnabled ? StoreTaskConfigMode.Self : StoreTaskConfigMode.None, _ => mode }; if (!encryptionEnabled && effectiveMode is StoreTaskConfigMode.SelfWithSecrets or StoreTaskConfigMode.AllWithSecrets) { if (effectiveMode is StoreTaskConfigMode.SelfWithSecrets) { effectiveMode = StoreTaskConfigMode.Self; Library.Logging.Log.WriteWarningMessage(LOGTAG, "NotStoringUnencryptedSecrets", null, $"Refusing to store secrets in an unencrypted backup, reverting to {effectiveMode}"); } else if (effectiveMode is StoreTaskConfigMode.AllWithSecrets) { effectiveMode = StoreTaskConfigMode.All; Library.Logging.Log.WriteWarningMessage(LOGTAG, "NotStoringUnencryptedSecrets", null, $"Refusing to store secrets in an unencrypted backup, reverting to {effectiveMode}"); } } if (effectiveMode == StoreTaskConfigMode.None) return null; if (effectiveMode == StoreTaskConfigMode.Auto) throw new InvalidOperationException("Auto mode should have been resolved to a concrete mode"); var removeSecrets = effectiveMode.RemoveSecrets(); var includeAllTasks = effectiveMode.IncludeAllTasks(); // Sanity check: if encryption is disabled, we should not be storing secrets. Effectively unreachable. if (!encryptionEnabled && !removeSecrets) { if (!(effectiveMode is StoreTaskConfigMode.SelfWithUnencryptedSecrets or StoreTaskConfigMode.AllWithUnencryptedSecrets)) throw new InvalidOperationException($"Mode {effectiveMode} is not allowed when encryption is disabled"); } return new ResolvedTaskConfigMode( includeAllTasks, removeSecrets ); } /// /// Determines whether secrets should be removed based on the current mode. /// /// The mode to check. /// true if secrets should be removed; otherwise, false. private static bool RemoveSecrets(this StoreTaskConfigMode mode) => mode switch { StoreTaskConfigMode.SelfWithSecrets or StoreTaskConfigMode.AllWithSecrets or StoreTaskConfigMode.SelfWithUnencryptedSecrets or StoreTaskConfigMode.AllWithUnencryptedSecrets => false, _ => true }; /// /// Determines whether all tasks should be included based on the current mode. /// /// The mode to check. /// true if all tasks should be included; otherwise, false. private static bool IncludeAllTasks(this StoreTaskConfigMode mode) => mode switch { StoreTaskConfigMode.All or StoreTaskConfigMode.AllWithSecrets or StoreTaskConfigMode.AllWithUnencryptedSecrets => true, _ => false }; }