From e497903bf419b2a0746746805292c6df5fc6514d Mon Sep 17 00:00:00 2001 From: Andrew Camilleri Date: Thu, 14 Mar 2024 10:25:40 +0100 Subject: [PATCH] Support Admin being able to view stores (#5782) * Support Admin being able to view stores * fix null check * Delete obsolete empty view * Add test * Apply CanViewStoreSettings policy changes Taken from #5719 * Fix Selenium tests * Update dashboard permission requirement --------- Co-authored-by: Dennis Reimann --- BTCPayServer.Tests/SeleniumTester.cs | 21 +++++ BTCPayServer.Tests/SeleniumTests.cs | 51 +++++++++- .../Components/MainNav/Default.cshtml | 7 +- BTCPayServer/Controllers/UIHomeController.cs | 15 +-- .../Controllers/UIInvoiceController.UI.cs | 4 +- .../Controllers/UIPaymentRequestController.cs | 2 +- ...torePullPaymentsController.PullPayments.cs | 7 +- .../UIStoresController.Dashboard.cs | 16 +++- .../Controllers/UIStoresController.Email.cs | 5 +- .../UIStoresController.Integrations.cs | 12 +++ .../UIStoresController.LightningLike.cs | 11 ++- .../Controllers/UIStoresController.Onchain.cs | 21 ++++- .../Controllers/UIStoresController.Roles.cs | 33 +++---- .../Controllers/UIStoresController.cs | 39 +++++--- .../Controllers/UIWalletsController.cs | 4 +- BTCPayServer/Extensions/StoreExtensions.cs | 2 +- BTCPayServer/Forms/UIFormsController.cs | 6 +- ...ningAutomatedPayoutProcessorsController.cs | 3 +- ...hainAutomatedPayoutProcessorsController.cs | 3 +- .../UIPayoutProcessorsController.cs | 2 +- .../Controllers/UICrowdfundController.cs | 2 +- .../Controllers/UIPointOfSaleController.cs | 2 +- .../Security/CookieAuthorizationHandler.cs | 38 +++++--- .../Shared/Crowdfund/NavExtension.cshtml | 18 +++- .../Shared/Crowdfund/UpdateCrowdfund.cshtml | 11 ++- BTCPayServer/Views/Shared/EmailsBody.cshtml | 21 +---- BTCPayServer/Views/Shared/EmailsTest.cshtml | 13 +++ BTCPayServer/Views/Shared/ListRoles.cshtml | 93 ++++++++----------- .../Shared/PointOfSale/NavExtension.cshtml | 18 +++- .../PointOfSale/UpdatePointOfSale.cshtml | 15 +-- BTCPayServer/Views/UIForms/FormsList.cshtml | 13 +-- .../Views/UIInvoice/ListInvoices.cshtml | 7 +- .../Configure.cshtml | 4 +- .../Configure.cshtml | 5 +- .../ConfigureStorePayoutProcessors.cshtml | 7 +- BTCPayServer/Views/UIServer/Emails.cshtml | 1 + .../Views/UIStores/CheckoutAppearance.cshtml | 5 +- BTCPayServer/Views/UIStores/Dashboard.cshtml | 1 - .../Views/UIStores/GeneralSettings.cshtml | 42 +++++---- BTCPayServer/Views/UIStores/Index.cshtml | 0 BTCPayServer/Views/UIStores/ListTokens.cshtml | 12 ++- BTCPayServer/Views/UIStores/Rates.cshtml | 12 ++- .../Views/UIStores/StoreEmailSettings.cshtml | 6 +- .../Views/UIStores/StoreEmails.cshtml | 5 +- BTCPayServer/Views/UIStores/StoreUsers.cshtml | 61 ++++++------ BTCPayServer/Views/UIStores/Webhooks.cshtml | 10 +- BTCPayServer/Views/UIStores/_Nav.cshtml | 22 ++--- .../Views/UIUserStores/ListStores.cshtml | 8 +- BTCPayServer/wwwroot/main/layout.css | 10 +- 49 files changed, 440 insertions(+), 286 deletions(-) create mode 100644 BTCPayServer/Views/Shared/EmailsTest.cshtml delete mode 100644 BTCPayServer/Views/UIStores/Index.cshtml diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 09662aab9..4b9df09ba 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -645,5 +645,26 @@ retry: Driver.FindElement(By.Id($"SectionNav-{navPages}")).Click(); } } + + public void AssertPageAccess(bool shouldHaveAccess, string url) + { + GoToUrl(url); + Assert.DoesNotMatch("404 - Page not found $"/stores/{storeId}/{subPath}"; + + // Owner access + s.AssertPageAccess(false, GetStorePath("")); + s.AssertPageAccess(true, GetStorePath("reports")); + s.AssertPageAccess(true, GetStorePath("invoices")); + s.AssertPageAccess(false, GetStorePath("invoices/create")); + s.AssertPageAccess(true, GetStorePath("payment-requests")); + s.AssertPageAccess(false, GetStorePath("payment-requests/edit")); + s.AssertPageAccess(true, GetStorePath("pull-payments")); + s.AssertPageAccess(true, GetStorePath("payouts")); + s.AssertPageAccess(false, GetStorePath("onchain/BTC")); + s.AssertPageAccess(false, GetStorePath("onchain/BTC/settings")); + s.AssertPageAccess(false, GetStorePath("lightning/BTC")); + s.AssertPageAccess(false, GetStorePath("lightning/BTC/settings")); + s.AssertPageAccess(false, GetStorePath("apps/create")); + foreach (var path in storeSettingsPaths) + { // should have view access to settings, but no submit buttons or create links + s.AssertPageAccess(true, $"stores/{storeId}/{path}"); + s.Driver.ElementDoesNotExist(By.CssSelector("#mainContent .btn-primary")); + } + } private static void CanBrowseContent(SeleniumTester s) { diff --git a/BTCPayServer/Components/MainNav/Default.cshtml b/BTCPayServer/Components/MainNav/Default.cshtml index da209c340..36b2114cd 100644 --- a/BTCPayServer/Components/MainNav/Default.cshtml +++ b/BTCPayServer/Components/MainNav/Default.cshtml @@ -26,16 +26,16 @@ { @if (Model.Store != null) { -
+ diff --git a/BTCPayServer/Controllers/UIHomeController.cs b/BTCPayServer/Controllers/UIHomeController.cs index 8b1ab5ee8..98c5714e0 100644 --- a/BTCPayServer/Controllers/UIHomeController.cs +++ b/BTCPayServer/Controllers/UIHomeController.cs @@ -76,17 +76,17 @@ namespace BTCPayServer.Controllers if (storeId != null) { // verify store exists and redirect to it - var store = await _storeRepository.FindStore(storeId, userId); + var store = await _storeRepository.FindStore(storeId); if (store != null) { - return RedirectToStore(userId, store); + return RedirectToStore(userId, storeId); } } var stores = await _storeRepository.GetStoresByUserId(userId); var activeStore = stores.FirstOrDefault(s => !s.Archived); return activeStore != null - ? RedirectToStore(userId, activeStore) + ? RedirectToStore(userId, activeStore.Id) : RedirectToAction(nameof(UIUserStoresController.CreateStore), "UIUserStores"); } @@ -198,14 +198,9 @@ namespace BTCPayServer.Controllers { return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } - public static RedirectToActionResult RedirectToStore(string userId, StoreData store) + public static RedirectToActionResult RedirectToStore(string userId, string storeId) { - var perms = store.GetPermissionSet(userId); - if (perms.Contains(Policies.CanModifyStoreSettings, store.Id)) - return new RedirectToActionResult("Dashboard", "UIStores", new {storeId = store.Id}); - if (perms.Contains(Policies.CanViewInvoices, store.Id)) - return new RedirectToActionResult("ListInvoices", "UIInvoice", new { storeId = store.Id }); - return new RedirectToActionResult("Index", "UIStores", new {storeId = store.Id}); + return new RedirectToActionResult("Index", "UIStores", new {storeId}); } } } diff --git a/BTCPayServer/Controllers/UIInvoiceController.UI.cs b/BTCPayServer/Controllers/UIInvoiceController.UI.cs index b2afa60e3..73b2528d1 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.UI.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.UI.cs @@ -1144,7 +1144,7 @@ namespace BTCPayServer.Controllers [HttpGet("/stores/{storeId}/invoices/create")] [HttpGet("invoices/create")] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task CreateInvoice(InvoicesModel? model = null) { @@ -1154,7 +1154,7 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(UIHomeController.Index), "UIHome"); } - var store = await _StoreRepository.FindStore(model.StoreId, GetUserId()); + var store = await _StoreRepository.FindStore(model.StoreId); if (store == null) return NotFound(); diff --git a/BTCPayServer/Controllers/UIPaymentRequestController.cs b/BTCPayServer/Controllers/UIPaymentRequestController.cs index 62ae18c86..41580148a 100644 --- a/BTCPayServer/Controllers/UIPaymentRequestController.cs +++ b/BTCPayServer/Controllers/UIPaymentRequestController.cs @@ -108,7 +108,7 @@ namespace BTCPayServer.Controllers } [HttpGet("/stores/{storeId}/payment-requests/edit/{payReqId?}")] - [Authorize(Policy = Policies.CanViewPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyPaymentRequests, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task EditPaymentRequest(string storeId, string payReqId) { var store = GetCurrentStore(); diff --git a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs index 2e3cf8812..b2d3eea90 100644 --- a/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs +++ b/BTCPayServer/Controllers/UIStorePullPaymentsController.PullPayments.cs @@ -83,7 +83,7 @@ namespace BTCPayServer.Controllers Message = "You must enable at least one payment method before creating a pull payment.", Severity = StatusMessageModel.StatusSeverity.Error }); - return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId }); + return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); } return View(new NewPullPaymentModel @@ -161,6 +161,7 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(PullPayments), new { storeId = storeId }); } + [Authorize(Policy = Policies.CanViewPullPayments, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("stores/{storeId}/pull-payments")] public async Task PullPayments( string storeId, @@ -199,7 +200,7 @@ namespace BTCPayServer.Controllers Message = "You must enable at least one payment method before creating a pull payment.", Severity = StatusMessageModel.StatusSeverity.Error }); - return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId }); + return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); } var vm = this.ParseListQuery(new PullPaymentsModel @@ -482,7 +483,7 @@ namespace BTCPayServer.Controllers Message = "You must enable at least one payment method before creating a payout.", Severity = StatusMessageModel.StatusSeverity.Error }); - return RedirectToAction(nameof(UIStoresController.Dashboard), "UIStores", new { storeId }); + return RedirectToAction(nameof(UIStoresController.Index), "UIStores", new { storeId }); } var vm = this.ParseListQuery(new PayoutsModel diff --git a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs index 0ad491f07..b169ac61e 100644 --- a/BTCPayServer/Controllers/UIStoresController.Dashboard.cs +++ b/BTCPayServer/Controllers/UIStoresController.Dashboard.cs @@ -1,12 +1,15 @@ #nullable enable using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Client; using BTCPayServer.Components.StoreLightningBalance; using BTCPayServer.Components.StoreNumbers; using BTCPayServer.Components.StoreRecentInvoices; using BTCPayServer.Components.StoreRecentTransactions; using BTCPayServer.Data; using BTCPayServer.Models.StoreViewModels; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers @@ -14,9 +17,13 @@ namespace BTCPayServer.Controllers public partial class UIStoresController { [HttpGet("{storeId}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Dashboard() { var store = CurrentStore; + if (store is null) + return NotFound(); + var storeBlob = store.GetStoreBlob(); AddPaymentMethods(store, storeBlob, @@ -38,16 +45,17 @@ namespace BTCPayServer.Controllers }; // Widget data - if (!vm.WalletEnabled && !vm.LightningEnabled) + if (vm is { WalletEnabled: false, LightningEnabled: false }) return View(vm); var userId = GetUserId(); if (userId is null) return NotFound(); + var apps = await _appService.GetAllApps(userId, false, store.Id); foreach (var app in apps) { - var appData = await _appService.GetAppDataIfOwner(userId, app.Id); + var appData = await _appService.GetAppData(userId, app.Id); vm.Apps.Add(appData); } @@ -55,6 +63,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/dashboard/{cryptoCode}/lightning/balance")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult LightningBalance(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -66,6 +75,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/dashboard/{cryptoCode}/numbers")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult StoreNumbers(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -77,6 +87,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/dashboard/{cryptoCode}/recent-transactions")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult RecentTransactions(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -88,6 +99,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/dashboard/{cryptoCode}/recent-invoices")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult RecentInvoices(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); diff --git a/BTCPayServer/Controllers/UIStoresController.Email.cs b/BTCPayServer/Controllers/UIStoresController.Email.cs index 5cfd68b42..720e51de1 100644 --- a/BTCPayServer/Controllers/UIStoresController.Email.cs +++ b/BTCPayServer/Controllers/UIStoresController.Email.cs @@ -7,9 +7,11 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.Services.Mails; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using MimeKit; @@ -43,6 +45,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/emails")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task StoreEmails(string storeId, StoreEmailRuleViewModel vm, string command) { vm.Rules ??= new List(); @@ -185,13 +188,13 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/email-settings")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task StoreEmailSettings(string storeId, EmailsViewModel model, string command) { var store = HttpContext.GetStoreData(); if (store == null) return NotFound(); - var emailSender = await _emailSenderFactory.GetEmailSender(store.Id) as StoreEmailSender; var fallbackSettings = await _emailSenderFactory.GetEmailSender(store.Id) is StoreEmailSender { FallbackSender: not null } storeSender ? await storeSender.FallbackSender.GetEmailSettings() : null; diff --git a/BTCPayServer/Controllers/UIStoresController.Integrations.cs b/BTCPayServer/Controllers/UIStoresController.Integrations.cs index dc5c6cbe6..573dd8d08 100644 --- a/BTCPayServer/Controllers/UIStoresController.Integrations.cs +++ b/BTCPayServer/Controllers/UIStoresController.Integrations.cs @@ -4,10 +4,12 @@ using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Client.Models; using BTCPayServer.Data; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using NBitcoin; using NBitcoin.DataEncoders; @@ -46,6 +48,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/webhooks/new")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult NewWebhook() { return View(nameof(ModifyWebhook), new EditWebhookViewModel @@ -58,6 +61,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/webhooks/{webhookId}/remove")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteWebhook(string webhookId) { var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId); @@ -68,6 +72,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/webhooks/{webhookId}/remove")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteWebhookPost(string webhookId) { var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId); @@ -80,6 +85,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/webhooks/new")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task NewWebhook(string storeId, EditWebhookViewModel viewModel) { if (!ModelState.IsValid) @@ -91,6 +97,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/webhooks/{webhookId}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ModifyWebhook(string webhookId) { var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId); @@ -107,6 +114,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/webhooks/{webhookId}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ModifyWebhook(string webhookId, EditWebhookViewModel viewModel) { var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId); @@ -121,6 +129,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/webhooks/{webhookId}/test")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task TestWebhook(string webhookId) { var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId); @@ -131,6 +140,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/webhooks/{webhookId}/test")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task TestWebhook(string webhookId, TestWebhookViewModel viewModel, CancellationToken cancellationToken) { var result = await WebhookNotificationManager.TestWebhook(CurrentStore.Id, webhookId, viewModel.Type, cancellationToken); @@ -148,6 +158,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task RedeliverWebhook(string webhookId, string deliveryId) { var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId); @@ -168,6 +179,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WebhookDelivery(string webhookId, string deliveryId) { var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId); diff --git a/BTCPayServer/Controllers/UIStoresController.LightningLike.cs b/BTCPayServer/Controllers/UIStoresController.LightningLike.cs index d19980488..20d192dfe 100644 --- a/BTCPayServer/Controllers/UIStoresController.LightningLike.cs +++ b/BTCPayServer/Controllers/UIStoresController.LightningLike.cs @@ -5,7 +5,7 @@ using System.Threading; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; -using BTCPayServer.Components.StoreLightningBalance; +using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Data; using BTCPayServer.Lightning; @@ -14,7 +14,7 @@ using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments; using BTCPayServer.Payments.Lightning; -using BTCPayServer.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; @@ -22,8 +22,8 @@ namespace BTCPayServer.Controllers { public partial class UIStoresController { - [HttpGet("{storeId}/lightning/{cryptoCode}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult Lightning(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -85,6 +85,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/lightning/{cryptoCode}/setup")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult SetupLightningNode(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -101,6 +102,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/lightning/{cryptoCode}/setup")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task SetupLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode) { vm.CryptoCode = cryptoCode; @@ -217,6 +219,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/lightning/{cryptoCode}/settings")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult LightningSettings(string storeId, string cryptoCode) { var store = HttpContext.GetStoreData(); @@ -257,6 +260,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/lightning/{cryptoCode}/settings")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task LightningSettings(LightningSettingsViewModel vm) { var store = HttpContext.GetStoreData(); @@ -310,6 +314,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/lightning/{cryptoCode}/status")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task SetLightningNodeEnabled(string storeId, string cryptoCode, bool enabled) { var store = HttpContext.GetStoreData(); diff --git a/BTCPayServer/Controllers/UIStoresController.Onchain.cs b/BTCPayServer/Controllers/UIStoresController.Onchain.cs index 934940672..4c8e563a1 100644 --- a/BTCPayServer/Controllers/UIStoresController.Onchain.cs +++ b/BTCPayServer/Controllers/UIStoresController.Onchain.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; @@ -8,12 +7,12 @@ using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Data; using BTCPayServer.Events; -using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; using BTCPayServer.Payments; -using BTCPayServer.Services; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using NBitcoin; @@ -27,6 +26,7 @@ namespace BTCPayServer.Controllers public partial class UIStoresController { [HttpGet("{storeId}/onchain/{cryptoCode}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public ActionResult SetupWallet(WalletSetupViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out var store, out _); @@ -42,6 +42,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/import/{method?}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ImportWallet(WalletSetupViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out _, out var network); @@ -71,6 +72,7 @@ namespace BTCPayServer.Controllers [HttpPost("{storeId}/onchain/{cryptoCode}/modify")] [HttpPost("{storeId}/onchain/{cryptoCode}/import/{method}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task UpdateWallet(WalletSetupViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network); @@ -197,6 +199,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/generate/{method?}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task GenerateWallet(WalletSetupViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out _, out var network); @@ -235,8 +238,11 @@ namespace BTCPayServer.Controllers return View(vm.ViewName, vm); } + internal GenerateWalletResponse GenerateWalletResponse; + [HttpPost("{storeId}/onchain/{cryptoCode}/generate/{method}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task GenerateWallet(string storeId, string cryptoCode, WalletSetupMethod method, WalletSetupRequest request) { var checkResult = IsAvailable(cryptoCode, out _, out var network); @@ -356,6 +362,7 @@ namespace BTCPayServer.Controllers // The purpose of this action is to show the user a success message, which confirms // that the store settings have been updated after generating a new wallet. [HttpGet("{storeId}/onchain/{cryptoCode}/generate/confirm")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public ActionResult GenerateWalletConfirm(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out _, out var network); @@ -371,6 +378,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/settings")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WalletSettings(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); @@ -440,6 +448,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/onchain/{cryptoCode}/settings/wallet")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task UpdateWalletSettings(WalletSettingsViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out var store, out _); @@ -549,6 +558,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/onchain/{cryptoCode}/settings/payment")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task UpdatePaymentSettings(WalletSettingsViewModel vm) { var checkResult = IsAvailable(vm.CryptoCode, out var store, out _); @@ -612,6 +622,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/seed")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WalletSeed(string storeId, string cryptoCode, CancellationToken cancellationToken = default) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); @@ -658,6 +669,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/replace")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public ActionResult ReplaceWallet(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); @@ -677,6 +689,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/onchain/{cryptoCode}/replace")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult ConfirmReplaceWallet(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out _); @@ -695,6 +708,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/onchain/{cryptoCode}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public ActionResult DeleteWallet(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); @@ -714,6 +728,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/onchain/{cryptoCode}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ConfirmDeleteWallet(string storeId, string cryptoCode) { var checkResult = IsAvailable(cryptoCode, out var store, out var network); diff --git a/BTCPayServer/Controllers/UIStoresController.Roles.cs b/BTCPayServer/Controllers/UIStoresController.Roles.cs index cb1e52738..1a519260c 100644 --- a/BTCPayServer/Controllers/UIStoresController.Roles.cs +++ b/BTCPayServer/Controllers/UIStoresController.Roles.cs @@ -1,15 +1,12 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Data; using System.Linq; using System.Threading.Tasks; -using Amazon.S3.Transfer; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; +using BTCPayServer.Client; using BTCPayServer.Models.ServerViewModels; using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers @@ -60,19 +57,19 @@ namespace BTCPayServer.Controllers ModelState.Remove(nameof(role)); return View(new UpdateRoleViewModel()); } - else + + var roleData = await storeRepository.GetStoreRole(new StoreRoleId(storeId, role)); + if (roleData == null) + return NotFound(); + return View(new UpdateRoleViewModel { - var roleData = await storeRepository.GetStoreRole(new StoreRoleId(storeId, role)); - if (roleData == null) - return NotFound(); - return View(new UpdateRoleViewModel() - { - Policies = roleData.Permissions, - Role = roleData.Role - }); - } - } + Policies = roleData.Permissions, + Role = roleData.Role + }); + } + [HttpPost("{storeId}/roles/{role}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task CreateOrEditRole( string storeId, [FromServices] StoreRepository storeRepository, @@ -119,10 +116,9 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(ListRoles), new { storeId }); } - - [HttpGet("{storeId}/roles/{role}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteRole( string storeId, [FromServices] StoreRepository storeRepository, @@ -142,6 +138,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/roles/{role}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteRolePost( string storeId, [FromServices] StoreRepository storeRepository, diff --git a/BTCPayServer/Controllers/UIStoresController.cs b/BTCPayServer/Controllers/UIStoresController.cs index fba2572bc..bf8ca7fc2 100644 --- a/BTCPayServer/Controllers/UIStoresController.cs +++ b/BTCPayServer/Controllers/UIStoresController.cs @@ -12,7 +12,6 @@ using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Configuration; using BTCPayServer.Data; -using BTCPayServer.HostedServices; using BTCPayServer.HostedServices.Webhooks; using BTCPayServer.Models; using BTCPayServer.Models.StoreViewModels; @@ -39,10 +38,9 @@ using StoreData = BTCPayServer.Data.StoreData; namespace BTCPayServer.Controllers { - [Route("stores")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class UIStoresController : Controller { @@ -145,21 +143,19 @@ namespace BTCPayServer.Controllers if (string.IsNullOrEmpty(userId)) return Forbid(); - var store = await _Repo.FindStore(storeId, userId); + var store = await _Repo.FindStore(storeId); if (store is null) - { - return Forbid(); - } - HttpContext.SetStoreData(store); - if (store.GetPermissionSet(userId).Contains(Policies.CanModifyStoreSettings, storeId)) + return NotFound(); + + if ((await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings)).Succeeded) { return RedirectToAction("Dashboard", new { storeId }); } - if (store.GetPermissionSet(userId).Contains(Policies.CanViewInvoices, storeId)) + if ((await _authorizationService.AuthorizeAsync(User, Policies.CanViewInvoices)).Succeeded) { return RedirectToAction("ListInvoices", "UIInvoice", new { storeId }); } - return View(); + return Forbid(); } [HttpGet("{storeId}/users")] @@ -182,9 +178,10 @@ namespace BTCPayServer.Controllers }).ToList(); } - public StoreData CurrentStore => HttpContext.GetStoreData(); + public StoreData? CurrentStore => HttpContext.GetStoreData(); [HttpPost("{storeId}/users")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task StoreUsers(string storeId, StoreUsersViewModel vm) { await FillUsers(vm); @@ -217,6 +214,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/users/{userId}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteStoreUser(string userId) { var user = await _UserManager.FindByIdAsync(userId); @@ -226,6 +224,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/users/{userId}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteStoreUserPost(string storeId, string userId) { if (await _Repo.RemoveStoreUser(storeId, userId)) @@ -255,6 +254,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/rates")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Rates(RatesViewModel model, string? command = null, string? storeId = null, CancellationToken cancellationToken = default) { if (command == "scripting-on") @@ -373,6 +373,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/rates/confirm")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult ShowRateRules(bool scripting) { return View("Confirm", new ConfirmModel @@ -387,6 +388,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/rates/confirm")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ShowRateRulesPost(bool scripting) { var blob = CurrentStore.GetStoreBlob(); @@ -487,6 +489,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/checkout")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task CheckoutAppearance(CheckoutAppearanceViewModel model, [FromForm] bool RemoveSoundFile = false) { bool needUpdate = false; @@ -723,6 +726,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/settings")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task GeneralSettings( GeneralSettingsViewModel model, [FromForm] bool RemoveLogoFile = false, @@ -863,7 +867,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/archive")] - [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie, Policy = Policies.CanModifyStoreSettings)] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ToggleArchive(string storeId) { CurrentStore.Archived = !CurrentStore.Archived; @@ -880,12 +884,14 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult DeleteStore(string storeId) { return View("Confirm", new ConfirmModel("Delete store", "The store will be permanently deleted. This action will also delete all invoices, apps and data associated with the store. Are you sure?", "Delete")); } [HttpPost("{storeId}/delete")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task DeleteStorePost(string storeId) { await _Repo.DeleteStore(CurrentStore.Id); @@ -945,6 +951,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/tokens/{tokenId}/revoke")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task RevokeToken(string tokenId) { var token = await _TokenRepository.GetToken(tokenId); @@ -954,6 +961,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/tokens/{tokenId}/revoke")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task RevokeTokenConfirm(string tokenId) { var token = await _TokenRepository.GetToken(tokenId); @@ -967,6 +975,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/tokens/{tokenId}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ShowToken(string tokenId) { var token = await _TokenRepository.GetToken(tokenId); @@ -976,6 +985,7 @@ namespace BTCPayServer.Controllers } [HttpGet("{storeId}/tokens/create")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult CreateToken(string storeId) { var model = new CreateTokenViewModel(); @@ -987,6 +997,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/tokens/create")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task CreateToken(string storeId, CreateTokenViewModel model) { if (!ModelState.IsValid) @@ -1065,6 +1076,7 @@ namespace BTCPayServer.Controllers } [HttpPost("{storeId}/tokens/apikey")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task GenerateAPIKey(string storeId, string command = "") { var store = HttpContext.GetStoreData(); @@ -1129,6 +1141,7 @@ namespace BTCPayServer.Controllers } [HttpPost("/api-access-request")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Pair(string pairingCode, string storeId) { if (pairingCode == null) diff --git a/BTCPayServer/Controllers/UIWalletsController.cs b/BTCPayServer/Controllers/UIWalletsController.cs index 5df8150a8..9001c9910 100644 --- a/BTCPayServer/Controllers/UIWalletsController.cs +++ b/BTCPayServer/Controllers/UIWalletsController.cs @@ -439,7 +439,7 @@ namespace BTCPayServer.Controllers { if (walletId?.StoreId == null) return NotFound(); - var store = await Repository.FindStore(walletId.StoreId, GetUserId()); + var store = await Repository.FindStore(walletId.StoreId); var paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null || store is null) return NotFound(); @@ -564,7 +564,7 @@ namespace BTCPayServer.Controllers { if (walletId?.StoreId == null) return NotFound(); - var store = await Repository.FindStore(walletId.StoreId, GetUserId()); + var store = await Repository.FindStore(walletId.StoreId); if (store == null) return NotFound(); var network = NetworkProvider.GetNetwork(walletId.CryptoCode); diff --git a/BTCPayServer/Extensions/StoreExtensions.cs b/BTCPayServer/Extensions/StoreExtensions.cs index 67862acb0..f944b2adb 100644 --- a/BTCPayServer/Extensions/StoreExtensions.cs +++ b/BTCPayServer/Extensions/StoreExtensions.cs @@ -10,7 +10,7 @@ namespace BTCPayServer { public static StoreRole? GetStoreRoleOfUser(this StoreData store, string userId) { - return store.UserStores.FirstOrDefault(r => r.ApplicationUserId == userId)?.StoreRole; + return store.UserStores?.FirstOrDefault(r => r.ApplicationUserId == userId)?.StoreRole; } public static PermissionSet GetPermissionSet(this StoreRole storeRole, string storeId) diff --git a/BTCPayServer/Forms/UIFormsController.cs b/BTCPayServer/Forms/UIFormsController.cs index b83e189b6..af32c4078 100644 --- a/BTCPayServer/Forms/UIFormsController.cs +++ b/BTCPayServer/Forms/UIFormsController.cs @@ -22,7 +22,7 @@ using Newtonsoft.Json.Linq; namespace BTCPayServer.Forms; -[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] +[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public class UIFormsController : Controller { private readonly FormDataService _formDataService; @@ -48,6 +48,7 @@ public class UIFormsController : Controller } [HttpGet("~/stores/{storeId}/forms/new")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public IActionResult Create(string storeId) { var vm = new ModifyForm { FormConfig = new Form().ToString() }; @@ -55,6 +56,7 @@ public class UIFormsController : Controller } [HttpGet("~/stores/{storeId}/forms/modify/{id}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Modify(string storeId, string id) { var form = await _formDataService.GetForm(storeId, id); @@ -66,6 +68,7 @@ public class UIFormsController : Controller } [HttpPost("~/stores/{storeId}/forms/modify/{id?}")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Modify(string storeId, string? id, ModifyForm modifyForm) { if (id is not null) @@ -122,6 +125,7 @@ public class UIFormsController : Controller } [HttpPost("~/stores/{storeId}/forms/{id}/remove")] + [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Remove(string storeId, string id) { await _formDataService.RemoveForm(id, storeId); diff --git a/BTCPayServer/PayoutProcessors/Lightning/UILightningAutomatedPayoutProcessorsController.cs b/BTCPayServer/PayoutProcessors/Lightning/UILightningAutomatedPayoutProcessorsController.cs index 3ace5a038..1040b8a57 100644 --- a/BTCPayServer/PayoutProcessors/Lightning/UILightningAutomatedPayoutProcessorsController.cs +++ b/BTCPayServer/PayoutProcessors/Lightning/UILightningAutomatedPayoutProcessorsController.cs @@ -31,9 +31,10 @@ public class UILightningAutomatedPayoutProcessorsController : Controller _lightningAutomatedPayoutSenderFactory = lightningAutomatedPayoutSenderFactory; _payoutProcessorService = payoutProcessorService; } + [HttpGet("~/stores/{storeId}/payout-processors/lightning-automated/{cryptocode}")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Configure(string storeId, string cryptoCode) { if (!_lightningAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => diff --git a/BTCPayServer/PayoutProcessors/OnChain/UIOnChainAutomatedPayoutProcessorsController.cs b/BTCPayServer/PayoutProcessors/OnChain/UIOnChainAutomatedPayoutProcessorsController.cs index d55a53d41..99c660fa3 100644 --- a/BTCPayServer/PayoutProcessors/OnChain/UIOnChainAutomatedPayoutProcessorsController.cs +++ b/BTCPayServer/PayoutProcessors/OnChain/UIOnChainAutomatedPayoutProcessorsController.cs @@ -34,10 +34,9 @@ public class UIOnChainAutomatedPayoutProcessorsController : Controller _payoutProcessorService = payoutProcessorService; } - [HttpGet("~/stores/{storeId}/payout-processors/onchain-automated/{cryptocode}")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Configure(string storeId, string cryptoCode) { if (!_onChainAutomatedPayoutSenderFactory.GetSupportedPaymentMethods().Any(id => diff --git a/BTCPayServer/PayoutProcessors/UIPayoutProcessorsController.cs b/BTCPayServer/PayoutProcessors/UIPayoutProcessorsController.cs index 1b2e44af8..cb9e40827 100644 --- a/BTCPayServer/PayoutProcessors/UIPayoutProcessorsController.cs +++ b/BTCPayServer/PayoutProcessors/UIPayoutProcessorsController.cs @@ -34,7 +34,7 @@ public class UIPayoutProcessorsController : Controller [HttpGet("~/stores/{storeId}/payout-processors")] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task ConfigureStorePayoutProcessors(string storeId) { var activeProcessors = diff --git a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs index 4e6cc1d3f..6de3851be 100644 --- a/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs +++ b/BTCPayServer/Plugins/Crowdfund/Controllers/UICrowdfundController.cs @@ -379,7 +379,7 @@ namespace BTCPayServer.Plugins.Crowdfund.Controllers return View("Views/UIForms/View", viewModel); } - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/settings/crowdfund")] public async Task UpdateCrowdfund(string appId) { diff --git a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs index 2ed29b23e..6126d0776 100644 --- a/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs +++ b/BTCPayServer/Plugins/PointOfSale/Controllers/UIPointOfSaleController.cs @@ -558,7 +558,7 @@ namespace BTCPayServer.Plugins.PointOfSale.Controllers return Json(recent); } - [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("{appId}/settings/pos")] public async Task UpdatePointOfSale(string appId) { diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs index 2a09bf6f1..8f6ce4edd 100644 --- a/BTCPayServer/Security/CookieAuthorizationHandler.cs +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -1,5 +1,5 @@ using System; -using System.Linq; +using System.Collections.Generic; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Abstractions.Contracts; @@ -42,7 +42,10 @@ namespace BTCPayServer.Security _pluginHookService = pluginHookService; _paymentRequestRepository = paymentRequestRepository; } - + //TODO: In the future, we will add these store permissions to actual aspnet roles, and remove this class. + private static readonly PermissionSet ServerAdminRolePermissions = + new PermissionSet(new[] {Permission.Create(Policies.CanViewStoreSettings, null)}); + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) { if (context.User.Identity.AuthenticationType != AuthenticationSchemes.Cookie) @@ -67,7 +70,10 @@ namespace BTCPayServer.Security storeId = s; } else + { storeId = _httpContext.GetImplicitStoreId(); + store = _httpContext.GetStoreData(); + } var routeData = _httpContext.GetRouteData(); if (routeData != null) { @@ -124,12 +130,9 @@ namespace BTCPayServer.Security storeId = null; } - if (!string.IsNullOrEmpty(storeId)) + if (!string.IsNullOrEmpty(storeId) && store is null) { - var cachedStore = _httpContext.GetStoreData(); - store = cachedStore?.Id == storeId - ? cachedStore - : await _storeRepository.FindStore(storeId, userId); + store = await _storeRepository.FindStore(storeId, userId); } if (Policies.IsServerPolicy(policy) && isAdmin) @@ -142,14 +145,18 @@ namespace BTCPayServer.Security } else if (Policies.IsStorePolicy(policy)) { - if (store is not null) + if (isAdmin && storeId is not null) { - if (store.HasPermission(userId,policy)) - { - success = true; - } + success = ServerAdminRolePermissions.HasPermission(policy, storeId); + } - else if (requiredUnscoped) + + if (!success && store?.HasPermission(userId, policy) is true) + { + success = true; + } + + if (!success && store is null && requiredUnscoped) { success = true; } @@ -166,6 +173,11 @@ namespace BTCPayServer.Security context.Succeed(requirement); if (!explicitResource) { + if (storeId is not null && store is null) + { + store = await _storeRepository.FindStore(storeId); + + } if (store != null) { if (_httpContext.GetStoreData()?.Id != store.Id) diff --git a/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml b/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml index cd17b4102..4428a60ea 100644 --- a/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml +++ b/BTCPayServer/Views/Shared/Crowdfund/NavExtension.cshtml @@ -1,8 +1,6 @@ @using BTCPayServer.Client @using Microsoft.AspNetCore.Mvc.TagHelpers @using BTCPayServer.Views.Apps -@using BTCPayServer.Abstractions.Extensions -@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Plugins.Crowdfund @using BTCPayServer.Services.Apps @inject AppService AppService; @@ -14,20 +12,30 @@ @if (store != null) { var appType = AppService.GetAppType(CrowdfundAppType.AppType)!; + var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList(); - @foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type)) + @if (apps.Any()) { - + } + @foreach (var app in apps) + { + -
-
-
-
-

Testing

-

- To test your settings, enter an email address below. -

- - - -
- +
diff --git a/BTCPayServer/Views/Shared/EmailsTest.cshtml b/BTCPayServer/Views/Shared/EmailsTest.cshtml new file mode 100644 index 000000000..efcaa47a7 --- /dev/null +++ b/BTCPayServer/Views/Shared/EmailsTest.cshtml @@ -0,0 +1,13 @@ +@model BTCPayServer.Models.EmailsViewModel + +

Testing

+
+
+
+ + + +
+ +
+
diff --git a/BTCPayServer/Views/Shared/ListRoles.cshtml b/BTCPayServer/Views/Shared/ListRoles.cshtml index f93104a76..102238c64 100644 --- a/BTCPayServer/Views/Shared/ListRoles.cshtml +++ b/BTCPayServer/Views/Shared/ListRoles.cshtml @@ -1,32 +1,24 @@ -@using BTCPayServer.Components @using BTCPayServer.Views.Server @using BTCPayServer.Views.Stores @using Microsoft.AspNetCore.Mvc.TagHelpers -@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Client @model BTCPayServer.Models.ServerViewModels.RolesViewModel @{ Layout = "_NavLayout.cshtml"; var storeId = Context.GetRouteValue("storeId") as string; var controller = ViewContext.RouteData.Values["controller"].ToString().TrimEnd("Controller", StringComparison.InvariantCultureIgnoreCase); - if (storeId is null) + if (string.IsNullOrEmpty(storeId)) ViewData.SetActivePage(ServerNavPages.Roles); - else - { ViewData.SetActivePage(StoreNavPages.Roles); - } + var permission = string.IsNullOrEmpty(storeId) ? Policies.CanModifyServerSettings : Policies.CanModifyStoreSettings; var nextRoleSortOrder = (string) ViewData["NextRoleSortOrder"]; - String roleSortOrder = null; - switch (nextRoleSortOrder) + var roleSortOrder = nextRoleSortOrder switch { - case "asc": - roleSortOrder = "desc"; - break; - case "desc": - roleSortOrder = "asc"; - break; - } + "asc" => "desc", + "desc" => "asc", + _ => null + }; var sortIconClass = "fa-sort"; if (roleSortOrder != null) @@ -36,16 +28,12 @@ var sortByDesc = "Sort by descending..."; var sortByAsc = "Sort by ascending..."; - var showInUseColumn = !Model.Roles.Any(r => r.IsUsed is null); }

@ViewData["Title"]

- - Add Role - + Add Role
@@ -61,15 +49,16 @@ class="text-nowrap" title="@(nextRoleSortOrder == "desc" ? sortByAsc : sortByDesc)"> Role - + - Permissions + Scope + Permissions @if (showInUseColumn) { - In use + In use } - Actions + Actions @@ -78,21 +67,29 @@
- @role.Role - @if (role.IsServerRole) + @role.Role + @if (Model.DefaultRole == role.Id) { - - Server-wide + + Default - @if (Model.DefaultRole == role.Id) - { - - Default - - } }
+ + @if (role.IsServerRole) + { + + Server-wide + + } + else + { + + Store-level + + } + @if (!role.Permissions.Any()) { @@ -122,28 +119,18 @@ } } - - - Edit - - - - Remove - - @if (role.IsServerRole && Model.DefaultRole != role.Id) - { - - - - Set as default - - } + +
+ @if (role.IsServerRole && Model.DefaultRole != role.Id) + { + Set as default + } + Edit + Remove +
}
- - diff --git a/BTCPayServer/Views/Shared/PointOfSale/NavExtension.cshtml b/BTCPayServer/Views/Shared/PointOfSale/NavExtension.cshtml index 136794cfa..2a7326ff6 100644 --- a/BTCPayServer/Views/Shared/PointOfSale/NavExtension.cshtml +++ b/BTCPayServer/Views/Shared/PointOfSale/NavExtension.cshtml @@ -1,8 +1,6 @@ @using BTCPayServer.Client @using Microsoft.AspNetCore.Mvc.TagHelpers @using BTCPayServer.Views.Apps -@using BTCPayServer.Abstractions.Extensions -@using BTCPayServer.Abstractions.TagHelpers @using BTCPayServer.Plugins.PointOfSale @using BTCPayServer.Services.Apps @inject AppService AppService; @@ -14,20 +12,30 @@ @if (store != null) { var appType = AppService.GetAppType(PointOfSaleAppType.AppType)!; + var apps = Model.Apps.Where(app => app.AppType == appType.Type).ToList(); - @foreach (var app in Model.Apps.Where(app => app.AppType == appType.Type)) + @if (apps.Any()) { - + } + @foreach (var app in apps) + { + -