Merge pull request #5891 from duplicati/feature/add-support-for-cors

Added support for CORS
This commit is contained in:
Kenneth Skovhede
2025-01-30 10:53:27 +01:00
committed by GitHub
5 changed files with 54 additions and 9 deletions
+1
View File
@@ -73,6 +73,7 @@ Error message: {0}", error); }
public static string WebserverApiOnlyDescription { get { return LC.L(@"Disable the web interface and only allow API access"); } }
public static string WebserverDisableSigninTokensDescription { get { return LC.L(@"Disable the use of signin tokens"); } }
public static string WebserverSpaPathsDescription { get { return LC.L(@"The relative paths that should be served as single page applications, separated with semicolons."); } }
public static string WebserverCorsOriginsDescription { get { return LC.L(@"A list of CORS origins to allow, separated with semicolons. Each origin must be a valid URL."); } }
public static string WebserverTimezoneDescription { get { return LC.L(@"The timezone to use for the webserver. The timezone must be a valid timezone identifier, such as ""America/New_York"" or ""UTC"". Common three-letter abbreviations like ""CET"" are supported, but ambiguous in some cases."); } }
public static string DisabledbencryptionLong { get { return LC.L(@"Use this option to disable database encryption of sensitive fields"); } }
public static string DisabledbencryptionShort { get { return LC.L(@"Disable database encryption"); } }
+3 -1
View File
@@ -403,7 +403,8 @@ namespace Duplicati.Server
parsedOptions.Servername,
parsedOptions.AllowedHostnames,
parsedOptions.DisableStaticFiles,
parsedOptions.SPAPaths
parsedOptions.SPAPaths,
parsedOptions.CorsOrigins
);
var server = DuplicatiWebserver.CreateWebServer(mappedSettings, connection);
@@ -980,6 +981,7 @@ namespace Duplicati.Server
new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableSigninTokensDescription, Strings.Program.WebserverDisableSigninTokensDescription),
new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SPAPATHS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverSpaPathsDescription, Strings.Program.WebserverSpaPathsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS),
new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.String, Strings.Program.WebserverTimezoneDescription, Strings.Program.WebserverTimezoneDescription, TimeZoneHelper.GetLocalTimeZone(), null, TimeZoneHelper.GetTimeZones().Select(x => x.Id).ToArray()),
new Duplicati.Library.Interface.CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_CORS_ORIGINS, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverCorsOriginsDescription, Strings.Program.WebserverCorsOriginsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS),
new Duplicati.Library.Interface.CommandLineArgument(PING_PONG_KEEPALIVE_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong),
new Duplicati.Library.Interface.CommandLineArgument("log-retention", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION),
new Duplicati.Library.Interface.CommandLineArgument("server-datafolder", Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DATAFOLDER_ENV_NAME), System.IO.Path.Combine(System.Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Library.AutoUpdater.AutoUpdateSettings.AppName)),
+9 -2
View File
@@ -99,6 +99,10 @@ public static class WebServerLoader
/// </summary>
public const string OPTION_WEBSERVICE_SPAPATHS = "webservice-spa-paths";
/// <summary>
/// The CORS origins to allow
/// </summary>
public const string OPTION_WEBSERVICE_CORS_ORIGINS = "webservice-cors-origins";
/// <summary>
/// Option for setting the webservice timezone
/// </summary>
public const string OPTION_WEBSERVICE_TIMEZONE = "webservice-timezone";
@@ -154,6 +158,7 @@ public static class WebServerLoader
/// <param name="AllowedHostnames">The allowed hostnames</param>
/// <param name="DisableStaticFiles">If static files should be disabled</param>
/// <param name="SPAPaths">The paths to serve as SPAs</param>
/// <param name="CorsOrigins">The origins to allow for CORS</param>
public record ParsedWebserverSettings(
string WebRoot,
int Port,
@@ -162,7 +167,8 @@ public static class WebServerLoader
string Servername,
IEnumerable<string> AllowedHostnames,
bool DisableStaticFiles,
IEnumerable<string> SPAPaths
IEnumerable<string> SPAPaths,
IEnumerable<string> CorsOrigins
);
@@ -251,7 +257,8 @@ public static class WebServerLoader
string.Format("{0} v{1}", Library.AutoUpdater.AutoUpdateSettings.AppName, Library.AutoUpdater.UpdaterManager.SelfVersion.Version),
(connection.ApplicationSettings.AllowedHostnames ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries),
Duplicati.Library.Utility.Utility.ParseBoolOption(options, OPTION_WEBSERVICE_API_ONLY),
spaPathsString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
spaPathsString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries),
options.GetValueOrDefault(OPTION_WEBSERVICE_CORS_ORIGINS)?.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty<string>()
);
// Materialize the list of ports, and move the last-used port to the front, so we try the last-known port first
+35 -3
View File
@@ -48,6 +48,11 @@ public class DuplicatiWebserver
/// The log tag for this class
/// </summary>
private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType<DuplicatiWebserver>();
/// <summary>
/// The name of the CORS policy
/// </summary>
public const string CorsPolicyName = "CustomCorsPolicy";
/// <summary>
/// The configuration for the server
/// </summary>
@@ -68,6 +73,11 @@ public class DuplicatiWebserver
/// </summary>
public required int Port { get; init; }
/// <summary>
/// The port the server is listening on
/// </summary>
public required bool CorsEnabled { get; init; }
/// <summary>
/// The task that will be set when the server is terminated
/// </summary>
@@ -93,6 +103,7 @@ public class DuplicatiWebserver
/// <param name="AllowedHostnames">The allowed hostnames</param>
/// <param name="DisableStaticFiles">If static files should be disabled</param>
/// <param name="SPAPaths">The paths to serve as SPAs</param>
/// <param name="CorsOrigins">The origins to allow for CORS</param>
public record InitSettings(
string WebRoot,
int Port,
@@ -101,7 +112,8 @@ public class DuplicatiWebserver
string Servername,
IEnumerable<string> AllowedHostnames,
bool DisableStaticFiles,
IEnumerable<string> SPAPaths
IEnumerable<string> SPAPaths,
IEnumerable<string> CorsOrigins
);
/// <summary>
@@ -253,6 +265,22 @@ public class DuplicatiWebserver
builder.Services.AddHttpClient();
var useCors = settings.CorsOrigins != null && settings.CorsOrigins.Any();
if (useCors)
{
builder.Services.AddCors(options =>
{
options.AddPolicy(
name: CorsPolicyName,
policy =>
{
policy.WithOrigins(settings.CorsOrigins!.ToArray())
.AllowAnyHeader()
.AllowAnyMethod();
});
});
}
var app = builder.Build();
HttpClientHelper.Configure(app.Services.GetRequiredService<IHttpClientFactory>());
@@ -271,6 +299,9 @@ public class DuplicatiWebserver
if (!settings.DisableStaticFiles)
app.UseDefaultStaticFiles(settings.WebRoot, settings.SPAPaths);
if (useCors)
app.UseCors(CorsPolicyName);
app.UseExceptionHandler(app =>
{
app.Run(async context =>
@@ -308,7 +339,8 @@ public class DuplicatiWebserver
{
Configuration = builder.Configuration,
App = app,
Port = settings.Port
Port = settings.Port,
CorsEnabled = useCors
};
}
@@ -345,7 +377,7 @@ public class DuplicatiWebserver
public Task Start()
{
App.MapHealthChecks("/health");
App.AddEndpoints()
App.AddEndpoints(CorsEnabled)
.UseNotifications("/notifications");
return TerminationTask = App.RunAsync();
@@ -26,12 +26,12 @@ namespace Duplicati.WebserverCore.Extensions;
public static class WebApplicationExtensions
{
public static WebApplication AddEndpoints(this WebApplication application)
public static WebApplication AddEndpoints(this WebApplication application, bool useCors)
{
return AddV1(application);
return AddV1(application, useCors);
}
private static WebApplication AddV1(WebApplication application)
private static WebApplication AddV1(WebApplication application, bool useCors)
{
var mapperInterfaceType = typeof(IEndpointV1);
var endpoints =
@@ -46,6 +46,9 @@ public static class WebApplicationExtensions
if (!string.IsNullOrWhiteSpace(PreSharedKeyFilter.PreSharedKey))
group = group.AddEndpointFilter<PreSharedKeyFilter>();
if (useCors)
group.RequireCors(DuplicatiWebserver.CorsPolicyName);
foreach (var endpoint in endpoints)
{
var methodMap = endpoint.GetMethod(nameof(IEndpointV1.Map), BindingFlags.Static | BindingFlags.Public);