From a75c9d4547f9c5f0aa393bf7767f91d6fe13aae5 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 9 Jun 2023 10:35:33 +0200 Subject: [PATCH 1/4] Add playback auth permission --- Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs | 1 + MediaBrowser.Common/Api/Policies.cs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs index 597643ed19..0945d9ae28 100644 --- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs +++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs @@ -77,6 +77,7 @@ namespace Jellyfin.Server.Extensions 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.Playback, new UserPermissionRequirement(PermissionKind.EnableMediaPlayback)); options.AddPolicy(Policies.SyncPlayHasAccess, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.HasAccess)); options.AddPolicy(Policies.SyncPlayCreateGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.CreateGroup)); options.AddPolicy(Policies.SyncPlayJoinGroup, new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.JoinGroup)); diff --git a/MediaBrowser.Common/Api/Policies.cs b/MediaBrowser.Common/Api/Policies.cs index 435f4798ff..1d32a3c9c8 100644 --- a/MediaBrowser.Common/Api/Policies.cs +++ b/MediaBrowser.Common/Api/Policies.cs @@ -94,4 +94,9 @@ public static class Policies /// Policy name for accessing lyric management. /// public const string LyricManagement = "LyricManagement"; + + /// + /// Policy name for playback. + /// + public const string Playback = "Playback"; } From c41d95b31b342928c2db9a03a2bda11833d65c7a Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Fri, 9 Jun 2023 10:37:07 +0200 Subject: [PATCH 2/4] Enforce playback permission --- Jellyfin.Api/Controllers/AudioController.cs | 5 ++++- Jellyfin.Api/Controllers/DynamicHlsController.cs | 3 ++- Jellyfin.Api/Controllers/HlsSegmentController.cs | 2 ++ Jellyfin.Api/Controllers/LiveTvController.cs | 2 ++ Jellyfin.Api/Controllers/UniversalAudioController.cs | 3 ++- Jellyfin.Api/Controllers/VideosController.cs | 3 ++- 6 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index cd09d2bfab..25941ebbb3 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -4,10 +4,11 @@ using System.ComponentModel.DataAnnotations; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; -using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Common.Api; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Streaming; using MediaBrowser.Model.Dlna; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -87,6 +88,7 @@ public class AudioController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream", Name = "GetAudioStream")] [HttpHead("{itemId}/stream", Name = "HeadAudioStream")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesAudioFile] public async Task GetAudioStream( @@ -251,6 +253,7 @@ public class AudioController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")] [HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesAudioFile] public async Task GetAudioStreamByContainer( diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 590cdc33f0..8b895da9ec 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -15,6 +15,7 @@ using Jellyfin.Api.Models.StreamingDtos; using Jellyfin.Data.Enums; using Jellyfin.Extensions; using Jellyfin.MediaEncoding.Hls.Playlist; +using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Library; @@ -37,7 +38,7 @@ namespace Jellyfin.Api.Controllers; /// Dynamic hls controller. /// [Route("")] -[Authorize] +[Authorize(Policy = Policies.Playback)] public class DynamicHlsController : BaseJellyfinApiController { private const string DefaultVodEncoderPreset = "veryfast"; diff --git a/Jellyfin.Api/Controllers/HlsSegmentController.cs b/Jellyfin.Api/Controllers/HlsSegmentController.cs index 1927a332b2..d9a725b75e 100644 --- a/Jellyfin.Api/Controllers/HlsSegmentController.cs +++ b/Jellyfin.Api/Controllers/HlsSegmentController.cs @@ -5,6 +5,7 @@ using System.IO; using System.Threading.Tasks; using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; +using MediaBrowser.Common.Api; using MediaBrowser.Common.Configuration; using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.MediaEncoding; @@ -20,6 +21,7 @@ namespace Jellyfin.Api.Controllers; /// The hls segment controller. /// [Route("")] +[Authorize(Policy = Policies.Playback)] public class HlsSegmentController : BaseJellyfinApiController { private readonly IFileSystem _fileSystem; diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs index 7768b3c45f..98c6b16e18 100644 --- a/Jellyfin.Api/Controllers/LiveTvController.cs +++ b/Jellyfin.Api/Controllers/LiveTvController.cs @@ -1139,6 +1139,7 @@ public class LiveTvController : BaseJellyfinApiController /// or a if recording not found. /// [HttpGet("LiveRecordings/{recordingId}/stream")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesVideoFile] @@ -1166,6 +1167,7 @@ public class LiveTvController : BaseJellyfinApiController /// or a if stream not found. /// [HttpGet("LiveStreamFiles/{streamId}/stream.{container}")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesVideoFile] diff --git a/Jellyfin.Api/Controllers/UniversalAudioController.cs b/Jellyfin.Api/Controllers/UniversalAudioController.cs index 4c3ef2c7f7..43705fffe1 100644 --- a/Jellyfin.Api/Controllers/UniversalAudioController.cs +++ b/Jellyfin.Api/Controllers/UniversalAudioController.cs @@ -8,6 +8,7 @@ using Jellyfin.Api.Attributes; using Jellyfin.Api.Helpers; using Jellyfin.Api.ModelBinders; using Jellyfin.Api.Models.StreamingDtos; +using MediaBrowser.Common.Api; using Jellyfin.Data.Enums; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; @@ -26,6 +27,7 @@ namespace Jellyfin.Api.Controllers; /// The universal audio controller. /// [Route("")] +[Authorize(Policy = Policies.Playback)] public class UniversalAudioController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager; @@ -82,7 +84,6 @@ public class UniversalAudioController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("Audio/{itemId}/universal")] [HttpHead("Audio/{itemId}/universal", Name = "HeadUniversalAudioStream")] - [Authorize] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status302Found)] [ProducesAudioFile] diff --git a/Jellyfin.Api/Controllers/VideosController.cs b/Jellyfin.Api/Controllers/VideosController.cs index b3029d6fa8..69d7b641d5 100644 --- a/Jellyfin.Api/Controllers/VideosController.cs +++ b/Jellyfin.Api/Controllers/VideosController.cs @@ -7,7 +7,6 @@ using System.Net.Http; 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; @@ -307,6 +306,7 @@ public class VideosController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream")] [HttpHead("{itemId}/stream", Name = "HeadVideoStream")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesVideoFile] public async Task GetVideoStream( @@ -545,6 +545,7 @@ public class VideosController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream.{container}")] [HttpHead("{itemId}/stream.{container}", Name = "HeadVideoStreamByContainer")] + [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesVideoFile] public Task GetVideoStreamByContainer( From a68dd941747dd7daec585b71a6393f4cd8818ed0 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sun, 21 Jan 2024 11:00:47 +0100 Subject: [PATCH 3/4] Simplyfy LocalAccessOrRequiresElevationHandler --- .../LocalAccessOrRequiresElevationHandler.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs index 557b7d3aa4..f4be266c18 100644 --- a/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs +++ b/Jellyfin.Api/Auth/LocalAccessOrRequiresElevationPolicy/LocalAccessOrRequiresElevationHandler.cs @@ -34,20 +34,9 @@ namespace Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy var ip = _httpContextAccessor.HttpContext?.GetNormalizedRemoteIP(); // Loopback will be on LAN, so we can accept null. - if (ip is null || _networkManager.IsInLocalNetwork(ip)) + if (ip is null || _networkManager.IsInLocalNetwork(ip) || context.User.IsInRole(UserRoles.Administrator)) { context.Succeed(requirement); - - return Task.CompletedTask; - } - - if (context.User.IsInRole(UserRoles.Administrator)) - { - context.Succeed(requirement); - } - else - { - context.Fail(); } return Task.CompletedTask; From f1b1c7355c5b93dbbbc4141dff66e3a0429c17b6 Mon Sep 17 00:00:00 2001 From: Shadowghost Date: Sat, 2 Mar 2024 11:29:42 +0100 Subject: [PATCH 4/4] Cleanup --- Jellyfin.Api/Controllers/AudioController.cs | 3 +- .../Controllers/DynamicHlsController.cs | 30 +++++++++---------- .../Controllers/VideoAttachmentsController.cs | 2 ++ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Jellyfin.Api/Controllers/AudioController.cs b/Jellyfin.Api/Controllers/AudioController.cs index 25941ebbb3..0caa75c98e 100644 --- a/Jellyfin.Api/Controllers/AudioController.cs +++ b/Jellyfin.Api/Controllers/AudioController.cs @@ -17,6 +17,7 @@ namespace Jellyfin.Api.Controllers; /// /// The audio controller. /// +[Authorize(Policy = Policies.Playback)] public class AudioController : BaseJellyfinApiController { private readonly AudioHelper _audioHelper; @@ -88,7 +89,6 @@ public class AudioController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream", Name = "GetAudioStream")] [HttpHead("{itemId}/stream", Name = "HeadAudioStream")] - [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesAudioFile] public async Task GetAudioStream( @@ -253,7 +253,6 @@ public class AudioController : BaseJellyfinApiController /// A containing the audio file. [HttpGet("{itemId}/stream.{container}", Name = "GetAudioStreamByContainer")] [HttpHead("{itemId}/stream.{container}", Name = "HeadAudioStreamByContainer")] - [Authorize(Policy = Policies.Playback)] [ProducesResponseType(StatusCodes.Status200OK)] [ProducesAudioFile] public async Task GetAudioStreamByContainer( diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs index 8b895da9ec..af7d745a6c 100644 --- a/Jellyfin.Api/Controllers/DynamicHlsController.cs +++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs @@ -37,16 +37,14 @@ namespace Jellyfin.Api.Controllers; /// /// Dynamic hls controller. /// -[Route("")] [Authorize(Policy = Policies.Playback)] public class DynamicHlsController : BaseJellyfinApiController { private const string DefaultVodEncoderPreset = "veryfast"; private const string DefaultEventEncoderPreset = "superfast"; - private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls; + private const TranscodingJobType DefaultTranscodingJobType = TranscodingJobType.Hls; private readonly Version _minFFmpegFlacInMp4 = new Version(6, 0); - private readonly ILibraryManager _libraryManager; private readonly IUserManager _userManager; private readonly IMediaSourceManager _mediaSourceManager; @@ -286,7 +284,7 @@ public class DynamicHlsController : BaseJellyfinApiController _mediaEncoder, _encodingHelper, _transcodeManager, - TranscodingJobType, + DefaultTranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -307,7 +305,7 @@ public class DynamicHlsController : BaseJellyfinApiController playlistPath, GetCommandLineArguments(playlistPath, state, true, 0), Request.HttpContext.User.GetUserId(), - TranscodingJobType, + DefaultTranscodingJobType, cancellationTokenSource) .ConfigureAwait(false); job.IsLiveOutput = true; @@ -327,7 +325,7 @@ public class DynamicHlsController : BaseJellyfinApiController } } - job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, DefaultTranscodingJobType); if (job is not null) { @@ -509,7 +507,7 @@ public class DynamicHlsController : BaseJellyfinApiController EnableTrickplay = enableTrickplay }; - return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + return await _dynamicHlsHelper.GetMasterHlsPlaylist(DefaultTranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); } /// @@ -675,7 +673,7 @@ public class DynamicHlsController : BaseJellyfinApiController EnableAdaptiveBitrateStreaming = enableAdaptiveBitrateStreaming }; - return await _dynamicHlsHelper.GetMasterHlsPlaylist(TranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); + return await _dynamicHlsHelper.GetMasterHlsPlaylist(DefaultTranscodingJobType, streamingRequest, enableAdaptiveBitrateStreaming).ConfigureAwait(false); } /// @@ -1380,7 +1378,7 @@ public class DynamicHlsController : BaseJellyfinApiController _mediaEncoder, _encodingHelper, _transcodeManager, - TranscodingJobType, + DefaultTranscodingJobType, cancellationTokenSource.Token) .ConfigureAwait(false); @@ -1418,7 +1416,7 @@ public class DynamicHlsController : BaseJellyfinApiController _mediaEncoder, _encodingHelper, _transcodeManager, - TranscodingJobType, + DefaultTranscodingJobType, cancellationToken) .ConfigureAwait(false); @@ -1432,7 +1430,7 @@ public class DynamicHlsController : BaseJellyfinApiController if (System.IO.File.Exists(segmentPath)) { - job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, DefaultTranscodingJobType); _logger.LogDebug("returning {0} [it exists, try 1]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } @@ -1442,7 +1440,7 @@ public class DynamicHlsController : BaseJellyfinApiController var startTranscoding = false; if (System.IO.File.Exists(segmentPath)) { - job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, DefaultTranscodingJobType); _logger.LogDebug("returning {0} [it exists, try 2]", segmentPath); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } @@ -1493,7 +1491,7 @@ public class DynamicHlsController : BaseJellyfinApiController playlistPath, GetCommandLineArguments(playlistPath, state, false, segmentId), Request.HttpContext.User.GetUserId(), - TranscodingJobType, + DefaultTranscodingJobType, cancellationTokenSource).ConfigureAwait(false); } catch @@ -1506,7 +1504,7 @@ public class DynamicHlsController : BaseJellyfinApiController } else { - job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job = _transcodeManager.OnTranscodeBeginRequest(playlistPath, DefaultTranscodingJobType); if (job?.TranscodingThrottler is not null) { await job.TranscodingThrottler.UnpauseTranscoding().ConfigureAwait(false); @@ -1515,7 +1513,7 @@ public class DynamicHlsController : BaseJellyfinApiController } _logger.LogDebug("returning {0} [general case]", segmentPath); - job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, TranscodingJobType); + job ??= _transcodeManager.OnTranscodeBeginRequest(playlistPath, DefaultTranscodingJobType); return await GetSegmentResult(state, playlistPath, segmentPath, segmentExtension, segmentId, job, cancellationToken).ConfigureAwait(false); } @@ -1993,7 +1991,7 @@ public class DynamicHlsController : BaseJellyfinApiController private int? GetCurrentTranscodingIndex(string playlist, string segmentExtension) { - var job = _transcodeManager.GetTranscodingJob(playlist, TranscodingJobType); + var job = _transcodeManager.GetTranscodingJob(playlist, DefaultTranscodingJobType); if (job is null || job.HasExited) { diff --git a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs index 23b9ba46f6..3b3e12824c 100644 --- a/Jellyfin.Api/Controllers/VideoAttachmentsController.cs +++ b/Jellyfin.Api/Controllers/VideoAttachmentsController.cs @@ -7,6 +7,7 @@ using Jellyfin.Api.Attributes; using MediaBrowser.Common.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -16,6 +17,7 @@ namespace Jellyfin.Api.Controllers; /// Attachments controller. /// [Route("Videos")] +[Authorize] public class VideoAttachmentsController : BaseJellyfinApiController { private readonly ILibraryManager _libraryManager;