Plugins: Add authorization hook (#3977)

* Plugins: Add authorization hook

Makes the `PolicyRequirement` available to plugins.
Adds a filter hook to the authorization handlers, so that plugins can extend and leverage the existing authorization policies and permissions.

* Update to pass back and forth handle class
This commit is contained in:
d11n
2022-08-02 07:20:16 +02:00
committed by GitHub
parent 3db2b60b92
commit 1e378dd986
6 changed files with 65 additions and 23 deletions

View File

@@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
namespace BTCPayServer.Security;
public class AuthorizationFilterHandle
{
public AuthorizationHandlerContext Context { get; }
public PolicyRequirement Requirement { get; }
public HttpContext HttpContext { get; }
public bool Success { get; }
public AuthorizationFilterHandle(
AuthorizationHandlerContext context,
PolicyRequirement requirement,
HttpContext httpContext)
{
Context = context;
Requirement = requirement;
HttpContext = httpContext;
}
}

View File

@@ -84,6 +84,10 @@ namespace BTCPayServer.Client
{
return policy.StartsWith("btcpay.server", StringComparison.OrdinalIgnoreCase);
}
public static bool IsPluginPolicy(string policy)
{
return policy.StartsWith("btcpay.plugin", StringComparison.OrdinalIgnoreCase);
}
}
public class Permission
{

View File

@@ -132,13 +132,13 @@ namespace BTCPayServer.Controllers.Greenfield
_httpContextAccessor = httpContextAccessor;
_serviceProvider = serviceProvider;
}
private T GetController<T>() where T : ControllerBase
{
var authoverride = new AuthorizationService(new GreenfieldAuthorizationHandler(_httpContextAccessor,
_serviceProvider.GetService<UserManager<ApplicationUser>>(),
_serviceProvider.GetService<StoreRepository>()));
_serviceProvider.GetService<StoreRepository>(),
_serviceProvider.GetService<IPluginHookService>()));
var controller = _serviceProvider.GetService<T>();
controller.ControllerContext.HttpContext = _httpContextAccessor.HttpContext;
@@ -172,7 +172,6 @@ namespace BTCPayServer.Controllers.Greenfield
_greenfieldAuthorizationHandler = greenfieldAuthorizationHandler;
}
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource,
IEnumerable<IAuthorizationRequirement> requirements)
{

View File

@@ -1,8 +1,8 @@
using System;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
@@ -23,19 +23,22 @@ namespace BTCPayServer.Security
private readonly AppService _appService;
private readonly PaymentRequestRepository _paymentRequestRepository;
private readonly InvoiceRepository _invoiceRepository;
private readonly IPluginHookService _pluginHookService;
public CookieAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository,
AppService appService,
InvoiceRepository invoiceRepository,
PaymentRequestRepository paymentRequestRepository)
PaymentRequestRepository paymentRequestRepository,
IPluginHookService pluginHookService)
{
_httpContext = httpContextAccessor.HttpContext;
_userManager = userManager;
_appService = appService;
_storeRepository = storeRepository;
_invoiceRepository = invoiceRepository;
_pluginHookService = pluginHookService;
_paymentRequestRepository = paymentRequestRepository;
}
@@ -144,6 +147,14 @@ namespace BTCPayServer.Security
if (context.User != null)
success = true;
break;
default:
if (Policies.IsPluginPolicy(requirement.Policy))
{
var handle = (AuthorizationFilterHandle)await _pluginHookService.ApplyFilter("handle-authorization-requirement",
new AuthorizationFilterHandle(context, requirement, _httpContext));
success = handle.Success;
}
break;
}
if (success)
@@ -151,7 +162,6 @@ namespace BTCPayServer.Security
context.Succeed(requirement);
if (!explicitResource)
{
if (store != null)
{
_httpContext.SetStoreData(store);

View File

@@ -1,17 +1,13 @@
using System.Buffers;
using System.Collections.Generic;
using System.Globalization;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Newtonsoft.Json;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Security.Greenfield
@@ -21,14 +17,17 @@ namespace BTCPayServer.Security.Greenfield
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
private readonly IPluginHookService _pluginHookService;
public LocalGreenfieldAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository)
StoreRepository storeRepository,
IPluginHookService pluginHookService)
{
_httpContextAccessor = httpContextAccessor;
_userManager = userManager;
_storeRepository = storeRepository;
_pluginHookService = pluginHookService;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement)
{
@@ -38,7 +37,8 @@ namespace BTCPayServer.Security.Greenfield
var newUser = new ClaimsPrincipal(new ClaimsIdentity(context.User.Claims,
$"{GreenfieldConstants.AuthenticationType}"));
var newContext = new AuthorizationHandlerContext(context.Requirements, newUser, null);
return new GreenfieldAuthorizationHandler(_httpContextAccessor, _userManager, _storeRepository).HandleAsync(newContext);
return new GreenfieldAuthorizationHandler(
_httpContextAccessor, _userManager, _storeRepository, _pluginHookService).HandleAsync(newContext);
}
var succeed = context.User.Identity.AuthenticationType == $"Local{GreenfieldConstants.AuthenticationType}";
@@ -50,21 +50,23 @@ namespace BTCPayServer.Security.Greenfield
return Task.CompletedTask;
}
}
public class GreenfieldAuthorizationHandler : AuthorizationHandler<PolicyRequirement>
{
private readonly HttpContext _HttpContext;
private readonly HttpContext _httpContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
private readonly IPluginHookService _pluginHookService;
public GreenfieldAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository)
StoreRepository storeRepository,
IPluginHookService pluginHookService)
{
_HttpContext = httpContextAccessor.HttpContext;
_httpContext = httpContextAccessor.HttpContext;
_userManager = userManager;
_storeRepository = storeRepository;
_pluginHookService = pluginHookService;
}
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context,
@@ -85,7 +87,7 @@ namespace BTCPayServer.Security.Greenfield
switch (policy)
{
case { } when Policies.IsStorePolicy(policy):
var storeId = _HttpContext.GetImplicitStoreId();
var storeId = _httpContext.GetImplicitStoreId();
// Specific store action
if (storeId != null)
{
@@ -102,7 +104,7 @@ namespace BTCPayServer.Security.Greenfield
break;
}
success = true;
_HttpContext.SetStoreData(store);
_httpContext.SetStoreData(store);
}
}
else
@@ -118,7 +120,7 @@ namespace BTCPayServer.Security.Greenfield
}
if (!requiredUnscoped && permissionedStores.Count is 0)
break;
_HttpContext.SetStoresData(permissionedStores.ToArray());
_httpContext.SetStoresData(permissionedStores.ToArray());
success = true;
}
break;
@@ -133,6 +135,11 @@ namespace BTCPayServer.Security.Greenfield
success = true;
}
break;
case { } when Policies.IsPluginPolicy(requirement.Policy):
var handle = (AuthorizationFilterHandle)await _pluginHookService.ApplyFilter("handle-authorization-requirement",
new AuthorizationFilterHandle(context, requirement, _httpContext));
success = handle.Success;
break;
case Policies.CanManageNotificationsForUser:
case Policies.CanViewNotificationsForUser:
case Policies.CanModifyProfile:
@@ -147,7 +154,7 @@ namespace BTCPayServer.Security.Greenfield
{
context.Succeed(requirement);
}
_HttpContext.Items[RequestedPermissionKey] = policy;
_httpContext.Items[RequestedPermissionKey] = policy;
}
public const string RequestedPermissionKey = nameof(RequestedPermissionKey);
}