Browse Source

Add more authorization handlers, actually authorize requests

crobibero 5 years ago
parent
commit
4aac936721

+ 25 - 20
Emby.Server.Implementations/HttpServer/Security/AuthService.cs

@@ -39,9 +39,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
             _networkManager = networkManager;
         }
 
-        public void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues)
+        public void Authenticate(IRequest request, IAuthenticationAttributes authAttributes)
         {
-            ValidateUser(request, authAttribtues);
+            ValidateUser(request, authAttributes);
         }
 
         public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
@@ -51,17 +51,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
             return user;
         }
 
-        private User ValidateUser(IRequest request, IAuthenticationAttributes authAttribtues)
+        public AuthorizationInfo Authenticate(HttpRequest request)
+        {
+            var auth = _authorizationContext.GetAuthorizationInfo(request);
+            if (auth?.User == null)
+            {
+                return null;
+            }
+
+            if (auth.User.HasPermission(PermissionKind.IsDisabled))
+            {
+                throw new SecurityException("User account has been disabled.");
+            }
+
+            return auth;
+        }
+
+        private User ValidateUser(IRequest request, IAuthenticationAttributes authAttributes)
         {
             // This code is executed before the service
             var auth = _authorizationContext.GetAuthorizationInfo(request);
 
-            if (!IsExemptFromAuthenticationToken(authAttribtues, request))
+            if (!IsExemptFromAuthenticationToken(authAttributes, request))
             {
                 ValidateSecurityToken(request, auth.Token);
             }
 
-            if (authAttribtues.AllowLocalOnly && !request.IsLocal)
+            if (authAttributes.AllowLocalOnly && !request.IsLocal)
             {
                 throw new SecurityException("Operation not found.");
             }
@@ -75,14 +91,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
 
             if (user != null)
             {
-                ValidateUserAccess(user, request, authAttribtues, auth);
+                ValidateUserAccess(user, request, authAttributes);
             }
 
             var info = GetTokenInfo(request);
 
-            if (!IsExemptFromRoles(auth, authAttribtues, request, info))
+            if (!IsExemptFromRoles(auth, authAttributes, request, info))
             {
-                var roles = authAttribtues.GetRoles();
+                var roles = authAttributes.GetRoles();
 
                 ValidateRoles(roles, user);
             }
@@ -106,8 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
         private void ValidateUserAccess(
             User user,
             IRequest request,
-            IAuthenticationAttributes authAttributes,
-            AuthorizationInfo auth)
+            IAuthenticationAttributes authAttributes)
         {
             if (user.HasPermission(PermissionKind.IsDisabled))
             {
@@ -230,16 +245,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
             {
                 throw new AuthenticationException("Access token is invalid or expired.");
             }
-
-            //if (!string.IsNullOrEmpty(info.UserId))
-            //{
-            //    var user = _userManager.GetUserById(info.UserId);
-
-            //    if (user == null || user.Configuration.IsDisabled)
-            //    {
-            //        throw new SecurityException("User account has been disabled.");
-            //    }
-            //}
         }
     }
 }

+ 102 - 0
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs

