Browse Source

Allow for dynamic cors response

crobibero 4 years ago
parent
commit
3c0484cc97

+ 6 - 2
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 =>
                 {

+ 68 - 0
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);
+        }
+    }
+}

+ 30 - 13
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; }
     }
-}
+}

+ 5 - 3
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();
             }
 

+ 6 - 0
MediaBrowser.Model/Configuration/ServerConfiguration.cs

@@ -264,6 +264,11 @@ namespace MediaBrowser.Model.Configuration
         /// </summary>
         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>
@@ -372,6 +377,7 @@ namespace MediaBrowser.Model.Configuration
 
             EnableSlowResponseWarning = true;
             SlowResponseThresholdMs = 500;
+            CorsHosts = new[] { "*" };
         }
     }