Validate item access (#11171)

pull/11356/head
Cody Robibero 1 month ago committed by GitHub
parent 9a4db80085
commit 6fb6b5f176
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -46,6 +46,7 @@ using MediaBrowser.Model.Library;
using MediaBrowser.Model.Querying; using MediaBrowser.Model.Querying;
using MediaBrowser.Model.Tasks; using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using TMDbLib.Objects.Authentication;
using Episode = MediaBrowser.Controller.Entities.TV.Episode; using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using EpisodeInfo = Emby.Naming.TV.EpisodeInfo; using EpisodeInfo = Emby.Naming.TV.EpisodeInfo;
using Genre = MediaBrowser.Controller.Entities.Genre; using Genre = MediaBrowser.Controller.Entities.Genre;
@ -1222,12 +1223,7 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
/// <summary> /// <inheritdoc />
/// Gets the item by id.
/// </summary>
/// <param name="id">The id.</param>
/// <returns>BaseItem.</returns>
/// <exception cref="ArgumentNullException"><paramref name="id"/> is <c>null</c>.</exception>
public BaseItem GetItemById(Guid id) public BaseItem GetItemById(Guid id)
{ {
if (id.IsEmpty()) if (id.IsEmpty())
@ -1263,6 +1259,22 @@ namespace Emby.Server.Implementations.Library
return null; return null;
} }
/// <inheritdoc />
public T GetItemById<T>(Guid id, Guid userId)
where T : BaseItem
{
var user = userId.IsEmpty() ? null : _userManager.GetUserById(userId);
return GetItemById<T>(id, user);
}
/// <inheritdoc />
public T GetItemById<T>(Guid id, User user)
where T : BaseItem
{
var item = GetItemById<T>(id);
return ItemIsVisible(item, user) ? item : null;
}
public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent) public List<BaseItem> GetItemList(InternalItemsQuery query, bool allowExternalContent)
{ {
if (query.Recursive && !query.ParentId.IsEmpty()) if (query.Recursive && !query.ParentId.IsEmpty())
@ -3191,5 +3203,20 @@ namespace Emby.Server.Implementations.Library
CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions); CollectionFolder.SaveLibraryOptions(virtualFolderPath, libraryOptions);
} }
private static bool ItemIsVisible(BaseItem item, User user)
{
if (item is null)
{
return false;
}
if (user is null)
{
return true;
}
return item is UserRootFolder || item.IsVisibleStandalone(user);
}
} }
} }

@ -194,7 +194,7 @@ public class DisplayPreferencesController : BaseJellyfinApiController
foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase))) foreach (var key in displayPreferences.CustomPrefs.Keys.Where(key => key.StartsWith("landing-", StringComparison.OrdinalIgnoreCase)))
{ {
if (!Enum.TryParse<ViewType>(displayPreferences.CustomPrefs[key], true, out var type)) if (!Enum.TryParse<ViewType>(displayPreferences.CustomPrefs[key], true, out _))
{ {
_logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]); _logger.LogError("Invalid ViewType: {LandingScreenOption}", displayPreferences.CustomPrefs[key]);
displayPreferences.CustomPrefs.Remove(key); displayPreferences.CustomPrefs.Remove(key);

@ -162,7 +162,7 @@ public class FilterController : BaseJellyfinApiController
} }
else if (parentId.HasValue) else if (parentId.HasValue)
{ {
parentItem = _libraryManager.GetItemById(parentId.Value); parentItem = _libraryManager.GetItemById<BaseItem>(parentId.Value);
} }
var filters = new QueryFilters(); var filters = new QueryFilters();

