Allow pull payments for store guests (#3128)

This commit is contained in:
Andrew Camilleri 2021-12-07 16:40:24 +01:00 committed by GitHub
parent e113c12768
commit fd75008499
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 29 deletions

View file

@ -26,7 +26,7 @@ using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Route("stores/{storeId}/pull-payments")] [Route("stores/{storeId}/pull-payments")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public class StorePullPaymentsController: Controller public class StorePullPaymentsController: Controller
{ {
@ -60,6 +60,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("new")] [HttpGet("new")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> NewPullPayment(string storeId) public async Task<IActionResult> NewPullPayment(string storeId)
{ {
if (CurrentStore is null) if (CurrentStore is null)
@ -86,6 +87,7 @@ namespace BTCPayServer.Controllers
} }
[HttpPost("new")] [HttpPost("new")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model) public async Task<IActionResult> NewPullPayment(string storeId, NewPullPaymentModel model)
{ {
if (CurrentStore is null) if (CurrentStore is null)
@ -217,6 +219,7 @@ namespace BTCPayServer.Controllers
} }
[HttpGet("{pullPaymentId}/archive")] [HttpGet("{pullPaymentId}/archive")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public IActionResult ArchivePullPayment(string storeId, public IActionResult ArchivePullPayment(string storeId,
string pullPaymentId) string pullPaymentId)
{ {
@ -224,6 +227,7 @@ namespace BTCPayServer.Controllers
} }
[HttpPost("{pullPaymentId}/archive")] [HttpPost("{pullPaymentId}/archive")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> ArchivePullPaymentPost(string storeId, public async Task<IActionResult> ArchivePullPaymentPost(string storeId,
string pullPaymentId) string pullPaymentId)
{ {
@ -236,6 +240,7 @@ namespace BTCPayServer.Controllers
return RedirectToAction(nameof(PullPayments), new { storeId = storeId }); return RedirectToAction(nameof(PullPayments), new { storeId = storeId });
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("payouts")] [HttpPost("payouts")]
public async Task<IActionResult> PayoutsPost( public async Task<IActionResult> PayoutsPost(
string storeId, PayoutsModel vm, CancellationToken cancellationToken) string storeId, PayoutsModel vm, CancellationToken cancellationToken)

View file

@ -37,7 +37,7 @@ namespace BTCPayServer.Security
return; return;
} }
string storeId = _HttpContext.GetImplicitStoreId(); string storeId = context.Resource is string s? s :_HttpContext.GetImplicitStoreId();
if (storeId == null) if (storeId == null)
return; return;
@ -47,20 +47,20 @@ namespace BTCPayServer.Security
var store = await _storeRepository.FindStore(storeId, userid); var store = await _storeRepository.FindStore(storeId, userid);
if (store == null)
return;
bool success = false; bool success = false;
switch (requirement.Policy) switch (requirement.Policy)
{ {
case Policies.CanModifyStoreSettings: case Policies.CanModifyStoreSettings:
if (store.Role == StoreRoles.Owner || isAdmin) if (store != null && (store.Role == StoreRoles.Owner || isAdmin))
success = true;
break;
case Policies.CanViewStoreSettings:
if (store != null || isAdmin)
success = true; success = true;
break; break;
case Policies.CanCreateInvoice: case Policies.CanCreateInvoice:
if (store.Role == StoreRoles.Owner || if (store != null || isAdmin)
store.Role == StoreRoles.Guest ||
isAdmin ||
store.GetStoreBlob().AnyoneCanInvoice)
success = true; success = true;
break; break;
} }

View file

@ -0,0 +1,51 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.Security
{
[HtmlTargetElement(Attributes = nameof(Permission))]
public class PermissionTagHelper : TagHelper
{
private readonly IAuthorizationService _authorizationService;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly ILogger<PermissionTagHelper> _logger;
public PermissionTagHelper(IAuthorizationService authorizationService, IHttpContextAccessor httpContextAccessor, ILogger<PermissionTagHelper> logger)
{
_authorizationService = authorizationService;
_httpContextAccessor = httpContextAccessor;
_logger = logger;
}
public string Permission { get; set; }
public string PermissionResource { get; set; }
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.IsNullOrEmpty(Permission))
{
return;
}
var key = $"{Permission}_{PermissionResource}";
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key,out var cachedResult))
{
var result = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User,
PermissionResource,
Permission);
cachedResult = result;
_httpContextAccessor.HttpContext.Items.Add(key, result);
}
if (!((AuthorizationResult)cachedResult).Succeeded)
{
output.SuppressOutput();
}
}
}
}

View file

