diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index dceca27d4..18b2f98a3 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -2,9 +2,11 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using BTCPayServer.Data; using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Security; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Rates; using Microsoft.AspNetCore.Authorization; @@ -46,7 +48,7 @@ namespace BTCPayServer.Controllers Items = _AppsHelper.Parse(settings.Template, settings.Currency) }); } - + [HttpPost] [Route("/apps/{appId}/pos")] [IgnoreAntiforgeryToken] @@ -90,6 +92,7 @@ namespace BTCPayServer.Controllers title = settings.Title; } var store = await _AppsHelper.GetStore(app); + store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); var invoice = await _InvoiceController.CreateInvoiceCore(new NBitpayClient.Invoice() { ItemDesc = title, diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index 374f887a4..da7bc8524 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -14,7 +14,7 @@ namespace BTCPayServer.Controllers { [EnableCors("BitpayAPI")] [BitpayAPIConstraint] - [Authorize(Policies.CanUseStore.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] + [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] public class InvoiceControllerAPI : Controller { private InvoiceController _InvoiceController; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 084c0dfcd..c430cb8cf 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -499,7 +499,7 @@ namespace BTCPayServer.Controllers return View(model); } 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"); return View(model); diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 14e2d99e2..29ee0f9e5 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -62,6 +62,8 @@ namespace BTCPayServer.Controllers internal async Task> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { + if (!store.HasClaim(Policies.CanCreateInvoice.Key)) + throw new UnauthorizedAccessException(); InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting"); var entity = new InvoiceEntity diff --git a/BTCPayServer/Controllers/PublicController.cs b/BTCPayServer/Controllers/PublicController.cs index f9802ba9d..bec4d906b 100644 --- a/BTCPayServer/Controllers/PublicController.cs +++ b/BTCPayServer/Controllers/PublicController.cs @@ -33,7 +33,7 @@ namespace BTCPayServer.Controllers else { var storeBlob = store.GetStoreBlob(); - if (!storeBlob.PayButtonEnabled) + if (!storeBlob.AnyoneCanInvoice) ModelState.AddModelError("Store", "Store has not enabled Pay Button"); } diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index a431ae7e9..a50ad4b21 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -399,6 +399,7 @@ namespace BTCPayServer.Controllers vm.StoreName = store.StoreName; vm.StoreWebsite = store.StoreWebsite; vm.NetworkFee = !storeBlob.NetworkFeeDisabled; + vm.AnyoneCanCreateInvoice = storeBlob.AnyoneCanInvoice; vm.SpeedPolicy = store.SpeedPolicy; vm.CanDelete = _Repo.CanDeleteStores(); AddPaymentMethods(store, storeBlob, vm); @@ -470,6 +471,7 @@ namespace BTCPayServer.Controllers } var blob = StoreData.GetStoreBlob(); + blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice; blob.NetworkFeeDisabled = !model.NetworkFee; blob.MonitoringExpiration = model.MonitoringExpiration; blob.InvoiceExpiration = model.InvoiceExpiration; @@ -777,7 +779,7 @@ namespace BTCPayServer.Controllers var store = StoreData; var storeBlob = store.GetStoreBlob(); - if (!storeBlob.PayButtonEnabled) + if (!storeBlob.AnyoneCanInvoice) { return View("PayButtonEnable", null); } @@ -800,7 +802,7 @@ namespace BTCPayServer.Controllers public async Task PayButton(bool enableStore) { var blob = StoreData.GetStoreBlob(); - blob.PayButtonEnabled = enableStore; + blob.AnyoneCanInvoice = enableStore; if (StoreData.SetStoreBlob(blob)) { await _Repo.UpdateStore(StoreData); diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index ae1d69a39..19fcdfc25 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -166,24 +166,25 @@ namespace BTCPayServer.Data public Claim[] GetClaims() { List claims = new List(); + claims.AddRange(AdditionalClaims); #pragma warning disable CS0612 // Type or member is obsolete var role = Role; #pragma warning restore CS0612 // Type or member is obsolete if (role == StoreRoles.Owner) { 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(); } 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 @@ -196,6 +197,9 @@ namespace BTCPayServer.Data public List PairedSINs { get; set; } public IEnumerable APIKeys { get; set; } + [NotMapped] + public List AdditionalClaims { get; set; } = new List(); + #pragma warning disable CS0618 public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null) { @@ -302,7 +306,7 @@ namespace BTCPayServer.Data public string RateScript { get; set; } - public bool PayButtonEnabled { get; set; } + public bool AnyoneCanInvoice { get; set; } string _LightningDescriptionTemplate; diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index 1c8e31c0f..9401bb957 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -47,6 +47,9 @@ namespace BTCPayServer.Models.StoreViewModels set; } + [Display(Name = "Allow anyone to create invoice")] + public bool AnyoneCanCreateInvoice { get; set; } + public List DerivationSchemes { get; set; } = new List(); [Display(Name = "Invoice expires if the full amount has not been paid after ... minutes")] diff --git a/BTCPayServer/Security/BitpayAuthentication.cs b/BTCPayServer/Security/BitpayAuthentication.cs index d85503284..2fac207ad 100644 --- a/BTCPayServer/Security/BitpayAuthentication.cs +++ b/BTCPayServer/Security/BitpayAuthentication.cs @@ -88,8 +88,9 @@ namespace BTCPayServer.Security { 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); + store.AdditionalClaims.AddRange(claims); Context.Request.HttpContext.SetStoreData(store); } return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index d41c234e2..f9ce9a1f7 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -12,9 +12,9 @@ namespace BTCPayServer.Security public const string CookieAuthentication = "Identity.Application"; public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) { - AddClaim(options, CanUseStore.Key); AddClaim(options, CanModifyStoreSettings.Key); AddClaim(options, CanModifyServerSettings.Key); + AddClaim(options, CanCreateInvoice.Key); return options; } @@ -27,13 +27,14 @@ namespace BTCPayServer.Security { public const string Key = "btcpay.store.canmodifyserversettings"; } - public class CanUseStore - { - public const string Key = "btcpay.store.canusestore"; - } public class CanModifyStoreSettings { public const string Key = "btcpay.store.canmodifystoresettings"; } + + public class CanCreateInvoice + { + public const string Key = "btcpay.store.cancreateinvoice"; + } } } diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 1ef1b4764..3a68bf032 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -148,7 +148,7 @@ namespace BTCPayServer.Services.Invoices } 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() { @@ -249,7 +249,7 @@ namespace BTCPayServer.Services.Invoices { 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)); } - if(queryObject.Unusual != null) + if (queryObject.Unusual != null) { var unused = queryObject.Unusual.Value; query = query.Where(i => unused == (i.Status == "invalid" || i.ExceptionStatus != null)); @@ -554,7 +554,7 @@ namespace BTCPayServer.Services.Invoices { await context.SaveChangesAsync().ConfigureAwait(false); } - catch(DbUpdateException) { return null; } // Already exists + catch (DbUpdateException) { return null; } // Already exists AddToTextSearch(invoiceId, paymentData.GetSearchTerms()); return entity; } diff --git a/BTCPayServer/Views/Stores/PayButton.cshtml b/BTCPayServer/Views/Stores/PayButton.cshtml index f6a5e8e38..c2768e610 100644 --- a/BTCPayServer/Views/Stores/PayButton.cshtml +++ b/BTCPayServer/Views/Stores/PayButton.cshtml @@ -122,25 +122,6 @@ -

-
-

Disable Pay Button

-
-
-
-
-

- 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. -

- @Html.Hidden("EnableStore", false) - -
-
-
-
- @section HeadScripts { diff --git a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml index bfd326248..ab59924ab 100644 --- a/BTCPayServer/Views/Stores/PayButtonEnable.cshtml +++ b/BTCPayServer/Views/Stores/PayButtonEnable.cshtml @@ -1,6 +1,6 @@ @{ 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"; } @@ -12,10 +12,10 @@

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.

@Html.Hidden("EnableStore", true) - +
diff --git a/BTCPayServer/Views/Stores/UpdateStore.cshtml b/BTCPayServer/Views/Stores/UpdateStore.cshtml index 68f715c37..912806b60 100644 --- a/BTCPayServer/Views/Stores/UpdateStore.cshtml +++ b/BTCPayServer/Views/Stores/UpdateStore.cshtml @@ -46,6 +46,10 @@ +
+ + +