浏览代码

Validate requested user id (#8812)

Cody Robibero 2 年之前
父节点
当前提交
a527034ebe

+ 6 - 3
Jellyfin.Api/Controllers/ArtistsController.cs

@@ -118,6 +118,7 @@ public class ArtistsController : BaseJellyfinApiController
         [FromQuery] bool? enableImages = true,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -125,7 +126,7 @@ public class ArtistsController : BaseJellyfinApiController
         User? user = null;
         BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
 
-        if (userId.HasValue && !userId.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             user = _userManager.GetUserById(userId.Value);
         }
@@ -321,6 +322,7 @@ public class ArtistsController : BaseJellyfinApiController
         [FromQuery] bool? enableImages = true,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -328,7 +330,7 @@ public class ArtistsController : BaseJellyfinApiController
         User? user = null;
         BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
 
-        if (userId.HasValue && !userId.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             user = _userManager.GetUserById(userId.Value);
         }
@@ -462,11 +464,12 @@ public class ArtistsController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<BaseItemDto> GetArtistByName([FromRoute, Required] string name, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions().AddClientFields(User);
 
         var item = _libraryManager.GetArtist(name, dtoOptions);
 
-        if (userId.HasValue && !userId.Value.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             var user = _userManager.GetUserById(userId.Value);
 

+ 6 - 3
Jellyfin.Api/Controllers/ChannelsController.cs

@@ -60,11 +60,12 @@ public class ChannelsController : BaseJellyfinApiController
         [FromQuery] bool? supportsMediaDeletion,
         [FromQuery] bool? isFavorite)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         return _channelManager.GetChannels(new ChannelQuery
         {
             Limit = limit,
             StartIndex = startIndex,
-            UserId = userId ?? Guid.Empty,
+            UserId = userId.Value,
             SupportsLatestItems = supportsLatestItems,
             SupportsMediaDeletion = supportsMediaDeletion,
             IsFavorite = isFavorite
@@ -124,7 +125,8 @@ public class ChannelsController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] sortBy,
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -198,7 +200,8 @@ public class ChannelsController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 

+ 2 - 0
Jellyfin.Api/Controllers/DevicesController.cs

@@ -2,6 +2,7 @@ using System;
 using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Dtos;
 using Jellyfin.Data.Entities.Security;
 using Jellyfin.Data.Queries;
@@ -48,6 +49,7 @@ public class DevicesController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public async Task<ActionResult<QueryResult<DeviceInfo>>> GetDevices([FromQuery] bool? supportsSync, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         return await _deviceManager.GetDevicesForUser(userId, supportsSync).ConfigureAwait(false);
     }
 

+ 6 - 2
Jellyfin.Api/Controllers/FilterController.cs

@@ -1,5 +1,7 @@
 using System;
 using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
 using MediaBrowser.Controller.Dto;
@@ -51,7 +53,8 @@ public class FilterController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] BaseItemKind[] includeItemTypes,
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] string[] mediaTypes)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -143,7 +146,8 @@ public class FilterController : BaseJellyfinApiController
         [FromQuery] bool? isSeries,
         [FromQuery] bool? recursive)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 

+ 4 - 2
Jellyfin.Api/Controllers/GenresController.cs

@@ -90,11 +90,12 @@ public class GenresController : BaseJellyfinApiController
         [FromQuery] bool? enableImages = true,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
 
-        User? user = userId is null || userId.Value.Equals(default)
+        User? user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -155,6 +156,7 @@ public class GenresController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<BaseItemDto> GetGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions()
             .AddClientFields(User);
 
@@ -170,7 +172,7 @@ public class GenresController : BaseJellyfinApiController
 
         item ??= new Genre();
 
-        if (userId is null || userId.Value.Equals(default))
+        if (userId.Value.Equals(default))
         {
             return _dtoService.GetBaseItemDto(item, dtoOptions);
         }

+ 15 - 7
Jellyfin.Api/Controllers/InstantMixController.cs

@@ -2,6 +2,7 @@ using System;
 using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Dto;
