Add btcpay.store.cancreateinvoice claim, and use that for the store

This commit is contained in:
nicolas.dorier 2018-09-08 14:32:26 +09:00
parent e86b4d89ca
commit fed53661b3
14 changed files with 44 additions and 43 deletions

View file

@ -2,9 +2,11 @@
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Models.AppViewModels; using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Security;
using BTCPayServer.Services.Apps; using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@ -46,7 +48,7 @@ namespace BTCPayServer.Controllers
Items = _AppsHelper.Parse(settings.Template, settings.Currency) Items = _AppsHelper.Parse(settings.Template, settings.Currency)
}); });
} }
[HttpPost] [HttpPost]
[Route("/apps/{appId}/pos")] [Route("/apps/{appId}/pos")]
[IgnoreAntiforgeryToken] [IgnoreAntiforgeryToken]
@ -90,6 +92,7 @@ namespace BTCPayServer.Controllers
title = settings.Title; title = settings.Title;
} }
var store = await _AppsHelper.GetStore(app); var store = await _AppsHelper.GetStore(app);
store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id));
var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice()
{ {
ItemDesc = title, ItemDesc = title,

View file

@ -14,7 +14,7 @@ namespace BTCPayServer.Controllers
{ {
[EnableCors("BitpayAPI")] [EnableCors("BitpayAPI")]
[BitpayAPIConstraint] [BitpayAPIConstraint]
[Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)]
public class InvoiceControllerAPI : Controller public class InvoiceControllerAPI : Controller
{ {
private InvoiceController _InvoiceController; private InvoiceController _InvoiceController;

View file

@ -499,7 +499,7 @@ namespace BTCPayServer.Controllers
return View(model); return View(model);
} }
StatusMessage = null; StatusMessage = null;
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) if (!store.HasClaim(Policies.CanCreateInvoice.Key))
{ {
ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice"); ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice");
return View(model); return View(model);

View file

@ -62,6 +62,8 @@ namespace BTCPayServer.Controllers
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
{ {
if (!store.HasClaim(Policies.CanCreateInvoice.Key))
throw new UnauthorizedAccessException();
InvoiceLogs logs = new InvoiceLogs(); InvoiceLogs logs = new InvoiceLogs();
logs.Write("Creation of invoice starting"); logs.Write("Creation of invoice starting");
var entity = new InvoiceEntity var entity = new InvoiceEntity

View file

@ -33,7 +33,7 @@ namespace BTCPayServer.Controllers
else else
{ {
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
if (!storeBlob.PayButtonEnabled) if (!storeBlob.AnyoneCanInvoice)
ModelState.AddModelError("Store", "Store has not enabled Pay Button"); ModelState.AddModelError("Store", "Store has not enabled Pay Button");
} }

View file

@ -399,6 +399,7 @@ namespace BTCPayServer.Controllers
vm.StoreName = store.StoreName; vm.StoreName = store.StoreName;
vm.StoreWebsite = store.StoreWebsite; vm.StoreWebsite = store.StoreWebsite;
vm.NetworkFee = !storeBlob.NetworkFeeDisabled; vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice;
vm.SpeedPolicy = store.SpeedPolicy; vm.SpeedPolicy = store.SpeedPolicy;
vm.CanDelete = _Repo.CanDeleteStores(); vm.CanDelete = _Repo.CanDeleteStores();
AddPaymentMethods(store, storeBlob, vm); AddPaymentMethods(store, storeBlob, vm);
@ -470,6 +471,7 @@ namespace BTCPayServer.Controllers
} }
var blob = StoreData.GetStoreBlob(); var blob = StoreData.GetStoreBlob();
blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice;
blob.NetworkFeeDisabled = !model.NetworkFee; blob.NetworkFeeDisabled = !model.NetworkFee;
blob.MonitoringExpiration = model.MonitoringExpiration; blob.MonitoringExpiration = model.MonitoringExpiration;
blob.InvoiceExpiration = model.InvoiceExpiration; blob.InvoiceExpiration = model.InvoiceExpiration;
@ -777,7 +779,7 @@ namespace BTCPayServer.Controllers
var store = StoreData; var store = StoreData;
var storeBlob = store.GetStoreBlob(); var storeBlob = store.GetStoreBlob();
if (!storeBlob.PayButtonEnabled) if (!storeBlob.AnyoneCanInvoice)
{ {
return View("PayButtonEnable", null); return View("PayButtonEnable", null);
} }
@ -800,7 +802,7 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> PayButton(bool enableStore) public async Task<IActionResult> PayButton(bool enableStore)
{ {
var blob = StoreData.GetStoreBlob(); var blob = StoreData.GetStoreBlob();
blob.PayButtonEnabled = enableStore; blob.AnyoneCanInvoice = enableStore;
if (StoreData.SetStoreBlob(blob)) if (StoreData.SetStoreBlob(blob))
{ {
await _Repo.UpdateStore(StoreData); await _Repo.UpdateStore(StoreData);

View file

@ -166,24 +166,25 @@ namespace BTCPayServer.Data
public Claim[] GetClaims() public Claim[] GetClaims()
{ {
List<Claim> claims = new List<Claim>(); List<Claim> claims = new List<Claim>();
claims.AddRange(AdditionalClaims);
#pragma warning disable CS0612 // Type or member is obsolete #pragma warning disable CS0612 // Type or member is obsolete
var role = Role; var role = Role;
#pragma warning restore CS0612 // Type or member is obsolete #pragma warning restore CS0612 // Type or member is obsolete
if (role == StoreRoles.Owner) if (role == StoreRoles.Owner)
{ {
claims.Add(new Claim(Policies.CanModifyStoreSettings.Key, Id)); claims.Add(new Claim(Policies.CanModifyStoreSettings.Key, Id));
claims.Add(new Claim(Policies.CanUseStore.Key, Id));
} }
if (role == StoreRoles.Guest)
if(role == StoreRoles.Owner || role == StoreRoles.Guest || GetStoreBlob().AnyoneCanInvoice)
{ {
claims.Add(new Claim(Policies.CanUseStore.Key, Id)); claims.Add(new Claim(Policies.CanCreateInvoice.Key, Id));
} }
return claims.ToArray(); return claims.ToArray();
} }
public bool HasClaim(string claim) public bool HasClaim(string claim)
{ {
return GetClaims().Any(c => c.Type == claim); return GetClaims().Any(c => c.Type == claim && c.Value == Id);
} }
public byte[] StoreBlob public byte[] StoreBlob
@ -196,6 +197,9 @@ namespace BTCPayServer.Data
public List<PairedSINData> PairedSINs { get; set; } public List<PairedSINData> PairedSINs { get; set; }
public IEnumerable<APIKeyData> APIKeys { get; set; } public IEnumerable<APIKeyData> APIKeys { get; set; }
[NotMapped]
public List<Claim> AdditionalClaims { get; set; } = new List<Claim>();
#pragma warning disable CS0618 #pragma warning disable CS0618
public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null) public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null)
{ {
@ -302,7 +306,7 @@ namespace BTCPayServer.Data
public string RateScript { get; set; } public string RateScript { get; set; }
public bool PayButtonEnabled { get; set; } public bool AnyoneCanInvoice { get; set; }
string _LightningDescriptionTemplate; string _LightningDescriptionTemplate;

View file

@ -47,6 +47,9 @@ namespace BTCPayServer.Models.StoreViewModels
set; set;
} }
[Display(Name = "Allow anyone to create invoice")]
public bool AnyoneCanCreateInvoice { get; set; }
public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>(); public List<StoreViewModel.DerivationScheme> DerivationSchemes { get; set; } = new List<StoreViewModel.DerivationScheme>();
[Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")] [Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")]

View file

@ -88,8 +88,9 @@ namespace BTCPayServer.Security
{ {
if (storeId != null) if (storeId != null)
{ {
claims.Add(new Claim(Policies.CanUseStore.Key, storeId)); claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId));
var store = await _StoreRepository.FindStore(storeId); var store = await _StoreRepository.FindStore(storeId);
store.AdditionalClaims.AddRange(claims);
Context.Request.HttpContext.SetStoreData(store); Context.Request.HttpContext.SetStoreData(store);
} }
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication));

View file

@ -12,9 +12,9 @@ namespace BTCPayServer.Security
public const string CookieAuthentication = "Identity.Application"; public const string CookieAuthentication = "Identity.Application";
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
{ {
AddClaim(options, CanUseStore.Key);
AddClaim(options, CanModifyStoreSettings.Key); AddClaim(options, CanModifyStoreSettings.Key);
AddClaim(options, CanModifyServerSettings.Key); AddClaim(options, CanModifyServerSettings.Key);
AddClaim(options, CanCreateInvoice.Key);
return options; return options;
} }
@ -27,13 +27,14 @@ namespace BTCPayServer.Security
{ {
public const string Key = "btcpay.store.canmodifyserversettings"; public const string Key = "btcpay.store.canmodifyserversettings";
} }
public class CanUseStore
{
public const string Key = "btcpay.store.canusestore";
}
public class CanModifyStoreSettings public class CanModifyStoreSettings
{ {
public const string Key = "btcpay.store.canmodifystoresettings"; public const string Key = "btcpay.store.canmodifystoresettings";
} }
public class CanCreateInvoice
{
public const string Key = "btcpay.store.cancreateinvoice";
}
} }
} }

