diff options
Diffstat (limited to 'Jellyfin.Server')
| -rw-r--r-- | Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs | 27 | ||||
| -rw-r--r-- | Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 89 | ||||
| -rw-r--r-- | Jellyfin.Server/Jellyfin.Server.csproj | 4 | ||||
| -rw-r--r-- | Jellyfin.Server/Program.cs | 70 | ||||
| -rw-r--r-- | Jellyfin.Server/Startup.cs | 81 |
5 files changed, 270 insertions, 1 deletions
diff --git a/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs new file mode 100644 index 000000000..db06eb455 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs @@ -0,0 +1,27 @@ +using Microsoft.AspNetCore.Builder; + +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> + /// <returns>The updated application builder.</returns> + public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) + { + applicationBuilder.UseSwagger(); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + return applicationBuilder.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); + }); + } + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs new file mode 100644 index 000000000..e5a8937e8 --- /dev/null +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -0,0 +1,89 @@ +using Jellyfin.Api; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; +using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Controllers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; + +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) + { + serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); + return serviceCollection.AddAuthorizationCore(options => + { + options.AddPolicy( + "RequiresElevation", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new RequiresElevationRequirement()); + }); + options.AddPolicy( + "FirstTimeSetupOrElevated", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); + }); + }); + } + + /// <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("CustomAuthentication") + .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("CustomAuthentication", null); + } + + /// <summary> + /// Extension method for adding the jellyfin API to the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <param name="baseUrl">The base url for the API.</param> + /// <returns>The MVC builder.</returns> + public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) + { + return serviceCollection.AddMvc(opts => + { + opts.UseGeneralRoutePrefix(baseUrl); + }) + + // Clear app parts to avoid other assemblies being picked up + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) + .AddApplicationPart(typeof(StartupController).Assembly) + .AddControllersAsServices(); + } + + /// <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 => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + }); + } + } +} diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj index 4238d7fe3..dc784becf 100644 --- a/Jellyfin.Server/Jellyfin.Server.csproj +++ b/Jellyfin.Server/Jellyfin.Server.csproj @@ -20,6 +20,10 @@ <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.4" /> diff --git a/Jellyfin.Server/Program.cs b/Jellyfin.Server/Program.cs index e8bd0cd30..998f1125f 100644 --- a/Jellyfin.Server/Program.cs +++ b/Jellyfin.Server/Program.cs @@ -18,8 +18,10 @@ using Jellyfin.Drawing.Skia; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Drawing; using MediaBrowser.Model.Globalization; +using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; using Serilog; using Serilog.Extensions.Logging; @@ -164,7 +166,24 @@ namespace Jellyfin.Server appConfig); try { - await appHost.InitAsync(new ServiceCollection()).ConfigureAwait(false); + ServiceCollection serviceCollection = new ServiceCollection(); + await appHost.InitAsync(serviceCollection).ConfigureAwait(false); + + var host = CreateWebHostBuilder(appHost, serviceCollection).Build(); + + // A bit hacky to re-use service provider since ASP.NET doesn't allow a custom service collection. + appHost.ServiceProvider = host.Services; + appHost.FindParts(); + + try + { + await host.StartAsync().ConfigureAwait(false); + } + catch + { + _logger.LogError("Kestrel failed to start! This is most likely due to an invalid address or port bind - correct your bind configuration in system.xml and try again."); + throw; + } appHost.ImageProcessor.ImageEncoder = GetImageEncoder(appPaths, appHost.LocalizationManager); @@ -196,6 +215,55 @@ namespace Jellyfin.Server } } + private static IWebHostBuilder CreateWebHostBuilder(ApplicationHost appHost, IServiceCollection serviceCollection) + { + return new WebHostBuilder() + .UseKestrel(options => + { + var addresses = appHost.ServerConfigurationManager + .Configuration + .LocalNetworkAddresses + .Select(appHost.NormalizeConfiguredLocalAddress) + .Where(i => i != null) + .ToList(); + if (addresses.Any()) + { + foreach (var address in addresses) + { + _logger.LogInformation("Kestrel listening on {ipaddr}", address); + options.Listen(address, appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.Listen( + address, + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + } + else + { + _logger.LogInformation("Kestrel listening on all interfaces"); + options.ListenAnyIP(appHost.HttpPort); + + if (appHost.EnableHttps && appHost.Certificate != null) + { + options.ListenAnyIP( + appHost.HttpsPort, + listenOptions => listenOptions.UseHttps(appHost.Certificate)); + } + } + }) + .UseContentRoot(appHost.ContentRoot) + .ConfigureServices(services => + { + // Merge the external ServiceCollection into ASP.NET DI + services.TryAdd(serviceCollection); + }) + .UseStartup<Startup>(); + } + /// <summary> /// Create the data, config and log paths from the variety of inputs(command line args, /// environment variables) or decide on what default to use. For Windows it's %AppPath% diff --git a/Jellyfin.Server/Startup.cs b/Jellyfin.Server/Startup.cs new file mode 100644 index 000000000..3ee5fb8b5 --- /dev/null +++ b/Jellyfin.Server/Startup.cs @@ -0,0 +1,81 @@ +using Jellyfin.Server.Extensions; +using MediaBrowser.Controller; +using MediaBrowser.Controller.Configuration; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Jellyfin.Server +{ + /// <summary> + /// Startup configuration for the Kestrel webhost. + /// </summary> + public class Startup + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// <summary> + /// Initializes a new instance of the <see cref="Startup" /> class. + /// </summary> + /// <param name="serverConfigurationManager">The server configuration manager.</param> + public Startup(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// <summary> + /// Configures the service collection for the webhost. + /// </summary> + /// <param name="services">The service collection.</param> + public void ConfigureServices(IServiceCollection services) + { + services.AddResponseCompression(); + services.AddHttpContextAccessor(); + services.AddJellyfinApi(_serverConfigurationManager.Configuration.BaseUrl.TrimStart('/')); + + services.AddJellyfinApiSwagger(); + + // configure custom legacy authentication + services.AddCustomAuthentication(); + + services.AddJellyfinApiAuthorization(); + } + + /// <summary> + /// Configures the app builder for the webhost. + /// </summary> + /// <param name="app">The application builder.</param> + /// <param name="env">The webhost environment.</param> + /// <param name="serverApplicationHost">The server application host.</param> + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IServerApplicationHost serverApplicationHost) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseWebSockets(); + + app.UseResponseCompression(); + + // TODO app.UseMiddleware<WebSocketMiddleware>(); + app.Use(serverApplicationHost.ExecuteWebsocketHandlerAsync); + + // TODO use when old API is removed: app.UseAuthentication(); + app.UseJellyfinApiSwagger(); + app.UseRouting(); + app.UseAuthorization(); + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + + app.Use(serverApplicationHost.ExecuteHttpHandlerAsync); + } + } +} |
