diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
new file mode 100644
index 0000000000..0b0877d068
--- /dev/null
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs
@@ -0,0 +1,52 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
+{
+ ///
+ /// Local access or require elevated privileges handler.
+ ///
+ public class LocalAccessOrRequiresElevationHandler : AuthorizationHandler
+ {
+ private readonly INetworkManager _networkManager;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public LocalAccessOrRequiresElevationHandler(
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ {
+ _networkManager = networkManager;
+ _httpContextAccessor = httpContextAccessor;
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessOrRequiresElevationRequirement requirement)
+ {
+ 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);
+ }
+
+ if (context.User.IsInRole(UserRoles.Administrator))
+ {
+ context.Succeed(requirement);
+ }
+
+ context.Fail();
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
new file mode 100644
index 0000000000..f633c69d8f
--- /dev/null
+++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationRequirement.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy
+{
+ ///
+ /// The local access or elevated privileges authorization requirement.
+ ///
+ public class LocalAccessOrRequiresElevationRequirement : IAuthorizationRequirement
+ {
+ }
+}
diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs
index 1ef38ca072..53841b0c44 100644
--- a/Jellyfin.Api/Constants/Policies.cs
+++ b/Jellyfin.Api/Constants/Policies.cs
@@ -74,4 +74,14 @@ public static class Policies
/// Policy name for accessing collection management.
///
public const string CollectionManagement = "CollectionManagement";
+
+ ///
+ /// Policy name for accessing LiveTV.
+ ///
+ public const string LiveTvAccess = "LiveTvAccess";
+
+ ///
+ /// Policy name for managing LiveTV.
+ ///
+ public const string LiveTvManagement = "LiveTvManagement";
}
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index 3425c85890..318ed5c673 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -10,20 +10,19 @@ using System.Text;
using System.Threading;
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.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;
@@ -94,7 +93,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetLiveTvInfo()
{
return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
@@ -130,7 +129,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Channels")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetLiveTvChannels(
[FromQuery] ChannelType? type,
[FromQuery] Guid? userId,
@@ -209,7 +208,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the live tv channel.
[HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -250,7 +249,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the live tv recordings.
[HttpGet("Recordings")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetRecordings(
[FromQuery] string? channelId,
[FromQuery] Guid? userId,
@@ -321,7 +320,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the live tv recordings.
[HttpGet("Recordings/Series")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [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")]
@@ -364,7 +363,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the recording groups.
[HttpGet("Recordings/Groups")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[Obsolete("This endpoint is obsolete.")]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "userId", Justification = "Imported from ServiceStack")]
public ActionResult> GetRecordingGroups([FromQuery] Guid? userId)
@@ -380,7 +379,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the recording folders.
[HttpGet("Recordings/Folders")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetRecordingFolders([FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -402,7 +401,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the live tv recording.
[HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -424,10 +423,9 @@ public class LiveTvController : BaseJellyfinApiController
/// A .
[HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
public async Task ResetTuner([FromRoute, Required] string tunerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.ResetTuner(tunerId, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -442,7 +440,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Timers/{timerId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task> GetTimer([FromRoute, Required] string timerId)
{
return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
@@ -458,7 +456,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Timers/Defaults")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task> GetDefaultTimer([FromQuery] string? programId)
{
return string.IsNullOrEmpty(programId)
@@ -478,7 +476,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Timers")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetTimers(
[FromQuery] string? channelId,
[FromQuery] string? seriesTimerId,
@@ -532,7 +530,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpGet("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetLiveTvPrograms(
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId,
@@ -615,7 +613,7 @@ public class LiveTvController : BaseJellyfinApiController
///
[HttpPost("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetPrograms([FromBody] GetProgramsDto body)
{
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
@@ -681,7 +679,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Recommended epgs returned.
/// A containing the queryresult of recommended epgs.
[HttpGet("Programs/Recommended")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetRecommendedPrograms(
[FromQuery] Guid? userId,
@@ -733,7 +731,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Program returned.
/// An containing the livetv program.
[HttpGet("Programs/{programId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetProgram(
[FromRoute, Required] string programId,
@@ -754,13 +752,11 @@ public class LiveTvController : BaseJellyfinApiController
/// Item not found.
/// A on success, or a if item not found.
[HttpDelete("Recordings/{recordingId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task 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)
{
@@ -782,11 +778,10 @@ public class LiveTvController : BaseJellyfinApiController
/// Timer deleted.
/// A .
[HttpDelete("Timers/{timerId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task CancelTimer([FromRoute, Required] string timerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelTimer(timerId).ConfigureAwait(false);
return NoContent();
}
@@ -799,12 +794,11 @@ public class LiveTvController : BaseJellyfinApiController
/// Timer updated.
/// A .
[HttpPost("Timers/{timerId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -816,11 +810,10 @@ public class LiveTvController : BaseJellyfinApiController
/// Timer created.
/// A .
[HttpPost("Timers")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task CreateTimer([FromBody] TimerInfoDto timerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -833,7 +826,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Series timer not found.
/// A on success, or a if timer not found.
[HttpGet("SeriesTimers/{timerId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> GetSeriesTimer([FromRoute, Required] string timerId)
@@ -855,7 +848,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Timers returned.
/// An of live tv series timers.
[HttpGet("SeriesTimers")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
{
@@ -875,11 +868,10 @@ public class LiveTvController : BaseJellyfinApiController
/// Timer cancelled.
/// A .
[HttpDelete("SeriesTimers/{timerId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task CancelSeriesTimer([FromRoute, Required] string timerId)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CancelSeriesTimer(timerId).ConfigureAwait(false);
return NoContent();
}
@@ -892,12 +884,11 @@ public class LiveTvController : BaseJellyfinApiController
/// Series timer updated.
/// A .
[HttpPost("SeriesTimers/{timerId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
[SuppressMessage("Microsoft.Performance", "CA1801:ReviewUnusedParameters", MessageId = "timerId", Justification = "Imported from ServiceStack")]
public async Task UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -909,11 +900,10 @@ public class LiveTvController : BaseJellyfinApiController
/// Series timer info created.
/// A .
[HttpPost("SeriesTimers")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public async Task CreateSeriesTimer([FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.CreateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -924,7 +914,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Group id.
/// A .
[HttpGet("Recordings/Groups/{groupId}")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")]
public ActionResult GetRecordingGroup([FromRoute, Required] Guid groupId)
@@ -938,7 +928,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Guid info returned.
/// An containing the guide info.
[HttpGet("GuideInfo")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetGuideInfo()
{
@@ -952,7 +942,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Created tuner host returned.
/// A containing the created tuner host.
[HttpPost("TunerHosts")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{
@@ -966,7 +956,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Tuner host deleted.
/// A .
[HttpDelete("TunerHosts")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteTunerHost([FromQuery] string? id)
{
@@ -982,7 +972,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Default listings provider info returned.
/// An containing the default listings provider info.
[HttpGet("ListingProviders/Default")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetDefaultListingProvider()
{
@@ -999,7 +989,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Created listings provider returned.
/// A containing the created listings provider.
[HttpPost("ListingProviders")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
[SuppressMessage("Microsoft.Performance", "CA5350:RemoveSha1", MessageId = "AddListingProvider", Justification = "Imported from ServiceStack")]
public async Task> AddListingProvider(
@@ -1025,7 +1015,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Listing provider deleted.
/// A .
[HttpDelete("ListingProviders")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteListingProvider([FromQuery] string? id)
{
@@ -1043,7 +1033,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Available lineups returned.
/// A containing the available lineups.
[HttpGet("ListingProviders/Lineups")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetLineups(
[FromQuery] string? id,
@@ -1060,7 +1050,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Available countries returned.
/// A containing the available countries.
[HttpGet("ListingProviders/SchedulesDirect/Countries")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile(MediaTypeNames.Application.Json)]
public async Task GetSchedulesDirectCountries()
@@ -1081,7 +1071,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Channel mapping options returned.
/// An containing the channel mapping options.
[HttpGet("ChannelMappingOptions")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetChannelMappingOptions([FromQuery] string? providerId)
{
@@ -1119,7 +1109,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Created channel mapping returned.
/// An containing the created channel mapping.
[HttpPost("ChannelMappings")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
{
@@ -1132,7 +1122,7 @@ public class LiveTvController : BaseJellyfinApiController
/// Tuner host types returned.
/// An containing the tuner host types.
[HttpGet("TunerHosts/Types")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetTunerHostTypes()
{
@@ -1147,7 +1137,7 @@ public class LiveTvController : BaseJellyfinApiController
/// An containing the tuners.
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
[HttpGet("Tuners/Discover")]
- [Authorize]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{
@@ -1207,26 +1197,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()) ?? throw new ResourceNotFoundException();
- 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.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index dffcfbba87..9867c9e47a 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -11,6 +11,7 @@ using Jellyfin.Api.Auth;
using Jellyfin.Api.Auth.AnonymousLanAccessPolicy;
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupPolicy;
+using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.SyncPlayAccessPolicy;
using Jellyfin.Api.Auth.UserPermissionPolicy;
using Jellyfin.Api.Constants;
@@ -65,17 +66,20 @@ namespace Jellyfin.Server.Extensions
.AddRequirements(new DefaultAuthorizationRequirement())
.Build();
+ options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement());
+ options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement));
options.AddPolicy(Policies.Download, new UserPermissionRequirement(PermissionKind.EnableContentDownloading));
options.AddPolicy(Policies.FirstTimeSetupOrDefault, new FirstTimeSetupRequirement(requireAdmin: false));
options.AddPolicy(Policies.FirstTimeSetupOrElevated, new FirstTimeSetupRequirement());
options.AddPolicy(Policies.FirstTimeSetupOrIgnoreParentalControl, new FirstTimeSetupRequirement(false, false));
options.AddPolicy(Policies.IgnoreParentalControl, new DefaultAuthorizationRequirement(validateParentalSchedule: false));
+ options.AddPolicy(Policies.LiveTvAccess, new UserPermissionRequirement(PermissionKind.EnableLiveTvAccess));
+ options.AddPolicy(Policies.LiveTvManagement, new UserPermissionRequirement(PermissionKind.EnableLiveTvManagement));
+ options.AddPolicy(Policies.LocalAccessOrRequiresElevation, new LocalAccessOrRequiresElevationRequirement());
options.AddPolicy(Policies.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess));
options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup));
options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup));
options.AddPolicy(Policies.SyncPlayIsInGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup));
- options.AddPolicy(Policies.CollectionManagement, new UserPermissionRequirement(PermissionKind.EnableCollectionManagement));
- options.AddPolicy(Policies.AnonymousLanAccessPolicy, new AnonymousLanAccessRequirement());
options.AddPolicy(
Policies.RequiresElevation,
policy => policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication)