diff options
Diffstat (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs')
| -rw-r--r-- | Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 228 |
1 files changed, 101 insertions, 127 deletions
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 924b250ce..3271e08e4 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -5,29 +5,28 @@ using System.Linq; using System.Net; using System.Net.Sockets; using System.Reflection; +using System.Security.Claims; using Emby.Server.Implementations; using Jellyfin.Api.Auth; +using Jellyfin.Api.Auth.AnonymousLanAccessPolicy; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; -using Jellyfin.Api.Auth.DownloadPolicy; -using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy; -using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy; -using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy; -using Jellyfin.Api.Auth.IgnoreParentalControlPolicy; +using Jellyfin.Api.Auth.FirstTimeSetupPolicy; using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; -using Jellyfin.Api.Auth.LocalAccessPolicy; -using Jellyfin.Api.Auth.RequiresElevationPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; +using Jellyfin.Api.Auth.UserPermissionPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; +using Jellyfin.Api.Formatters; using Jellyfin.Api.ModelBinders; using Jellyfin.Data.Enums; +using Jellyfin.Extensions.Json; using Jellyfin.Networking.Configuration; +using Jellyfin.Networking.Constants; +using Jellyfin.Networking.Extensions; using Jellyfin.Server.Configuration; using Jellyfin.Server.Filters; -using Jellyfin.Server.Formatters; -using MediaBrowser.Common.Json; -using MediaBrowser.Common.Net; using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Session; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -54,109 +53,38 @@ namespace Jellyfin.Server.Extensions /// <returns>The updated service collection.</returns> public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) { + // The default handler must be first so that it is evaluated first serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, DownloadHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrDefaultHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreParentalControlHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeOrIgnoreParentalControlSetupHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>(); - serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, UserPermissionHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupHandler>(); + serviceCollection.AddSingleton<IAuthorizationHandler, AnonymousLanAccessHandler>(); serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>(); + return serviceCollection.AddAuthorizationCore(options => { - options.AddPolicy( - Policies.DefaultAuthorization, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new DefaultAuthorizationRequirement()); - }); - options.AddPolicy( - Policies.Download, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new DownloadRequirement()); - }); - options.AddPolicy( - Policies.FirstTimeSetupOrDefault, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new FirstTimeSetupOrDefaultRequirement()); - }); - options.AddPolicy( - Policies.FirstTimeSetupOrElevated, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement()); - }); - options.AddPolicy( - Policies.IgnoreParentalControl, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new IgnoreParentalControlRequirement()); - }); - options.AddPolicy( - Policies.FirstTimeSetupOrIgnoreParentalControl, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new FirstTimeOrIgnoreParentalControlSetupRequirement()); - }); - options.AddPolicy( - Policies.LocalAccessOnly, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new LocalAccessRequirement()); - }); - options.AddPolicy( - Policies.LocalAccessOrRequiresElevation, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new LocalAccessOrRequiresElevationRequirement()); - }); + options.DefaultPolicy = new AuthorizationPolicyBuilder() + .AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) + .AddRequirements(new DefaultAuthorizationRequirement()) + .Build(); + + options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement()); + options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement)); + options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading)); + options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement(requireAdmin: false)); + options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement()); + options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(false, false)); + options.AddPolicy(Policies.IgnoreParentalControl, new DefaultAuthorizationRequirement(validateParentalSchedule: false)); + options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess)); + options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement)); + options.AddPolicy(Policies.LocalAccessOrRequiresElevation, new LocalAccessOrRequiresElevationRequirement()); + options.AddPolicy(Policies.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess)); + options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup)); + options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup)); + options.AddPolicy(Policies.SyncPlayIsInGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); options.AddPolicy( Policies.RequiresElevation, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new RequiresElevationRequirement()); - }); - options.AddPolicy( - Policies.SyncPlayHasAccess, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess)); - }); - options.AddPolicy( - Policies.SyncPlayCreateGroup, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup)); - }); - options.AddPolicy( - Policies.SyncPlayJoinGroup, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup)); - }); - options.AddPolicy( - Policies.SyncPlayIsInGroup, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); - }); + policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) + .RequireClaim(ClaimTypes.Role, UserRoles.Administrator)); }); } @@ -172,7 +100,7 @@ namespace Jellyfin.Server.Extensions } /// <summary> - /// Extension method for adding the jellyfin API to the service collection. + /// Extension method for adding the Jellyfin API to the service collection. /// </summary> /// <param name="serviceCollection">The service collection.</param> /// <param name="pluginAssemblies">An IEnumerable containing all plugin assemblies with API controllers.</param> @@ -188,7 +116,8 @@ namespace Jellyfin.Server.Extensions // https://github.com/dotnet/aspnetcore/blob/master/src/Middleware/HttpOverrides/src/ForwardedHeadersMiddleware.cs // Enable debug logging on Microsoft.AspNetCore.HttpOverrides.ForwardedHeadersMiddleware to help investigate issues. - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + if (config.KnownProxies.Length == 0) { options.KnownNetworks.Clear(); @@ -278,7 +207,7 @@ namespace Jellyfin.Server.Extensions { Type = SecuritySchemeType.ApiKey, In = ParameterLocation.Header, - Name = "X-Emby-Authorization", + Name = "Authorization", Description = "API key header parameter" }); @@ -303,7 +232,7 @@ namespace Jellyfin.Server.Extensions { description.TryGetMethodInfo(out MethodInfo methodInfo); // Attribute name, method name, none. - return description?.ActionDescriptor?.AttributeRouteInfo?.Name + return description?.ActionDescriptor.AttributeRouteInfo?.Name ?? methodInfo?.Name ?? null; }); @@ -323,8 +252,16 @@ namespace Jellyfin.Server.Extensions }); } + private static void AddPolicy(this AuthorizationOptions authorizationOptions, string policyName, IAuthorizationRequirement authorizationRequirement) + { + authorizationOptions.AddPolicy(policyName, policy => + { + policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication).AddRequirements(authorizationRequirement); + }); + } + /// <summary> - /// Sets up the proxy configuration based on the addresses in <paramref name="allowedProxies"/>. + /// Sets up the proxy configuration based on the addresses/subnets in <paramref name="allowedProxies"/>. /// </summary> /// <param name="config">The <see cref="NetworkConfiguration"/> containing the config settings.</param> /// <param name="allowedProxies">The string array to parse.</param> @@ -333,36 +270,40 @@ namespace Jellyfin.Server.Extensions { for (var i = 0; i < allowedProxies.Length; i++) { - if (IPNetAddress.TryParse(allowedProxies[i], out var addr)) + if (IPAddress.TryParse(allowedProxies[i], out var addr)) + { + AddIPAddress(config, options, addr, addr.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize); + } + else if (NetworkExtensions.TryParseToSubnet(allowedProxies[i], out var subnet)) { - AddIpAddress(config, options, addr.Address, addr.PrefixLength); + if (subnet is not null) + { + AddIPAddress(config, options, subnet.Prefix, subnet.PrefixLength); + } } - else if (IPHost.TryParse(allowedProxies[i], out var host)) + else if (NetworkExtensions.TryParseHost(allowedProxies[i], out var addresses)) { - foreach (var address in host.GetAddresses()) + foreach (var address in addresses) { - AddIpAddress(config, options, addr.Address, addr.PrefixLength); + AddIPAddress(config, options, address, address.AddressFamily == AddressFamily.InterNetwork ? Network.MinimumIPv4PrefixSize : Network.MinimumIPv6PrefixSize); } } } } - private static void AddIpAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength) + private static void AddIPAddress(NetworkConfiguration config, ForwardedHeadersOptions options, IPAddress addr, int prefixLength) { - if ((!config.EnableIPV4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPV6 && addr.AddressFamily == AddressFamily.InterNetworkV6)) + if (addr.IsIPv4MappedToIPv6) { - return; + addr = addr.MapToIPv4(); } - // In order for dual-mode sockets to be used, IP6 has to be enabled in JF and an interface has to have an IP6 address. - if (addr.AddressFamily == AddressFamily.InterNetwork && config.EnableIPV6) + if ((!config.EnableIPv4 && addr.AddressFamily == AddressFamily.InterNetwork) || (!config.EnableIPv6 && addr.AddressFamily == AddressFamily.InterNetworkV6)) { - // If the server is using dual-mode sockets, IPv4 addresses are supplied in an IPv6 format. - // https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-5.0 . - addr = addr.MapToIPv6(); + return; } - if (prefixLength == 32) + if (prefixLength == Network.MinimumIPv4PrefixSize) { options.KnownProxies.Add(addr); } @@ -397,7 +338,7 @@ namespace Jellyfin.Server.Extensions Type = "object", Properties = typeof(ImageType).GetEnumNames().ToDictionary( name => name, - name => new OpenApiSchema + _ => new OpenApiSchema { Type = "object", AdditionalProperties = new OpenApiSchema @@ -406,6 +347,39 @@ namespace Jellyfin.Server.Extensions } }) }); + + // Support dictionary with nullable string value. + options.MapType<Dictionary<string, string?>>(() => + new OpenApiSchema + { + Type = "object", + AdditionalProperties = new OpenApiSchema + { + Type = "string", + Nullable = true + } + }); + + // Manually describe Flags enum. + options.MapType<TranscodeReason>(() => + new OpenApiSchema + { + Type = "array", + Items = new OpenApiSchema + { + Reference = new OpenApiReference + { + Id = nameof(TranscodeReason), + Type = ReferenceType.Schema, + } + } + }); + + // Swashbuckle doesn't use JsonOptions to describe responses, so we need to manually describe it. + options.MapType<Version>(() => new OpenApiSchema + { + Type = "string" + }); } } } |
