using System; using System.Collections.Generic; using System.IO; using DryIoc; using Lidarr.Api.V1.System; using Lidarr.Http; using Lidarr.Http.Authentication; using Lidarr.Http.ClientSchema; using Lidarr.Http.ErrorManagement; using Lidarr.Http.Frontend; using Lidarr.Http.Middleware; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.DataProtection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.OpenApi.Models; using NLog.Extensions.Logging; using NzbDrone.Common.EnvironmentInfo; using NzbDrone.Common.Instrumentation; using NzbDrone.Common.Processes; using NzbDrone.Common.Serializer; using NzbDrone.Core.Configuration; using NzbDrone.Core.Datastore; using NzbDrone.Core.Instrumentation; using NzbDrone.Core.Lifecycle; using NzbDrone.Core.Messaging.Events; using NzbDrone.Host.AccessControl; using NzbDrone.Http.Authentication; using NzbDrone.SignalR; using LogLevel = Microsoft.Extensions.Logging.LogLevel; namespace NzbDrone.Host { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } public void ConfigureServices(IServiceCollection services) { services.AddLogging(b => { b.ClearProviders(); b.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); b.AddFilter("Microsoft.AspNetCore", Microsoft.Extensions.Logging.LogLevel.Warning); b.AddFilter("Lidarr.Http.Authentication", LogLevel.Information); b.AddFilter("Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager", LogLevel.Error); b.AddNLog(); }); services.Configure(options => { options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; options.KnownNetworks.Clear(); options.KnownProxies.Clear(); }); services.AddRouting(options => options.LowercaseUrls = true); services.AddResponseCompression(options => options.EnableForHttps = true); services.AddCors(options => { options.AddPolicy(VersionedApiControllerAttribute.API_CORS_POLICY, builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader()); options.AddPolicy("AllowGet", builder => builder.AllowAnyOrigin() .WithMethods("GET", "OPTIONS") .AllowAnyHeader()); }); services .AddControllers(options => { options.ReturnHttpNotAcceptable = true; }) .AddApplicationPart(typeof(SystemController).Assembly) .AddApplicationPart(typeof(StaticResourceController).Assembly) .AddJsonOptions(options => { STJson.ApplySerializerSettings(options.JsonSerializerOptions); }) .AddControllersAsServices(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Version = "1.0.0", Title = "Lidarr", Description = "Lidarr API docs", License = new OpenApiLicense { Name = "GPL-3.0", Url = new Uri("https://github.com/Lidarr/Lidarr/blob/develop/LICENSE") } }); var apiKeyHeader = new OpenApiSecurityScheme { Name = "X-Api-Key", Type = SecuritySchemeType.ApiKey, Scheme = "apiKey", Description = "Apikey passed as header", In = ParameterLocation.Header, Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "X-Api-Key" }, }; c.AddSecurityDefinition("X-Api-Key", apiKeyHeader); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { apiKeyHeader, Array.Empty() } }); var apikeyQuery = new OpenApiSecurityScheme { Name = "apikey", Type = SecuritySchemeType.ApiKey, Scheme = "apiKey", Description = "Apikey passed as header", In = ParameterLocation.Query, Reference = new OpenApiReference { Type = ReferenceType.SecurityScheme, Id = "apikey" }, }; c.AddServer(new OpenApiServer { Url = "{protocol}://{hostpath}", Variables = new Dictionary { { "protocol", new OpenApiServerVariable { Default = "http", Enum = new List { "http", "https" } } }, { "hostpath", new OpenApiServerVariable { Default = "localhost:8686" } } } }); c.AddSecurityDefinition("apikey", apikeyQuery); c.AddSecurityRequirement(new OpenApiSecurityRequirement { { apikeyQuery, Array.Empty() } }); c.DescribeAllParametersInCamelCase(); }); services .AddSignalR() .AddJsonProtocol(options => { options.PayloadSerializerOptions = STJson.GetSerializerSettings(); }); services.AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(Configuration["dataProtectionFolder"])); services.AddSingleton(); services.AddSingleton(); services.AddAuthorization(options => { options.AddPolicy("SignalR", policy => { policy.AuthenticationSchemes.Add("SignalR"); policy.RequireAuthenticatedUser(); }); // Require auth on everything except those marked [AllowAnonymous] options.FallbackPolicy = new AuthorizationPolicyBuilder("API") .RequireAuthenticatedUser() .Build(); }); services.AddAppAuthentication(); services.PostConfigure(options => { var builtInFactory = options.InvalidModelStateResponseFactory; options.InvalidModelStateResponseFactory = context => { var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); var logger = loggerFactory.CreateLogger(context.ActionDescriptor.DisplayName); logger.LogError(STJson.ToJson(context.ModelState)); return builtInFactory(context); }; }); } public void Configure(IApplicationBuilder app, IContainer container, IStartupContext startupContext, Lazy mainDatabaseFactory, Lazy logDatabaseFactory, DatabaseTarget dbTarget, ISingleInstancePolicy singleInstancePolicy, InitializeLogger initializeLogger, ReconfigureLogging reconfigureLogging, IAppFolderFactory appFolderFactory, IProvidePidFile pidFileProvider, IConfigFileProvider configFileProvider, IRuntimeInfo runtimeInfo, IFirewallAdapter firewallAdapter, LidarrErrorPipeline errorHandler, IEventAggregator eventAggregator) { initializeLogger.Initialize(); appFolderFactory.Register(); pidFileProvider.Write(); configFileProvider.EnsureDefaultConfigFile(); reconfigureLogging.Reconfigure(); EnsureSingleInstance(false, startupContext, singleInstancePolicy); // instantiate the databases to initialize/migrate them _ = mainDatabaseFactory.Value; _ = logDatabaseFactory.Value; dbTarget.Register(); SchemaBuilder.Initialize(container); if (OsInfo.IsNotWindows) { Console.CancelKeyPress += (sender, eventArgs) => NLog.LogManager.Configuration = null; } eventAggregator.PublishEvent(new ApplicationStartingEvent()); if (OsInfo.IsWindows && runtimeInfo.IsAdmin) { firewallAdapter.MakeAccessible(); } app.UseForwardedHeaders(); app.UseMiddleware(); app.UsePathBase(new PathString(configFileProvider.UrlBase)); app.UseExceptionHandler(new ExceptionHandlerOptions { AllowStatusCode404Response = true, ExceptionHandler = errorHandler.HandleException }); app.UseRouting(); app.UseCors(); app.UseAuthentication(); app.UseAuthorization(); app.UseResponseCompression(); app.Properties["host.AppName"] = BuildInfo.AppName; app.UseMiddleware(); app.UseMiddleware(configFileProvider.UrlBase); app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(); app.UseMiddleware(new List { "/api/v1/command" }); app.UseWebSockets(); // Enable middleware to serve generated Swagger as a JSON endpoint. if (BuildInfo.IsDebug) { app.UseSwagger(c => { c.RouteTemplate = "docs/{documentName}/openapi.json"; }); } app.UseEndpoints(x => { x.MapHub("/signalr/messages").RequireAuthorization("SignalR"); x.MapControllers(); }); } private void EnsureSingleInstance(bool isService, IStartupContext startupContext, ISingleInstancePolicy instancePolicy) { if (startupContext.Flags.Contains(StartupContext.NO_SINGLE_INSTANCE_CHECK)) { return; } if (startupContext.Flags.Contains(StartupContext.TERMINATE)) { instancePolicy.KillAllOtherInstance(); } else if (startupContext.Args.ContainsKey(StartupContext.APPDATA)) { instancePolicy.WarnIfAlreadyRunning(); } else if (isService) { instancePolicy.KillAllOtherInstance(); } else { instancePolicy.PreventStartIfAlreadyRunning(); } } } }