From 209edd38a4163a8cf4abd5e47bfe0ea1a100f351 Mon Sep 17 00:00:00 2001 From: cvium Date: Wed, 8 Feb 2023 23:55:26 +0100 Subject: refactor: simplify authz --- .../Extensions/ApiServiceCollectionExtensions.cs | 143 +++++---------------- 1 file changed, 32 insertions(+), 111 deletions(-) (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index e9af1cf83..e2dcaf5f5 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -5,19 +5,15 @@ 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.LocalAccessOrRequiresElevationPolicy; -using Jellyfin.Api.Auth.LocalAccessPolicy; -using Jellyfin.Api.Auth.RequiresElevationPolicy; +using Jellyfin.Api.Auth.FirstTimeSetupPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; +using Jellyfin.Api.Auth.UserPermissionPolicy; using Jellyfin.Api.Constants; using Jellyfin.Api.Controllers; using Jellyfin.Api.Formatters; @@ -56,117 +52,34 @@ namespace Jellyfin.Server.Extensions /// The updated service collection. public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection) { + // The default handler must be first so that it is evaluated first serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + 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) + .RequireAuthenticatedUser() + .Build(); + + options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading)); + options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement()); + options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement(requireAdmin: true)); + options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(validateParentalSchedule: false)); + options.AddPolicy(Policies.IgnoreParentalControl, new DefaultAuthorizationRequirement(validateParentalSchedule: false)); + 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.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement()); 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)); - }); - options.AddPolicy( - Policies.AnonymousLanAccessPolicy, - policy => - { - policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication); - policy.AddRequirements(new AnonymousLanAccessRequirement()); - }); + policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) + .RequireClaim(ClaimTypes.Role, UserRoles.Administrator)); }); } @@ -334,6 +247,14 @@ 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); + }); + } + /// /// Sets up the proxy configuration based on the addresses in . /// -- cgit v1.2.3 From 956c89dc2f5d6c3b8a3f362564b174fe303a9401 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 9 Feb 2023 13:15:58 +0100 Subject: fix default policy --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index e2dcaf5f5..9eaed703a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -63,7 +63,7 @@ namespace Jellyfin.Server.Extensions { options.DefaultPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) - .RequireAuthenticatedUser() + .AddRequirements(new DefaultAuthorizationRequirement()) .Build(); options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading)); -- cgit v1.2.3 From c9aef96dba26d29df3db9e3a1ce243be23bea772 Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 9 Feb 2023 21:06:51 +0100 Subject: fix firsttimesetup --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 9eaed703a..968a8e58c 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -67,9 +67,9 @@ namespace Jellyfin.Server.Extensions .Build(); options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading)); - options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement()); - options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement(requireAdmin: true)); - options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(validateParentalSchedule: false)); + 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.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess)); options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup)); -- cgit v1.2.3 From b7418d6e9e08af44edfc30971cd7b1b7871554c7 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 13 Feb 2023 15:42:04 +0100 Subject: Add permission for collection management --- Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs | 3 +-- .../Auth/UserPermissionPolicy/UserPermissionRequirement.cs | 2 +- Jellyfin.Api/Constants/Policies.cs | 5 +++++ Jellyfin.Api/Controllers/CollectionController.cs | 3 ++- Jellyfin.Data/Entities/User.cs | 1 + Jellyfin.Data/Enums/PermissionKind.cs | 7 ++++++- Jellyfin.Server.Implementations/Users/UserManager.cs | 2 ++ Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 2 +- Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs | 1 + MediaBrowser.Controller/Entities/Movies/BoxSet.cs | 2 +- MediaBrowser.Model/Users/UserPolicy.cs | 7 +++++++ 11 files changed, 28 insertions(+), 7 deletions(-) (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs index ba2b1b657..e72bec46f 100644 --- a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using Jellyfin.Api.Auth.DownloadPolicy; using Jellyfin.Api.Extensions; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -8,7 +7,7 @@ using Microsoft.AspNetCore.Authorization; namespace Jellyfin.Api.Auth.UserPermissionPolicy { /// - /// Download authorization handler. + /// User permission authorization handler. /// public class UserPermissionHandler : AuthorizationHandler { diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs index 195a61199..4694556eb 100644 --- a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs +++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs @@ -1,7 +1,7 @@ using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Data.Enums; -namespace Jellyfin.Api.Auth.DownloadPolicy +namespace Jellyfin.Api.Auth.UserPermissionPolicy { /// /// The user permission requirement. diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index adc95e57b..1ef38ca07 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -69,4 +69,9 @@ public static class Policies /// Policy name for accessing a SyncPlay group. /// public const string SyncPlayIsInGroup = "SyncPlayIsInGroup"; + + /// + /// Policy name for accessing collection management. + /// + public const string CollectionManagement = "CollectionManagement"; } diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs index f9f9be7ce..2db04afb8 100644 --- a/Jellyfin.Api/Controllers/CollectionController.cs +++ b/Jellyfin.Api/Controllers/CollectionController.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; +using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.ModelBinders; using MediaBrowser.Controller.Collections; @@ -16,7 +17,7 @@ namespace Jellyfin.Api.Controllers; /// The collection controller. /// [Route("Collections")] -[Authorize] +[Authorize(Policy = Policies.CollectionManagement)] public class CollectionController : BaseJellyfinApiController { private readonly ICollectionManager _collectionManager; diff --git a/Jellyfin.Data/Entities/User.cs b/Jellyfin.Data/Entities/User.cs index 4ce581749..606e1b542 100644 --- a/Jellyfin.Data/Entities/User.cs +++ b/Jellyfin.Data/Entities/User.cs @@ -508,6 +508,7 @@ namespace Jellyfin.Data.Entities Permissions.Add(new Permission(PermissionKind.EnableVideoPlaybackTranscoding, true)); Permissions.Add(new Permission(PermissionKind.ForceRemoteSourceTranscoding, false)); Permissions.Add(new Permission(PermissionKind.EnableRemoteControlOfOtherUsers, false)); + Permissions.Add(new Permission(PermissionKind.EnableCollectionManagement, false)); } /// diff --git a/Jellyfin.Data/Enums/PermissionKind.cs b/Jellyfin.Data/Enums/PermissionKind.cs index 7d5200874..40280b95e 100644 --- a/Jellyfin.Data/Enums/PermissionKind.cs +++ b/Jellyfin.Data/Enums/PermissionKind.cs @@ -108,6 +108,11 @@ namespace Jellyfin.Data.Enums /// /// Whether the server should force transcoding on remote connections for the user. /// - ForceRemoteSourceTranscoding = 20 + ForceRemoteSourceTranscoding = 20, + + /// + /// Whether the user can create, modify and delete collections. + /// + EnableCollectionManagement = 21 } } diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs index f9679510d..92384986a 100644 --- a/Jellyfin.Server.Implementations/Users/UserManager.cs +++ b/Jellyfin.Server.Implementations/Users/UserManager.cs @@ -369,6 +369,7 @@ namespace Jellyfin.Server.Implementations.Users EnablePlaybackRemuxing = user.HasPermission(PermissionKind.EnablePlaybackRemuxing), ForceRemoteSourceTranscoding = user.HasPermission(PermissionKind.ForceRemoteSourceTranscoding), EnablePublicSharing = user.HasPermission(PermissionKind.EnablePublicSharing), + EnableCollectionManagement = user.HasPermission(PermissionKind.EnableCollectionManagement), AccessSchedules = user.AccessSchedules.ToArray(), BlockedTags = user.GetPreference(PreferenceKind.BlockedTags), AllowedTags = user.GetPreference(PreferenceKind.AllowedTags), @@ -685,6 +686,7 @@ namespace Jellyfin.Server.Implementations.Users user.SetPermission(PermissionKind.EnableAllFolders, policy.EnableAllFolders); user.SetPermission(PermissionKind.EnableRemoteControlOfOtherUsers, policy.EnableRemoteControlOfOtherUsers); user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); + user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement); user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 968a8e58c..dffcfbba8 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -10,7 +10,6 @@ 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.FirstTimeSetupPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; using Jellyfin.Api.Auth.UserPermissionPolicy; @@ -75,6 +74,7 @@ namespace Jellyfin.Server.Extensions 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.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement)); options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement()); options.AddPolicy( Policies.RequiresElevation, diff --git a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs index ea2f03302..9bf1e6b80 100644 --- a/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs +++ b/Jellyfin.Server/Migrations/Routines/MigrateUserDb.cs @@ -163,6 +163,7 @@ namespace Jellyfin.Server.Migrations.Routines user.SetPermission(PermissionKind.EnablePlaybackRemuxing, policy.EnablePlaybackRemuxing); user.SetPermission(PermissionKind.ForceRemoteSourceTranscoding, policy.ForceRemoteSourceTranscoding); user.SetPermission(PermissionKind.EnablePublicSharing, policy.EnablePublicSharing); + user.SetPermission(PermissionKind.EnableCollectionManagement, policy.EnableCollectionManagement); foreach (var policyAccessSchedule in policy.AccessSchedules) { diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 882abc927..66210cb6c 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -104,7 +104,7 @@ namespace MediaBrowser.Controller.Entities.Movies public override bool IsAuthorizedToDelete(User user, List allCollectionFolders) { - return true; + return user.HasPermission(PermissionKind.IsAdministrator) || user.HasPermission(PermissionKind.EnableCollectionManagement); } public override bool IsSaveLocalMetadataEnabled() diff --git a/MediaBrowser.Model/Users/UserPolicy.cs b/MediaBrowser.Model/Users/UserPolicy.cs index 1619dac5a..cc7d57eb0 100644 --- a/MediaBrowser.Model/Users/UserPolicy.cs +++ b/MediaBrowser.Model/Users/UserPolicy.cs @@ -13,6 +13,7 @@ namespace MediaBrowser.Model.Users public UserPolicy() { IsHidden = true; + EnableCollectionManagement = false; EnableContentDeletion = false; EnableContentDeletionFromFolders = Array.Empty(); @@ -73,6 +74,12 @@ namespace MediaBrowser.Model.Users /// true if this instance is hidden; otherwise, false. public bool IsHidden { get; set; } + /// + /// Gets or sets a value indicating whether this instance can manage collections. + /// + /// true if this instance is hidden; otherwise, false. + public bool EnableCollectionManagement { get; set; } + /// /// Gets or sets a value indicating whether this instance is disabled. /// -- cgit v1.2.3 From eeb0f7af6c1d3f422b66128e5a91830a3682e331 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Mon, 13 Feb 2023 15:38:18 +0100 Subject: Add permissions for LiveTV access and management --- Jellyfin.Api/Constants/Policies.cs | 10 ++ Jellyfin.Api/Controllers/LiveTvController.cs | 112 ++++++++------------- .../Extensions/ApiServiceCollectionExtensions.cs | 3 +- 3 files changed, 52 insertions(+), 73 deletions(-) (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index 1ef38ca07..53841b0c4 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -74,4 +74,14 @@ public static class Policies /// Policy name for accessing collection management. /// public const string CollectionManagement = "CollectionManagement"; + + /// + /// Policy name for accessing LiveTV. + /// + public const string LiveTvAccess = "LiveTvAccess"; + + /// + /// Policy name for managing LiveTV. + /// + public const string LiveTvManagement = "LiveTvManagement"; } diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 3425c8589..318ed5c67 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -10,20 +10,19 @@ using System.Text; using System.Threading; using System.Threading.Tasks; using Jellyfin.Api.Attributes; +using Jellyfin.Api.Constants; using Jellyfin.Api.Extensions; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.LiveTvDtos; using Jellyfin.Data.Enums; using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Net; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; -using MediaBrowser.Controller.Net; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Entities; @@ -94,7 +93,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Info")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult GetLiveTvInfo() { return _liveTvManager.GetLiveTvInfo(CancellationToken.None); @@ -130,7 +129,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Channels")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult> GetLiveTvChannels( [FromQuery] ChannelType? type, [FromQuery] Guid? userId, @@ -209,7 +208,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the live tv channel. [HttpGet("Channels/{channelId}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) { var user = userId is null || userId.Value.Equals(default) @@ -250,7 +249,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the live tv recordings. [HttpGet("Recordings")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult> GetRecordings( [FromQuery] string? channelId, [FromQuery] Guid? userId, @@ -321,7 +320,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the live tv recordings. [HttpGet("Recordings/Series")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [Obsolete("This endpoint is obsolete.")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] @@ -364,7 +363,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the recording groups. [HttpGet("Recordings/Groups")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [Obsolete("This endpoint is obsolete.")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")] public ActionResult> GetRecordingGroups([FromQuery] Guid? userId) @@ -380,7 +379,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the recording folders. [HttpGet("Recordings/Folders")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult> GetRecordingFolders([FromQuery] Guid? userId) { var user = userId is null || userId.Value.Equals(default) @@ -402,7 +401,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the live tv recording. [HttpGet("Recordings/{recordingId}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) { var user = userId is null || userId.Value.Equals(default) @@ -424,10 +423,9 @@ public class LiveTvController : BaseJellyfinApiController /// A . [HttpPost("Tuners/{tunerId}/Reset")] [ProducesResponseType(StatusCodes.Status204NoContent)] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] public async Task ResetTuner([FromRoute, Required] string tunerId) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -442,7 +440,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Timers/{timerId}")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public async Task> GetTimer([FromRoute, Required] string timerId) { return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false); @@ -458,7 +456,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Timers/Defaults")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public async Task> GetDefaultTimer([FromQuery] string? programId) { return string.IsNullOrEmpty(programId) @@ -478,7 +476,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Timers")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public async Task>> GetTimers( [FromQuery] string? channelId, [FromQuery] string? seriesTimerId, @@ -532,7 +530,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpGet("Programs")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public async Task>> GetLiveTvPrograms( [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds, [FromQuery] Guid? userId, @@ -615,7 +613,7 @@ public class LiveTvController : BaseJellyfinApiController /// [HttpPost("Programs")] [ProducesResponseType(StatusCodes.Status200OK)] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] public async Task>> GetPrograms([FromBody] GetProgramsDto body) { var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId); @@ -681,7 +679,7 @@ public class LiveTvController : BaseJellyfinApiController /// Recommended epgs returned. /// A containing the queryresult of recommended epgs. [HttpGet("Programs/Recommended")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetRecommendedPrograms( [FromQuery] Guid? userId, @@ -733,7 +731,7 @@ public class LiveTvController : BaseJellyfinApiController /// Program returned. /// An containing the livetv program. [HttpGet("Programs/{programId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetProgram( [FromRoute, Required] string programId, @@ -754,13 +752,11 @@ public class LiveTvController : BaseJellyfinApiController /// Item not found. /// A on success, or a if item not found. [HttpDelete("Recordings/{recordingId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task DeleteRecording([FromRoute, Required] Guid recordingId) + public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); - var item = _libraryManager.GetItemById(recordingId); if (item is null) { @@ -782,11 +778,10 @@ public class LiveTvController : BaseJellyfinApiController /// Timer deleted. /// A . [HttpDelete("Timers/{timerId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CancelTimer([FromRoute, Required] string timerId) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -799,12 +794,11 @@ public class LiveTvController : BaseJellyfinApiController /// Timer updated. /// A . [HttpPost("Timers/{timerId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -816,11 +810,10 @@ public class LiveTvController : BaseJellyfinApiController /// Timer created. /// A . [HttpPost("Timers")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateTimer([FromBody] TimerInfoDto timerInfo) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -833,7 +826,7 @@ public class LiveTvController : BaseJellyfinApiController /// Series timer not found. /// A on success, or a if timer not found. [HttpGet("SeriesTimers/{timerId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> GetSeriesTimer([FromRoute, Required] string timerId) @@ -855,7 +848,7 @@ public class LiveTvController : BaseJellyfinApiController /// Timers returned. /// An of live tv series timers. [HttpGet("SeriesTimers")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder) { @@ -875,11 +868,10 @@ public class LiveTvController : BaseJellyfinApiController /// Timer cancelled. /// A . [HttpDelete("SeriesTimers/{timerId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CancelSeriesTimer([FromRoute, Required] string timerId) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false); return NoContent(); } @@ -892,12 +884,11 @@ public class LiveTvController : BaseJellyfinApiController /// Series timer updated. /// A . [HttpPost("SeriesTimers/{timerId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")] public async Task UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -909,11 +900,10 @@ public class LiveTvController : BaseJellyfinApiController /// Series timer info created. /// A . [HttpPost("SeriesTimers")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo) { - await AssertUserCanManageLiveTv().ConfigureAwait(false); await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false); return NoContent(); } @@ -924,7 +914,7 @@ public class LiveTvController : BaseJellyfinApiController /// Group id. /// A . [HttpGet("Recordings/Groups/{groupId}")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status404NotFound)] [Obsolete("This endpoint is obsolete.")] public ActionResult GetRecordingGroup([FromRoute, Required] Guid groupId) @@ -938,7 +928,7 @@ public class LiveTvController : BaseJellyfinApiController /// Guid info returned. /// An containing the guide info. [HttpGet("GuideInfo")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetGuideInfo() { @@ -952,7 +942,7 @@ public class LiveTvController : BaseJellyfinApiController /// Created tuner host returned. /// A containing the created tuner host. [HttpPost("TunerHosts")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo) { @@ -966,7 +956,7 @@ public class LiveTvController : BaseJellyfinApiController /// Tuner host deleted. /// A . [HttpDelete("TunerHosts")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult DeleteTunerHost([FromQuery] string? id) { @@ -982,7 +972,7 @@ public class LiveTvController : BaseJellyfinApiController /// Default listings provider info returned. /// An containing the default listings provider info. [HttpGet("ListingProviders/Default")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult GetDefaultListingProvider() { @@ -999,7 +989,7 @@ public class LiveTvController : BaseJellyfinApiController /// Created listings provider returned. /// A containing the created listings provider. [HttpPost("ListingProviders")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] [SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")] public async Task> AddListingProvider( @@ -1025,7 +1015,7 @@ public class LiveTvController : BaseJellyfinApiController /// Listing provider deleted. /// A . [HttpDelete("ListingProviders")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status204NoContent)] public ActionResult DeleteListingProvider([FromQuery] string? id) { @@ -1043,7 +1033,7 @@ public class LiveTvController : BaseJellyfinApiController /// Available lineups returned. /// A containing the available lineups. [HttpGet("ListingProviders/Lineups")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> GetLineups( [FromQuery] string? id, @@ -1060,7 +1050,7 @@ public class LiveTvController : BaseJellyfinApiController /// Available countries returned. /// A containing the available countries. [HttpGet("ListingProviders/SchedulesDirect/Countries")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesFile(MediaTypeNames.Application.Json)] public async Task GetSchedulesDirectCountries() @@ -1081,7 +1071,7 @@ public class LiveTvController : BaseJellyfinApiController /// Channel mapping options returned. /// An containing the channel mapping options. [HttpGet("ChannelMappingOptions")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> GetChannelMappingOptions([FromQuery] string? providerId) { @@ -1119,7 +1109,7 @@ public class LiveTvController : BaseJellyfinApiController /// Created channel mapping returned. /// An containing the created channel mapping. [HttpPost("ChannelMappings")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto) { @@ -1132,7 +1122,7 @@ public class LiveTvController : BaseJellyfinApiController /// Tuner host types returned. /// An containing the tuner host types. [HttpGet("TunerHosts/Types")] - [Authorize] + [Authorize(Policy = Policies.LiveTvAccess)] [ProducesResponseType(StatusCodes.Status200OK)] public ActionResult> GetTunerHostTypes() { @@ -1147,7 +1137,7 @@ public class LiveTvController : BaseJellyfinApiController /// An containing the tuners. [HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")] [HttpGet("Tuners/Discover")] - [Authorize] + [Authorize(Policy = Policies.LiveTvManagement)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false) { @@ -1207,26 +1197,4 @@ public class LiveTvController : BaseJellyfinApiController var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream()); return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container)); } - - private async Task AssertUserCanManageLiveTv() - { - var user = _userManager.GetUserById(User.GetUserId()) ?? throw new ResourceNotFoundException(); - var session = await _sessionManager.LogSessionActivity( - User.GetClient(), - User.GetVersion(), - User.GetDeviceId(), - User.GetDevice(), - HttpContext.GetNormalizedRemoteIp().ToString(), - user).ConfigureAwait(false); - - if (session.UserId.Equals(default)) - { - throw new SecurityException("Anonymous live tv management is not allowed."); - } - - if (!user.HasPermission(PermissionKind.EnableLiveTvManagement)) - { - throw new SecurityException("The current user does not have permission to manage live tv."); - } - } } diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index dffcfbba8..61957b4ea 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -75,7 +75,8 @@ namespace Jellyfin.Server.Extensions options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup)); options.AddPolicy(Policies.SyncPlayIsInGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup)); options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement)); - options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement()); + options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess)); + options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement)); options.AddPolicy( Policies.RequiresElevation, policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) -- cgit v1.2.3 From b8ed1f81cda7222078ed245c5f46562fc7822758 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Tue, 14 Feb 2023 19:04:18 +0100 Subject: Add back LocalAccessOrRequiresElevationPolicy --- .../LocalAccessOrRequiresElevationHandler.cs | 52 ++++++++++++++++++++++ .../LocalAccessOrRequiresElevationRequirement.cs | 11 +++++ .../Extensions/ApiServiceCollectionExtensions.cs | 9 ++-- 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs create mode 100644 Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs (limited to 'Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs') diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs new file mode 100644 index 000000000..0b0877d06 --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs @@ -0,0 +1,52 @@ +using System.Threading.Tasks; +using Jellyfin.Api.Constants; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// + /// Local access or require elevated privileges handler. + /// + public class LocalAccessOrRequiresElevationHandler : AuthorizationHandler + { + private readonly INetworkManager _networkManager; + private readonly IHttpContextAccessor _httpContextAccessor; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + public LocalAccessOrRequiresElevationHandler( + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + { + _networkManager = networkManager; + _httpContextAccessor = httpContextAccessor; + } + + /// + protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement) + { + var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIp(); + + // Loopback will be on LAN, so we can accept null. + if (ip is null || _networkManager.IsInLocalNetwork(ip)) + { + context.Succeed(requirement); + } + + if (context.User.IsInRole(UserRoles.Administrator)) + { + context.Succeed(requirement); + } + + context.Fail(); + + return Task.CompletedTask; + } + } +} diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs new file mode 100644 index 000000000..f633c69d8 --- /dev/null +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy +{ + /// + /// The local access or elevated privileges authorization requirement. + /// + public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement + { + } +} diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 61957b4ea..9867c9e47 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -11,6 +11,7 @@ using Jellyfin.Api.Auth; using Jellyfin.Api.Auth.AnonymousLanAccessPolicy; using Jellyfin.Api.Auth.DefaultAuthorizationPolicy; using Jellyfin.Api.Auth.FirstTimeSetupPolicy; +using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy; using Jellyfin.Api.Auth.SyncPlayAccessPolicy; using Jellyfin.Api.Auth.UserPermissionPolicy; using Jellyfin.Api.Constants; @@ -65,18 +66,20 @@ namespace Jellyfin.Server.Extensions .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.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement)); - options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess)); - options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement)); options.AddPolicy( Policies.RequiresElevation, policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication) -- cgit v1.2.3