mirror of
				https://github.com/jellyfin/jellyfin.git
				synced 2025-11-02 18:47:18 -05:00 
			
		
		
		
	Fix access to playlists not created by a user (#9746)
This commit is contained in:
		
							parent
							
								
									fdda721394
								
							
						
					
					
						commit
						a8cdf4434b
					
				@ -30,7 +30,7 @@ namespace Emby.Server.Implementations.Library.Resolvers
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            if (args.IsDirectory)
 | 
					            if (args.IsDirectory)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // It's a boxset if the path is a directory with [playlist] in it's the name
 | 
					                // It's a boxset if the path is a directory with [playlist] in its name
 | 
				
			||||||
                var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
 | 
					                var filename = Path.GetFileName(Path.TrimEndingDirectorySeparator(args.Path));
 | 
				
			||||||
                if (string.IsNullOrEmpty(filename))
 | 
					                if (string.IsNullOrEmpty(filename))
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@ -42,7 +42,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
 | 
				
			|||||||
                    return new Playlist
 | 
					                    return new Playlist
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Path = args.Path,
 | 
					                        Path = args.Path,
 | 
				
			||||||
                        Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim()
 | 
					                        Name = filename.Replace("[playlist]", string.Empty, StringComparison.OrdinalIgnoreCase).Trim(),
 | 
				
			||||||
 | 
					                        OpenAccess = true
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -53,7 +54,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
 | 
				
			|||||||
                    return new Playlist
 | 
					                    return new Playlist
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        Path = args.Path,
 | 
					                        Path = args.Path,
 | 
				
			||||||
                        Name = filename
 | 
					                        Name = filename,
 | 
				
			||||||
 | 
					                        OpenAccess = true
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@ -70,7 +72,8 @@ namespace Emby.Server.Implementations.Library.Resolvers
 | 
				
			|||||||
                        Path = args.Path,
 | 
					                        Path = args.Path,
 | 
				
			||||||
                        Name = Path.GetFileNameWithoutExtension(args.Path),
 | 
					                        Name = Path.GetFileNameWithoutExtension(args.Path),
 | 
				
			||||||
                        IsInMixedFolder = true,
 | 
					                        IsInMixedFolder = true,
 | 
				
			||||||
                        PlaylistMediaType = MediaType.Audio
 | 
					                        PlaylistMediaType = MediaType.Audio,
 | 
				
			||||||
 | 
					                        OpenAccess = true
 | 
				
			||||||
                    };
 | 
					                    };
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -549,7 +549,7 @@ namespace Emby.Server.Implementations.Playlists
 | 
				
			|||||||
                        SavePlaylistFile(playlist);
 | 
					                        SavePlaylistFile(playlist);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else if (!playlist.OpenAccess)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    // Remove playlist if not shared
 | 
					                    // Remove playlist if not shared
 | 
				
			||||||
                    _libraryManager.DeleteItem(
 | 
					                    _libraryManager.DeleteItem(
 | 
				
			||||||
 | 
				
			|||||||
@ -64,12 +64,15 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="userId">The user id.</param>
 | 
					    /// <param name="userId">The user id.</param>
 | 
				
			||||||
    /// <param name="mediaType">The media type.</param>
 | 
					    /// <param name="mediaType">The media type.</param>
 | 
				
			||||||
    /// <param name="createPlaylistRequest">The create playlist payload.</param>
 | 
					    /// <param name="createPlaylistRequest">The create playlist payload.</param>
 | 
				
			||||||
 | 
					    /// <response code="200">Playlist created.</response>
 | 
				
			||||||
 | 
					    /// <response code="403">User does not have permission to create playlists.</response>
 | 
				
			||||||
    /// <returns>
 | 
					    /// <returns>
 | 
				
			||||||
    /// A <see cref="Task" /> that represents the asynchronous operation to create a playlist.
 | 
					    /// A <see cref="Task" /> that represents the asynchronous operation to create a playlist.
 | 
				
			||||||
    /// The task result contains an <see cref="OkResult"/> indicating success.
 | 
					    /// The task result contains an <see cref="OkResult"/> indicating success.
 | 
				
			||||||
    /// </returns>
 | 
					    /// </returns>
 | 
				
			||||||
    [HttpPost]
 | 
					    [HttpPost]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
    public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
 | 
					    public async Task<ActionResult<PlaylistCreationResult>> CreatePlaylist(
 | 
				
			||||||
        [FromQuery, ParameterObsolete] string? name,
 | 
					        [FromQuery, ParameterObsolete] string? name,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder)), ParameterObsolete] IReadOnlyList<Guid> ids,
 | 
				
			||||||
