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
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.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging; using System;
using System.Linq;
namespace BTCPayServer.Abstractions.TagHelpers; namespace BTCPayServer.Abstractions.TagHelpers;
[HtmlTargetElement(Attributes = "[permission]")] [HtmlTargetElement(Attributes = "[permission]")]
[HtmlTargetElement(Attributes = "[not-permission]" )] [HtmlTargetElement(Attributes = "[not-permission]")]
public class PermissionTagHelper : TagHelper public class PermissionTagHelper : TagHelper
{ {
private readonly IAuthorizationService _authorizationService; private readonly IAuthorizationService _authorizationService;
@@ -22,29 +23,72 @@ public class PermissionTagHelper : TagHelper
public string Permission { get; set; } public string Permission { get; set; }
public string NotPermission { get; set; } public string NotPermission { get; set; }
public string PermissionResource { get; set; } public string PermissionResource { get; set; }
public bool AndMode { get; set; } = false;
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) 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; return;
if (_httpContextAccessor.HttpContext is null) if (_httpContextAccessor.HttpContext is null)
return; return;
var expectedResult = !string.IsNullOrEmpty(Permission); bool shouldRender = true; // Assume tag should be rendered unless a check fails
var key = $"{Permission??NotPermission}_{PermissionResource}";
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var o) || // Process 'Permission' - User must have these permissions
o is not AuthorizationResult res) if (permissions.Any())
{ {
res = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User, bool finalResult = AndMode;
PermissionResource, foreach (var perm in permissions)
Permission); {
_httpContextAccessor.HttpContext.Items.Add(key, res); 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(); 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 CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:"; public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings"; public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewReports = "btcpay.store.canviewreports";
public const string CanViewInvoices = "btcpay.store.canviewinvoices"; public const string CanViewInvoices = "btcpay.store.canviewinvoices";
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice"; public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices"; public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices";
@@ -34,7 +35,10 @@ namespace BTCPayServer.Client
public const string CanDeleteUser = "btcpay.user.candeleteuser"; public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments"; public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanArchivePullPayments = "btcpay.store.canarchivepullpayments"; 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 CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanViewPullPayments = "btcpay.store.canviewpullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments"; public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts"; public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts"; public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
@@ -53,6 +57,7 @@ namespace BTCPayServer.Client
yield return CanModifyServerSettings; yield return CanModifyServerSettings;
yield return CanModifyStoreSettings; yield return CanModifyStoreSettings;
yield return CanViewStoreSettings; yield return CanViewStoreSettings;
yield return CanViewReports;
yield return CanViewPaymentRequests; yield return CanViewPaymentRequests;
yield return CanModifyPaymentRequests; yield return CanModifyPaymentRequests;
yield return CanModifyProfile; yield return CanModifyProfile;
@@ -72,6 +77,7 @@ namespace BTCPayServer.Client
yield return CanManagePullPayments; yield return CanManagePullPayments;
yield return CanArchivePullPayments; yield return CanArchivePullPayments;
yield return CanCreatePullPayments; yield return CanCreatePullPayments;
yield return CanViewPullPayments;
yield return CanCreateNonApprovedPullPayments; yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts; yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts; yield return CanManageCustodianAccounts;
@@ -79,6 +85,8 @@ namespace BTCPayServer.Client
yield return CanWithdrawFromCustodianAccounts; yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount; yield return CanTradeCustodianAccount;
yield return CanManageUsers; yield return CanManageUsers;
yield return CanManagePayouts;
yield return CanViewPayouts;
} }
} }
public static bool IsValidPolicy(string policy) public static bool IsValidPolicy(string policy)
@@ -252,11 +260,13 @@ namespace BTCPayServer.Client
Policies.CanViewStoreSettings, Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks, Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests, Policies.CanModifyPaymentRequests,
Policies.CanManagePayouts,
Policies.CanUseLightningNodeInStore); Policies.CanUseLightningNodeInStore);
PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser); PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments, Policies.CanArchivePullPayments); PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments, Policies.CanArchivePullPayments);
PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments); PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(policyMap, Policies.CanCreateNonApprovedPullPayments, Policies.CanViewPullPayments);
PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests); PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile); PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile);
PolicyHasChild(policyMap,Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore); 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.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
PolicyHasChild(policyMap, Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts); PolicyHasChild(policyMap, Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
PolicyHasChild(policyMap, Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore); 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(); var missingPolicies = Policies.AllPolicies.ToHashSet();
//recurse through the tree to see which policies are not included in the tree //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 Policies.CanModifyServerSettings
}); });
s.GoToUrl("/logout");
await alice.MakeAdmin(); await alice.MakeAdmin();
s.Logout();
s.GoToLogin(); s.GoToLogin();
s.LogIn(alice.Email, alice.Password); s.LogIn(alice.Email, alice.Password);
s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}"); s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}");

