From fae1dc8dbb05e3ccfa55cc8ba110b8ea45385cda Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Mon, 20 Mar 2023 02:46:46 +0100 Subject: [PATCH] Adapt cookie auth to work with same API permission system (#4595) * Adapt cookie auth to work with same API permission system * Handle unscoped store permission case * Do not consider Unscoped as a valid policy * Add tests * Refactor permissions scopes --------- Co-authored-by: Dennis Reimann Co-authored-by: nicolas.dorier --- BTCPayServer.Client/Permissions.cs | 79 +++++++++----- BTCPayServer.Data/Data/StoreData.cs | 1 + BTCPayServer.Tests/SeleniumTests.cs | 101 ++++++++++++++++++ BTCPayServer.Tests/TestAccount.cs | 5 +- BTCPayServer/BTCPayServer.csproj | 1 + .../Controllers/UIAccountController.cs | 20 ++++ BTCPayServer/Controllers/UIHomeController.cs | 2 +- .../Controllers/UIInvoiceController.UI.cs | 13 ++- .../Controllers/UIInvoiceController.cs | 6 +- BTCPayServer/Data/StoreDataExtensions.cs | 29 +++++ .../CheatPermissionsViewModel.cs | 10 ++ .../Security/CookieAuthorizationHandler.cs | 67 ++++++------ .../Security/GreenField/APIKeyExtensions.cs | 6 -- .../GreenFieldAuthorizationHandler.cs | 15 ++- .../Views/UIAccount/CheatPermissions.cshtml | 22 ++++ BTCPayServer/Views/UIInvoice/Invoice.cshtml | 6 +- 16 files changed, 298 insertions(+), 85 deletions(-) create mode 100644 BTCPayServer/Models/AccountViewModels/CheatPermissionsViewModel.cs create mode 100644 BTCPayServer/Views/UIAccount/CheatPermissions.cshtml diff --git a/BTCPayServer.Client/Permissions.cs b/BTCPayServer.Client/Permissions.cs index 8fceecf6a..42f502a71 100644 --- a/BTCPayServer.Client/Permissions.cs +++ b/BTCPayServer.Client/Permissions.cs @@ -98,6 +98,37 @@ namespace BTCPayServer.Client { return policy.StartsWith("btcpay.plugin", StringComparison.OrdinalIgnoreCase); } + public static bool IsUserPolicy(string policy) + { + return policy.StartsWith("btcpay.user", StringComparison.OrdinalIgnoreCase); + } + } + + public class PermissionSet + { + public PermissionSet() : this(Array.Empty()) + { + + } + public PermissionSet(Permission[] permissions) + { + Permissions = permissions; + } + + public Permission[] Permissions { get; } + + public bool Contains(Permission requestedPermission) + { + return Permissions.Any(p => p.Contains(requestedPermission)); + } + public bool Contains(string permission, string store) + { + if (permission is null) + throw new ArgumentNullException(nameof(permission)); + if (store is null) + throw new ArgumentNullException(nameof(store)); + return Contains(Permission.Create(permission, store)); + } } public class Permission { @@ -105,7 +136,7 @@ namespace BTCPayServer.Client { Init(); } - + public static Permission Create(string policy, string scope = null) { if (TryCreatePermission(policy, scope, out var r)) @@ -121,7 +152,7 @@ namespace BTCPayServer.Client policy = policy.Trim().ToLowerInvariant(); if (!Policies.IsValidPolicy(policy)) return false; - if (scope != null && !Policies.IsStorePolicy(policy)) + if (!string.IsNullOrEmpty(scope) && !Policies.IsStorePolicy(policy)) return false; permission = new Permission(policy, scope); return true; @@ -174,7 +205,7 @@ namespace BTCPayServer.Client } if (!Policies.IsStorePolicy(subpermission.Policy)) return true; - return Scope == null || subpermission.Scope == this.Scope; + return Scope == null || subpermission.Scope == Scope; } public static IEnumerable ToPermissions(string[] permissions) @@ -199,7 +230,8 @@ namespace BTCPayServer.Client return true; if (policy == subpolicy) return true; - if (!PolicyMap.TryGetValue(policy, out var subPolicies)) return false; + if (!PolicyMap.TryGetValue(policy, out var subPolicies)) + return false; return subPolicies.Contains(subpolicy) || subPolicies.Any(s => ContainsPolicy(s, subpolicy)); } @@ -213,23 +245,23 @@ namespace BTCPayServer.Client Policies.CanModifyInvoices, Policies.CanViewStoreSettings, Policies.CanModifyStoreWebhooks, - Policies.CanModifyPaymentRequests); + Policies.CanModifyPaymentRequests, + Policies.CanUseLightningNodeInStore); PolicyHasChild(Policies.CanManageUsers, Policies.CanCreateUser); - PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments ); - PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments ); - PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests ); - PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile ); - PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore ); - PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser ); + PolicyHasChild(Policies.CanManagePullPayments, Policies.CanCreatePullPayments); + PolicyHasChild(Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments); + PolicyHasChild(Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests); + PolicyHasChild(Policies.CanModifyProfile, Policies.CanViewProfile); + PolicyHasChild(Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore); + PolicyHasChild(Policies.CanManageNotificationsForUser, Policies.CanViewNotificationsForUser); PolicyHasChild(Policies.CanModifyServerSettings, Policies.CanUseInternalLightningNode, Policies.CanManageUsers); - PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode,Policies.CanViewLightningInvoiceInternalNode ); - PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts ); - PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice ); - PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests ); - + PolicyHasChild(Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode); + PolicyHasChild(Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts); + PolicyHasChild(Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore); + PolicyHasChild(Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests); } private static void PolicyHasChild(string policy, params string[] subPolicies) @@ -243,33 +275,26 @@ namespace BTCPayServer.Client } else { - PolicyMap.Add(policy,subPolicies.ToHashSet()); + PolicyMap.Add(policy, subPolicies.ToHashSet()); } } - public string Scope { get; } public string Policy { get; } public override string ToString() { - if (Scope != null) - { - return $"{Policy}:{Scope}"; - } - return Policy; + return Scope != null ? $"{Policy}:{Scope}" : Policy; } public override bool Equals(object obj) { Permission item = obj as Permission; - if (item == null) - return false; - return ToString().Equals(item.ToString()); + return item != null && ToString().Equals(item.ToString()); } public static bool operator ==(Permission a, Permission b) { - if (System.Object.ReferenceEquals(a, b)) + if (ReferenceEquals(a, b)) return true; if (((object)a == null) || ((object)b == null)) return false; diff --git a/BTCPayServer.Data/Data/StoreData.cs b/BTCPayServer.Data/Data/StoreData.cs index 3ad1dd22c..cee3fe488 100644 --- a/BTCPayServer.Data/Data/StoreData.cs +++ b/BTCPayServer.Data/Data/StoreData.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Text; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 1ce647e25..b76a55924 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -4,11 +4,13 @@ using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Reflection.Metadata; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Lightning; @@ -831,6 +833,105 @@ namespace BTCPayServer.Tests AssertUrlHasPairingCode(s); } + [Fact(Timeout = TestTimeout)] + public async Task CookieReflectProperPermissions() + { + using var s = CreateSeleniumTester(); + await s.StartAsync(); + var alice = s.Server.NewAccount(); + alice.Register(false); + await alice.CreateStoreAsync(); + var bob = s.Server.NewAccount(); + await bob.CreateStoreAsync(); + await bob.AddGuest(alice.UserId); + + s.GoToLogin(); + s.LogIn(alice.Email, alice.Password); + s.GoToUrl($"/cheat/permissions/stores/{bob.StoreId}"); + var pageSource = s.Driver.PageSource; + AssertPermissions(pageSource, true, + new[] + { + Policies.CanViewInvoices, + Policies.CanModifyInvoices, + Policies.CanViewPaymentRequests, + Policies.CanViewStoreSettings, + Policies.CanModifyStoreSettingsUnscoped, + Policies.CanDeleteUser + }); + AssertPermissions(pageSource, false, + new[] + { + Policies.CanModifyStoreSettings, + Policies.CanCreateNonApprovedPullPayments, + Policies.CanCreatePullPayments, + Policies.CanManagePullPayments, + Policies.CanModifyServerSettings + }); + + s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}"); + pageSource = s.Driver.PageSource; + + AssertPermissions(pageSource, true, + new[] + { + Policies.CanViewInvoices, + Policies.CanModifyInvoices, + Policies.CanViewPaymentRequests, + Policies.CanViewStoreSettings, + Policies.CanModifyStoreSettingsUnscoped, + Policies.CanDeleteUser, + Policies.CanModifyStoreSettings, + Policies.CanCreateNonApprovedPullPayments, + Policies.CanCreatePullPayments, + Policies.CanManagePullPayments + }); + AssertPermissions(pageSource, false, + new[] + { + Policies.CanModifyServerSettings + }); + + await alice.MakeAdmin(); + s.Logout(); + s.GoToLogin(); + s.LogIn(alice.Email, alice.Password); + s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}"); + pageSource = s.Driver.PageSource; + + AssertPermissions(pageSource, true, + new[] + { + Policies.CanViewInvoices, + Policies.CanModifyInvoices, + Policies.CanViewPaymentRequests, + Policies.CanViewStoreSettings, + Policies.CanModifyStoreSettingsUnscoped, + Policies.CanDeleteUser, + Policies.CanModifyStoreSettings, + Policies.CanCreateNonApprovedPullPayments, + Policies.CanCreatePullPayments, + Policies.CanManagePullPayments, + Policies.CanModifyServerSettings, + Policies.CanCreateUser, + Policies.CanManageUsers + }); + } + + void AssertPermissions(string source, bool expected, string[] permissions) + { + if (expected) + { + foreach (var p in permissions) + Assert.Contains(p + "<", source); + } + else + { + foreach (var p in permissions) + Assert.DoesNotContain(p + "<", source); + } + } + [Fact(Timeout = TestTimeout)] public async Task CanCreateAppPoS() { diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 02fb42c04..74316fb81 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -220,8 +220,8 @@ namespace BTCPayServer.Tests RegisterDetails = new RegisterViewModel() { Email = Utils.GenerateEmail(), - ConfirmPassword = "Kitten0@", - Password = "Kitten0@", + ConfirmPassword = Password, + Password = Password, IsAdmin = isAdmin }; await account.Register(RegisterDetails); @@ -240,6 +240,7 @@ namespace BTCPayServer.Tests Email = RegisterDetails.Email; IsAdmin = account.RegisteredAdmin; } + public string Password { get; set; } = "Kitten0@"; public RegisterViewModel RegisterDetails { get; set; } diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 8369ca56c..d2b0d6774 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -139,6 +139,7 @@ + PreserveNewest $(IncludeRazorContentInPack) diff --git a/BTCPayServer/Controllers/UIAccountController.cs b/BTCPayServer/Controllers/UIAccountController.cs index 0e8e7c334..78c5b9d1e 100644 --- a/BTCPayServer/Controllers/UIAccountController.cs +++ b/BTCPayServer/Controllers/UIAccountController.cs @@ -7,10 +7,12 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Events; using BTCPayServer.Fido2; using BTCPayServer.Fido2.Models; +using BTCPayServer.Filters; using BTCPayServer.Logging; using BTCPayServer.Models.AccountViewModels; using BTCPayServer.Services; @@ -83,6 +85,24 @@ namespace BTCPayServer.Controllers get; set; } + [HttpGet("/cheat/permissions")] + [HttpGet("/cheat/permissions/stores/{storeId}")] + [CheatModeRoute] + public async Task CheatPermissions([FromServices]IAuthorizationService authorizationService, string storeId = null) + { + var vm = new CheatPermissionsViewModel(); + vm.StoreId = storeId; + var results = new System.Collections.Generic.List<(string, Task)>(); + foreach (var p in Policies.AllPolicies.Concat(new[] { Policies.CanModifyStoreSettingsUnscoped })) + { + results.Add((p, authorizationService.AuthorizeAsync(User, storeId, p))); + } + await Task.WhenAll(results.Select(r => r.Item2)); + results = results.OrderBy(r => r.Item1).ToList(); + vm.Permissions = results.Select(r => (r.Item1, r.Item2.Result)).ToArray(); + return View(vm); + } + [HttpGet("/login")] [AllowAnonymous] public async Task Login(string returnUrl = null, string email = null) diff --git a/BTCPayServer/Controllers/UIHomeController.cs b/BTCPayServer/Controllers/UIHomeController.cs index 06cfdcc67..fe56502ba 100644 --- a/BTCPayServer/Controllers/UIHomeController.cs +++ b/BTCPayServer/Controllers/UIHomeController.cs @@ -222,7 +222,7 @@ namespace BTCPayServer.Controllers public RedirectToActionResult RedirectToStore(StoreData store) { - return store.Role == StoreRoles.Owner + return store.HasPermission(Policies.CanModifyStoreSettings) ? RedirectToAction("Dashboard", "UIStores", new { storeId = store.Id }) : RedirectToAction("ListInvoices", "UIInvoice", new { storeId = store.Id }); } diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index ca7a61f08..87e87e5ab 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -254,7 +254,7 @@ namespace BTCPayServer.Controllers } [HttpGet("invoices/{invoiceId}/refund")] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Refund([FromServices] IEnumerable payoutHandlers, string invoiceId, CancellationToken cancellationToken) { await using var ctx = _dbContextFactory.CreateContext(); @@ -317,7 +317,7 @@ namespace BTCPayServer.Controllers } [HttpPost("invoices/{invoiceId}/refund")] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanCreateNonApprovedPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Refund(string invoiceId, RefundModel model, CancellationToken cancellationToken) { await using var ctx = _dbContextFactory.CreateContext(); @@ -385,18 +385,21 @@ namespace BTCPayServer.Controllers StoreId = invoice.StoreId, BOLT11Expiration = store.GetStoreBlob().RefundBOLT11Expiration }; + var authorizedForAutoApprove = (await + _authorizationService.AuthorizeAsync(User, invoice.StoreId, Policies.CanCreatePullPayments)) + .Succeeded; switch (model.SelectedRefundOption) { case "RateThen": createPullPayment.Currency = paymentMethodId.CryptoCode; createPullPayment.Amount = model.CryptoAmountThen; - createPullPayment.AutoApproveClaims = true; + createPullPayment.AutoApproveClaims = authorizedForAutoApprove; break; case "CurrentRate": createPullPayment.Currency = paymentMethodId.CryptoCode; createPullPayment.Amount = model.CryptoAmountNow; - createPullPayment.AutoApproveClaims = true; + createPullPayment.AutoApproveClaims = authorizedForAutoApprove; break; case "Fiat": @@ -441,7 +444,7 @@ namespace BTCPayServer.Controllers createPullPayment.Currency = model.CustomCurrency; createPullPayment.Amount = model.CustomAmount; - createPullPayment.AutoApproveClaims = paymentMethodId.CryptoCode == model.CustomCurrency; + createPullPayment.AutoApproveClaims = authorizedForAutoApprove && paymentMethodId.CryptoCode == model.CustomCurrency; break; default: diff --git a/BTCPayServer/Controllers/UIInvoiceController.cs b/BTCPayServer/Controllers/UIInvoiceController.cs index caaee46f6..d9d0b12db 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.cs @@ -24,6 +24,7 @@ using BTCPayServer.Services.PaymentRequests; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Validation; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -56,6 +57,7 @@ namespace BTCPayServer.Controllers private readonly UIWalletsController _walletsController; private readonly InvoiceActivator _invoiceActivator; private readonly LinkGenerator _linkGenerator; + private readonly IAuthorizationService _authorizationService; public WebhookSender WebhookNotificationManager { get; } @@ -78,7 +80,8 @@ namespace BTCPayServer.Controllers ExplorerClientProvider explorerClients, UIWalletsController walletsController, InvoiceActivator invoiceActivator, - LinkGenerator linkGenerator) + LinkGenerator linkGenerator, + IAuthorizationService authorizationService) { _displayFormatter = displayFormatter; _CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable)); @@ -98,6 +101,7 @@ namespace BTCPayServer.Controllers _walletsController = walletsController; _invoiceActivator = invoiceActivator; _linkGenerator = linkGenerator; + _authorizationService = authorizationService; } diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index f8be733d9..4662478d9 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -1,8 +1,10 @@ #nullable enable using System; using System.Collections.Generic; +using System.Data; using System.Linq; using System.Text; +using BTCPayServer.Client; using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; using BTCPayServer.Services.Rates; @@ -14,6 +16,33 @@ namespace BTCPayServer.Data { public static class StoreDataExtensions { + public static PermissionSet GetPermissionSet(this StoreData store) + { + ArgumentNullException.ThrowIfNull(store); + if (store.Role is null) + return new PermissionSet(); + return new PermissionSet(store.Role == StoreRoles.Owner + ? new[] + { + Permission.Create(Policies.CanModifyStoreSettings, store.Id), + Permission.Create(Policies.CanTradeCustodianAccount, store.Id), + Permission.Create(Policies.CanWithdrawFromCustodianAccounts, store.Id), + Permission.Create(Policies.CanDepositToCustodianAccounts, store.Id) + } + : new[] + { + Permission.Create(Policies.CanViewStoreSettings, store.Id), + Permission.Create(Policies.CanModifyInvoices, store.Id), + Permission.Create(Policies.CanViewCustodianAccounts, store.Id), + Permission.Create(Policies.CanDepositToCustodianAccounts, store.Id) + }); + } + public static bool HasPermission(this StoreData store, string permission) + { + ArgumentNullException.ThrowIfNull(store); + return store.GetPermissionSet().Contains(permission, store.Id); + } + #pragma warning disable CS0618 public static PaymentMethodId? GetDefaultPaymentId(this StoreData storeData) { diff --git a/BTCPayServer/Models/AccountViewModels/CheatPermissionsViewModel.cs b/BTCPayServer/Models/AccountViewModels/CheatPermissionsViewModel.cs new file mode 100644 index 000000000..672a097f2 --- /dev/null +++ b/BTCPayServer/Models/AccountViewModels/CheatPermissionsViewModel.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Authorization; + +namespace BTCPayServer.Models.AccountViewModels +{ + public class CheatPermissionsViewModel + { + public string StoreId { get; internal set; } + public (string, AuthorizationResult Result)[] Permissions { get; set; } + } +} diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs index 8f0859bc7..aab119a28 100644 --- a/BTCPayServer/Security/CookieAuthorizationHandler.cs +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; @@ -112,49 +113,49 @@ namespace BTCPayServer.Security } // Fall back to user prefs cookie - if (storeId == null) + storeId ??= _httpContext.GetUserPrefsCookie()?.CurrentStoreId; + + var policy = requirement.Policy; + bool requiredUnscoped = false; + if (policy.EndsWith(':')) { - storeId = _httpContext.GetUserPrefsCookie()?.CurrentStoreId; + policy = policy.Substring(0, policy.Length - 1); + requiredUnscoped = true; + storeId = null; } - if (string.IsNullOrEmpty(storeId)) - storeId = null; - if (storeId != null) + if (!string.IsNullOrEmpty(storeId)) { store = await _storeRepository.FindStore(storeId, userId); } - switch (requirement.Policy) + if (Policies.IsServerPolicy(policy) && isAdmin) { - case Policies.CanModifyServerSettings: - if (isAdmin) - success = true; - break; - case Policies.CanModifyStoreSettings: - if (store != null && (store.Role == StoreRoles.Owner)) - success = true; - break; - case Policies.CanViewInvoices: - case Policies.CanViewStoreSettings: - case Policies.CanCreateInvoice: - if (store != null) - success = true; - break; - case Policies.CanViewProfile: - case Policies.CanViewNotificationsForUser: - case Policies.CanManageNotificationsForUser: - case Policies.CanModifyStoreSettingsUnscoped: - if (context.User != null) - success = true; - break; - default: - if (Policies.IsPluginPolicy(requirement.Policy)) + success = true; + } + else if (Policies.IsUserPolicy(policy) && userId is not null) + { + success = true; + } + else if (Policies.IsStorePolicy(policy)) + { + if (store is not null) + { + if (store.HasPermission(policy)) { - var handle = (AuthorizationFilterHandle)await _pluginHookService.ApplyFilter("handle-authorization-requirement", - new AuthorizationFilterHandle(context, requirement, _httpContext)); - success = handle.Success; + success = true; } - break; + } + else if (requiredUnscoped) + { + success = true; + } + } + else if (Policies.IsPluginPolicy(requirement.Policy)) + { + var handle = (AuthorizationFilterHandle)await _pluginHookService.ApplyFilter("handle-authorization-requirement", + new AuthorizationFilterHandle(context, requirement, _httpContext)); + success = handle.Success; } if (success) diff --git a/BTCPayServer/Security/GreenField/APIKeyExtensions.cs b/BTCPayServer/Security/GreenField/APIKeyExtensions.cs index 52f4d8ff7..c6be705b8 100644 --- a/BTCPayServer/Security/GreenField/APIKeyExtensions.cs +++ b/BTCPayServer/Security/GreenField/APIKeyExtensions.cs @@ -48,18 +48,12 @@ namespace BTCPayServer.Security.Greenfield .Select(claim => claim.Value).ToArray(); } public static bool HasPermission(this AuthorizationHandlerContext context, Permission permission) - { - return HasPermission(context, permission, false); - } - public static bool HasPermission(this AuthorizationHandlerContext context, Permission permission, bool requireUnscoped) { foreach (var claim in context.User.Claims.Where(c => c.Type.Equals(GreenfieldConstants.ClaimTypes.Permission, StringComparison.InvariantCultureIgnoreCase))) { if (Permission.TryParse(claim.Value, out var claimPermission)) { - if (requireUnscoped && claimPermission.Scope is not null) - continue; if (claimPermission.Contains(permission)) { return true; diff --git a/BTCPayServer/Security/GreenField/GreenFieldAuthorizationHandler.cs b/BTCPayServer/Security/GreenField/GreenFieldAuthorizationHandler.cs index 16860ffa9..3fdad0d68 100644 --- a/BTCPayServer/Security/GreenField/GreenFieldAuthorizationHandler.cs +++ b/BTCPayServer/Security/GreenField/GreenFieldAuthorizationHandler.cs @@ -87,22 +87,19 @@ namespace BTCPayServer.Security.Greenfield switch (policy) { case { } when Policies.IsStorePolicy(policy): - var storeId = _httpContext.GetImplicitStoreId(); + var storeId = requiredUnscoped ? null : _httpContext.GetImplicitStoreId(); // Specific store action if (storeId != null) { - if (context.HasPermission(Permission.Create(policy, storeId), requiredUnscoped)) + if (context.HasPermission(Permission.Create(policy, storeId))) { if (string.IsNullOrEmpty(userid)) break; var store = await _storeRepository.FindStore(storeId, userid); if (store == null) break; - if (Policies.IsStoreModifyPolicy(policy) || policy == Policies.CanUseLightningNodeInStore) - { - if (store.Role != StoreRoles.Owner) - break; - } + if (!store.HasPermission(policy)) + break; success = true; _httpContext.SetStoreData(store); } @@ -115,7 +112,7 @@ namespace BTCPayServer.Security.Greenfield List permissionedStores = new List(); foreach (var store in stores) { - if (context.HasPermission(Permission.Create(policy, store.Id), requiredUnscoped)) + if (context.HasPermission(Permission.Create(policy, store.Id))) permissionedStores.Add(store); } _httpContext.SetStoresData(permissionedStores.ToArray()); @@ -144,7 +141,7 @@ namespace BTCPayServer.Security.Greenfield case Policies.CanViewProfile: case Policies.CanDeleteUser: case Policies.Unrestricted: - success = context.HasPermission(Permission.Create(policy), requiredUnscoped); + success = context.HasPermission(Permission.Create(policy)); break; } diff --git a/BTCPayServer/Views/UIAccount/CheatPermissions.cshtml b/BTCPayServer/Views/UIAccount/CheatPermissions.cshtml new file mode 100644 index 000000000..d38caee5c --- /dev/null +++ b/BTCPayServer/Views/UIAccount/CheatPermissions.cshtml @@ -0,0 +1,22 @@ +@model CheatPermissionsViewModel + +@{ + ViewData["Title"] = "Permissions"; + Layout = "_LayoutSignedOut"; +} + +@if (Model.StoreId is not null) +{ +

Store: @Model.StoreId

+} +else +{ +

No scope

+} + +
    +@foreach (var p in Model.Permissions.Where(o => o.Result.Succeeded)) +{ +
  • @p.Item1
  • +} +
diff --git a/BTCPayServer/Views/UIInvoice/Invoice.cshtml b/BTCPayServer/Views/UIInvoice/Invoice.cshtml index 1122bf081..334b9b5ba 100644 --- a/BTCPayServer/Views/UIInvoice/Invoice.cshtml +++ b/BTCPayServer/Views/UIInvoice/Invoice.cshtml @@ -1,3 +1,7 @@ +@using BTCPayServer.Client.Models +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using BTCPayServer.Client +@using BTCPayServer.Abstractions.TagHelpers @model InvoiceDetailsModel @{ ViewData["Title"] = $"Invoice {Model.Id}"; @@ -102,7 +106,7 @@ } @if (Model.CanRefund) { - Issue Refund + Issue Refund } else {