diff options
Diffstat (limited to 'Jellyfin.Server')
| -rw-r--r-- | Jellyfin.Server/Configuration/CorsPolicyProvider.cs | 49 | ||||
| -rw-r--r-- | Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 24 | ||||
| -rw-r--r-- | Jellyfin.Server/Filters/FileResponseFilter.cs | 52 | ||||
| -rw-r--r-- | Jellyfin.Server/Jellyfin.Server.csproj | 19 | ||||
| -rw-r--r-- | Jellyfin.Server/Middleware/BaseUrlRedirectionMiddleware.cs | 6 | ||||
| -rw-r--r-- | Jellyfin.Server/Middleware/ExceptionMiddleware.cs | 1 | ||||
| -rw-r--r-- | Jellyfin.Server/Middleware/IpBasedAccessValidationMiddleware.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Server/Middleware/ResponseTimeMiddleware.cs | 3 | ||||
| -rw-r--r-- | Jellyfin.Server/Middleware/ServerStartupMessageMiddleware.cs | 4 | ||||
| -rw-r--r-- | Jellyfin.Server/Models/ServerCorsPolicy.cs | 30 | ||||
| -rw-r--r-- | Jellyfin.Server/Program.cs | 7 | ||||
| -rw-r--r-- | Jellyfin.Server/Properties/launchSettings.json | 12 | ||||
| -rw-r--r-- | Jellyfin.Server/Startup.cs | 12 | ||||
| -rw-r--r-- | Jellyfin.Server/StartupOptions.cs | 4 |
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; } |
