diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
index 13d3257dff..cb7e2447ef 100644
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
@@ -43,12 +43,16 @@ namespace Jellyfin.Api.Auth
/// Whether to ignore parental control.
/// Whether access is to be allowed locally only.
/// Whether validation requires download permission.
+ /// Whether validation requires LiveTV management permission.
+ /// Whether validation requires LiveTV management permission.
/// Validated claim status.
protected bool ValidateClaims(
ClaimsPrincipal claimsPrincipal,
bool ignoreSchedule = false,
bool localAccessOnly = false,
- bool requiredDownloadPermission = false)
+ bool requiredDownloadPermission = false,
+ bool requireLiveTvManagementPermission = false,
+ bool requireLiveTvAccessPermission = false)
{
// ApiKey is currently global admin, always allow.
var isApiKey = ClaimHelpers.GetIsApiKey(claimsPrincipal);
@@ -106,6 +110,20 @@ namespace Jellyfin.Api.Auth
return false;
}
+ // User attempting to access LiveTV without permission.
+ if (requireLiveTvAccessPermission
+ && !user.HasPermission(PermissionKind.EnableLiveTvAccess))
+ {
+ return false;
+ }
+
+ // User attempting to manage LiveTV without permission.
+ if (requireLiveTvManagementPermission
+ && !user.HasPermission(PermissionKind.EnableLiveTvManagement))
+ {
+ return false;
+ }
+
return true;
}
}
diff --git a/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessHandler.cs b/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessHandler.cs
new file mode 100644
index 0000000000..ac961dcbd9
--- /dev/null
+++ b/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessHandler.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
+
+///
+/// Authorization handler for LiveTV access.
+///
+public class LiveTvAccessHandler : BaseAuthorizationHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public LiveTvAccessHandler(
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvAccessRequirement requirement)
+ {
+ var validated = ValidateClaims(context.User, requireLiveTvAccessPermission: true);
+ if (validated)
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessRequirement.cs b/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessRequirement.cs
new file mode 100644
index 0000000000..710638329d
--- /dev/null
+++ b/Jellyfin.Api/Auth/LiveTvAccessPolicy/LiveTvAccessRequirement.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.LiveTvAccessPolicy;
+
+///
+/// The LiveTV access requirement.
+///
+public class LiveTvAccessRequirement : IAuthorizationRequirement
+{
+}
diff --git a/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementHandler.cs b/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementHandler.cs
new file mode 100644
index 0000000000..3d70e97afe
--- /dev/null
+++ b/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementHandler.cs
@@ -0,0 +1,43 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
+
+///
+/// Authorization handler for LiveTV management access.
+///
+public class LiveTvManagementHandler : BaseAuthorizationHandler
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public LiveTvManagementHandler(
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LiveTvManagementRequirement requirement)
+ {
+ var validated = ValidateClaims(context.User, requireLiveTvManagementPermission: true);
+ if (validated)
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+}
diff --git a/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementRequirement.cs b/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementRequirement.cs
new file mode 100644
index 0000000000..f1f19fc481
--- /dev/null
+++ b/Jellyfin.Api/Auth/LiveTvManagementPolicy/LiveTvManagementRequirement.cs
@@ -0,0 +1,10 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.LiveTvManagementPolicy;
+
+///
+/// The LiveTV management requirement.
+///
+public class LiveTvManagementRequirement : IAuthorizationRequirement
+{
+}
diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs
index a72eeea284..8920dcf10f 100644
--- a/Jellyfin.Api/Constants/Policies.cs
+++ b/Jellyfin.Api/Constants/Policies.cs
@@ -74,5 +74,15 @@ namespace Jellyfin.Api.Constants
/// Policy name for accessing a SyncPlay group.
///
public const string SyncPlayIsInGroup = "SyncPlayIsInGroup";
+
+ ///
+ /// 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 05340099bf..4810d9da81 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -93,7 +93,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Info")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetLiveTvInfo()
{
return _liveTvManager.GetLiveTvInfo(CancellationToken.None);
@@ -129,7 +129,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Channels")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetLiveTvChannels(
[FromQuery] ChannelType? type,
[FromQuery] Guid? userId,
@@ -208,7 +208,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the live tv channel.
[HttpGet("Channels/{channelId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetChannel([FromRoute, Required] Guid channelId, [FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -249,7 +249,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the live tv recordings.
[HttpGet("Recordings")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetRecordings(
[FromQuery] string? channelId,
[FromQuery] Guid? userId,
@@ -320,7 +320,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the live tv recordings.
[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")]
@@ -363,7 +363,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the recording groups.
[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> GetRecordingGroups([FromQuery] Guid? userId)
@@ -379,7 +379,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the recording folders.
[HttpGet("Recordings/Folders")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult> GetRecordingFolders([FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -401,7 +401,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the live tv recording.
[HttpGet("Recordings/{recordingId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public ActionResult GetRecording([FromRoute, Required] Guid recordingId, [FromQuery] Guid? userId)
{
var user = userId is null || userId.Value.Equals(default)
@@ -423,10 +423,9 @@ namespace Jellyfin.Api.Controllers
/// A .
[HttpPost("Tuners/{tunerId}/Reset")]
[ProducesResponseType(StatusCodes.Status204NoContent)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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();
}
@@ -441,7 +440,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Timers/{timerId}")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task> GetTimer([FromRoute, Required] string timerId)
{
return await _liveTvManager.GetTimer(timerId, CancellationToken.None).ConfigureAwait(false);
@@ -457,7 +456,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Timers/Defaults")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task> GetDefaultTimer([FromQuery] string? programId)
{
return string.IsNullOrEmpty(programId)
@@ -477,7 +476,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Timers")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetTimers(
[FromQuery] string? channelId,
[FromQuery] string? seriesTimerId,
@@ -531,7 +530,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpGet("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetLiveTvPrograms(
[FromQuery, ModelBinder(typeof(CommaDelimitedArrayModelBinder))] Guid[] channelIds,
[FromQuery] Guid? userId,
@@ -614,7 +613,7 @@ namespace Jellyfin.Api.Controllers
///
[HttpPost("Programs")]
[ProducesResponseType(StatusCodes.Status200OK)]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
public async Task>> GetPrograms([FromBody] GetProgramsDto body)
{
var user = body.UserId.Equals(default) ? null : _userManager.GetUserById(body.UserId);
@@ -680,7 +679,7 @@ namespace Jellyfin.Api.Controllers
/// Recommended epgs returned.
/// A containing the queryresult of recommended epgs.
[HttpGet("Programs/Recommended")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetRecommendedPrograms(
[FromQuery] Guid? userId,
@@ -732,7 +731,7 @@ namespace Jellyfin.Api.Controllers
/// Program returned.
/// An containing the livetv program.
[HttpGet("Programs/{programId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetProgram(
[FromRoute, Required] string programId,
@@ -753,13 +752,11 @@ namespace Jellyfin.Api.Controllers
/// Item not found.
/// A on success, or a if item not found.
[HttpDelete("Recordings/{recordingId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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 == null)
{
@@ -781,11 +778,10 @@ namespace Jellyfin.Api.Controllers
/// Timer deleted.
/// A .
[HttpDelete("Timers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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();
}
@@ -798,12 +794,11 @@ namespace Jellyfin.Api.Controllers
/// Timer updated.
/// A .
[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 UpdateTimer([FromRoute, Required] string timerId, [FromBody] TimerInfoDto timerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateTimer(timerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -815,11 +810,10 @@ namespace Jellyfin.Api.Controllers
/// Timer created.
/// A .
[HttpPost("Timers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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();
}
@@ -832,7 +826,7 @@ namespace Jellyfin.Api.Controllers
/// Series timer not found.
/// A on success, or a if timer not found.
[HttpGet("SeriesTimers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task> GetSeriesTimer([FromRoute, Required] string timerId)
@@ -854,7 +848,7 @@ namespace Jellyfin.Api.Controllers
/// Timers returned.
/// An of live tv series timers.
[HttpGet("SeriesTimers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetSeriesTimers([FromQuery] string? sortBy, [FromQuery] SortOrder? sortOrder)
{
@@ -874,11 +868,10 @@ namespace Jellyfin.Api.Controllers
/// Timer cancelled.
/// A .
[HttpDelete("SeriesTimers/{timerId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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();
}
@@ -891,12 +884,11 @@ namespace Jellyfin.Api.Controllers
/// Series timer updated.
/// A .
[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 UpdateSeriesTimer([FromRoute, Required] string timerId, [FromBody] SeriesTimerInfoDto seriesTimerInfo)
{
- await AssertUserCanManageLiveTv().ConfigureAwait(false);
await _liveTvManager.UpdateSeriesTimer(seriesTimerInfo, CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
@@ -908,11 +900,10 @@ namespace Jellyfin.Api.Controllers
/// Series timer info created.
/// A .
[HttpPost("SeriesTimers")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [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();
}
@@ -923,7 +914,7 @@ namespace Jellyfin.Api.Controllers
/// Group id.
/// A .
[HttpGet("Recordings/Groups/{groupId}")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[Obsolete("This endpoint is obsolete.")]
public ActionResult GetRecordingGroup([FromRoute, Required] Guid groupId)
@@ -937,7 +928,7 @@ namespace Jellyfin.Api.Controllers
/// Guid info returned.
/// An containing the guide info.
[HttpGet("GuideInfo")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetGuideInfo()
{
@@ -951,7 +942,7 @@ namespace Jellyfin.Api.Controllers
/// Created tuner host returned.
/// A containing the created tuner host.
[HttpPost("TunerHosts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> AddTunerHost([FromBody] TunerHostInfo tunerHostInfo)
{
@@ -965,7 +956,7 @@ namespace Jellyfin.Api.Controllers
/// Tuner host deleted.
/// A .
[HttpDelete("TunerHosts")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteTunerHost([FromQuery] string? id)
{
@@ -981,7 +972,7 @@ namespace Jellyfin.Api.Controllers
/// Default listings provider info returned.
/// An containing the default listings provider info.
[HttpGet("ListingProviders/Default")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult GetDefaultListingProvider()
{
@@ -998,7 +989,7 @@ namespace Jellyfin.Api.Controllers
/// Created listings provider returned.
/// A containing the created listings provider.
[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> AddListingProvider(
@@ -1025,7 +1016,7 @@ namespace Jellyfin.Api.Controllers
/// Listing provider deleted.
/// A .
[HttpDelete("ListingProviders")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status204NoContent)]
public ActionResult DeleteListingProvider([FromQuery] string? id)
{
@@ -1043,7 +1034,7 @@ namespace Jellyfin.Api.Controllers
/// Available lineups returned.
/// A containing the available lineups.
[HttpGet("ListingProviders/Lineups")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> GetLineups(
[FromQuery] string? id,
@@ -1060,7 +1051,7 @@ namespace Jellyfin.Api.Controllers
/// Available countries returned.
/// A containing the available countries.
[HttpGet("ListingProviders/SchedulesDirect/Countries")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesFile(MediaTypeNames.Application.Json)]
public async Task GetSchedulesDirectCountries()
@@ -1081,7 +1072,7 @@ namespace Jellyfin.Api.Controllers
/// Channel mapping options returned.
/// An containing the channel mapping options.
[HttpGet("ChannelMappingOptions")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> GetChannelMappingOptions([FromQuery] string? providerId)
{
@@ -1119,7 +1110,7 @@ namespace Jellyfin.Api.Controllers
/// Created channel mapping returned.
/// An containing the created channel mapping.
[HttpPost("ChannelMappings")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task> SetChannelMapping([FromBody, Required] SetChannelMappingDto setChannelMappingDto)
{
@@ -1132,7 +1123,7 @@ namespace Jellyfin.Api.Controllers
/// Tuner host types returned.
/// An containing the tuner host types.
[HttpGet("TunerHosts/Types")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult> GetTunerHostTypes()
{
@@ -1147,7 +1138,7 @@ namespace Jellyfin.Api.Controllers
/// An containing the tuners.
[HttpGet("Tuners/Discvover", Name = "DiscvoverTuners")]
[HttpGet("Tuners/Discover")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
+ [Authorize(Policy = Policies.LiveTvManagement)]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task>> DiscoverTuners([FromQuery] bool newDevicesOnly = false)
{
@@ -1165,6 +1156,7 @@ namespace Jellyfin.Api.Controllers
/// or a if recording not found.
///
[HttpGet("LiveRecordings/{recordingId}/stream")]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile]
@@ -1193,6 +1185,7 @@ namespace Jellyfin.Api.Controllers
/// or a if stream not found.
///
[HttpGet("LiveStreamFiles/{streamId}/stream.{container}")]
+ [Authorize(Policy = Policies.LiveTvAccess)]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
[ProducesVideoFile]
@@ -1207,20 +1200,5 @@ namespace Jellyfin.Api.Controllers
var liveStream = new ProgressiveFileStream(liveStreamInfo.GetStream());
return new FileStreamResult(liveStream, MimeTypes.GetMimeType("file." + container));
}
-
- private async Task AssertUserCanManageLiveTv()
- {
- var user = await _sessionContext.GetUser(Request).ConfigureAwait(false);
-
- if (user == null)
- {
- 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 f74152405a..3d317390d3 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -14,6 +14,8 @@ using Jellyfin.Api.Auth.FirstTimeOrIgnoreParentalControlSetupPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrDefaultPolicy;
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
+using Jellyfin.Api.Auth.LiveTvAccessPolicy;
+using Jellyfin.Api.Auth.LiveTvManagementPolicy;
using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.LocalAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy;
@@ -66,6 +68,8 @@ namespace Jellyfin.Server.Extensions
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
return serviceCollection.AddAuthorizationCore(options =>
{
@@ -167,6 +171,20 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new AnonymousLanAccessRequirement());
});
+ options.AddPolicy(
+ Policies.LiveTvAccess,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new LiveTvAccessRequirement());
+ });
+ options.AddPolicy(
+ Policies.LiveTvManagement,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new LiveTvManagementRequirement());
+ });
});
}