@@ -0,0 +1,102 @@
+#nullable enable
+
+using System.Net;
+using System.Security.Claims;
+using Jellyfin.Api.Helpers;
+using Jellyfin.Data.Enums;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth
+{
+    /// <summary>
+    /// Base authorization handler.
+    /// </summary>
+    /// <typeparam name="T">Type of Authorization Requirement.</typeparam>
+    public abstract class BaseAuthorizationHandler<T> : AuthorizationHandler<T>
+        where T : IAuthorizationRequirement
+    {
+        private readonly IUserManager _userManager;
+        private readonly INetworkManager _networkManager;
+        private readonly IHttpContextAccessor _httpContextAccessor;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BaseAuthorizationHandler{T}"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        protected BaseAuthorizationHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+        {
+            _userManager = userManager;
+            _networkManager = networkManager;
+            _httpContextAccessor = httpContextAccessor;
+        }
+
+        /// <summary>
+        /// Validate authenticated claims.
+        /// </summary>
+        /// <param name="claimsPrincipal">Request claims.</param>
+        /// <param name="ignoreSchedule">Whether to ignore parental control.</param>
+        /// <param name="localAccessOnly">Whether access is to be allowed locally only.</param>
+        /// <returns>Validated claim status.</returns>
+        protected bool ValidateClaims(
+            ClaimsPrincipal claimsPrincipal,
+            bool ignoreSchedule = false,
+            bool localAccessOnly = false)
+        {
+            // Ensure claim has userId.
+            var userId = ClaimHelpers.GetUserId(claimsPrincipal);
+            if (userId == null)
+            {
+                return false;
+            }
+
+            // Ensure userId links to a valid user.
+            var user = _userManager.GetUserById(userId.Value);
+            if (user == null)
+            {
+                return false;
+            }
+
+            // Ensure user is not disabled.
+            if (user.HasPermission(PermissionKind.IsDisabled))
+            {
+                return false;
+            }
+
+            var ip = NormalizeIp(_httpContextAccessor.HttpContext.Connection.RemoteIpAddress).ToString();
+            var isInLocalNetwork = _networkManager.IsInLocalNetwork(ip);
+            // User cannot access remotely and user is remote
+            if (!user.HasPermission(PermissionKind.EnableRemoteAccess) && !isInLocalNetwork)
+            {
+                return false;
+            }
+
+            if (localAccessOnly && !isInLocalNetwork)
+            {
+                return false;
+            }
+
+            // User attempting to access out of parental control hours.
+            if (!ignoreSchedule
+                && !user.HasPermission(PermissionKind.IsAdministrator)
+                && !user.IsParentalScheduleAllowed())
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        private static IPAddress NormalizeIp(IPAddress ip)
+        {
+            return ip.IsIPv4MappedToIPv6 ? ip.MapToIPv4() : ip;
+        }
+    }
+}

+ 14 - 11
Jellyfin.Api/Auth/CustomAuthenticationHandler.cs

@@ -1,3 +1,6 @@
+#nullable enable
+
+using System.Globalization;
 using System.Security.Authentication;
 using System.Security.Claims;
 using System.Text.Encodings.Web;
@@ -39,15 +42,10 @@ namespace Jellyfin.Api.Auth
         /// <inheritdoc />
         protected override Task<AuthenticateResult> HandleAuthenticateAsync()
         {
-            var authenticatedAttribute = new AuthenticatedAttribute
-            {
-                IgnoreLegacyAuth = true
-            };
-
             try
             {
-                var user = _authService.Authenticate(Request, authenticatedAttribute);
-                if (user == null)
+                var authorizationInfo = _authService.Authenticate(Request);
+                if (authorizationInfo == null)
                 {
                     return Task.FromResult(AuthenticateResult.NoResult());
                     // TODO return when legacy API is removed.
@@ -57,11 +55,16 @@ namespace Jellyfin.Api.Auth
 
                 var claims = new[]
                 {
-                    new Claim(ClaimTypes.Name, user.Username),
-                    new Claim(
-                        ClaimTypes.Role,
-                        value: user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User)
+                    new Claim(ClaimTypes.Name, authorizationInfo.User.Username),
+                    new Claim(ClaimTypes.Role, value: authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User),
+                    new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)),
+                    new Claim(InternalClaimTypes.DeviceId, authorizationInfo.DeviceId),
+                    new Claim(InternalClaimTypes.Device, authorizationInfo.Device),
+                    new Claim(InternalClaimTypes.Client, authorizationInfo.Client),
+                    new Claim(InternalClaimTypes.Version, authorizationInfo.Version),
+                    new Claim(InternalClaimTypes.Token, authorizationInfo.Token)
                 };
+
                 var identity = new ClaimsIdentity(claims, Scheme.Name);
                 var principal = new ClaimsPrincipal(identity);
                 var ticket = new AuthenticationTicket(principal, Scheme.Name);

+ 42 - 0
Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationHandler.cs

@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
+{
+    /// <summary>
+    /// Default authorization handler.
+    /// </summary>
+    public class DefaultAuthorizationHandler : BaseAuthorizationHandler<DefaultAuthorizationRequirement>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DefaultAuthorizationHandler"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public DefaultAuthorizationHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
+        {
+        }
+
+        /// <inheritdoc />
+        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
+        {
+            var validated = ValidateClaims(context.User);
+            if (!validated)
+            {
+                context.Fail();
+                return Task.CompletedTask;
+            }
+
+            context.Succeed(requirement);
+            return Task.CompletedTask;
+        }
+    }
+}

+ 11 - 0
Jellyfin.Api/Auth/DefaultAuthorizationPolicy/DefaultAuthorizationRequirement.cs

