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()); + }); }); }