mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-03-04 09:58:13 +01:00
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 <mail@dennisreimann.de> Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
This commit is contained in:
parent
6f2b673021
commit
fae1dc8dbb
16 changed files with 298 additions and 85 deletions
|
@ -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<Permission>())
|
||||
{
|
||||
|
||||
}
|
||||
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<Permission> 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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -139,6 +139,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Watch Include="Views\**\*.*"></Watch>
|
||||
<Watch Remove="Views\UIAccount\CheatPermissions.cshtml" />
|
||||
<Content Update="Views\UIApps\_ViewImports.cshtml">
|
||||
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
|
|
|
@ -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<IActionResult> CheatPermissions([FromServices]IAuthorizationService authorizationService, string storeId = null)
|
||||
{
|
||||
var vm = new CheatPermissionsViewModel();
|
||||
vm.StoreId = storeId;
|
||||
var results = new System.Collections.Generic.List<(string, Task<AuthorizationResult>)>();
|
||||
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<IActionResult> Login(string returnUrl = null, string email = null)
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
|
@ -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<IActionResult> Refund([FromServices] IEnumerable<IPayoutHandler> 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<IActionResult> 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:
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<StoreData> permissionedStores = new List<StoreData>();
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
22
BTCPayServer/Views/UIAccount/CheatPermissions.cshtml
Normal file
22
BTCPayServer/Views/UIAccount/CheatPermissions.cshtml
Normal file
|
@ -0,0 +1,22 @@
|
|||
@model CheatPermissionsViewModel
|
||||
|
||||
@{
|
||||
ViewData["Title"] = "Permissions";
|
||||
Layout = "_LayoutSignedOut";
|
||||
}
|
||||
|
||||
@if (Model.StoreId is not null)
|
||||
{
|
||||
<h1>Store: @Model.StoreId</h1>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h1>No scope</h1>
|
||||
}
|
||||
|
||||
<ul>
|
||||
@foreach (var p in Model.Permissions.Where(o => o.Result.Succeeded))
|
||||
{
|
||||
<li>@p.Item1</li>
|
||||
}
|
||||
</ul>
|
|
@ -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)
|
||||
{
|
||||
<a asp-action="Refund" asp-route-invoiceId="@Model.Id" id="IssueRefund" class="btn btn-primary text-nowrap" data-bs-toggle="modal" data-bs-target="#RefundModal">Issue Refund</a>
|
||||
<a asp-action="Refund" asp-route-invoiceId="@Model.Id" id="IssueRefund" class="btn btn-primary text-nowrap" data-bs-toggle="modal" data-bs-target="#RefundModal" permission="@Policies.CanCreateNonApprovedPullPayments">Issue Refund</a>
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue