From e5d57bd82f9a089b7c19ea357efd2b8b34fd418b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 2 Jul 2019 12:21:54 +0200 Subject: Move StartupWizard to ASP.NET Web Api --- Jellyfin.Api/Controllers/StartupController.cs | 88 +++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 Jellyfin.Api/Controllers/StartupController.cs (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs new file mode 100644 index 000000000..c17b534eb --- /dev/null +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -0,0 +1,88 @@ +using System.Linq; +using System.Threading.Tasks; +using Jellyfin.Api.Models.Startup; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api.Controllers +{ + [ApiVersion("1")] + [Route("[controller]")] + public class StartupController : ControllerBase + { + private readonly IServerConfigurationManager _config; + private readonly IUserManager _userManager; + + public StartupController(IServerConfigurationManager config, IUserManager userManager) + { + _config = config; + _userManager = userManager; + } + + [HttpPost("Complete")] + public void Post() + { + _config.Configuration.IsStartupWizardCompleted = true; + _config.SetOptimalValues(); + _config.SaveConfiguration(); + } + + [HttpGet("Configuration")] + public StartupConfiguration Get() + { + var result = new StartupConfiguration + { + UICulture = _config.Configuration.UICulture, + MetadataCountryCode = _config.Configuration.MetadataCountryCode, + PreferredMetadataLanguage = _config.Configuration.PreferredMetadataLanguage + }; + + return result; + } + + [HttpPost("Configuration")] + public void UpdateInitial([FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) + { + _config.Configuration.UICulture = uiCulture; + _config.Configuration.MetadataCountryCode = metadataCountryCode; + _config.Configuration.PreferredMetadataLanguage = preferredMetadataLanguage; + _config.SaveConfiguration(); + } + + [HttpPost("RemoteAccess")] + public void Post([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) + { + _config.Configuration.EnableRemoteAccess = enableRemoteAccess; + _config.Configuration.EnableUPnP = enableAutomaticPortMapping; + _config.SaveConfiguration(); + } + + [HttpGet("User")] + public StartupUser GetUser() + { + var user = _userManager.Users.First(); + + return new StartupUser + { + Name = user.Name, + Password = user.Password + }; + } + + [HttpPost("User")] + public async Task Post([FromForm] StartupUser startupUser) + { + var user = _userManager.Users.First(); + + user.Name = startupUser.Name; + + _userManager.UpdateUser(user); + + if (!string.IsNullOrEmpty(startupUser.Password)) + { + await _userManager.ChangePassword(user, startupUser.Password).ConfigureAwait(false); + } + } + } +} -- cgit v1.2.3 From 05b7e2280843f25e48c2300b135f171aee0a54ea Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Tue, 2 Jul 2019 20:17:00 +0200 Subject: Add SwaggerUI --- Emby.Server.Implementations/ApplicationHost.cs | 27 ++++++++++++++++++++++ .../Emby.Server.Implementations.csproj | 4 +++- Jellyfin.Api/Controllers/StartupController.cs | 5 ++-- 3 files changed, 32 insertions(+), 4 deletions(-) (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 11ee6d2d2..3d2d61225 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -113,7 +113,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; +using Microsoft.OpenApi.Models; using ServiceStack; +using Swashbuckle.AspNetCore.SwaggerGen; using OperatingSystem = MediaBrowser.Common.System.OperatingSystem; namespace Emby.Server.Implementations @@ -663,11 +665,36 @@ namespace Emby.Server.Implementations .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) .AddApplicationPart(Assembly.Load("Jellyfin.Api")); services.AddApiVersioning(opt => opt.ReportApiVersions = true); + services.AddSwaggerGen(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); + c.DocInclusionPredicate((docName, apiDesc) => + { + if (!apiDesc.TryGetMethodInfo(out var methodInfo)) + { + return false; + } + + // A bit of a hack to make Swagger pick the versioned endpoints instead of the legacy emby endpoints + return methodInfo.DeclaringType?.BaseType == typeof(ControllerBase) && + apiDesc.RelativePath.Contains("api/v"); + }); + }); + // Merge the external ServiceCollection into ASP.NET DI services.TryAdd(serviceCollection); }) .Configure(app => { + app.UseSwagger(); + + // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), + // specifying the Swagger JSON endpoint. + app.UseSwaggerUI(c => + { + c.SwaggerEndpoint("/swagger/v1/swagger.json", "Jellyfin API V1"); + }); + app.UseWebSockets(); app.UseResponseCompression(); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 23e35f77e..26301b379 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -1,4 +1,4 @@ - + @@ -21,6 +21,7 @@ + @@ -37,6 +38,7 @@ + diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index c17b534eb..45e4cd5ac 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { [ApiVersion("1")] - [Route("[controller]")] public class StartupController : ControllerBase { private readonly IServerConfigurationManager _config; @@ -21,7 +20,7 @@ namespace Jellyfin.Api.Controllers } [HttpPost("Complete")] - public void Post() + public void CompleteWizard() { _config.Configuration.IsStartupWizardCompleted = true; _config.SetOptimalValues(); @@ -71,7 +70,7 @@ namespace Jellyfin.Api.Controllers } [HttpPost("User")] - public async Task Post([FromForm] StartupUser startupUser) + public async Task UpdateUser([FromForm] StartupUser startupUser) { var user = _userManager.Users.First(); -- cgit v1.2.3 From 3f651de24c76f9980fac690e51fa93b3d1163f72 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 23 Nov 2019 16:31:02 +0100 Subject: Add authentication and remove versioning --- Emby.Server.Implementations/ApplicationHost.cs | 58 ++++++++++++++++------ .../Emby.Server.Implementations.csproj | 5 +- .../HttpServer/Security/AuthService.cs | 17 ++++++- Emby.Server.Implementations/MvcRoutePrefix.cs | 2 +- Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 53 ++++++++++++++++++++ .../FirstTimeSetupOrElevatedHandler.cs | 35 +++++++++++++ .../FirstTimeSetupOrElevatedRequirement.cs | 8 +++ .../RequiresElevationHandler.cs | 18 +++++++ .../RequiresElevationRequirement.cs | 9 ++++ Jellyfin.Api/BaseJellyfinApiController.cs | 11 ++++ Jellyfin.Api/Controllers/StartupController.cs | 25 +++++----- Jellyfin.Api/Jellyfin.Api.csproj | 34 ++++++------- .../Models/Startup/StartupConfiguration.cs | 9 ---- .../Models/Startup/StartupConfigurationDto.cs | 9 ++++ Jellyfin.Api/Models/Startup/StartupUser.cs | 8 --- Jellyfin.Api/Models/Startup/StartupUserDto.cs | 8 +++ MediaBrowser.Controller/Net/IAuthService.cs | 3 ++ 17 files changed, 247 insertions(+), 65 deletions(-) create mode 100644 Jellyfin.Api/Auth/CustomAuthenticationHandler.cs create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs create mode 100644 Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs create mode 100644 Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs create mode 100644 Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs create mode 100644 Jellyfin.Api/BaseJellyfinApiController.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupConfiguration.cs create mode 100644 Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupUser.cs create mode 100644 Jellyfin.Api/Models/Startup/StartupUserDto.cs (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 3d2d61225..9227ef61b 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,6 +47,10 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; +using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; +using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Controllers; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -104,11 +108,14 @@ using MediaBrowser.Providers.Subtitles; using MediaBrowser.Providers.TV.TheTVDB; using MediaBrowser.WebDashboard.Api; using MediaBrowser.XbmcMetadata.Providers; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -660,25 +667,45 @@ namespace Emby.Server.Implementations services.AddHttpContextAccessor(); services.AddMvc(opts => { - opts.UseGeneralRoutePrefix("emby", "emby/emby", "api/v{version:apiVersion}"); + var policy = new AuthorizationPolicyBuilder() + .RequireAuthenticatedUser() + .Build(); + opts.Filters.Add(new AuthorizeFilter(policy)); + opts.EnableEndpointRouting = false; + opts.UseGeneralRoutePrefix(ServerConfigurationManager.Configuration.BaseUrl.TrimStart('/')); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) - .AddApplicationPart(Assembly.Load("Jellyfin.Api")); - services.AddApiVersioning(opt => opt.ReportApiVersions = true); + .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) // Clear app parts to avoid other assemblies being picked up + .AddApplicationPart(typeof(StartupController).Assembly) + .AddControllersAsServices(); services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Jellyfin API", Version = "v1" }); - c.DocInclusionPredicate((docName, apiDesc) => - { - if (!apiDesc.TryGetMethodInfo(out var methodInfo)) - { - return false; - } + }); + + services.AddSingleton(); + services.AddSingleton(); - // A bit of a hack to make Swagger pick the versioned endpoints instead of the legacy emby endpoints - return methodInfo.DeclaringType?.BaseType == typeof(ControllerBase) && - apiDesc.RelativePath.Contains("api/v"); - }); + // configure custom legacy authentication + services.AddAuthentication("CustomAuthentication") + .AddScheme("CustomAuthentication", null); + + services.AddAuthorizationCore(options => + { + options.AddPolicy( + "RequiresElevation", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new RequiresElevationRequirement()); + }); + options.AddPolicy( + "FirstTimeSetupOrElevated", + policy => + { + policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); + }); }); // Merge the external ServiceCollection into ASP.NET DI @@ -686,6 +713,7 @@ namespace Emby.Server.Implementations }) .Configure(app => { + app.UseDeveloperExceptionPage(); app.UseSwagger(); // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.), @@ -698,9 +726,9 @@ namespace Emby.Server.Implementations app.UseWebSockets(); app.UseResponseCompression(); - // TODO app.UseMiddleware(); app.Use(ExecuteWebsocketHandlerAsync); + //app.UseAuthentication(); app.UseMvc(); app.Use(ExecuteHttpHandlerAsync); }) @@ -938,7 +966,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(authContext); serviceCollection.AddSingleton(new SessionContext(UserManager, authContext, SessionManager)); - AuthService = new AuthService(authContext, ServerConfigurationManager, SessionManager, NetworkManager); + AuthService = new AuthService(LoggerFactory, authContext, ServerConfigurationManager, SessionManager, NetworkManager); serviceCollection.AddSingleton(AuthService); SubtitleEncoder = new MediaBrowser.MediaEncoding.Subtitles.SubtitleEncoder(LibraryManager, LoggerFactory, ApplicationPaths, FileSystemManager, MediaEncoder, JsonSerializer, HttpClient, MediaSourceManager, ProcessFactory); diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 26301b379..e7164342c 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -21,6 +21,9 @@ + + + @@ -38,7 +41,7 @@ - + diff --git a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs index 93a61fe67..81dab83d5 100644 --- a/Emby.Server.Implementations/HttpServer/Security/AuthService.cs +++ b/Emby.Server.Implementations/HttpServer/Security/AuthService.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Emby.Server.Implementations.SocketSharp; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; @@ -7,22 +8,27 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Security; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Logging; namespace Emby.Server.Implementations.HttpServer.Security { public class AuthService : IAuthService { + private readonly ILogger _logger; private readonly IAuthorizationContext _authorizationContext; private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly INetworkManager _networkManager; public AuthService( + ILoggerFactory loggerFactory, IAuthorizationContext authorizationContext, IServerConfigurationManager config, ISessionManager sessionManager, INetworkManager networkManager) { + _logger = loggerFactory.CreateLogger(); _authorizationContext = authorizationContext; _config = config; _sessionManager = sessionManager; @@ -34,7 +40,14 @@ namespace Emby.Server.Implementations.HttpServer.Security ValidateUser(request, authAttribtues); } - private void ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) + public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes) + { + var req = new WebSocketSharpRequest(request, null, request.Path, _logger); + var user = ValidateUser(req, authAttributes); + return user; + } + + private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues) { // This code is executed before the service var auth = _authorizationContext.GetAuthorizationInfo(request); @@ -81,6 +94,8 @@ namespace Emby.Server.Implementations.HttpServer.Security request.RemoteIp, user); } + + return user; } private void ValidateUserAccess( diff --git a/Emby.Server.Implementations/MvcRoutePrefix.cs b/Emby.Server.Implementations/MvcRoutePrefix.cs index fb26ae09d..974a2a885 100644 --- a/Emby.Server.Implementations/MvcRoutePrefix.cs +++ b/Emby.Server.Implementations/MvcRoutePrefix.cs @@ -12,7 +12,7 @@ namespace Emby.Server.Implementations opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes)); } - internal class RoutePrefixConvention : IApplicationModelConvention + private class RoutePrefixConvention : IApplicationModelConvention { private readonly AttributeRouteModel[] _routePrefixes; diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs new file mode 100644 index 000000000..bb6192b03 --- /dev/null +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -0,0 +1,53 @@ +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using MediaBrowser.Controller.Net; +using Microsoft.AspNetCore.Authentication; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Jellyfin.Api.Auth +{ + public class CustomAuthenticationHandler : AuthenticationHandler + { + private readonly IAuthService _authService; + + public CustomAuthenticationHandler( + IAuthService authService, + IOptionsMonitor options, + ILoggerFactory logger, + UrlEncoder encoder, + ISystemClock clock) : base(options, logger, encoder, clock) + { + _authService = authService; + } + + protected override Task HandleAuthenticateAsync() + { + var authenticatedAttribute = new AuthenticatedAttribute(); + try + { + var user = _authService.Authenticate(Request, authenticatedAttribute); + if (user == null) + { + return Task.FromResult(AuthenticateResult.Fail("Invalid user")); + } + + var claims = new[] + { + new Claim(ClaimTypes.Name, user.Name), + new Claim(ClaimTypes.Role, user.Policy.IsAdministrator ? "Administrator" : "User"), + }; + var identity = new ClaimsIdentity(claims, Scheme.Name); + var principal = new ClaimsPrincipal(identity); + var ticket = new AuthenticationTicket(principal, Scheme.Name); + + return Task.FromResult(AuthenticateResult.Success(ticket)); + } + catch (SecurityException ex) + { + return Task.FromResult(AuthenticateResult.Fail(ex)); + } + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs new file mode 100644 index 000000000..73925cd61 --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using MediaBrowser.Common.Configuration; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy +{ + public class FirstTimeSetupOrElevatedHandler : AuthorizationHandler + { + private readonly IConfigurationManager _configurationManager; + + public FirstTimeSetupOrElevatedHandler(IConfigurationManager configurationManager) + { + _configurationManager = configurationManager; + } + + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement firstTimeSetupOrElevatedRequirement) + { + if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted) + { + context.Succeed(firstTimeSetupOrElevatedRequirement); + } + else if (context.User.IsInRole("Administrator")) + { + // TODO user role enum + context.Succeed(firstTimeSetupOrElevatedRequirement); + } + else + { + context.Fail(); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs new file mode 100644 index 000000000..42436c870 --- /dev/null +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy +{ + public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs new file mode 100644 index 000000000..694827458 --- /dev/null +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs @@ -0,0 +1,18 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.RequiresElevationPolicy +{ + public class RequiresElevationHandler : AuthorizationHandler + { + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement) + { + if (context.User.IsInRole("Administrator")) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs new file mode 100644 index 000000000..dd51cd3c2 --- /dev/null +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.RequiresElevationPolicy +{ + public class RequiresElevationRequirement : IAuthorizationRequirement + { + + } +} diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs new file mode 100644 index 000000000..796a8039a --- /dev/null +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Jellyfin.Api +{ + [ApiController] + [Route("[controller]")] + public class BaseJellyfinApiController : ControllerBase + { + + } +} diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 45e4cd5ac..fb61b8d0b 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -3,12 +3,13 @@ using System.Threading.Tasks; using Jellyfin.Api.Models.Startup; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { - [ApiVersion("1")] - public class StartupController : ControllerBase + [Authorize(Policy = "FirstTimeSetupOrElevated")] + public class StartupController : BaseJellyfinApiController { private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; @@ -28,9 +29,9 @@ namespace Jellyfin.Api.Controllers } [HttpGet("Configuration")] - public StartupConfiguration Get() + public StartupConfigurationDto GetStartupConfiguration() { - var result = new StartupConfiguration + var result = new StartupConfigurationDto { UICulture = _config.Configuration.UICulture, MetadataCountryCode = _config.Configuration.MetadataCountryCode, @@ -41,7 +42,7 @@ namespace Jellyfin.Api.Controllers } [HttpPost("Configuration")] - public void UpdateInitial([FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) + public void UpdateInitialConfiguration([FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) { _config.Configuration.UICulture = uiCulture; _config.Configuration.MetadataCountryCode = metadataCountryCode; @@ -50,7 +51,7 @@ namespace Jellyfin.Api.Controllers } [HttpPost("RemoteAccess")] - public void Post([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) + public void SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) { _config.Configuration.EnableRemoteAccess = enableRemoteAccess; _config.Configuration.EnableUPnP = enableAutomaticPortMapping; @@ -58,11 +59,11 @@ namespace Jellyfin.Api.Controllers } [HttpGet("User")] - public StartupUser GetUser() + public StartupUserDto GetUser() { var user = _userManager.Users.First(); - return new StartupUser + return new StartupUserDto { Name = user.Name, Password = user.Password @@ -70,17 +71,17 @@ namespace Jellyfin.Api.Controllers } [HttpPost("User")] - public async Task UpdateUser([FromForm] StartupUser startupUser) + public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) { var user = _userManager.Users.First(); - user.Name = startupUser.Name; + user.Name = startupUserDto.Name; _userManager.UpdateUser(user); - if (!string.IsNullOrEmpty(startupUser.Password)) + if (!string.IsNullOrEmpty(startupUserDto.Password)) { - await _userManager.ChangePassword(user, startupUser.Password).ConfigureAwait(false); + await _userManager.ChangePassword(user, startupUserDto.Password).ConfigureAwait(false); } } } diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 7a7e49e30..647004cb6 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -1,18 +1,16 @@ - - - - netstandard2.0 - Library - - - - - - - - - - - - - + + + + netstandard2.1 + + + + + + + + + + + + diff --git a/Jellyfin.Api/Models/Startup/StartupConfiguration.cs b/Jellyfin.Api/Models/Startup/StartupConfiguration.cs deleted file mode 100644 index 08dd59a17..000000000 --- a/Jellyfin.Api/Models/Startup/StartupConfiguration.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - public class StartupConfiguration - { - public string UICulture { get; set; } - public string MetadataCountryCode { get; set; } - public string PreferredMetadataLanguage { get; set; } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs new file mode 100644 index 000000000..769d2e1bb --- /dev/null +++ b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs @@ -0,0 +1,9 @@ +namespace Jellyfin.Api.Models.Startup +{ + public class StartupConfigurationDto + { + public string UICulture { get; set; } + public string MetadataCountryCode { get; set; } + public string PreferredMetadataLanguage { get; set; } + } +} diff --git a/Jellyfin.Api/Models/Startup/StartupUser.cs b/Jellyfin.Api/Models/Startup/StartupUser.cs deleted file mode 100644 index 93a09e865..000000000 --- a/Jellyfin.Api/Models/Startup/StartupUser.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - public class StartupUser - { - public string Name { get; set; } - public string Password { get; set; } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupUserDto.cs b/Jellyfin.Api/Models/Startup/StartupUserDto.cs new file mode 100644 index 000000000..c7c2e8cb0 --- /dev/null +++ b/Jellyfin.Api/Models/Startup/StartupUserDto.cs @@ -0,0 +1,8 @@ +namespace Jellyfin.Api.Models.Startup +{ + public class StartupUserDto + { + public string Name { get; set; } + public string Password { get; set; } + } +} diff --git a/MediaBrowser.Controller/Net/IAuthService.cs b/MediaBrowser.Controller/Net/IAuthService.cs index 142f1d91c..4c9120e0c 100644 --- a/MediaBrowser.Controller/Net/IAuthService.cs +++ b/MediaBrowser.Controller/Net/IAuthService.cs @@ -1,9 +1,12 @@ +using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Services; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller.Net { public interface IAuthService { void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues); + User Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues); } } -- cgit v1.2.3 From c9669a0d21f37fe06a8838c001a6f93505ba549b Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 23 Nov 2019 19:59:45 +0100 Subject: Split a long line --- Jellyfin.Api/Controllers/StartupController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index fb61b8d0b..271745e05 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -42,7 +42,10 @@ namespace Jellyfin.Api.Controllers } [HttpPost("Configuration")] - public void UpdateInitialConfiguration([FromForm] string uiCulture, [FromForm] string metadataCountryCode, [FromForm] string preferredMetadataLanguage) + public void UpdateInitialConfiguration( + [FromForm] string uiCulture, + [FromForm] string metadataCountryCode, + [FromForm] string preferredMetadataLanguage) { _config.Configuration.UICulture = uiCulture; _config.Configuration.MetadataCountryCode = metadataCountryCode; -- cgit v1.2.3 From c2cdbc909ba8371261bb88b5dd313262be755fa3 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sat, 23 Nov 2019 20:31:17 +0100 Subject: Add style rules and fix it all --- Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 17 +++++++++- .../FirstTimeSetupOrElevatedHandler.cs | 14 ++++++-- .../FirstTimeSetupOrElevatedRequirement.cs | 3 ++ .../RequiresElevationHandler.cs | 7 +++- .../RequiresElevationRequirement.cs | 4 ++- Jellyfin.Api/BaseJellyfinApiController.cs | 4 ++- Jellyfin.Api/Controllers/StartupController.cs | 37 +++++++++++++++++++++- Jellyfin.Api/Enums/UserRole.cs | 23 ++++++++++++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 8 +++++ .../Extensions/ApiServiceCollectionExtensions.cs | 26 ++++++++++++++- Jellyfin.Api/Jellyfin.Api.csproj | 14 ++++++++ .../Models/Startup/StartupConfigurationDto.cs | 14 ++++++++ Jellyfin.Api/Models/Startup/StartupUserDto.cs | 10 ++++++ Jellyfin.Api/MvcRoutePrefix.cs | 10 +++++- 14 files changed, 181 insertions(+), 10 deletions(-) create mode 100644 Jellyfin.Api/Enums/UserRole.cs (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index bb6192b03..a753d6083 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -1,6 +1,7 @@ using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; +using Jellyfin.Api.Enums; using MediaBrowser.Controller.Net; using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; @@ -8,10 +9,21 @@ using Microsoft.Extensions.Options; namespace Jellyfin.Api.Auth { + /// + /// Custom authentication handler wrapping the legacy authentication. + /// public class CustomAuthenticationHandler : AuthenticationHandler { private readonly IAuthService _authService; + /// + /// Initializes a new instance of the class. + /// + /// The jellyfin authentication service. + /// Options monitor. + /// The logger. + /// The url encoder. + /// The system clock. public CustomAuthenticationHandler( IAuthService authService, IOptionsMonitor options, @@ -22,6 +34,7 @@ namespace Jellyfin.Api.Auth _authService = authService; } + /// protected override Task HandleAuthenticateAsync() { var authenticatedAttribute = new AuthenticatedAttribute(); @@ -36,7 +49,9 @@ namespace Jellyfin.Api.Auth var claims = new[] { new Claim(ClaimTypes.Name, user.Name), - new Claim(ClaimTypes.Role, user.Policy.IsAdministrator ? "Administrator" : "User"), + new Claim( + ClaimTypes.Role, + value: user.Policy.IsAdministrator ? UserRole.Administrator.ToString() : UserRole.User.ToString()) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs index 73925cd61..f07e568de 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs @@ -1,27 +1,35 @@ using System.Threading.Tasks; +using Jellyfin.Api.Enums; using MediaBrowser.Common.Configuration; using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy { + /// + /// Authorization handler for requiring first time setup or elevated privileges. + /// public class FirstTimeSetupOrElevatedHandler : AuthorizationHandler { private readonly IConfigurationManager _configurationManager; + /// + /// Initializes a new instance of the class. + /// + /// The jellyfin configuration manager. public FirstTimeSetupOrElevatedHandler(IConfigurationManager configurationManager) { _configurationManager = configurationManager; } - protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement firstTimeSetupOrElevatedRequirement) + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement firstTimeSetupOrElevatedRequirement) { if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted) { context.Succeed(firstTimeSetupOrElevatedRequirement); } - else if (context.User.IsInRole("Administrator")) + else if (context.User.IsInRole(UserRole.Administrator.ToString())) { - // TODO user role enum context.Succeed(firstTimeSetupOrElevatedRequirement); } else diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs index 42436c870..a59015542 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs @@ -2,6 +2,9 @@ using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy { + /// + /// The authorization requirement, requiring first time setup or elevated privileges, for the authorization handler. + /// public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement { } diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs index 694827458..8674f3e26 100644 --- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs @@ -1,13 +1,18 @@ using System.Threading.Tasks; +using Jellyfin.Api.Enums; using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Api.Auth.RequiresElevationPolicy { + /// + /// Authorization handler for requiring elevated privileges. + /// public class RequiresElevationHandler : AuthorizationHandler { + /// protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement) { - if (context.User.IsInRole("Administrator")) + if (context.User.IsInRole(UserRole.Administrator.ToString())) { context.Succeed(requirement); } diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs index dd51cd3c2..cfff1cc0c 100644 --- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs @@ -2,8 +2,10 @@ using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Api.Auth.RequiresElevationPolicy { + /// + /// The authorization requirement for requiring elevated privileges in the authorization handler. + /// public class RequiresElevationRequirement : IAuthorizationRequirement { - } } diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs index 796a8039a..1f4508e6c 100644 --- a/Jellyfin.Api/BaseJellyfinApiController.cs +++ b/Jellyfin.Api/BaseJellyfinApiController.cs @@ -2,10 +2,12 @@ using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api { + /// + /// Base api controller for the API setting a default route. + /// [ApiController] [Route("[controller]")] public class BaseJellyfinApiController : ControllerBase { - } } diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 271745e05..0e7d17a27 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -8,18 +8,29 @@ using Microsoft.AspNetCore.Mvc; namespace Jellyfin.Api.Controllers { + /// + /// The startup wizard controller. + /// [Authorize(Policy = "FirstTimeSetupOrElevated")] public class StartupController : BaseJellyfinApiController { private readonly IServerConfigurationManager _config; private readonly IUserManager _userManager; + /// + /// Initializes a new instance of the class. + /// + /// The server configuration manager. + /// The user manager. public StartupController(IServerConfigurationManager config, IUserManager userManager) { _config = config; _userManager = userManager; } + /// + /// Api endpoint for completing the startup wizard. + /// [HttpPost("Complete")] public void CompleteWizard() { @@ -28,6 +39,10 @@ namespace Jellyfin.Api.Controllers _config.SaveConfiguration(); } + /// + /// Endpoint for getting the initial startup wizard configuration. + /// + /// The initial startup wizard configuration. [HttpGet("Configuration")] public StartupConfigurationDto GetStartupConfiguration() { @@ -41,6 +56,12 @@ namespace Jellyfin.Api.Controllers return result; } + /// + /// Endpoint for updating the initial startup wizard configuration. + /// + /// The UI language culture. + /// The metadata country code. + /// The preferred language for metadata. [HttpPost("Configuration")] public void UpdateInitialConfiguration( [FromForm] string uiCulture, @@ -53,6 +74,11 @@ namespace Jellyfin.Api.Controllers _config.SaveConfiguration(); } + /// + /// Endpoint for (dis)allowing remote access and UPnP. + /// + /// Enable remote access. + /// Enable UPnP. [HttpPost("RemoteAccess")] public void SetRemoteAccess([FromForm] bool enableRemoteAccess, [FromForm] bool enableAutomaticPortMapping) { @@ -61,8 +87,12 @@ namespace Jellyfin.Api.Controllers _config.SaveConfiguration(); } + /// + /// Endpoint for returning the first user. + /// + /// The first user. [HttpGet("User")] - public StartupUserDto GetUser() + public StartupUserDto GetFirstUser() { var user = _userManager.Users.First(); @@ -73,6 +103,11 @@ namespace Jellyfin.Api.Controllers }; } + /// + /// Endpoint for updating the user name and password. + /// + /// The DTO containing username and password. + /// The async task. [HttpPost("User")] public async Task UpdateUser([FromForm] StartupUserDto startupUserDto) { diff --git a/Jellyfin.Api/Enums/UserRole.cs b/Jellyfin.Api/Enums/UserRole.cs new file mode 100644 index 000000000..05826d9f4 --- /dev/null +++ b/Jellyfin.Api/Enums/UserRole.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Enums +{ + /// + /// Enum for user roles used in the authentication and authorization for the API. + /// + public enum UserRole + { + /// + /// Guest user. + /// + Guest = 0, + + /// + /// Regular user with no special privileges. + /// + User = 1, + + /// + /// Administrator user with elevated privileges. + /// + Administrator = 2 + } +} diff --git a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs index 18442bf27..f70466ebe 100644 --- a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs +++ b/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs @@ -2,8 +2,16 @@ using Microsoft.AspNetCore.Builder; namespace Jellyfin.Api.Extensions { + /// + /// Extensions for adding API specific functionality to the application pipeline. + /// public static class ApiApplicationBuilderExtensions { + /// + /// Adds swagger and swagger UI to the application pipeline. + /// + /// The application builder. + /// The updated application builder. public static IApplicationBuilder UseJellyfinApiSwagger(this IApplicationBuilder applicationBuilder) { applicationBuilder.UseSwagger(); diff --git a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs index 1c682f8e4..38f5f6d39 100644 --- a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs @@ -1,4 +1,3 @@ -using Emby.Server.Implementations; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; @@ -12,8 +11,16 @@ using Microsoft.OpenApi.Models; namespace Jellyfin.Api.Extensions { + /// + /// API specific extensions for the service collection. + /// public static class ApiServiceCollectionExtensions { + /// + /// Adds jellyfin API authorization policies to the DI container. + /// + /// The service collection. + /// The updated service collection. public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) { serviceCollection.AddSingleton(); @@ -37,12 +44,23 @@ namespace Jellyfin.Api.Extensions }); } + /// + /// Adds custom legacy authentication to the service collection. + /// + /// The service collection. + /// The updated service collection. public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) { return serviceCollection.AddAuthentication("CustomAuthentication") .AddScheme("CustomAuthentication", null); } + /// + /// Extension method for adding the jellyfin API to the service collection. + /// + /// The service collection. + /// The base url for the API. + /// The MVC builder. public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) { return serviceCollection.AddMvc(opts => @@ -55,12 +73,18 @@ namespace Jellyfin.Api.Extensions opts.UseGeneralRoutePrefix(baseUrl); }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) + // Clear app parts to avoid other assemblies being picked up .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) .AddApplicationPart(typeof(StartupController).Assembly) .AddControllersAsServices(); } + /// + /// Adds Swagger to the service collection. + /// + /// The service collection. + /// The updated service collection. public static IServiceCollection AddJellyfinApiSwagger(this IServiceCollection serviceCollection) { return serviceCollection.AddSwaggerGen(c => diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index d77861cc4..1cc23c07b 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -2,6 +2,8 @@ netstandard2.1 + true + true @@ -15,4 +17,16 @@ + + + + + + + + + + ../jellyfin.ruleset + + diff --git a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs index 769d2e1bb..dac15e412 100644 --- a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs +++ b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs @@ -1,9 +1,23 @@ namespace Jellyfin.Api.Models.Startup { + /// + /// The startup configuration DTO. + /// public class StartupConfigurationDto { + /// + /// Gets or sets UI language culture. + /// public string UICulture { get; set; } + + /// + /// Gets or sets the metadata country code. + /// public string MetadataCountryCode { get; set; } + + /// + /// Gets or sets the preferred language for the metadata. + /// public string PreferredMetadataLanguage { get; set; } } } diff --git a/Jellyfin.Api/Models/Startup/StartupUserDto.cs b/Jellyfin.Api/Models/Startup/StartupUserDto.cs index c7c2e8cb0..7e890d76a 100644 --- a/Jellyfin.Api/Models/Startup/StartupUserDto.cs +++ b/Jellyfin.Api/Models/Startup/StartupUserDto.cs @@ -1,8 +1,18 @@ namespace Jellyfin.Api.Models.Startup { + /// + /// The startup user DTO. + /// public class StartupUserDto { + /// + /// Gets or sets the username. + /// public string Name { get; set; } + + /// + /// Gets or sets the user's password. + /// public string Password { get; set; } } } diff --git a/Jellyfin.Api/MvcRoutePrefix.cs b/Jellyfin.Api/MvcRoutePrefix.cs index 974a2a885..e00973094 100644 --- a/Jellyfin.Api/MvcRoutePrefix.cs +++ b/Jellyfin.Api/MvcRoutePrefix.cs @@ -3,10 +3,18 @@ using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationModels; -namespace Emby.Server.Implementations +namespace Jellyfin.Api { + /// + /// Route prefixing for ASP.NET MVC. + /// public static class MvcRoutePrefix { + /// + /// Adds route prefixes to the MVC conventions. + /// + /// The MVC options. + /// The list of prefixes. public static void UseGeneralRoutePrefix(this MvcOptions opts, params string[] prefixes) { opts.Conventions.Insert(0, new RoutePrefixConvention(prefixes)); -- cgit v1.2.3 From 27e3cf15588f8ab8fe19aa611d79fa2ccd8ecda8 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Nov 2019 15:27:58 +0100 Subject: Move appbuilder and service collection to Jellyfin.Server --- Emby.Server.Implementations/ApplicationHost.cs | 110 +++------------------ .../HttpServer/HttpListenerHost.cs | 3 +- .../Session/SessionWebSocketListener.cs | 4 +- Jellyfin.Api/Controllers/StartupController.cs | 2 +- .../Extensions/ApiApplicationBuilderExtensions.cs | 27 ----- .../Extensions/ApiServiceCollectionExtensions.cs | 96 ------------------ .../Models/Startup/StartupConfigurationDto.cs | 23 ----- Jellyfin.Api/Models/Startup/StartupUserDto.cs | 18 ---- .../Models/StartupDtos/StartupConfigurationDto.cs | 23 +++++ Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs | 18 ++++ .../Extensions/ApiApplicationBuilderExtensions.cs | 27 +++++ .../Extensions/ApiServiceCollectionExtensions.cs | 89 +++++++++++++++++ Jellyfin.Server/Jellyfin.Server.csproj | 4 + Jellyfin.Server/Program.cs | 70 ++++++++++++- Jellyfin.Server/Startup.cs | 81 +++++++++++++++ MediaBrowser.Controller/IServerApplicationHost.cs | 5 + 16 files changed, 336 insertions(+), 264 deletions(-) delete mode 100644 Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs delete mode 100644 Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs delete mode 100644 Jellyfin.Api/Models/Startup/StartupUserDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs create mode 100644 Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs create mode 100644 Jellyfin.Server/Extensions/ApiApplicationBuilderExtensions.cs create mode 100644 Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs create mode 100644 Jellyfin.Server/Startup.cs (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index c6cdd4786..3b9ea4121 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -47,7 +47,6 @@ using Emby.Server.Implementations.Session; using Emby.Server.Implementations.SocketSharp; using Emby.Server.Implementations.TV; using Emby.Server.Implementations.Updates; -using Jellyfin.Api.Extensions; using MediaBrowser.Api; using MediaBrowser.Common; using MediaBrowser.Common.Configuration; @@ -232,7 +231,7 @@ namespace Emby.Server.Implementations } } - protected IServiceProvider _serviceProvider; + public IServiceProvider ServiceProvider; /// /// Gets the server configuration manager. @@ -461,7 +460,7 @@ namespace Emby.Server.Implementations /// The type. /// System.Object. public object CreateInstance(Type type) - => ActivatorUtilities.CreateInstance(_serviceProvider, type); + => ActivatorUtilities.CreateInstance(ServiceProvider, type); /// /// Creates an instance of type and resolves all constructor dependencies @@ -469,7 +468,7 @@ namespace Emby.Server.Implementations /// /// The type. /// T. public T CreateInstance() - => ActivatorUtilities.CreateInstance(_serviceProvider); + => ActivatorUtilities.CreateInstance(ServiceProvider); /// /// Creates the instance safe. @@ -481,7 +480,7 @@ namespace Emby.Server.Implementations try { Logger.LogDebug("Creating instance of {Type}", type); - return ActivatorUtilities.CreateInstance(_serviceProvider, type); + return ActivatorUtilities.CreateInstance(ServiceProvider, type); } catch (Exception ex) { @@ -495,7 +494,7 @@ namespace Emby.Server.Implementations /// /// The type /// ``0. - public T Resolve() => _serviceProvider.GetService(); + public T Resolve() => ServiceProvider.GetService(); /// /// Gets the export types. @@ -611,93 +610,14 @@ namespace Emby.Server.Implementations await RegisterResources(serviceCollection).ConfigureAwait(false); - string contentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; - if (string.IsNullOrEmpty(contentRoot)) + ContentRoot = ServerConfigurationManager.Configuration.DashboardSourcePath; + if (string.IsNullOrEmpty(ContentRoot)) { - contentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; - } - - var host = new WebHostBuilder() - .UseKestrel(options => - { - var addresses = ServerConfigurationManager - .Configuration - .LocalNetworkAddresses - .Select(NormalizeConfiguredLocalAddress) - .Where(i => i != null) - .ToList(); - if (addresses.Any()) - { - foreach (var address in addresses) - { - Logger.LogInformation("Kestrel listening on {ipaddr}", address); - options.Listen(address, HttpPort); - - if (EnableHttps && Certificate != null) - { - options.Listen(address, HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - } - else - { - Logger.LogInformation("Kestrel listening on all interfaces"); - options.ListenAnyIP(HttpPort); - - if (EnableHttps && Certificate != null) - { - options.ListenAnyIP(HttpsPort, listenOptions => listenOptions.UseHttps(Certificate)); - } - } - }) - .UseContentRoot(contentRoot) - .ConfigureServices(services => - { - services.AddResponseCompression(); - services.AddHttpContextAccessor(); - services.AddJellyfinApi(ServerConfigurationManager.Configuration.BaseUrl.TrimStart('/')); - - services.AddJellyfinApiSwagger(); - - // configure custom legacy authentication - services.AddCustomAuthentication(); - - services.AddJellyfinApiAuthorization(); - - // Merge the external ServiceCollection into ASP.NET DI - services.TryAdd(serviceCollection); - }) - .Configure(app => - { - app.UseWebSockets(); - - app.UseResponseCompression(); - - // TODO app.UseMiddleware(); - app.Use(ExecuteWebsocketHandlerAsync); - - // TODO use when old API is removed: app.UseAuthentication(); - app.UseJellyfinApiSwagger(); - app.UseMvc(); - app.Use(ExecuteHttpHandlerAsync); - }) - .Build(); - - _serviceProvider = host.Services; - 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; + ContentRoot = ServerConfigurationManager.ApplicationPaths.WebPath; } } - private async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) + public async Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next) { if (!context.WebSockets.IsWebSocketRequest) { @@ -708,7 +628,7 @@ namespace Emby.Server.Implementations await HttpServer.ProcessWebSocketRequest(context).ConfigureAwait(false); } - private async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) + public async Task ExecuteHttpHandlerAsync(HttpContext context, Func next) { if (context.WebSockets.IsWebSocketRequest) { @@ -1090,9 +1010,9 @@ namespace Emby.Server.Implementations /// /// Finds the parts. /// - protected void FindParts() + public void FindParts() { - InstallationManager = _serviceProvider.GetService(); + InstallationManager = ServiceProvider.GetService(); InstallationManager.PluginInstalled += PluginInstalled; if (!ServerConfigurationManager.Configuration.IsPortAuthorized) @@ -1221,7 +1141,7 @@ namespace Emby.Server.Implementations private CertificateInfo CertificateInfo { get; set; } - protected X509Certificate2 Certificate { get; private set; } + public X509Certificate2 Certificate { get; private set; } private IEnumerable GetUrlPrefixes() { @@ -1605,7 +1525,7 @@ namespace Emby.Server.Implementations return resultList; } - private IPAddress NormalizeConfiguredLocalAddress(string address) + public IPAddress NormalizeConfiguredLocalAddress(string address) { var index = address.Trim('/').IndexOf('/'); @@ -1685,6 +1605,8 @@ namespace Emby.Server.Implementations public int HttpsPort { get; private set; } + public string ContentRoot { get; private set; } + /// /// Shuts down. /// diff --git a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs index dc1a56e27..6dd016f8a 100644 --- a/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs +++ b/Emby.Server.Implementations/HttpServer/HttpListenerHost.cs @@ -18,7 +18,6 @@ using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; @@ -164,7 +163,7 @@ namespace Emby.Server.Implementations.HttpServer { OnReceive = ProcessWebSocketMessageReceived, Url = e.Url, - QueryString = e.QueryString ?? new QueryCollection() + QueryString = e.QueryString }; connection.Closed += OnConnectionClosed; diff --git a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs index 63ec75762..930f2d35d 100644 --- a/Emby.Server.Implementations/Session/SessionWebSocketListener.cs +++ b/Emby.Server.Implementations/Session/SessionWebSocketListener.cs @@ -4,7 +4,6 @@ using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Events; using MediaBrowser.Model.Serialization; -using MediaBrowser.Model.Services; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -67,7 +66,7 @@ namespace Emby.Server.Implementations.Session { if (queryString == null) { - throw new ArgumentNullException(nameof(queryString)); + return null; } var token = queryString["api_key"]; @@ -75,6 +74,7 @@ namespace Emby.Server.Implementations.Session { return null; } + var deviceId = queryString["deviceId"]; return _sessionManager.GetSessionByAuthenticationToken(token, deviceId, remoteEndpoint); } diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 0e7d17a27..50f3dc83c 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -1,6 +1,6 @@ using System.Linq; using System.Threading.Tasks; -using Jellyfin.Api.Models.Startup; +using Jellyfin.Api.Models.StartupDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; using Microsoft.AspNetCore.Authorization; diff --git a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs b/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs deleted file mode 100644 index f70466ebe..000000000 --- a/Jellyfin.Api/Extensions/ApiApplicationBuilderExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Microsoft.AspNetCore.Builder; - -namespace Jellyfin.Api.Extensions -{ - /// - /// Extensions for adding API specific functionality to the application pipeline. - /// - public static class ApiApplicationBuilderExtensions - { - /// - /// Adds swagger and swagger UI to the application pipeline. - /// - /// The application builder. - /// The updated application builder. - 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.Api/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs deleted file mode 100644 index 38f5f6d39..000000000 --- a/Jellyfin.Api/Extensions/ApiServiceCollectionExtensions.cs +++ /dev/null @@ -1,96 +0,0 @@ -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.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Authorization; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.OpenApi.Models; - -namespace Jellyfin.Api.Extensions -{ - /// - /// API specific extensions for the service collection. - /// - public static class ApiServiceCollectionExtensions - { - /// - /// Adds jellyfin API authorization policies to the DI container. - /// - /// The service collection. - /// The updated service collection. - public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) - { - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - 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()); - }); - }); - } - - /// - /// Adds custom legacy authentication to the service collection. - /// - /// The service collection. - /// The updated service collection. - public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) - { - return serviceCollection.AddAuthentication("CustomAuthentication") - .AddScheme("CustomAuthentication", null); - } - - /// - /// Extension method for adding the jellyfin API to the service collection. - /// - /// The service collection. - /// The base url for the API. - /// The MVC builder. - public static IMvcBuilder AddJellyfinApi(this IServiceCollection serviceCollection, string baseUrl) - { - return serviceCollection.AddMvc(opts => - { - var policy = new AuthorizationPolicyBuilder() - .RequireAuthenticatedUser() - .Build(); - opts.Filters.Add(new AuthorizeFilter(policy)); - opts.EnableEndpointRouting = false; - opts.UseGeneralRoutePrefix(baseUrl); - }) - .SetCompatibilityVersion(CompatibilityVersion.Version_2_2) - - // Clear app parts to avoid other assemblies being picked up - .ConfigureApplicationPartManager(a => a.ApplicationParts.Clear()) - .AddApplicationPart(typeof(StartupController).Assembly) - .AddControllersAsServices(); - } - - /// - /// Adds Swagger to the service collection. - /// - /// The service collection. - /// The updated service collection. - 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.Api/Models/Startup/StartupConfigurationDto.cs b/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs deleted file mode 100644 index dac15e412..000000000 --- a/Jellyfin.Api/Models/Startup/StartupConfigurationDto.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup configuration DTO. - /// - public class StartupConfigurationDto - { - /// - /// Gets or sets UI language culture. - /// - public string UICulture { get; set; } - - /// - /// Gets or sets the metadata country code. - /// - public string MetadataCountryCode { get; set; } - - /// - /// Gets or sets the preferred language for the metadata. - /// - public string PreferredMetadataLanguage { get; set; } - } -} diff --git a/Jellyfin.Api/Models/Startup/StartupUserDto.cs b/Jellyfin.Api/Models/Startup/StartupUserDto.cs deleted file mode 100644 index 7e890d76a..000000000 --- a/Jellyfin.Api/Models/Startup/StartupUserDto.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Jellyfin.Api.Models.Startup -{ - /// - /// The startup user DTO. - /// - public class StartupUserDto - { - /// - /// Gets or sets the username. - /// - public string Name { get; set; } - - /// - /// Gets or sets the user's password. - /// - public string Password { get; set; } - } -} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs new file mode 100644 index 000000000..d048dad0a --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupConfigurationDto.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup configuration DTO. + /// + public class StartupConfigurationDto + { + /// + /// Gets or sets UI language culture. + /// + public string UICulture { get; set; } + + /// + /// Gets or sets the metadata country code. + /// + public string MetadataCountryCode { get; set; } + + /// + /// Gets or sets the preferred language for the metadata. + /// + public string PreferredMetadataLanguage { get; set; } + } +} diff --git a/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs new file mode 100644 index 000000000..3a9348037 --- /dev/null +++ b/Jellyfin.Api/Models/StartupDtos/StartupUserDto.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Models.StartupDtos +{ + /// + /// The startup user DTO. + /// + public class StartupUserDto + { + /// + /// Gets or sets the username. + /// + public string Name { get; set; } + + /// + /// Gets or sets the user's password. + /// + public string Password { get; set; } + } +} 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 +{ + /// + /// Extensions for adding API specific functionality to the application pipeline. + /// + public static class ApiApplicationBuilderExtensions + { + /// + /// Adds swagger and swagger UI to the application pipeline. + /// + /// The application builder. + /// The updated application builder. + 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 +{ + /// + /// API specific extensions for the service collection. + /// + public static class ApiServiceCollectionExtensions + { + /// + /// Adds jellyfin API authorization policies to the DI container. + /// + /// The service collection. + /// The updated service collection. + public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) + { + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + 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()); + }); + }); + } + + /// + /// Adds custom legacy authentication to the service collection. + /// + /// The service collection. + /// The updated service collection. + public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) + { + return serviceCollection.AddAuthentication("CustomAuthentication") + .AddScheme("CustomAuthentication", null); + } + + /// + /// Extension method for adding the jellyfin API to the service collection. + /// + /// The service collection. + /// The base url for the API. + /// The MVC builder. + 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(); + } + + /// + /// Adds Swagger to the service collection. + /// + /// The service collection. + /// The updated service collection. + 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 @@ + + + + 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(); + } + /// /// 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 +{ + /// + /// Startup configuration for the Kestrel webhost. + /// + public class Startup + { + private readonly IServerConfigurationManager _serverConfigurationManager; + + /// + /// Initializes a new instance of the class. + /// + /// The server configuration manager. + public Startup(IServerConfigurationManager serverConfigurationManager) + { + _serverConfigurationManager = serverConfigurationManager; + } + + /// + /// Configures the service collection for the webhost. + /// + /// The service collection. + 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(); + } + + /// + /// Configures the app builder for the webhost. + /// + /// The application builder. + /// The webhost environment. + /// The server application host. + public void Configure( + IApplicationBuilder app, + IWebHostEnvironment env, + IServerApplicationHost serverApplicationHost) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseWebSockets(); + + app.UseResponseCompression(); + + // TODO app.UseMiddleware(); + 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); + } + } +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 61b2c15ae..b3c56bdd5 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Common; using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller { @@ -87,5 +88,9 @@ namespace MediaBrowser.Controller string ExpandVirtualPath(string path); string ReverseVirtualPath(string path); + + Task ExecuteHttpHandlerAsync(HttpContext context, Func next); + + Task ExecuteWebsocketHandlerAsync(HttpContext context, Func next); } } -- cgit v1.2.3 From 47a4f2f387825d9c249c53b3796a43e3eac52b58 Mon Sep 17 00:00:00 2001 From: Claus Vium Date: Sun, 24 Nov 2019 19:25:46 +0100 Subject: Fix more review comments --- Emby.Server.Implementations/ApplicationHost.cs | 21 ++++++++++++++------ Jellyfin.Api/Auth/CustomAuthenticationHandler.cs | 2 +- .../FirstTimeSetupOrElevatedHandler.cs | 2 +- .../RequiresElevationHandler.cs | 2 +- Jellyfin.Api/Constants/AuthenticationSchemes.cs | 13 ++++++++++++ Jellyfin.Api/Constants/Policies.cs | 18 +++++++++++++++++ Jellyfin.Api/Constants/UserRole.cs | 23 ---------------------- Jellyfin.Api/Constants/UserRoles.cs | 23 ++++++++++++++++++++++ Jellyfin.Api/Controllers/StartupController.cs | 3 ++- Jellyfin.Api/Jellyfin.Api.csproj | 6 +++--- .../Extensions/ApiServiceCollectionExtensions.cs | 13 ++++++------ 11 files changed, 84 insertions(+), 42 deletions(-) create mode 100644 Jellyfin.Api/Constants/AuthenticationSchemes.cs create mode 100644 Jellyfin.Api/Constants/Policies.cs delete mode 100644 Jellyfin.Api/Constants/UserRole.cs create mode 100644 Jellyfin.Api/Constants/UserRoles.cs (limited to 'Jellyfin.Api/Controllers/StartupController.cs') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 4fd08258a..c5f8b58c4 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -236,6 +236,21 @@ namespace Emby.Server.Implementations /// public IServiceProvider ServiceProvider { get; set; } + /// + /// Gets the http port for the webhost. + /// + public int HttpPort { get; private set; } + + /// + /// Gets the https port for the webhost. + /// + public int HttpsPort { get; private set; } + + /// + /// Gets the content root for the webhost. + /// + public string ContentRoot { get; private set; } + /// /// Gets the server configuration manager. /// @@ -1604,12 +1619,6 @@ namespace Emby.Server.Implementations ? Environment.MachineName : ServerConfigurationManager.Configuration.ServerName; - public int HttpPort { get; private set; } - - public int HttpsPort { get; private set; } - - public string ContentRoot { get; private set; } - /// /// Shuts down. /// diff --git a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs index 6ca992c61..26f7d9d2d 100644 --- a/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs +++ b/Jellyfin.Api/Auth/CustomAuthenticationHandler.cs @@ -51,7 +51,7 @@ namespace Jellyfin.Api.Auth new Claim(ClaimTypes.Name, user.Name), new Claim( ClaimTypes.Role, - value: user.Policy.IsAdministrator ? UserRole.Administrator : UserRole.User) + value: user.Policy.IsAdministrator ? UserRoles.Administrator : UserRoles.User) }; var identity = new ClaimsIdentity(claims, Scheme.Name); var principal = new ClaimsPrincipal(identity); diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs index 2450e7bc7..34aa5d12c 100644 --- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs +++ b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs @@ -28,7 +28,7 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy { context.Succeed(firstTimeSetupOrElevatedRequirement); } - else if (context.User.IsInRole(UserRole.Administrator)) + else if (context.User.IsInRole(UserRoles.Administrator)) { context.Succeed(firstTimeSetupOrElevatedRequirement); } diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs index 108c29a2c..2d3bb1aa4 100644 --- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs +++ b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs @@ -12,7 +12,7 @@ namespace Jellyfin.Api.Auth.RequiresElevationPolicy /// protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement) { - if (context.User.IsInRole(UserRole.Administrator)) + if (context.User.IsInRole(UserRoles.Administrator)) { context.Succeed(requirement); } diff --git a/Jellyfin.Api/Constants/AuthenticationSchemes.cs b/Jellyfin.Api/Constants/AuthenticationSchemes.cs new file mode 100644 index 000000000..bac3379e7 --- /dev/null +++ b/Jellyfin.Api/Constants/AuthenticationSchemes.cs @@ -0,0 +1,13 @@ +namespace Jellyfin.Api.Constants +{ + /// + /// Authentication schemes for user authentication in the API. + /// + public static class AuthenticationSchemes + { + /// + /// Scheme name for the custom legacy authentication. + /// + public const string CustomAuthentication = "CustomAuthentication"; + } +} diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs new file mode 100644 index 000000000..e2b383f75 --- /dev/null +++ b/Jellyfin.Api/Constants/Policies.cs @@ -0,0 +1,18 @@ +namespace Jellyfin.Api.Constants +{ + /// + /// Policies for the API authorization. + /// + public static class Policies + { + /// + /// Policy name for requiring first time setup or elevated privileges. + /// + public const string FirstTimeSetupOrElevated = "FirstTimeOrElevated"; + + /// + /// Policy name for requiring elevated privileges. + /// + public const string RequiresElevation = "RequiresElevation"; + } +} diff --git a/Jellyfin.Api/Constants/UserRole.cs b/Jellyfin.Api/Constants/UserRole.cs deleted file mode 100644 index b1da61557..000000000 --- a/Jellyfin.Api/Constants/UserRole.cs +++ /dev/null @@ -1,23 +0,0 @@ -namespace Jellyfin.Api.Constants -{ - /// - /// Constants for user roles used in the authentication and authorization for the API. - /// - public static class UserRole - { - /// - /// Guest user. - /// - public const string Guest = "Guest"; - - /// - /// Regular user with no special privileges. - /// - public const string User = "User"; - - /// - /// Administrator user with elevated privileges. - /// - public const string Administrator = "Administrator"; - } -} diff --git a/Jellyfin.Api/Constants/UserRoles.cs b/Jellyfin.Api/Constants/UserRoles.cs new file mode 100644 index 000000000..d9a536e7d --- /dev/null +++ b/Jellyfin.Api/Constants/UserRoles.cs @@ -0,0 +1,23 @@ +namespace Jellyfin.Api.Constants +{ + /// + /// Constants for user roles used in the authentication and authorization for the API. + /// + public static class UserRoles + { + /// + /// Guest user. + /// + public const string Guest = "Guest"; + + /// + /// Regular user with no special privileges. + /// + public const string User = "User"; + + /// + /// Administrator user with elevated privileges. + /// + public const string Administrator = "Administrator"; + } +} diff --git a/Jellyfin.Api/Controllers/StartupController.cs b/Jellyfin.Api/Controllers/StartupController.cs index 50f3dc83c..1014c8c56 100644 --- a/Jellyfin.Api/Controllers/StartupController.cs +++ b/Jellyfin.Api/Controllers/StartupController.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Threading.Tasks; +using Jellyfin.Api.Constants; using Jellyfin.Api.Models.StartupDtos; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; @@ -11,7 +12,7 @@ namespace Jellyfin.Api.Controllers /// /// The startup wizard controller. /// - [Authorize(Policy = "FirstTimeSetupOrElevated")] + [Authorize(Policy = Policies.FirstTimeSetupOrElevated)] public class StartupController : BaseJellyfinApiController { private readonly IServerConfigurationManager _config; diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj index 6ad97b60f..a2818b45d 100644 --- a/Jellyfin.Api/Jellyfin.Api.csproj +++ b/Jellyfin.Api/Jellyfin.Api.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index e5a8937e8..dd4f9cd23 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -2,6 +2,7 @@ using Jellyfin.Api; using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; @@ -27,17 +28,17 @@ namespace Jellyfin.Server.Extensions return serviceCollection.AddAuthorizationCore(options => { options.AddPolicy( - "RequiresElevation", + Policies.RequiresElevation, policy => { - policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddRequirements(new RequiresElevationRequirement()); }); options.AddPolicy( - "FirstTimeSetupOrElevated", + Policies.FirstTimeSetupOrElevated, policy => { - policy.AddAuthenticationSchemes("CustomAuthentication"); + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); }); }); @@ -50,8 +51,8 @@ namespace Jellyfin.Server.Extensions /// The updated service collection. public static AuthenticationBuilder AddCustomAuthentication(this IServiceCollection serviceCollection) { - return serviceCollection.AddAuthentication("CustomAuthentication") - .AddScheme("CustomAuthentication", null); + return serviceCollection.AddAuthentication(AuthenticationSchemes.CustomAuthentication) + .AddScheme(AuthenticationSchemes.CustomAuthentication, null); } /// -- cgit v1.2.3