Merge pull request #5891 from duplicati/feature/add-support-for-cors
Added support for CORS
This commit is contained in:
@@ -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"); } }
|
||||
|
||||
@@ -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)),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user