enhance fine grain permissions (#5502)

Co-authored-by: d11n <mail@dennisreimann.de>
This commit is contained in:
Andrew Camilleri 2023-12-01 09:12:02 +01:00 committed by GitHub
parent 2c94a87be4
commit 605741182d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 167 additions and 71 deletions

View file

@ -2,12 +2,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
namespace BTCPayServer.Abstractions.TagHelpers;
[HtmlTargetElement(Attributes = "[permission]")]
[HtmlTargetElement(Attributes = "[not-permission]" )]
[HtmlTargetElement(Attributes = "[not-permission]")]
public class PermissionTagHelper : TagHelper
{
private readonly IAuthorizationService _authorizationService;
@ -22,29 +23,72 @@ public class PermissionTagHelper : TagHelper
public string Permission { get; set; }
public string NotPermission { get; set; }
public string PermissionResource { get; set; }
public bool AndMode { get; set; } = false;
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.IsNullOrEmpty(Permission) && string.IsNullOrEmpty(NotPermission))
var permissions = Permission?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
var notPermissions = NotPermission?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
if (!permissions.Any() && !notPermissions.Any())
return;
if (_httpContextAccessor.HttpContext is null)
return;
var expectedResult = !string.IsNullOrEmpty(Permission);
var key = $"{Permission??NotPermission}_{PermissionResource}";
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var o) ||
o is not AuthorizationResult res)
bool shouldRender = true; // Assume tag should be rendered unless a check fails
// Process 'Permission' - User must have these permissions
if (permissions.Any())
{
res = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User,
PermissionResource,
Permission);
_httpContextAccessor.HttpContext.Items.Add(key, res);
bool finalResult = AndMode;
foreach (var perm in permissions)
{
var key = $"{perm}_{PermissionResource}";
AuthorizationResult res = await GetOrAddAuthorizationResult(key, perm);
if (AndMode)
finalResult &= res.Succeeded;
else
finalResult |= res.Succeeded;
if (!AndMode && finalResult) break;
}
shouldRender = finalResult;
}
if (expectedResult != res.Succeeded)
// Process 'NotPermission' - User must not have these permissions
if (shouldRender && notPermissions.Any())
{
foreach (var notPerm in notPermissions)
{
var key = $"{notPerm}_{PermissionResource}";
AuthorizationResult res = await GetOrAddAuthorizationResult(key, notPerm);
if (res.Succeeded) // If the user has a 'NotPermission', they should not see the tag
{
shouldRender = false;
break;
}
}
}
if (!shouldRender)
{
output.SuppressOutput();
}
}
private async Task<AuthorizationResult> GetOrAddAuthorizationResult(string key, string permission)
{
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var cachedResult))
{
var res = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User,
PermissionResource, permission);
_httpContextAccessor.HttpContext.Items[key] = res;
return res;
}
return cachedResult as AuthorizationResult;
}
}

View file

