From 7e0ea296c383b9b9cd778bb12834c2a73df3d1ea Mon Sep 17 00:00:00 2001 From: cvium Date: Thu, 3 Dec 2020 10:43:44 +0100 Subject: [PATCH] Move request validation to auth policies --- .../SyncPlay/SyncPlayManager.cs | 62 ------------------- .../SyncPlayAccessHandler.cs | 58 +++++++++++++++++ .../SyncPlayAccessRequirement.cs | 33 ++++++++++ Jellyfin.Api/Constants/Policies.cs | 10 +++ .../Controllers/SyncPlayController.cs | 3 +- .../ApiServiceCollectionExtensions.cs | 17 +++++ 6 files changed, 120 insertions(+), 63 deletions(-) create mode 100644 Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs create mode 100644 Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs index 7e1f24f8c2..0410048c4f 100644 --- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs +++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs @@ -102,11 +102,6 @@ namespace Emby.Server.Implementations.SyncPlay /// 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 /// 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 /// 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 /// public List ListGroups(SessionInfo session, ListGroupsRequest request) { - if (!IsRequestValid(session, request)) - { - return new List(); - } - var user = _userManager.GetUserById(session.UserId); List list = new List(); @@ -260,11 +240,6 @@ namespace Emby.Server.Implementations.SyncPlay /// 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!"); } } - - /// - /// Checks if a given session is allowed to make a given request. - /// - /// The session. - /// The request. - /// true if the request is valid, false otherwise. Will return false also when session or request is null. - 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(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(Guid.Empty, GroupUpdateType.CreateGroupDenied, string.Empty); - _sessionManager.SendSyncPlayGroupUpdate(session, error, CancellationToken.None); - return false; - } - - return true; - } } } diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs new file mode 100644 index 0000000000..2c3294523f --- /dev/null +++ b/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 +{ + /// + /// Default authorization handler. + /// + public class SyncPlayAccessHandler : BaseAuthorizationHandler + { + private readonly IUserManager _userManager; + + /// + /// Initializes a new instance of the class. + /// + /// Instance of the interface. + /// Instance of the interface. + /// Instance of the interface. + public SyncPlayAccessHandler( + IUserManager userManager, + INetworkManager networkManager, + IHttpContextAccessor httpContextAccessor) + : base(userManager, networkManager, httpContextAccessor) + { + _userManager = userManager; + } + + /// + 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; + } + } +} diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs new file mode 100644 index 0000000000..7fcaf69f6e --- /dev/null +++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs @@ -0,0 +1,33 @@ +using Jellyfin.Data.Enums; +using Microsoft.AspNetCore.Authorization; + +namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy +{ + /// + /// The default authorization requirement. + /// + public class SyncPlayAccessRequirement : IAuthorizationRequirement + { + /// + /// Initializes a new instance of the class. + /// + /// A value of . + public SyncPlayAccessRequirement(SyncPlayAccess requiredAccess) + { + RequiredAccess = requiredAccess; + } + + /// + /// Initializes a new instance of the class. + /// + public SyncPlayAccessRequirement() + { + RequiredAccess = null; + } + + /// + /// Gets the required SyncPlay access. + /// + public SyncPlayAccess? RequiredAccess { get; } + } +} diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs index 7d77674700..b35ceea1a3 100644 --- a/Jellyfin.Api/Constants/Policies.cs +++ b/Jellyfin.Api/Constants/Policies.cs @@ -49,5 +49,15 @@ namespace Jellyfin.Api.Constants /// Policy name for escaping schedule controls or requiring first time setup. /// public const string FirstTimeSetupOrIgnoreParentalControl = "FirstTimeSetupOrIgnoreParentalControl"; + + /// + /// Policy name for requiring access to SyncPlay. + /// + public const string SyncPlayAccess = "SyncPlayAccess"; + + /// + /// Policy name for requiring group creation access to SyncPlay. + /// + public const string SyncPlayCreateGroupAccess = "SyncPlayCreateGroupAccess"; } } diff --git a/Jellyfin.Api/Controllers/SyncPlayController.cs b/Jellyfin.Api/Controllers/SyncPlayController.cs index ed5ea3c8a7..763940c733 100644 --- a/Jellyfin.Api/Controllers/SyncPlayController.cs +++ b/Jellyfin.Api/Controllers/SyncPlayController.cs @@ -20,7 +20,7 @@ namespace Jellyfin.Api.Controllers /// /// The sync play controller. /// - [Authorize(Policy = Policies.DefaultAuthorization)] + [Authorize(Policy = Policies.SyncPlayAccess)] public class SyncPlayController : BaseJellyfinApiController { private readonly ISessionManager _sessionManager; @@ -51,6 +51,7 @@ namespace Jellyfin.Api.Controllers /// A indicating success. [HttpPost("New")] [ProducesResponseType(StatusCodes.Status204NoContent)] + [Authorize(Policy = Policies.SyncPlayCreateGroupAccess)] public ActionResult SyncPlayCreateGroup( [FromBody, Required] NewGroupRequestBody requestData) { diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 6cb88c9f73..cdcc4bb86a 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/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(); serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); 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)); + }); }); }