@ -2,6 +2,7 @@
@using BTCPayServer.Payments @using BTCPayServer.Payments
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@model BTCPayServer.Models.WalletViewModels.PayoutsModel @model BTCPayServer.Models.WalletViewModels.PayoutsModel
@inject IEnumerable<IPayoutHandler> PayoutHandlers; @inject IEnumerable<IPayoutHandler> PayoutHandlers;
@ -88,9 +89,9 @@
</ul> </ul>
</div> </div>
@if (Model.Payouts.Any() && stateActions.Any()) @if (Model.Payouts.Any() && stateActions.Any() )
{ {
<div class="col-12"> <div class="col-12" permission="@Policies.CanModifyStoreSettings" >
<div class="dropdown mt-4 ms-xl-auto mt-xl-0"> <div class="dropdown mt-4 ms-xl-auto mt-xl-0">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" id="@Model.PayoutState-actions"> <button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" id="@Model.PayoutState-actions">
Actions Actions
@ -114,7 +115,7 @@
<table class="table table-hover table-responsive-lg"> <table class="table table-hover table-responsive-lg">
<thead class="thead-inverse"> <thead class="thead-inverse">
<tr> <tr>
<th> <th permission="@Policies.CanModifyStoreSettings" >
<input id="@Model.PayoutState-selectAllCheckbox" type="checkbox" class="form-check-input selectAll" data-payout-state="@Model.PayoutState.ToString()" /> <input id="@Model.PayoutState-selectAllCheckbox" type="checkbox" class="form-check-input selectAll" data-payout-state="@Model.PayoutState.ToString()" />
</th> </th>
<th style="min-width: 90px;" class="col-md-auto"> <th style="min-width: 90px;" class="col-md-auto">
@ -134,7 +135,7 @@
{ {
var pp = Model.Payouts[i]; var pp = Model.Payouts[i];
<tr class="payout"> <tr class="payout">
<td> <td permission="@Policies.CanModifyStoreSettings" >
<span> <span>
<input type="checkbox" class="selection-item-@Model.PayoutState.ToString() form-check-input" asp-for="Payouts[i].Selected"/> <input type="checkbox" class="selection-item-@Model.PayoutState.ToString() form-check-input" asp-for="Payouts[i].Selected"/>
<input type="hidden" asp-for="Payouts[i].PayoutId"/> <input type="hidden" asp-for="Payouts[i].PayoutId"/>

View file

@ -1,5 +1,6 @@
@using BTCPayServer.Views.Stores @using BTCPayServer.Views.Stores
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@model BTCPayServer.Models.WalletViewModels.PullPaymentsModel @model BTCPayServer.Models.WalletViewModels.PullPaymentsModel
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; Layout = "../Shared/_NavLayout.cshtml";
@ -44,7 +45,7 @@
</a> </a>
</small> </small>
</h4> </h4>
<a asp-action="NewPullPayment" asp-route-storeId="@Context.GetRouteValue("storeId")" class="btn btn-primary" role="button" id="NewPullPayment"> <a permission="@Policies.CanModifyStoreSettings" asp-action="NewPullPayment" asp-route-storeId="@Context.GetRouteValue("storeId")" class="btn btn-primary" role="button" id="NewPullPayment">
<span class="fa fa-plus"></span> Create a new pull payment <span class="fa fa-plus"></span> Create a new pull payment
</a> </a>
</div> </div>
@ -88,7 +89,7 @@
</th> </th>
<th scope="col">Name</th> <th scope="col">Name</th>
<th scope="col">Refunded</th> <th scope="col">Refunded</th>
<th scope="col" class="text-end">Actions</th> <th scope="col" class="text-end" >Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -113,12 +114,15 @@
asp-route-pullPaymentId="@pp.Id"> asp-route-pullPaymentId="@pp.Id">
View View
</a> - </a> -
<a class="pp-payout" asp-action="Payouts" <a class="pp-payout"
asp-action="Payouts"
asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-storeId="@Context.GetRouteValue("storeId")"
asp-route-pullPaymentId="@pp.Id"> asp-route-pullPaymentId="@pp.Id">
Payouts Payouts
</a> - </a>
<a asp-action="ArchivePullPayment" <a asp-action="ArchivePullPayment"
permission="@Policies.CanModifyStoreSettings"
asp-route-storeId="@Context.GetRouteValue("storeId")" asp-route-storeId="@Context.GetRouteValue("storeId")"
asp-route-pullPaymentId="@pp.Id" asp-route-pullPaymentId="@pp.Id"
data-bs-toggle="modal" data-bs-toggle="modal"

View file

@ -1,14 +1,15 @@
@using BTCPayServer.Client
<nav id="sideNav" class="nav flex-column mb-4"> <nav id="sideNav" class="nav flex-column mb-4">
<a id="Nav-@(nameof(StoreNavPages.PaymentMethods))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PaymentMethods)" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment Methods</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.PaymentMethods))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PaymentMethods)" asp-controller="Stores" asp-action="PaymentMethods" asp-route-storeId="@Context.GetRouteValue("storeId")">Payment Methods</a>
<a id="Nav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="Stores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.Rates))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Rates)" asp-controller="Stores" asp-action="Rates" asp-route-storeId="@Context.GetRouteValue("storeId")">Rates</a>
<a id="Nav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="Stores" asp-action="CheckoutAppearance" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout Appearance</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.CheckoutAppearance))" class="nav-link @ViewData.IsActivePage(StoreNavPages.CheckoutAppearance)" asp-controller="Stores" asp-action="CheckoutAppearance" asp-route-storeId="@Context.GetRouteValue("storeId")">Checkout Appearance</a>
<a id="Nav-@(nameof(StoreNavPages.GeneralSettings))" class="nav-link @ViewData.IsActivePage(StoreNavPages.GeneralSettings)" asp-controller="Stores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General Settings</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.GeneralSettings))" class="nav-link @ViewData.IsActivePage(StoreNavPages.GeneralSettings)" asp-controller="Stores" asp-action="GeneralSettings" asp-route-storeId="@Context.GetRouteValue("storeId")">General Settings</a>
<a id="Nav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="Stores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.Tokens))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Tokens)" asp-controller="Stores" asp-action="ListTokens" asp-route-storeId="@Context.GetRouteValue("storeId")">Access Tokens</a>
<a id="Nav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@Context.GetRouteValue("storeId")">Users</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.Users))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Users)" asp-controller="Stores" asp-action="StoreUsers" asp-route-storeId="@Context.GetRouteValue("storeId")">Users</a>
<a id="Nav-@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Context.GetRouteValue("storeId")">Pay Button</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.PayButton))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PayButton)" asp-controller="Stores" asp-action="PayButton" asp-route-storeId="@Context.GetRouteValue("storeId")">Pay Button</a>
<a id="Nav-@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@Context.GetRouteValue("storeId")">Integrations</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.Integrations))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Integrations)" asp-controller="Stores" asp-action="Integrations" asp-route-storeId="@Context.GetRouteValue("storeId")">Integrations</a>
<a id="Nav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@Context.GetRouteValue("storeId")">Webhooks</a> <a permission="@Policies.CanModifyStoreSettings" id="Nav-@(nameof(StoreNavPages.Webhooks))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Webhooks)" asp-controller="Stores" asp-action="Webhooks" asp-route-storeId="@Context.GetRouteValue("storeId")">Webhooks</a>
<a id="Nav-@(nameof(StoreNavPages.PullPayments))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Pull Payments</a> <a id="Nav-@(nameof(StoreNavPages.PullPayments))" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Pull Payments</a>
<a id="Nav-@(nameof(StoreNavPages.Payouts))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Payouts</a> <a id="Nav-@(nameof(StoreNavPages.Payouts))" class="nav-link @ViewData.IsActivePage(StoreNavPages.Payouts)" asp-action="Payouts" asp-controller="StorePullPayments" asp-route-storeId="@Context.GetRouteValue("storeId")">Payouts</a>
<vc:ui-extension-point location="store-nav" model="@Model" /> <vc:ui-extension-point location="store-nav" model="@Model"/>
</nav> </nav>

View file

@ -75,9 +75,10 @@
@if (store.IsOwner) @if (store.IsOwner)
{ {
<a asp-action="PaymentMethods" asp-controller="Stores" asp-route-storeId="@store.Id" id="update-store-@store.Id">Settings</a><span> - </span> <a asp-action="PaymentMethods" asp-controller="Stores" asp-route-storeId="@store.Id" id="update-store-@store.Id">Settings</a><span> - </span>
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@store.Name</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete</a><span> - </span>
} }
<a asp-action="DeleteStore" asp-controller="Stores" asp-route-storeId="@store.Id" data-bs-toggle="modal" data-bs-target="#ConfirmModal" data-description="The store <strong>@store.Name</strong> will be permanently deleted. This action will also delete all invoices, apps and data associated with the store." data-confirm-input="DELETE">Delete</a> <a asp-action="PullPayments" asp-controller="StorePullPayments" asp-route-storeId="@store.Id" >Pull Payments</a>
</td> </td>
</tr> </tr>
} }
</tbody> </tbody>