@@ -74,7 +75,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var item = _libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -110,7 +112,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var album = _libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -146,7 +149,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var playlist = (Playlist)_libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -181,7 +185,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery] int? imageTypeLimit,
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -217,7 +222,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var item = _libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -253,7 +259,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var item = _libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }
@@ -326,7 +333,8 @@ public class InstantMixController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes)
     {
         var item = _libraryManager.GetItemById(id);
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }

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

@@ -240,7 +240,8 @@ public class ItemsController : BaseJellyfinApiController
     {
         var isApiKey = User.GetIsApiKey();
         // if api key is used (auth.IsApiKey == true), then `user` will be null throughout this method
-        var user = !isApiKey && userId.HasValue && !userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = !isApiKey && !userId.Value.Equals(default)
             ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
             : null;
 

+ 14 - 8
Jellyfin.Api/Controllers/LibraryController.cs

@@ -9,6 +9,7 @@ using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.LibraryDtos;
 using Jellyfin.Data.Entities;
@@ -142,12 +143,13 @@ public class LibraryController : BaseJellyfinApiController
         [FromQuery] Guid? userId,
         [FromQuery] bool inheritFromParent = false)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
         var item = itemId.Equals(default)
-            ? (userId is null || userId.Value.Equals(default)
+            ? (userId.Value.Equals(default)
                 ? _libraryManager.RootFolder
                 : _libraryManager.GetUserRootFolder())
             : _libraryManager.GetItemById(itemId);
@@ -208,12 +210,13 @@ public class LibraryController : BaseJellyfinApiController
         [FromQuery] Guid? userId,
         [FromQuery] bool inheritFromParent = false)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
         var item = itemId.Equals(default)