View file

@ -148,7 +148,7 @@ namespace BTCPayServer.Services.Invoices
} }
context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id }); context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id });
foreach(var log in creationLogs.ToList()) foreach (var log in creationLogs.ToList())
{ {
context.InvoiceEvents.Add(new InvoiceEventData() context.InvoiceEvents.Add(new InvoiceEventData()
{ {
@ -249,7 +249,7 @@ namespace BTCPayServer.Services.Invoices
{ {
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
catch(DbUpdateException) { } // Probably the invoice does not exists anymore catch (DbUpdateException) { } // Probably the invoice does not exists anymore
} }
} }
@ -441,7 +441,7 @@ namespace BTCPayServer.Services.Invoices
query = query.Where(i => statusSet.Contains(i.Status)); query = query.Where(i => statusSet.Contains(i.Status));
} }
if(queryObject.Unusual != null) if (queryObject.Unusual != null)
{ {
var unused = queryObject.Unusual.Value; var unused = queryObject.Unusual.Value;
query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null)); query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null));
@ -554,7 +554,7 @@ namespace BTCPayServer.Services.Invoices
{ {
await context.SaveChangesAsync().ConfigureAwait(false); await context.SaveChangesAsync().ConfigureAwait(false);
} }
catch(DbUpdateException) { return null; } // Already exists catch (DbUpdateException) { return null; } // Already exists
AddToTextSearch(invoiceId, paymentData.GetSearchTerms()); AddToTextSearch(invoiceId, paymentData.GetSearchTerms());
return entity; return entity;
} }

