diff --git a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
index 7d68aecf9..456f45d97 100644
--- a/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
+++ b/Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
@@ -1,4 +1,4 @@
-using System.Security.Claims;
+using System.Security.Claims;
using Jellyfin.Api.Helpers;
using Jellyfin.Data.Enums;
using MediaBrowser.Common.Extensions;
@@ -36,6 +36,16 @@ namespace Jellyfin.Api.Auth
_httpContextAccessor = httpContextAccessor;
}
+ ///
+ /// Gets a value indicating being used.
+ ///
+ protected INetworkManager NetworkManager => _networkManager;
+
+ ///
+ /// Gets a value indicating the being used.
+ ///
+ protected IHttpContextAccessor HttpContextAccessor => _httpContextAccessor;
+
///
/// Validate authenticated claims.
///
diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs
new file mode 100644
index 000000000..e6b33f565
--- /dev/null
+++ b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessHandler.cs
@@ -0,0 +1,48 @@
+using System.Threading.Tasks;
+using Jellyfin.Api.Auth;
+using MediaBrowser.Common.Extensions;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.NetworkAccessPolicy
+{
+ ///
+ /// Local access handler.
+ ///
+ public class NetworkAccessHandler : BaseAuthorizationHandler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Instance of the interface.
+ /// Instance of the interface.
+ /// Instance of the interface.
+ public NetworkAccessHandler(
+ IUserManager userManager,
+ INetworkManager networkManager,
+ IHttpContextAccessor httpContextAccessor)
+ : base(userManager, networkManager, httpContextAccessor)
+ {
+ }
+
+ ///
+ protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NetworkAccessRequirement requirement)
+ {
+ var ip = HttpContextAccessor.HttpContext?.Connection.RemoteIpAddress;
+
+ // Loopback will be on LAN, so we can accept null.
+ if (ip == null || NetworkManager.IsInLocalNetwork(ip))
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs
new file mode 100644
index 000000000..b5431501b
--- /dev/null
+++ b/Jellyfin.Api/Auth/NetworkAccessPolicy/NetworkAccessRequirement.cs
@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.NetworkAccessPolicy
+{
+ ///
+ /// The local network authorization requirement.
+ ///
+ public class NetworkAccessRequirement : IAuthorizationRequirement
+ {
+ }
+}
diff --git a/Jellyfin.Api/Constants/Policies.cs b/Jellyfin.Api/Constants/Policies.cs
index 632dedb3c..d98debd0a 100644
--- a/Jellyfin.Api/Constants/Policies.cs
+++ b/Jellyfin.Api/Constants/Policies.cs
@@ -45,6 +45,11 @@ namespace Jellyfin.Api.Constants
///
public const string LocalAccessOrRequiresElevation = "LocalAccessOrRequiresElevation";
+ ///
+ /// Policy name for requiring local LAN access.
+ ///
+ public const string NetworkAccessPolicy = "NetworkAccessPolicy";
+
///
/// Policy name for escaping schedule controls or requiring first time setup.
///
diff --git a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
index 2b34370a0..48afc2b8c 100644
--- a/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
+++ b/Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs
@@ -15,6 +15,7 @@ using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
using Jellyfin.Api.Auth.IgnoreParentalControlPolicy;
using Jellyfin.Api.Auth.LocalAccessOrRequiresElevationPolicy;
using Jellyfin.Api.Auth.LocalAccessPolicy;
+using Jellyfin.Api.Auth.NetworkAccessPolicy;
using Jellyfin.Api.Auth.RequiresElevationPolicy;
using Jellyfin.Api.Auth.SyncPlayAccessPolicy;
using Jellyfin.Api.Constants;
@@ -61,6 +62,7 @@ namespace Jellyfin.Server.Extensions
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
serviceCollection.AddSingleton();
@@ -113,7 +115,7 @@ namespace Jellyfin.Server.Extensions
policy =>
{
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
- policy.AddRequirements(new LocalAccessRequirement());
+ policy.AddRequirements(new NetworkAccessRequirement());
});
options.AddPolicy(
Policies.LocalAccessOrRequiresElevation,
@@ -157,6 +159,13 @@ namespace Jellyfin.Server.Extensions
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
policy.AddRequirements(new SyncPlayAccessRequirement(SyncPlayAccessRequirementType.IsInGroup));
});
+ options.AddPolicy(
+ Policies.NetworkAccessPolicy,
+ policy =>
+ {
+ policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+ policy.AddRequirements(new NetworkAccessRequirement());
+ });
});
}