diff --git a/Duplicati/WebserverCore/Dto/V2/DestinationTestRequestDto.cs b/Duplicati/WebserverCore/Dto/V2/DestinationTestRequestDto.cs
index 359e6213c..61075c60b 100644
--- a/Duplicati/WebserverCore/Dto/V2/DestinationTestRequestDto.cs
+++ b/Duplicati/WebserverCore/Dto/V2/DestinationTestRequestDto.cs
@@ -53,6 +53,10 @@ public sealed record DestinationTestRequestDto
/// The backup ID, if known
///
public string? BackupId { get; init; }
+ ///
+ /// The connection string ID, if known
+ ///
+ public long? ConnectionStringId { get; init; }
///
/// Any additional options to pass to the destination
diff --git a/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs
index 1e4da6306..be5baf572 100644
--- a/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs
+++ b/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs
@@ -67,9 +67,9 @@ public class SharedRemoteOperation
return modules;
}
- public static async Task<(string Url, Dictionary Options)> ExpandUrl(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
+ public static async Task<(string Url, Dictionary Options)> ExpandUrl(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
- url = UnmaskUrl(connection, url, backupId);
+ url = UnmaskUrl(connection, url, backupId, connectionStringId);
var uri = new Library.Utility.Uri(url);
var opts = ParseUrlOptions(connection, uri);
@@ -88,26 +88,26 @@ public class SharedRemoteOperation
return (url, opts);
}
- public static async Task GetBackend(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
+ public static async Task GetBackend(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
- (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
+ (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var backend = Library.DynamicLoader.BackendLoader.GetBackend(url, opts);
return new BackendTupleDisposeWrapper(backend, modules);
}
- public static async Task GetSourceProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
+ public static async Task GetSourceProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
- (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
+ (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var sourceProvider = await Library.DynamicLoader.SourceProviderLoader.GetSourceProviderForTesting(url, "", opts, cancelToken);
return new SourceProviderTupleDisposeWrapper(sourceProvider, modules);
}
- public static async Task GetRestoreDestinationProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, CancellationToken cancelToken)
+ public static async Task GetRestoreDestinationProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken)
{
- (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, cancelToken);
+ (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, cancelToken);
var modules = ConfigureModules(opts);
var restoreDestinationProvider = await Library.DynamicLoader.RestoreDestinationProviderLoader.GetRestoreDestinationProviderForTesting(url, opts, cancelToken);
@@ -132,9 +132,17 @@ public class SharedRemoteOperation
}
}
- private static string UnmaskUrl(Connection connection, string maskedurl, string? backupId)
+ private static string UnmaskUrl(Connection connection, string maskedurl, string? backupId, long connectionStringId)
{
- var previousUrl = !string.IsNullOrWhiteSpace(backupId) ? connection.GetBackup(backupId)?.TargetURL : null;
+
+ string? previousUrl = null;
+
+ if (!string.IsNullOrWhiteSpace(backupId))
+ previousUrl = connection.GetBackup(backupId)?.TargetURL;
+
+ if (string.IsNullOrWhiteSpace(previousUrl) && connectionStringId > 0)
+ previousUrl = connection.GetConnectionString(connectionStringId)?.BaseUrl;
+
var unmasked = string.IsNullOrWhiteSpace(previousUrl)
? maskedurl
: QuerystringMasking.Unmask(maskedurl, previousUrl);
diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
index ed66e6eef..522d076e7 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
@@ -31,7 +31,7 @@ namespace Duplicati.WebserverCore.Endpoints.V1
{
public class RemoteOperation : IEndpointV1
{
- private record RemoteOperationInput(string path, string? backupId);
+ private record RemoteOperationInput(string path, string? backupId, long? connectionStringId);
public static void Map(RouteGroupBuilder group)
{
@@ -40,11 +40,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1
.RequireAuthorization();
group.MapPost("/remoteoperation/test", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromQuery] bool? autocreate, [FromQuery] Dto.V2.RemoteDestinationType? type, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
- => ExecuteTest(connection, applicationSettings, input.path, input.backupId, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken))
+ => ExecuteTest(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken))
.RequireAuthorization();
group.MapPost("/remoteoperation/create", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
- => ExecuteCreate(connection, applicationSettings, input.path, input.backupId, cancelToken))
+ => ExecuteCreate(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, cancelToken))
.RequireAuthorization();
}
@@ -54,25 +54,25 @@ namespace Duplicati.WebserverCore.Endpoints.V1
return new Dto.GetDbPathDto(!string.IsNullOrWhiteSpace(path), path);
}
- private static async Task ExecuteTest(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken)
+ private static async Task ExecuteTest(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken)
{
try
{
if (type == Dto.V2.RemoteDestinationType.SourceProvider)
{
- using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, maskedurl, backupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var s = wrapper.SourceProvider)
await s.Test(cancelToken).ConfigureAwait(false);
}
else if (type == Dto.V2.RemoteDestinationType.RestoreDestinationProvider)
{
- using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, maskedurl, backupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var r = wrapper.RestoreDestinationProvider)
await r.Test(cancelToken).ConfigureAwait(false);
}
else
{
- using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
{
try { await b.TestAsync(cancelToken).ConfigureAwait(false); }
@@ -120,11 +120,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1
}
- private static async Task ExecuteCreate(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, CancellationToken cancelToken)
+ private static async Task ExecuteCreate(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, CancellationToken cancelToken)
{
try
{
- using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
await b.CreateFolderAsync(cancelToken).ConfigureAwait(false);
}
diff --git a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
index 4292f5903..6f1906782 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
@@ -55,7 +55,7 @@ public record WebModules : IEndpointV1
var url = options.GetValueOrDefault("url");
if (!string.IsNullOrWhiteSpace(url))
{
- var (newUrl, opts) = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), cancellationToken);
+ var (newUrl, opts) = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), Library.Utility.Utility.ParseLongOption(options, "connection-string-id", -1), cancellationToken);
options["url"] = newUrl;
foreach (var k in opts.Keys)
options[k] = opts[k];
diff --git a/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs b/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs
index c01b64abc..216246f49 100644
--- a/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs
+++ b/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs
@@ -47,7 +47,7 @@ public class DestinationVerify : IEndpointV2
{
if (destinationType == RemoteDestinationType.SourceProvider)
{
- using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
await wrapper.SourceProvider.Test(cancelToken);
// Technically we also count folders as files here, but really we just want to know if there is data to backup
var anyFiles = await wrapper.SourceProvider.Enumerate(cancelToken).AnyAsync(cancelToken);
@@ -60,7 +60,7 @@ public class DestinationVerify : IEndpointV2
}
else if (destinationType == RemoteDestinationType.RestoreDestinationProvider)
{
- using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
await wrapper.RestoreDestinationProvider.Test(cancelToken);
return DestinationTestResponseDto.Create(
@@ -71,7 +71,7 @@ public class DestinationVerify : IEndpointV2
}
else
{
- using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, input.DestinationUrl, input.BackupId, cancelToken);
+ using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken);
using (var b = wrapper.Backend)
{