Jelajahi Sumber

Move request validation to auth policies

cvium 4 tahun lalu
induk
melakukan
7e0ea296c3

+ 0 - 62
Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs

@@ -102,11 +102,6 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         public void NewGroup(SessionInfo session, NewGroupRequest request, CancellationToken cancellationToken)
         {
-            if (!IsRequestValid(session, request))
-            {
-                return;
-            }
-
             // Locking required to access list of groups.
             lock (_groupsLock)
             {
@@ -132,11 +127,6 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         public void JoinGroup(SessionInfo session, JoinGroupRequest request, CancellationToken cancellationToken)
         {
-            if (!IsRequestValid(session, request))
-            {
-                return;
-            }
-
             var user = _userManager.GetUserById(session.UserId);
 
             // Locking required to access list of groups.
@@ -190,11 +180,6 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         public void LeaveGroup(SessionInfo session, LeaveGroupRequest request, CancellationToken cancellationToken)
         {
-            if (!IsRequestValid(session, request))
-            {
-                return;
-            }
-
             // Locking required to access list of groups.
             lock (_groupsLock)
             {
@@ -230,11 +215,6 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         public List<GroupInfoDto> ListGroups(SessionInfo session, ListGroupsRequest request)
         {
-            if (!IsRequestValid(session, request))
-            {
-                return new List<GroupInfoDto>();
-            }
-
             var user = _userManager.GetUserById(session.UserId);
             List<GroupInfoDto> list = new List<GroupInfoDto>();
 
@@ -260,11 +240,6 @@ namespace Emby.Server.Implementations.SyncPlay
         /// <inheritdoc />
         public void HandleRequest(SessionInfo session, IGroupPlaybackRequest request, CancellationToken cancellationToken)
         {
-            if (!IsRequestValid(session, request))
-            {
-                return;
-            }
-
             IGroupController group;
             lock (_mapsLock)
             {
@@ -417,42 +392,5 @@ namespace Emby.Server.Implementations.SyncPlay
                 throw new InvalidOperationException("Session was in wrong group!");
             }
         }
-
-        /// <summary>
-        /// Checks if a given session is allowed to make a given request.
-        /// </summary>
-        /// <param name="session">The session.</param>
-        /// <param name="request">The request.</param>
-        /// <returns><c>true</c> if the request is valid, <c>false</c> otherwise. Will return <c>false</c> also when session or request is null.</returns>
-        private bool IsRequestValid(SessionInfo session, ISyncPlayRequest request)
-        {
-            if (session == null || (request == null))
-            {
-                return false;
-            }
-
-            var user = _userManager.GetUserById(session.UserId);
-
-            if (user.SyncPlayAccess == SyncPlayAccess.None)
-            {
-                _logger.LogWarning("Session {SessionId} requested {RequestType} but does not have access to SyncPlay.", session.Id, request.Type);
-
-                // TODO: rename to a more generic error. Next PR will fix this.
-                var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.JoinGroupDenied, string.Empty);
-                _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
-                return false;
-            }
-
-            if (request.Type.Equals(RequestType.NewGroup) && user.SyncPlayAccess != SyncPlayAccess.CreateAndJoinGroups)
-            {
-                _logger.LogWarning("Session {SessionId} does not have permission to create groups.", session.Id);
-
-                var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.CreateGroupDenied, string.Empty);
-                _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None);
-                return false;
-            }
-
-            return true;
-        }
     }
 }

+ 58 - 0
Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs

@@ -0,0 +1,58 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
+{
+    /// <summary>
+    /// Default authorization handler.
+    /// </summary>
+    public class SyncPlayAccessHandler : BaseAuthorizationHandler<SyncPlayAccessRequirement>
+    {
+        private readonly IUserManager _userManager;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SyncPlayAccessHandler"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public SyncPlayAccessHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
+        {
+            _userManager = userManager;
+        }
+
+        /// <inheritdoc />
+        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncPlayAccessRequirement requirement)
+        {
+            if (!ValidateClaims(context.User))
+            {
+                context.Fail();
+                return Task.CompletedTask;
+            }
+
+            var userId = ClaimHelpers.GetUserId(context.User);
+            var user = _userManager.GetUserById(userId!.Value);
+
+            if ((requirement.RequiredAccess.HasValue && user.SyncPlayAccess == requirement.RequiredAccess)
+                || (user.SyncPlayAccess == SyncPlayAccess.JoinGroups || user.SyncPlayAccess == SyncPlayAccess.CreateAndJoinGroups))
+            {
+                context.Succeed(requirement);
+            }
+            else
+            {
+                context.Fail();
+            }
+
+            return Task.CompletedTask;
+        }
+    }
+}

