repo creation with initial code after cloning public repo
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using Jellyfin.Api.Middleware;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Controller.Configuration;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extensions for adding API specific functionality to the application pipeline.
|
||||
/// </summary>
|
||||
public static class ApiApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds swagger and swagger UI to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The application builder.</param>
|
||||
/// <param name="serverConfigurationManager">The server configuration.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseJellyfinApiSwagger(
|
||||
this IApplicationBuilder applicationBuilder,
|
||||
IServerConfigurationManager serverConfigurationManager)
|
||||
{
|
||||
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
|
||||
// specifying the Swagger JSON endpoint.
|
||||
|
||||
var baseUrl = serverConfigurationManager.GetNetworkConfiguration().BaseUrl.Trim('/');
|
||||
var apiDocBaseUrl = serverConfigurationManager.GetNetworkConfiguration().BaseUrl;
|
||||
if (!string.IsNullOrEmpty(baseUrl))
|
||||
{
|
||||
baseUrl += '/';
|
||||
}
|
||||
|
||||
return applicationBuilder
|
||||
.UseSwagger(c =>
|
||||
{
|
||||
// Custom path requires {documentName}, SwaggerDoc documentName is 'api-docs'
|
||||
c.RouteTemplate = "{documentName}/openapi.json";
|
||||
c.PreSerializeFilters.Add((swagger, httpReq) =>
|
||||
{
|
||||
swagger.Servers = new List<OpenApiServer> { new OpenApiServer { Url = $"{httpReq.Scheme}://{httpReq.Host.Value}{apiDocBaseUrl}" } };
|
||||
});
|
||||
})
|
||||
.UseSwaggerUI(c =>
|
||||
{
|
||||
c.DocumentTitle = "Jellyfin API";
|
||||
c.SwaggerEndpoint($"/{baseUrl}api-docs/openapi.json", "Jellyfin API");
|
||||
c.InjectStylesheet($"/{baseUrl}api-docs/swagger/custom.css");
|
||||
c.RoutePrefix = "api-docs/swagger";
|
||||
})
|
||||
.UseReDoc(c =>
|
||||
{
|
||||
c.DocumentTitle = "Jellyfin API";
|
||||
c.SpecUrl($"/{baseUrl}api-docs/openapi.json");
|
||||
c.InjectStylesheet($"/{baseUrl}api-docs/redoc/custom.css");
|
||||
c.RoutePrefix = "api-docs/redoc";
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds IP based access validation to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseIPBasedAccessValidation(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<IPBasedAccessValidationMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables url decoding before binding to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The <see cref="IApplicationBuilder"/>.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseQueryStringDecoding(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<QueryStringDecodingMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds base url redirection to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseBaseUrlRedirection(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<BaseUrlRedirectionMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a custom message during server startup to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseServerStartupMessage(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<ServerStartupMessageMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a WebSocket request handler to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseWebSocketHandler(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<WebSocketHandlerMiddleware>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds robots.txt redirection to the application pipeline.
|
||||
/// </summary>
|
||||
/// <param name="appBuilder">The application builder.</param>
|
||||
/// <returns>The updated application builder.</returns>
|
||||
public static IApplicationBuilder UseRobotsRedirection(this IApplicationBuilder appBuilder)
|
||||
{
|
||||
return appBuilder.UseMiddleware<RobotsRedirectionMiddleware>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using System.Security.Claims;
|
||||
using Emby.Server.Implementations;
|
||||
using Jellyfin.Api.Auth;
|
||||
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
|
||||
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||
using Jellyfin.Api.Auth.FirstTimeSetupPolicy;
|
||||
using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
|
||||
using Jellyfin.Api.Auth.SyncPlayAccessPolicy;
|
||||
using Jellyfin.Api.Auth.UserPermissionPolicy;
|
||||
using Jellyfin.Api.Constants;
|
||||
using Jellyfin.Api.Controllers;
|
||||
using Jellyfin.Api.Formatters;
|
||||
using Jellyfin.Api.ModelBinders;
|
||||
using Jellyfin.Data.Enums;
|
||||
using Jellyfin.Database.Implementations.Enums;
|
||||
using Jellyfin.Extensions.Json;
|
||||
using Jellyfin.Server.Configuration;
|
||||
using Jellyfin.Server.Filters;
|
||||
using MediaBrowser.Common.Api;
|
||||
using MediaBrowser.Common.Net;
|
||||
using MediaBrowser.Model.Entities;
|
||||
using MediaBrowser.Model.Session;
|
||||
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.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.OpenApi.Any;
|
||||
using Microsoft.OpenApi.Interfaces;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.Swagger;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using AuthenticationSchemes = Jellyfin.Api.Constants.AuthenticationSchemes;
|
||||
|
||||
namespace Jellyfin.Server.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// API specific extensions for the service collection.
|
||||
/// </summary>
|
||||
public static class ApiServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds jellyfin API authorization policies to the DI container.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <returns>The updated service collection.</returns>
|
||||
public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
|
||||
{
|
||||
// The default handler must be first so that it is evaluated first
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, UserPermissionHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
|
||||
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
|
||||
|
||||
return serviceCollection.AddAuthorizationCore(options =>
|
||||
{
|
||||
options.DefaultPolicy = new AuthorizationPolicyBuilder()
|
||||
.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication)
|
||||
.AddRequirements(new DefaultAuthorizationRequirement())
|
||||
.Build();
|
||||
|
||||
options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement());
|
||||
options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement));
|
||||
options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading));
|
||||
options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement(requireAdmin: false));
|
||||
options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement());
|
||||
options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(false, false));
|
||||
options.AddPolicy(Policies.IgnoreParentalControl, new DefaultAuthorizationRequirement(validateParentalSchedule: false));
|
||||
options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess));
|
||||
options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement));
|
||||
options.AddPolicy(Policies.LocalAccessOrRequiresElevation, new LocalAccessOrRequiresElevationRequirement());
|
||||
options.AddPolicy(Policies.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess));
|
||||
options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup));
|
||||
options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup));
|
||||
options.AddPolicy(Policies.SyncPlayIsInGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup));
|
||||
options.AddPolicy(Policies.SubtitleManagement, new UserPermissionRequirement(PermissionKind.EnableSubtitleManagement));
|
||||
options.AddPolicy(Policies.LyricManagement, new UserPermissionRequirement(PermissionKind.EnableLyricManagement));
|
||||
options.AddPolicy(
|
||||
Policies.RequiresElevation,
|
||||
policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication)
|
||||
.RequireClaim(ClaimTypes.Role, UserRoles.Administrator));
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds custom legacy authentication to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <returns>The updated service collection.</returns>
|
||||
public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection.AddAuthentication(AuthenticationSchemes.CustomAuthentication)
|
||||
.AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>(AuthenticationSchemes.CustomAuthentication, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension method for adding the Jellyfin API to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param>
|
||||
/// <param name="config">The <see cref="NetworkConfiguration"/>.</param>
|
||||
/// <returns>The MVC builder.</returns>
|
||||
public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, IEnumerable<Assembly> pluginAssemblies, NetworkConfiguration config)
|
||||
{
|
||||
IMvcBuilder mvcBuilder = serviceCollection
|
||||
.AddCors()
|
||||
.AddTransient<ICorsPolicyProvider, CorsPolicyProvider>()
|
||||
.Configure<ForwardedHeadersOptions>(options =>
|
||||
{
|
||||
ConfigureForwardHeaders(config, options);
|
||||
})
|
||||
.AddMvc(opts =>
|
||||
{
|
||||
// Allow requester to change between camelCase and PascalCase
|
||||
opts.RespectBrowserAcceptHeader = true;
|
||||
|
||||
opts.OutputFormatters.Insert(0, new CamelCaseJsonProfileFormatter());
|
||||
opts.OutputFormatters.Insert(0, new PascalCaseJsonProfileFormatter());
|
||||
|
||||
opts.OutputFormatters.Add(new CssOutputFormatter());
|
||||
opts.OutputFormatters.Add(new XmlOutputFormatter());
|
||||
|
||||
opts.ModelBinderProviders.Insert(0, new NullableEnumModelBinderProvider());
|
||||
})
|
||||
|
||||
// Clear app parts to avoid other assemblies being picked up
|
||||
.ConfigureApplicationPartManager(a => a.ApplicationParts.Clear())
|
||||
.AddApplicationPart(typeof(StartupController).Assembly)
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
// Update all properties that are set in JsonDefaults
|
||||
var jsonOptions = JsonDefaults.PascalCaseOptions;
|
||||
|
||||
// From JsonDefaults
|
||||
options.JsonSerializerOptions.ReadCommentHandling = jsonOptions.ReadCommentHandling;
|
||||
options.JsonSerializerOptions.WriteIndented = jsonOptions.WriteIndented;
|
||||
options.JsonSerializerOptions.DefaultIgnoreCondition = jsonOptions.DefaultIgnoreCondition;
|
||||
options.JsonSerializerOptions.NumberHandling = jsonOptions.NumberHandling;
|
||||
|
||||
options.JsonSerializerOptions.Converters.Clear();
|
||||
foreach (var converter in jsonOptions.Converters)
|
||||
{
|
||||
options.JsonSerializerOptions.Converters.Add(converter);
|
||||
}
|
||||
|
||||
// From JsonDefaults.PascalCase
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = jsonOptions.PropertyNamingPolicy;
|
||||
});
|
||||
|
||||
foreach (Assembly pluginAssembly in pluginAssemblies)
|
||||
{
|
||||
mvcBuilder.AddApplicationPart(pluginAssembly);
|
||||
}
|
||||
|
||||
return mvcBuilder.AddControllersAsServices();
|
||||
}
|
||||
|
||||
internal static void ConfigureForwardHeaders(NetworkConfiguration config, ForwardedHeadersOptions options)
|
||||
{
|
||||
// https://github.com/dotnet/aspnetcore/blob/master/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs
|
||||
// Enable debug logging on Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware to help investigate issues.
|
||||
|
||||
if (config.KnownProxies.Length == 0)
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.None;
|
||||
options.KnownIPNetworks.Clear();
|
||||
options.KnownProxies.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost;
|
||||
AddProxyAddresses(config, config.KnownProxies, options);
|
||||
}
|
||||
|
||||
// Only set forward limit if we have some known proxies or some known networks.
|
||||
if (options.KnownProxies.Count != 0 || options.KnownIPNetworks.Count != 0)
|
||||
{
|
||||
options.ForwardLimit = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds Swagger to the service collection.
|
||||
/// </summary>
|
||||
/// <param name="serviceCollection">The service collection.</param>
|
||||
/// <returns>The updated service collection.</returns>
|
||||
public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection)
|
||||
{
|
||||
return serviceCollection.AddSwaggerGen(c =>
|
||||
{
|
||||
var version = typeof(ApplicationHost).Assembly.GetName().Version?.ToString(3) ?? "0.0.1";
|
||||
c.SwaggerDoc("api-docs", new OpenApiInfo
|
||||
{
|
||||
Title = "Jellyfin API",
|
||||
Version = version,
|
||||
Extensions = new Dictionary<string, IOpenApiExtension>
|
||||
{
|
||||
{
|
||||
"x-jellyfin-version",
|
||||
new OpenApiString(version)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
c.AddSecurityDefinition(AuthenticationSchemes.CustomAuthentication, new OpenApiSecurityScheme
|
||||
{
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
In = ParameterLocation.Header,
|
||||
Name = "Authorization",
|
||||
Description = "API key header parameter"
|
||||
});
|
||||
|
||||
// Add all xml doc files to swagger generator.
|
||||
var xmlFiles = Directory.EnumerateFiles(
|
||||
AppContext.BaseDirectory,
|
||||
"*.xml",
|
||||
SearchOption.TopDirectoryOnly);
|
||||
|
||||
foreach (var xmlFile in xmlFiles)
|
||||
{
|
||||
c.IncludeXmlComments(xmlFile);
|
||||
}
|
||||
|
||||
// Order actions by route path, then by http method.
|
||||
c.OrderActionsBy(description =>
|
||||
$"{description.ActionDescriptor.RouteValues["controller"]}_{description.RelativePath}");
|
||||
|
||||
// Use method name as operationId
|
||||
c.CustomOperationIds(
|
||||
description =>
|
||||
{
|
||||
description.TryGetMethodInfo(out MethodInfo methodInfo);
|
||||
// Attribute name, method name, none.
|
||||
return description?.ActionDescriptor.AttributeRouteInfo?.Name
|
||||
?? methodInfo?.Name
|
||||
?? null;
|
||||
});
|
||||
|
||||
// Allow parameters to properly be nullable.
|
||||
c.UseAllOfToExtendReferenceSchemas();
|
||||
c.SupportNonNullableReferenceTypes();
|
||||
|
||||
// TODO - remove when all types are supported in System.Text.Json
|
||||
c.AddSwaggerTypeMappings();
|
||||
|
||||
c.SchemaFilter<IgnoreEnumSchemaFilter>();
|
||||
c.SchemaFilter<FlagsEnumSchemaFilter>();
|
||||
c.OperationFilter<RetryOnTemporarilyUnavailableFilter>();
|
||||
c.OperationFilter<SecurityRequirementsOperationFilter>();
|
||||
c.OperationFilter<FileResponseFilter>();
|
||||
c.OperationFilter<FileRequestFilter>();
|
||||
c.OperationFilter<ParameterObsoleteFilter>();
|
||||
c.DocumentFilter<AdditionalModelFilter>();
|
||||
})
|
||||
.Replace(ServiceDescriptor.Transient<ISwaggerProvider, CachingOpenApiProvider>());
|
||||
}
|
||||
|
||||
private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement)
|
||||
{
|
||||
authorizationOptions.AddPolicy(policyName, policy =>
|
||||
{
|
||||
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication).AddRequirements(authorizationRequirement);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the proxy configuration based on the addresses/subnets in <paramref name="allowedProxies"/>.
|
||||
/// </summary>
|
||||
/// <param name="config">The <see cref="NetworkConfiguration"/> containing the config settings.</param>
|
||||
/// <param name="allowedProxies">The string array to parse.</param>
|
||||
/// <param name="options">The <see cref="ForwardedHeadersOptions"/> instance.</param>
|
||||
internal static void AddProxyAddresses(NetworkConfiguration config, string[] allowedProxies, ForwardedHeadersOptions options)
|
||||
{
|
||||
for (var i = 0; i < allowedProxies.Length; i++)
|
||||
{
|
||||
if (IPAddress.TryParse(allowedProxies[i], out var addr))
|
||||
{
|
||||
AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
|
||||
}
|
||||
else if (NetworkUtils.TryParseToSubnet(allowedProxies[i], out var subnet))
|
||||
{
|
||||
AddIPAddress(config, options, subnet.Address, subnet.Subnet.PrefixLength);
|
||||
}
|
||||
else if (NetworkUtils.TryParseHost(allowedProxies[i], out var addresses, config.EnableIPv4, config.EnableIPv6))
|
||||
{
|
||||
foreach (var address in addresses)
|
||||
{
|
||||
AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? NetworkConstants.MinimumIPv4PrefixSize : NetworkConstants.MinimumIPv6PrefixSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddIPAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength)
|
||||
{
|
||||
if (addr.IsIPv4MappedToIPv6)
|
||||
{
|
||||
addr = addr.MapToIPv4();
|
||||
}
|
||||
|
||||
if ((!config.EnableIPv4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPv6 && addr.AddressFamily == AddressFamily.InterNetworkV6))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (prefixLength == NetworkConstants.MinimumIPv4PrefixSize)
|
||||
{
|
||||
options.KnownProxies.Add(addr);
|
||||
}
|
||||
else
|
||||
{
|
||||
options.KnownIPNetworks.Add(new System.Net.IPNetwork(addr, prefixLength));
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddSwaggerTypeMappings(this SwaggerGenOptions options)
|
||||
{
|
||||
/*
|
||||
* TODO remove when System.Text.Json properly supports non-string keys.
|
||||
* Used in BaseItemDto.ImageBlurHashes
|
||||
*/
|
||||
options.MapType<Dictionary<ImageType, string>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
}
|
||||
});
|
||||
|
||||
// Support dictionary with nullable string value.
|
||||
options.MapType<Dictionary<string, string?>>(() =>
|
||||
new OpenApiSchema
|
||||
{
|
||||
Type = "object",
|
||||
AdditionalProperties = new OpenApiSchema
|
||||
{
|
||||
Type = "string",
|
||||
Nullable = true
|
||||
}
|
||||
});
|
||||
|
||||
// Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it.
|
||||
options.MapType<Version>(() => new OpenApiSchema
|
||||
{
|
||||
Type = "string"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Jellyfin.Server.Helpers;
|
||||
using MediaBrowser.Common.Configuration;
|
||||
using MediaBrowser.Controller.Extensions;
|
||||
using MediaBrowser.Model.Net;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Jellyfin.Server.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for configuring the web host builder.
|
||||
/// </summary>
|
||||
public static class WebHostBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configure the web host builder.
|
||||
/// </summary>
|
||||
/// <param name="builder">The builder to configure.</param>
|
||||
/// <param name="appHost">The application host.</param>
|
||||
/// <param name="startupConfig">The application configuration.</param>
|
||||
/// <param name="appPaths">The application paths.</param>
|
||||
/// <param name="logger">The logger.</param>
|
||||
/// <returns>The configured web host builder.</returns>
|
||||
public static IWebHostBuilder ConfigureWebHostBuilder(
|
||||
this IWebHostBuilder builder,
|
||||
CoreAppHost appHost,
|
||||
IConfiguration startupConfig,
|
||||
IApplicationPaths appPaths,
|
||||
ILogger logger)
|
||||
{
|
||||
return builder
|
||||
.UseKestrel((builderContext, options) =>
|
||||
{
|
||||
SetupJellyfinWebServer(
|
||||
appHost.NetManager.GetAllBindInterfaces(false),
|
||||
appHost.HttpPort,
|
||||
appHost.ListenWithHttps ? appHost.HttpsPort : null,
|
||||
appHost.Certificate,
|
||||
startupConfig,
|
||||
appPaths,
|
||||
logger,
|
||||
builderContext,
|
||||
options);
|
||||
})
|
||||
.UseStartup(context => new Startup(appHost, context.Configuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Configures a Kestrel type webServer to bind to the specific arguments.
|
||||
/// </summary>
|
||||
/// <param name="addresses">The IP addresses that should be listend to.</param>
|
||||
/// <param name="httpPort">The http port.</param>
|
||||
/// <param name="httpsPort">If set the https port. If set you must also set the certificate.</param>
|
||||
/// <param name="certificate">The certificate used for https port.</param>
|
||||
/// <param name="startupConfig">The startup config.</param>
|
||||
/// <param name="appPaths">The app paths.</param>
|
||||
/// <param name="logger">A logger.</param>
|
||||
/// <param name="builderContext">The kestrel build pipeline context.</param>
|
||||
/// <param name="options">The kestrel server options.</param>
|
||||
/// <exception cref="InvalidOperationException">Will be thrown when a https port is set but no or an invalid certificate is provided.</exception>
|
||||
public static void SetupJellyfinWebServer(
|
||||
IReadOnlyList<IPData> addresses,
|
||||
int httpPort,
|
||||
int? httpsPort,
|
||||
X509Certificate2? certificate,
|
||||
IConfiguration startupConfig,
|
||||
IApplicationPaths appPaths,
|
||||
ILogger logger,
|
||||
WebHostBuilderContext builderContext,
|
||||
KestrelServerOptions options)
|
||||
{
|
||||
bool flagged = false;
|
||||
foreach (var netAdd in addresses)
|
||||
{
|
||||
var address = netAdd.Address;
|
||||
logger.LogInformation("Kestrel is listening on {Address}", address.Equals(IPAddress.IPv6Any) ? "all interfaces" : address);
|
||||
options.Listen(netAdd.Address, httpPort);
|
||||
if (httpsPort.HasValue)
|
||||
{
|
||||
if (builderContext.HostingEnvironment.IsDevelopment())
|
||||
{
|
||||
try
|
||||
{
|
||||
options.Listen(
|
||||
address,
|
||||
httpsPort.Value,
|
||||
listenOptions => listenOptions.UseHttps());
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
if (!flagged)
|
||||
{
|
||||
logger.LogWarning("Failed to listen to HTTPS using the ASP.NET Core HTTPS development certificate. Please ensure it has been installed and set as trusted");
|
||||
flagged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (certificate is null)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot run jellyfin with https without setting a valid certificate.");
|
||||
}
|
||||
|
||||
options.Listen(
|
||||
address,
|
||||
httpsPort.Value,
|
||||
listenOptions => listenOptions.UseHttps(certificate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bind to unix socket (only on unix systems)
|
||||
if (startupConfig.UseUnixSocket() && Environment.OSVersion.Platform == PlatformID.Unix)
|
||||
{
|
||||
var socketPath = StartupHelpers.GetUnixSocketPath(startupConfig, appPaths);
|
||||
|
||||
// Workaround for https://github.com/aspnet/AspNetCore/issues/14134
|
||||
if (File.Exists(socketPath))
|
||||
{
|
||||
File.Delete(socketPath);
|
||||
}
|
||||
|
||||
options.ListenUnixSocket(socketPath);
|
||||
logger.LogInformation("Kestrel listening to unix socket {SocketPath}", socketPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user