@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
+{
+    /// <summary>
+    /// The default authorization requirement.
+    /// </summary>
+    public class DefaultAuthorizationRequirement : IAuthorizationRequirement
+    {
+    }
+}

+ 17 - 4
Jellyfin.Api/Auth/FirstTimeSetupOrElevatedPolicy/FirstTimeSetupOrElevatedHandler.cs

@@ -1,22 +1,33 @@
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
 using MediaBrowser.Common.Configuration;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
 
 namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
 {
     /// <summary>
     /// Authorization handler for requiring first time setup or elevated privileges.
     /// </summary>
-    public class FirstTimeSetupOrElevatedHandler : AuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
+    public class FirstTimeSetupOrElevatedHandler : BaseAuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
     {
         private readonly IConfigurationManager _configurationManager;
 
         /// <summary>
         /// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class.
         /// </summary>
-        /// <param name="configurationManager">The jellyfin configuration manager.</param>
-        public FirstTimeSetupOrElevatedHandler(IConfigurationManager configurationManager)
+        /// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public FirstTimeSetupOrElevatedHandler(
+            IConfigurationManager configurationManager,
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
         {
             _configurationManager = configurationManager;
         }
@@ -28,7 +39,9 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
             {
                 context.Succeed(firstTimeSetupOrElevatedRequirement);
             }
-            else if (context.User.IsInRole(UserRoles.Administrator))
+
+            var validated = ValidateClaims(context.User);
+            if (validated && context.User.IsInRole(UserRoles.Administrator))
             {
                 context.Succeed(firstTimeSetupOrElevatedRequirement);
             }

+ 42 - 0
Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleHandler.cs

@@ -0,0 +1,42 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
+{
+    /// <summary>
+    /// Escape schedule controls handler.
+    /// </summary>
+    public class IgnoreScheduleHandler : BaseAuthorizationHandler<IgnoreScheduleRequirement>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="IgnoreScheduleHandler"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public IgnoreScheduleHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
+        {
+        }
+
+        /// <inheritdoc />
+        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, IgnoreScheduleRequirement requirement)
+        {
+            var validated = ValidateClaims(context.User, ignoreSchedule: true);
+            if (!validated)
+            {
+                context.Fail();
+                return Task.CompletedTask;
+            }
+
+            context.Succeed(requirement);
+            return Task.CompletedTask;
+        }
+    }
+}

+ 11 - 0
Jellyfin.Api/Auth/IgnoreSchedulePolicy/IgnoreScheduleRequirement.cs

@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
+{
+    /// <summary>
+    /// Escape schedule controls requirement.
+    /// </summary>
+    public class IgnoreScheduleRequirement : IAuthorizationRequirement
+    {
+    }
+}

+ 44 - 0
Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs

@@ -0,0 +1,44 @@
+using System.Threading.Tasks;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+
+namespace Jellyfin.Api.Auth.LocalAccessPolicy
+{
+    /// <summary>
+    /// Local access handler.
+    /// </summary>
+    public class LocalAccessHandler : BaseAuthorizationHandler<LocalAccessRequirement>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="LocalAccessHandler"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public LocalAccessHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
+        {
+        }
+
+        /// <inheritdoc />
+        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, LocalAccessRequirement requirement)
+        {
+            var validated = ValidateClaims(context.User, localAccessOnly: true);
+            if (!validated)
+            {
+                context.Fail();
+            }
+            else
+            {
+                context.Succeed(requirement);
+            }
+
+            return Task.CompletedTask;
+        }
+    }
+}

+ 11 - 0
Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessRequirement.cs

@@ -0,0 +1,11 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Jellyfin.Api.Auth.LocalAccessPolicy
+{
+    /// <summary>
+    /// The local access authorization requirement.
+    /// </summary>
+    public class LocalAccessRequirement : IAuthorizationRequirement
+    {
+    }
+}

+ 24 - 2
Jellyfin.Api/Auth/RequiresElevationPolicy/RequiresElevationHandler.cs

@@ -1,21 +1,43 @@
 using System.Threading.Tasks;
 using Jellyfin.Api.Constants;
