aboutsummaryrefslogtreecommitdiff
path: root/Jellyfin.Api
diff options
context:
space:
mode:
Diffstat (limited to 'Jellyfin.Api')
-rw-r--r--Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs3
-rw-r--r--Jellyfin.Api/Auth/BaseAuthorizationHandler.cs113
-rw-r--r--Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs53
-rw-r--r--Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs13
-rw-r--r--Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs44
-rw-r--r--Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs56
-rw-r--r--Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs56
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs (renamed from Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs)46
-rw-r--r--Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs25
-rw-r--r--Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs44
-rw-r--r--Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs27
-rw-r--r--Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs2
-rw-r--r--Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs44
-rw-r--r--Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs45
-rw-r--r--Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs11
-rw-r--r--Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs46
-rw-r--r--Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs6
-rw-r--r--Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs42
-rw-r--r--Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs26
-rw-r--r--Jellyfin.Api/Constants/Policies.cs20
-rw-r--r--Jellyfin.Api/Controllers/ArtistsController.cs12
-rw-r--r--Jellyfin.Api/Controllers/ChannelsController.cs12
-rw-r--r--Jellyfin.Api/Controllers/ClientLogController.cs3
-rw-r--r--Jellyfin.Api/Controllers/CollectionController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ConfigurationController.cs2
-rw-r--r--Jellyfin.Api/Controllers/DashboardController.cs3
-rw-r--r--Jellyfin.Api/Controllers/DevicesController.cs2
-rw-r--r--Jellyfin.Api/Controllers/DisplayPreferencesController.cs3
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs9
-rw-r--r--Jellyfin.Api/Controllers/GenresController.cs11
-rw-r--r--Jellyfin.Api/Controllers/HlsSegmentController.cs5
-rw-r--r--Jellyfin.Api/Controllers/ImageController.cs97
-rw-r--r--Jellyfin.Api/Controllers/InstantMixController.cs25
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs2
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs14
-rw-r--r--Jellyfin.Api/Controllers/LibraryController.cs116
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs137
-rw-r--r--Jellyfin.Api/Controllers/MediaInfoController.cs8
-rw-r--r--Jellyfin.Api/Controllers/MoviesController.cs7
-rw-r--r--Jellyfin.Api/Controllers/MusicGenresController.cs14
-rw-r--r--Jellyfin.Api/Controllers/PackageController.cs2
-rw-r--r--Jellyfin.Api/Controllers/PersonsController.cs10
-rw-r--r--Jellyfin.Api/Controllers/PlaylistsController.cs11
-rw-r--r--Jellyfin.Api/Controllers/PlaystateController.cs23
-rw-r--r--Jellyfin.Api/Controllers/PluginsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/QuickConnectController.cs13
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs4
-rw-r--r--Jellyfin.Api/Controllers/SearchController.cs6
-rw-r--r--Jellyfin.Api/Controllers/SessionController.cs32
-rw-r--r--Jellyfin.Api/Controllers/StudiosController.cs9
-rw-r--r--Jellyfin.Api/Controllers/SubtitleController.cs12
-rw-r--r--Jellyfin.Api/Controllers/SuggestionsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/SystemController.cs4
-rw-r--r--Jellyfin.Api/Controllers/TrailersController.cs3
-rw-r--r--Jellyfin.Api/Controllers/TvShowsController.cs18
-rw-r--r--Jellyfin.Api/Controllers/UniversalAudioController.cs9
-rw-r--r--Jellyfin.Api/Controllers/UserController.cs37
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs44
-rw-r--r--Jellyfin.Api/Controllers/UserViewsController.cs3
-rw-r--r--Jellyfin.Api/Controllers/VideosController.cs14
-rw-r--r--Jellyfin.Api/Controllers/YearsController.cs9
-rw-r--r--Jellyfin.Api/Helpers/MediaInfoHelper.cs2
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs34
-rw-r--r--Jellyfin.Api/Middleware/LanFilteringMiddleware.cs11
-rw-r--r--Jellyfin.Api/Models/UserDtos/CreateUserByName.cs7
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs2
-rw-r--r--Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs2
74 files changed, 731 insertions, 870 deletions
diff --git a/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs
index d4b1ffb06..741b88ea9 100644
--- a/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs
+++ b/Jellyfin.Api/Auth/AnonymousLanAccessPolicy/AnonymousLanAccessHandler.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -29,7 +30,7 @@ namespace Jellyfin.Api.Auth.AnonymousLanAccessPolicy
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AnonymousLanAccessRequirement requirement)
{
- var ip = _httpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
+ var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIp();
// Loopback will be on LAN, so we can accept null.
if (ip is null || _networkManager.IsInLocalNetwork(ip))
diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
deleted file mode 100644
index 8e5e66d64..000000000
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-using System.Security.Claims;
-using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
-using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Extensions;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth
-{
- /// <summary>
- /// Base authorization handler.
- /// </summary>
- /// <typeparam name="T">Type of Authorization Requirement.</typeparam>
- public abstract class BaseAuthorizationHandler<T> : AuthorizationHandler<T>
- where T : IAuthorizationRequirement
- {
- private readonly IUserManager _userManager;
- private readonly INetworkManager _networkManager;
- private readonly IHttpContextAccessor _httpContextAccessor;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="BaseAuthorizationHandler{T}"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- protected BaseAuthorizationHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- {
- _userManager = userManager;
- _networkManager = networkManager;
- _httpContextAccessor = httpContextAccessor;
- }
-
- /// <summary>
- /// Validate authenticated claims.
- /// </summary>
- /// <param name="claimsPrincipal">Request claims.</param>
- /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
- /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
- /// <param name="requiredDownloadPermission">Whether validation requires download permission.</param>
- /// <returns>Validated claim status.</returns>
- protected bool ValidateClaims(
- ClaimsPrincipal claimsPrincipal,
- bool ignoreSchedule = false,
- bool localAccessOnly = false,
- bool requiredDownloadPermission = false)
- {
- // ApiKey is currently global admin, always allow.
- var isApiKey = claimsPrincipal.GetIsApiKey();
- if (isApiKey)
- {
- return true;
- }
-
- // Ensure claim has userId.
- var userId = claimsPrincipal.GetUserId();
- if (userId.Equals(default))
- {
- return false;
- }
-
- // Ensure userId links to a valid user.
- var user = _userManager.GetUserById(userId);
- if (user is null)
- {
- return false;
- }
-
- // Ensure user is not disabled.
- if (user.HasPermission(PermissionKind.IsDisabled))
- {
- return false;
- }
-
- var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
- && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp());
-
- // User cannot access remotely and user is remote
- if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork)
- {
- return false;
- }
-
- if (localAccessOnly && !isInLocalNetwork)
- {
- return false;
- }
-
- // User attempting to access out of parental control hours.
- if (!ignoreSchedule
- && !user.HasPermission(PermissionKind.IsAdministrator)
- && !user.IsParentalScheduleAllowed())
- {
- return false;
- }
-
- // User attempting to download without permission.
- if (requiredDownloadPermission
- && !user.HasPermission(PermissionKind.EnableContentDownloading))
- {
- return false;
- }
-
- return true;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
index be77b7a4e..b1d97e4a1 100644
--- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs
@@ -1,4 +1,8 @@
using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
@@ -9,8 +13,12 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
/// <summary>
/// Default authorization handler.
/// </summary>
- public class DefaultAuthorizationHandler : BaseAuthorizationHandler<DefaultAuthorizationRequirement>
+ public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
+ private readonly IUserManager _userManager;
+ private readonly INetworkManager _networkManager;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
/// <summary>
/// Initializes a new instance of the <see cref="DefaultAuthorizationHandler"/> class.
/// </summary>
@@ -21,21 +29,56 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
{
+ _userManager = userManager;
+ _networkManager = networkManager;
+ _httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
{
- var validated = ValidateClaims(context.User);
- if (validated)
+ var isApiKey = context.User.GetIsApiKey();
+ var userId = context.User.GetUserId();
+ // This likely only happens during the wizard, so skip the default checks and let any other handlers do it
+ if (!isApiKey && userId.Equals(default))
+ {
+ return Task.CompletedTask;
+ }
+
+ var isInLocalNetwork = _httpContextAccessor.HttpContext is not null
+ && _networkManager.IsInLocalNetwork(_httpContextAccessor.HttpContext.GetNormalizedRemoteIp());
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ // User cannot access remotely and user is remote
+ if (!isInLocalNetwork && !user.HasPermission(PermissionKind.EnableRemoteAccess))
+ {
+ context.Fail();
+ return Task.CompletedTask;
+ }
+
+ // Admins can do everything
+ if (isApiKey || context.User.IsInRole(UserRoles.Administrator))
{
context.Succeed(requirement);
+ return Task.CompletedTask;
}
- else
+
+ // It's not great to have this check, but parental schedule must usually be honored except in a few rare cases
+ if (requirement.ValidateParentalSchedule && !user.IsParentalScheduleAllowed())
{
context.Fail();
+ return Task.CompletedTask;
+ }
+
+ // Only succeed if the requirement isn't a subclass as any subclassed requirement will handle success in its own handler
+ if (requirement.GetType() == typeof(DefaultAuthorizationRequirement))
+ {
+ context.Succeed(requirement);
}
return Task.CompletedTask;
diff --git a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs
index 7cea00b69..5ba1bc330 100644
--- a/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs
+++ b/Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs
@@ -7,5 +7,18 @@ namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
/// </summary>
public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DefaultAuthorizationRequirement"/> class.
+ /// </summary>
+ /// <param name="validateParentalSchedule">A value indicating whether to validate parental schedule.</param>
+ public DefaultAuthorizationRequirement(bool validateParentalSchedule = true)
+ {
+ ValidateParentalSchedule = validateParentalSchedule;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether to ignore parental schedule.
+ /// </summary>
+ public bool ValidateParentalSchedule { get; }
}
}
diff --git a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs b/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
deleted file mode 100644
index b61680ab1..000000000
--- a/Jellyfin.Api/Auth/DownloadPolicy/DownloadHandler.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.DownloadPolicy
-{
- /// <summary>
- /// Download authorization handler.
- /// </summary>
- public class DownloadHandler : BaseAuthorizationHandler<DownloadRequirement>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="DownloadHandler"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public DownloadHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
- {
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DownloadRequirement requirement)
- {
- var validated = ValidateClaims(context.User);
- if (validated)
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs b/Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs
deleted file mode 100644
index b0a72a9de..000000000
--- a/Jellyfin.Api/Auth/DownloadPolicy/DownloadRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.DownloadPolicy
-{
- /// <summary>
- /// The download permission requirement.
- /// </summary>
- public class DownloadRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
deleted file mode 100644
index 31482a930..000000000
--- a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupHandler.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
-{
- /// <summary>
- /// Ignore parental control schedule and allow before startup wizard has been completed.
- /// </summary>
- public class FirstTimeOrIgnoreParentalControlSetupHandler : BaseAuthorizationHandler<FirstTimeOrIgnoreParentalControlSetupRequirement>
- {
- private readonly IConfigurationManager _configurationManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FirstTimeOrIgnoreParentalControlSetupHandler"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
- public FirstTimeOrIgnoreParentalControlSetupHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor,
- IConfigurationManager configurationManager)
- : base(userManager, networkManager, httpContextAccessor)
- {
- _configurationManager = configurationManager;
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeOrIgnoreParentalControlSetupRequirement requirement)
- {
- if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
- {
- context.Succeed(requirement);
- return Task.CompletedTask;
- }
-
- var validated = ValidateClaims(context.User, ignoreSchedule: true);
- if (validated)
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs b/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs
deleted file mode 100644
index 00aaec334..000000000
--- a/Jellyfin.Api/Auth/FirstTimeOrIgnoreParentalControlSetupPolicy/FirstTimeOrIgnoreParentalControlSetupRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy
-{
- /// <summary>
- /// First time setup or ignore parental controls requirement.
- /// </summary>
- public class FirstTimeOrIgnoreParentalControlSetupRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
deleted file mode 100644
index dd0bd4ec2..000000000
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultHandler.cs
+++ /dev/null
@@ -1,56 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
-{
- /// <summary>
- /// Authorization handler for requiring first time setup or default privileges.
- /// </summary>
- public class FirstTimeSetupOrDefaultHandler : BaseAuthorizationHandler<FirstTimeSetupOrDefaultRequirement>
- {
- private readonly IConfigurationManager _configurationManager;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="FirstTimeSetupOrDefaultHandler" /> class.
- /// </summary>
- /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public FirstTimeSetupOrDefaultHandler(
- IConfigurationManager configurationManager,
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
- {
- _configurationManager = configurationManager;
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrDefaultRequirement requirement)
- {
- if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
- {
- context.Succeed(requirement);
- return Task.CompletedTask;
- }
-
- var validated = ValidateClaims(context.User);
- if (validated)
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs
deleted file mode 100644
index f7366bd7a..000000000
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrDefaultPolicy/FirstTimeSetupOrDefaultRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy
-{
- /// <summary>
- /// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
- /// </summary>
- public class FirstTimeSetupOrDefaultRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs
deleted file mode 100644
index 51ba637b6..000000000
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
-{
- /// <summary>
- /// The authorization requirement, requiring incomplete first time setup or elevated privileges, for the authorization handler.
- /// </summary>
- public class FirstTimeSetupOrElevatedRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs
index 90b76ee99..28ba25850 100644
--- a/Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs
+++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupHandler.cs
@@ -1,39 +1,36 @@
using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using Jellyfin.Api.Extensions;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
+namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
{
/// <summary>
- /// Authorization handler for requiring first time setup or elevated privileges.
+ /// Authorization handler for requiring first time setup or default privileges.
/// </summary>
- public class FirstTimeSetupOrElevatedHandler : BaseAuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
+ public class FirstTimeSetupHandler : AuthorizationHandler<FirstTimeSetupRequirement>
{
private readonly IConfigurationManager _configurationManager;
+ private readonly IUserManager _userManager;
/// <summary>
- /// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class.
+ /// Initializes a new instance of the <see cref="FirstTimeSetupHandler" /> class.
/// </summary>
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public FirstTimeSetupOrElevatedHandler(
+ public FirstTimeSetupHandler(
IConfigurationManager configurationManager,
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
+ IUserManager userManager)
{
_configurationManager = configurationManager;
+ _userManager = userManager;
}
/// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupOrElevatedRequirement requirement)
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, FirstTimeSetupRequirement requirement)
{
if (!_configurationManager.CommonConfiguration.IsStartupWizardCompleted)
{
@@ -41,14 +38,27 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
return Task.CompletedTask;
}
- var validated = ValidateClaims(context.User);
- if (validated && context.User.IsInRole(UserRoles.Administrator))
+ if (requirement.RequireAdmin && !context.User.IsInRole(UserRoles.Administrator))
+ {
+ context.Fail();
+ return Task.CompletedTask;
+ }
+
+ if (!requirement.ValidateParentalSchedule)
{
context.Succeed(requirement);
+ return Task.CompletedTask;
}
- else
+
+ var user = _userManager.GetUserById(context.User.GetUserId());
+ if (user is null)
{
- context.Fail();
+ throw new ResourceNotFoundException();
+ }
+
+ if (user.IsParentalScheduleAllowed())
+ {
+ context.Succeed(requirement);
}
return Task.CompletedTask;
diff --git a/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs
new file mode 100644
index 000000000..6252a2feb
--- /dev/null
+++ b/Jellyfin.Api/Auth/FirstTimeSetupPolicy/FirstTimeSetupRequirement.cs
@@ -0,0 +1,25 @@
+using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
+
+namespace Jellyfin.Api.Auth.FirstTimeSetupPolicy
+{
+ /// <summary>
+ /// The authorization requirement, requiring incomplete first time setup or default privileges, for the authorization handler.
+ /// </summary>
+ public class FirstTimeSetupRequirement : DefaultAuthorizationRequirement
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="FirstTimeSetupRequirement"/> class.
+ /// </summary>
+ /// <param name="validateParentalSchedule">A value indicating whether to ignore parental schedule.</param>
+ /// <param name="requireAdmin">A value indicating whether administrator role is required.</param>
+ public FirstTimeSetupRequirement(bool validateParentalSchedule = false, bool requireAdmin = true) : base(validateParentalSchedule)
+ {
+ RequireAdmin = requireAdmin;
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether administrator role is required.
+ /// </summary>
+ public bool RequireAdmin { get; }
+ }
+}
diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
deleted file mode 100644
index a7623556a..000000000
--- a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlHandler.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
-{
- /// <summary>
- /// Escape schedule controls handler.
- /// </summary>
- public class IgnoreParentalControlHandler : BaseAuthorizationHandler<IgnoreParentalControlRequirement>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="IgnoreParentalControlHandler"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public IgnoreParentalControlHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
- {
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreParentalControlRequirement requirement)
- {
- var validated = ValidateClaims(context.User, ignoreSchedule: true);
- if (validated)
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs b/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs
deleted file mode 100644
index cdad74270..000000000
--- a/Jellyfin.Api/Auth/IgnoreParentalControlPolicy/IgnoreParentalControlRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.IgnoreParentalControlPolicy
-{
- /// <summary>
- /// Escape schedule controls requirement.
- /// </summary>
- public class IgnoreParentalControlRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
index 14722aa57..6ed6fc90b 100644
--- a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
@@ -1,7 +1,7 @@
-using System.Threading.Tasks;
+using System.Threading.Tasks;
using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
@@ -10,27 +10,38 @@ namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
/// <summary>
/// Local access or require elevated privileges handler.
/// </summary>
- public class LocalAccessOrRequiresElevationHandler : BaseAuthorizationHandler<LocalAccessOrRequiresElevationRequirement>
+ public class LocalAccessOrRequiresElevationHandler : AuthorizationHandler<LocalAccessOrRequiresElevationRequirement>
{
+ private readonly INetworkManager _networkManager;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
/// <summary>
/// Initializes a new instance of the <see cref="LocalAccessOrRequiresElevationHandler"/> class.
/// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
/// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
/// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public LocalAccessOrRequiresElevationHandler(
- IUserManager userManager,
INetworkManager networkManager,
IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
{
+ _networkManager = networkManager;
+ _httpContextAccessor = httpContextAccessor;
}
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement)
{
- var validated = ValidateClaims(context.User, localAccessOnly: true);
- if (validated || context.User.IsInRole(UserRoles.Administrator))
+ var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIp();
+
+ // Loopback will be on LAN, so we can accept null.
+ if (ip is null || _networkManager.IsInLocalNetwork(ip))
+ {
+ context.Succeed(requirement);
+
+ return Task.CompletedTask;
+ }
+
+ if (context.User.IsInRole(UserRoles.Administrator))
{
context.Succeed(requirement);
}
diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
index d9c64d01c..f633c69d8 100644
--- a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
@@ -1,4 +1,4 @@
-using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Authorization;
namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
{
diff --git a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
deleted file mode 100644
index d772ec554..000000000
--- a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
+++ /dev/null
@@ -1,44 +0,0 @@
-using System.Threading.Tasks;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.LocalAccessPolicy
-{
- /// <summary>
- /// Local access handler.
- /// </summary>
- public class LocalAccessHandler : BaseAuthorizationHandler<LocalAccessRequirement>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="LocalAccessHandler"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public LocalAccessHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
- {
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessRequirement requirement)
- {
- var validated = ValidateClaims(context.User, localAccessOnly: true);
- if (validated)
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessRequirement.cs b/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessRequirement.cs
deleted file mode 100644
index 761127fa4..000000000
--- a/Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.LocalAccessPolicy
-{
- /// <summary>
- /// The local access authorization requirement.
- /// </summary>
- public class LocalAccessRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs
deleted file mode 100644
index b235c4b63..000000000
--- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
-using MediaBrowser.Common.Net;
-using MediaBrowser.Controller.Library;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-
-namespace Jellyfin.Api.Auth.RequiresElevationPolicy
-{
- /// <summary>
- /// Authorization handler for requiring elevated privileges.
- /// </summary>
- public class RequiresElevationHandler : BaseAuthorizationHandler<RequiresElevationRequirement>
- {
- /// <summary>
- /// Initializes a new instance of the <see cref="RequiresElevationHandler"/> class.
- /// </summary>
- /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
- public RequiresElevationHandler(
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
- {
- }
-
- /// <inheritdoc />
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement)
- {
- var validated = ValidateClaims(context.User);
- if (validated && context.User.IsInRole(UserRoles.Administrator))
- {
- context.Succeed(requirement);
- }
- else
- {
- context.Fail();
- }
-
- return Task.CompletedTask;
- }
- }
-}
diff --git a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs b/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs
deleted file mode 100644
index cfff1cc0c..000000000
--- a/Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationRequirement.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Microsoft.AspNetCore.Authorization;
-
-namespace Jellyfin.Api.Auth.RequiresElevationPolicy
-{
- /// <summary>
- /// The authorization requirement for requiring elevated privileges in the authorization handler.
- /// </summary>
- public class RequiresElevationRequirement : IAuthorizationRequirement
- {
- }
-}
diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
index cdd7d8a52..75ec9fcec 100644
--- a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
+++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessHandler.cs
@@ -1,19 +1,17 @@
using System.Threading.Tasks;
using Jellyfin.Api.Extensions;
-using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
-using MediaBrowser.Common.Net;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.SyncPlay;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
{
/// <summary>
/// Default authorization handler.
/// </summary>
- public class SyncPlayAccessHandler : BaseAuthorizationHandler<SyncPlayAccessRequirement>
+ public class SyncPlayAccessHandler : AuthorizationHandler<SyncPlayAccessRequirement>
{
private readonly ISyncPlayManager _syncPlayManager;
private readonly IUserManager _userManager;
@@ -23,14 +21,9 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
/// </summary>
/// <param name="syncPlayManager">Instance of the <see cref="ISyncPlayManager"/> interface.</param>
/// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
- /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
public SyncPlayAccessHandler(
ISyncPlayManager syncPlayManager,
- IUserManager userManager,
- INetworkManager networkManager,
- IHttpContextAccessor httpContextAccessor)
- : base(userManager, networkManager, httpContextAccessor)
+ IUserManager userManager)
{
_syncPlayManager = syncPlayManager;
_userManager = userManager;
@@ -39,27 +32,20 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
/// <inheritdoc />
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SyncPlayAccessRequirement requirement)
{
- if (!ValidateClaims(context.User))
- {
- context.Fail();
- return Task.CompletedTask;
- }
-
var userId = context.User.GetUserId();
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ throw new ResourceNotFoundException();
+ }
if (requirement.RequiredAccess == SyncPlayAccessRequirementType.HasAccess)
{
- if (user.SyncPlayAccess == SyncPlayUserAccessType.CreateAndJoinGroups
- || user.SyncPlayAccess == SyncPlayUserAccessType.JoinGroups
+ if (user.SyncPlayAccess is SyncPlayUserAccessType.CreateAndJoinGroups or SyncPlayUserAccessType.JoinGroups
|| _syncPlayManager.IsUserActive(userId))
{
context.Succeed(requirement);
}
- else
- {
- context.Fail();
- }
}
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.CreateGroup)
{
@@ -67,10 +53,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
{
context.Succeed(requirement);
}
- else
- {
- context.Fail();
- }
}
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.JoinGroup)
{
@@ -79,10 +61,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
{
context.Succeed(requirement);
}
- else
- {
- context.Fail();
- }
}
else if (requirement.RequiredAccess == SyncPlayAccessRequirementType.IsInGroup)
{
@@ -90,14 +68,6 @@ namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
{
context.Succeed(requirement);
}
- else
- {
- context.Fail();
- }
- }
- else
- {
- context.Fail();
}
return Task.CompletedTask;
diff --git a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs
index 6fab4c0ad..220b223b3 100644
--- a/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs
+++ b/Jellyfin.Api/Auth/SyncPlayAccessPolicy/SyncPlayAccessRequirement.cs
@@ -1,12 +1,12 @@
-using Jellyfin.Data.Enums;
-using Microsoft.AspNetCore.Authorization;
+using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
+using Jellyfin.Data.Enums;
namespace Jellyfin.Api.Auth.SyncPlayAccessPolicy
{
/// <summary>
/// The default authorization requirement.
/// </summary>
- public class SyncPlayAccessRequirement : IAuthorizationRequirement
+ public class SyncPlayAccessRequirement : DefaultAuthorizationRequirement
{
/// <summary>
/// Initializes a new instance of the <see cref="SyncPlayAccessRequirement"/> class.
diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs
new file mode 100644
index 000000000..e72bec46f
--- /dev/null
+++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionHandler.cs
@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Extensions;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.UserPermissionPolicy
+{
+ /// <summary>
+ /// User permission authorization handler.
+ /// </summary>
+ public class UserPermissionHandler : AuthorizationHandler<UserPermissionRequirement>
+ {
+ private readonly IUserManager _userManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserPermissionHandler"/> class.
+ /// </summary>
+ /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+ public UserPermissionHandler(IUserManager userManager)
+ {
+ _userManager = userManager;
+ }
+
+ /// <inheritdoc />
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserPermissionRequirement requirement)
+ {
+ var user = _userManager.GetUserById(context.User.GetUserId());
+ if (user is null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
+ if (user.HasPermission(requirement.RequiredPermission))
+ {
+ context.Succeed(requirement);
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs
new file mode 100644
index 000000000..4694556eb
--- /dev/null
+++ b/Jellyfin.Api/Auth/UserPermissionPolicy/UserPermissionRequirement.cs
@@ -0,0 +1,26 @@
+using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
+using Jellyfin.Data.Enums;
+
+namespace Jellyfin.Api.Auth.UserPermissionPolicy
+{
+ /// <summary>
+ /// The user permission requirement.
+ /// </summary>
+ public class UserPermissionRequirement : DefaultAuthorizationRequirement
+ {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserPermissionRequirement"/> class.
+ /// </summary>
+ /// <param name="requiredPermission">The required <see cref="PermissionKind"/>.</param>
+ /// <param name="validateParentalSchedule">Whether to validate the user's parental schedule.</param>
+ public UserPermissionRequirement(PermissionKind requiredPermission, bool validateParentalSchedule = true) : base(validateParentalSchedule)
+ {
+ RequiredPermission = requiredPermission;
+ }
+
+ /// <summary>
+ /// Gets the required user permission.
+ /// </summary>
+ public PermissionKind RequiredPermission { get; }
+ }
+}
diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs
index 5a5a2bf46..53841b0c4 100644
--- a/Jellyfin.Api/Constants/Policies.cs
+++ b/Jellyfin.Api/Constants/Policies.cs
@@ -6,11 +6,6 @@ namespace Jellyfin.Api.Constants;
public static class Policies
{
/// <summary>
- /// Policy name for default authorization.
- /// </summary>
- public const string DefaultAuthorization = "DefaultAuthorization";
-
- /// <summary>
/// Policy name for requiring first time setup or elevated privileges.
/// </summary>
public const string FirstTimeSetupOrElevated = "FirstTimeSetupOrElevated";
@@ -74,4 +69,19 @@ public static class Policies
/// Policy name for accessing a SyncPlay group.
/// </summary>
public const string SyncPlayIsInGroup = "SyncPlayIsInGroup";
+
+ /// <summary>
+ /// Policy name for accessing collection management.
+ /// </summary>
+ public const string CollectionManagement = "CollectionManagement";
+
+ /// <summary>
+ /// Policy name for accessing LiveTV.
+ /// </summary>
+ public const string LiveTvAccess = "LiveTvAccess";
+
+ /// <summary>
+ /// Policy name for managing LiveTV.
+ /// </summary>
+ public const string LiveTvManagement = "LiveTvManagement";
}
diff --git a/Jellyfin.Api/Controllers/ArtistsController.cs b/Jellyfin.Api/Controllers/ArtistsController.cs
index 069e7311b..c9d2f67f9 100644
--- a/Jellyfin.Api/Controllers/ArtistsController.cs
+++ b/Jellyfin.Api/Controllers/ArtistsController.cs
@@ -1,7 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// The artists controller.
/// </summary>
[Route("Artists")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ArtistsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -119,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);
@@ -126,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);
}
@@ -322,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);
@@ -329,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);
}
@@ -463,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);
diff --git a/Jellyfin.Api/Controllers/ChannelsController.cs b/Jellyfin.Api/Controllers/ChannelsController.cs
index 573b7069c..b5c4d8346 100644
--- a/Jellyfin.Api/Controllers/ChannelsController.cs
+++ b/Jellyfin.Api/Controllers/ChannelsController.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Channels Controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ChannelsController : BaseJellyfinApiController
{
private readonly IChannelManager _channelManager;
@@ -61,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
@@ -125,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);
@@ -199,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);
diff --git a/Jellyfin.Api/Controllers/ClientLogController.cs b/Jellyfin.Api/Controllers/ClientLogController.cs
index 21c31bc93..2c5dbacbb 100644
--- a/Jellyfin.Api/Controllers/ClientLogController.cs
+++ b/Jellyfin.Api/Controllers/ClientLogController.cs
@@ -1,7 +1,6 @@
using System.Net.Mime;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Models.ClientLogDtos;
using MediaBrowser.Controller.ClientEvent;
@@ -15,7 +14,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Client log controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ClientLogController : BaseJellyfinApiController
{
private const int MaxDocumentSize = 1_000_000;
diff --git a/Jellyfin.Api/Controllers/CollectionController.cs b/Jellyfin.Api/Controllers/CollectionController.cs
index 5a4a9bf07..2db04afb8 100644
--- a/Jellyfin.Api/Controllers/CollectionController.cs
+++ b/Jellyfin.Api/Controllers/CollectionController.cs
@@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers;
/// The collection controller.
/// </summary>
[Route("Collections")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize(Policy = Policies.CollectionManagement)]
public class CollectionController : BaseJellyfinApiController
{
private readonly ICollectionManager _collectionManager;
diff --git a/Jellyfin.Api/Controllers/ConfigurationController.cs b/Jellyfin.Api/Controllers/ConfigurationController.cs
index d53d7cefd..9007dfc41 100644
--- a/Jellyfin.Api/Controllers/ConfigurationController.cs
+++ b/Jellyfin.Api/Controllers/ConfigurationController.cs
@@ -19,7 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Configuration Controller.
/// </summary>
[Route("System")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ConfigurationController : BaseJellyfinApiController
{
private readonly IServerConfigurationManager _configurationManager;
diff --git a/Jellyfin.Api/Controllers/DashboardController.cs b/Jellyfin.Api/Controllers/DashboardController.cs
index f7e978bad..076084c7a 100644
--- a/Jellyfin.Api/Controllers/DashboardController.cs
+++ b/Jellyfin.Api/Controllers/DashboardController.cs
@@ -4,7 +4,6 @@ using System.IO;
using System.Linq;
using System.Net.Mime;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Models;
using MediaBrowser.Common.Plugins;
using MediaBrowser.Model.Net;
@@ -48,7 +47,7 @@ public class DashboardController : BaseJellyfinApiController
[HttpGet("web/ConfigurationPages")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
public ActionResult<IEnumerable<ConfigurationPageInfo>> GetConfigurationPages(
[FromQuery] bool? enableInMainMenu)
{
diff --git a/Jellyfin.Api/Controllers/DevicesController.cs b/Jellyfin.Api/Controllers/DevicesController.cs
index 497862236..aa0dff212 100644
--- a/Jellyfin.Api/Controllers/DevicesController.cs
+++ b/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);
}
diff --git a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
index 49d87a362..6f0006832 100644
--- a/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
+++ b/Jellyfin.Api/Controllers/DisplayPreferencesController.cs
@@ -3,7 +3,6 @@ using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
@@ -19,7 +18,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Display Preferences Controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class DisplayPreferencesController : BaseJellyfinApiController
{
private readonly IDisplayPreferencesManager _displayPreferencesManager;
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index b68849171..4d8b4de24 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -9,7 +9,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.PlaybackDtos;
using Jellyfin.Api.Models.StreamingDtos;
@@ -36,7 +35,7 @@ namespace Jellyfin.Api.Controllers;
/// Dynamic hls controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class DynamicHlsController : BaseJellyfinApiController
{
private const string DefaultVodEncoderPreset = "veryfast";
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index 2378aada5..dac07429f 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -1,6 +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;
@@ -18,7 +19,7 @@ namespace Jellyfin.Api.Controllers;
/// Filters controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class FilterController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -52,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);
@@ -144,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);
diff --git a/Jellyfin.Api/Controllers/GenresController.cs b/Jellyfin.Api/Controllers/GenresController.cs
index 28ebe2047..eb03b514c 100644
--- a/Jellyfin.Api/Controllers/GenresController.cs
+++ b/Jellyfin.Api/Controllers/GenresController.cs
@@ -1,7 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// The genres controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class GenresController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -91,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);
@@ -132,7 +132,7 @@ public class GenresController : BaseJellyfinApiController
QueryResult<(BaseItem, ItemCounts)> result;
if (parentItem is ICollectionFolder parentCollectionFolder
&& (string.Equals(parentCollectionFolder.CollectionType, CollectionType.Music, StringComparison.Ordinal)
- || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos, StringComparison.Ordinal)))
+ || string.Equals(parentCollectionFolder.CollectionType, CollectionType.MusicVideos, StringComparison.Ordinal)))
{
result = _libraryManager.GetMusicGenres(query);
}
@@ -156,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);
@@ -171,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);
}
diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs
index 085115e1c..d7cec865e 100644
--- a/Jellyfin.Api/Controllers/HlsSegmentController.cs
+++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs
@@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Helpers;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Configuration;
@@ -80,7 +79,7 @@ public class HlsSegmentController : BaseJellyfinApiController
/// <response code="200">Hls video playlist returned.</response>
/// <returns>A <see cref="FileStreamResult"/> containing the playlist.</returns>
[HttpGet("Videos/{itemId}/hls/{playlistId}/stream.m3u8")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "itemId", Justification = "Required for ServiceStack")]
@@ -106,7 +105,7 @@ public class HlsSegmentController : BaseJellyfinApiController
/// <response code="204">Encoding stopped successfully.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpDelete("Videos/ActiveEncodings")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult StopEncodingProcess(
[FromQuery, Required] string deviceId,
diff --git a/Jellyfin.Api/Controllers/ImageController.cs b/Jellyfin.Api/Controllers/ImageController.cs
index cc824c65a..3c5f18af5 100644
--- a/Jellyfin.Api/Controllers/ImageController.cs
+++ b/Jellyfin.Api/Controllers/ImageController.cs
@@ -88,9 +88,10 @@ public class ImageController : BaseJellyfinApiController
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
@@ -99,12 +100,22 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] ImageType imageType,
[FromQuery] int? index = null)
{
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
- var user = _userManager.GetUserById(userId);
+ if (!TryGetImageExtensionFromContentType(Request.ContentType, out string? extension))
+ {
+ return BadRequest("Incorrect ContentType.");
+ }
+
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
await using (memoryStream.ConfigureAwait(false))
{
@@ -116,7 +127,7 @@ public class ImageController : BaseJellyfinApiController
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
- user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
+ user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
@@ -137,9 +148,10 @@ public class ImageController : BaseJellyfinApiController
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Users/{userId}/Images/{imageType}/{index}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
@@ -148,12 +160,22 @@ public class ImageController : BaseJellyfinApiController
[FromRoute, Required] ImageType imageType,
[FromRoute] int index)
{
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
if (!RequestHelpers.AssertCanUpdateUser(_userManager, HttpContext.User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User is not allowed to update the image.");
}
- var user = _userManager.GetUserById(userId);
+ if (!TryGetImageExtensionFromContentType(Request.ContentType, out string? extension))
+ {
+ return BadRequest("Incorrect ContentType.");
+ }
+
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
await using (memoryStream.ConfigureAwait(false))
{
@@ -165,7 +187,7 @@ public class ImageController : BaseJellyfinApiController
await _userManager.ClearProfileImageAsync(user).ConfigureAwait(false);
}
- user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + MimeTypes.ToExtension(mimeType ?? string.Empty)));
+ user.ProfileImage = new Data.Entities.ImageInfo(Path.Combine(userDataPath, "profile" + extension));
await _providerManager
.SaveImage(memoryStream, mimeType, user.ProfileImage.Path)
@@ -186,7 +208,7 @@ public class ImageController : BaseJellyfinApiController
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Users/{userId}/Images/{imageType}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -230,7 +252,7 @@ public class ImageController : BaseJellyfinApiController
/// <response code="403">User does not have permission to delete the image.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Users/{userId}/Images/{imageType}/{index}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "imageType", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
@@ -332,6 +354,7 @@ public class ImageController : BaseJellyfinApiController
[Authorize(Policy = Policies.RequiresElevation)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> SetItemImage(
@@ -344,6 +367,11 @@ public class ImageController : BaseJellyfinApiController
return NotFound();
}
+ if (!TryGetImageExtensionFromContentType(Request.ContentType, out _))
+ {
+ return BadRequest("Incorrect ContentType.");
+ }
+
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
await using (memoryStream.ConfigureAwait(false))
{
@@ -369,6 +397,7 @@ public class ImageController : BaseJellyfinApiController
[Authorize(Policy = Policies.RequiresElevation)]
[AcceptsImageFile]
[ProducesResponseType(StatusCodes.Status204NoContent)]
+ [ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> SetItemImageByIndex(
@@ -382,6 +411,11 @@ public class ImageController : BaseJellyfinApiController
return NotFound();
}
+ if (!TryGetImageExtensionFromContentType(Request.ContentType, out _))
+ {
+ return BadRequest("Incorrect ContentType.");
+ }
+
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
await using (memoryStream.ConfigureAwait(false))
{
@@ -432,7 +466,7 @@ public class ImageController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>The list of image infos on success, or <see cref="NotFoundResult"/> if item not found.</returns>
[HttpGet("Items/{itemId}/Images")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<ImageInfo>>> GetItemImageInfos([FromRoute, Required] Guid itemId)
@@ -1753,22 +1787,14 @@ public class ImageController : BaseJellyfinApiController
[AcceptsImageFile]
public async Task<ActionResult> UploadCustomSplashscreen()
{
+ if (!TryGetImageExtensionFromContentType(Request.ContentType, out var extension))
+ {
+ return BadRequest("Incorrect ContentType.");
+ }
+
var memoryStream = await GetMemoryStream(Request.Body).ConfigureAwait(false);
await using (memoryStream.ConfigureAwait(false))
{
- var mimeType = MediaTypeHeaderValue.Parse(Request.ContentType).MediaType;
-
- if (!mimeType.HasValue)
- {
- return BadRequest("Error reading mimetype from uploaded image");
- }
-
- var extension = MimeTypes.ToExtension(mimeType.Value);
- if (string.IsNullOrEmpty(extension))
- {
- return BadRequest("Error converting mimetype to an image extension");
- }
-
var filePath = Path.Combine(_appPaths.DataPath, "splashscreen-upload" + extension);
var brandingOptions = _serverConfigurationManager.GetConfiguration<BrandingOptions>("branding");
brandingOptions.SplashscreenLocation = filePath;
@@ -1930,10 +1956,10 @@ public class ImageController : BaseJellyfinApiController
}
var responseHeaders = new Dictionary<string, string>
- {
- { "transferMode.dlna.org", "Interactive" },
- { "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" }
- };
+ {
+ { "transferMode.dlna.org", "Interactive" },
+ { "realTimeInfo.dlna.org", "DLNA.ORG_TLAG=*" }
+ };
if (!imageInfo.IsLocalFile && item is not null)
{
@@ -2096,4 +2122,23 @@ public class ImageController : BaseJellyfinApiController
return PhysicalFile(imagePath, imageContentType ?? MediaTypeNames.Text.Plain);
}
+
+ internal static bool TryGetImageExtensionFromContentType(string? contentType, [NotNullWhen(true)] out string? extension)
+ {
+ extension = null;
+ if (string.IsNullOrEmpty(contentType))
+ {
+ return false;
+ }
+
+ if (MediaTypeHeaderValue.TryParse(contentType, out var parsedValue)
+ && parsedValue.MediaType.HasValue
+ && MimeTypes.IsImage(parsedValue.MediaType.Value))
+ {
+ extension = MimeTypes.ToExtension(parsedValue.MediaType.Value);
+ return extension is not null;
+ }
+
+ return false;
+ }
}
diff --git a/Jellyfin.Api/Controllers/InstantMixController.cs b/Jellyfin.Api/Controllers/InstantMixController.cs
index 89592bade..4dc2a4253 100644
--- a/Jellyfin.Api/Controllers/InstantMixController.cs
+++ b/Jellyfin.Api/Controllers/InstantMixController.cs
@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
@@ -22,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// The instant mix controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class InstantMixController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -75,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 }
@@ -111,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 }
@@ -147,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 }
@@ -182,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 }
@@ -218,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 }
@@ -254,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 }
@@ -327,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 }
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index c2ce4e67e..b030e74dd 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -23,7 +23,7 @@ namespace Jellyfin.Api.Controllers;
/// Item lookup controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ItemLookupController : BaseJellyfinApiController
{
private readonly IProviderManager _providerManager;
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 134974dbe..728e62810 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -1,11 +1,11 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The items controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class ItemsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -240,8 +240,9 @@ 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)
- ? _userManager.GetUserById(userId.Value)
+ userId = RequestHelpers.GetUserId(User, userId);
+ var user = !isApiKey && !userId.Value.Equals(default)
+ ? _userManager.GetUserById(userId.Value) ?? throw new ResourceNotFoundException()
: null;
// beyond this point, we're either using an api key or we have a valid user
@@ -815,6 +816,11 @@ public class ItemsController : BaseJellyfinApiController
[FromQuery] bool excludeActiveSessions = false)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
var parentIdGuid = parentId ?? Guid.Empty;
var dtoOptions = new DtoOptions { Fields = fields }
.AddClientFields(User)
diff --git a/Jellyfin.Api/Controllers/LibraryController.cs b/Jellyfin.Api/Controllers/LibraryController.cs
index 830f84849..bf59febed 100644
--- a/Jellyfin.Api/Controllers/LibraryController.cs
+++ b/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;
@@ -95,7 +96,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="FileStreamResult"/> with the original file.</returns>
[HttpGet("Items/{itemId}/File")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesFile("video/*", "audio/*")]
@@ -116,7 +117,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="200">Critic reviews returned.</response>
/// <returns>The list of critic reviews.</returns>
[HttpGet("Items/{itemId}/CriticReviews")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[Obsolete("This endpoint is obsolete.")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetCriticReviews()
@@ -134,7 +135,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>The item theme songs.</returns>
[HttpGet("Items/{itemId}/ThemeSongs")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeSongs(
@@ -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);
@@ -200,7 +202,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>The item theme videos.</returns>
[HttpGet("Items/{itemId}/ThemeVideos")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<ThemeMediaResult> GetThemeVideos(
@@ -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);
@@ -266,7 +269,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>The item theme videos.</returns>
[HttpGet("Items/{itemId}/ThemeMedia")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<AllThemeMediaResult> GetThemeMedia(
[FromRoute, Required] Guid itemId,
@@ -283,6 +286,11 @@ public class LibraryController : BaseJellyfinApiController
userId,
inheritFromParent);
+ if (themeSongs.Result is NotFoundObjectResult || themeVideos.Result is NotFoundObjectResult)
+ {
+ return NotFound();
+ }
+
return new AllThemeMediaResult
{
ThemeSongsResult = themeSongs?.Value,
@@ -321,7 +329,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="401">Unauthorized access.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Items/{itemId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public ActionResult DeleteItem(Guid itemId)
@@ -350,7 +358,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="401">Unauthorized access.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Items")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public ActionResult DeleteItems([FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] ids)
@@ -392,13 +400,14 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="200">Item counts returned.</response>
/// <returns>Item counts.</returns>
[HttpGet("Items/Counts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ItemCounts> GetItemCounts(
[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);
@@ -426,12 +435,13 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>Item parents.</returns>
[HttpGet("Items/{itemId}/Ancestors")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
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)
{
@@ -440,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);
@@ -452,6 +462,10 @@ public class LibraryController : BaseJellyfinApiController
if (user is not null)
{
parent = TranslateParentItem(parent, user);
+ if (parent is null)
+ {
+ break;
+ }
}
baseItemDtos.Add(_dtoService.GetBaseItemDto(parent, dtoOptions, user));
@@ -509,7 +523,7 @@ public class LibraryController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Series/Added", Name = "PostAddedSeries")]
[HttpPost("Library/Series/Updated")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedSeries([FromQuery] string? tvdbId)
{
@@ -539,7 +553,7 @@ public class LibraryController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Movies/Added", Name = "PostAddedMovies")]
[HttpPost("Library/Movies/Updated")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMovies([FromQuery] string? tmdbId, [FromQuery] string? imdbId)
{
@@ -580,7 +594,7 @@ public class LibraryController : BaseJellyfinApiController
/// <response code="204">Report success.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Library/Media/Updated")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult PostUpdatedMedia([FromBody, Required] MediaUpdateInfoDto dto)
{
@@ -657,7 +671,7 @@ public class LibraryController : BaseJellyfinApiController
[HttpGet("Shows/{itemId}/Similar", Name = "GetSimilarShows")]
[HttpGet("Movies/{itemId}/Similar", Name = "GetSimilarMovies")]
[HttpGet("Trailers/{itemId}/Similar", Name = "GetSimilarTrailers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<QueryResult<BaseItemDto>> GetSimilarItems(
[FromRoute, Required] Guid itemId,
@@ -666,18 +680,24 @@ 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);
+ if (item is null)
+ {
+ return NotFound();
+ }
+
if (item is Episode || (item is IItemByName && item is not MusicArtist))
{
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 }
@@ -802,32 +822,32 @@ public class LibraryController : BaseJellyfinApiController
Type = type,
MetadataFetchers = plugins
- .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
- .Select(i => new LibraryOptionInfoDto
- {
- Name = i.Name,
- DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
- })
- .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
- .ToArray(),
+ .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.MetadataFetcher))
+ .Select(i => new LibraryOptionInfoDto
+ {
+ Name = i.Name,
+ DefaultEnabled = IsMetadataFetcherEnabledByDefault(i.Name, type, isNewLibrary)
+ })
+ .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToArray(),
ImageFetchers = plugins
- .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
- .Select(i => new LibraryOptionInfoDto
- {
- Name = i.Name,
- DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
- })
- .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
- .ToArray(),
+ .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => i.Plugins.Where(p => p.Type == MetadataPluginType.ImageFetcher))
+ .Select(i => new LibraryOptionInfoDto
+ {
+ Name = i.Name,
+ DefaultEnabled = IsImageFetcherEnabledByDefault(i.Name, type, isNewLibrary)
+ })
+ .DistinctBy(i => i.Name, StringComparer.OrdinalIgnoreCase)
+ .ToArray(),
SupportedImageTypes = plugins
- .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
- .Distinct()
- .ToArray(),
+ .Where(i => string.Equals(i.ItemType, type, StringComparison.OrdinalIgnoreCase))
+ .SelectMany(i => i.SupportedImageTypes ?? Array.Empty<ImageType>())
+ .Distinct()
+ .ToArray(),
DefaultImageOptions = defaultImageOptions ?? Array.Empty<ImageOption>()
});
@@ -920,13 +940,13 @@ public class LibraryController : BaseJellyfinApiController
if (string.Equals(name, "TheMovieDb", StringComparison.OrdinalIgnoreCase))
{
return !(string.Equals(type, "Season", StringComparison.OrdinalIgnoreCase)
- || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
- || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
+ || string.Equals(type, "Episode", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(type, "MusicVideo", StringComparison.OrdinalIgnoreCase));
}
return string.Equals(name, "TheTVDB", StringComparison.OrdinalIgnoreCase)
- || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
- || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
+ || string.Equals(name, "TheAudioDB", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(name, "MusicBrainz", StringComparison.OrdinalIgnoreCase);
}
var metadataOptions = _serverConfigurationManager.Configuration.MetadataOptions
@@ -934,7 +954,7 @@ public class LibraryController : BaseJellyfinApiController
.ToArray();
return metadataOptions.Length == 0
- || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase));
+ || metadataOptions.Any(i => !i.DisabledMetadataFetchers.Contains(name, StringComparison.OrdinalIgnoreCase));
}
private bool IsImageFetcherEnabledByDefault(string name, string type, bool isNewLibrary)
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 21b424346..96fc91f93 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -17,14 +17,12 @@ using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.LiveTvDtos;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Configuration;
-using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
-using MediaBrowser.Controller.Net;
using MediaBrowser.Controller.Session;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -95,7 +93,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<LiveTvInfo> GetLiveTvInfo()
{
return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
@@ -131,7 +129,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Channels")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetLiveTvChannels(
[FromQuery] ChannelType? type,
[FromQuery] Guid? userId,
@@ -155,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);
@@ -163,7 +162,7 @@ public class LiveTvController : BaseJellyfinApiController
new LiveTvChannelQuery
{
ChannelType = type,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
StartIndex = startIndex,
Limit = limit,
IsFavorite = isFavorite,
@@ -182,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);
@@ -210,10 +209,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the live tv channel.</returns>
[HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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)
@@ -251,7 +251,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult<QueryResult<BaseItemDto>> GetRecordings(
[FromQuery] string? channelId,
[FromQuery] Guid? userId,
@@ -273,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);
@@ -281,7 +282,7 @@ public class LiveTvController : BaseJellyfinApiController
new RecordingQuery
{
ChannelId = channelId,
- UserId = userId ?? Guid.Empty,
+ UserId = userId.Value,
StartIndex = startIndex,
Limit = limit,
Status = status,
@@ -322,7 +323,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the live tv recordings.</returns>
[HttpGet("Recordings/Series")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "channelId", Justification = "Imported from ServiceStack")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
@@ -365,7 +366,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the recording groups.</returns>
[HttpGet("Recordings/Groups")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
public ActionResult<QueryResult<BaseItemDto>> GetRecordingGroups([FromQuery] Guid? userId)
@@ -381,10 +382,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the recording folders.</returns>
[HttpGet("Recordings/Folders")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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);
@@ -403,10 +405,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the live tv recording.</returns>
[HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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);
@@ -425,10 +428,9 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
public async Task<ActionResult> ResetTuner([FromRoute, Required] string tunerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -443,7 +445,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Timers/{timerId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<TimerInfoDto>> GetTimer([FromRoute, Required] string timerId)
{
return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
@@ -459,7 +461,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Timers/Defaults")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetDefaultTimer([FromQuery] string? programId)
{
return string.IsNullOrEmpty(programId)
@@ -479,7 +481,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Timers")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<TimerInfoDto>>> GetTimers(
[FromQuery] string? channelId,
[FromQuery] string? seriesTimerId,
@@ -533,7 +535,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpGet("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetLiveTvPrograms(
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId,
@@ -563,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);
@@ -616,7 +619,7 @@ public class LiveTvController : BaseJellyfinApiController
/// </returns>
[HttpPost("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetPrograms([FromBody] GetProgramsDto body)
{
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
@@ -682,7 +685,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Recommended epgs returned.</response>
/// <returns>A <see cref="OkResult"/> containing the queryresult of recommended epgs.</returns>
[HttpGet("Programs/Recommended")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetRecommendedPrograms(
[FromQuery] Guid? userId,
@@ -702,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);
@@ -734,13 +738,14 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Program returned.</response>
/// <returns>An <see cref="OkResult"/> containing the livetv program.</returns>
[HttpGet("Programs/{programId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<BaseItemDto>> GetProgram(
[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);
@@ -755,13 +760,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>A <see cref="NoContentResult"/> on success, or a <see cref="NotFoundResult"/> if item not found.</returns>
[HttpDelete("Recordings/{recordingId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task<ActionResult> DeleteRecording([FromRoute, Required] Guid recordingId)
+ public ActionResult DeleteRecording([FromRoute, Required] Guid recordingId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
-
var item = _libraryManager.GetItemById(recordingId);
if (item is null)
{
@@ -783,11 +786,10 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Timer deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Timers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelTimer([FromRoute, Required] string timerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
return NoContent();
}
@@ -800,12 +802,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -817,11 +818,10 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Timer created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Timers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateTimer([FromBody] TimerInfoDto timerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -834,7 +834,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="404">Series timer not found.</response>
/// <returns>A <see cref="OkResult"/> on success, or a <see cref="NotFoundResult"/> if timer not found.</returns>
[HttpGet("SeriesTimers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<SeriesTimerInfoDto>> GetSeriesTimer([FromRoute, Required] string timerId)
@@ -856,7 +856,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Timers returned.</response>
/// <returns>An <see cref="OkResult"/> of live tv series timers.</returns>
[HttpGet("SeriesTimers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<QueryResult<SeriesTimerInfoDto>>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
{
@@ -876,11 +876,10 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Timer cancelled.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("SeriesTimers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CancelSeriesTimer([FromRoute, Required] string timerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
return NoContent();
}
@@ -893,12 +892,11 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Series timer updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task<ActionResult> UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -910,11 +908,10 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Series timer info created.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("SeriesTimers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -925,7 +922,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <param name="groupId">Group id.</param>
/// <returns>A <see cref="NotFoundResult"/>.</returns>
[HttpGet("Recordings/Groups/{groupId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")]
public ActionResult<BaseItemDto> GetRecordingGroup([FromRoute, Required] Guid groupId)
@@ -939,7 +936,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Guid info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the guide info.</returns>
[HttpGet("GuideInfo")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<GuideInfo> GetGuideInfo()
{
@@ -953,7 +950,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Created tuner host returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created tuner host.</returns>
[HttpPost("TunerHosts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerHostInfo>> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{
@@ -967,7 +964,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Tuner host deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("TunerHosts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteTunerHost([FromQuery] string? id)
{
@@ -983,7 +980,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Default listings provider info returned.</response>
/// <returns>An <see cref="OkResult"/> containing the default listings provider info.</returns>
[HttpGet("ListingProviders/Default")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<ListingsProviderInfo> GetDefaultListingProvider()
{
@@ -1000,7 +997,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Created listings provider returned.</response>
/// <returns>A <see cref="OkResult"/> containing the created listings provider.</returns>
[HttpPost("ListingProviders")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
public async Task<ActionResult<ListingsProviderInfo>> AddListingProvider(
@@ -1026,7 +1023,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="204">Listing provider deleted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("ListingProviders")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteListingProvider([FromQuery] string? id)
{
@@ -1044,7 +1041,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Available lineups returned.</response>
/// <returns>A <see cref="OkResult"/> containing the available lineups.</returns>
[HttpGet("ListingProviders/Lineups")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<NameIdPair>>> GetLineups(
[FromQuery] string? id,
@@ -1061,7 +1058,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Available countries returned.</response>
/// <returns>A <see cref="FileResult"/> containing the available countries.</returns>
[HttpGet("ListingProviders/SchedulesDirect/Countries")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile(MediaTypeNames.Application.Json)]
public async Task<ActionResult> GetSchedulesDirectCountries()
@@ -1082,7 +1079,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Channel mapping options returned.</response>
/// <returns>An <see cref="OkResult"/> containing the channel mapping options.</returns>
[HttpGet("ChannelMappingOptions")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<ChannelMappingOptionsDto>> GetChannelMappingOptions([FromQuery] string? providerId)
{
@@ -1120,7 +1117,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Created channel mapping returned.</response>
/// <returns>An <see cref="OkResult"/> containing the created channel mapping.</returns>
[HttpPost("ChannelMappings")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<TunerChannelMapping>> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
{
@@ -1133,7 +1130,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <response code="200">Tuner host types returned.</response>
/// <returns>An <see cref="OkResult"/> containing the tuner host types.</returns>
[HttpGet("TunerHosts/Types")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<NameIdPair>> GetTunerHostTypes()
{
@@ -1148,7 +1145,7 @@ public class LiveTvController : BaseJellyfinApiController
/// <returns>An <see cref="OkResult"/> containing the tuners.</returns>
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
[HttpGet("Tuners/Discover")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<TunerHostInfo>>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{
@@ -1208,26 +1205,4 @@ public class LiveTvController : BaseJellyfinApiController
var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
}
-
- private async Task AssertUserCanManageLiveTv()
- {
- var user = _userManager.GetUserById(User.GetUserId());
- var session = await _sessionManager.LogSessionActivity(
- User.GetClient(),
- User.GetVersion(),
- User.GetDeviceId(),
- User.GetDevice(),
- HttpContext.GetNormalizedRemoteIp().ToString(),
- user).ConfigureAwait(false);
-
- if (session.UserId.Equals(default))
- {
- throw new SecurityException("Anonymous live tv management is not allowed.");
- }
-
- if (!user.HasPermission(PermissionKind.EnableLiveTvManagement))
- {
- throw new SecurityException("The current user does not have permission to manage live tv.");
- }
- }
}
diff --git a/Jellyfin.Api/Controllers/MediaInfoController.cs b/Jellyfin.Api/Controllers/MediaInfoController.cs
index eee7df3af..da24616ff 100644
--- a/Jellyfin.Api/Controllers/MediaInfoController.cs
+++ b/Jellyfin.Api/Controllers/MediaInfoController.cs
@@ -5,7 +5,6 @@ using System.Linq;
using System.Net.Mime;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.Models.MediaInfoDtos;
@@ -25,7 +24,7 @@ namespace Jellyfin.Api.Controllers;
/// The media info controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class MediaInfoController : BaseJellyfinApiController
{
private readonly IMediaSourceManager _mediaSourceManager;
@@ -133,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;
@@ -254,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,
diff --git a/Jellyfin.Api/Controllers/MoviesController.cs b/Jellyfin.Api/Controllers/MoviesController.cs
index 4c30dd2b3..e1145481f 100644
--- a/Jellyfin.Api/Controllers/MoviesController.cs
+++ b/Jellyfin.Api/Controllers/MoviesController.cs
@@ -2,8 +2,8 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
@@ -23,7 +23,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Movies controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class MoviesController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -68,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 }
diff --git a/Jellyfin.Api/Controllers/MusicGenresController.cs b/Jellyfin.Api/Controllers/MusicGenresController.cs
index 302f138eb..435457af6 100644
--- a/Jellyfin.Api/Controllers/MusicGenresController.cs
+++ b/Jellyfin.Api/Controllers/MusicGenresController.cs
@@ -1,7 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// The music genres controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class MusicGenresController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -91,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);
@@ -145,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;
@@ -158,7 +159,12 @@ public class MusicGenresController : BaseJellyfinApiController
item = _libraryManager.GetMusicGenre(genreName);
}
- if (userId.HasValue && !userId.Value.Equals(default))
+ if (item is null)
+ {
+ return NotFound();
+ }
+
+ if (!userId.Value.Equals(default))
{
var user = _userManager.GetUserById(userId.Value);
diff --git a/Jellyfin.Api/Controllers/PackageController.cs b/Jellyfin.Api/Controllers/PackageController.cs
index 3cb3caadb..0ba5e995f 100644
--- a/Jellyfin.Api/Controllers/PackageController.cs
+++ b/Jellyfin.Api/Controllers/PackageController.cs
@@ -17,7 +17,7 @@ namespace Jellyfin.Api.Controllers;
/// Package Controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class PackageController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
diff --git a/Jellyfin.Api/Controllers/PersonsController.cs b/Jellyfin.Api/Controllers/PersonsController.cs
index 9fb6da527..b4c6f490a 100644
--- a/Jellyfin.Api/Controllers/PersonsController.cs
+++ b/Jellyfin.Api/Controllers/PersonsController.cs
@@ -1,8 +1,8 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Entities;
using MediaBrowser.Controller.Dto;
@@ -20,7 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Persons controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class PersonsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -78,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);
@@ -118,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);
@@ -127,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);
diff --git a/Jellyfin.Api/Controllers/PlaylistsController.cs b/Jellyfin.Api/Controllers/PlaylistsController.cs
index 11e589301..c6dbea5e2 100644
--- a/Jellyfin.Api/Controllers/PlaylistsController.cs
+++ b/Jellyfin.Api/Controllers/PlaylistsController.cs
@@ -4,8 +4,8 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
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.PlaylistDtos;
using MediaBrowser.Controller.Dto;
@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Playlists controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class PlaylistsController : BaseJellyfinApiController
{
private readonly IPlaylistManager _playlistManager;
@@ -82,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);
@@ -108,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();
}
diff --git a/Jellyfin.Api/Controllers/PlaystateController.cs b/Jellyfin.Api/Controllers/PlaystateController.cs
index 18d6ebf1e..8ad553bcb 100644
--- a/Jellyfin.Api/Controllers/PlaystateController.cs
+++ b/Jellyfin.Api/Controllers/PlaystateController.cs
@@ -2,7 +2,6 @@ using System;
using System.ComponentModel.DataAnnotations;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// Playstate controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class PlaystateController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -77,6 +76,11 @@ public class PlaystateController : BaseJellyfinApiController
[FromQuery, ModelBinder(typeof(LegacyDateTimeModelBinder))] DateTime? datePlayed)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var item = _libraryManager.GetItemById(itemId);
@@ -89,6 +93,11 @@ public class PlaystateController : BaseJellyfinApiController
foreach (var additionalUserInfo in session.AdditionalUsers)
{
var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
+ if (additionalUser is null)
+ {
+ return NotFound();
+ }
+
UpdatePlayedStatus(additionalUser, item, true, datePlayed);
}
@@ -109,6 +118,11 @@ public class PlaystateController : BaseJellyfinApiController
public async Task<ActionResult<UserItemDataDto>> MarkUnplayedItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
var session = await RequestHelpers.GetSession(_sessionManager, _userManager, HttpContext).ConfigureAwait(false);
var item = _libraryManager.GetItemById(itemId);
@@ -121,6 +135,11 @@ public class PlaystateController : BaseJellyfinApiController
foreach (var additionalUserInfo in session.AdditionalUsers)
{
var additionalUser = _userManager.GetUserById(additionalUserInfo.UserId);
+ if (additionalUser is null)
+ {
+ return NotFound();
+ }
+
UpdatePlayedStatus(additionalUser, item, false, null);
}
diff --git a/Jellyfin.Api/Controllers/PluginsController.cs b/Jellyfin.Api/Controllers/PluginsController.cs
index 5a037d7a6..4726cf066 100644
--- a/Jellyfin.Api/Controllers/PluginsController.cs
+++ b/Jellyfin.Api/Controllers/PluginsController.cs
@@ -21,7 +21,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Plugins controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class PluginsController : BaseJellyfinApiController
{
private readonly IInstallationManager _installationManager;
diff --git a/Jellyfin.Api/Controllers/QuickConnectController.cs b/Jellyfin.Api/Controllers/QuickConnectController.cs
index a58e85b2b..d7e54b5b6 100644
--- a/Jellyfin.Api/Controllers/QuickConnectController.cs
+++ b/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;
@@ -111,22 +112,16 @@ public class QuickConnectController : BaseJellyfinApiController
/// <response code="403">Unknown user id.</response>
/// <returns>Boolean indicating if the authorization was successful.</returns>
[HttpPost("Authorize")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[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)
{
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 445c5594f..5c77db240 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -56,7 +56,7 @@ public class RemoteImageController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>Remote Image Result.</returns>
[HttpGet("Items/{itemId}/RemoteImages")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<RemoteImageResult>> GetRemoteImages(
@@ -121,7 +121,7 @@ public class RemoteImageController : BaseJellyfinApiController
/// <response code="404">Item not found.</response>
/// <returns>List of remote image providers.</returns>
[HttpGet("Items/{itemId}/RemoteImages/Providers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<IEnumerable<ImageProviderInfo>> GetRemoteImageProviders([FromRoute, Required] Guid itemId)
diff --git a/Jellyfin.Api/Controllers/SearchController.cs b/Jellyfin.Api/Controllers/SearchController.cs
index 46b4920ca..f638c31c3 100644
--- a/Jellyfin.Api/Controllers/SearchController.cs
+++ b/Jellyfin.Api/Controllers/SearchController.cs
@@ -4,6 +4,7 @@ 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;
@@ -26,7 +27,7 @@ namespace Jellyfin.Api.Controllers;
/// Search controller.
/// </summary>
[Route("Search/Hints")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class SearchController : BaseJellyfinApiController
{
private readonly ISearchEngine _searchEngine;
@@ -99,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,
@@ -109,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,
diff --git a/Jellyfin.Api/Controllers/SessionController.cs b/Jellyfin.Api/Controllers/SessionController.cs
index ef3364478..e93456de6 100644
--- a/Jellyfin.Api/Controllers/SessionController.cs
+++ b/Jellyfin.Api/Controllers/SessionController.cs
@@ -56,7 +56,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="200">List of sessions returned.</response>
/// <returns>An <see cref="IEnumerable{SessionInfo}"/> with the available sessions.</returns>
[HttpGet("Sessions")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<SessionInfo>> GetSessions(
[FromQuery] Guid? controllableByUserId,
@@ -75,6 +75,10 @@ public class SessionController : BaseJellyfinApiController
result = result.Where(i => i.SupportsRemoteControl);
var user = _userManager.GetUserById(controllableByUserId.Value);
+ if (user is null)
+ {
+ return NotFound();
+ }
if (!user.HasPermission(PermissionKind.EnableRemoteControlOfOtherUsers))
{
@@ -119,7 +123,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Viewing")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DisplayContent(
[FromRoute, Required] string sessionId,
@@ -158,7 +162,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Instruction sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> Play(
[FromRoute, Required] string sessionId,
@@ -201,7 +205,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Playstate command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Playing/{command}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> SendPlaystateCommand(
[FromRoute, Required] string sessionId,
@@ -232,7 +236,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">System command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/System/{command}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> SendSystemCommand(
[FromRoute, Required] string sessionId,
@@ -258,7 +262,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">General command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Command/{command}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> SendGeneralCommand(
[FromRoute, Required] string sessionId,
@@ -286,7 +290,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Full general command sent to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Command")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> SendFullGeneralCommand(
[FromRoute, Required] string sessionId,
@@ -316,7 +320,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Message sent.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/Message")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> SendMessageCommand(
[FromRoute, Required] string sessionId,
@@ -345,7 +349,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">User added to session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/{sessionId}/User/{userId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult AddUserToSession(
[FromRoute, Required] string sessionId,
@@ -363,7 +367,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">User removed from session.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpDelete("Sessions/{sessionId}/User/{userId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult RemoveUserFromSession(
[FromRoute, Required] string sessionId,
@@ -385,7 +389,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Capabilities posted.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Capabilities")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> PostCapabilities(
[FromQuery] string? id,
@@ -419,7 +423,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Capabilities updated.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Capabilities/Full")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> PostFullCapabilities(
[FromQuery] string? id,
@@ -443,7 +447,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Session reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Viewing")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportViewing(
[FromQuery] string? sessionId,
@@ -461,7 +465,7 @@ public class SessionController : BaseJellyfinApiController
/// <response code="204">Session end reported to server.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Sessions/Logout")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> ReportSessionEnded()
{
diff --git a/Jellyfin.Api/Controllers/StudiosController.cs b/Jellyfin.Api/Controllers/StudiosController.cs
index 799be2ae8..f434f60f5 100644
--- a/Jellyfin.Api/Controllers/StudiosController.cs
+++ b/Jellyfin.Api/Controllers/StudiosController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -21,7 +20,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Studios controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class StudiosController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -87,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);
@@ -140,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);
diff --git a/Jellyfin.Api/Controllers/SubtitleController.cs b/Jellyfin.Api/Controllers/SubtitleController.cs
index fd0a71f9e..e38421338 100644
--- a/Jellyfin.Api/Controllers/SubtitleController.cs
+++ b/Jellyfin.Api/Controllers/SubtitleController.cs
@@ -114,7 +114,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="200">Subtitles retrieved.</response>
/// <returns>An array of <see cref="RemoteSubtitleInfo"/>.</returns>
[HttpGet("Items/{itemId}/RemoteSearch/Subtitles/{language}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<IEnumerable<RemoteSubtitleInfo>>> SearchRemoteSubtitles(
[FromRoute, Required] Guid itemId,
@@ -134,7 +134,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="204">Subtitle downloaded.</response>
/// <returns>A <see cref="NoContentResult"/>.</returns>
[HttpPost("Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task<ActionResult> DownloadRemoteSubtitles(
[FromRoute, Required] Guid itemId,
@@ -164,7 +164,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="200">File returned.</response>
/// <returns>A <see cref="FileStreamResult"/> with the subtitle file.</returns>
[HttpGet("Providers/Subtitles/Subtitles/{id}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[Produces(MediaTypeNames.Application.Octet)]
[ProducesFile("text/*")]
@@ -322,7 +322,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="200">Subtitle playlist retrieved.</response>
/// <returns>A <see cref="FileContentResult"/> with the HLS subtitle playlist.</returns>
[HttpGet("Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesPlaylistFile]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "index", Justification = "Imported from ServiceStack")]
@@ -463,7 +463,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="200">Information retrieved.</response>
/// <returns>An array of <see cref="FontFile"/> with the available font files.</returns>
[HttpGet("FallbackFont/Fonts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public IEnumerable<FontFile> GetFallbackFontList()
{
@@ -514,7 +514,7 @@ public class SubtitleController : BaseJellyfinApiController
/// <response code="200">Fallback font file retrieved.</response>
/// <returns>The fallback font file.</returns>
[HttpGet("FallbackFont/Fonts/{name}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile("font/*")]
public ActionResult GetFallbackFont([FromRoute, Required] string name)
diff --git a/Jellyfin.Api/Controllers/SuggestionsController.cs b/Jellyfin.Api/Controllers/SuggestionsController.cs
index c5c429757..5b808f257 100644
--- a/Jellyfin.Api/Controllers/SuggestionsController.cs
+++ b/Jellyfin.Api/Controllers/SuggestionsController.cs
@@ -1,6 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -19,7 +18,7 @@ namespace Jellyfin.Api.Controllers;
/// The suggestions controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class SuggestionsController : BaseJellyfinApiController
{
private readonly IDtoService _dtoService;
diff --git a/Jellyfin.Api/Controllers/SystemController.cs b/Jellyfin.Api/Controllers/SystemController.cs
index b0b2e2d6d..4ab705f40 100644
--- a/Jellyfin.Api/Controllers/SystemController.cs
+++ b/Jellyfin.Api/Controllers/SystemController.cs
@@ -172,7 +172,7 @@ public class SystemController : BaseJellyfinApiController
/// <response code="200">Information retrieved.</response>
/// <returns><see cref="EndPointInfo"/> with information about the endpoint.</returns>
[HttpGet("Endpoint")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<EndPointInfo> GetEndpointInfo()
{
@@ -210,7 +210,7 @@ public class SystemController : BaseJellyfinApiController
/// <response code="200">Information retrieved.</response>
/// <returns>An <see cref="IEnumerable{WakeOnLanInfo}"/> with the WakeOnLan infos.</returns>
[HttpGet("WakeOnLanInfo")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[Obsolete("This endpoint is obsolete.")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<WakeOnLanInfo>> GetWakeOnLanInfo()
diff --git a/Jellyfin.Api/Controllers/TrailersController.cs b/Jellyfin.Api/Controllers/TrailersController.cs
index 115efcd8f..b5b640620 100644
--- a/Jellyfin.Api/Controllers/TrailersController.cs
+++ b/Jellyfin.Api/Controllers/TrailersController.cs
@@ -1,5 +1,4 @@
using System;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using MediaBrowser.Model.Dto;
@@ -14,7 +13,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// The trailers controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class TrailersController : BaseJellyfinApiController
{
private readonly ItemsController _itemsController;
diff --git a/Jellyfin.Api/Controllers/TvShowsController.cs b/Jellyfin.Api/Controllers/TvShowsController.cs
index 2be32095e..7d23281f2 100644
--- a/Jellyfin.Api/Controllers/TvShowsController.cs
+++ b/Jellyfin.Api/Controllers/TvShowsController.cs
@@ -2,8 +2,8 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
+using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -25,7 +25,7 @@ namespace Jellyfin.Api.Controllers;
/// The tv shows controller.
/// </summary>
[Route("Shows")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class TvShowsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -88,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);
@@ -99,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,
@@ -107,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);
@@ -145,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);
@@ -216,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);
@@ -332,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);
diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs
index 6946caa2b..12d033ae6 100644
--- a/Jellyfin.Api/Controllers/UniversalAudioController.cs
+++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs
@@ -5,7 +5,6 @@ using System.Globalization;
using System.Linq;
using System.Threading.Tasks;
using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -82,7 +81,7 @@ public class UniversalAudioController : BaseJellyfinApiController
/// <returns>A <see cref="Task"/> containing the audio file.</returns>
[HttpGet("Audio/{itemId}/universal")]
[HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status302Found)]
[ProducesAudioFile]
@@ -107,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);
diff --git a/Jellyfin.Api/Controllers/UserController.cs b/Jellyfin.Api/Controllers/UserController.cs
index 7f184f31e..b0973b8a1 100644
--- a/Jellyfin.Api/Controllers/UserController.cs
+++ b/Jellyfin.Api/Controllers/UserController.cs
@@ -81,7 +81,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="200">Users returned.</response>
/// <returns>An <see cref="IEnumerable{UserDto}"/> containing the users.</returns>
[HttpGet]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult<IEnumerable<UserDto>> GetUsers(
[FromQuery] bool? isHidden,
@@ -147,6 +147,11 @@ public class UserController : BaseJellyfinApiController
public async Task<ActionResult> DeleteUser([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
await _sessionManager.RevokeUserTokens(user.Id, null).ConfigureAwait(false);
await _userManager.DeleteUserAsync(userId).ConfigureAwait(false);
return NoContent();
@@ -251,7 +256,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
[HttpPost("{userId}/Password")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -281,8 +286,8 @@ public class UserController : BaseJellyfinApiController
{
var success = await _userManager.AuthenticateUser(
user.Username,
- request.CurrentPw,
- request.CurrentPw,
+ request.CurrentPw ?? string.Empty,
+ request.CurrentPw ?? string.Empty,
HttpContext.GetNormalizedRemoteIp().ToString(),
false).ConfigureAwait(false);
@@ -292,7 +297,7 @@ public class UserController : BaseJellyfinApiController
}
}
- await _userManager.ChangePassword(user, request.NewPw).ConfigureAwait(false);
+ await _userManager.ChangePassword(user, request.NewPw ?? string.Empty).ConfigureAwait(false);
var currentToken = User.GetToken();
@@ -312,7 +317,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="404">User not found.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="ForbidResult"/> or a <see cref="NotFoundResult"/> on failure.</returns>
[HttpPost("{userId}/EasyPassword")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -338,7 +343,7 @@ public class UserController : BaseJellyfinApiController
}
else
{
- await _userManager.ChangeEasyPassword(user, request.NewPw, request.NewPassword).ConfigureAwait(false);
+ await _userManager.ChangeEasyPassword(user, request.NewPw ?? string.Empty, request.NewPassword ?? string.Empty).ConfigureAwait(false);
}
return NoContent();
@@ -354,7 +359,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="403">User update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success or a <see cref="BadRequestResult"/> or a <see cref="ForbidResult"/> on failure.</returns>
[HttpPost("{userId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
@@ -362,13 +367,17 @@ public class UserController : BaseJellyfinApiController
[FromRoute, Required] Guid userId,
[FromBody, Required] UserDto updateUser)
{
+ var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
if (!RequestHelpers.AssertCanUpdateUser(_userManager, User, userId, true))
{
return StatusCode(StatusCodes.Status403Forbidden, "User update not allowed.");
}
- var user = _userManager.GetUserById(userId);
-
if (!string.Equals(user.Username, updateUser.Name, StringComparison.Ordinal))
{
await _userManager.RenameUser(user, updateUser.Name).ConfigureAwait(false);
@@ -398,6 +407,10 @@ public class UserController : BaseJellyfinApiController
[FromBody, Required] UserPolicy newPolicy)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
// If removing admin access
if (!newPolicy.IsAdministrator && user.HasPermission(PermissionKind.IsAdministrator))
@@ -440,7 +453,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="403">User configuration update forbidden.</response>
/// <returns>A <see cref="NoContentResult"/> indicating success.</returns>
[HttpPost("{userId}/Configuration")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status403Forbidden)]
public async Task<ActionResult> UpdateUserConfiguration(
@@ -526,7 +539,7 @@ public class UserController : BaseJellyfinApiController
/// <response code="400">Token is not owned by a user.</response>
/// <returns>A <see cref="UserDto"/> for the authenticated user.</returns>
[HttpGet("Me")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public ActionResult<UserDto> GetCurrentUser()
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index 556cf3894..1233995b4 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -4,7 +4,6 @@ using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Data.Enums;
@@ -28,7 +27,7 @@ namespace Jellyfin.Api.Controllers;
/// User library controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class UserLibraryController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
@@ -79,10 +78,18 @@ public class UserLibraryController : BaseJellyfinApiController
public async Task<ActionResult<BaseItemDto>> GetItem([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+ if (item is null)
+ {
+ return NotFound();
+ }
await RefreshItemOnDemandIfNeeded(item).ConfigureAwait(false);
@@ -102,6 +109,11 @@ public class UserLibraryController : BaseJellyfinApiController
public ActionResult<BaseItemDto> GetRootFolder([FromRoute, Required] Guid userId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
+
var item = _libraryManager.GetUserRootFolder();
var dtoOptions = new DtoOptions().AddClientFields(User);
return _dtoService.GetBaseItemDto(item, dtoOptions, user);
@@ -119,10 +131,18 @@ public class UserLibraryController : BaseJellyfinApiController
public async Task<ActionResult<QueryResult<BaseItemDto>>> GetIntros([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+ if (item is null)
+ {
+ return NotFound();
+ }
var items = await _libraryManager.GetIntros(item, user).ConfigureAwait(false);
var dtoOptions = new DtoOptions().AddClientFields(User);
@@ -200,10 +220,18 @@ public class UserLibraryController : BaseJellyfinApiController
public ActionResult<IEnumerable<BaseItemDto>> GetLocalTrailers([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+ if (item is null)
+ {
+ return NotFound();
+ }
var dtoOptions = new DtoOptions().AddClientFields(User);
@@ -230,10 +258,18 @@ public class UserLibraryController : BaseJellyfinApiController
public ActionResult<IEnumerable<BaseItemDto>> GetSpecialFeatures([FromRoute, Required] Guid userId, [FromRoute, Required] Guid itemId)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
var item = itemId.Equals(default)
? _libraryManager.GetUserRootFolder()
: _libraryManager.GetItemById(itemId);
+ if (item is null)
+ {
+ return NotFound();
+ }
var dtoOptions = new DtoOptions().AddClientFields(User);
@@ -275,6 +311,10 @@ public class UserLibraryController : BaseJellyfinApiController
[FromQuery] bool groupItems = true)
{
var user = _userManager.GetUserById(userId);
+ if (user is null)
+ {
+ return NotFound();
+ }
if (!isPlayed.HasValue)
{
diff --git a/Jellyfin.Api/Controllers/UserViewsController.cs b/Jellyfin.Api/Controllers/UserViewsController.cs
index aa7ba8891..838b43234 100644
--- a/Jellyfin.Api/Controllers/UserViewsController.cs
+++ b/Jellyfin.Api/Controllers/UserViewsController.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.ModelBinders;
using Jellyfin.Api.Models.UserViewDtos;
@@ -23,7 +22,7 @@ namespace Jellyfin.Api.Controllers;
/// User views controller.
/// </summary>
[Route("")]
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class UserViewsController : BaseJellyfinApiController
{
private readonly IUserManager _userManager;
diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs
index 01a319879..c0ec646ed 100644
--- a/Jellyfin.Api/Controllers/VideosController.cs
+++ b/Jellyfin.Api/Controllers/VideosController.cs
@@ -100,16 +100,17 @@ public class VideosController : BaseJellyfinApiController
/// <response code="200">Additional parts returned.</response>
/// <returns>A <see cref="QueryResult{BaseItemDto}"/> with the parts.</returns>
[HttpGet("{itemId}/AdditionalParts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize]
[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);
@@ -155,7 +156,12 @@ public class VideosController : BaseJellyfinApiController
if (video.LinkedAlternateVersions.Length == 0)
{
- video = (Video)_libraryManager.GetItemById(video.PrimaryVersionId);
+ video = (Video?)_libraryManager.GetItemById(video.PrimaryVersionId);
+ }
+
+ if (video is null)
+ {
+ return NotFound();
}
foreach (var link in video.GetLinkedAlternateVersions())
diff --git a/Jellyfin.Api/Controllers/YearsController.cs b/Jellyfin.Api/Controllers/YearsController.cs
index 2e5fdc146..74370db50 100644
--- a/Jellyfin.Api/Controllers/YearsController.cs
+++ b/Jellyfin.Api/Controllers/YearsController.cs
@@ -2,7 +2,6 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
-using Jellyfin.Api.Constants;
using Jellyfin.Api.Extensions;
using Jellyfin.Api.Helpers;
using Jellyfin.Api.ModelBinders;
@@ -24,7 +23,7 @@ namespace Jellyfin.Api.Controllers;
/// <summary>
/// Years controller.
/// </summary>
-[Authorize(Policy = Policies.DefaultAuthorization)]
+[Authorize]
public class YearsController : BaseJellyfinApiController
{
private readonly ILibraryManager _libraryManager;
@@ -86,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);
@@ -172,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)
{
@@ -181,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);
diff --git a/Jellyfin.Api/Helpers/MediaInfoHelper.cs b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
index df37d96c6..5910d8073 100644
--- a/Jellyfin.Api/Helpers/MediaInfoHelper.cs
+++ b/Jellyfin.Api/Helpers/MediaInfoHelper.cs
@@ -200,7 +200,7 @@ public class MediaInfoHelper
options.SubtitleStreamIndex = subtitleStreamIndex;
}
- var user = _userManager.GetUserById(userId);
+ var user = _userManager.GetUserById(userId) ?? throw new ResourceNotFoundException();
if (!enableDirectPlay)
{
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index 3ce2b834d..57098edba 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/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;
@@ -56,6 +57,32 @@ public static class RequestHelpers
}
/// <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>
/// <param name="userManager">An instance of the <see cref="IUserManager"/> interface.</param>
@@ -81,6 +108,11 @@ public static class RequestHelpers
}
var user = userManager.GetUserById(userId);
+ if (user is null)
+ {
+ throw new ResourceNotFoundException();
+ }
+
return user.EnableUserPreferenceAccess;
}
@@ -98,7 +130,7 @@ public static class RequestHelpers
if (session is null)
{
- throw new ArgumentException("Session not found.");
+ throw new ResourceNotFoundException("Session not found.");
}
return session;
diff --git a/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
index 7b05351e3..9c2194faf 100644
--- a/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
+++ b/Jellyfin.Api/Middleware/LanFilteringMiddleware.cs
@@ -1,6 +1,6 @@
-using System.Net;
using System.Threading.Tasks;
using Jellyfin.Networking.Configuration;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using MediaBrowser.Controller.Configuration;
using Microsoft.AspNetCore.Http;
@@ -32,9 +32,14 @@ public class LanFilteringMiddleware
/// <returns>The async task.</returns>
public async Task Invoke(HttpContext httpContext, INetworkManager networkManager, IServerConfigurationManager serverConfigurationManager)
{
- var host = httpContext.Connection.RemoteIpAddress ?? IPAddress.Loopback;
+ if (serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
+ {
+ await _next(httpContext).ConfigureAwait(false);
+ return;
+ }
- if (!networkManager.IsInLocalNetwork(host) && !serverConfigurationManager.GetNetworkConfiguration().EnableRemoteAccess)
+ var host = httpContext.GetNormalizedRemoteIp();
+ if (!networkManager.IsInLocalNetwork(host))
{
return;
}
diff --git a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
index 0503c5d57..6b6d9682b 100644
--- a/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
+++ b/Jellyfin.Api/Models/UserDtos/CreateUserByName.cs
@@ -1,4 +1,6 @@
-namespace Jellyfin.Api.Models.UserDtos;
+using System.ComponentModel.DataAnnotations;
+
+namespace Jellyfin.Api.Models.UserDtos;
/// <summary>
/// The create user by name request body.
@@ -8,7 +10,8 @@ public class CreateUserByName
/// <summary>
/// Gets or sets the username.
/// </summary>
- public string? Name { get; set; }
+ [Required]
+ required public string Name { get; set; }
/// <summary>
/// Gets or sets the password.
diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
index ebe9297ea..a0631fd07 100644
--- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
+++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordDto.cs
@@ -11,5 +11,5 @@ public class ForgotPasswordDto
/// Gets or sets the entered username to have its password reset.
/// </summary>
[Required]
- public string? EnteredUsername { get; set; }
+ required public string EnteredUsername { get; set; }
}
diff --git a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
index 2949efe29..79b8a5d63 100644
--- a/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
+++ b/Jellyfin.Api/Models/UserDtos/ForgotPasswordPinDto.cs
@@ -11,5 +11,5 @@ public class ForgotPasswordPinDto
/// Gets or sets the entered pin to have the password reset.
/// </summary>
[Required]
- public string? Pin { get; set; }
+ required public string Pin { get; set; }
}