@ -19,6 +19,7 @@ namespace BTCPayServer.Client
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewReports = "btcpay.store.canviewreports";
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices";
@ -34,7 +35,10 @@ namespace BTCPayServer.Client
public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanArchivePullPayments = "btcpay.store.canarchivepullpayments";
public const string CanManagePayouts = "btcpay.store.canmanagepayouts";
public const string CanViewPayouts = "btcpay.store.canviewpayouts";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanViewPullPayments = "btcpay.store.canviewpullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
@ -53,6 +57,7 @@ namespace BTCPayServer.Client
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
yield return CanViewStoreSettings;
yield return CanViewReports;
yield return CanViewPaymentRequests;
yield return CanModifyPaymentRequests;
yield return CanModifyProfile;
@ -72,6 +77,7 @@ namespace BTCPayServer.Client
yield return CanManagePullPayments;
yield return CanArchivePullPayments;
yield return CanCreatePullPayments;
yield return CanViewPullPayments;
yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts;
@ -79,6 +85,8 @@ namespace BTCPayServer.Client
yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount;
yield return CanManageUsers;
yield return CanManagePayouts;
yield return CanViewPayouts;
}
}
public static bool IsValidPolicy(string policy)
@ -252,11 +260,13 @@ namespace BTCPayServer.Client
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanManagePayouts,
Policies.CanUseLightningNodeInStore);
PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments, Policies.CanArchivePullPayments);
PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(policyMap, Policies.CanCreateNonApprovedPullPayments, Policies.CanViewPullPayments);
PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile);
PolicyHasChild(policyMap,Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
@ -267,7 +277,8 @@ namespace BTCPayServer.Client
PolicyHasChild(policyMap, Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
PolicyHasChild(policyMap, Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
PolicyHasChild(policyMap, Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(policyMap, Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap, Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests, Policies.CanViewReports, Policies.CanViewPullPayments, Policies.CanViewPayouts);
PolicyHasChild(policyMap, Policies.CanManagePayouts, Policies.CanViewPayouts);
var missingPolicies = Policies.AllPolicies.ToHashSet();
//recurse through the tree to see which policies are not included in the tree

View file

@ -938,8 +938,9 @@ namespace BTCPayServer.Tests
Policies.CanModifyServerSettings
});
s.GoToUrl("/logout");
await alice.MakeAdmin();
s.Logout();
s.GoToLogin();
s.LogIn(alice.Email, alice.Password);
s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}");

View file

@ -135,25 +135,25 @@
<span>Invoices</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewInvoices">
<li class="nav-item" permission="@Policies.CanViewReports">
<a asp-area="" asp-controller="UIReports" asp-action="StoreReports" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Reporting)" id="SectionNav-Reporting">
<vc:icon symbol="invoice" />
<span>Reporting</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPaymentRequests">
<a asp-area="" asp-controller="UIPaymentRequest" asp-action="GetPaymentRequests" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActiveCategory(typeof(PaymentRequestsNavPages))" id="StoreNav-PaymentRequests">
<vc:icon symbol="payment-requests"/>
<span>Requests</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPullPayments">
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" id="StoreNav-PullPayments">
<vc:icon symbol="pull-payments"/>
<span>Pull Payments</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPayouts">
<a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId=""

View file

@ -31,8 +31,7 @@
}
else
{
<a asp-controller="UIStores" asp-action="Dashboard" permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIInvoice" asp-action="ListInvoices" not-permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
}
@if (Model.Options.Any() || Model.ArchivedCount > 0)
{
@ -54,21 +53,14 @@ else
@foreach (var option in Model.Options)
{
<li>
@if (option.IsOwner)
{
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
}
else
{
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
}
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
</li>
}
@if (Model.Options.Any())
{
<li><hr class="dropdown-divider"></li>
}
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.IsActivePage(StoreNavPages.Create)" id="StoreSelectorCreate">Create Store</a></li>
<li ><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.IsActivePage(StoreNavPages.Create)" id="StoreSelectorCreate">Create Store</a></li>
@if (Model.ArchivedCount > 0)
{
<li><hr class="dropdown-divider"></li>

View file

@ -48,7 +48,7 @@ namespace BTCPayServer.Components.StoreSelector
Value = store.Id,
Selected = store.Id == currentStore?.Id,
WalletId = walletId,
IsOwner = role != null && role.Permissions.Contains(Policies.CanModifyStoreSettings)
Store = store,
};
})
.OrderBy(s => s.Text)

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using BTCPayServer.Data;
namespace BTCPayServer.Components.StoreSelector
{
@ -14,9 +15,9 @@ namespace BTCPayServer.Components.StoreSelector
public class StoreSelectorOption
{
public bool Selected { get; set; }
public bool IsOwner { get; set; }
public string Text { get; set; }
public string Value { get; set; }
public WalletId WalletId { get; set; }
public StoreData Store { get; set; }
}
}

View file