+using MediaBrowser.Common.Net;
+using MediaBrowser.Controller.Library;
 using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
 
 namespace Jellyfin.Api.Auth.RequiresElevationPolicy
 {
     /// <summary>
     /// Authorization handler for requiring elevated privileges.
     /// </summary>
-    public class RequiresElevationHandler : AuthorizationHandler<RequiresElevationRequirement>
+    public class RequiresElevationHandler : BaseAuthorizationHandler<RequiresElevationRequirement>
     {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RequiresElevationHandler"/> class.
+        /// </summary>
+        /// <param name="userManager">Instance of the <see cref="IUserManager"/> interface.</param>
+        /// <param name="networkManager">Instance of the <see cref="INetworkManager"/> interface.</param>
+        /// <param name="httpContextAccessor">Instance of the <see cref="IHttpContextAccessor"/> interface.</param>
+        public RequiresElevationHandler(
+            IUserManager userManager,
+            INetworkManager networkManager,
+            IHttpContextAccessor httpContextAccessor)
+            : base(userManager, networkManager, httpContextAccessor)
+        {
+        }
+
         /// <inheritdoc />
         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement)
         {
-            if (context.User.IsInRole(UserRoles.Administrator))
+            var validated = ValidateClaims(context.User);
+            if (validated && context.User.IsInRole(UserRoles.Administrator))
             {
                 context.Succeed(requirement);
             }
+            else
+            {
+                context.Fail();
+            }
 
             return Task.CompletedTask;
         }

+ 38 - 0
Jellyfin.Api/Constants/InternalClaimTypes.cs

@@ -0,0 +1,38 @@
+namespace Jellyfin.Api.Constants
+{
+    /// <summary>
+    /// Internal claim types for authorization.
+    /// </summary>
+    public static class InternalClaimTypes
+    {
+        /// <summary>
+        /// User Id.
+        /// </summary>
+        public const string UserId = "Jellyfin-UserId";
+
+        /// <summary>
+        /// Device Id.
+        /// </summary>
+        public const string DeviceId = "Jellyfin-DeviceId";
+
+        /// <summary>
+        /// Device.
+        /// </summary>
+        public const string Device = "Jellyfin-Device";
+
+        /// <summary>
+        /// Client.
+        /// </summary>
+        public const string Client = "Jellyfin-Client";
+
+        /// <summary>
+        /// Version.
+        /// </summary>
+        public const string Version = "Jellyfin-Version";
+
+        /// <summary>
+        /// Token.
+        /// </summary>
+        public const string Token = "Jellyfin-Token";
+    }
+}

+ 15 - 0
Jellyfin.Api/Constants/Policies.cs

@@ -5,6 +5,11 @@ namespace Jellyfin.Api.Constants
     /// </summary>
     public static class Policies
     {
+        /// <summary>
+        /// Policy name for default authorization.
+        /// </summary>
+        public const string DefaultAuthorization = "DefaultAuthorization";
+
         /// <summary>
         /// Policy name for requiring first time setup or elevated privileges.
         /// </summary>
@@ -14,5 +19,15 @@ namespace Jellyfin.Api.Constants
         /// Policy name for requiring elevated privileges.
         /// </summary>
         public const string RequiresElevation = "RequiresElevation";
+
+        /// <summary>
+        /// Policy name for allowing local access only.
+        /// </summary>
+        public const string LocalAccessOnly = "LocalAccessOnly";
+
+        /// <summary>
+        /// Policy name for escaping schedule controls.
+        /// </summary>
+        public const string IgnoreSchedule = "IgnoreSchedule";
     }
 }

+ 77 - 0
Jellyfin.Api/Helpers/ClaimHelpers.cs

@@ -0,0 +1,77 @@
+#nullable enable
+
+using System;
+using System.Linq;
+using System.Security.Claims;
+using Jellyfin.Api.Constants;
+
+namespace Jellyfin.Api.Helpers
+{
+    /// <summary>
+    /// Claim Helpers.
+    /// </summary>
+    public static class ClaimHelpers
+    {
+        /// <summary>
+        /// Get user id from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>User id.</returns>
+        public static Guid? GetUserId(in ClaimsPrincipal user)
+        {
+            var value = GetClaimValue(user, InternalClaimTypes.UserId);
+            return string.IsNullOrEmpty(value)
+                ? null
+                : (Guid?)Guid.Parse(value);
+        }
+
+        /// <summary>
+        /// Get device id from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>Device id.</returns>
+        public static string? GetDeviceId(in ClaimsPrincipal user)
+            => GetClaimValue(user, InternalClaimTypes.DeviceId);
+
+        /// <summary>
+        /// Get device from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>Device.</returns>
+        public static string? GetDevice(in ClaimsPrincipal user)
+            => GetClaimValue(user, InternalClaimTypes.Device);
+
+        /// <summary>
+        /// Get client from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>Client.</returns>
+        public static string? GetClient(in ClaimsPrincipal user)
+            => GetClaimValue(user, InternalClaimTypes.Client);
+
+        /// <summary>
+        /// Get version from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>Version.</returns>
+        public static string? GetVersion(in ClaimsPrincipal user)
+            => GetClaimValue(user, InternalClaimTypes.Version);
+
+        /// <summary>
+        /// Get token from claims.
+        /// </summary>
+        /// <param name="user">Current claims principal.</param>
+        /// <returns>Token.</returns>
+        public static string? GetToken(in ClaimsPrincipal user)
+            => GetClaimValue(user, InternalClaimTypes.Token);
+
+        private static string? GetClaimValue(in ClaimsPrincipal user, string name)
+        {
+            return user?.Identities
+                .SelectMany(c => c.Claims)
+                .Where(claim => claim.Type.Equals(name, StringComparison.OrdinalIgnoreCase))
+                .Select(claim => claim.Value)
+                .FirstOrDefault();
+        }
+    }
+}