-            ? (userId is null || userId.Value.Equals(default)
+            ? (userId.Value.Equals(default)
                 ? _libraryManager.RootFolder
                 : _libraryManager.GetUserRootFolder())
             : _libraryManager.GetItemById(itemId);
@@ -403,7 +406,8 @@ public class LibraryController : BaseJellyfinApiController
         [FromQuery] Guid? userId,
         [FromQuery] bool? isFavorite)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -437,6 +441,7 @@ public class LibraryController : BaseJellyfinApiController
     public ActionResult<IEnumerable<BaseItemDto>> GetAncestors([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
     {
         var item = _libraryManager.GetItemById(itemId);
+        userId = RequestHelpers.GetUserId(User, userId);
 
         if (item is null)
         {
@@ -445,7 +450,7 @@ public class LibraryController : BaseJellyfinApiController
 
         var baseItemDtos = new List<BaseItemDto>();
 
-        var user = userId is null || userId.Value.Equals(default)
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -675,8 +680,9 @@ public class LibraryController : BaseJellyfinApiController
         [FromQuery] int? limit,
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var item = itemId.Equals(default)
-            ? (userId is null || userId.Value.Equals(default)
+            ? (userId.Value.Equals(default)
                 ? _libraryManager.RootFolder
                 : _libraryManager.GetUserRootFolder())
             : _libraryManager.GetItemById(itemId);
@@ -691,7 +697,7 @@ public class LibraryController : BaseJellyfinApiController
             return new QueryResult<BaseItemDto>();
         }
 
-        var user = userId is null || userId.Value.Equals(default)
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }

+ 17 - 9
Jellyfin.Api/Controllers/LiveTvController.cs

@@ -153,6 +153,7 @@ public class LiveTvController : BaseJellyfinApiController
         [FromQuery] bool enableFavoriteSorting = false,
         [FromQuery] bool addCurrentProgram = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -161,7 +162,7 @@ public class LiveTvController : BaseJellyfinApiController
             new LiveTvChannelQuery
             {
                 ChannelType = type,
-                UserId = userId ?? Guid.Empty,
+                UserId = userId.Value,
                 StartIndex = startIndex,
                 Limit = limit,
                 IsFavorite = isFavorite,
@@ -180,7 +181,7 @@ public class LiveTvController : BaseJellyfinApiController
             dtoOptions,
             CancellationToken.None);
 
-        var user = userId is null || userId.Value.Equals(default)
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -211,7 +212,8 @@ public class LiveTvController : BaseJellyfinApiController
     [Authorize(Policy = Policies.LiveTvAccess)]
     public ActionResult<BaseItemDto> GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var item = channelId.Equals(default)
@@ -271,6 +273,7 @@ public class LiveTvController : BaseJellyfinApiController
         [FromQuery] bool? isLibraryItem,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -279,7 +282,7 @@ public class LiveTvController : BaseJellyfinApiController
             new RecordingQuery
             {
                 ChannelId = channelId,
-                UserId = userId ?? Guid.Empty,
+                UserId = userId.Value,
                 StartIndex = startIndex,
                 Limit = limit,
                 Status = status,
@@ -382,7 +385,8 @@ public class LiveTvController : BaseJellyfinApiController
     [Authorize(Policy = Policies.LiveTvAccess)]
     public ActionResult<QueryResult<BaseItemDto>> GetRecordingFolders([FromQuery] Guid? userId)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var folders = _liveTvManager.GetRecordingFolders(user);
@@ -404,7 +408,8 @@ public class LiveTvController : BaseJellyfinApiController
     [Authorize(Policy = Policies.LiveTvAccess)]
     public ActionResult<BaseItemDto> GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var item = recordingId.Equals(default) ? _libraryManager.GetUserRootFolder() : _libraryManager.GetItemById(recordingId);
@@ -560,7 +565,8 @@ public class LiveTvController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ItemFields[] fields,
         [FromQuery] bool enableTotalRecordCount = true)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -699,7 +705,8 @@ public class LiveTvController : BaseJellyfinApiController
         [FromQuery] bool? enableUserData,
         [FromQuery] bool enableTotalRecordCount = true)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -737,7 +744,8 @@ public class LiveTvController : BaseJellyfinApiController
         [FromRoute, Required] string programId,
         [FromQuery] Guid? userId)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 

+ 4 - 1
Jellyfin.Api/Controllers/MediaInfoController.cs

@@ -132,6 +132,7 @@ public class MediaInfoController : BaseJellyfinApiController
         // Copy params from posted body
         // TODO clean up when breaking API compatibility.
         userId ??= playbackInfoDto?.UserId;
+        userId = RequestHelpers.GetUserId(User, userId);
         maxStreamingBitrate ??= playbackInfoDto?.MaxStreamingBitrate;
         startTimeTicks ??= playbackInfoDto?.StartTimeTicks;
         audioStreamIndex ??= playbackInfoDto?.AudioStreamIndex;
@@ -253,10 +254,12 @@ public class MediaInfoController : BaseJellyfinApiController
         [FromQuery] bool? enableDirectPlay,
         [FromQuery] bool? enableDirectStream)
     {
+        userId ??= openLiveStreamDto?.UserId;
+        userId = RequestHelpers.GetUserId(User, userId);
         var request = new LiveStreamRequest
         {
             OpenToken = openToken ?? openLiveStreamDto?.OpenToken,
-            UserId = userId ?? openLiveStreamDto?.UserId ?? Guid.Empty,
+            UserId = userId.Value,
             PlaySessionId = playSessionId ?? openLiveStreamDto?.PlaySessionId,
             MaxStreamingBitrate = maxStreamingBitrate ?? openLiveStreamDto?.MaxStreamingBitrate,
             StartTimeTicks = startTimeTicks ?? openLiveStreamDto?.StartTimeTicks,

+ 3 - 1
Jellyfin.Api/Controllers/MoviesController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Entities;
 using Jellyfin.Data.Enums;
@@ -67,7 +68,8 @@ public class MoviesController : BaseJellyfinApiController
         [FromQuery] int categoryLimit = 5,
         [FromQuery] int itemLimit = 8)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         var dtoOptions = new DtoOptions { Fields = fields }

+ 4 - 2
Jellyfin.Api/Controllers/MusicGenresController.cs

@@ -90,11 +90,12 @@ public class MusicGenresController : BaseJellyfinApiController
         [FromQuery] bool? enableImages = true,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, false, imageTypeLimit, enableImageTypes);
 
