diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs index 12186e02e6..4ced64ae70 100644 --- a/Jellyfin.Api/Controllers/PlaylistsController.cs +++ b/Jellyfin.Api/Controllers/PlaylistsController.cs @@ -106,7 +106,7 @@ public class PlaylistsController : BaseJellyfinApiController /// The playlist id. /// The id. /// Playlist updated. - /// Unauthorized access. + /// Access forbidden. /// Playlist not found. /// /// A that represents the asynchronous operation to update a playlist. @@ -114,10 +114,11 @@ public class PlaylistsController : BaseJellyfinApiController /// [HttpPost("{playlistId}")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdatePlaylist( [FromRoute, Required] Guid playlistId, - [FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Disallow)] UpdatePlaylistDto updatePlaylistRequest) + [FromBody, Required] UpdatePlaylistDto updatePlaylistRequest) { var callingUserId = User.GetUserId(); @@ -132,7 +133,7 @@ public class PlaylistsController : BaseJellyfinApiController if (!isPermitted) { - return Unauthorized("Unauthorized access"); + return Forbid(); } await _playlistManager.UpdatePlaylist(new PlaylistUpdateRequest @@ -153,14 +154,14 @@ public class PlaylistsController : BaseJellyfinApiController /// /// The playlist id. /// Found shares. - /// Unauthorized access. + /// Access forbidden. /// Playlist not found. /// /// A list of objects. /// [HttpGet("{playlistId}/User")] [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetPlaylistUsers( [FromRoute, Required] Guid playlistId) @@ -173,10 +174,9 @@ public class PlaylistsController : BaseJellyfinApiController return NotFound("Playlist not found"); } - var isPermitted = playlist.OwnerUserId.Equals(userId) - || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId)); + var isPermitted = playlist.OwnerUserId.Equals(userId); - return isPermitted ? playlist.Shares.ToList() : Unauthorized("Unauthorized Access"); + return isPermitted ? playlist.Shares.ToList() : Forbid(); } /// @@ -186,7 +186,7 @@ public class PlaylistsController : BaseJellyfinApiController /// The user id. /// The . /// User's permissions modified. - /// Unauthorized access. + /// Access forbidden. /// Playlist not found. /// /// A that represents the asynchronous operation to modify an user's playlist permissions. @@ -194,7 +194,8 @@ public class PlaylistsController : BaseJellyfinApiController /// [HttpPost("{playlistId}/User/{userId}")] [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task UpdatePlaylistUser( [FromRoute, Required] Guid playlistId, [FromRoute, Required] Guid userId, @@ -208,12 +209,11 @@ public class PlaylistsController : BaseJellyfinApiController return NotFound("Playlist not found"); } - var isPermitted = playlist.OwnerUserId.Equals(callingUserId) - || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + var isPermitted = playlist.OwnerUserId.Equals(callingUserId); if (!isPermitted) { - return Unauthorized("Unauthorized access"); + return Forbid(); } await _playlistManager.AddUserToShares(new PlaylistUserUpdateRequest @@ -240,7 +240,7 @@ public class PlaylistsController : BaseJellyfinApiController /// [HttpDelete("{playlistId}/User/{userId}")] [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task RemoveUserFromPlaylist( [FromRoute, Required] Guid playlistId, @@ -259,7 +259,7 @@ public class PlaylistsController : BaseJellyfinApiController if (!isPermitted) { - return Unauthorized("Unauthorized access"); + return Forbid(); } var share = playlist.Shares.FirstOrDefault(s => s.UserId.Equals(userId)); @@ -280,15 +280,33 @@ public class PlaylistsController : BaseJellyfinApiController /// Item id, comma delimited. /// The userId. /// Items added to playlist. + /// Access forbidden. + /// Playlist not found. /// An on success. [HttpPost("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task AddItemToPlaylist( [FromRoute, Required] Guid playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids, [FromQuery] Guid? userId) { userId = RequestHelpers.GetUserId(User, userId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId.Value); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(userId.Value) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(userId.Value)); + + if (!isPermitted) + { + return Forbid(); + } + await _playlistManager.AddItemToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false); return NoContent(); } @@ -300,14 +318,34 @@ public class PlaylistsController : BaseJellyfinApiController /// The item id. /// The new index. /// Item moved to new index. + /// Access forbidden. + /// Playlist not found. /// An on success. [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task MoveItem( [FromRoute, Required] string playlistId, [FromRoute, Required] string itemId, [FromRoute, Required] int newIndex) { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + await _playlistManager.MoveItemAsync(playlistId, itemId, newIndex).ConfigureAwait(false); return NoContent(); } @@ -318,13 +356,33 @@ public class PlaylistsController : BaseJellyfinApiController /// The playlist id. /// The item ids, comma delimited. /// Items removed. + /// Access forbidden. + /// Playlist not found. /// An on success. [HttpDelete("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task RemoveItemFromPlaylist( [FromRoute, Required] string playlistId, [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds) { + var callingUserId = User.GetUserId(); + + var playlist = _playlistManager.GetPlaylistForUser(Guid.Parse(playlistId), callingUserId); + if (playlist is null) + { + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OwnerUserId.Equals(callingUserId) + || playlist.Shares.Any(s => s.CanEdit && s.UserId.Equals(callingUserId)); + + if (!isPermitted) + { + return Forbid(); + } + await _playlistManager.RemoveItemFromPlaylistAsync(playlistId, entryIds).ConfigureAwait(false); return NoContent(); } @@ -342,10 +400,12 @@ public class PlaylistsController : BaseJellyfinApiController /// Optional. The max number of images to return, per image type. /// Optional. The image types to include in the output. /// Original playlist returned. + /// Access forbidden. /// Playlist not found. /// The original playlist items. [HttpGet("{playlistId}/Items")] [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status404NotFound)] public ActionResult> GetPlaylistItems( [FromRoute, Required] Guid playlistId, @@ -359,10 +419,19 @@ public class PlaylistsController : BaseJellyfinApiController [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) { userId = RequestHelpers.GetUserId(User, userId); - var playlist = (Playlist)_libraryManager.GetItemById(playlistId); + var playlist = _playlistManager.GetPlaylistForUser(playlistId, userId.Value); if (playlist is null) { - return NotFound(); + return NotFound("Playlist not found"); + } + + var isPermitted = playlist.OpenAccess + || playlist.OwnerUserId.Equals(userId.Value) + || playlist.Shares.Any(s => s.UserId.Equals(userId.Value)); + + if (!isPermitted) + { + return Forbid(); } var user = userId.IsNullOrEmpty() diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs index 821b901a03..cbe4bd87f5 100644 --- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs +++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs @@ -23,7 +23,7 @@ namespace MediaBrowser.Controller.Playlists /// Creates the playlist. /// /// The . - /// Task<Playlist>. + /// The created playlist. Task CreatePlaylist(PlaylistCreationRequest request); ///