mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
Add more authorization handlers, actually authorize requests
This commit is contained in:
parent
cf9223b8cb
commit
4aac936721
@ -39,9 +39,9 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
_networkManager = networkManager;
|
_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)
|
public User Authenticate(HttpRequest request, IAuthenticationAttributes authAttributes)
|
||||||
@ -51,17 +51,33 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
return user;
|
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
|
// This code is executed before the service
|
||||||
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
var auth = _authorizationContext.GetAuthorizationInfo(request);
|
||||||
|
|
||||||
if (!IsExemptFromAuthenticationToken(authAttribtues, request))
|
if (!IsExemptFromAuthenticationToken(authAttributes, request))
|
||||||
{
|
{
|
||||||
ValidateSecurityToken(request, auth.Token);
|
ValidateSecurityToken(request, auth.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authAttribtues.AllowLocalOnly && !request.IsLocal)
|
if (authAttributes.AllowLocalOnly && !request.IsLocal)
|
||||||
{
|
{
|
||||||
throw new SecurityException("Operation not found.");
|
throw new SecurityException("Operation not found.");
|
||||||
}
|
}
|
||||||
@ -75,14 +91,14 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
|
|
||||||
if (user != null)
|
if (user != null)
|
||||||
{
|
{
|
||||||
ValidateUserAccess(user, request, authAttribtues, auth);
|
ValidateUserAccess(user, request, authAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
var info = GetTokenInfo(request);
|
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);
|
ValidateRoles(roles, user);
|
||||||
}
|
}
|
||||||
@ -106,8 +122,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
private void ValidateUserAccess(
|
private void ValidateUserAccess(
|
||||||
User user,
|
User user,
|
||||||
IRequest request,
|
IRequest request,
|
||||||
IAuthenticationAttributes authAttributes,
|
IAuthenticationAttributes authAttributes)
|
||||||
AuthorizationInfo auth)
|
|
||||||
{
|
{
|
||||||
if (user.HasPermission(PermissionKind.IsDisabled))
|
if (user.HasPermission(PermissionKind.IsDisabled))
|
||||||
{
|
{
|
||||||
@ -230,16 +245,6 @@ namespace Emby.Server.Implementations.HttpServer.Security
|
|||||||
{
|
{
|
||||||
throw new AuthenticationException("Access token is invalid or expired.");
|
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
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
Normal file
102
Jellyfin.Api/Auth/BaseAuthorizationHandler.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,6 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Globalization;
|
||||||
using System.Security.Authentication;
|
using System.Security.Authentication;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
@ -39,15 +42,10 @@ namespace Jellyfin.Api.Auth
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
{
|
{
|
||||||
var authenticatedAttribute = new AuthenticatedAttribute
|
|
||||||
{
|
|
||||||
IgnoreLegacyAuth = true
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var user = _authService.Authenticate(Request, authenticatedAttribute);
|
var authorizationInfo = _authService.Authenticate(Request);
|
||||||
if (user == null)
|
if (authorizationInfo == null)
|
||||||
{
|
{
|
||||||
return Task.FromResult(AuthenticateResult.NoResult());
|
return Task.FromResult(AuthenticateResult.NoResult());
|
||||||
// TODO return when legacy API is removed.
|
// TODO return when legacy API is removed.
|
||||||
@ -57,11 +55,16 @@ namespace Jellyfin.Api.Auth
|
|||||||
|
|
||||||
var claims = new[]
|
var claims = new[]
|
||||||
{
|
{
|
||||||
new Claim(ClaimTypes.Name, user.Username),
|
new Claim(ClaimTypes.Name, authorizationInfo.User.Username),
|
||||||
new Claim(
|
new Claim(ClaimTypes.Role, value: authorizationInfo.User.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User),
|
||||||
ClaimTypes.Role,
|
new Claim(InternalClaimTypes.UserId, authorizationInfo.UserId.ToString("N", CultureInfo.InvariantCulture)),
|
||||||
value: user.HasPermission(PermissionKind.IsAdministrator) ? UserRoles.Administrator : UserRoles.User)
|
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 identity = new ClaimsIdentity(claims, Scheme.Name);
|
||||||
var principal = new ClaimsPrincipal(identity);
|
var principal = new ClaimsPrincipal(identity);
|
||||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Auth.DefaultAuthorizationPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The default authorization requirement.
|
||||||
|
/// </summary>
|
||||||
|
public class DefaultAuthorizationRequirement : IAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,33 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authorization handler for requiring first time setup or elevated privileges.
|
/// Authorization handler for requiring first time setup or elevated privileges.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class FirstTimeSetupOrElevatedHandler : AuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
|
public class FirstTimeSetupOrElevatedHandler : BaseAuthorizationHandler<FirstTimeSetupOrElevatedRequirement>
|
||||||
{
|
{
|
||||||
private readonly IConfigurationManager _configurationManager;
|
private readonly IConfigurationManager _configurationManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class.
|
/// Initializes a new instance of the <see cref="FirstTimeSetupOrElevatedHandler" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="configurationManager">The jellyfin configuration manager.</param>
|
/// <param name="configurationManager">Instance of the <see cref="IConfigurationManager"/> interface.</param>
|
||||||
public FirstTimeSetupOrElevatedHandler(IConfigurationManager configurationManager)
|
/// <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;
|
_configurationManager = configurationManager;
|
||||||
}
|
}
|
||||||
@ -28,7 +39,9 @@ namespace Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy
|
|||||||
{
|
{
|
||||||
context.Succeed(firstTimeSetupOrElevatedRequirement);
|
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);
|
context.Succeed(firstTimeSetupOrElevatedRequirement);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Auth.IgnoreSchedulePolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Escape schedule controls requirement.
|
||||||
|
/// </summary>
|
||||||
|
public class IgnoreScheduleRequirement : IAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
44
Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
Normal file
44
Jellyfin.Api/Auth/LocalAccessPolicy/LocalAccessHandler.cs
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
||||||
|
namespace Jellyfin.Api.Auth.LocalAccessPolicy
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The local access authorization requirement.
|
||||||
|
/// </summary>
|
||||||
|
public class LocalAccessRequirement : IAuthorizationRequirement
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -1,21 +1,43 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
|
using MediaBrowser.Common.Net;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
namespace Jellyfin.Api.Auth.RequiresElevationPolicy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Authorization handler for requiring elevated privileges.
|
/// Authorization handler for requiring elevated privileges.
|
||||||
/// </summary>
|
/// </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 />
|
/// <inheritdoc />
|
||||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RequiresElevationRequirement requirement)
|
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);
|
context.Succeed(requirement);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.Fail();
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
38
Jellyfin.Api/Constants/InternalClaimTypes.cs
Normal file
38
Jellyfin.Api/Constants/InternalClaimTypes.cs
Normal file
@ -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";
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,11 @@ namespace Jellyfin.Api.Constants
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static class Policies
|
public static class Policies
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Policy name for default authorization.
|
||||||
|
/// </summary>
|
||||||
|
public const string DefaultAuthorization = "DefaultAuthorization";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Policy name for requiring first time setup or elevated privileges.
|
/// Policy name for requiring first time setup or elevated privileges.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -14,5 +19,15 @@ namespace Jellyfin.Api.Constants
|
|||||||
/// Policy name for requiring elevated privileges.
|
/// Policy name for requiring elevated privileges.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string RequiresElevation = "RequiresElevation";
|
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
Jellyfin.Api/Helpers/ClaimHelpers.cs
Normal file
77
Jellyfin.Api/Helpers/ClaimHelpers.cs
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,7 +5,10 @@ using System.Linq;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Jellyfin.Api;
|
using Jellyfin.Api;
|
||||||
using Jellyfin.Api.Auth;
|
using Jellyfin.Api.Auth;
|
||||||
|
using Jellyfin.Api.Auth.DefaultAuthorizationPolicy;
|
||||||
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
|
using Jellyfin.Api.Auth.FirstTimeSetupOrElevatedPolicy;
|
||||||
|
using Jellyfin.Api.Auth.IgnoreSchedulePolicy;
|
||||||
|
using Jellyfin.Api.Auth.LocalAccessPolicy;
|
||||||
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
using Jellyfin.Api.Auth.RequiresElevationPolicy;
|
||||||
using Jellyfin.Api.Constants;
|
using Jellyfin.Api.Constants;
|
||||||
using Jellyfin.Api.Controllers;
|
using Jellyfin.Api.Controllers;
|
||||||
@ -33,16 +36,19 @@ namespace Jellyfin.Server.Extensions
|
|||||||
/// <returns>The updated service collection.</returns>
|
/// <returns>The updated service collection.</returns>
|
||||||
public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
|
public static IServiceCollection AddJellyfinApiAuthorization(this IServiceCollection serviceCollection)
|
||||||
{
|
{
|
||||||
|
serviceCollection.AddSingleton<IAuthorizationHandler, DefaultAuthorizationHandler>();
|
||||||
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
|
serviceCollection.AddSingleton<IAuthorizationHandler, FirstTimeSetupOrElevatedHandler>();
|
||||||
|
serviceCollection.AddSingleton<IAuthorizationHandler, IgnoreScheduleHandler>();
|
||||||
|
serviceCollection.AddSingleton<IAuthorizationHandler, LocalAccessHandler>();
|
||||||
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
|
serviceCollection.AddSingleton<IAuthorizationHandler, RequiresElevationHandler>();
|
||||||
return serviceCollection.AddAuthorizationCore(options =>
|
return serviceCollection.AddAuthorizationCore(options =>
|
||||||
{
|
{
|
||||||
options.AddPolicy(
|
options.AddPolicy(
|
||||||
Policies.RequiresElevation,
|
Policies.DefaultAuthorization,
|
||||||
policy =>
|
policy =>
|
||||||
{
|
{
|
||||||
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
||||||
policy.AddRequirements(new RequiresElevationRequirement());
|
policy.AddRequirements(new DefaultAuthorizationRequirement());
|
||||||
});
|
});
|
||||||
options.AddPolicy(
|
options.AddPolicy(
|
||||||
Policies.FirstTimeSetupOrElevated,
|
Policies.FirstTimeSetupOrElevated,
|
||||||
@ -51,6 +57,27 @@ namespace Jellyfin.Server.Extensions
|
|||||||
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
policy.AddAuthenticationSchemes(AuthenticationSchemes.CustomAuthentication);
|
||||||
policy.AddRequirements(new FirstTimeSetupOrElevatedRequirement());
|
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());
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,31 @@ using Microsoft.AspNetCore.Http;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Net
|
namespace MediaBrowser.Controller.Net
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// IAuthService.
|
||||||
|
/// </summary>
|
||||||
public interface IAuthService
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user