-        User? user = userId is null || userId.Value.Equals(default)
+        User? user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -144,6 +145,7 @@ public class MusicGenresController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<BaseItemDto> GetMusicGenre([FromRoute, Required] string genreName, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions().AddClientFields(User);
 
         MusicGenre? item;
@@ -162,7 +164,7 @@ public class MusicGenresController : BaseJellyfinApiController
             return NotFound();
         }
 
-        if (userId.HasValue && !userId.Value.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             var user = _userManager.GetUserById(userId.Value);
 

+ 5 - 2
Jellyfin.Api/Controllers/PersonsController.cs

@@ -2,6 +2,7 @@ using System;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Entities;
 using MediaBrowser.Controller.Dto;
@@ -77,11 +78,12 @@ public class PersonsController : BaseJellyfinApiController
         [FromQuery] Guid? userId,
         [FromQuery] bool? enableImages = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 
-        User? user = userId is null || userId.Value.Equals(default)
+        User? user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -117,6 +119,7 @@ public class PersonsController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status404NotFound)]
     public ActionResult<BaseItemDto> GetPerson([FromRoute, Required] string name, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions()
             .AddClientFields(User);
 
@@ -126,7 +129,7 @@ public class PersonsController : BaseJellyfinApiController
             return NotFound();
         }
 
-        if (userId.HasValue && !userId.Value.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             var user = _userManager.GetUserById(userId.Value);
             return _dtoService.GetBaseItemDto(item, dtoOptions, user);

+ 6 - 2
Jellyfin.Api/Controllers/PlaylistsController.cs

@@ -5,6 +5,7 @@ using System.Linq;
 using System.Threading.Tasks;
 using Jellyfin.Api.Attributes;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Api.Models.PlaylistDtos;
 using MediaBrowser.Controller.Dto;
@@ -81,11 +82,13 @@ public class PlaylistsController : BaseJellyfinApiController
             ids = createPlaylistRequest?.Ids ?? Array.Empty<Guid>();
         }
 
+        userId ??= createPlaylistRequest?.UserId ?? default;
+        userId = RequestHelpers.GetUserId(User, userId);
         var result = await _playlistManager.CreatePlaylist(new PlaylistCreationRequest
         {
             Name = name ?? createPlaylistRequest?.Name,
             ItemIdList = ids,
-            UserId = userId ?? createPlaylistRequest?.UserId ?? default,
+            UserId = userId.Value,
             MediaType = mediaType ?? createPlaylistRequest?.MediaType
         }).ConfigureAwait(false);
 
@@ -107,7 +110,8 @@ public class PlaylistsController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids,
         [FromQuery] Guid? userId)
     {
-        await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId ?? Guid.Empty).ConfigureAwait(false);
+        userId = RequestHelpers.GetUserId(User, userId);
+        await _playlistManager.AddToPlaylistAsync(playlistId, ids, userId.Value).ConfigureAwait(false);
         return NoContent();
     }
 

+ 3 - 8
Jellyfin.Api/Controllers/QuickConnectController.cs

@@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations;
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Authentication;
 using MediaBrowser.Controller.Net;