+ 33 - 0
Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs

@@ -0,0 +1,33 @@
+using Jellyfin.Data.Enums;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
+{
+    /// <summary>
+    /// The default authorization requirement.
+    /// </summary>
+    public class SyncPlayAccessRequirement : IAuthorizationRequirement
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SyncPlayAccessRequirement"/> class.
+        /// </summary>
+        /// <param name="requiredAccess">A value of <see cref="SyncPlayAccess"/>.</param>
+        public SyncPlayAccessRequirement(SyncPlayAccess requiredAccess)
+        {
+            RequiredAccess = requiredAccess;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SyncPlayAccessRequirement"/> class.
+        /// </summary>
+        public SyncPlayAccessRequirement()
+        {
+            RequiredAccess = null;
+        }
+
+        /// <summary>
+        /// Gets the required SyncPlay access.
+        /// </summary>
+        public SyncPlayAccess? RequiredAccess { get; }
+    }
+}

+ 10 - 0
Jellyfin.Api/Constants/Policies.cs

@@ -49,5 +49,15 @@ namespace Jellyfin.Api.Constants
         /// Policy name for escaping schedule controls or requiring first time setup.
         /// </summary>
         public const string FirstTimeSetupOrIgnoreParentalControl = "FirstTimeSetupOrIgnoreParentalControl";
+
+        /// <summary>
+        /// Policy name for requiring access to SyncPlay.
+        /// </summary>
+        public const string SyncPlayAccess = "SyncPlayAccess";
+
+        /// <summary>
+        /// Policy name for requiring group creation access to SyncPlay.
+        /// </summary>
+        public const string SyncPlayCreateGroupAccess = "SyncPlayCreateGroupAccess";
     }
 }

+ 2 - 1
Jellyfin.Api/Controllers/SyncPlayController.cs

@@ -20,7 +20,7 @@ namespace Jellyfin.Api.Controllers
     /// <summary>
     /// The sync play controller.
     /// </summary>
-    [Authorize(Policy = Policies.DefaultAuthorization)]
+    [Authorize(Policy = Policies.SyncPlayAccess)]
     public class SyncPlayController : BaseJellyfinApiController
     {
         private readonly ISessionManager _sessionManager;
@@ -51,6 +51,7 @@ namespace Jellyfin.Api.Controllers
         /// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
         [HttpPost("New")]
         [ProducesResponseType(StatusCodes.Status204NoContent)]
+        [Authorize(Policy = Policies.SyncPlayCreateGroupAccess)]
         public ActionResult SyncPlayCreateGroup(
             [FromBody, Required] NewGroupRequestBody requestData)
         {

+ 17 - 0
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -15,9 +15,11 @@ using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
 using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
 using Jellyfin.Api.Auth.LocalAccessPolicy;
 using Jellyfin.Api.Auth.RequiresElevationPolicy;
+using Jellyfin.Api.Auth.SyncPlayAccessPolicy;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Controllers;
 using Jellyfin.Api.ModelBinders;
+using Jellyfin.Data.Enums;
 using Jellyfin.Server.Configuration;
 using Jellyfin.Server.Filters;
 using Jellyfin.Server.Formatters;
@@ -58,6 +60,7 @@ namespace Jellyfin.Server.Extensions
             serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
             serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessOrRequiresElevationHandler>();
             serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
+            serviceCollection.AddSingleton<IAuthorizationHandler, SyncPlayAccessHandler>();
             return serviceCollection.AddAuthorizationCore(options =>
             {
                 options.AddPolicy(
@@ -123,6 +126,20 @@ namespace Jellyfin.Server.Extensions
                         policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
                         policy.AddRequirements(new RequiresElevationRequirement());
                     });
+                options.AddPolicy(
+                    Policies.SyncPlayAccess,
+                    policy =>
+                    {
+                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+                        policy.AddRequirements(new SyncPlayAccessRequirement());
+                    });
+                options.AddPolicy(
+                    Policies.SyncPlayCreateGroupAccess,
+                    policy =>
+                    {
+                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+                        policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccess.CreateAndJoinGroups));
+                    });
             });
         }