diff --git a/BTCPayServer.Data/Data/StoreData.cs b/BTCPayServer.Data/Data/StoreData.cs index ca9941842..a00a0f640 100644 --- a/BTCPayServer.Data/Data/StoreData.cs +++ b/BTCPayServer.Data/Data/StoreData.cs @@ -73,7 +73,6 @@ namespace BTCPayServer.Data } [NotMapped] - [Obsolete] public string Role { get; set; @@ -88,9 +87,6 @@ namespace BTCPayServer.Data public string DefaultCrypto { get; set; } public List PairedSINs { get; set; } public IEnumerable APIKeys { get; set; } - - [NotMapped] - public List AdditionalClaims { get; set; } = new List(); } public enum NetworkFeeMode diff --git a/BTCPayServer.Tests/AuthenticationTests.cs b/BTCPayServer.Tests/AuthenticationTests.cs index 4b0566200..66f5e3f23 100644 --- a/BTCPayServer.Tests/AuthenticationTests.cs +++ b/BTCPayServer.Tests/AuthenticationTests.cs @@ -25,6 +25,7 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using OpenIddict.Abstractions; using OpenQA.Selenium; +using Microsoft.AspNetCore.Identity; namespace BTCPayServer.Tests { @@ -89,6 +90,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); + await user.MakeAdmin(); var token = await RegisterPasswordClientAndGetAccessToken(user, null, tester); await TestApiAgainstAccessToken(token, tester, user); token = await RegisterPasswordClientAndGetAccessToken(user, "secret", tester); @@ -205,6 +207,7 @@ namespace BTCPayServer.Tests var user = tester.NewAccount(); user.GrantAccess(); + await user.MakeAdmin(); var id = Guid.NewGuid().ToString(); var redirecturi = new Uri("http://127.0.0.1/oidc-callback"); var secret = "secret"; @@ -399,7 +402,7 @@ namespace BTCPayServer.Tests $"api/test/me/stores/{testAccount.StoreId}/can-edit", tester.PayTester.HttpClient)); - Assert.Equal(testAccount.RegisterDetails.IsAdmin, await TestApiAgainstAccessToken(accessToken, + Assert.True(await TestApiAgainstAccessToken(accessToken, $"api/test/me/is-admin", tester.PayTester.HttpClient)); diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 59ca6f23e..a49689ea2 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -42,6 +42,7 @@ using BTCPayServer.Services; using System.Net.Http; using Microsoft.AspNetCore.Hosting.Server.Features; using System.Threading.Tasks; +using AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes; namespace BTCPayServer.Tests { @@ -295,7 +296,7 @@ namespace BTCPayServer.Tests public string SSHPassword { get; internal set; } public string SSHKeyFile { get; internal set; } public string SSHConnection { get; set; } - public T GetController(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller + public T GetController(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller { var context = new DefaultHttpContext(); context.Request.Host = new HostString("127.0.0.1", Port); @@ -305,9 +306,9 @@ namespace BTCPayServer.Tests { List claims = new List(); claims.Add(new Claim(OpenIdConnectConstants.Claims.Subject, userId)); - if (additionalClaims != null) - claims.AddRange(additionalClaims); - context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), Policies.CookieAuthentication)); + if (isAdmin) + claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin)); + context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie)); } if (storeId != null) { diff --git a/BTCPayServer.Tests/ChangellyTests.cs b/BTCPayServer.Tests/ChangellyTests.cs index d09374508..16c666a76 100644 --- a/BTCPayServer.Tests/ChangellyTests.cs +++ b/BTCPayServer.Tests/ChangellyTests.cs @@ -40,7 +40,7 @@ namespace BTCPayServer.Tests var controller = tester.PayTester.GetController(user.UserId, user.StoreId); - var storeBlob = controller.StoreData.GetStoreBlob(); + var storeBlob = controller.CurrentStore.GetStoreBlob(); Assert.Null(storeBlob.ChangellySettings); var updateModel = new UpdateChangellySettingsViewModel() @@ -55,7 +55,7 @@ namespace BTCPayServer.Tests await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName); var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId); - storeBlob = controller.StoreData.GetStoreBlob(); + storeBlob = controller.CurrentStore.GetStoreBlob(); Assert.NotNull(storeBlob.ChangellySettings); Assert.NotNull(storeBlob.ChangellySettings); Assert.IsType(storeBlob.ChangellySettings); @@ -224,6 +224,7 @@ namespace BTCPayServer.Tests } } + [Fact] [Trait("Fast", "Fast")] public void CanComputeBaseAmount() { diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index e66f09112..a63ca56f5 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -24,30 +24,8 @@ namespace BTCPayServer.Tests Logs.Tester = new XUnitLog(helper) {Name = "Tests"}; Logs.LogProvider = new XUnitLogProvider(helper); } - - + [Fact(Timeout = TestTimeout)] - public async Task CanCreateInvoice() - { - using (var s = SeleniumTester.Create()) - { - await s.StartAsync(); - s.RegisterNewUser(); - var store = s.CreateNewStore().storeName; - s.AddDerivationScheme(); - - s.CreateInvoice(store); - - s.Driver.FindElement(By.ClassName("invoice-details-link")).Click(); - s.Driver.AssertNoError(); - s.Driver.Navigate().Back(); - s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click(); - Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl"))); - s.Driver.Quit(); - } - } - - [Fact] public async Task CanHandleRefundEmailForm() { @@ -63,7 +41,12 @@ namespace BTCPayServer.Tests s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput")); s.GoToHome(); var invoiceId = s.CreateInvoice(store.storeName); - s.GoToInvoiceCheckout(invoiceId); + s.Driver.FindElement(By.ClassName("invoice-details-link")).Click(); + s.Driver.AssertNoError(); + s.Driver.Navigate().Back(); + s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click(); + Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl"))); + Assert.True(s.Driver.FindElement(By.Id("emailAddressFormInput")).Displayed); s.Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys("xxx"); s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button")) @@ -74,14 +57,11 @@ namespace BTCPayServer.Tests Assert.Contains("form-input-invalid", formInput.GetAttribute("class")); formInput = s.Driver.FindElement(By.Id("emailAddressFormInput")); formInput.SendKeys("@g.com"); - - s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button")) - .Click(); - await Task.Delay(1000); - s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput")); - - s.Driver.Navigate().Refresh(); + var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button")); + actionButton.Click(); + s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput")); + s.Driver.Navigate().Refresh(); s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput")); } } diff --git a/BTCPayServer.Tests/CoinSwitchTests.cs b/BTCPayServer.Tests/CoinSwitchTests.cs index 9b8f6cb1c..84bf78b46 100644 --- a/BTCPayServer.Tests/CoinSwitchTests.cs +++ b/BTCPayServer.Tests/CoinSwitchTests.cs @@ -30,7 +30,7 @@ namespace BTCPayServer.Tests var controller = tester.PayTester.GetController(user.UserId, user.StoreId); - var storeBlob = controller.StoreData.GetStoreBlob(); + var storeBlob = controller.CurrentStore.GetStoreBlob(); Assert.Null(storeBlob.CoinSwitchSettings); var updateModel = new UpdateCoinSwitchSettingsViewModel() @@ -42,7 +42,7 @@ namespace BTCPayServer.Tests await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName); var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId); - storeBlob = controller.StoreData.GetStoreBlob(); + storeBlob = controller.CurrentStore.GetStoreBlob(); Assert.NotNull(storeBlob.CoinSwitchSettings); Assert.NotNull(storeBlob.CoinSwitchSettings); Assert.IsType(storeBlob.CoinSwitchSettings); diff --git a/BTCPayServer.Tests/Extensions.cs b/BTCPayServer.Tests/Extensions.cs index 17eea8065..55249c829 100644 --- a/BTCPayServer.Tests/Extensions.cs +++ b/BTCPayServer.Tests/Extensions.cs @@ -1,4 +1,6 @@ -using System.Text; +using System; +using System.Text; +using System.Threading; using System.Threading.Tasks; using BTCPayServer.Tests.Logging; using Microsoft.AspNetCore.Mvc; @@ -68,20 +70,26 @@ namespace BTCPayServer.Tests return Assert.IsType(vr.Model); } - public static IWebElement AssertElementNotFound(this IWebDriver driver, By by) + public static void AssertElementNotFound(this IWebDriver driver, By by) { - try - { - var webElement = driver.FindElement(by); - Assert.False(webElement.Displayed); - return webElement; - } - catch (NoSuchElementException) - { - - } + DateTimeOffset now = DateTimeOffset.Now; + var wait = SeleniumTester.ImplicitWait; - return null; + while (DateTimeOffset.UtcNow - now < wait) + { + try + { + var webElement = driver.FindElement(by); + if (!webElement.Displayed) + return; + } + catch (NoSuchElementException) + { + return; + } + Thread.Sleep(50); + } + Assert.False(true, "Elements was found"); } } } diff --git a/BTCPayServer.Tests/PSBTTests.cs b/BTCPayServer.Tests/PSBTTests.cs index ec4a9ae6b..4c1a37e43 100644 --- a/BTCPayServer.Tests/PSBTTests.cs +++ b/BTCPayServer.Tests/PSBTTests.cs @@ -49,7 +49,7 @@ namespace BTCPayServer.Tests Assert.Equal("paid", invoice.Status); }); - var walletController = tester.PayTester.GetController(user.UserId); + var walletController = user.GetController(); var walletId = new WalletId(user.StoreId, "BTC"); var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString(); var sendModel = new WalletSendModel() diff --git a/BTCPayServer.Tests/SeleniumTester.cs b/BTCPayServer.Tests/SeleniumTester.cs index 3675c56d0..eba70f3e4 100644 --- a/BTCPayServer.Tests/SeleniumTester.cs +++ b/BTCPayServer.Tests/SeleniumTester.cs @@ -65,11 +65,12 @@ namespace BTCPayServer.Tests Logs.Tester.LogInformation("Selenium: Using chrome driver"); Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri); Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}"); - Driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(10); + Driver.Manage().Timeouts().ImplicitWait = ImplicitWait; Driver.Navigate().GoToUrl(Server.PayTester.ServerUri); Driver.AssertNoError(); } + public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10); public string Link(string relativeLink) { return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash(); diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 927016c46..8535c2843 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -8,6 +8,7 @@ using OpenQA.Selenium.Interactions; using System.Linq; using NBitcoin; using System.Threading.Tasks; +using System.Text.RegularExpressions; namespace BTCPayServer.Tests { @@ -233,8 +234,56 @@ namespace BTCPayServer.Tests } } + [Fact(Timeout = TestTimeout)] + public async Task CanUsePairing() + { + using (var s = SeleniumTester.Create()) + { + await s.StartAsync(); + s.Driver.Navigate().GoToUrl(s.Link("/api-access-request")); + Assert.Contains("ReturnUrl", s.Driver.Url); + + var alice = s.RegisterNewUser(); + var store = s.CreateNewStore().storeName; + s.AddDerivationScheme(); + + s.Driver.FindElement(By.Id("Tokens")).Click(); + s.Driver.FindElement(By.Id("CreateNewToken")).Click(); + s.Driver.FindElement(By.Id("RequestPairing")).Click(); + + var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)"); + Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex"); + var pairingCode = regex.Groups[1].Value; + + s.Driver.FindElement(By.Id("ApprovePairing")).Click(); + Assert.Contains(pairingCode, s.Driver.PageSource); + + var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri); + await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode)); + await client.CreateInvoiceAsync(new NBitpayClient.Invoice() + { + Price = 0.000000012m, + Currency = "USD", + FullNotifications = true + }, NBitpayClient.Facade.Merchant); + + client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri); + + var code = await client.RequestClientAuthorizationAsync("hehe", NBitpayClient.Facade.Merchant); + s.Driver.Navigate().GoToUrl(code.CreateLink(s.Server.PayTester.ServerUri)); + s.Driver.FindElement(By.Id("ApprovePairing")).Click(); + + await client.CreateInvoiceAsync(new NBitpayClient.Invoice() + { + Price = 0.000000012m, + Currency = "USD", + FullNotifications = true + }, NBitpayClient.Facade.Merchant); + } + } + + - [Fact(Timeout = TestTimeout)] public async Task CanCreateAppPoS() { @@ -316,7 +365,7 @@ namespace BTCPayServer.Tests using (var s = SeleniumTester.Create()) { await s.StartAsync(); - s.RegisterNewUser(); + s.RegisterNewUser(true); s.CreateNewStore(); // In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed @@ -332,6 +381,10 @@ namespace BTCPayServer.Tests s.ClickOnAllSideMenus(); + // Make sure we can rescan, because we are admin! + s.Driver.FindElement(By.Id("WalletRescan")).ForceClick(); + Assert.Contains("The batch size make sure", s.Driver.PageSource); + // We setup the fingerprint and the account key path s.Driver.FindElement(By.Id("WalletSettings")).ForceClick(); s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160"); diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 93dd9ef2d..30067c13d 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -20,6 +20,7 @@ using BTCPayServer.Lightning.CLightning; using BTCPayServer.Data; using OpenIddict.Abstractions; using OpenIddict.Core; +using Microsoft.AspNetCore.Identity; namespace BTCPayServer.Tests { @@ -37,6 +38,13 @@ namespace BTCPayServer.Tests GrantAccessAsync().GetAwaiter().GetResult(); } + public async Task MakeAdmin() + { + var userManager = parent.PayTester.GetService>(); + var u = await userManager.FindByIdAsync(UserId); + await userManager.AddToRoleAsync(u, Roles.ServerAdmin); + } + public void Register() { RegisterAsync().GetAwaiter().GetResult(); @@ -78,7 +86,8 @@ namespace BTCPayServer.Tests public T GetController(bool setImplicitStore = true) where T : Controller { - return parent.PayTester.GetController(UserId, setImplicitStore ? StoreId : null); + var controller = parent.PayTester.GetController(UserId, setImplicitStore ? StoreId : null, IsAdmin); + return controller; } public async Task CreateStoreAsync() @@ -140,6 +149,7 @@ namespace BTCPayServer.Tests { get; set; } + public bool IsAdmin { get; internal set; } public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType) { diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index ba9958eed..3a1845da8 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -745,7 +745,7 @@ namespace BTCPayServer.Tests pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant); acc.CreateStore(); var store2 = acc.GetController(); - store2.Pair(pairingCode.ToString(), store2.StoreData.Id).GetAwaiter().GetResult(); + await store2.Pair(pairingCode.ToString(), store2.CurrentStore.Id); Assert.Contains(nameof(PairingResult.ReusedKey), store2.StatusMessage, StringComparison.CurrentCultureIgnoreCase); } } @@ -780,7 +780,7 @@ namespace BTCPayServer.Tests var btcDerivationScheme = acc.DerivationScheme; acc.RegisterDerivationScheme("LTC", true); - var walletController = tester.PayTester.GetController(acc.UserId); + var walletController = acc.GetController(); WalletId walletId = new WalletId(acc.StoreId, "LTC"); var rescan = Assert.IsType(Assert.IsType(walletController.WalletRescan(walletId).Result).Model); Assert.False(rescan.Ok); @@ -789,8 +789,9 @@ namespace BTCPayServer.Tests Assert.False(rescan.IsServerAdmin); walletId = new WalletId(acc.StoreId, "BTC"); - var serverAdminClaim = new[] { new Claim(Policies.CanModifyServerSettings.Key, "true") }; - walletController = tester.PayTester.GetController(acc.UserId, additionalClaims: serverAdminClaim); + acc.IsAdmin = true; + walletController = acc.GetController(); + rescan = Assert.IsType(Assert.IsType(walletController.WalletRescan(walletId).Result).Model); Assert.True(rescan.Ok); Assert.True(rescan.IsFullySync); @@ -921,32 +922,32 @@ namespace BTCPayServer.Tests acc.RegisterDerivationScheme("LTC"); var rateController = acc.GetController(); - var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", acc.StoreId, default) + var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", default) .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); Assert.NotNull(GetBaseCurrencyRatesResult); Assert.NotNull(GetBaseCurrencyRatesResult.Data); Assert.Equal(2, GetBaseCurrencyRatesResult.Data.Length); Assert.Single(GetBaseCurrencyRatesResult.Data.Where(o => o.Code == "LTC")); - var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default) + var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default) .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); // We don't have any default currencies, so this should be failing Assert.Null(GetRatesResult?.Data); var store = acc.GetController(); - var ratesVM = (RatesViewModel)(Assert.IsType(store.Rates(acc.StoreId)).Model); + var ratesVM = (RatesViewModel)(Assert.IsType(store.Rates()).Model); ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD"; store.Rates(ratesVM).Wait(); store = acc.GetController(); rateController = acc.GetController(); - GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default) + GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default) .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); // Now we should have a result Assert.NotNull(GetRatesResult); Assert.NotNull(GetRatesResult.Data); Assert.Equal(2, GetRatesResult.Data.Length); - var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", acc.StoreId, default) + var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", default) .GetAwaiter().GetResult()).Value.ToJson()).ToObject>(); Assert.NotNull(GetCurrencyPairRateResult); @@ -957,7 +958,9 @@ namespace BTCPayServer.Tests var rates = acc.BitPay.GetRates(); HttpClient client = new HttpClient(); // Unauthentified requests should also be ok - var response = client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}").GetAwaiter().GetResult(); + var response = await client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/api/rates?storeId={acc.StoreId}"); + response.EnsureSuccessStatusCode(); + response = await client.GetAsync($"http://127.0.0.1:{tester.PayTester.Port}/rates?storeId={acc.StoreId}"); response.EnsureSuccessStatusCode(); } } @@ -1226,7 +1229,7 @@ namespace BTCPayServer.Tests private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD") { var storeController = user.GetController(); - var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model; + var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model; vm.PreferredExchange = exchange; storeController.Rates(vm).Wait(); var invoice2 = user.BitPay.CreateInvoice(new Invoice() @@ -1252,12 +1255,12 @@ namespace BTCPayServer.Tests user.GrantAccess(); user.RegisterDerivationScheme("BTC"); - Logs.Tester.LogInformation("StoreId without anyone can create invoice = 401"); + Logs.Tester.LogInformation("StoreId without anyone can create invoice = 403"); var response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId={user.StoreId}") { Content = new StringContent("{\"Price\": 5000, \"currency\": \"USD\"}", Encoding.UTF8, "application/json"), }); - Assert.Equal(401, (int)response.StatusCode); + Assert.Equal(403, (int)response.StatusCode); Logs.Tester.LogInformation("No store without anyone can create invoice = 404 because the bitpay API can't know the storeid"); response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices") @@ -1268,12 +1271,12 @@ namespace BTCPayServer.Tests user.ModifyStore(s => s.AnyoneCanCreateInvoice = true); - Logs.Tester.LogInformation("Bad store with anyone can create invoice = 401"); + Logs.Tester.LogInformation("Bad store with anyone can create invoice = 403"); response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId=badid") { Content = new StringContent("{\"Price\": 5000, \"currency\": \"USD\"}", Encoding.UTF8, "application/json"), }); - Assert.Equal(401, (int)response.StatusCode); + Assert.Equal(403, (int)response.StatusCode); Logs.Tester.LogInformation("Good store with anyone can create invoice = 200"); response = await tester.PayTester.HttpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, $"invoices?storeId={user.StoreId}") @@ -1307,7 +1310,7 @@ namespace BTCPayServer.Tests Assert.Equal(Money.Coins(1.0m), invoice1.BtcPrice); var storeController = user.GetController(); - var vm = (RatesViewModel)((ViewResult)storeController.Rates(user.StoreId)).Model; + var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model; Assert.Equal(0.0, vm.Spread); vm.Spread = 40; storeController.Rates(vm).Wait(); @@ -1406,7 +1409,7 @@ namespace BTCPayServer.Tests user.RegisterDerivationScheme("BTC"); var store = user.GetController(); - var rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); + var rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); Assert.False(rateVm.ShowScripting); Assert.Equal("coinaverage", rateVm.PreferredExchange); Assert.Equal(0.0, rateVm.Spread); @@ -1414,7 +1417,7 @@ namespace BTCPayServer.Tests rateVm.PreferredExchange = "bitflyer"; Assert.IsType(store.Rates(rateVm, "Save").Result); - rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); Assert.Equal("bitflyer", rateVm.PreferredExchange); rateVm.ScriptTest = "BTC_JPY,BTC_CAD"; @@ -1431,7 +1434,7 @@ namespace BTCPayServer.Tests Assert.IsType(store.ShowRateRulesPost(true).Result); Assert.IsType(store.Rates(rateVm, "Save").Result); store = user.GetController(); - rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); Assert.Equal(rateVm.StoreId, user.StoreId); Assert.Equal(rateVm.DefaultScript, rateVm.Script); Assert.True(rateVm.ShowScripting); @@ -1449,7 +1452,7 @@ namespace BTCPayServer.Tests Assert.True(rateVm.TestRateRules.All(t => !t.Error)); Assert.IsType(store.Rates(rateVm, "Save").Result); store = user.GetController(); - rateVm = Assert.IsType(Assert.IsType(store.Rates(user.StoreId)).Model); + rateVm = Assert.IsType(Assert.IsType(store.Rates()).Model); Assert.Equal(50, rateVm.Spread); Assert.True(rateVm.ShowScripting); Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase); diff --git a/BTCPayServer/Authentication/BTCPayScopes.cs b/BTCPayServer/Authentication/BTCPayScopes.cs index 0b0e602ba..afb7d77b0 100644 --- a/BTCPayServer/Authentication/BTCPayScopes.cs +++ b/BTCPayServer/Authentication/BTCPayScopes.cs @@ -33,7 +33,6 @@ namespace BTCPayServer.Authentication } public const string CanViewStores = nameof(CanViewStores); - public const string CanEditStore = nameof(CanEditStore); public const string CanManageStores = nameof(CanManageStores); public const string CanViewInvoices = nameof(CanViewInvoices); public const string CanCreateInvoices = nameof(CanCreateInvoices); @@ -42,66 +41,5 @@ namespace BTCPayServer.Authentication public const string CanViewApps = nameof(CanViewApps); public const string CanManageWallet = nameof(CanManageWallet); public const string CanViewProfile = nameof(CanViewProfile); - - public static AuthorizationOptions AddBTCPayRESTApiPolicies(this AuthorizationOptions options) - { - AddScopePolicy(options, CanViewStores, - context => context.HasScopes(BTCPayScopes.StoreManagement) || - context.HasScopes(BTCPayScopes.ViewStores)); - options.AddPolicy(CanEditStore, p => p.RequireClaim(CanEditStore)); - AddScopePolicy(options, CanManageStores, - context => context.HasScopes(BTCPayScopes.StoreManagement)); - AddScopePolicy(options, CanViewInvoices, - context => context.HasScopes(BTCPayScopes.ViewInvoices) || - context.HasScopes(BTCPayScopes.InvoiceManagement)); - AddScopePolicy(options, CanCreateInvoices, - context => context.HasScopes(BTCPayScopes.CreateInvoices) || - context.HasScopes(BTCPayScopes.InvoiceManagement)); - AddScopePolicy(options, CanViewApps, - context => context.HasScopes(BTCPayScopes.AppManagement) || context.HasScopes(BTCPayScopes.ViewApps)); - AddScopePolicy(options, CanManageInvoices, - context => context.HasScopes(BTCPayScopes.InvoiceManagement)); - AddScopePolicy(options, CanManageApps, - context => context.HasScopes(BTCPayScopes.AppManagement)); - AddScopePolicy(options, CanManageWallet, - context => context.HasScopes(BTCPayScopes.WalletManagement)); - AddScopePolicy(options, CanViewProfile, - context => context.HasScopes(OpenIddictConstants.Scopes.Profile)); - return options; - } - - private static void AddScopePolicy(AuthorizationOptions options, string name, - Func scopeGroups) - { - options.AddPolicy(name, - builder => builder.AddRequirements(new LambdaRequirement(scopeGroups))); - } - - public static bool HasScopes(this AuthorizationHandlerContext context, params string[] scopes) - { - return scopes.All(s => context.User.HasClaim(OpenIddictConstants.Claims.Scope, s)); - } - } - - public class LambdaRequirement : - AuthorizationHandler, IAuthorizationRequirement - { - private readonly Func _Func; - - public LambdaRequirement(Func func) - { - _Func = func; - } - - protected override Task HandleRequirementAsync( - AuthorizationHandlerContext context, LambdaRequirement requirement) - { - if (_Func.Invoke(context)) - { - context.Succeed(requirement); - } - - return Task.CompletedTask; - } } } diff --git a/BTCPayServer/Controllers/AccessTokenController.cs b/BTCPayServer/Controllers/AccessTokenController.cs index a731b757d..aa42f7808 100644 --- a/BTCPayServer/Controllers/AccessTokenController.cs +++ b/BTCPayServer/Controllers/AccessTokenController.cs @@ -1,6 +1,7 @@ using BTCPayServer.Authentication; using BTCPayServer.Filters; using BTCPayServer.Models; +using BTCPayServer.Security; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; @@ -13,7 +14,7 @@ using System.Threading.Tasks; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)] + [Authorize(AuthenticationSchemes = Security.AuthenticationSchemes.Bitpay)] [BitpayAPIConstraint()] public class AccessTokenController : Controller { diff --git a/BTCPayServer/Controllers/AccountController.cs b/BTCPayServer/Controllers/AccountController.cs index 62111fe67..ab40e637f 100644 --- a/BTCPayServer/Controllers/AccountController.cs +++ b/BTCPayServer/Controllers/AccountController.cs @@ -26,7 +26,7 @@ using BTCPayServer.Data; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Route("[controller]/[action]")] public class AccountController : Controller { diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index 2f6d56cb2..3a876356d 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -19,7 +19,7 @@ using NBitcoin.DataEncoders; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] [Route("apps")] public partial class AppsController : Controller diff --git a/BTCPayServer/Controllers/AppsPublicController.cs b/BTCPayServer/Controllers/AppsPublicController.cs index 5bf783330..adf4ee1d1 100644 --- a/BTCPayServer/Controllers/AppsPublicController.cs +++ b/BTCPayServer/Controllers/AppsPublicController.cs @@ -165,7 +165,6 @@ namespace BTCPayServer.Controllers } } var store = await _AppService.GetStore(app); - store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest() { ItemCode = choice?.Id, @@ -283,7 +282,6 @@ namespace BTCPayServer.Controllers return NotFound("Contribution Amount is more than is currently allowed."); } - store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); try { var invoice = await _InvoiceController.CreateInvoiceCore(new CreateInvoiceRequest() diff --git a/BTCPayServer/Controllers/AuthorizationController.cs b/BTCPayServer/Controllers/AuthorizationController.cs index e701380da..4df5d6646 100644 --- a/BTCPayServer/Controllers/AuthorizationController.cs +++ b/BTCPayServer/Controllers/AuthorizationController.cs @@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers _IdentityOptions = identityOptions; } - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpGet("/connect/authorize")] public async Task Authorize(OpenIddictRequest openIdConnectRequest) { @@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers }); } - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [HttpPost("/connect/authorize")] public async Task Authorize(OpenIddictRequest openIdConnectRequest, string consent, bool createAuthorization = true) diff --git a/BTCPayServer/Controllers/InvoiceController.API.cs b/BTCPayServer/Controllers/InvoiceController.API.cs index 6e90700a7..d1de6b054 100644 --- a/BTCPayServer/Controllers/InvoiceController.API.cs +++ b/BTCPayServer/Controllers/InvoiceController.API.cs @@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Mvc; namespace BTCPayServer.Controllers { [BitpayAPIConstraint] - [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = Policies.BitpayAuthentication)] + [Authorize(Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Bitpay)] public class InvoiceControllerAPI : Controller { private InvoiceController _InvoiceController; diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 09458bba2..90b0c4187 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers { [HttpGet] [Route("invoices/{invoiceId}")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task Invoice(string invoiceId) { var invoice = (await _InvoiceRepository.GetInvoices(new InvoiceQuery() @@ -394,7 +394,7 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("invoices")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int timezoneOffset = 0) { @@ -454,7 +454,7 @@ namespace BTCPayServer.Controllers } [HttpGet] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task Export(string format, string searchTerm = null, int timezoneOffset = 0) { @@ -488,7 +488,7 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("invoices/create")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task CreateInvoice() { @@ -504,31 +504,19 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("invoices/create")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(Policy = Policies.CanCreateInvoice.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task CreateInvoice(CreateInvoiceModel model, CancellationToken cancellationToken) { var stores = await _StoreRepository.GetStoresByUserId(GetUserId()); model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId); - model.AvailablePaymentMethods = GetPaymentMethodsSelectList(); - - var store = stores.FirstOrDefault(s => s.Id == model.StoreId); - if (store == null) - { - ModelState.AddModelError(nameof(model.StoreId), "Store not found"); - } + var store = HttpContext.GetStoreData(); if (!ModelState.IsValid) { return View(model); } StatusMessage = null; - 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); - } - if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0) { ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice"); @@ -576,7 +564,7 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("invoices/{invoiceId}/changestate/{newState}")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [BitpayAPIConstraint(false)] public async Task ChangeInvoiceState(string invoiceId, string newState) { @@ -585,15 +573,12 @@ namespace BTCPayServer.Controllers InvoiceId = new[] {invoiceId}, UserId = GetUserId() })).FirstOrDefault(); - var model = new InvoiceStateChangeModel(); if (invoice == null) { model.NotFound = true; return NotFound(model); } - - if (newState == "invalid") { await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId); diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 6a1092d33..8ef048379 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -66,8 +66,6 @@ namespace BTCPayServer.Controllers internal async Task> CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List additionalTags = null, CancellationToken cancellationToken = default) { - if (!store.HasClaim(Policies.CanCreateInvoice.Key)) - throw new UnauthorizedAccessException(); invoice.Currency = invoice.Currency?.ToUpperInvariant() ?? "USD"; InvoiceLogs logs = new InvoiceLogs(); logs.Write("Creation of invoice starting"); diff --git a/BTCPayServer/Controllers/ManageController.cs b/BTCPayServer/Controllers/ManageController.cs index 9b8ae9291..72fe9d1a2 100644 --- a/BTCPayServer/Controllers/ManageController.cs +++ b/BTCPayServer/Controllers/ManageController.cs @@ -26,7 +26,7 @@ using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Route("[controller]/[action]")] public partial class ManageController : Controller { diff --git a/BTCPayServer/Controllers/PaymentRequestController.cs b/BTCPayServer/Controllers/PaymentRequestController.cs index b06dffc30..68f784829 100644 --- a/BTCPayServer/Controllers/PaymentRequestController.cs +++ b/BTCPayServer/Controllers/PaymentRequestController.cs @@ -31,7 +31,7 @@ using NBitpayClient; namespace BTCPayServer.Controllers { [Route("payment-requests")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] public class PaymentRequestController : Controller { private readonly InvoiceController _InvoiceController; @@ -289,7 +289,6 @@ namespace BTCPayServer.Controllers var pr = await _PaymentRequestRepository.FindPaymentRequest(id, null); var blob = pr.GetBlob(); var store = pr.StoreData; - store.AdditionalClaims.Add(new Claim(Policies.CanCreateInvoice.Key, store.Id)); try { var redirectUrl = Request.GetDisplayUrl().TrimEnd("/pay", StringComparison.InvariantCulture) diff --git a/BTCPayServer/Controllers/RateController.cs b/BTCPayServer/Controllers/RateController.cs index 760dbfd94..8c09e61bc 100644 --- a/BTCPayServer/Controllers/RateController.cs +++ b/BTCPayServer/Controllers/RateController.cs @@ -15,14 +15,21 @@ using BTCPayServer.Authentication; using Microsoft.AspNetCore.Cors; using System.Threading; using BTCPayServer.Data; +using BTCPayServer.Security; namespace BTCPayServer.Controllers { - [Authorize(AuthenticationSchemes = Security.Policies.BitpayAuthentication)] - [AllowAnonymous] [EnableCors(CorsPolicies.All)] + [Authorize(Policy = Policies.CanGetRates.Key, AuthenticationSchemes = Security.AuthenticationSchemes.Bitpay)] public class RateController : Controller { + public StoreData CurrentStore + { + get + { + return HttpContext.GetStoreData(); + } + } RateFetcher _RateProviderFactory; BTCPayNetworkProvider _NetworkProvider; CurrencyNameTable _CurrencyNameTable; @@ -47,40 +54,28 @@ namespace BTCPayServer.Controllers [Route("rates/{baseCurrency}")] [HttpGet] [BitpayAPIConstraint] - public async Task GetBaseCurrencyRates(string baseCurrency, string storeId, CancellationToken cancellationToken) + public async Task GetBaseCurrencyRates(string baseCurrency, CancellationToken cancellationToken) { - storeId = await GetStoreId(storeId); - var store = this.HttpContext.GetStoreData(); - if (store == null || store.Id != storeId) - store = await _StoreRepo.FindStore(storeId); - if (store == null) - { - var err = Json(new BitpayErrorsModel() { Error = "Store not found" }); - err.StatusCode = 404; - return err; - } - var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider); + var supportedMethods = CurrentStore.GetSupportedPaymentMethods(_NetworkProvider); var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode)) .Select(method => method.PaymentId.CryptoCode).Distinct(); var currencypairs = BuildCurrencyPairs(currencyCodes, baseCurrency); - - var result = await GetRates2(currencypairs, store.Id, cancellationToken); + + var result = await GetRates2(currencypairs, null, cancellationToken); var rates = (result as JsonResult)?.Value as Rate[]; if (rates == null) return result; return Json(new DataWrapper(rates)); } - [Route("rates/{baseCurrency}/{currency}")] [HttpGet] [BitpayAPIConstraint] - public async Task GetCurrencyPairRate(string baseCurrency, string currency, string storeId, CancellationToken cancellationToken) + public async Task GetCurrencyPairRate(string baseCurrency, string currency, CancellationToken cancellationToken) { - storeId = await GetStoreId(storeId); - var result = await GetRates2($"{baseCurrency}_{currency}", storeId, cancellationToken); + var result = await GetRates2($"{baseCurrency}_{currency}", null, cancellationToken); var rates = (result as JsonResult)?.Value as Rate[]; if (rates == null) return result; @@ -90,54 +85,27 @@ namespace BTCPayServer.Controllers [Route("rates")] [HttpGet] [BitpayAPIConstraint] - public async Task GetRates(string currencyPairs, string storeId, CancellationToken cancellationToken) + public async Task GetRates(string currencyPairs, CancellationToken cancellationToken) { - var result = await GetRates2(currencyPairs, storeId, cancellationToken); + var result = await GetRates2(currencyPairs, null, cancellationToken); var rates = (result as JsonResult)?.Value as Rate[]; if (rates == null) return result; return Json(new DataWrapper(rates)); } - private async Task GetStoreId(string storeId) - { - if (storeId != null && this.HttpContext.GetStoreData()?.Id == storeId) - return storeId; - if(storeId == null) - { - var tokens = await this.TokenRepository.GetTokens(this.User.GetSIN()); - storeId = tokens.Select(s => s.StoreId).Where(s => s != null).FirstOrDefault(); - } - if (storeId == null) - return null; - var store = await _StoreRepo.FindStore(storeId); - if (store == null) - return null; - this.HttpContext.SetStoreData(store); - return storeId; - } - [Route("api/rates")] [HttpGet] + [AllowAnonymous] public async Task GetRates2(string currencyPairs, string storeId, CancellationToken cancellationToken) { - storeId = await GetStoreId(storeId); - if (storeId == null) - { - var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings)" }); - result.StatusCode = 400; - return result; - } - var store = this.HttpContext.GetStoreData(); - if (store == null || store.Id != storeId) - store = await _StoreRepo.FindStore(storeId); + var store = this.CurrentStore ?? await this._StoreRepo.FindStore(storeId); if (store == null) { - var result = Json(new BitpayErrorsModel() { Error = "Store not found" }); - result.StatusCode = 404; - return result; + var err = Json(new BitpayErrorsModel() { Error = "Store not found" }); + err.StatusCode = 404; + return err; } - if (currencyPairs == null) { currencyPairs = store.GetStoreBlob().GetDefaultCurrencyPairString(); @@ -186,7 +154,7 @@ namespace BTCPayServer.Controllers bool first = true; foreach (var currencyCode in currencyCodes) { - if(!first) + if (!first) currencyPairsBuilder.Append(","); first = false; currencyPairsBuilder.Append($"{baseCrypto}_{currencyCode}"); diff --git a/BTCPayServer/Controllers/RestApi/TestController.cs b/BTCPayServer/Controllers/RestApi/TestController.cs index 5d7f3c246..7fbbd6d54 100644 --- a/BTCPayServer/Controllers/RestApi/TestController.cs +++ b/BTCPayServer/Controllers/RestApi/TestController.cs @@ -19,7 +19,7 @@ namespace BTCPayServer.Controllers.RestApi /// [Route("api/[controller]")] [ApiController] - [Authorize(AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.OpenId)] public class TestController : ControllerBase { private readonly UserManager _userManager; @@ -44,9 +44,10 @@ namespace BTCPayServer.Controllers.RestApi } [HttpGet("me/is-admin")] + [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.OpenId)] public bool AmIAnAdmin() { - return User.IsInRole(Roles.ServerAdmin); + return true; } [HttpGet("me/stores")] @@ -57,8 +58,8 @@ namespace BTCPayServer.Controllers.RestApi [HttpGet("me/stores/{storeId}/can-edit")] - [Authorize(Policy = RestAPIPolicies.CanEditStore, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + [Authorize(Policy = Policies.CanModifyStoreSettings.Key, + AuthenticationSchemes = AuthenticationSchemes.OpenId)] public bool CanEdit(string storeId) { return true; @@ -68,48 +69,48 @@ namespace BTCPayServer.Controllers.RestApi #region scopes tests [Authorize(Policy = RestAPIPolicies.CanViewStores, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanViewStores))] public bool ScopeCanViewStores() { return true; } [Authorize(Policy = RestAPIPolicies.CanManageStores, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanManageStores))] public bool ScopeCanManageStores() { return true; } [Authorize(Policy = RestAPIPolicies.CanViewInvoices, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanViewInvoices))] public bool ScopeCanViewInvoices() { return true; } [Authorize(Policy = RestAPIPolicies.CanCreateInvoices, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanCreateInvoices))] public bool ScopeCanCreateInvoices() { return true; } [Authorize(Policy = RestAPIPolicies.CanManageInvoices, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanManageInvoices))] public bool ScopeCanManageInvoices() { return true; } [Authorize(Policy = RestAPIPolicies.CanManageApps, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanManageApps))] public bool ScopeCanManageApps() { return true; } [Authorize(Policy = RestAPIPolicies.CanViewApps, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanViewApps))] public bool ScopeCanViewApps() { return true; } [Authorize(Policy = RestAPIPolicies.CanManageWallet, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanManageWallet))] public bool ScopeCanManageWallet() { return true; } [Authorize(Policy = RestAPIPolicies.CanViewProfile, - AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)] + AuthenticationSchemes = AuthenticationSchemes.OpenId)] [HttpGet(nameof(ScopeCanViewProfile))] public bool ScopeCanViewProfile() { return true; } diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 91f889439..dced40a18 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -38,7 +38,8 @@ using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Controllers { - [Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)] + [Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key, + AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes.Cookie)] public partial class ServerController : Controller { private UserManager _UserManager; diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 51def6328..41e34f903 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -39,8 +39,8 @@ using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment; namespace BTCPayServer.Controllers { [Route("stores")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class StoresController : Controller { @@ -124,8 +124,8 @@ namespace BTCPayServer.Controllers private async Task FillUsers(StoreUsersViewModel vm) { - var users = await _Repo.GetStoreUsers(StoreData.Id); - vm.StoreId = StoreData.Id; + var users = await _Repo.GetStoreUsers(CurrentStore.Id); + vm.StoreId = CurrentStore.Id; vm.Users = users.Select(u => new StoreUsersViewModel.StoreUserViewModel() { Email = u.Email, @@ -134,7 +134,7 @@ namespace BTCPayServer.Controllers }).ToList(); } - public StoreData StoreData + public StoreData CurrentStore { get { @@ -163,7 +163,7 @@ namespace BTCPayServer.Controllers ModelState.AddModelError(nameof(vm.Role), "Invalid role"); return View(vm); } - if (!await _Repo.AddStoreUser(StoreData.Id, user.Id, vm.Role)) + if (!await _Repo.AddStoreUser(CurrentStore.Id, user.Id, vm.Role)) { ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store"); return View(vm); @@ -199,13 +199,13 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("{storeId}/rates")] - public IActionResult Rates(string storeId) + public IActionResult Rates() { - var storeBlob = StoreData.GetStoreBlob(); + var storeBlob = CurrentStore.GetStoreBlob(); var vm = new RatesViewModel(); vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName); vm.Spread = (double)(storeBlob.Spread * 100m); - vm.StoreId = storeId; + vm.StoreId = CurrentStore.Id; vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString(); vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString(); vm.AvailableExchanges = GetSupportedExchanges(); @@ -239,7 +239,7 @@ namespace BTCPayServer.Controllers if (model.PreferredExchange != null) model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant(); - var blob = StoreData.GetStoreBlob(); + var blob = CurrentStore.GetStoreBlob(); model.DefaultScript = blob.GetDefaultRateRules(_NetworkProvider).ToString(); model.AvailableExchanges = GetSupportedExchanges(); @@ -311,14 +311,14 @@ namespace BTCPayServer.Controllers } else // command == Save { - if (StoreData.SetStoreBlob(blob)) + if (CurrentStore.SetStoreBlob(blob)) { - await _Repo.UpdateStore(StoreData); + await _Repo.UpdateStore(CurrentStore); StatusMessage = "Rate settings updated"; } return RedirectToAction(nameof(Rates), new { - storeId = StoreData.Id + storeId = CurrentStore.Id }); } } @@ -342,22 +342,22 @@ namespace BTCPayServer.Controllers [Route("{storeId}/rates/confirm")] public async Task ShowRateRulesPost(bool scripting) { - var blob = StoreData.GetStoreBlob(); + var blob = CurrentStore.GetStoreBlob(); blob.RateScripting = scripting; blob.RateScript = blob.GetDefaultRateRules(_NetworkProvider).ToString(); - StoreData.SetStoreBlob(blob); - await _Repo.UpdateStore(StoreData); + CurrentStore.SetStoreBlob(blob); + await _Repo.UpdateStore(CurrentStore); StatusMessage = "Rate rules scripting activated"; - return RedirectToAction(nameof(Rates), new { storeId = StoreData.Id }); + return RedirectToAction(nameof(Rates), new { storeId = CurrentStore.Id }); } [HttpGet] [Route("{storeId}/checkout")] public IActionResult CheckoutExperience() { - var storeBlob = StoreData.GetStoreBlob(); + var storeBlob = CurrentStore.GetStoreBlob(); var vm = new CheckoutExperienceViewModel(); - SetCryptoCurrencies(vm, StoreData); + SetCryptoCurrencies(vm, CurrentStore); vm.CustomCSS = storeBlob.CustomCSS?.AbsoluteUri; vm.CustomLogo = storeBlob.CustomLogo?.AbsoluteUri; vm.HtmlTitle = storeBlob.HtmlTitle; @@ -402,14 +402,14 @@ namespace BTCPayServer.Controllers } } bool needUpdate = false; - var blob = StoreData.GetStoreBlob(); + var blob = CurrentStore.GetStoreBlob(); var defaultPaymentMethodId = model.DefaultPaymentMethod == null ? null : PaymentMethodId.Parse(model.DefaultPaymentMethod); - if (StoreData.GetDefaultPaymentId(_NetworkProvider) != defaultPaymentMethodId) + if (CurrentStore.GetDefaultPaymentId(_NetworkProvider) != defaultPaymentMethodId) { needUpdate = true; - StoreData.SetDefaultPaymentId(defaultPaymentMethodId); + CurrentStore.SetDefaultPaymentId(defaultPaymentMethodId); } - SetCryptoCurrencies(model, StoreData); + SetCryptoCurrencies(model, CurrentStore); model.SetLanguages(_LangService, model.DefaultLang); if (!ModelState.IsValid) @@ -425,19 +425,19 @@ namespace BTCPayServer.Controllers blob.LightningMaxValue = lightningMaxValue; blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi; blob.RedirectAutomatically = model.RedirectAutomatically; - if (StoreData.SetStoreBlob(blob)) + if (CurrentStore.SetStoreBlob(blob)) { needUpdate = true; } if (needUpdate) { - await _Repo.UpdateStore(StoreData); + await _Repo.UpdateStore(CurrentStore); StatusMessage = "Store successfully updated"; } return RedirectToAction(nameof(CheckoutExperience), new { - storeId = StoreData.Id + storeId = CurrentStore.Id }); } @@ -529,23 +529,23 @@ namespace BTCPayServer.Controllers public async Task UpdateStore(StoreViewModel model, string command = null) { bool needUpdate = false; - if (StoreData.SpeedPolicy != model.SpeedPolicy) + if (CurrentStore.SpeedPolicy != model.SpeedPolicy) { needUpdate = true; - StoreData.SpeedPolicy = model.SpeedPolicy; + CurrentStore.SpeedPolicy = model.SpeedPolicy; } - if (StoreData.StoreName != model.StoreName) + if (CurrentStore.StoreName != model.StoreName) { needUpdate = true; - StoreData.StoreName = model.StoreName; + CurrentStore.StoreName = model.StoreName; } - if (StoreData.StoreWebsite != model.StoreWebsite) + if (CurrentStore.StoreWebsite != model.StoreWebsite) { needUpdate = true; - StoreData.StoreWebsite = model.StoreWebsite; + CurrentStore.StoreWebsite = model.StoreWebsite; } - var blob = StoreData.GetStoreBlob(); + var blob = CurrentStore.GetStoreBlob(); blob.AnyoneCanInvoice = model.AnyoneCanCreateInvoice; blob.NetworkFeeMode = model.NetworkFeeMode; blob.MonitoringExpiration = model.MonitoringExpiration; @@ -553,20 +553,20 @@ namespace BTCPayServer.Controllers blob.LightningDescriptionTemplate = model.LightningDescriptionTemplate ?? string.Empty; blob.PaymentTolerance = model.PaymentTolerance; - if (StoreData.SetStoreBlob(blob)) + if (CurrentStore.SetStoreBlob(blob)) { needUpdate = true; } if (needUpdate) { - await _Repo.UpdateStore(StoreData); + await _Repo.UpdateStore(CurrentStore); StatusMessage = "Store successfully updated"; } return RedirectToAction(nameof(UpdateStore), new { - storeId = StoreData.Id + storeId = CurrentStore.Id }); } @@ -588,7 +588,7 @@ namespace BTCPayServer.Controllers [Route("{storeId}/delete")] public async Task DeleteStorePost(string storeId) { - await _Repo.DeleteStore(StoreData.Id); + await _Repo.DeleteStore(CurrentStore.Id); StatusMessage = "Success: Store successfully deleted"; return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores"); } @@ -614,7 +614,7 @@ namespace BTCPayServer.Controllers public async Task ListTokens() { var model = new TokensViewModel(); - var tokens = await _TokenRepository.GetTokensByStoreIdAsync(StoreData.Id); + var tokens = await _TokenRepository.GetTokensByStoreIdAsync(CurrentStore.Id); model.StatusMessage = StatusMessage; model.StoreNotConfigured = StoreNotConfigured; model.Tokens = tokens.Select(t => new TokenViewModel() @@ -624,7 +624,7 @@ namespace BTCPayServer.Controllers Id = t.Value }).ToArray(); - model.ApiKey = (await _TokenRepository.GetLegacyAPIKeys(StoreData.Id)).FirstOrDefault(); + model.ApiKey = (await _TokenRepository.GetLegacyAPIKeys(CurrentStore.Id)).FirstOrDefault(); if (model.ApiKey == null) model.EncodedApiKey = "*API Key*"; else @@ -637,7 +637,7 @@ namespace BTCPayServer.Controllers public async Task RevokeToken(string tokenId) { var token = await _TokenRepository.GetToken(tokenId); - if (token == null || token.StoreId != StoreData.Id) + if (token == null || token.StoreId != CurrentStore.Id) return NotFound(); return View("Confirm", new ConfirmModel() { @@ -653,7 +653,7 @@ namespace BTCPayServer.Controllers { var token = await _TokenRepository.GetToken(tokenId); if (token == null || - token.StoreId != StoreData.Id || + token.StoreId != CurrentStore.Id || !await _TokenRepository.DeleteToken(tokenId)) StatusMessage = "Failure to revoke this token"; else @@ -666,7 +666,7 @@ namespace BTCPayServer.Controllers public async Task ShowToken(string tokenId) { var token = await _TokenRepository.GetToken(tokenId); - if (token == null || token.StoreId != StoreData.Id) + if (token == null || token.StoreId != CurrentStore.Id) return NotFound(); return View(token); } @@ -674,7 +674,6 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("/api-tokens")] [Route("{storeId}/Tokens/Create")] - [AllowAnonymous] public async Task CreateToken(CreateTokenViewModel model) { if (!ModelState.IsValid) @@ -684,23 +683,17 @@ namespace BTCPayServer.Controllers model.Label = model.Label ?? String.Empty; var userId = GetUserId(); if (userId == null) - return Challenge(Policies.CookieAuthentication); + return Challenge(AuthenticationSchemes.Cookie); - var store = StoreData; - var storeId = StoreData?.Id; + var store = CurrentStore; + var storeId = CurrentStore?.Id; if (storeId == null) { storeId = model.StoreId; store = await _Repo.FindStore(storeId, userId); if (store == null) - return Challenge(Policies.CookieAuthentication); + return Challenge(AuthenticationSchemes.Cookie); } - - if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) - { - return Challenge(Policies.CookieAuthentication); - } - var tokenRequest = new TokenRequest() { Label = model.Label, @@ -737,20 +730,12 @@ namespace BTCPayServer.Controllers [HttpGet] [Route("/api-tokens")] [Route("{storeId}/Tokens/Create")] - [AllowAnonymous] public async Task CreateToken() { var userId = GetUserId(); if (string.IsNullOrWhiteSpace(userId)) - return Challenge(Policies.CookieAuthentication); - var storeId = StoreData?.Id; - if (StoreData != null) - { - if (!StoreData.HasClaim(Policies.CanModifyStoreSettings.Key)) - { - return Challenge(Policies.CookieAuthentication); - } - } + return Challenge(AuthenticationSchemes.Cookie); + var storeId = CurrentStore?.Id; var model = new CreateTokenViewModel(); ViewBag.HidePublicKey = storeId == null; ViewBag.ShowStores = storeId == null; @@ -759,7 +744,7 @@ namespace BTCPayServer.Controllers if (storeId == null) { var stores = await _Repo.GetStoresByUserId(userId); - model.Stores = new SelectList(stores.Where(s => s.HasClaim(Policies.CanModifyStoreSettings.Key)), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId); + model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName), storeId); if (model.Stores.Count() == 0) { StatusMessage = "Error: You need to be owner of at least one store before pairing"; @@ -776,7 +761,7 @@ namespace BTCPayServer.Controllers var store = HttpContext.GetStoreData(); if (store == null) return NotFound(); - await _TokenRepository.GenerateLegacyAPIKey(StoreData.Id); + await _TokenRepository.GenerateLegacyAPIKey(CurrentStore.Id); StatusMessage = "API Key re-generated"; return RedirectToAction(nameof(ListTokens)); } @@ -788,7 +773,7 @@ namespace BTCPayServer.Controllers { var userId = GetUserId(); if (userId == null) - return Challenge(Policies.CookieAuthentication); + return Challenge(AuthenticationSchemes.Cookie); if (pairingCode == null) return NotFound(); var pairing = await _TokenRepository.GetPairingAsync(pairingCode); @@ -805,8 +790,8 @@ namespace BTCPayServer.Controllers Id = pairing.Id, Label = pairing.Label, SIN = pairing.SIN ?? "Server-Initiated Pairing", - SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id, - Stores = stores.Where(u => u.HasClaim(Policies.CanModifyStoreSettings.Key)).Select(s => new PairingModel.StoreViewModel() + StoreId = selectedStore ?? stores.FirstOrDefault()?.Id, + Stores = stores.Where(u => u.Role == StoreRoles.Owner).Select(s => new PairingModel.StoreViewModel() { Id = s.Id, Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName @@ -817,24 +802,15 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("/api-access-request")] - [AllowAnonymous] - public async Task Pair(string pairingCode, string selectedStore) + public async Task Pair(string pairingCode, string storeId) { if (pairingCode == null) return NotFound(); - var userId = GetUserId(); - if (userId == null) - return Challenge(Policies.CookieAuthentication); - var store = await _Repo.FindStore(selectedStore, userId); + var store = CurrentStore; var pairing = await _TokenRepository.GetPairingAsync(pairingCode); if (store == null || pairing == null) return NotFound(); - if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) - { - return Challenge(Policies.CookieAuthentication); - } - var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id); if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial) { @@ -863,7 +839,7 @@ namespace BTCPayServer.Controllers private string GetUserId() { - if (User.Identity.AuthenticationType != Policies.CookieAuthentication) + if (User.Identity.AuthenticationType != AuthenticationSchemes.Cookie) return null; return _UserManager.GetUserId(User); } @@ -877,7 +853,7 @@ namespace BTCPayServer.Controllers [Route("{storeId}/paybutton")] public IActionResult PayButton() { - var store = StoreData; + var store = CurrentStore; var storeBlob = store.GetStoreBlob(); if (!storeBlob.AnyoneCanInvoice) @@ -906,17 +882,17 @@ namespace BTCPayServer.Controllers [Route("{storeId}/paybutton")] public async Task PayButton(bool enableStore) { - var blob = StoreData.GetStoreBlob(); + var blob = CurrentStore.GetStoreBlob(); blob.AnyoneCanInvoice = enableStore; - if (StoreData.SetStoreBlob(blob)) + if (CurrentStore.SetStoreBlob(blob)) { - await _Repo.UpdateStore(StoreData); + await _Repo.UpdateStore(CurrentStore); StatusMessage = "Store successfully updated"; } return RedirectToAction(nameof(PayButton), new { - storeId = StoreData.Id + storeId = CurrentStore.Id }); } diff --git a/BTCPayServer/Controllers/UserStoresController.cs b/BTCPayServer/Controllers/UserStoresController.cs index d5cbb86da..201fc9d0a 100644 --- a/BTCPayServer/Controllers/UserStoresController.cs +++ b/BTCPayServer/Controllers/UserStoresController.cs @@ -17,7 +17,7 @@ using NBXplorer.DerivationStrategy; namespace BTCPayServer.Controllers { [Route("stores")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class UserStoresController : Controller { @@ -91,7 +91,7 @@ namespace BTCPayServer.Controllers Id = store.Id, Name = store.StoreName, WebSite = store.StoreWebsite, - IsOwner = store.HasClaim(Policies.CanModifyStoreSettings.Key) + IsOwner = store.Role == StoreRoles.Owner }); } return View(result); diff --git a/BTCPayServer/Controllers/WalletsController.PSBT.cs b/BTCPayServer/Controllers/WalletsController.PSBT.cs index ee6f29603..d887590ae 100644 --- a/BTCPayServer/Controllers/WalletsController.PSBT.cs +++ b/BTCPayServer/Controllers/WalletsController.PSBT.cs @@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers case "ledger": return ViewWalletSendLedger(psbt); case "update": - var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); + var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); psbt = await UpdatePSBT(derivationSchemeSettings, psbt, network); if (psbt == null) { @@ -140,7 +140,7 @@ namespace BTCPayServer.Controllers vm.SigningKey = signingKey; vm.SigningKeyPath = signingKeyPath; - var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); + var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) return NotFound(); try @@ -263,7 +263,7 @@ namespace BTCPayServer.Controllers try { psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); - var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); + var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) return NotFound(); await FetchTransactionDetails(derivationSchemeSettings, vm, network); @@ -295,7 +295,7 @@ namespace BTCPayServer.Controllers vm.GlobalError = "Error while broadcasting: " + ex.Message; return View(vm); } - return await RedirectToWalletTransaction(walletId, transaction); + return RedirectToWalletTransaction(walletId, transaction); } else if (command == "analyze-psbt") { diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index b808df17b..c6037e9c2 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -34,7 +34,7 @@ using static BTCPayServer.Controllers.StoresController; namespace BTCPayServer.Controllers { [Route("wallets")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] [AutoValidateAntiforgeryToken] public partial class WalletsController : Controller { @@ -46,7 +46,7 @@ namespace BTCPayServer.Controllers private readonly UserManager _userManager; private readonly JsonSerializerSettings _serializerSettings; private readonly NBXplorerDashboard _dashboard; - + private readonly IAuthorizationService _authorizationService; private readonly IFeeProviderFactory _feeRateProvider; private readonly BTCPayWalletProvider _walletProvider; public RateFetcher RateFetcher { get; } @@ -62,6 +62,7 @@ namespace BTCPayServer.Controllers MvcNewtonsoftJsonOptions mvcJsonOptions, NBXplorerDashboard dashboard, RateFetcher rateProvider, + IAuthorizationService authorizationService, ExplorerClientProvider explorerProvider, IFeeProviderFactory feeRateProvider, BTCPayWalletProvider walletProvider) @@ -70,6 +71,7 @@ namespace BTCPayServer.Controllers Repository = repo; WalletRepository = walletRepository; RateFetcher = rateProvider; + _authorizationService = authorizationService; NetworkProvider = networkProvider; _userManager = userManager; _serializerSettings = mvcJsonOptions.SerializerSettings; @@ -119,7 +121,7 @@ namespace BTCPayServer.Controllers catch { } ///////// - DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId); + DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) return NotFound(); @@ -185,8 +187,14 @@ namespace BTCPayServer.Controllers return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() }); } + [HttpGet] + [AllowAnonymous] public async Task ListWallets() { + if (GetUserId() == null) + { + return Challenge(AuthenticationSchemes.Cookie); + } var wallets = new ListWalletsViewModel(); var stores = await Repository.GetStoresByUserId(GetUserId()); @@ -209,7 +217,8 @@ namespace BTCPayServer.Controllers ListWalletsViewModel.WalletViewModel walletVm = new ListWalletsViewModel.WalletViewModel(); wallets.Wallets.Add(walletVm); walletVm.Balance = await wallet.Balance + " " + wallet.Wallet.Network.CryptoCode; - if (!wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key)) + walletVm.IsOwner = wallet.Store.Role == StoreRoles.Owner; + if (!walletVm.IsOwner) { walletVm.Balance = ""; } @@ -217,7 +226,6 @@ namespace BTCPayServer.Controllers walletVm.StoreId = wallet.Store.Id; walletVm.Id = new WalletId(wallet.Store.Id, wallet.Network.CryptoCode); walletVm.StoreName = wallet.Store.StoreName; - walletVm.IsOwner = wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key); } return View(wallets); @@ -229,7 +237,7 @@ namespace BTCPayServer.Controllers [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string labelFilter = null) { - DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId); + DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) return NotFound(); @@ -279,7 +287,7 @@ namespace BTCPayServer.Controllers if (walletId?.StoreId == null) return NotFound(); var store = await Repository.FindStore(walletId.StoreId, GetUserId()); - DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId, store); + DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) return NotFound(); var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode); @@ -422,7 +430,7 @@ namespace BTCPayServer.Controllers if (!ModelState.IsValid) return View(vm); - DerivationSchemeSettings derivationScheme = await GetDerivationSchemeSettings(walletId); + DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId); CreatePSBTResponse psbt = null; try @@ -511,7 +519,7 @@ namespace BTCPayServer.Controllers } ExtKey signingKey = null; - var settings = (await GetDerivationSchemeSettings(walletId)); + var settings = GetDerivationSchemeSettings(walletId); var signingKeySettings = settings.GetSigningAccountKeySettings(); if (signingKeySettings.RootFingerprint is null) signingKeySettings.RootFingerprint = extKey.GetPublicKey().GetHDFingerPrint(); @@ -557,13 +565,13 @@ namespace BTCPayServer.Controllers } } - private async Task RedirectToWalletTransaction(WalletId walletId, Transaction transaction) + private IActionResult RedirectToWalletTransaction(WalletId walletId, Transaction transaction) { var network = NetworkProvider.GetNetwork(walletId.CryptoCode); if (transaction != null) { var wallet = _walletProvider.GetWallet(network); - var derivationSettings = await GetDerivationSchemeSettings(walletId); + var derivationSettings = GetDerivationSchemeSettings(walletId); wallet.InvalidateCache(derivationSettings.AccountDerivation); StatusMessage = $"Transaction broadcasted successfully ({transaction.GetHash().ToString()})"; } @@ -578,13 +586,13 @@ namespace BTCPayServer.Controllers { if (walletId?.StoreId == null) return NotFound(); - DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId); + DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) return NotFound(); var vm = new RescanWalletModel(); vm.IsFullySync = _dashboard.IsFullySynched(walletId.CryptoCode, out var unused); - vm.IsServerAdmin = User.Claims.Any(c => c.Type == Policies.CanModifyServerSettings.Key && c.Value == "true"); + vm.IsServerAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded; vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true; var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode); var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation); @@ -614,14 +622,14 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("{walletId}/rescan")] - [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public async Task WalletRescan( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, RescanWalletModel vm) { if (walletId?.StoreId == null) return NotFound(); - DerivationSchemeSettings paymentMethod = await GetDerivationSchemeSettings(walletId); + DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) return NotFound(); var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode); @@ -649,24 +657,23 @@ namespace BTCPayServer.Controllers return null; } - private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId, StoreData store) + public StoreData CurrentStore { - if (store == null || !store.HasClaim(Policies.CanModifyStoreSettings.Key)) - return null; + get + { + return HttpContext.GetStoreData(); + } + } - var paymentMethod = store + private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId) + { + var paymentMethod = CurrentStore .GetSupportedPaymentMethods(NetworkProvider) .OfType() .FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode); return paymentMethod; } - private async Task GetDerivationSchemeSettings(WalletId walletId) - { - var store = (await Repository.FindStore(walletId.StoreId, GetUserId())); - return GetDerivationSchemeSettings(walletId, store); - } - private static async Task GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy) { using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) @@ -703,12 +710,11 @@ namespace BTCPayServer.Controllers { if (!HttpContext.WebSockets.IsWebSocketRequest) return NotFound(); - + var storeData = CurrentStore; var network = NetworkProvider.GetNetwork(walletId.CryptoCode); if (network == null) throw new FormatException("Invalid value for crypto code"); - var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); - var derivationSettings = GetDerivationSchemeSettings(walletId, storeData); + var derivationSettings = GetDerivationSchemeSettings(walletId); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); @@ -813,7 +819,7 @@ namespace BTCPayServer.Controllers [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId) { - var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); + var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) return NotFound(); var store = (await Repository.FindStore(walletId.StoreId, GetUserId())); @@ -842,7 +848,7 @@ namespace BTCPayServer.Controllers { if (!ModelState.IsValid) return View(vm); - var derivationScheme = await GetDerivationSchemeSettings(walletId); + var derivationScheme = GetDerivationSchemeSettings(walletId); if (derivationScheme == null) return NotFound(); derivationScheme.Label = vm.Label; diff --git a/BTCPayServer/Data/StoreDataExtensions.cs b/BTCPayServer/Data/StoreDataExtensions.cs index 23ab118ea..48ae7e992 100644 --- a/BTCPayServer/Data/StoreDataExtensions.cs +++ b/BTCPayServer/Data/StoreDataExtensions.cs @@ -16,29 +16,6 @@ namespace BTCPayServer.Data { public static class StoreDataExtensions { - public static bool HasClaim(this StoreData storeData, string claim) - { - return storeData.GetClaims().Any(c => c.Type == claim && c.Value == storeData.Id); - } - public static Claim[] GetClaims(this StoreData storeData) - { - List claims = new List(); - claims.AddRange(storeData.AdditionalClaims); -#pragma warning disable CS0612 // Type or member is obsolete - var role = storeData.Role; -#pragma warning restore CS0612 // Type or member is obsolete - if (role == StoreRoles.Owner) - { - claims.Add(new Claim(Policies.CanModifyStoreSettings.Key, storeData.Id)); - } - - if (role == StoreRoles.Owner || role == StoreRoles.Guest || storeData.GetStoreBlob().AnyoneCanInvoice) - { - claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeData.Id)); - } - return claims.ToArray(); - } - #pragma warning disable CS0618 public static PaymentMethodId GetDefaultPaymentId(this StoreData storeData, BTCPayNetworkProvider networks) { diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 4338468ae..e5e9d3e57 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -326,12 +326,7 @@ namespace BTCPayServer public static string GetSIN(this ClaimsPrincipal principal) { - return principal.Claims.Where(c => c.Type == Claims.SIN).Select(c => c.Value).FirstOrDefault(); - } - - public static string GetStoreId(this ClaimsPrincipal principal) - { - return principal.Claims.Where(c => c.Type == Claims.OwnStore).Select(c => c.Value).FirstOrDefault(); + return principal.Claims.Where(c => c.Type == Security.Bitpay.BitpayClaims.SIN).Select(c => c.Value).FirstOrDefault(); } public static void SetIsBitpayAPI(this HttpContext ctx, bool value) diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 2ec0e495b..4f70f7221 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -54,6 +54,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using BTCPayServer.Security.Bitpay; namespace BTCPayServer.Hosting { @@ -176,7 +178,6 @@ namespace BTCPayServer.Hosting return htmlSanitizer; }); - services.AddTransient(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -224,7 +225,9 @@ namespace BTCPayServer.Hosting services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); - services.AddTransient, BTCPayClaimsFilter>(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); services.TryAddSingleton(); services.TryAddSingleton(o => @@ -246,8 +249,8 @@ namespace BTCPayServer.Hosting services.AddSingleton(); // bundling - services.AddAuthorization(o => o.AddBTCPayPolicies().AddBTCPayRESTApiPolicies()); services.AddBtcPayServerAuthenticationSchemes(configuration); + services.AddAuthorization(o => o.AddBTCPayPolicies()); services.AddSingleton(); services.AddTransient(provider => diff --git a/BTCPayServer/Models/StoreViewModels/PairingModel.cs b/BTCPayServer/Models/StoreViewModels/PairingModel.cs index 86f3c8a03..addc17ef1 100644 --- a/BTCPayServer/Models/StoreViewModels/PairingModel.cs +++ b/BTCPayServer/Models/StoreViewModels/PairingModel.cs @@ -39,7 +39,7 @@ namespace BTCPayServer.Models.StoreViewModels [Display(Name = "Pair to")] [Required] - public string SelectedStore + public string StoreId { get; set; } diff --git a/BTCPayServer/Security/AuthenticationExtensions.cs b/BTCPayServer/Security/AuthenticationExtensions.cs index 4378162aa..b12c8a427 100644 --- a/BTCPayServer/Security/AuthenticationExtensions.cs +++ b/BTCPayServer/Security/AuthenticationExtensions.cs @@ -8,7 +8,7 @@ namespace BTCPayServer.Security { public static AuthenticationBuilder AddBitpayAuthentication(this AuthenticationBuilder builder) { - builder.AddScheme(Policies.BitpayAuthentication, o => { }); + builder.AddScheme(AuthenticationSchemes.Bitpay, o => { }); return builder; } } diff --git a/BTCPayServer/Security/AuthenticationSchemes.cs b/BTCPayServer/Security/AuthenticationSchemes.cs new file mode 100644 index 000000000..bd861ec31 --- /dev/null +++ b/BTCPayServer/Security/AuthenticationSchemes.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using OpenIddict.Validation; + +namespace BTCPayServer.Security +{ + public class AuthenticationSchemes + { + public const string Cookie = "Identity.Application"; + public const string Bitpay = "Bitpay"; + public const string OpenId = OpenIddictValidationDefaults.AuthenticationScheme; + } +} diff --git a/BTCPayServer/Security/BTCPayClaimsFilter.cs b/BTCPayServer/Security/BTCPayClaimsFilter.cs deleted file mode 100644 index bfb30d681..000000000 --- a/BTCPayServer/Security/BTCPayClaimsFilter.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Security.Claims; -using System.Threading.Tasks; -using BTCPayServer.Data; -using BTCPayServer.Models; -using BTCPayServer.Services.Stores; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Options; - -namespace BTCPayServer.Security -{ - public class BTCPayClaimsFilter : IAsyncAuthorizationFilter, IConfigureOptions - { - UserManager _userManager; - StoreRepository _StoreRepository; - public BTCPayClaimsFilter( - UserManager userManager, - StoreRepository storeRepository) - { - _userManager = userManager; - _StoreRepository = storeRepository; - } - - void IConfigureOptions.Configure(MvcOptions options) - { - options.Filters.Add(typeof(BTCPayClaimsFilter)); - } - - public async Task OnAuthorizationAsync(AuthorizationFilterContext context) - { - if (context.HttpContext.User?.Identity?.AuthenticationType != Policies.CookieAuthentication) - return; - var principal = context.HttpContext.User; - var identity = ((ClaimsIdentity)principal.Identity); - if (principal.IsInRole(Roles.ServerAdmin)) - { - identity.AddClaim(new Claim(Policies.CanModifyServerSettings.Key, "true")); - } - - if (context.RouteData.Values.TryGetValue("storeId", out var storeId)) - { - var userid = _userManager.GetUserId(principal); - - if (!string.IsNullOrEmpty(userid)) - { - var store = await _StoreRepository.FindStore((string)storeId, userid); - if (store == null) - { - context.Result = new ChallengeResult(); - } - else - { - context.HttpContext.SetStoreData(store); - identity.AddClaims(store.GetClaims()); - } - } - } - } - } -} diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs b/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs index a20baad25..74425455d 100644 --- a/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs +++ b/BTCPayServer/Security/Bitpay/BitpayAuthenticationHandler.cs @@ -41,63 +41,39 @@ namespace BTCPayServer.Security.Bitpay protected override async Task HandleAuthenticateAsync() { - List claims = new List(); if (!Context.Request.HttpContext.TryGetBitpayAuth(out var bitpayAuth)) return AuthenticateResult.NoResult(); - string storeId = null; - bool anonymous = true; - bool? success = null; if (!string.IsNullOrEmpty(bitpayAuth.Signature) && !string.IsNullOrEmpty(bitpayAuth.Id)) { - var result = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id, claims); - storeId = result.StoreId; - success = result.SuccessAuth; - anonymous = false; + var sin = await CheckBitId(Context.Request.HttpContext, bitpayAuth.Signature, bitpayAuth.Id); + if (sin == null) + return AuthenticateResult.Fail("BitId authentication failed"); + return Success(BitpayClaims.SIN, sin, BitpayAuthenticationTypes.SinAuthentication); } else if (!string.IsNullOrEmpty(bitpayAuth.Authorization)) { - storeId = await CheckLegacyAPIKey(Context.Request.HttpContext, bitpayAuth.Authorization); - success = storeId != null; - anonymous = false; + var storeId = await GetStoreIdFromAuth(Context.Request.HttpContext, bitpayAuth.Authorization); + if (storeId == null) + return AuthenticateResult.Fail("ApiKey authentication failed"); + return Success(BitpayClaims.ApiKeyStoreId, storeId, BitpayAuthenticationTypes.ApiKeyAuthentication); } else { - if (Context.Request.HttpContext.Request.Query.TryGetValue("storeId", out var storeIdStringValues)) - { - storeId = storeIdStringValues.FirstOrDefault() ?? string.Empty; - success = true; - anonymous = true; - } + return Success(null, null, BitpayAuthenticationTypes.Anonymous); } - - if (success is true) - { - if (storeId != null) - { - claims.Add(new Claim(Policies.CanCreateInvoice.Key, storeId)); - var store = await _StoreRepository.FindStore(storeId); - if (store == null || - (anonymous && !store.GetStoreBlob().AnyoneCanInvoice)) - { - return AuthenticateResult.Fail("Invalid credentials"); - } - store.AdditionalClaims.AddRange(claims); - Context.Request.HttpContext.SetStoreData(store); - } - return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, Policies.BitpayAuthentication)), Policies.BitpayAuthentication)); - } - else if (success is false) - { - return AuthenticateResult.Fail("Invalid credentials"); - } - return AuthenticateResult.NoResult(); } - private async Task<(string StoreId, bool SuccessAuth)> CheckBitId(HttpContext httpContext, string sig, string id, List claims) + private AuthenticateResult Success(string claimType, string claimValue, string authenticationType) + { + List claims = new List(); + if (claimType != null) + claims.Add(new Claim(claimType, claimValue)); + return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType)), authenticationType)); + } + + private async Task CheckBitId(HttpContext httpContext, string sig, string id) { httpContext.Request.EnableBuffering(); - - string storeId = null; string body = string.Empty; if (httpContext.Request.ContentLength != 0 && httpContext.Request.Body != null) { @@ -114,44 +90,14 @@ namespace BTCPayServer.Security.Bitpay var key = new PubKey(id); if (BitIdExtensions.CheckBitIDSignature(key, sig, url, body)) { - var sin = key.GetBitIDSIN(); - claims.Add(new Claim(Claims.SIN, sin)); - - string token = null; - if (httpContext.Request.Query.TryGetValue("token", out var tokenValues)) - { - token = tokenValues[0]; - } - - if (token == null && !String.IsNullOrEmpty(body) && httpContext.Request.Method == "POST") - { - try - { - token = JObject.Parse(body)?.Property("token", StringComparison.OrdinalIgnoreCase)?.Value?.Value(); - } - catch { } - } - - if (token != null) - { - var bitToken = (await _TokenRepository.GetTokens(sin)).FirstOrDefault(); - if (bitToken == null) - { - return (null, false); - } - storeId = bitToken.StoreId; - } - } - else - { - return (storeId, false); + return key.GetBitIDSIN(); } } - catch (FormatException) { } - return (storeId, true); + catch { } + return null; } - private async Task CheckLegacyAPIKey(HttpContext httpContext, string auth) + private async Task GetStoreIdFromAuth(HttpContext httpContext, string auth) { var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries); if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase)) diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthenticationTypes.cs b/BTCPayServer/Security/Bitpay/BitpayAuthenticationTypes.cs new file mode 100644 index 000000000..b2e181493 --- /dev/null +++ b/BTCPayServer/Security/Bitpay/BitpayAuthenticationTypes.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Security.Bitpay +{ + public class BitpayAuthenticationTypes + { + public const string ApiKeyAuthentication = "Bitpay.APIKey"; + public const string SinAuthentication = "Bitpay.SIN"; + public const string Anonymous = "Bitpay.Anonymous"; + } +} diff --git a/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs new file mode 100644 index 000000000..b13f4a627 --- /dev/null +++ b/BTCPayServer/Security/Bitpay/BitpayAuthorizationHandler.cs @@ -0,0 +1,73 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authentication; +using BTCPayServer.Authentication; +using BTCPayServer.Services; +using BTCPayServer.Security.Bitpay; + +namespace BTCPayServer.Security.Bitpay +{ + public class BitpayAuthorizationHandler : AuthorizationHandler + { + private readonly HttpContext _HttpContext; + private readonly StoreRepository _storeRepository; + private readonly TokenRepository _tokenRepository; + + public BitpayAuthorizationHandler(IHttpContextAccessor httpContextAccessor, + StoreRepository storeRepository, + TokenRepository tokenRepository) + { + _HttpContext = httpContextAccessor.HttpContext; + _storeRepository = storeRepository; + _tokenRepository = tokenRepository; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) + { + string storeId = null; + if (context.User.Identity.AuthenticationType == BitpayAuthenticationTypes.ApiKeyAuthentication) + { + storeId = context.User.Claims.Where(c => c.Type == BitpayClaims.ApiKeyStoreId).Select(c => c.Value).First(); + } + else if (context.User.Identity.AuthenticationType == BitpayAuthenticationTypes.SinAuthentication) + { + var sin = context.User.Claims.Where(c => c.Type == BitpayClaims.SIN).Select(c => c.Value).First(); + var bitToken = (await _tokenRepository.GetTokens(sin)).FirstOrDefault(); + storeId = bitToken?.StoreId; + } + else if (context.User.Identity.AuthenticationType == BitpayAuthenticationTypes.Anonymous) + { + storeId = _HttpContext.GetImplicitStoreId(); + } + if (storeId == null) + return; + var store = await _storeRepository.FindStore(storeId); + if (store == null) + return; + var isAnonymous = context.User.Identity.AuthenticationType == BitpayAuthenticationTypes.Anonymous; + var anyoneCanInvoice = store.GetStoreBlob().AnyoneCanInvoice; + switch (requirement.Policy) + { + case Policies.CanCreateInvoice.Key: + if (!isAnonymous || (isAnonymous && anyoneCanInvoice)) + { + context.Succeed(requirement); + _HttpContext.SetStoreData(store); + return; + } + break; + case Policies.CanGetRates.Key: + context.Succeed(requirement); + _HttpContext.SetStoreData(store); + return; + } + } + } +} diff --git a/BTCPayServer/Security/Bitpay/BitpayClaims.cs b/BTCPayServer/Security/Bitpay/BitpayClaims.cs new file mode 100644 index 000000000..ea8c86f0c --- /dev/null +++ b/BTCPayServer/Security/Bitpay/BitpayClaims.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Security.Bitpay +{ + public class BitpayClaims + { + public const string SIN = "Bitpay.SIN"; + public const string ApiKeyStoreId = "Bitpay.ApiKeyStoreId"; + } +} diff --git a/BTCPayServer/Security/ClaimTransformer.cs b/BTCPayServer/Security/ClaimTransformer.cs deleted file mode 100644 index 2397c9bb5..000000000 --- a/BTCPayServer/Security/ClaimTransformer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using System.Linq; -using Microsoft.AspNetCore.Http; -using BTCPayServer.Data; -using BTCPayServer.Services.Stores; -using System.Security.Claims; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Authentication; -using BTCPayServer.Authentication; - -namespace BTCPayServer.Security -{ - public class ClaimTransformer : IClaimsTransformation - { - private readonly HttpContext _HttpContext; - private readonly UserManager _userManager; - private readonly StoreRepository _storeRepository; - - public ClaimTransformer(IHttpContextAccessor httpContextAccessor, - UserManager userManager, - StoreRepository storeRepository) - { - _HttpContext = httpContextAccessor.HttpContext; - _userManager = userManager; - _storeRepository = storeRepository; - } - public async Task TransformAsync(ClaimsPrincipal principal) - { - var routeData = _HttpContext.GetRouteData(); - if (routeData == null) - return principal; - var identity = ((ClaimsIdentity)principal.Identity); - // A ClaimTransform can be called several time, we prevent dups by removing all the - // claims this transform might add. - var claims = new[] { RestAPIPolicies.CanEditStore }; - foreach (var claim in identity.Claims.Where(c => claims.Contains(c.Type)).ToList()) - { - identity.RemoveClaim(claim); - } - - if (!routeData.Values.TryGetValue("storeId", out var storeId)) - { - return principal; - } - var userid = _userManager.GetUserId(principal); - if (!string.IsNullOrEmpty(userid)) - { - var store = await _storeRepository.FindStore((string)storeId, userid); - if (store != null) - { - _HttpContext.SetStoreData(store); - foreach (var claim in store.GetClaims()) - { - if (claim.Type.Equals(Policies.CanModifyStoreSettings.Key, System.StringComparison.OrdinalIgnoreCase)) - { - identity.AddClaim(new Claim(RestAPIPolicies.CanEditStore, store.Id)); - } - } - } - } - return principal; - } - } -} diff --git a/BTCPayServer/Security/CookieAuthorizationHandler.cs b/BTCPayServer/Security/CookieAuthorizationHandler.cs new file mode 100644 index 000000000..1859fa632 --- /dev/null +++ b/BTCPayServer/Security/CookieAuthorizationHandler.cs @@ -0,0 +1,81 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authentication; +using BTCPayServer.Authentication; +using Microsoft.Extensions.Primitives; + +namespace BTCPayServer.Security +{ + public class CookieAuthorizationHandler : AuthorizationHandler + { + private readonly HttpContext _HttpContext; + private readonly UserManager _userManager; + private readonly StoreRepository _storeRepository; + + public CookieAuthorizationHandler(IHttpContextAccessor httpContextAccessor, + UserManager userManager, + StoreRepository storeRepository) + { + _HttpContext = httpContextAccessor.HttpContext; + _userManager = userManager; + _storeRepository = storeRepository; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) + { + if (context.User.Identity.AuthenticationType != AuthenticationSchemes.Cookie) + return; + + var isAdmin = context.User.IsInRole(Roles.ServerAdmin); + switch (requirement.Policy) + { + case Policies.CanModifyServerSettings.Key: + if (isAdmin) + context.Succeed(requirement); + return; + } + + string storeId = _HttpContext.GetImplicitStoreId(); + if (storeId == null) + return; + + var userid = _userManager.GetUserId(context.User); + if (string.IsNullOrEmpty(userid)) + return; + + + var store = await _storeRepository.FindStore((string)storeId, userid); + if (store == null) + return; + bool success = false; + switch (requirement.Policy) + { + case Policies.CanModifyStoreSettings.Key: + if (store.Role == StoreRoles.Owner || isAdmin) + success = true; + break; + case Policies.CanCreateInvoice.Key: + if (store.Role == StoreRoles.Owner || + store.Role == StoreRoles.Guest || + isAdmin || + store.GetStoreBlob().AnyoneCanInvoice) + success = true; + break; + } + + if (success) + { + context.Succeed(requirement); + _HttpContext.SetStoreData(store); + return; + } + } + } +} diff --git a/BTCPayServer/Security/OpenIdAuthorizationHandler.cs b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs new file mode 100644 index 000000000..cde79318e --- /dev/null +++ b/BTCPayServer/Security/OpenIdAuthorizationHandler.cs @@ -0,0 +1,92 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using System.Security.Claims; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Authentication; +using BTCPayServer.Authentication; +using Microsoft.Extensions.Primitives; +using static BTCPayServer.Authentication.RestAPIPolicies; +using OpenIddict.Abstractions; + +namespace BTCPayServer.Security +{ + public class OpenIdAuthorizationHandler : AuthorizationHandler + { + private readonly HttpContext _HttpContext; + private readonly UserManager _userManager; + private readonly StoreRepository _storeRepository; + + public OpenIdAuthorizationHandler(IHttpContextAccessor httpContextAccessor, + UserManager userManager, + StoreRepository storeRepository) + { + _HttpContext = httpContextAccessor.HttpContext; + _userManager = userManager; + _storeRepository = storeRepository; + } + protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PolicyRequirement requirement) + { + if (context.User.Identity.AuthenticationType != "AuthenticationTypes.Federation") + return; + + bool success = false; + switch (requirement.Policy) + { + case RestAPIPolicies.CanViewStores: + success = context.HasScopes(BTCPayScopes.StoreManagement) || context.HasScopes(BTCPayScopes.ViewStores); + break; + case RestAPIPolicies.CanManageStores: + success = context.HasScopes(BTCPayScopes.StoreManagement); + break; + case RestAPIPolicies.CanViewInvoices: + success = context.HasScopes(BTCPayScopes.ViewInvoices) || context.HasScopes(BTCPayScopes.InvoiceManagement); + break; + case RestAPIPolicies.CanCreateInvoices: + success = context.HasScopes(BTCPayScopes.CreateInvoices) || context.HasScopes(BTCPayScopes.InvoiceManagement); + break; + case RestAPIPolicies.CanViewApps: + success = context.HasScopes(BTCPayScopes.AppManagement) || context.HasScopes(BTCPayScopes.ViewApps); + break; + case RestAPIPolicies.CanManageInvoices: + success = context.HasScopes(BTCPayScopes.InvoiceManagement); + break; + case RestAPIPolicies.CanManageApps: + success = context.HasScopes(BTCPayScopes.AppManagement); + break; + case RestAPIPolicies.CanManageWallet: + success = context.HasScopes(BTCPayScopes.WalletManagement); + break; + case RestAPIPolicies.CanViewProfile: + success = context.HasScopes(OpenIddictConstants.Scopes.Profile); + break; + case Policies.CanModifyStoreSettings.Key: + string storeId = _HttpContext.GetImplicitStoreId(); + if (storeId == null) + break; + var userid = _userManager.GetUserId(context.User); + if (string.IsNullOrEmpty(userid)) + break; + var store = await _storeRepository.FindStore((string)storeId, userid); + if (store == null) + break; + success = true; + _HttpContext.SetStoreData(store); + break; + case Policies.CanModifyServerSettings.Key: + success = context.User.HasClaim("role", Roles.ServerAdmin); + break; + } + + if (success) + { + context.Succeed(requirement); + } + } + } +} diff --git a/BTCPayServer/Security/Policies.cs b/BTCPayServer/Security/Policies.cs index c7dd4c16e..69f015a1a 100644 --- a/BTCPayServer/Security/Policies.cs +++ b/BTCPayServer/Security/Policies.cs @@ -2,26 +2,35 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using BTCPayServer.Authentication; using Microsoft.AspNetCore.Authorization; namespace BTCPayServer.Security { public static class Policies { - - public const string BitpayAuthentication = "Bitpay.Auth"; - public const string CookieAuthentication = "Identity.Application"; public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) { - AddClaim(options, CanModifyStoreSettings.Key); - AddClaim(options, CanModifyServerSettings.Key); - AddClaim(options, CanCreateInvoice.Key); + options.AddPolicy(CanModifyStoreSettings.Key); + options.AddPolicy(CanCreateInvoice.Key); + options.AddPolicy(CanGetRates.Key); + options.AddPolicy(CanModifyServerSettings.Key); + + options.AddPolicy(RestAPIPolicies.CanCreateInvoices); + options.AddPolicy(RestAPIPolicies.CanManageApps); + options.AddPolicy(RestAPIPolicies.CanManageInvoices); + options.AddPolicy(RestAPIPolicies.CanManageStores); + options.AddPolicy(RestAPIPolicies.CanManageWallet); + options.AddPolicy(RestAPIPolicies.CanViewApps); + options.AddPolicy(RestAPIPolicies.CanViewInvoices); + options.AddPolicy(RestAPIPolicies.CanViewProfile); + options.AddPolicy(RestAPIPolicies.CanViewStores); return options; } - private static void AddClaim(AuthorizationOptions options, string key) + public static void AddPolicy(this AuthorizationOptions options, string policy) { - options.AddPolicy(key, o => o.RequireClaim(key)); + options.AddPolicy(policy, o => o.AddRequirements(new PolicyRequirement(policy))); } public class CanModifyServerSettings @@ -36,6 +45,10 @@ namespace BTCPayServer.Security { public const string Key = "btcpay.store.cancreateinvoice"; } - + + public class CanGetRates + { + public const string Key = "btcpay.store.cangetrates"; + } } } diff --git a/BTCPayServer/Security/PolicyRequirement.cs b/BTCPayServer/Security/PolicyRequirement.cs new file mode 100644 index 000000000..3275a2950 --- /dev/null +++ b/BTCPayServer/Security/PolicyRequirement.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; + +namespace BTCPayServer.Security +{ + public class PolicyRequirement : IAuthorizationRequirement + { + public PolicyRequirement(string policy) + { + if (policy == null) + throw new ArgumentNullException(nameof(policy)); + Policy = policy; + } + public string Policy { get; } + } +} diff --git a/BTCPayServer/Security/SecurityExtensions.cs b/BTCPayServer/Security/SecurityExtensions.cs new file mode 100644 index 000000000..ddeba1575 --- /dev/null +++ b/BTCPayServer/Security/SecurityExtensions.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Routing; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using OpenIddict.Abstractions; +using BTCPayServer.Data; +using BTCPayServer.Services.Stores; +using Microsoft.AspNetCore.Identity; + +namespace BTCPayServer.Security +{ + public static class SecurityExtensions + { + public static bool HasScopes(this AuthorizationHandlerContext context, params string[] scopes) + { + return scopes.All(s => context.User.HasClaim(OpenIddictConstants.Claims.Scope, s)); + } + public static string GetImplicitStoreId(this HttpContext httpContext) + { + // 1. Check in the routeData + var routeData = httpContext.GetRouteData(); + string storeId = null; + if (routeData != null) + { + if (routeData.Values.TryGetValue("storeId", out var v)) + storeId = v as string; + } + + if (storeId == null) + { + if (httpContext.Request.Query.TryGetValue("storeId", out var sv)) + { + storeId = sv.FirstOrDefault(); + } + } + + // 2. Check in forms + if (storeId == null) + { + if (httpContext.Request.HasFormContentType && + httpContext.Request.Form != null && + httpContext.Request.Form.TryGetValue("storeId", out var sv)) + { + storeId = sv.FirstOrDefault(); + } + } + + // 3. Checks in walletId + if (storeId == null && routeData != null) + { + if (routeData.Values.TryGetValue("walletId", out var walletId) && + WalletId.TryParse((string)walletId, out var w)) + { + storeId = w.StoreId; + } + } + + return storeId; + } + } +} diff --git a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs index c727aeef3..35ee83c38 100644 --- a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs +++ b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs @@ -25,9 +25,9 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI { [Route("stores/{storeId}/monerolike")] [OnlyIfSupportAttribute("XMR")] - [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] - [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)] - [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = Policies.CookieAuthentication)] + [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyStoreSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] + [Authorize(Policy = Policies.CanModifyServerSettings.Key, AuthenticationSchemes = AuthenticationSchemes.Cookie)] public class MoneroLikeStoreController : Controller { private readonly MoneroLikeConfiguration _MoneroLikeConfiguration; diff --git a/BTCPayServer/Services/Claims.cs b/BTCPayServer/Services/Claims.cs deleted file mode 100644 index fafe8562a..000000000 --- a/BTCPayServer/Services/Claims.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; - -namespace BTCPayServer.Services -{ - public class Claims - { - public const string SIN = "BITID_SIN"; - - public const string OwnStore = "BTCPAY_OWN_STORE"; - } -} diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index e7f2e4645..4fb16edb1 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -48,9 +48,7 @@ namespace BTCPayServer.Services.Stores }).ToArrayAsync()) .Select(us => { -#pragma warning disable CS0612 // Type or member is obsolete us.Store.Role = us.Role; -#pragma warning restore CS0612 // Type or member is obsolete return us.Store; }).FirstOrDefault(); } @@ -90,9 +88,7 @@ namespace BTCPayServer.Services.Stores .ToArrayAsync()) .Select(u => { -#pragma warning disable CS0612 // Type or member is obsolete u.StoreData.Role = u.Role; -#pragma warning restore CS0612 // Type or member is obsolete return u.StoreData; }).ToArray(); } diff --git a/BTCPayServer/Views/Authorization/Authorize.cshtml b/BTCPayServer/Views/Authorization/Authorize.cshtml index 1f44b6d12..0a9c7f9ea 100644 --- a/BTCPayServer/Views/Authorization/Authorize.cshtml +++ b/BTCPayServer/Views/Authorization/Authorize.cshtml @@ -49,7 +49,7 @@
- + @@ -57,7 +57,7 @@
- +
diff --git a/BTCPayServer/Views/Stores/CreateToken.cshtml b/BTCPayServer/Views/Stores/CreateToken.cshtml index fbc3548c2..aa1229503 100644 --- a/BTCPayServer/Views/Stores/CreateToken.cshtml +++ b/BTCPayServer/Views/Stores/CreateToken.cshtml @@ -39,7 +39,7 @@ { }
- +
diff --git a/BTCPayServer/Views/Stores/ListTokens.cshtml b/BTCPayServer/Views/Stores/ListTokens.cshtml index a252ddcea..e4f7ece8a 100644 --- a/BTCPayServer/Views/Stores/ListTokens.cshtml +++ b/BTCPayServer/Views/Stores/ListTokens.cshtml @@ -20,7 +20,7 @@
- Create a new token + Create a new token diff --git a/BTCPayServer/Views/Stores/RequestPairing.cshtml b/BTCPayServer/Views/Stores/RequestPairing.cshtml index 5ad86a2f7..fca35e04c 100644 --- a/BTCPayServer/Views/Stores/RequestPairing.cshtml +++ b/BTCPayServer/Views/Stores/RequestPairing.cshtml @@ -33,12 +33,12 @@
- - - + + +
- +
diff --git a/BTCPayServer/Views/Wallets/_Nav.cshtml b/BTCPayServer/Views/Wallets/_Nav.cshtml index a5318ff0b..4d33b1dce 100644 --- a/BTCPayServer/Views/Wallets/_Nav.cshtml +++ b/BTCPayServer/Views/Wallets/_Nav.cshtml @@ -3,7 +3,7 @@