View File

@@ -135,25 +135,25 @@
<span>Invoices</span> <span>Invoices</span>
</a> </a>
</li> </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"> <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" /> <vc:icon symbol="invoice" />
<span>Reporting</span> <span>Reporting</span>
</a> </a>
</li> </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"> <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"/> <vc:icon symbol="payment-requests"/>
<span>Requests</span> <span>Requests</span>
</a> </a>
</li> </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"> <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"/> <vc:icon symbol="pull-payments"/>
<span>Pull Payments</span> <span>Pull Payments</span>
</a> </a>
</li> </li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings"> <li class="nav-item" permission="@Policies.CanViewPayouts">
<a asp-area="" <a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts" asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId="" asp-route-pullPaymentId=""

View File

@@ -31,8 +31,7 @@
} }
else 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="UIStores" asp-action="Index" 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>
} }
@if (Model.Options.Any() || Model.ArchivedCount > 0) @if (Model.Options.Any() || Model.ArchivedCount > 0)
{ {
@@ -54,21 +53,14 @@ else
@foreach (var option in Model.Options) @foreach (var option in Model.Options)
{ {
<li> <li>
@if (option.IsOwner) <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>
{
<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>
}
</li> </li>
} }
@if (Model.Options.Any()) @if (Model.Options.Any())
{ {
<li><hr class="dropdown-divider"></li> <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) @if (Model.ArchivedCount > 0)
{ {
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider"></li>

View File

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

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic; using System.Collections.Generic;
using BTCPayServer.Data;
namespace BTCPayServer.Components.StoreSelector namespace BTCPayServer.Components.StoreSelector
{ {
@@ -14,9 +15,9 @@ namespace BTCPayServer.Components.StoreSelector
public class StoreSelectorOption public class StoreSelectorOption
{ {
public bool Selected { get; set; } public bool Selected { get; set; }
public bool IsOwner { get; set; }
public string Text { get; set; } public string Text { get; set; }
public string Value { get; set; } public string Value { get; set; }
public WalletId WalletId { 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")] [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) public async Task<IActionResult> GetPullPayments(string storeId, bool includeArchived = false)
{ {
using var ctx = _dbContextFactory.CreateContext(); using var ctx = _dbContextFactory.CreateContext();
@@ -456,7 +456,7 @@ namespace BTCPayServer.Controllers.Greenfield
[HttpGet("~/api/v1/stores/{storeId}/payouts")] [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) public async Task<IActionResult> GetStorePayouts(string storeId, bool includeCancelled = false)
{ {
var payouts = await _pullPaymentService.GetPayouts(new PullPaymentHostedService.PayoutQuery() var payouts = await _pullPaymentService.GetPayouts(new PullPaymentHostedService.PayoutQuery()
@@ -471,7 +471,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpDelete("~/api/v1/stores/{storeId}/payouts/{payoutId}")] [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) public async Task<IActionResult> CancelPayout(string storeId, string payoutId)
{ {
var res = await _pullPaymentService.Cancel(new PullPaymentHostedService.CancelRequest(new[] { payoutId }, new[] { storeId })); 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}")] [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) public async Task<IActionResult> ApprovePayout(string storeId, string payoutId, ApprovePayoutRequest approvePayoutRequest, CancellationToken cancellationToken = default)
{ {
using var ctx = _dbContextFactory.CreateContext(); using var ctx = _dbContextFactory.CreateContext();
@@ -533,7 +533,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpPost("~/api/v1/stores/{storeId}/payouts/{payoutId}/mark-paid")] [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) public async Task<IActionResult> MarkPayoutPaid(string storeId, string payoutId, CancellationToken cancellationToken = default)
{ {
return await MarkPayout(storeId, payoutId, new Client.Models.MarkPayoutRequest() 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")] [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) public async Task<IActionResult> MarkPayout(string storeId, string payoutId, Client.Models.MarkPayoutRequest request)
{ {
request ??= new(); request ??= new();
@@ -571,7 +571,7 @@ namespace BTCPayServer.Controllers.Greenfield
} }
[HttpGet("~/api/v1/stores/{storeId}/payouts/{payoutId}")] [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) public async Task<IActionResult> GetStorePayout(string storeId, string payoutId)
{ {
await using var ctx = _dbContextFactory.CreateContext(); await using var ctx = _dbContextFactory.CreateContext();

View File

@@ -32,7 +32,7 @@ public class GreenfieldReportsController : Controller
public ApplicationDbContextFactory DBContextFactory { get; } public ApplicationDbContextFactory DBContextFactory { get; }
public ReportService ReportService { 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")] [HttpPost("~/api/v1/stores/{storeId}/reports")]
[NonAction] // Disabling this endpoint as we still need to figure out the request/response model [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) 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); var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: true);
if (result.Succeeded) if (result.Succeeded)
{ {

View File

@@ -197,12 +197,14 @@ namespace BTCPayServer.Controllers
{ {
return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
} }
public static RedirectToActionResult RedirectToStore(string userId, StoreData store)
public RedirectToActionResult RedirectToStore(string userId, StoreData store)
{ {
return store.HasPermission(userId, Policies.CanModifyStoreSettings) var perms = store.GetPermissionSet(userId);
? RedirectToAction("Dashboard", "UIStores", new { storeId = store.Id }) if (perms.Contains(Policies.CanModifyStoreSettings, store.Id))
: RedirectToAction("ListInvoices", "UIInvoice", new { storeId = 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.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 stores settings.")},
{$"{Policies.CanViewStoreSettings}:", ("View your stores", "Allows viewing the selected 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.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.CanViewProfile, ("View your profile", "Allows viewing your user profile.")},
{Policies.CanModifyProfile, ("Manage your profile", "Allows viewing and modifying 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.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 payment requests.")},
{$"{Policies.CanViewPaymentRequests}:", ("View your payment requests", "Allows viewing the selected stores' 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 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.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 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.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", "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.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", "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.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.")}, {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; FormDataService = formDataService;
} }
[BitpayAPIConstraint(false)]
[HttpGet("/stores/{storeId}/payment-requests")] [HttpGet("/stores/{storeId}/payment-requests")]
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> GetPaymentRequests(string storeId, ListPaymentRequestsViewModel model = null) public async Task<IActionResult> GetPaymentRequests(string storeId, ListPaymentRequestsViewModel model = null)
{ {
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel()); model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());
@@ -105,6 +106,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")] [HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")]
[Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPaymentRequest(string storeId, string payReqId) public async Task<IActionResult> EditPaymentRequest(string storeId, string payReqId)
{ {
var store = GetCurrentStore(); var store = GetCurrentStore();
@@ -127,6 +129,7 @@ namespace BTCPayServer.Controllers
} }
[HttpPost("/stores/{storeId}/payment-requests/edit/{payReqId?}")] [HttpPost("/stores/{storeId}/payment-requests/edit/{payReqId?}")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> EditPaymentRequest(string payReqId, UpdatePaymentRequestViewModel viewModel) public async Task<IActionResult> EditPaymentRequest(string payReqId, UpdatePaymentRequestViewModel viewModel)
{ {
if (!string.IsNullOrEmpty(viewModel.Currency) && if (!string.IsNullOrEmpty(viewModel.Currency) &&
@@ -386,6 +389,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("{payReqId}/clone")] [HttpGet("{payReqId}/clone")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ClonePaymentRequest(string payReqId) public async Task<IActionResult> ClonePaymentRequest(string payReqId)
{ {
var store = GetCurrentStore(); var store = GetCurrentStore();
@@ -405,6 +409,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("{payReqId}/archive")] [HttpGet("{payReqId}/archive")]
[Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> TogglePaymentRequestArchival(string payReqId) public async Task<IActionResult> TogglePaymentRequestArchival(string payReqId)
{ {
var store = GetCurrentStore(); var store = GetCurrentStore();

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Route("stores")] [Route("stores")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
@@ -128,6 +129,30 @@ namespace BTCPayServer.Controllers
get; set; 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] [HttpGet]
[Route("{storeId}/users")] [Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers() public async Task<IActionResult> StoreUsers()

View File

@@ -1,7 +1,7 @@
@using BTCPayServer.Services.PaymentRequests @using BTCPayServer.Services.PaymentRequests
@using System.Globalization @using System.Globalization
@using BTCPayServer.Client
@using BTCPayServer.Forms @using BTCPayServer.Forms
@using BTCPayServer.Services.Stores
@using BTCPayServer.TagHelpers @using BTCPayServer.TagHelpers
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@inject FormDataService FormDataService @inject FormDataService FormDataService
@@ -28,11 +28,11 @@
<div class="d-flex gap-3 mt-3 mt-sm-0"> <div class="d-flex gap-3 mt-3 mt-sm-0">
@if (string.IsNullOrEmpty(Model.Id)) @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 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> <a class="btn btn-secondary" asp-action="ViewPaymentRequest" asp-route-payReqId="@Model.Id" id="ViewPaymentRequest" target="_blank">View</a>
} }
</div> </div>
@@ -152,18 +152,19 @@
{ {
<div class="d-flex gap-3 mt-3"> <div class="d-flex gap-3 mt-3">
<a class="btn btn-secondary" <a class="btn btn-secondary"
permission="@Policies.CanViewInvoices"
asp-action="ListInvoices" asp-action="ListInvoices"
asp-controller="UIInvoice" asp-controller="UIInvoice"
asp-route-storeId="@Model.StoreId" asp-route-storeId="@Model.StoreId"
asp-route-searchterm="@($"orderid:{PaymentRequestRepository.GetOrderIdForPaymentRequest(Model.Id)}")">Invoices</a> 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) @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 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> </div>
} }

View File

@@ -2,6 +2,7 @@
@using Microsoft.AspNetCore.Html @using Microsoft.AspNetCore.Html
@using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Components @using BTCPayServer.Components
@using BTCPayServer.Client
@model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel @model BTCPayServer.Models.PaymentRequestViewModels.ListPaymentRequestsViewModel
@{ @{
Layout = "_Layout"; Layout = "_Layout";
@@ -29,7 +30,7 @@
<vc:icon symbol="info" /> <vc:icon symbol="info" />
</a> </a>
</h2> </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 Create Request
</a> </a>
</div> </div>
@@ -117,10 +118,10 @@
<vc:icon symbol="dots" /> <vc:icon symbol="dots" />
</button> </button>
<ul class="dropdown-menu" aria-labelledby="actionDropdown"> <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" 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" 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.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 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> </ul>
</div> </div>
</div> </div>

View File

@@ -73,9 +73,9 @@
<div class="flex-fill"> <div class="flex-fill">
<p class="mb-3"> <p class="mb-3">
Payouts allow you to process pull payments, in the form of refunds, salary payouts, or withdrawals. 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> <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> </p>
<a href="https://docs.btcpayserver.org/Payouts/" target="_blank" rel="noreferrer noopener">Learn More</a> <a href="https://docs.btcpayserver.org/Payouts/" target="_blank" rel="noreferrer noopener">Learn More</a>
</div> </div>
@@ -93,7 +93,7 @@
PaymentMethods = new[] { Model.PaymentMethodId } PaymentMethods = new[] { Model.PaymentMethodId }
})).Any()) })).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/> <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. 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> <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"> <tr class="mass-action-row">
<td class="date-col">@pp.StartDate.ToBrowserDate()</td> <td class="date-col">@pp.StartDate.ToBrowserDate()</td>
<td> <td>
<a asp-action="EditPullPayment" <a
asp-controller="UIPullPayment" permission="@Policies.CanManagePullPayments"
asp-route-storeId="@storeId" asp-action="EditPullPayment"
asp-route-pullPaymentId="@pp.Id"> asp-controller="UIPullPayment"
asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id">
@pp.Name @pp.Name
</a> </a>
<span not-permission="@Policies.CanManagePullPayments">@pp.Name</span>
</td> </td>
<td>@pp.AutoApproveClaims</td> <td>@pp.AutoApproveClaims</td>
<td class="align-middle"> <td class="align-middle">
@@ -149,12 +152,14 @@
<td class="actions-col"> <td class="actions-col">
<div class="d-inline-flex align-items-center gap-3"> <div class="d-inline-flex align-items-center gap-3">
<a asp-action="ViewPullPayment" <a asp-action="ViewPullPayment"
permission="@Policies.CanViewPullPayments"
asp-controller="UIPullPayment" asp-controller="UIPullPayment"
asp-route-pullPaymentId="@pp.Id" asp-route-pullPaymentId="@pp.Id"
target="_blank"> target="_blank">
View View
</a> </a>
<a class="pp-payout" <a class="pp-payout"
permission="@Policies.CanViewPayouts"
asp-action="Payouts" asp-action="Payouts"
asp-route-storeId="@storeId" asp-route-storeId="@storeId"
asp-route-pullPaymentId="@pp.Id"> asp-route-pullPaymentId="@pp.Id">

View File

View File

@@ -124,7 +124,7 @@
"securitySchemes": { "securitySchemes": {
"API_Key": { "API_Key": {
"type": "apiKey", "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", "name": "Authorization",
"in": "header" "in": "header"
}, },