@ -58,7 +58,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpGet("~/api/v1/stores/{storeId}/pull-payments")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> GetPullPayments(string storeId, bool includeArchived = false)
{
using var ctx = _dbContextFactory.CreateContext();
@ -456,7 +456,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/payouts")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanViewPayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> GetStorePayouts(string storeId, bool includeCancelled = false)
{
var payouts = await _pullPaymentService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
@ -471,7 +471,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpDelete("~/api/v1/stores/{storeId}/payouts/{payoutId}")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanManagePayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> CancelPayout(string storeId, string payoutId)
{
var res = await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(new[] { payoutId }, new[] { storeId }));
@ -479,7 +479,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpPost("~/api/v1/stores/{storeId}/payouts/{payoutId}")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanManagePayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> ApprovePayout(string storeId, string payoutId, ApprovePayoutRequest approvePayoutRequest, CancellationToken cancellationToken = default)
{
using var ctx = _dbContextFactory.CreateContext();
@ -533,7 +533,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpPost("~/api/v1/stores/{storeId}/payouts/{payoutId}/mark-paid")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanManagePayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> MarkPayoutPaid(string storeId, string payoutId, CancellationToken cancellationToken = default)
{
return await MarkPayout(storeId, payoutId, new Client.Models.MarkPayoutRequest()
@ -544,7 +544,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpPost("~/api/v1/stores/{storeId}/payouts/{payoutId}/mark")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanManagePayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> MarkPayout(string storeId, string payoutId, Client.Models.MarkPayoutRequest request)
{
request ??= new();
@ -571,7 +571,7 @@ namespace BTCPayServer.Controllers.Greenfield
}
[HttpGet("~/api/v1/stores/{storeId}/payouts/{payoutId}")]
[Authorize(Policy = Policies.CanManagePullPayments, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanViewPayouts, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public async Task<IActionResult> GetStorePayout(string storeId, string payoutId)
{
await using var ctx = _dbContextFactory.CreateContext();

View file

@ -32,7 +32,7 @@ public class GreenfieldReportsController : Controller
public ApplicationDbContextFactory DBContextFactory { get; }
public ReportService ReportService { get; }
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPost("~/api/v1/stores/{storeId}/reports")]
[NonAction] // Disabling this endpoint as we still need to figure out the request/response model
public async Task<IActionResult> StoreReports(string storeId, [FromBody] StoreReportRequest? vm = null, CancellationToken cancellationToken = default)

View file

@ -212,7 +212,6 @@ namespace BTCPayServer.Controllers
}
}
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded)
{

View file

@ -197,12 +197,14 @@ namespace BTCPayServer.Controllers
{
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
}
public RedirectToActionResult RedirectToStore(string userId, StoreData store)
public static RedirectToActionResult RedirectToStore(string userId, StoreData store)
{
return store.HasPermission(userId, Policies.CanModifyStoreSettings)
? RedirectToAction("Dashboard", "UIStores", new { storeId = store.Id })
: RedirectToAction("ListInvoices", "UIInvoice", new { storeId = store.Id });
var perms = store.GetPermissionSet(userId);
if (perms.Contains(Policies.CanModifyStoreSettings, store.Id))
return new RedirectToActionResult("Dashboard", "UIStores", new {storeId = store.Id});
if (perms.Contains(Policies.CanViewInvoices, store.Id))
return new RedirectToActionResult("ListInvoices", "UIInvoice", new { storeId = store.Id });
return new RedirectToActionResult("Index", "UIStores", new {storeId = store.Id});
}
}
}

View file

@ -527,6 +527,8 @@ namespace BTCPayServer.Controllers
{$"{Policies.CanModifyStoreWebhooks}:", ("Modify selected stores' webhooks", "Allows modifying the webhooks of the selected stores.")},
{Policies.CanViewStoreSettings, ("View your stores", "Allows viewing stores settings.")},
{$"{Policies.CanViewStoreSettings}:", ("View your stores", "Allows viewing the selected stores' settings.")},
{Policies.CanViewReports, ("View your reports", "Allows viewing reports.")},
{$"{Policies.CanViewReports}:", ("View your selected stores' reports", "Allows viewing the selected stores' reports.")},
{Policies.CanModifyServerSettings, ("Manage your server", "Grants total control on the server settings of your server.")},
{Policies.CanViewProfile, ("View your profile", "Allows viewing your user profile.")},
{Policies.CanModifyProfile, ("Manage your profile", "Allows viewing and modifying your user profile.")},
@ -542,12 +544,18 @@ namespace BTCPayServer.Controllers
{$"{Policies.CanModifyPaymentRequests}:", ("Manage selected stores' payment requests", "Allows viewing, modifying, deleting and creating new payment requests on the selected stores.")},
{Policies.CanViewPaymentRequests, ("View your payment requests", "Allows viewing payment requests.")},
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "Allows viewing the selected stores' payment requests.")},
{Policies.CanViewPullPayments, ("View your pull payments", "Allows viewing pull payments on all your stores.")},
{$"{Policies.CanViewPullPayments}:", ("View selected stores' pull payments", "Allows viewing pull payments on the selected stores.")},
{Policies.CanManagePullPayments, ("Manage your pull payments", "Allows viewing, modifying, deleting and creating pull payments on all your stores.")},
{$"{Policies.CanManagePullPayments}:", ("Manage selected stores' pull payments", "Allows viewing, modifying, deleting and creating pull payments on the selected stores.")},
{Policies.CanArchivePullPayments, ("Archive your pull payments", "Allows deleting pull payments on all your stores.")},
{$"{Policies.CanArchivePullPayments}:", ("Archive selected stores' pull payments", "Allows deleting pull payments on the selected stores.")},
{Policies.CanCreatePullPayments, ("Create pull payments", "Allows creating pull payments on all your stores.")},
{$"{Policies.CanCreatePullPayments}:", ("Create pull payments in selected stores", "Allows creating pull payments on the selected stores.")},
{Policies.CanManagePayouts, ("Manage payouts", "Allows managing payouts on all your stores.")},
{$"{Policies.CanManagePayouts}:", ("Manage payouts in selected stores", "Allows managing payouts on the selected stores.")},
{Policies.CanViewPayouts, ("View payouts", "Allows viewing payouts on all your stores.")},
{$"{Policies.CanViewPayouts}:", ("View payouts in selected stores", "Allows viewing payouts on the selected stores.")},
{Policies.CanCreateNonApprovedPullPayments, ("Create non-approved pull payments", "Allows creating pull payments without automatic approval on all your stores.")},
{$"{Policies.CanCreateNonApprovedPullPayments}:", ("Create non-approved pull payments in selected stores", "Allows viewing, modifying, deleting and creating pull payments without automatic approval on the selected stores.")},
{Policies.CanUseInternalLightningNode, ("Use the internal lightning node", "Allows using the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices.")},

View file

@ -71,8 +71,9 @@ namespace BTCPayServer.Controllers
FormDataService = formDataService;
}
[BitpayAPIConstraint(false)]
[HttpGet("/stores/{storeId}/payment-requests")]
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GetPaymentRequests(string storeId, ListPaymentRequestsViewModel model = null)
{
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());
@ -105,6 +106,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")]
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPaymentRequest(string storeId, string payReqId)
{
var store = GetCurrentStore();
@ -127,6 +129,7 @@ namespace BTCPayServer.Controllers
}
[HttpPost("/stores/{storeId}/payment-requests/edit/{payReqId?}")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPaymentRequest(string payReqId, UpdatePaymentRequestViewModel viewModel)
{
if (!string.IsNullOrEmpty(viewModel.Currency) &&
@ -386,6 +389,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{payReqId}/clone")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ClonePaymentRequest(string payReqId)
{
var store = GetCurrentStore();
@ -405,6 +409,7 @@ namespace BTCPayServer.Controllers
}
[HttpGet("{payReqId}/archive")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> TogglePaymentRequestArchival(string payReqId)
{
var store = GetCurrentStore();

View file

@ -18,7 +18,7 @@ using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers;
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
public partial class UIReportsController : Controller
{
@ -49,7 +49,7 @@ public partial class UIReportsController : Controller
[HttpPost("stores/{storeId}/reports")]
[AcceptMediaTypeConstraint("application/json")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[IgnoreAntiforgeryToken]
public async Task<IActionResult> StoreReportsJson(string storeId, [FromBody] StoreReportRequest? request = null, bool fakeData = false, CancellationToken cancellation = default)
{
@ -64,7 +64,7 @@ public partial class UIReportsController : Controller
[HttpGet("stores/{storeId}/reports")]
[AcceptMediaTypeConstraint("text/html")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewReports, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult StoreReports(
string storeId,
string ? viewName = null)

View file

@ -28,7 +28,7 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers
{
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken]
public class UIStorePullPaymentsController : Controller
{
@ -278,7 +278,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(PullPayments), new { storeId });
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanManagePayouts, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("stores/{storeId}/pull-payments/payouts")]
[HttpPost("stores/{storeId}/pull-payments/{pullPaymentId}/payouts")]
[HttpPost("stores/{storeId}/payouts")]
@ -472,6 +472,7 @@ namespace BTCPayServer.Controllers
[HttpGet("stores/{storeId}/pull-payments/{pullPaymentId}/payouts")]
[HttpGet("stores/{storeId}/payouts")]
[Authorize(Policy = Policies.CanViewPayouts, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Payouts(
string storeId, string pullPaymentId, string paymentMethodId, PayoutState payoutState,
int skip = 0, int count = 50)

View file

@ -37,6 +37,7 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers
{
[Route("stores")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
@ -127,6 +128,30 @@ namespace BTCPayServer.Controllers
{
get; set;
}
[AllowAnonymous]
[HttpGet("{storeId}/index")]
public async Task<IActionResult> Index(string storeId)
{
var userId = _UserManager.GetUserId(User);
if(userId is null)
return Forbid();
var store = await _Repo.FindStore(storeId, _UserManager.GetUserId(User));
if (store is null)
{
return Forbid();
}
if (store.GetPermissionSet(userId).Contains(Policies.CanModifyStoreSettings, storeId))
{
return RedirectToAction("Dashboard", new { storeId });
}
if (store.GetPermissionSet(userId).Contains(Policies.CanViewInvoices, storeId))
{
return RedirectToAction("ListInvoices", "UIInvoice", new { storeId });
}
HttpContext.SetStoreData(store);
return View();
}
[HttpGet]
[Route("{storeId}/users")]

View file

@ -1,7 +1,7 @@
@using BTCPayServer.Services.PaymentRequests
@using System.Globalization
@using BTCPayServer.Client
@using BTCPayServer.Forms
@using BTCPayServer.Services.Stores
@using BTCPayServer.TagHelpers
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject FormDataService FormDataService
@ -28,11 +28,11 @@
<div class="d-flex gap-3 mt-3 mt-sm-0">
@if (string.IsNullOrEmpty(Model.Id))
{
<button type="submit" class="btn btn-primary" id="SaveButton">Create</button>
<button type="submit" class="btn btn-primary" id="SaveButton" permission="@Policies.CanModifyPaymentRequests">Create</button>
}
else
{
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton">Save</button>
<button type="submit" class="btn btn-primary order-sm-1" id="SaveButton" permission="@Policies.CanModifyPaymentRequests">Save</button>
<a class="btn btn-secondary" asp-action="ViewPaymentRequest" asp-route-payReqId="@Model.Id" id="ViewPaymentRequest" target="_blank">View</a>
}
</div>
@ -152,18 +152,19 @@
{
<div class="d-flex gap-3 mt-3">
<a class="btn btn-secondary"
permission="@Policies.CanViewInvoices"
asp-action="ListInvoices"
asp-controller="UIInvoice"
asp-route-storeId="@Model.StoreId"
asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(Model.Id)}")">Invoices</a>
<a class="btn btn-secondary" asp-route-payReqId="@Model.Id" asp-action="ClonePaymentRequest" id="ClonePaymentRequest">Clone</a>
<a class="btn btn-secondary" asp-route-payReqId="@Model.Id" asp-action="ClonePaymentRequest" id="ClonePaymentRequest" permission="@Policies.CanModifyPaymentRequests">Clone</a>
@if (!Model.Archived)
{
<a class="btn btn-secondary" data-bs-toggle="tooltip" title="Archive this payment request so that it does not appear in the payment request list by default" asp-controller="UIPaymentRequest" asp-action="TogglePaymentRequestArchival" asp-route-payReqId="@Model.Id" id="ArchivePaymentRequest">Archive</a>
<a class="btn btn-secondary" data-bs-toggle="tooltip" title="Archive this payment request so that it does not appear in the payment request list by default" asp-controller="UIPaymentRequest" asp-action="TogglePaymentRequestArchival" asp-route-payReqId="@Model.Id" id="ArchivePaymentRequest" permission="@Policies.CanModifyPaymentRequests">Archive</a>
}
else
{
<a class="btn btn-secondary" data-bs-toggle="tooltip" title="Unarchive this payment request" asp-controller="UIPaymentRequest" asp-action="TogglePaymentRequestArchival" asp-route-payReqId="@Model.Id" id="UnarchivePaymentRequest">Unarchive</a>
<a class="btn btn-secondary" data-bs-toggle="tooltip" title="Unarchive this payment request" asp-controller="UIPaymentRequest" asp-action="TogglePaymentRequestArchival" asp-route-payReqId="@Model.Id" id="UnarchivePaymentRequest" permission="@Policies.CanModifyPaymentRequests">Unarchive</a>
}
</div>
}

View file

@ -2,6 +2,7 @@
@using Microsoft.AspNetCore.Html
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Components
@using BTCPayServer.Client
@model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel
@{
Layout = "_Layout";
@ -29,7 +30,7 @@
<vc:icon symbol="info" />
</a>
</h2>
<a asp-action="EditPaymentRequest" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button" id="CreatePaymentRequest">
<a asp-action="EditPaymentRequest" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button" id="CreatePaymentRequest" permission="@Policies.CanModifyPaymentRequests">
Create Request
</a>
</div>
@ -117,10 +118,10 @@
<vc:icon symbol="dots" />
</button>
<ul class="dropdown-menu" aria-labelledby="actionDropdown">
<li><a class="dropdown-item" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@item.StoreId" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")">Invoices</a></li>
<li><a class="dropdown-item" asp-action="ClonePaymentRequest" asp-route-storeId="@item.StoreId" asp-route-payReqId="@item.Id" id="Clone-@item.Id">Clone</a></li>
<li><a class="dropdown-item" permission="@Policies.CanViewInvoices" asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@item.StoreId" asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(item.Id)}")">Invoices</a></li>
<li><a class="dropdown-item" permission="@Policies.CanModifyPaymentRequests" asp-action="ClonePaymentRequest" asp-route-storeId="@item.StoreId" asp-route-payReqId="@item.Id" id="Clone-@item.Id">Clone</a></li>
<li class="dropdown-divider"></li>
<li><a class="dropdown-item" asp-action="TogglePaymentRequestArchival" asp-route-storeId="@item.StoreId" asp-route-payReqId="@item.Id" id="ToggleArchival-@item.Id">@(item.Archived ? "Unarchive" : "Archive")</a></li>
<li><a class="dropdown-item" permission="@Policies.CanModifyPaymentRequests" asp-action="TogglePaymentRequestArchival" asp-route-storeId="@item.StoreId" asp-route-payReqId="@item.Id" id="ToggleArchival-@item.Id">@(item.Archived ? "Unarchive" : "Archive")</a></li>
</ul>
</div>
</div>

View file

@ -73,9 +73,9 @@
<div class="flex-fill">
<p class="mb-3">
Payouts allow you to process pull payments, in the form of refunds, salary payouts, or withdrawals.
You can also
<span permission="@Policies.CanModifyStoreSettings">You can also
<a id="PayoutProcessors" asp-action="ConfigureStorePayoutProcessors" asp-controller="UIPayoutProcessors" asp-route-storeId="@storeId">configure payout processors</a>
to automate payouts.
to automate payouts.</span>
</p>
<a href="https://docs.btcpayserver.org/Payouts/" target="_blank" rel="noreferrer noopener">Learn More</a>
</div>
@ -93,7 +93,7 @@
PaymentMethods = new[] { Model.PaymentMethodId }
})).Any())
{
<div class="alert alert-info mb-5" role="alert">
<div class="alert alert-info mb-5" role="alert" permission="@Policies.CanModifyStoreSettings">
<strong>Pro tip:</strong> There are supported but unconfigured Payout Processors for this payout payment method.<br/>
Payout Processors help automate payouts so that you do not need to manually handle them.
<a class="alert-link p-0" asp-action="ConfigureStorePayoutProcessors" asp-controller="UIPayoutProcessors" asp-route-storeId="@storeId">Configure now</a>

View file

@ -128,12 +128,15 @@
<tr class="mass-action-row">
<td class="date-col">@pp.StartDate.ToBrowserDate()</td>
<td>
<a asp-action="EditPullPayment"
asp-controller="UIPullPayment"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id">
<a
permission="@Policies.CanManagePullPayments"
asp-action="EditPullPayment"
asp-controller="UIPullPayment"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id">
@pp.Name
</a>
<span not-permission="@Policies.CanManagePullPayments">@pp.Name</span>
</td>
<td>@pp.AutoApproveClaims</td>
<td class="align-middle">
@ -149,12 +152,14 @@
<td class="actions-col">
<div class="d-inline-flex align-items-center gap-3">
<a asp-action="ViewPullPayment"
permission="@Policies.CanViewPullPayments"
asp-controller="UIPullPayment"
asp-route-pullPaymentId="@pp.Id"
target="_blank">
View
</a>
<a class="pp-payout"
permission="@Policies.CanViewPayouts"
asp-action="Payouts"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id">

View file

View file

@ -124,7 +124,7 @@
"securitySchemes": {
"API_Key": {
"type": "apiKey",
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmanageusers`: Manage users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.canarchivepullpayments`: Archive your pull payments\n* `btcpay.store.cancreatepullpayments`: Create pull payments\n* `btcpay.store.cancreatenonapprovedpullpayments`: Create non-approved pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
"description": "BTCPay Server supports authenticating and authorizing users through an API Key that is generated by them. Send the API Key as a header value to Authorization with the format: `token {token}`. For a smoother experience, you can generate a url that redirects users to an API key creation screen.\n\n The following permissions are available to the context of the user creating the API Key:\n\n* `unrestricted`: Unrestricted access\n* `btcpay.user.candeleteuser`: Delete user\n* `btcpay.user.canviewprofile`: View your profile\n* `btcpay.user.canmodifyprofile`: Manage your profile\n* `btcpay.user.canmanagenotificationsforuser`: Manage your notifications\n* `btcpay.user.canviewnotificationsforuser`: View your notifications\n\nThe following permissions are available if the user is an administrator:\n\n* `btcpay.server.canviewusers`: View users\n* `btcpay.server.cancreateuser`: Create new users\n* `btcpay.server.canmanageusers`: Manage users\n* `btcpay.server.canmodifyserversettings`: Manage your server\n* `btcpay.server.canuseinternallightningnode`: Use the internal lightning node\n* `btcpay.server.canviewlightninginvoiceinternalnode`: View invoices from internal lightning node\n* `btcpay.server.cancreatelightninginvoiceinternalnode`: Create invoices with internal lightning node\n\nThe following permissions applies to all stores of the user, you can limit to a specific store with the following format: `btcpay.store.cancreateinvoice:6HSHAEU4iYWtjxtyRs9KyPjM9GAQp8kw2T9VWbGG1FnZ`:\n\n* `btcpay.store.canmodifystoresettings`: Modify your stores\n* `btcpay.store.canviewcustodianaccounts`: View exchange accounts linked to your stores\n* `btcpay.store.canmanagecustodianaccounts`: Manage exchange accounts linked to your stores\n* `btcpay.store.candeposittocustodianaccount`: Deposit funds to exchange accounts linked to your stores\n* `btcpay.store.canwithdrawfromcustodianaccount`: Withdraw funds from exchange accounts to your store\n* `btcpay.store.cantradecustodianaccount`: Trade funds on your store's exchange accounts\n* `btcpay.store.webhooks.canmodifywebhooks`: Modify stores webhooks\n* `btcpay.store.canviewstoresettings`: View your stores\n* `btcpay.store.canviewreports`: View your reports\n* `btcpay.store.cancreateinvoice`: Create an invoice\n* `btcpay.store.canviewinvoices`: View invoices\n* `btcpay.store.canmodifyinvoices`: Modify invoices\n* `btcpay.store.canmodifypaymentrequests`: Modify your payment requests\n* `btcpay.store.canviewpaymentrequests`: View your payment requests\n* `btcpay.store.canviewpullpayments`: View your pull payments\n* `btcpay.store.canmanagepullpayments`: Manage your pull payments\n* `btcpay.store.canarchivepullpayments`: Archive your pull payments\n* `btcpay.store.cancreatepullpayments`: Create pull payments\n* `btcpay.store.canmanagepayouts`: Manage payouts\n* `btcpay.store.canviewpayouts`: View payouts\n* `btcpay.store.cancreatenonapprovedpullpayments`: Create non-approved pull payments\n* `btcpay.store.canuselightningnode`: Use the lightning nodes associated with your stores\n* `btcpay.store.canviewlightninginvoice`: View the lightning invoices associated with your stores\n* `btcpay.store.cancreatelightninginvoice`: Create invoices from the lightning nodes associated with your stores\n\nNote that API Keys only limits permission of a user and can never expand it. If an API Key has the permission `btcpay.server.canmodifyserversettings` but that the user account creating this API Key is not administrator, the API Key will not be able to modify the server settings.\nSome permissions may include other permissions, see [this operation](#operation/permissionsMetadata).\n",
"name": "Authorization",
"in": "header"
},
@ -141,4 +141,4 @@
"Basic": []
}
]
}
}