@ -90,6 +90,7 @@ public class ImageController : BaseJellyfinApiController
/// <param name="userId">User Id.</param> /// <param name="userId">User Id.</param>
/// <response code="204">Image updated.</response> /// <response code="204">Image updated.</response>
/// <response code="403">User does not have permission to delete the image.</response> /// <response code="403">User does not have permission to delete the image.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("UserImage")] [HttpPost("UserImage")]
[Authorize] [Authorize]
@ -97,6 +98,7 @@ public class ImageController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)] [ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> PostUserImage( public async Task<ActionResult> PostUserImage(
[FromQuery] Guid? userId) [FromQuery] Guid? userId)
{ {
@ -289,7 +291,7 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromQuery] int? imageIndex) [FromQuery] int? imageIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -317,7 +319,7 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int imageIndex) [FromRoute] int imageIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -346,7 +348,7 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] ImageType imageType) [FromRoute, Required] ImageType imageType)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -390,7 +392,7 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] ImageType imageType, [FromRoute, Required] ImageType imageType,
[FromRoute] int imageIndex) [FromRoute] int imageIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -433,7 +435,7 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] int imageIndex, [FromRoute, Required] int imageIndex,
[FromQuery, Required] int newIndex) [FromQuery, Required] int newIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -456,7 +458,7 @@ public class ImageController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId) public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -559,7 +561,7 @@ public class ImageController : BaseJellyfinApiController
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromQuery] int? imageIndex) [FromQuery] int? imageIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -637,7 +639,7 @@ public class ImageController : BaseJellyfinApiController
[FromQuery] string? backgroundColor, [FromQuery] string? backgroundColor,
[FromQuery] string? foregroundLayer) [FromQuery] string? foregroundLayer)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -715,7 +717,7 @@ public class ImageController : BaseJellyfinApiController
[FromQuery] string? foregroundLayer, [FromQuery] string? foregroundLayer,
[FromRoute, Required] int imageIndex) [FromRoute, Required] int imageIndex)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -62,9 +62,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Songs/{itemId}/InstantMix")] [HttpGet("Songs/{itemId}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromSong(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -75,11 +77,16 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var item = _libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@ -99,9 +106,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Albums/{itemId}/InstantMix")] [HttpGet("Albums/{itemId}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromAlbum(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -112,15 +121,20 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var album = _libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(album, user, dtoOptions); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions); return GetResult(items, user, limit, dtoOptions);
} }
@ -136,9 +150,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Playlists/{itemId}/InstantMix")] [HttpGet("Playlists/{itemId}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromPlaylist(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -149,15 +165,20 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var playlist = (Playlist)_libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<Playlist>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
var items = _musicManager.GetInstantMixFromItem(playlist, user, dtoOptions); var items = _musicManager.GetInstantMixFromItem(item, user, dtoOptions);
return GetResult(items, user, limit, dtoOptions); return GetResult(items, user, limit, dtoOptions);
} }
@ -209,9 +230,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Artists/{itemId}/InstantMix")] [HttpGet("Artists/{itemId}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -222,11 +245,16 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var item = _libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@ -246,9 +274,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Items/{itemId}/InstantMix")] [HttpGet("Items/{itemId}/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromItem(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -259,11 +289,16 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var item = _libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@ -283,9 +318,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("Artists/InstantMix")] [HttpGet("Artists/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("Use GetInstantMixFromArtists")] [Obsolete("Use GetInstantMixFromArtists")]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromArtists2(
[FromQuery, Required] Guid id, [FromQuery, Required] Guid id,
@ -320,9 +357,11 @@ public class InstantMixController : 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">Instant playlist returned.</response> /// <response code="200">Instant playlist returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns> /// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the playlist items.</returns>
[HttpGet("MusicGenres/InstantMix")] [HttpGet("MusicGenres/InstantMix")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById( public ActionResult<QueryResult<BaseItemDto>> GetInstantMixFromMusicGenreById(
[FromQuery, Required] Guid id, [FromQuery, Required] Guid id,
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
@ -333,11 +372,16 @@ public class InstantMixController : BaseJellyfinApiController
[FromQuery] int? imageTypeLimit, [FromQuery] int? imageTypeLimit,
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
{ {
var item = _libraryManager.GetItemById(id);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(id, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User) .AddClientFields(User)
.AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes); .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);

@ -4,6 +4,8 @@ using System.ComponentModel.DataAnnotations;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -64,7 +66,7 @@ public class ItemLookupController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId) public ActionResult<IEnumerable<ExternalIdInfo>> GetExternalIdInfos([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -234,6 +236,7 @@ public class ItemLookupController : BaseJellyfinApiController
/// <param name="searchResult">The remote search result.</param> /// <param name="searchResult">The remote search result.</param>
/// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param> /// <param name="replaceAllImages">Optional. Whether or not to replace all images. Default: True.</param>
/// <response code="204">Item metadata refreshed.</response> /// <response code="204">Item metadata refreshed.</response>
/// <response code="404">Item not found.</response>
/// <returns> /// <returns>
/// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results. /// A <see cref="Task" /> that represents the asynchronous operation to get the remote search results.
/// The task result contains an <see cref="NoContentResult"/>. /// The task result contains an <see cref="NoContentResult"/>.
@ -241,12 +244,18 @@ public class ItemLookupController : BaseJellyfinApiController
[HttpPost("Items/RemoteSearch/Apply/{itemId}")] [HttpPost("Items/RemoteSearch/Apply/{itemId}")]
[Authorize(Policy = Policies.RequiresElevation)] [Authorize(Policy = Policies.RequiresElevation)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> ApplySearchCriteria( public async Task<ActionResult> ApplySearchCriteria(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromBody, Required] RemoteSearchResult searchResult, [FromBody, Required] RemoteSearchResult searchResult,
[FromQuery] bool replaceAllImages = true) [FromQuery] bool replaceAllImages = true)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
_logger.LogInformation( _logger.LogInformation(
"Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}", "Setting provider id's to item {ItemId}-{ItemName}: {@ProviderIds}",
item.Id, item.Id,

@ -2,7 +2,10 @@ using System;
using System.ComponentModel; using System.ComponentModel;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.IO; using MediaBrowser.Model.IO;
@ -61,7 +64,7 @@ public class ItemRefreshController : BaseJellyfinApiController
[FromQuery] bool replaceAllMetadata = false, [FromQuery] bool replaceAllMetadata = false,
[FromQuery] bool replaceAllImages = false) [FromQuery] bool replaceAllImages = false)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -5,6 +5,8 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
@ -72,7 +74,7 @@ public class ItemUpdateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request) public async Task<ActionResult> UpdateItem([FromRoute, Required] Guid itemId, [FromBody, Required] BaseItemDto request)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -145,7 +147,11 @@ public class ItemUpdateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId) public ActionResult<MetadataEditorInfo> GetMetadataEditorInfo([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
var info = new MetadataEditorInfo var info = new MetadataEditorInfo
{ {
@ -197,7 +203,7 @@ public class ItemUpdateController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType) public ActionResult UpdateItemContentType([FromRoute, Required] Guid itemId, [FromQuery] string? contentType)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -967,9 +967,13 @@ public class ItemsController : BaseJellyfinApiController
} }
var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
return (item == null) ? NotFound() : _userDataRepository.GetUserDataDto(item, user); return _userDataRepository.GetUserDataDto(item, user);
} }
/// <summary> /// <summary>
@ -1014,8 +1018,8 @@ public class ItemsController : BaseJellyfinApiController
} }
var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException(); var user = _userManager.GetUserById(requestUserId) ?? throw new ResourceNotFoundException();
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item == null) if (item is null)
{ {
return NotFound(); return NotFound();
} }

