aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs8
-rw-r--r--Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs68
-rw-r--r--Jellyfin.Server/Models/ServerCorsPolicy.cs43
-rw-r--r--Jellyfin.Server/Startup.cs8
-rw-r--r--MediaBrowser.Model/Configuration/ServerConfiguration.cs6
5 files changed, 115 insertions, 18 deletions
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 0fd599cfc..b2f861542 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -135,13 +135,17 @@ namespace Jellyfin.Server.Extensions
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
/// <param name="baseUrl">The base url for the API.</param>
+ /// <param name="corsHosts">The configured cors hosts.</param>
/// <returns>The MVC builder.</returns>
- public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl)
+ public static IMvcBuilder AddJellyfinApi(
+ this IServiceCollection serviceCollection,
+ string baseUrl,
+ string[] corsHosts)
{
return serviceCollection
.AddCors(options =>
{
- options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy);
+ options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, new ServerCorsPolicy(corsHosts).Policy);
})
.Configure<ForwardedHeadersOptions>(options =>
{
diff --git a/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs b/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs
new file mode 100644
index 000000000..4fad898a7
--- /dev/null
+++ b/Jellyfin.Server/Middleware/DynamicCorsMiddleware.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Net.Http.Headers;
+
+namespace Jellyfin.Server.Middleware
+{
+ /// <summary>
+ /// Dynamic cors middleware.
+ /// </summary>
+ public class DynamicCorsMiddleware
+ {
+ private readonly RequestDelegate _next;
+ private readonly ILogger<DynamicCorsMiddleware> _logger;
+ private readonly CorsMiddleware _corsMiddleware;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DynamicCorsMiddleware"/> class.
+ /// </summary>
+ /// <param name="next">Next request delegate.</param>
+ /// <param name="corsService">Instance of the <see cref="ICorsService"/> interface.</param>
+ /// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
+ /// <param name="policyName">The cors policy name.</param>
+ public DynamicCorsMiddleware(
+ RequestDelegate next,
+ ICorsService corsService,
+ ILoggerFactory loggerFactory,
+ string policyName)
+ {
+ _corsMiddleware = new CorsMiddleware(next, corsService, loggerFactory, policyName);
+ _next = next;
+ _logger = loggerFactory.CreateLogger<DynamicCorsMiddleware>();
+ }
+
+ /// <summary>
+ /// Invoke request.
+ /// </summary>
+ /// <param name="context">Request context.</param>
+ /// <param name="corsPolicyProvider">Instance of the <see cref="ICorsPolicyProvider"/> interface.</param>
+ /// <returns>Task.</returns>
+ ///
+ public async Task Invoke(HttpContext context, ICorsPolicyProvider corsPolicyProvider)
+ {
+ // Only execute if is preflight request.
+ if (string.Equals(context.Request.Method, CorsConstants.PreflightHttpMethod, StringComparison.OrdinalIgnoreCase))
+ {
+ // Invoke original cors middleware.
+ await _corsMiddleware.Invoke(context, corsPolicyProvider).ConfigureAwait(false);
+ if (context.Response.Headers.TryGetValue(HeaderNames.AccessControlAllowOrigin, out var headerValue)
+ && string.Equals(headerValue, "*", StringComparison.Ordinal))
+ {
+ context.Response.Headers[HeaderNames.AccessControlAllowOrigin] = context.Request.Host.Value;
+ _logger.LogDebug("Overwriting CORS response header: {HeaderName}: {HeaderValue}", HeaderNames.AccessControlAllowOrigin, context.Request.Host.Value);
+
+ if (!context.Response.Headers.ContainsKey(HeaderNames.AccessControlAllowCredentials))
+ {
+ context.Response.Headers[HeaderNames.AccessControlAllowCredentials] = "true";
+ }
+ }
+ }
+
+ // Call the next delegate/middleware in the pipeline
+ await this._next(context).ConfigureAwait(false);
+ }
+ }
+}
diff --git a/Jellyfin.Server/Models/ServerCorsPolicy.cs b/Jellyfin.Server/Models/ServerCorsPolicy.cs
index ae010c042..3a45db3b4 100644
--- a/Jellyfin.Server/Models/ServerCorsPolicy.cs
+++ b/Jellyfin.Server/Models/ServerCorsPolicy.cs
@@ -1,30 +1,47 @@
-using Microsoft.AspNetCore.Cors.Infrastructure;
+using System;
+using Microsoft.AspNetCore.Cors.Infrastructure;
namespace Jellyfin.Server.Models
{
/// <summary>
/// Server Cors Policy.
/// </summary>
- public static class ServerCorsPolicy
+ public class ServerCorsPolicy
{
/// <summary>
/// Default policy name.
/// </summary>
- public const string DefaultPolicyName = "DefaultCorsPolicy";
+ public const string DefaultPolicyName = nameof(ServerCorsPolicy);
/// <summary>
- /// Default Policy. Allow Everything.
+ /// Initializes a new instance of the <see cref="ServerCorsPolicy"/> class.
/// </summary>
- public static readonly CorsPolicy DefaultPolicy = new CorsPolicy
+ /// <param name="corsHosts">The configured cors hosts.</param>
+ public ServerCorsPolicy(string[] corsHosts)
{
- // Allow any origin
- Origins = { "*" },
+ var builder = new CorsPolicyBuilder()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
- // Allow any method
- Methods = { "*" },
+ // No hosts configured or only default configured.
+ if (corsHosts.Length == 0
+ || (corsHosts.Length == 1
+ && string.Equals(corsHosts[0], "*", StringComparison.Ordinal)))
+ {
+ builder.AllowAnyOrigin();
+ }
+ else
+ {
+ builder.WithOrigins(corsHosts)
+ .AllowCredentials();
+ }
- // Allow any header
- Headers = { "*" }
- };
+ Policy = builder.Build();
+ }
+
+ /// <summary>
+ /// Gets the cors policy.
+ /// </summary>
+ public CorsPolicy Policy { get; }
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index d0dd183c6..76f5e69ce 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -38,7 +38,9 @@ namespace Jellyfin.Server
{
services.AddResponseCompression();
services.AddHttpContextAccessor();
- services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'));
+ services.AddJellyfinApi(
+ _serverConfigurationManager.Configuration.BaseUrl.TrimStart('/'),
+ _serverConfigurationManager.Configuration.CorsHosts);
services.AddJellyfinApiSwagger();
@@ -78,11 +80,11 @@ namespace Jellyfin.Server
app.UseAuthentication();
app.UseJellyfinApiSwagger(_serverConfigurationManager);
app.UseRouting();
- app.UseCors(ServerCorsPolicy.DefaultPolicyName);
+ app.UseMiddleware<DynamicCorsMiddleware>(ServerCorsPolicy.DefaultPolicyName);
app.UseAuthorization();
if (_serverConfigurationManager.Configuration.EnableMetrics)
{
- // Must be registered after any middleware that could chagne HTTP response codes or the data will be bad
+ // Must be registered after any middleware that could change HTTP response codes or the data will be bad
app.UseHttpMetrics();
}
diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
index c66091f9d..a743277d7 100644
--- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs
+++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs
@@ -265,6 +265,11 @@ namespace MediaBrowser.Model.Configuration
public long SlowResponseThresholdMs { get; set; }
/// <summary>
+ /// Gets or sets the cors hosts.
+ /// </summary>
+ public string[] CorsHosts { get; set; }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="ServerConfiguration" /> class.
/// </summary>
public ServerConfiguration()
@@ -372,6 +377,7 @@ namespace MediaBrowser.Model.Configuration
EnableSlowResponseWarning = true;
SlowResponseThresholdMs = 500;
+ CorsHosts = new[] { "*" };
}
}