+ 29 - 2
Jellyfin.Server/Extensions/ApiServiceCollectionExtensions.cs

@@ -5,7 +5,10 @@ using System.Linq;
 using System.Reflection;
 using Jellyfin.Api;
 using Jellyfin.Api.Auth;
+using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
 using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
+using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
+using Jellyfin.Api.Auth.LocalAccessPolicy;
 using Jellyfin.Api.Auth.RequiresElevationPolicy;
 using Jellyfin.Api.Constants;
 using Jellyfin.Api.Controllers;
@@ -33,16 +36,19 @@ namespace Jellyfin.Server.Extensions
         /// <returns>The updated service collection.</returns>
         public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
         {
+            serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
             serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
+            serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>();
+            serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
             serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
             return serviceCollection.AddAuthorizationCore(options =>
             {
                 options.AddPolicy(
-                    Policies.RequiresElevation,
+                    Policies.DefaultAuthorization,
                     policy =>
                     {
                         policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
-                        policy.AddRequirements(new RequiresElevationRequirement());
+                        policy.AddRequirements(new DefaultAuthorizationRequirement());
                     });
                 options.AddPolicy(
                     Policies.FirstTimeSetupOrElevated,
@@ -51,6 +57,27 @@ namespace Jellyfin.Server.Extensions
                         policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
                         policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement());
                     });
+                options.AddPolicy(
+                    Policies.IgnoreSchedule,
+                    policy =>
+                    {
+                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+                        policy.AddRequirements(new IgnoreScheduleRequirement());
+                    });
+                options.AddPolicy(
+                    Policies.LocalAccessOnly,
+                    policy =>
+                    {
+                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+                        policy.AddRequirements(new LocalAccessRequirement());
+                    });
+                options.AddPolicy(
+                    Policies.RequiresElevation,
+                    policy =>
+                    {
+                        policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
+                        policy.AddRequirements(new RequiresElevationRequirement());
+                    });
             });
         }
 

+ 23 - 2
MediaBrowser.Controller/Net/IAuthService.cs

@@ -6,10 +6,31 @@ using Microsoft.AspNetCore.Http;
 
 namespace MediaBrowser.Controller.Net
 {
+    /// <summary>
+    /// IAuthService.
+    /// </summary>
     public interface IAuthService
     {
-        void Authenticate(IRequest request, IAuthenticationAttributes authAttribtues);
+        /// <summary>
+        /// Authenticate and authorize request.
+        /// </summary>
+        /// <param name="request">Request.</param>
+        /// <param name="authAttribtutes">Authorization attributes.</param>
+        void Authenticate(IRequest request, IAuthenticationAttributes authAttribtutes);
 
-        User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtues);
+        /// <summary>
+        /// Authenticate and authorize request.
+        /// </summary>
+        /// <param name="request">Request.</param>
+        /// <param name="authAttribtutes">Authorization attributes.</param>
+        /// <returns>Authenticated user.</returns>
+        User? Authenticate(HttpRequest request, IAuthenticationAttributes authAttribtutes);
+
+        /// <summary>
+        /// Authenticate request.
+        /// </summary>
+        /// <param name="request">The request.</param>
+        /// <returns>Authorization information. Null if unauthenticated.</returns>
+        AuthorizationInfo Authenticate(HttpRequest request);
     }
 }