@ -102,9 +105,11 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="ids">Item id, comma delimited.</param>
 | 
					    /// <param name="ids">Item id, comma delimited.</param>
 | 
				
			||||||
    /// <param name="userId">The userId.</param>
 | 
					    /// <param name="userId">The userId.</param>
 | 
				
			||||||
    /// <response code="204">Items added to playlist.</response>
 | 
					    /// <response code="204">Items added to playlist.</response>
 | 
				
			||||||
 | 
					    /// <response code="403">User does not have permission to add items to playlist.</response>
 | 
				
			||||||
    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
					    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
				
			||||||
    [HttpPost("{playlistId}/Items")]
 | 
					    [HttpPost("{playlistId}/Items")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
    public async Task<ActionResult> AddToPlaylist(
 | 
					    public async Task<ActionResult> AddToPlaylist(
 | 
				
			||||||
        [FromRoute, Required] Guid playlistId,
 | 
					        [FromRoute, Required] Guid playlistId,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
 | 
				
			||||||
@ -122,9 +127,11 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="itemId">The item id.</param>
 | 
					    /// <param name="itemId">The item id.</param>
 | 
				
			||||||
    /// <param name="newIndex">The new index.</param>
 | 
					    /// <param name="newIndex">The new index.</param>
 | 
				
			||||||
    /// <response code="204">Item moved to new index.</response>
 | 
					    /// <response code="204">Item moved to new index.</response>
 | 
				
			||||||
 | 
					    /// <response code="403">User does not have permission to move item.</response>
 | 
				
			||||||
    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
					    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
				
			||||||
    [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
 | 
					    [HttpPost("{playlistId}/Items/{itemId}/Move/{newIndex}")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
    public async Task<ActionResult> MoveItem(
 | 
					    public async Task<ActionResult> MoveItem(
 | 
				
			||||||
        [FromRoute, Required] string playlistId,
 | 
					        [FromRoute, Required] string playlistId,
 | 
				
			||||||
        [FromRoute, Required] string itemId,
 | 
					        [FromRoute, Required] string itemId,
 | 
				
			||||||
@ -140,9 +147,11 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="playlistId">The playlist id.</param>
 | 
					    /// <param name="playlistId">The playlist id.</param>
 | 
				
			||||||
    /// <param name="entryIds">The item ids, comma delimited.</param>
 | 
					    /// <param name="entryIds">The item ids, comma delimited.</param>
 | 
				
			||||||
    /// <response code="204">Items removed.</response>
 | 
					    /// <response code="204">Items removed.</response>
 | 
				
			||||||
 | 
					    /// <response code="403">User does not have permission to get playlist.</response>
 | 
				
			||||||
    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
					    /// <returns>An <see cref="NoContentResult"/> on success.</returns>
 | 
				
			||||||
    [HttpDelete("{playlistId}/Items")]
 | 
					    [HttpDelete("{playlistId}/Items")]
 | 
				
			||||||
    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
					    [ProducesResponseType(StatusCodes.Status204NoContent)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
    public async Task<ActionResult> RemoveFromPlaylist(
 | 
					    public async Task<ActionResult> RemoveFromPlaylist(
 | 
				
			||||||
        [FromRoute, Required] string playlistId,
 | 
					        [FromRoute, Required] string playlistId,
 | 
				
			||||||
        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds)
 | 
					        [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] entryIds)
 | 
				
			||||||
@ -164,9 +173,13 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
					    /// <param name="imageTypeLimit">Optional. The max number of images to return, per image type.</param>
 | 
				
			||||||
    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
					    /// <param name="enableImageTypes">Optional. The image types to include in the output.</param>
 | 
				
			||||||
    /// <response code="200">Original playlist returned.</response>
 | 
					    /// <response code="200">Original playlist returned.</response>
 | 
				
			||||||
 | 
					    /// <response code="403">User does not have permission to get playlist items.</response>
 | 
				
			||||||
    /// <response code="404">Playlist not found.</response>
 | 
					    /// <response code="404">Playlist not found.</response>
 | 
				
			||||||
    /// <returns>The original playlist items.</returns>
 | 
					    /// <returns>The original playlist items.</returns>
 | 
				
			||||||
    [HttpGet("{playlistId}/Items")]
 | 
					    [HttpGet("{playlistId}/Items")]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status200OK)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status403Forbidden)]
 | 
				
			||||||
 | 
					    [ProducesResponseType(StatusCodes.Status404NotFound)]
 | 
				
			||||||
    public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
 | 
					    public ActionResult<QueryResult<BaseItemDto>> GetPlaylistItems(
 | 
				
			||||||
        [FromRoute, Required] Guid playlistId,
 | 
					        [FromRoute, Required] Guid playlistId,
 | 
				
			||||||
        [FromQuery, Required] Guid userId,
 | 
					        [FromQuery, Required] Guid userId,
 | 
				
			||||||
@ -189,9 +202,7 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
            : _userManager.GetUserById(userId);
 | 
					            : _userManager.GetUserById(userId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var items = playlist.GetManageableItems().ToArray();
 | 
					        var items = playlist.GetManageableItems().ToArray();
 | 
				
			||||||
 | 
					 | 
				
			||||||
        var count = items.Length;
 | 
					        var count = items.Length;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (startIndex.HasValue)
 | 
					        if (startIndex.HasValue)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            items = items.Skip(startIndex.Value).ToArray();
 | 
					            items = items.Skip(startIndex.Value).ToArray();
 | 
				
			||||||
@ -207,7 +218,6 @@ public class PlaylistsController : BaseJellyfinApiController
 | 
				
			|||||||
            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
					            .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
 | 
					        var dtos = _dtoService.GetBaseItemDtos(items.Select(i => i.Item2).ToList(), dtoOptions, user);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (int index = 0; index < dtos.Count; index++)
 | 
					        for (int index = 0; index < dtos.Count; index++)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            dtos[index].PlaylistItemId = items[index].Item1.Id;
 | 
					            dtos[index].PlaylistItemId = items[index].Item1.Id;
 | 
				
			||||||
 | 
				
			|||||||
@ -53,12 +53,19 @@ internal class FixPlaylistOwner : IMigrationRoutine
 | 
				
			|||||||
            foreach (var playlist in playlists)
 | 
					            foreach (var playlist in playlists)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                var shares = playlist.Shares;
 | 
					                var shares = playlist.Shares;
 | 
				
			||||||
                var firstEditShare = shares.First(x => x.CanEdit);
 | 
					                if (shares.Length > 0)
 | 
				
			||||||
                if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid))
 | 
					 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    playlist.OwnerUserId = guid;
 | 
					                    var firstEditShare = shares.First(x => x.CanEdit);
 | 
				
			||||||
                    playlist.Shares = shares.Where(x => x != firstEditShare).ToArray();
 | 
					                    if (firstEditShare is not null && Guid.TryParse(firstEditShare.UserId, out var guid))
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        playlist.OwnerUserId = guid;
 | 
				
			||||||
 | 
					                        playlist.Shares = shares.Where(x => x != firstEditShare).ToArray();
 | 
				
			||||||
 | 
					                        _playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult();
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    playlist.OpenAccess = true;
 | 
				
			||||||
                    _playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult();
 | 
					                    _playlistManager.UpdatePlaylistAsync(playlist).GetAwaiter().GetResult();
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
				
			|||||||
@ -34,10 +34,13 @@ namespace MediaBrowser.Controller.Playlists
 | 
				
			|||||||
        public Playlist()
 | 
					        public Playlist()
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            Shares = Array.Empty<Share>();
 | 
					            Shares = Array.Empty<Share>();
 | 
				
			||||||
 | 
					            OpenAccess = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Guid OwnerUserId { get; set; }
 | 
					        public Guid OwnerUserId { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        public bool OpenAccess { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Share[] Shares { get; set; }
 | 
					        public Share[] Shares { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        [JsonIgnore]
 | 
					        [JsonIgnore]
 | 
				
			||||||
@ -233,6 +236,11 @@ namespace MediaBrowser.Controller.Playlists
 | 
				
			|||||||
                return base.IsVisible(user);
 | 
					                return base.IsVisible(user);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (OpenAccess)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                return true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var userId = user.Id;
 | 
					            var userId = user.Id;
 | 
				
			||||||
            if (userId.Equals(OwnerUserId))
 | 
					            if (userId.Equals(OwnerUserId))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user