aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Server
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Server')
-rw-r--r--Jellyfin.Server/Configuration/CorsPolicyProvider.cs49
-rw-r--r--Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs24
-rw-r--r--Jellyfin.Server/Filters/FileResponseFilter.cs52
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj19
-rw-r--r--Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs6
-rw-r--r--Jellyfin.Server/Middleware/ExceptionMiddleware.cs1
-rw-r--r--Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs4
-rw-r--r--Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs3
-rw-r--r--Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs4
-rw-r--r--Jellyfin.Server/Models/ServerCorsPolicy.cs30
-rw-r--r--Jellyfin.Server/Program.cs7
-rw-r--r--Jellyfin.Server/Properties/launchSettings.json12
-rw-r--r--Jellyfin.Server/Startup.cs12
-rw-r--r--Jellyfin.Server/StartupOptions.cs4
14 files changed, 163 insertions, 64 deletions
diff --git a/Jellyfin.Server/Configuration/CorsPolicyProvider.cs b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs
new file mode 100644
index 000000000..0d04b6bb1
--- /dev/null
+++ b/Jellyfin.Server/Configuration/CorsPolicyProvider.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Configuration;
+using Microsoft.AspNetCore.Cors.Infrastructure;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Server.Configuration
+{
+ /// <summary>
+ /// Cors policy provider.
+ /// </summary>
+ public class CorsPolicyProvider : ICorsPolicyProvider
+ {
+ private readonly IServerConfigurationManager _serverConfigurationManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CorsPolicyProvider"/> class.
+ /// </summary>
+ /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
+ public CorsPolicyProvider(IServerConfigurationManager serverConfigurationManager)
+ {
+ _serverConfigurationManager = serverConfigurationManager;
+ }
+
+ /// <inheritdoc />
+ public Task<CorsPolicy> GetPolicyAsync(HttpContext context, string policyName)
+ {
+ var corsHosts = _serverConfigurationManager.Configuration.CorsHosts;
+ var builder = new CorsPolicyBuilder()
+ .AllowAnyMethod()
+ .AllowAnyHeader();
+
+ // No hosts configured or only default configured.
+ if (corsHosts.Length == 0
+ || (corsHosts.Length == 1
+ && string.Equals(corsHosts[0], CorsConstants.AnyOrigin, StringComparison.Ordinal)))
+ {
+ builder.AllowAnyOrigin();
+ }
+ else
+ {
+ builder.WithOrigins(corsHosts)
+ .AllowCredentials();
+ }
+
+ return Task.FromResult(builder.Build());
+ }
+ }
+}
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 517d77412..5bcf6d5f0 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Net;
using System.Reflection;
using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
@@ -15,17 +16,20 @@ using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Controllers;
+using Jellyfin.Server.Configuration;
+using Jellyfin.Server.Filters;
using Jellyfin.Server.Formatters;
-using Jellyfin.Server.Models;
using MediaBrowser.Common.Json;
using MediaBrowser.Model.Entities;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
+using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
namespace Jellyfin.Server.Extensions
{
@@ -134,17 +138,23 @@ namespace Jellyfin.Server.Extensions
/// </summary>
/// <param name="serviceCollection">The service collection.</param>
/// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
+ /// <param name="knownProxies">A list of all known proxies to trust for X-Forwarded-For.</param>
/// <returns>The MVC builder.</returns>
- public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies)
+ public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies, IReadOnlyList<string> knownProxies)
{
IMvcBuilder mvcBuilder = serviceCollection
- .AddCors(options =>
- {
- options.AddPolicy(ServerCorsPolicy.DefaultPolicyName, ServerCorsPolicy.DefaultPolicy);
- })
+ .AddCors()
+ .AddTransient<ICorsPolicyProvider, CorsPolicyProvider>()
.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
+ for (var i = 0; i < knownProxies.Count; i++)
+ {
+ if (IPAddress.TryParse(knownProxies[i], out var address))
+ {
+ options.KnownProxies.Add(address);
+ }
+ }
})
.AddMvc(opts =>
{
@@ -248,6 +258,8 @@ namespace Jellyfin.Server.Extensions
// TODO - remove when all types are supported in System.Text.Json
c.AddSwaggerTypeMappings();
+
+ c.OperationFilter<FileResponseFilter>();
});
}
diff --git a/Jellyfin.Server/Filters/FileResponseFilter.cs b/Jellyfin.Server/Filters/FileResponseFilter.cs
new file mode 100644
index 000000000..8ea35c281
--- /dev/null
+++ b/Jellyfin.Server/Filters/FileResponseFilter.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using Jellyfin.Api.Attributes;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Jellyfin.Server.Filters
+{
+ /// <inheritdoc />
+ public class FileResponseFilter : IOperationFilter
+ {
+ private const string SuccessCode = "200";
+ private static readonly OpenApiMediaType _openApiMediaType = new OpenApiMediaType
+ {
+ Schema = new OpenApiSchema
+ {
+ Type = "file"
+ }
+ };
+
+ /// <inheritdoc />
+ public void Apply(OpenApiOperation operation, OperationFilterContext context)
+ {
+ foreach (var attribute in context.ApiDescription.ActionDescriptor.EndpointMetadata)
+ {
+ if (attribute is ProducesFileAttribute producesFileAttribute)
+ {
+ // Get operation response values.
+ var (_, value) = operation.Responses
+ .FirstOrDefault(o => o.Key.Equals(SuccessCode, StringComparison.Ordinal));
+
+ // Operation doesn't have a response.
+ if (value == null)
+ {
+ continue;
+ }
+
+ // Clear existing responses.
+ value.Content.Clear();
+
+ // Add all content-types as file.
+ foreach (var contentType in producesFileAttribute.GetContentTypes())
+ {
+ value.Content.Add(contentType, _openApiMediaType);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index c3bec1c71..761a92f6d 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk.Web">
<!-- ProjectGuid is only included as a requirement for SonarQube analysis -->
<PropertyGroup>
@@ -13,6 +13,7 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<Nullable>enable</Nullable>
+ <DisableImplicitAspNetCoreAnalyzers>true</DisableImplicitAspNetCoreAnalyzers>
</PropertyGroup>
<ItemGroup>
@@ -23,10 +24,6 @@
<EmbeddedResource Include="Resources/Configuration/*" />
</ItemGroup>
- <ItemGroup>
- <FrameworkReference Include="Microsoft.AspNetCore.App" />
- </ItemGroup>
-
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8" PrivateAssets="All" />
@@ -41,10 +38,10 @@
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.7" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.7" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.7" />
- <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.7" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="3.1.8" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="3.1.8" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks" Version="3.1.8" />
+ <PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="3.1.8" />
<PackageReference Include="prometheus-net" Version="3.6.0" />
<PackageReference Include="prometheus-net.AspNetCore" Version="3.6.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.4.0" />
@@ -53,8 +50,8 @@
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
- <PackageReference Include="Serilog.Sinks.Graylog" Version="2.1.3" />
- <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.3" />
+ <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.1" />
+ <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.4" />
<PackageReference Include="SQLitePCLRaw.provider.sqlite3.netstandard11" Version="1.1.14" />
</ItemGroup>
diff --git a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
index ae3a3a1c5..9316737bd 100644
--- a/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs
@@ -44,7 +44,11 @@ namespace Jellyfin.Server.Middleware
var localPath = httpContext.Request.Path.ToString();
var baseUrlPrefix = serverConfigurationManager.Configuration.BaseUrl;
- if (!localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(localPath, baseUrlPrefix + "/", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(localPath, baseUrlPrefix, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(localPath, "/", StringComparison.OrdinalIgnoreCase)
+ || string.IsNullOrEmpty(localPath)
+ || !localPath.StartsWith(baseUrlPrefix, StringComparison.OrdinalIgnoreCase))
{
// Always redirect back to the default path if the base prefix is invalid or missing
_logger.LogDebug("Normalizing an URL at {LocalPath}", localPath);
diff --git a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
index 63effafc1..fb1ee3b2b 100644
--- a/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ExceptionMiddleware.cs
@@ -125,6 +125,7 @@ namespace Jellyfin.Server.Middleware
switch (ex)
{
case ArgumentException _: return StatusCodes.Status400BadRequest;
+ case AuthenticationException _:
case SecurityException _: return StatusCodes.Status401Unauthorized;
case DirectoryNotFoundException _:
case FileNotFoundException _:
diff --git a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
index 59b5fb1ed..4bda8f273 100644
--- a/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
+++ b/Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs
@@ -32,13 +32,13 @@ namespace Jellyfin.Server.Middleware
/// <returns>The async task.</returns>
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
{
- if (httpContext.Request.IsLocal())
+ if (httpContext.IsLocal())
{
await _next(httpContext).ConfigureAwait(false);
return;
}
- var remoteIp = httpContext.Request.RemoteIp();
+ var remoteIp = httpContext.GetNormalizedRemoteIp();
if (serverConfigurationManager.Configuration.EnableRemoteAccess)
{
diff --git a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
index 3122d92cb..74874da1b 100644
--- a/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
@@ -69,7 +70,7 @@ namespace Jellyfin.Server.Middleware
_logger.LogWarning(
"Slow HTTP Response from {url} to {remoteIp} in {elapsed:g} with Status Code {statusCode}",
context.Request.GetDisplayUrl(),
- context.Connection.RemoteIpAddress,
+ context.GetNormalizedRemoteIp(),
watch.Elapsed,
context.Response.StatusCode);
}
diff --git a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs
index ea81c03a2..2ec063392 100644
--- a/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs
+++ b/Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs
@@ -1,3 +1,4 @@
+using System;
using System.Net.Mime;
using System.Threading.Tasks;
using MediaBrowser.Controller;
@@ -34,7 +35,8 @@ namespace Jellyfin.Server.Middleware
IServerApplicationHost serverApplicationHost,
ILocalizationManager localizationManager)
{
- if (serverApplicationHost.CoreStartupHasCompleted)
+ if (serverApplicationHost.CoreStartupHasCompleted
+ || httpContext.Request.Path.Equals("/system/ping", StringComparison.OrdinalIgnoreCase))
{
await _next(httpContext).ConfigureAwait(false);
return;
diff --git a/Jellyfin.Server/Models/ServerCorsPolicy.cs b/Jellyfin.Server/Models/ServerCorsPolicy.cs
deleted file mode 100644
index ae010c042..000000000
--- a/Jellyfin.Server/Models/ServerCorsPolicy.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.AspNetCore.Cors.Infrastructure;
-
-namespace Jellyfin.Server.Models
-{
- /// <summary>
- /// Server Cors Policy.
- /// </summary>
- public static class ServerCorsPolicy
- {
- /// <summary>
- /// Default policy name.
- /// </summary>
- public const string DefaultPolicyName = "DefaultCorsPolicy";
-
- /// <summary>
- /// Default Policy. Allow Everything.
- /// </summary>
- public static readonly CorsPolicy DefaultPolicy = new CorsPolicy
- {
- // Allow any origin
- Origins = { "*" },
-
- // Allow any method
- Methods = { "*" },
-
- // Allow any header
- Headers = { "*" }
- };
- }
-} \ No newline at end of file
diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs
index 45959aec2..c933d679f 100644
--- a/Jellyfin.Server/Program.cs
+++ b/Jellyfin.Server/Program.cs
@@ -527,6 +527,13 @@ namespace Jellyfin.Server
}
}
+ // Normalize paths. Only possible with GetFullPath for now - https://github.com/dotnet/runtime/issues/2162
+ dataDir = Path.GetFullPath(dataDir);
+ logDir = Path.GetFullPath(logDir);
+ configDir = Path.GetFullPath(configDir);
+ cacheDir = Path.GetFullPath(cacheDir);
+ webDir = Path.GetFullPath(webDir);
+
// Ensure the main folders exist before we continue
try
{
diff --git a/Jellyfin.Server/Properties/launchSettings.json b/Jellyfin.Server/Properties/launchSettings.json
index b6e2bcf97..20d432afc 100644
--- a/Jellyfin.Server/Properties/launchSettings.json
+++ b/Jellyfin.Server/Properties/launchSettings.json
@@ -2,6 +2,8 @@
"profiles": {
"Jellyfin.Server": {
"commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "http://localhost:8096",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -12,6 +14,16 @@
"ASPNETCORE_ENVIRONMENT": "Development"
},
"commandLineArgs": "--nowebclient"
+ },
+ "Jellyfin.Server (API Docs)": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "api-docs/swagger",
+ "applicationUrl": "http://localhost:8096",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "commandLineArgs": "--nowebclient"
}
}
}
diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs
index 597323b86..2f4620aa6 100644
--- a/Jellyfin.Server/Startup.cs
+++ b/Jellyfin.Server/Startup.cs
@@ -5,7 +5,6 @@ using Jellyfin.Api.TypeConverters;
using Jellyfin.Server.Extensions;
using Jellyfin.Server.Implementations;
using Jellyfin.Server.Middleware;
-using Jellyfin.Server.Models;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
@@ -53,7 +52,7 @@ namespace Jellyfin.Server
{
options.HttpsPort = _serverApplicationHost.HttpsPort;
});
- services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies());
+ services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.Configuration.KnownProxies);
services.AddJellyfinApiSwagger();
@@ -94,11 +93,7 @@ namespace Jellyfin.Server
IWebHostEnvironment env,
IConfiguration appConfig)
{
- // Only add base url redirection if a base url is set.
- if (!string.IsNullOrEmpty(_serverConfigurationManager.Configuration.BaseUrl))
- {
- app.UseBaseUrlRedirection();
- }
+ app.UseBaseUrlRedirection();
// Wrap rest of configuration so everything only listens on BaseUrl.
app.Map(_serverConfigurationManager.Configuration.BaseUrl, mainApp =>
@@ -108,6 +103,7 @@ namespace Jellyfin.Server
mainApp.UseDeveloperExceptionPage();
}
+ mainApp.UseForwardedHeaders();
mainApp.UseMiddleware<ExceptionMiddleware>();
mainApp.UseMiddleware<ResponseTimeMiddleware>();
@@ -116,7 +112,7 @@ namespace Jellyfin.Server
mainApp.UseResponseCompression();
- mainApp.UseCors(ServerCorsPolicy.DefaultPolicyName);
+ mainApp.UseCors();
if (_serverConfigurationManager.Configuration.RequireHttps
&& _serverApplicationHost.ListenWithHttps)
diff --git a/Jellyfin.Server/StartupOptions.cs b/Jellyfin.Server/StartupOptions.cs
index 41a1430d2..b63434092 100644
--- a/Jellyfin.Server/StartupOptions.cs
+++ b/Jellyfin.Server/StartupOptions.cs
@@ -64,10 +64,6 @@ namespace Jellyfin.Server
public bool IsService { get; set; }
/// <inheritdoc />
- [Option("noautorunwebapp", Required = false, HelpText = "Run headless if startup wizard is complete.")]
- public bool NoAutoRunWebApp { get; set; }
-
- /// <inheritdoc />
[Option("package-name", Required = false, HelpText = "Used when packaging Jellyfin (example, synology).")]
public string? PackageName { get; set; }