Files
duplicati/Duplicati/GUI/Duplicati.GUI.TrayIcon/PasswordStorageHelper.cs
T
2025-12-08 11:33:28 +01:00

132 lines
5.3 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Duplicati.Library.Interface;
using Duplicati.Library.Main;
#nullable enable
namespace Duplicati.GUI.TrayIcon;
public class PasswordStorageHelper
{
private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType<PasswordStorageHelper>();
private const string HOSTURL_SECRET_NAME = "duplicati-trayicon-hosturl";
private const string PASSWORD_SECRET_NAME = "duplicati-trayicon-password";
private readonly ISecretProvider? m_secretProvider;
private string m_hostUrl;
private string m_password;
private readonly TaskCompletionSource<bool> m_passwordUpdatedTcs = new TaskCompletionSource<bool>();
private readonly Program.PasswordSource m_passwordSource;
public event EventHandler? OnPasswordChanged;
public bool IsSetSupported => m_secretProvider?.IsSetSupported ?? false;
public string? HostUrl => m_hostUrl;
public string? Password => m_password;
public bool ShouldSavePassword { get; private set; }
public bool NeedsPasswordPrompt => m_passwordSource == Program.PasswordSource.SuppliedPassword && string.IsNullOrWhiteSpace(m_password);
public static async Task<PasswordStorageHelper> CreateAsync(string? hostUrl, bool customUrl, string? password, Program.PasswordSource passwordSource, Dictionary<string, string?> options)
{
var secretProvider = await SecretProviderHelper.GetDefaultSecretProvider(options, CancellationToken.None);
// If we get the password from the secret provider, we should save it back to the secret provider
var shouldSavePassword = false;
if (secretProvider != null && passwordSource == Program.PasswordSource.SuppliedPassword && (string.IsNullOrWhiteSpace(hostUrl) || !customUrl || string.IsNullOrWhiteSpace(password)))
{
if (string.IsNullOrWhiteSpace(hostUrl) || !customUrl)
{
try
{
hostUrl = await secretProvider.ResolveSecretAsync(HOSTURL_SECRET_NAME, CancellationToken.None).ConfigureAwait(false);
shouldSavePassword = true;
}
catch (Exception ex)
{
Library.Logging.Log.WriteInformationMessage(LOGTAG, "SecretProviderFailedToGetEncryptionKey", $"Failed to get stored configuration \"{HOSTURL_SECRET_NAME}\": {ex.Message}");
}
}
if (string.IsNullOrWhiteSpace(password))
{
try
{
password = await secretProvider.ResolveSecretAsync(PASSWORD_SECRET_NAME, CancellationToken.None).ConfigureAwait(false);
shouldSavePassword = true;
}
catch (Exception ex)
{
Library.Logging.Log.WriteInformationMessage(LOGTAG, "SecretProviderFailedToGetEncryptionKey", $"Failed to get stored configuration \"{PASSWORD_SECRET_NAME}\": {ex.Message}");
}
}
}
return new PasswordStorageHelper(secretProvider, hostUrl, password, passwordSource, shouldSavePassword);
}
private PasswordStorageHelper(ISecretProvider? secretProvider, string? hostUrl, string? password, Program.PasswordSource passwordSource, bool shouldSavePassword)
{
m_secretProvider = secretProvider;
m_hostUrl = hostUrl ?? string.Empty;
m_password = password ?? string.Empty;
m_passwordSource = passwordSource;
ShouldSavePassword = shouldSavePassword;
if (!string.IsNullOrWhiteSpace(m_password))
m_passwordUpdatedTcs.TrySetResult(true);
}
public Task WaitForPasswordUpdateAsync(CancellationToken cancellationToken)
=> m_passwordUpdatedTcs.Task.WaitAsync(cancellationToken);
public async Task<bool> UpdatePasswordAsync(string? hostUrl, string? password, bool save, CancellationToken cancellationToken)
{
var res = false;
if (m_secretProvider != null && IsSetSupported && save)
{
try
{
if (!string.IsNullOrWhiteSpace(hostUrl) && hostUrl != m_hostUrl)
{
await m_secretProvider.SetSecretAsync(
HOSTURL_SECRET_NAME,
hostUrl ?? string.Empty,
true,
cancellationToken).ConfigureAwait(false);
}
if (!string.IsNullOrWhiteSpace(password) && password != m_password)
{
await m_secretProvider.SetSecretAsync(
PASSWORD_SECRET_NAME,
password ?? string.Empty,
true,
cancellationToken).ConfigureAwait(false);
}
res = true;
ShouldSavePassword = true;
}
catch (Exception ex)
{
Library.Logging.Log.WriteWarningMessage(LOGTAG, "PasswordSaveFailed", ex, "Failed to save configuration");
}
}
m_hostUrl = hostUrl ?? string.Empty;
m_password = password ?? string.Empty;
m_passwordUpdatedTcs.TrySetResult(true);
OnPasswordChanged?.Invoke(this, EventArgs.Empty);
return res;
}
}