View file

@ -122,25 +122,6 @@
</div> </div>
</div> </div>
<br /><br />
<hr />
<h3>Disable Pay Button</h3>
<div class="row">
<div class="col-md-10">
<form method="post">
<div class="form-group">
<p>
Disabling this feature will cause your currently used Pay Buttons to stop working.
Customers trying to use Pay Button to create Invoices will be displayed appropriate message.
You can always reenable Pay Buttons at later time.
</p>
@Html.Hidden("EnableStore", false)
<button name="command" type="submit" value="save" class="btn btn-primary">Disable Pay Button</button>
</div>
</form>
</div>
</div>
@section HeadScripts { @section HeadScripts {
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css"> <link rel="stylesheet" href="~/vendor/highlightjs/default.min.css">

View file

@ -1,6 +1,6 @@
@{ @{
Layout = "../Shared/_NavLayout.cshtml"; Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Please confirm you want to enable Pay Button"); ViewData.SetActivePageAndTitle(StoreNavPages.PayButton, "Please confirm you want to allow anyone to create invoices in your store");
ViewBag.MainTitle = "Pay Button"; ViewBag.MainTitle = "Pay Button";
} }
@ -12,10 +12,10 @@
<div class="form-group"> <div class="form-group">
<p> <p>
To start using Pay Buttons you need to explicitly turn on this feature. To start using Pay Buttons you need to explicitly turn on this feature.
Once you do so, valid POST requests from any source will allow creation of Invoices on your instance of BtcPayServer. Once you do so, any source will be able to create an invoice on your instance store.
</p> </p>
@Html.Hidden("EnableStore", true) @Html.Hidden("EnableStore", true)
<button name="command" type="submit" value="save" class="btn btn-primary">Enable Pay Button</button> <button name="command" type="submit" value="save" class="btn btn-primary">Allow anyone to create invoices</button>
</div> </div>
</form> </form>
</div> </div>

View file

@ -46,6 +46,10 @@
<label asp-for="NetworkFee"></label> <label asp-for="NetworkFee"></label>
<input asp-for="NetworkFee" type="checkbox" class="form-check" /> <input asp-for="NetworkFee" type="checkbox" class="form-check" />
</div> </div>
<div class="form-group">
<label asp-for="AnyoneCanCreateInvoice"></label>
<input asp-for="AnyoneCanCreateInvoice" type="checkbox" class="form-check" />
</div>
<div class="form-group"> <div class="form-group">
<label asp-for="InvoiceExpiration"></label> <label asp-for="InvoiceExpiration"></label>
<input asp-for="InvoiceExpiration" class="form-control" /> <input asp-for="InvoiceExpiration" class="form-control" />