@ -102,7 +102,7 @@ public class LibraryController : BaseJellyfinApiController
[ProducesFile("video/*", "audio/*")] [ProducesFile("video/*", "audio/*")]
public ActionResult GetFile([FromRoute, Required] Guid itemId) public ActionResult GetFile([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -152,11 +152,10 @@ public class LibraryController : BaseJellyfinApiController
? (userId.IsNullOrEmpty() ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound("Item not found."); return NotFound();
} }
IEnumerable<BaseItem> themeItems; IEnumerable<BaseItem> themeItems;
@ -214,16 +213,14 @@ public class LibraryController : BaseJellyfinApiController
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? (userId.IsNullOrEmpty() ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound("Item not found."); return NotFound();
} }
IEnumerable<BaseItem> themeItems; IEnumerable<BaseItem> themeItems;
@ -286,7 +283,8 @@ public class LibraryController : BaseJellyfinApiController
userId, userId,
inheritFromParent); inheritFromParent);
if (themeSongs.Result is NotFoundObjectResult || themeVideos.Result is NotFoundObjectResult) if (themeSongs.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound }
|| themeVideos.Result is StatusCodeResult { StatusCode: StatusCodes.Status404NotFound })
{ {
return NotFound(); return NotFound();
} }
@ -327,6 +325,7 @@ public class LibraryController : BaseJellyfinApiController
/// <param name="itemId">The item id.</param> /// <param name="itemId">The item id.</param>
/// <response code="204">Item deleted.</response> /// <response code="204">Item deleted.</response>
/// <response code="401">Unauthorized access.</response> /// <response code="401">Unauthorized access.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Items/{itemId}")] [HttpDelete("Items/{itemId}")]
[Authorize] [Authorize]
@ -335,17 +334,18 @@ public class LibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteItem(Guid itemId) public ActionResult DeleteItem(Guid itemId)
{ {
var isApiKey = User.GetIsApiKey();
var userId = User.GetUserId(); var userId = User.GetUserId();
var user = !isApiKey && !userId.IsEmpty() var isApiKey = User.GetIsApiKey();
? _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException() var user = userId.IsEmpty() && isApiKey
: null; ? null
if (!isApiKey && user is null) : _userManager.GetUserById(userId);
if (user is null && !isApiKey)
{ {
return Unauthorized("Unauthorized access"); return NotFound();
} }
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -391,7 +391,7 @@ public class LibraryController : BaseJellyfinApiController
foreach (var i in ids) foreach (var i in ids)
{ {
var item = _libraryManager.GetItemById(i); var item = _libraryManager.GetItemById<BaseItem>(i, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -459,20 +459,18 @@ public class LibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{ {
var item = _libraryManager.GetItemById(itemId);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound("Item not found"); return NotFound();
} }
var baseItemDtos = new List<BaseItemDto>(); var baseItemDtos = new List<BaseItemDto>();
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
BaseItem? parent = item.GetParent(); BaseItem? parent = item.GetParent();
@ -644,14 +642,16 @@ public class LibraryController : BaseJellyfinApiController
[ProducesFile("video/*", "audio/*")] [ProducesFile("video/*", "audio/*")]
public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId) public async Task<ActionResult> GetDownload([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var userId = User.GetUserId();
var user = userId.IsEmpty()
? null
: _userManager.GetUserById(userId);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var user = _userManager.GetUserById(User.GetUserId());
if (user is not null) if (user is not null)
{ {
if (!item.CanDownload(user)) if (!item.CanDownload(user))
@ -704,12 +704,14 @@ public class LibraryController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields) [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? (userId.IsNullOrEmpty() ? (user is null
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -720,9 +722,6 @@ public class LibraryController : BaseJellyfinApiController
return new QueryResult<BaseItemDto>(); return new QueryResult<BaseItemDto>();
} }
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var dtoOptions = new DtoOptions { Fields = fields } var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User); .AddClientFields(User);

@ -6,6 +6,8 @@ using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LibraryStructureDto; using Jellyfin.Api.Models.LibraryStructureDto;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
@ -311,15 +313,21 @@ public class LibraryStructureController : BaseJellyfinApiController
/// </summary> /// </summary>
/// <param name="request">The library name and options.</param> /// <param name="request">The library name and options.</param>
/// <response code="204">Library updated.</response> /// <response code="204">Library updated.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("LibraryOptions")] [HttpPost("LibraryOptions")]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult UpdateLibraryOptions( public ActionResult UpdateLibraryOptions(
[FromBody] UpdateLibraryOptionsDto request) [FromBody] UpdateLibraryOptionsDto request)
{ {
var collectionFolder = (CollectionFolder)_libraryManager.GetItemById(request.Id); var item = _libraryManager.GetItemById<CollectionFolder>(request.Id, User.GetUserId());
if (item is null)
{
return NotFound();
}
collectionFolder.UpdateLibraryOptions(request.LibraryOptions); item.UpdateLibraryOptions(request.LibraryOptions);
return NoContent(); return NoContent();
} }
} }

@ -220,9 +220,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <param name="channelId">Channel id.</param> /// <param name="channelId">Channel id.</param>
/// <param name="userId">Optional. Attach user data.</param> /// <param name="userId">Optional. Attach user data.</param>
/// <response code="200">Live tv channel returned.</response> /// <response code="200">Live tv channel returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
[HttpGet("Channels/{channelId}")] [HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.LiveTvAccess)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{ {
@ -232,7 +234,12 @@ public class LiveTvController : BaseJellyfinApiController
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = channelId.IsEmpty() var item = channelId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(channelId); : _libraryManager.GetItemById<BaseItem>(channelId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(User); .AddClientFields(User);
@ -416,9 +423,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <param name="recordingId">Recording id.</param> /// <param name="recordingId">Recording id.</param>
/// <param name="userId">Optional. Attach user data.</param> /// <param name="userId">Optional. Attach user data.</param>
/// <response code="200">Recording returned.</response> /// <response code="200">Recording returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns> /// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
[HttpGet("Recordings/{recordingId}")] [HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Authorize(Policy = Policies.LiveTvAccess)] [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{ {
@ -426,7 +435,13 @@ public class LiveTvController : BaseJellyfinApiController
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = recordingId.IsEmpty() ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId); var item = recordingId.IsEmpty()
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById<BaseItem>(recordingId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions() var dtoOptions = new DtoOptions()
.AddClientFields(User); .AddClientFields(User);
@ -611,7 +626,8 @@ public class LiveTvController : BaseJellyfinApiController
{ {
query.IsSeries = true; query.IsSeries = true;
if (_libraryManager.GetItemById(librarySeriesId.Value) is Series series) var series = _libraryManager.GetItemById<Series>(librarySeriesId.Value);
if (series is not null)
{ {
query.Name = series.Name; query.Name = series.Name;
} }
@ -665,7 +681,8 @@ public class LiveTvController : BaseJellyfinApiController
{ {
query.IsSeries = true; query.IsSeries = true;
if (_libraryManager.GetItemById(body.LibrarySeriesId) is Series series) var series = _libraryManager.GetItemById<Series>(body.LibrarySeriesId);
if (series is not null)
{ {
query.Name = series.Name; query.Name = series.Name;
} }
@ -779,7 +796,7 @@ public class LiveTvController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId) public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{ {
var item = _libraryManager.GetItemById(recordingId); var item = _libraryManager.GetItemById<BaseItem>(recordingId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -7,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Extensions; using Jellyfin.Extensions;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Controller.Entities.Audio;
@ -66,37 +67,16 @@ public class LyricsController : BaseJellyfinApiController
[HttpGet("Audio/{itemId}/Lyrics")] [HttpGet("Audio/{itemId}/Lyrics")]
[Authorize] [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<LyricDto>> GetLyrics([FromRoute, Required] Guid itemId) public async Task<ActionResult<LyricDto>> GetLyrics([FromRoute, Required] Guid itemId)
{ {
var isApiKey = User.GetIsApiKey(); var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
var userId = User.GetUserId(); if (item is null)
if (!isApiKey && userId.IsEmpty())
{
return BadRequest();
}
var audio = _libraryManager.GetItemById<Audio>(itemId);
if (audio is null)
{ {
return NotFound(); return NotFound();
} }
if (!isApiKey) var result = await _lyricManager.GetLyricsAsync(item, CancellationToken.None).ConfigureAwait(false);
{
var user = _userManager.GetUserById(userId);
if (user is null)
{
return NotFound();
}
// Check the item is visible for the user
if (!audio.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {audio.Name}.");
}
}
var result = await _lyricManager.GetLyricsAsync(audio, CancellationToken.None).ConfigureAwait(false);
if (result is not null) if (result is not null)
{ {
return Ok(result); return Ok(result);
@ -124,8 +104,8 @@ public class LyricsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery, Required] string fileName) [FromQuery, Required] string fileName)
{ {
var audio = _libraryManager.GetItemById<Audio>(itemId); var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
if (audio is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
@ -147,7 +127,7 @@ public class LyricsController : BaseJellyfinApiController
{ {
await Request.Body.CopyToAsync(stream).ConfigureAwait(false); await Request.Body.CopyToAsync(stream).ConfigureAwait(false);
var uploadedLyric = await _lyricManager.SaveLyricAsync( var uploadedLyric = await _lyricManager.SaveLyricAsync(
audio, item,
format, format,
stream) stream)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -157,7 +137,7 @@ public class LyricsController : BaseJellyfinApiController
return BadRequest(); return BadRequest();
} }
_providerManager.QueueRefresh(audio.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
return Ok(uploadedLyric); return Ok(uploadedLyric);
} }
} }
@ -176,13 +156,13 @@ public class LyricsController : BaseJellyfinApiController
public async Task<ActionResult> DeleteLyrics( public async Task<ActionResult> DeleteLyrics(
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var audio = _libraryManager.GetItemById<Audio>(itemId); var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
if (audio is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
await _lyricManager.DeleteLyricsAsync(audio).ConfigureAwait(false); await _lyricManager.DeleteLyricsAsync(item).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@ -199,13 +179,13 @@ public class LyricsController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IReadOnlyList<RemoteLyricInfoDto>>> SearchRemoteLyrics([FromRoute, Required] Guid itemId) public async Task<ActionResult<IReadOnlyList<RemoteLyricInfoDto>>> SearchRemoteLyrics([FromRoute, Required] Guid itemId)
{ {
var audio = _libraryManager.GetItemById<Audio>(itemId); var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
if (audio is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var results = await _lyricManager.SearchLyricsAsync(audio, false, CancellationToken.None).ConfigureAwait(false); var results = await _lyricManager.SearchLyricsAsync(item, false, CancellationToken.None).ConfigureAwait(false);
return Ok(results); return Ok(results);
} }
@ -225,19 +205,19 @@ public class LyricsController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] string lyricId) [FromRoute, Required] string lyricId)
{ {
var audio = _libraryManager.GetItemById<Audio>(itemId); var item = _libraryManager.GetItemById<Audio>(itemId, User.GetUserId());
if (audio is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var downloadedLyrics = await _lyricManager.DownloadLyricsAsync(audio, lyricId, CancellationToken.None).ConfigureAwait(false); var downloadedLyrics = await _lyricManager.DownloadLyricsAsync(item, lyricId, CancellationToken.None).ConfigureAwait(false);
if (downloadedLyrics is null) if (downloadedLyrics is null)
{ {
return NotFound(); return NotFound();
} }
_providerManager.QueueRefresh(audio.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
return Ok(downloadedLyrics); return Ok(downloadedLyrics);
} }

@ -8,8 +8,10 @@ using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.MediaInfoDtos; using Jellyfin.Api.Models.MediaInfoDtos;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Devices; using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Model.MediaInfo; using MediaBrowser.Model.MediaInfo;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -32,6 +34,7 @@ public class MediaInfoController : BaseJellyfinApiController
private readonly ILibraryManager _libraryManager; private readonly ILibraryManager _libraryManager;
private readonly ILogger<MediaInfoController> _logger; private readonly ILogger<MediaInfoController> _logger;
private readonly MediaInfoHelper _mediaInfoHelper; private readonly MediaInfoHelper _mediaInfoHelper;
private readonly IUserManager _userManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="MediaInfoController"/> class. /// Initializes a new instance of the <see cref="MediaInfoController"/> class.
@ -41,18 +44,21 @@ public class MediaInfoController : BaseJellyfinApiController
/// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param> /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
/// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param> /// <param name="logger">Instance of the <see cref="ILogger{MediaInfoController}"/> interface.</param>
/// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param> /// <param name="mediaInfoHelper">Instance of the <see cref="MediaInfoHelper"/>.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface..</param>
public MediaInfoController( public MediaInfoController(
IMediaSourceManager mediaSourceManager, IMediaSourceManager mediaSourceManager,
IDeviceManager deviceManager, IDeviceManager deviceManager,
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILogger<MediaInfoController> logger, ILogger<MediaInfoController> logger,
MediaInfoHelper mediaInfoHelper) MediaInfoHelper mediaInfoHelper,
IUserManager userManager)
{ {
_mediaSourceManager = mediaSourceManager; _mediaSourceManager = mediaSourceManager;
_deviceManager = deviceManager; _deviceManager = deviceManager;
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_mediaInfoHelper = mediaInfoHelper; _mediaInfoHelper = mediaInfoHelper;
_userManager = userManager;
} }
/// <summary> /// <summary>
@ -61,16 +67,24 @@ public class MediaInfoController : BaseJellyfinApiController
/// <param name="itemId">The item id.</param> /// <param name="itemId">The item id.</param>
/// <param name="userId">The user id.</param> /// <param name="userId">The user id.</param>
/// <response code="200">Playback info returned.</response> /// <response code="200">Playback info returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns> /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback information.</returns>
[HttpGet("Items/{itemId}/PlaybackInfo")] [HttpGet("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId) public async Task<ActionResult<PlaybackInfoResponse>> GetPlaybackInfo([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
{ {
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
return await _mediaInfoHelper.GetPlaybackInfo( var user = userId.IsNullOrEmpty()
itemId, ? null
userId) : _userManager.GetUserById(userId.Value);
.ConfigureAwait(false); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
return await _mediaInfoHelper.GetPlaybackInfo(item, user).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -97,9 +111,11 @@ public class MediaInfoController : BaseJellyfinApiController
/// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param> /// <param name="allowAudioStreamCopy">Whether to allow to copy the audio stream. Default: true.</param>
/// <param name="playbackInfoDto">The playback info.</param> /// <param name="playbackInfoDto">The playback info.</param>
/// <response code="200">Playback info returned.</response> /// <response code="200">Playback info returned.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns> /// <returns>A <see cref="Task"/> containing a <see cref="PlaybackInfoResponse"/> with the playback info.</returns>
[HttpPost("Items/{itemId}/PlaybackInfo")] [HttpPost("Items/{itemId}/PlaybackInfo")]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo( public async Task<ActionResult<PlaybackInfoResponse>> GetPostedPlaybackInfo(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery, ParameterObsolete] Guid? userId, [FromQuery, ParameterObsolete] Guid? userId,
@ -148,9 +164,19 @@ public class MediaInfoController : BaseJellyfinApiController
allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true; allowVideoStreamCopy ??= playbackInfoDto?.AllowVideoStreamCopy ?? true;
allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true; allowAudioStreamCopy ??= playbackInfoDto?.AllowAudioStreamCopy ?? true;
userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var info = await _mediaInfoHelper.GetPlaybackInfo( var info = await _mediaInfoHelper.GetPlaybackInfo(
itemId, item,
userId, user,
mediaSourceId, mediaSourceId,
liveStreamId) liveStreamId)
.ConfigureAwait(false); .ConfigureAwait(false);
@ -163,8 +189,6 @@ public class MediaInfoController : BaseJellyfinApiController
if (profile is not null) if (profile is not null)
{ {
// set device specific data // set device specific data
var item = _libraryManager.GetItemById(itemId);
foreach (var mediaSource in info.MediaSources) foreach (var mediaSource in info.MediaSources)
{ {
_mediaInfoHelper.SetDeviceSpecificData( _mediaInfoHelper.SetDeviceSpecificData(

@ -482,8 +482,13 @@ public class PlaylistsController : BaseJellyfinApiController
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<Playlist>(playlistId, user);
if (item is null)
{
return NotFound();
}
var items = playlist.GetManageableItems().ToArray(); var items = item.GetManageableItems().ToArray();
var count = items.Length; var count = items.Length;
if (startIndex.HasValue) if (startIndex.HasValue)
{ {

@ -6,6 +6,7 @@ using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities; using Jellyfin.Data.Entities;
using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
@ -76,21 +77,21 @@ public class PlaystateController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed) [FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
} }
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
var item = _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, item, true, datePlayed); var dto = UpdatePlayedStatus(user, item, true, datePlayed);
foreach (var additionalUserInfo in session.AdditionalUsers) foreach (var additionalUserInfo in session.AdditionalUsers)
{ {
@ -141,21 +142,21 @@ public class PlaystateController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
} }
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false); var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
var item = _libraryManager.GetItemById(itemId);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var dto = UpdatePlayedStatus(user, item, false, null); var dto = UpdatePlayedStatus(user, item, false, null);
foreach (var additionalUserInfo in session.AdditionalUsers) foreach (var additionalUserInfo in session.AdditionalUsers)
{ {

@ -6,8 +6,11 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Constants; using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Controller; using MediaBrowser.Controller;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities; using MediaBrowser.Model.Entities;
@ -68,7 +71,7 @@ public class RemoteImageController : BaseJellyfinApiController
[FromQuery] string? providerName, [FromQuery] string? providerName,
[FromQuery] bool includeAllLanguages = false) [FromQuery] bool includeAllLanguages = false)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -127,7 +130,7 @@ public class RemoteImageController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId) public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -154,7 +157,7 @@ public class RemoteImageController : BaseJellyfinApiController
[FromQuery, Required] ImageType type, [FromQuery, Required] ImageType type,
[FromQuery] string? imageUrl) [FromQuery] string? imageUrl)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -211,7 +211,7 @@ public class SearchController : BaseJellyfinApiController
if (!item.ChannelId.IsEmpty()) if (!item.ChannelId.IsEmpty())
{ {
var channel = _libraryManager.GetItemById(item.ChannelId); var channel = _libraryManager.GetItemById<BaseItem>(item.ChannelId);
result.ChannelName = channel?.Name; result.ChannelName = channel?.Name;
} }

@ -12,6 +12,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.SubtitleDtos; using Jellyfin.Api.Models.SubtitleDtos;
using MediaBrowser.Common.Api; using MediaBrowser.Common.Api;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
@ -95,8 +96,7 @@ public class SubtitleController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] int index) [FromRoute, Required] int index)
{ {
var item = _libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
@ -113,18 +113,24 @@ public class SubtitleController : BaseJellyfinApiController
/// <param name="language">The language of the subtitles.</param> /// <param name="language">The language of the subtitles.</param>
/// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param> /// <param name="isPerfectMatch">Optional. Only show subtitles which are a perfect match.</param>
/// <response code="200">Subtitles retrieved.</response> /// <response code="200">Subtitles retrieved.</response>
/// <response code="404">Item not found.</response>
/// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns> /// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
[HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")] [HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
[Authorize(Policy = Policies.SubtitleManagement)] [Authorize(Policy = Policies.SubtitleManagement)]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles( public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] string language, [FromRoute, Required] string language,
[FromQuery] bool? isPerfectMatch) [FromQuery] bool? isPerfectMatch)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
return await _subtitleManager.SearchSubtitles(video, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false); return await _subtitleManager.SearchSubtitles(item, language, isPerfectMatch, false, CancellationToken.None).ConfigureAwait(false);
} }
/// <summary> /// <summary>
@ -133,22 +139,28 @@ public class SubtitleController : BaseJellyfinApiController
/// <param name="itemId">The item id.</param> /// <param name="itemId">The item id.</param>
/// <param name="subtitleId">The subtitle id.</param> /// <param name="subtitleId">The subtitle id.</param>
/// <response code="204">Subtitle downloaded.</response> /// <response code="204">Subtitle downloaded.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")] [HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
[Authorize(Policy = Policies.SubtitleManagement)] [Authorize(Policy = Policies.SubtitleManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DownloadRemoteSubtitles( public async Task<ActionResult> DownloadRemoteSubtitles(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromRoute, Required] string subtitleId) [FromRoute, Required] string subtitleId)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
try try
{ {
await _subtitleManager.DownloadSubtitles(video, subtitleId, CancellationToken.None) await _subtitleManager.DownloadSubtitles(item, subtitleId, CancellationToken.None)
.ConfigureAwait(false); .ConfigureAwait(false);
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -223,7 +235,7 @@ public class SubtitleController : BaseJellyfinApiController
if (string.IsNullOrEmpty(format)) if (string.IsNullOrEmpty(format))
{ {
var item = (Video)_libraryManager.GetItemById(itemId.Value); var item = _libraryManager.GetItemById<Video>(itemId.Value);
var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture); var idString = itemId.Value.ToString("N", CultureInfo.InvariantCulture);
var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false) var mediaSource = _mediaSourceManager.GetStaticMediaSources(item, false)
@ -321,10 +333,12 @@ public class SubtitleController : BaseJellyfinApiController
/// <param name="mediaSourceId">The media source id.</param> /// <param name="mediaSourceId">The media source id.</param>
/// <param name="segmentLength">The subtitle segment length.</param> /// <param name="segmentLength">The subtitle segment length.</param>
/// <response code="200">Subtitle playlist retrieved.</response> /// <response code="200">Subtitle playlist retrieved.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns> /// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")] [HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
[Authorize] [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesPlaylistFile] [ProducesPlaylistFile]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")] [SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> GetSubtitlePlaylist( public async Task<ActionResult> GetSubtitlePlaylist(
@ -333,7 +347,11 @@ public class SubtitleController : BaseJellyfinApiController
[FromRoute, Required] string mediaSourceId, [FromRoute, Required] string mediaSourceId,
[FromQuery, Required] int segmentLength) [FromQuery, Required] int segmentLength)
{ {
var item = (Video)_libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false); var mediaSource = await _mediaSourceManager.GetMediaSource(item, mediaSourceId, null, false, CancellationToken.None).ConfigureAwait(false);
@ -397,15 +415,21 @@ public class SubtitleController : BaseJellyfinApiController
/// <param name="itemId">The item the subtitle belongs to.</param> /// <param name="itemId">The item the subtitle belongs to.</param>
/// <param name="body">The request body.</param> /// <param name="body">The request body.</param>
/// <response code="204">Subtitle uploaded.</response> /// <response code="204">Subtitle uploaded.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns> /// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Videos/{itemId}/Subtitles")] [HttpPost("Videos/{itemId}/Subtitles")]
[Authorize(Policy = Policies.SubtitleManagement)] [Authorize(Policy = Policies.SubtitleManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> UploadSubtitle( public async Task<ActionResult> UploadSubtitle(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromBody, Required] UploadSubtitleDto body) [FromBody, Required] UploadSubtitleDto body)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
if (item is null)
{
return NotFound();
}
var bytes = Encoding.UTF8.GetBytes(body.Data); var bytes = Encoding.UTF8.GetBytes(body.Data);
var memoryStream = new MemoryStream(bytes, 0, bytes.Length, false, true); var memoryStream = new MemoryStream(bytes, 0, bytes.Length, false, true);
@ -416,7 +440,7 @@ public class SubtitleController : BaseJellyfinApiController
await using (stream.ConfigureAwait(false)) await using (stream.ConfigureAwait(false))
{ {
await _subtitleManager.UploadSubtitle( await _subtitleManager.UploadSubtitle(
video, item,
new SubtitleResponse new SubtitleResponse
{ {
Format = body.Format, Format = body.Format,
@ -425,7 +449,7 @@ public class SubtitleController : BaseJellyfinApiController
IsHearingImpaired = body.IsHearingImpaired, IsHearingImpaired = body.IsHearingImpaired,
Stream = stream Stream = stream
}).ConfigureAwait(false); }).ConfigureAwait(false);
_providerManager.QueueRefresh(video.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High); _providerManager.QueueRefresh(item.Id, new MetadataRefreshOptions(new DirectoryService(_fileSystem)), RefreshPriority.High);
return NoContent(); return NoContent();
} }
@ -452,7 +476,7 @@ public class SubtitleController : BaseJellyfinApiController
long? endPositionTicks, long? endPositionTicks,
bool copyTimestamps) bool copyTimestamps)
{ {
var item = _libraryManager.GetItemById(id); var item = _libraryManager.GetItemById<BaseItem>(id);
return _subtitleEncoder.GetSubtitles( return _subtitleEncoder.GetSubtitles(
item, item,

@ -5,6 +5,8 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Trickplay; using MediaBrowser.Controller.Trickplay;
using MediaBrowser.Model; using MediaBrowser.Model;
@ -84,7 +86,7 @@ public class TrickplayController : BaseJellyfinApiController
[FromRoute, Required] int index, [FromRoute, Required] int index,
[FromQuery] Guid? mediaSourceId) [FromQuery] Guid? mediaSourceId)
{ {
var item = _libraryManager.GetItemById(mediaSourceId ?? itemId); var item = _libraryManager.GetItemById<BaseItem>(itemId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -234,7 +234,7 @@ public class TvShowsController : BaseJellyfinApiController
if (seasonId.HasValue) // Season id was supplied. Get episodes by season id. if (seasonId.HasValue) // Season id was supplied. Get episodes by season id.
{ {
var item = _libraryManager.GetItemById(seasonId.Value); var item = _libraryManager.GetItemById<BaseItem>(seasonId.Value);
if (item is not Season seasonItem) if (item is not Season seasonItem)
{ {
return NotFound("No season exists with Id " + seasonId); return NotFound("No season exists with Id " + seasonId);
@ -244,7 +244,8 @@ public class TvShowsController : BaseJellyfinApiController
} }
else if (season.HasValue) // Season number was supplied. Get episodes by season number else if (season.HasValue) // Season number was supplied. Get episodes by season number
{ {
if (_libraryManager.GetItemById(seriesId) is not Series series) var series = _libraryManager.GetItemById<Series>(seriesId);
if (series is null)
{ {
return NotFound("Series not found"); return NotFound("Series not found");
} }
@ -259,7 +260,7 @@ public class TvShowsController : BaseJellyfinApiController
} }
else // No season number or season id was supplied. Returning all episodes. else // No season number or season id was supplied. Returning all episodes.
{ {
if (_libraryManager.GetItemById(seriesId) is not Series series) if (_libraryManager.GetItemById<BaseItem>(seriesId) is not Series series)
{ {
return NotFound("Series not found"); return NotFound("Series not found");
} }
@ -342,13 +343,13 @@ public class TvShowsController : BaseJellyfinApiController
var user = userId.IsNullOrEmpty() var user = userId.IsNullOrEmpty()
? null ? null
: _userManager.GetUserById(userId.Value); : _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<Series>(seriesId, user);
if (_libraryManager.GetItemById(seriesId) is not Series series) if (item is null)
{ {
return NotFound("Series not found"); return NotFound();
} }
var seasons = series.GetItemList(new InternalItemsQuery(user) var seasons = item.GetItemList(new InternalItemsQuery(user)
{ {
IsMissing = isMissing, IsMissing = isMissing,
IsSpecialSeason = isSpecialSeason, IsSpecialSeason = isSpecialSeason,

@ -9,7 +9,9 @@ using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Api.Models.StreamingDtos;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming; using MediaBrowser.Controller.Streaming;
@ -33,6 +35,7 @@ public class UniversalAudioController : BaseJellyfinApiController
private readonly MediaInfoHelper _mediaInfoHelper; private readonly MediaInfoHelper _mediaInfoHelper;
private readonly AudioHelper _audioHelper; private readonly AudioHelper _audioHelper;
private readonly DynamicHlsHelper _dynamicHlsHelper; private readonly DynamicHlsHelper _dynamicHlsHelper;
private readonly IUserManager _userManager;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="UniversalAudioController"/> class. /// Initializes a new instance of the <see cref="UniversalAudioController"/> class.
@ -42,18 +45,21 @@ public class UniversalAudioController : BaseJellyfinApiController
/// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param> /// <param name="mediaInfoHelper">Instance of <see cref="MediaInfoHelper"/>.</param>
/// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param> /// <param name="audioHelper">Instance of <see cref="AudioHelper"/>.</param>
/// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param> /// <param name="dynamicHlsHelper">Instance of <see cref="DynamicHlsHelper"/>.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
public UniversalAudioController( public UniversalAudioController(
ILibraryManager libraryManager, ILibraryManager libraryManager,
ILogger<UniversalAudioController> logger, ILogger<UniversalAudioController> logger,
MediaInfoHelper mediaInfoHelper, MediaInfoHelper mediaInfoHelper,
AudioHelper audioHelper, AudioHelper audioHelper,
DynamicHlsHelper dynamicHlsHelper) DynamicHlsHelper dynamicHlsHelper,
IUserManager userManager)
{ {
_libraryManager = libraryManager; _libraryManager = libraryManager;
_logger = logger; _logger = logger;
_mediaInfoHelper = mediaInfoHelper; _mediaInfoHelper = mediaInfoHelper;
_audioHelper = audioHelper; _audioHelper = audioHelper;
_dynamicHlsHelper = dynamicHlsHelper; _dynamicHlsHelper = dynamicHlsHelper;
_userManager = userManager;
} }
/// <summary> /// <summary>
@ -79,12 +85,14 @@ public class UniversalAudioController : BaseJellyfinApiController
/// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param> /// <param name="enableRedirection">Whether to enable redirection. Defaults to true.</param>
/// <response code="200">Audio stream returned.</response> /// <response code="200">Audio stream returned.</response>
/// <response code="302">Redirected to remote audio stream.</response> /// <response code="302">Redirected to remote audio stream.</response>
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="Task"/> containing the audio file.</returns> /// <returns>A <see cref="Task"/> containing the audio file.</returns>
[HttpGet("Audio/{itemId}/universal")] [HttpGet("Audio/{itemId}/universal")]
[HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")] [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
[Authorize] [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)] [ProducesResponseType(StatusCodes.Status302Found)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesAudioFile] [ProducesAudioFile]
public async Task<ActionResult> GetUniversalAudioStream( public async Task<ActionResult> GetUniversalAudioStream(
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
@ -106,20 +114,27 @@ public class UniversalAudioController : BaseJellyfinApiController
[FromQuery] bool breakOnNonKeyFrames = false, [FromQuery] bool breakOnNonKeyFrames = false,
[FromQuery] bool enableRedirection = true) [FromQuery] bool enableRedirection = true)
{ {
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
userId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
_logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile); _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
var info = await _mediaInfoHelper.GetPlaybackInfo( var info = await _mediaInfoHelper.GetPlaybackInfo(
itemId, item,
userId, user,
mediaSourceId) mediaSourceId)
.ConfigureAwait(false); .ConfigureAwait(false);
// set device specific data // set device specific data
var item = _libraryManager.GetItemById(itemId);
foreach (var sourceInfo in info.MediaSources) foreach (var sourceInfo in info.MediaSources)
{ {
_mediaInfoHelper.SetDeviceSpecificData( _mediaInfoHelper.SetDeviceSpecificData(

@ -77,8 +77,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -86,20 +86,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false); await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
@ -133,8 +125,8 @@ public class UserLibraryController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<BaseItemDto> GetRootFolder([FromQuery] Guid? userId) public ActionResult<BaseItemDto> GetRootFolder([FromQuery] Guid? userId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -172,8 +164,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -181,20 +173,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false); var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray(); var dtos = items.Select(i => _dtoService.GetBaseItemDto(i, dtoOptions, user)).ToArray();
@ -231,8 +215,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -240,20 +224,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return MarkFavorite(user, item, true); return MarkFavorite(user, item, true);
} }
@ -286,8 +262,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -295,20 +271,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return MarkFavorite(user, item, false); return MarkFavorite(user, item, false);
} }
@ -341,8 +309,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -350,20 +318,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return UpdateUserItemRatingInternal(user, item, null); return UpdateUserItemRatingInternal(user, item, null);
} }
@ -398,8 +358,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromRoute, Required] Guid itemId, [FromRoute, Required] Guid itemId,
[FromQuery] bool? likes) [FromQuery] bool? likes)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -407,20 +367,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
return UpdateUserItemRatingInternal(user, item, likes); return UpdateUserItemRatingInternal(user, item, likes);
} }
@ -455,8 +407,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -464,20 +416,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
if (item is IHasTrailers hasTrailers) if (item is IHasTrailers hasTrailers)
{ {
@ -519,8 +463,8 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] Guid? userId, [FromQuery] Guid? userId,
[FromRoute, Required] Guid itemId) [FromRoute, Required] Guid itemId)
{ {
var requestUserId = RequestHelpers.GetUserId(User, userId); userId = RequestHelpers.GetUserId(User, userId);
var user = _userManager.GetUserById(requestUserId); var user = _userManager.GetUserById(userId.Value);
if (user is null) if (user is null)
{ {
return NotFound(); return NotFound();
@ -528,20 +472,12 @@ public class UserLibraryController : BaseJellyfinApiController
var item = itemId.IsEmpty() var item = itemId.IsEmpty()
? _libraryManager.GetUserRootFolder() ? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
if (item is not UserRootFolder
// Check the item is visible for the user
&& !item.IsVisible(user))
{
return Unauthorized($"{user.Username} is not permitted to access item {item.Name}.");
}
var dtoOptions = new DtoOptions().AddClientFields(User); var dtoOptions = new DtoOptions().AddClientFields(User);
return Ok(item return Ok(item

@ -4,7 +4,10 @@ using System.Net.Mime;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -54,7 +57,7 @@ public class VideoAttachmentsController : BaseJellyfinApiController
{ {
try try
{ {
var item = _libraryManager.GetItemById(videoId); var item = _libraryManager.GetItemById<BaseItem>(videoId, User.GetUserId());
if (item is null) if (item is null)
{ {
return NotFound(); return NotFound();

@ -7,7 +7,6 @@ using System.Net.Http;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Jellyfin.Api.Attributes; using Jellyfin.Api.Attributes;
using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions; using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers; using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders; using Jellyfin.Api.ModelBinders;
@ -105,7 +104,11 @@ public class VideosController : BaseJellyfinApiController
? (userId.IsNullOrEmpty() ? (userId.IsNullOrEmpty()
? _libraryManager.RootFolder ? _libraryManager.RootFolder
: _libraryManager.GetUserRootFolder()) : _libraryManager.GetUserRootFolder())
: _libraryManager.GetItemById(itemId); : _libraryManager.GetItemById<BaseItem>(itemId, user);
if (item is null)
{
return NotFound();
}
var dtoOptions = new DtoOptions(); var dtoOptions = new DtoOptions();
dtoOptions = dtoOptions.AddClientFields(User); dtoOptions = dtoOptions.AddClientFields(User);
@ -139,24 +142,23 @@ public class VideosController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId) public async Task<ActionResult> DeleteAlternateSources([FromRoute, Required] Guid itemId)
{ {
var video = (Video)_libraryManager.GetItemById(itemId); var item = _libraryManager.GetItemById<Video>(itemId, User.GetUserId());
if (item is null)
if (video is null)
{ {
return NotFound("The video either does not exist or the id does not belong to a video."); return NotFound();
} }
if (video.LinkedAlternateVersions.Length == 0) if (item.LinkedAlternateVersions.Length == 0)
{ {
video = (Video?)_libraryManager.GetItemById(video.PrimaryVersionId); item = _libraryManager.GetItemById<Video>(Guid.Parse(item.PrimaryVersionId));
} }
if (video is null) if (item is null)
{ {
return NotFound(); return NotFound();
} }
foreach (var link in video.GetLinkedAlternateVersions()) foreach (var link in item.GetLinkedAlternateVersions())
{ {
link.SetPrimaryVersionId(null); link.SetPrimaryVersionId(null);
link.LinkedAlternateVersions = Array.Empty<LinkedChild>(); link.LinkedAlternateVersions = Array.Empty<LinkedChild>();
@ -164,9 +166,9 @@ public class VideosController : BaseJellyfinApiController
await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await link.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
} }
video.LinkedAlternateVersions = Array.Empty<LinkedChild>(); item.LinkedAlternateVersions = Array.Empty<LinkedChild>();
video.SetPrimaryVersionId(null); item.SetPrimaryVersionId(null);
await video.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false); await item.UpdateToRepositoryAsync(ItemUpdateType.MetadataEdit, CancellationToken.None).ConfigureAwait(false);
return NoContent(); return NoContent();
} }
@ -184,8 +186,9 @@ public class VideosController : BaseJellyfinApiController
[ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids) public async Task<ActionResult> MergeVersions([FromQuery, Required, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
{ {
var userId = User.GetUserId();
var items = ids var items = ids
.Select(i => _libraryManager.GetItemById(i)) .Select(i => _libraryManager.GetItemById<BaseItem>(i, userId))
.OfType<Video>() .OfType<Video>()
.OrderBy(i => i.Id) .OrderBy(i => i.Id)
.ToList(); .ToList();

@ -76,21 +76,17 @@ public class MediaInfoHelper
/// <summary> /// <summary>
/// Get playback info. /// Get playback info.
/// </summary> /// </summary>
/// <param name="id">Item id.</param> /// <param name="item">The item.</param>
/// <param name="userId">User Id.</param> /// <param name="user">The user.</param>
/// <param name="mediaSourceId">Media source id.</param> /// <param name="mediaSourceId">Media source id.</param>
/// <param name="liveStreamId">Live stream id.</param> /// <param name="liveStreamId">Live stream id.</param>
/// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns> /// <returns>A <see cref="Task"/> containing the <see cref="PlaybackInfoResponse"/>.</returns>
public async Task<PlaybackInfoResponse> GetPlaybackInfo( public async Task<PlaybackInfoResponse> GetPlaybackInfo(
Guid id, BaseItem item,
Guid? userId, User? user,
string? mediaSourceId = null, string? mediaSourceId = null,
string? liveStreamId = null) string? liveStreamId = null)
{ {
var user = userId.IsNullOrEmpty()
? null
: _userManager.GetUserById(userId.Value);
var item = _libraryManager.GetItemById(id);
var result = new PlaybackInfoResponse(); var result = new PlaybackInfoResponse();
MediaSourceInfo[] mediaSources; MediaSourceInfo[] mediaSources;
@ -402,7 +398,7 @@ public class MediaInfoHelper
if (profile is not null) if (profile is not null)
{ {
var item = _libraryManager.GetItemById(request.ItemId); var item = _libraryManager.GetItemById<BaseItem>(request.ItemId);
SetDeviceSpecificData( SetDeviceSpecificData(
item, item,

@ -11,6 +11,7 @@ using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration; using MediaBrowser.Common.Configuration;
using MediaBrowser.Common.Extensions; using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Streaming; using MediaBrowser.Controller.Streaming;
@ -107,7 +108,7 @@ public static class StreamingHelpers
?? state.SupportedSubtitleCodecs.FirstOrDefault(); ?? state.SupportedSubtitleCodecs.FirstOrDefault();
} }
var item = libraryManager.GetItemById(streamingRequest.Id); var item = libraryManager.GetItemById<BaseItem>(streamingRequest.Id);
state.IsInputVideo = item.MediaType == MediaType.Video; state.IsInputVideo = item.MediaType == MediaType.Video;
@ -125,7 +126,7 @@ public static class StreamingHelpers
if (mediaSource is null) if (mediaSource is null)
{ {
var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false); var mediaSources = await mediaSourceManager.GetPlaybackMediaSources(libraryManager.GetItemById<BaseItem>(streamingRequest.Id), null, false, false, cancellationToken).ConfigureAwait(false);
mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId) mediaSource = string.IsNullOrEmpty(streamingRequest.MediaSourceId)
? mediaSources[0] ? mediaSources[0]

@ -177,6 +177,26 @@ namespace MediaBrowser.Controller.Library
T GetItemById<T>(Guid id) T GetItemById<T>(Guid id)
where T : BaseItem; where T : BaseItem;
/// <summary>
/// Gets the item by id, as T, and validates user access.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="userId">The user id to validate against.</param>
/// <typeparam name="T">The type of item.</typeparam>
/// <returns>The item if found.</returns>
public T GetItemById<T>(Guid id, Guid userId)
where T : BaseItem;
/// <summary>
/// Gets the item by id, as T, and validates user access.
/// </summary>
/// <param name="id">The item id.</param>
/// <param name="user">The user to validate against.</param>
/// <typeparam name="T">The type of item.</typeparam>
/// <returns>The item if found.</returns>
public T GetItemById<T>(Guid id, User user)
where T : BaseItem;
/// <summary> /// <summary>
/// Gets the intros. /// Gets the intros.
/// </summary> /// </summary>

Loading…
Cancel
Save