@@ -116,17 +117,11 @@ public class QuickConnectController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status403Forbidden)]
     public async Task<ActionResult<bool>> AuthorizeQuickConnect([FromQuery, Required] string code, [FromQuery] Guid? userId = null)
     {
-        var currentUserId = User.GetUserId();
-        var actualUserId = userId ?? currentUserId;
-
-        if (actualUserId.Equals(default) || (!userId.Equals(currentUserId) && !User.IsInRole(UserRoles.Administrator)))
-        {
-            return Forbid("Unknown user id");
-        }
+        userId = RequestHelpers.GetUserId(User, userId);
 
         try
         {
-            return await _quickConnect.AuthorizeRequest(actualUserId, code).ConfigureAwait(false);
+            return await _quickConnect.AuthorizeRequest(userId.Value, code).ConfigureAwait(false);
         }
         catch (AuthenticationException)
         {

+ 4 - 1
Jellyfin.Api/Controllers/SearchController.cs

@@ -3,6 +3,8 @@ using System.ComponentModel;
 using System.ComponentModel.DataAnnotations;
 using System.Globalization;
 using System.Linq;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
@@ -98,6 +100,7 @@ public class SearchController : BaseJellyfinApiController
         [FromQuery] bool includeStudios = true,
         [FromQuery] bool includeArtists = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var result = _searchEngine.GetSearchHints(new SearchQuery
         {
             Limit = limit,
@@ -108,7 +111,7 @@ public class SearchController : BaseJellyfinApiController
             IncludePeople = includePeople,
             IncludeStudios = includeStudios,
             StartIndex = startIndex,
-            UserId = userId ?? Guid.Empty,
+            UserId = userId.Value,
             IncludeItemTypes = includeItemTypes,
             ExcludeItemTypes = excludeItemTypes,
             MediaTypes = mediaTypes,

+ 4 - 2
Jellyfin.Api/Controllers/StudiosController.cs

@@ -86,11 +86,12 @@ public class StudiosController : BaseJellyfinApiController
         [FromQuery] bool? enableImages = true,
         [FromQuery] bool enableTotalRecordCount = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 
-        User? user = userId is null || userId.Value.Equals(default)
+        User? user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -139,10 +140,11 @@ public class StudiosController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<BaseItemDto> GetStudio([FromRoute, Required] string name, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions().AddClientFields(User);
 
         var item = _libraryManager.GetStudio(name);
-        if (userId.HasValue && !userId.Equals(default))
+        if (!userId.Equals(default))
         {
             var user = _userManager.GetUserById(userId.Value);
 

+ 10 - 5
Jellyfin.Api/Controllers/TvShowsController.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.ComponentModel.DataAnnotations;
 using System.Linq;
 using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
 using Jellyfin.Api.ModelBinders;
 using Jellyfin.Data.Enums;
 using Jellyfin.Extensions;
@@ -87,6 +88,7 @@ public class TvShowsController : BaseJellyfinApiController
         [FromQuery] bool disableFirstEpisode = false,
         [FromQuery] bool enableRewatching = false)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var options = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
@@ -98,7 +100,7 @@ public class TvShowsController : BaseJellyfinApiController
                 ParentId = parentId,
                 SeriesId = seriesId,
                 StartIndex = startIndex,
-                UserId = userId ?? Guid.Empty,
+                UserId = userId.Value,
                 EnableTotalRecordCount = enableTotalRecordCount,
                 DisableFirstEpisode = disableFirstEpisode,
                 NextUpDateCutoff = nextUpDateCutoff ?? DateTime.MinValue,
@@ -106,7 +108,7 @@ public class TvShowsController : BaseJellyfinApiController
             },
             options);
 
-        var user = userId is null || userId.Value.Equals(default)
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -144,7 +146,8 @@ public class TvShowsController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
         [FromQuery] bool? enableUserData)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -215,7 +218,8 @@ public class TvShowsController : BaseJellyfinApiController
         [FromQuery] bool? enableUserData,
         [FromQuery] string? sortBy)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
@@ -331,7 +335,8 @@ public class TvShowsController : BaseJellyfinApiController
         [FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] ImageType[] enableImageTypes,
         [FromQuery] bool? enableUserData)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 

+ 1 - 5
Jellyfin.Api/Controllers/UniversalAudioController.cs

@@ -106,11 +106,7 @@ public class UniversalAudioController : BaseJellyfinApiController
         [FromQuery] bool enableRedirection = true)
     {
         var deviceProfile = GetDeviceProfile(container, transcodingContainer, audioCodec, transcodingProtocol, breakOnNonKeyFrames, transcodingAudioChannels, maxAudioSampleRate, maxAudioBitDepth, maxAudioChannels);
-
-        if (!userId.HasValue || userId.Value.Equals(default))
-        {
-            userId = User.GetUserId();
-        }
+        userId = RequestHelpers.GetUserId(User, userId);
 
         _logger.LogInformation("GetPostedPlaybackInfo profile: {@Profile}", deviceProfile);
 

+ 3 - 2
Jellyfin.Api/Controllers/VideosController.cs

@@ -104,12 +104,13 @@ public class VideosController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status200OK)]
     public ActionResult<QueryResult<BaseItemDto>> GetAdditionalPart([FromRoute, Required] Guid itemId, [FromQuery] Guid? userId)
     {
-        var user = userId is null || userId.Value.Equals(default)
+        userId = RequestHelpers.GetUserId(User, userId);
+        var user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
 
         var item = itemId.Equals(default)
-            ? (userId is null || userId.Value.Equals(default)
+            ? (userId.Value.Equals(default)
                 ? _libraryManager.RootFolder
                 : _libraryManager.GetUserRootFolder())
             : _libraryManager.GetItemById(itemId);

+ 4 - 2
Jellyfin.Api/Controllers/YearsController.cs

@@ -85,11 +85,12 @@ public class YearsController : BaseJellyfinApiController
         [FromQuery] bool recursive = true,
         [FromQuery] bool? enableImages = true)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var dtoOptions = new DtoOptions { Fields = fields }
             .AddClientFields(User)
             .AddAdditionalDtoOptions(enableImages, enableUserData, imageTypeLimit, enableImageTypes);
 
-        User? user = userId is null || userId.Value.Equals(default)
+        User? user = userId.Value.Equals(default)
             ? null
             : _userManager.GetUserById(userId.Value);
         BaseItem parentItem = _libraryManager.GetParentItem(parentId, userId);
@@ -171,6 +172,7 @@ public class YearsController : BaseJellyfinApiController
     [ProducesResponseType(StatusCodes.Status404NotFound)]
     public ActionResult<BaseItemDto> GetYear([FromRoute, Required] int year, [FromQuery] Guid? userId)
     {
+        userId = RequestHelpers.GetUserId(User, userId);
         var item = _libraryManager.GetYear(year);
         if (item is null)
         {
@@ -180,7 +182,7 @@ public class YearsController : BaseJellyfinApiController
         var dtoOptions = new DtoOptions()
             .AddClientFields(User);
 
-        if (userId.HasValue && !userId.Value.Equals(default))
+        if (!userId.Value.Equals(default))
         {
             var user = _userManager.GetUserById(userId.Value);
             return _dtoService.GetBaseItemDto(item, dtoOptions, user);

+ 27 - 0
Jellyfin.Api/Helpers/RequestHelpers.cs

@@ -11,6 +11,7 @@ using MediaBrowser.Common.Extensions;
 using MediaBrowser.Controller.Dto;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Net;
 using MediaBrowser.Controller.Session;
 using MediaBrowser.Model.Dto;
 using MediaBrowser.Model.Querying;
@@ -55,6 +56,32 @@ public static class RequestHelpers
         return result;
     }
 
+    /// <summary>
+    /// Checks if the user can access a user.
+    /// </summary>
+    /// <param name="claimsPrincipal">The <see cref="ClaimsPrincipal"/> for the current request.</param>
+    /// <param name="userId">The user id.</param>
+    /// <returns>A <see cref="bool"/> whether the user can access the user.</returns>
+    internal static Guid GetUserId(ClaimsPrincipal claimsPrincipal, Guid? userId)
+    {
+        var authenticatedUserId = claimsPrincipal.GetUserId();
+
+        // UserId not provided, fall back to authenticated user id.
+        if (userId is null || userId.Value.Equals(default))
+        {
+            return authenticatedUserId;
+        }
+
+        // User must be administrator to access another user.
+        var isAdministrator = claimsPrincipal.IsInRole(UserRoles.Administrator);
+        if (!userId.Value.Equals(authenticatedUserId) && !isAdministrator)
+        {
+            throw new SecurityException("Forbidden");
+        }
+
+        return userId.Value;
+    }
+
     /// <summary>
     /// Checks if the user can update an entry.
     /// </summary>

+ 80 - 0
tests/Jellyfin.Api.Tests/Helpers/RequestHelpersTests.cs

@@ -1,7 +1,11 @@
 using System;
 using System.Collections.Generic;
+using System.Globalization;
+using System.Security.Claims;
+using Jellyfin.Api.Constants;
 using Jellyfin.Api.Helpers;
 using Jellyfin.Data.Enums;
+using MediaBrowser.Controller.Net;
 using Xunit;
 
 namespace Jellyfin.Api.Tests.Helpers
@@ -15,6 +19,82 @@ namespace Jellyfin.Api.Tests.Helpers
             Assert.Equal(expected, RequestHelpers.GetOrderBy(sortBy, requestedSortOrder));
         }
 
+        [Fact]
+        public static void GetUserId_IsAdmin()
+        {
+            Guid? requestUserId = Guid.NewGuid();
+            Guid? authUserId = Guid.NewGuid();
+
+            var claims = new[]
+            {
+                new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
+                new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
+                new Claim(ClaimTypes.Role, UserRoles.Administrator)
+            };
+
+            var identity = new ClaimsIdentity(claims, string.Empty);
+            var principal = new ClaimsPrincipal(identity);
+
+            var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+            Assert.Equal(requestUserId, userId);
+        }
+
+        [Fact]
+        public static void GetUserId_IsApiKey_EmptyGuid()
+        {
+            Guid? requestUserId = Guid.Empty;
+
+            var claims = new[]
+            {
+                new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
+            };
+
+            var identity = new ClaimsIdentity(claims, string.Empty);
+            var principal = new ClaimsPrincipal(identity);
+
+            var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+            Assert.Equal(Guid.Empty, userId);
+        }
+
+        [Fact]
+        public static void GetUserId_IsApiKey_Null()
+        {
+            Guid? requestUserId = null;
+
+            var claims = new[]
+            {
+                new Claim(InternalClaimTypes.IsApiKey, bool.TrueString)
+            };
+
+            var identity = new ClaimsIdentity(claims, string.Empty);
+            var principal = new ClaimsPrincipal(identity);
+
+            var userId = RequestHelpers.GetUserId(principal, requestUserId);
+
+            Assert.Equal(Guid.Empty, userId);
+        }
+
+        [Fact]
+        public static void GetUserId_IsUser()
+        {
+            Guid? requestUserId = Guid.NewGuid();
+            Guid? authUserId = Guid.NewGuid();
+
+            var claims = new[]
+            {
+                new Claim(InternalClaimTypes.UserId, authUserId.Value.ToString("N", CultureInfo.InvariantCulture)),
+                new Claim(InternalClaimTypes.IsApiKey, bool.FalseString),
+                new Claim(ClaimTypes.Role, UserRoles.User)
+            };
+
+            var identity = new ClaimsIdentity(claims, string.Empty);
+            var principal = new ClaimsPrincipal(identity);
+
+            Assert.Throws<SecurityException>(() => RequestHelpers.GetUserId(principal, requestUserId));
+        }
+
         public static TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]> GetOrderBy_Success_TestData()
         {
             var data = new TheoryData<IReadOnlyList<string>, IReadOnlyList<SortOrder>, (string, SortOrder)[]>();

+ 2 - 2
tests/Jellyfin.Server.Integration.Tests/Controllers/ItemsControllerTests.cs

@@ -22,13 +22,13 @@ public sealed class ItemsControllerTests : IClassFixture<JellyfinApplicationFact
     }
 
     [Fact]
-    public async Task GetItems_NoApiKeyOrUserId_BadRequest()
+    public async Task GetItems_NoApiKeyOrUserId_Success()
     {
         var client = _factory.CreateClient();
         client.DefaultRequestHeaders.AddAuthHeader(_accessToken ??= await AuthHelper.CompleteStartupAsync(client).ConfigureAwait(false));
 
         var response = await client.GetAsync("Items").ConfigureAwait(false);
-        Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
+        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
     }
 
     [Theory]