Refactor authorizations

This commit is contained in:
nicolas.dorier 2019-10-12 20:35:30 +09:00
parent bd94b5f84e
commit 281a2461ad
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
55 changed files with 732 additions and 646 deletions

View file

@ -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<PairedSINData> PairedSINs { get; set; }
public IEnumerable<APIKeyData> APIKeys { get; set; }
[NotMapped]
public List<Claim> AdditionalClaims { get; set; } = new List<Claim>();
}
public enum NetworkFeeMode

View file

@ -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<bool>(accessToken,
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
$"api/test/me/is-admin",
tester.PayTester.HttpClient));

View file

@ -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<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
public T GetController<T>(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<Claim> claims = new List<Claim>();
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)
{

View file

@ -40,7 +40,7 @@ namespace BTCPayServer.Tests
var controller = tester.PayTester.GetController<StoresController>(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<ChangellySettings>(storeBlob.ChangellySettings);
@ -224,6 +224,7 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Fast", "Fast")]
public void CanComputeBaseAmount()
{

View file

@ -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"));
}
}

View file

@ -30,7 +30,7 @@ namespace BTCPayServer.Tests
var controller = tester.PayTester.GetController<StoresController>(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<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);

View file

@ -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<T>(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");
}
}
}

View file

@ -49,7 +49,7 @@ namespace BTCPayServer.Tests
Assert.Equal("paid", invoice.Status);
});
var walletController = tester.PayTester.GetController<WalletsController>(user.UserId);
var walletController = user.GetController<WalletsController>();
var walletId = new WalletId(user.StoreId, "BTC");
var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString();
var sendModel = new WalletSendModel()

View file

@ -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();

View file

@ -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");

View file

@ -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<UserManager<ApplicationUser>>();
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<T>(bool setImplicitStore = true) where T : Controller
{
return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
var controller = parent.PayTester.GetController<T>(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)
{

View file

@ -745,7 +745,7 @@ namespace BTCPayServer.Tests
pairingCode = acc.BitPay.RequestClientAuthorization("test1", Facade.Merchant);
acc.CreateStore();
var store2 = acc.GetController<StoresController>();
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<WalletsController>(acc.UserId);
var walletController = acc.GetController<WalletsController>();
WalletId walletId = new WalletId(acc.StoreId, "LTC");
var rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(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<WalletsController>(acc.UserId, additionalClaims: serverAdminClaim);
acc.IsAdmin = true;
walletController = acc.GetController<WalletsController>();
rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(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<RateController>();
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<DataWrapper<Rate[]>>();
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<DataWrapper<Rate[]>>();
// We don't have any default currencies, so this should be failing
Assert.Null(GetRatesResult?.Data);
var store = acc.GetController<StoresController>();
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates(acc.StoreId)).Model);
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates()).Model);
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
store.Rates(ratesVM).Wait();
store = acc.GetController<StoresController>();
rateController = acc.GetController<RateController>();
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId, default)
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
// 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<DataWrapper<Rate>>();
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<StoresController>();
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<StoresController>();
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<StoresController>();
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(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<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.Equal("bitflyer", rateVm.PreferredExchange);
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
@ -1431,7 +1434,7 @@ namespace BTCPayServer.Tests
Assert.IsType<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
store = user.GetController<StoresController>();
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(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<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
store = user.GetController<StoresController>();
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(user.StoreId)).Model);
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
Assert.Equal(50, rateVm.Spread);
Assert.True(rateVm.ShowScripting);
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);

View file

@ -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<AuthorizationHandlerContext, bool> 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<LambdaRequirement>, IAuthorizationRequirement
{
private readonly Func<AuthorizationHandlerContext, bool> _Func;
public LambdaRequirement(Func<AuthorizationHandlerContext, bool> func)
{
_Func = func;
}
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, LambdaRequirement requirement)
{
if (_Func.Invoke(context))
{
context.Succeed(requirement);
}
return Task.CompletedTask;
}
}
}

View file

@ -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
{

View file

@ -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
{

View file

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

View file

@ -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()

View file

@ -54,7 +54,7 @@ namespace BTCPayServer.Controllers
_IdentityOptions = identityOptions;
}
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIddictRequest openIdConnectRequest)
{
@ -89,7 +89,7 @@ namespace BTCPayServer.Controllers
});
}
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpPost("/connect/authorize")]
public async Task<IActionResult> Authorize(OpenIddictRequest openIdConnectRequest,
string consent, bool createAuthorization = true)

View file

@ -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;

View file

@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
{
[HttpGet]
[Route("invoices/{invoiceId}")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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);

View file

@ -66,8 +66,6 @@ namespace BTCPayServer.Controllers
internal async Task<DataWrapper<InvoiceResponse>> CreateInvoiceCore(CreateInvoiceRequest invoice, StoreData store, string serverUrl, List<string> 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");

View file

@ -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
{

View file

@ -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)

View file

@ -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<IActionResult> GetBaseCurrencyRates(string baseCurrency, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> 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<Rate[]>(rates));
}
[Route("rates/{baseCurrency}/{currency}")]
[HttpGet]
[BitpayAPIConstraint]
public async Task<IActionResult> GetCurrencyPairRate(string baseCurrency, string currency, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> 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<IActionResult> GetRates(string currencyPairs, string storeId, CancellationToken cancellationToken)
public async Task<IActionResult> 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<Rate[]>(rates));
}
private async Task<string> 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<IActionResult> 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}");

View file

@ -19,7 +19,7 @@ namespace BTCPayServer.Controllers.RestApi
/// </summary>
[Route("api/[controller]")]
[ApiController]
[Authorize(AuthenticationSchemes = OpenIddictValidationDefaults.AuthenticationScheme)]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.OpenId)]
public class TestController : ControllerBase
{
private readonly UserManager<ApplicationUser> _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; }

View file

@ -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<ApplicationUser> _UserManager;

View file

@ -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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> Pair(string pairingCode, string selectedStore)
public async Task<IActionResult> 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<IActionResult> 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
});
}

View file

@ -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);

View file

@ -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")
{

View file

@ -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<ApplicationUser> _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<IActionResult> 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<BTCPayNetwork>(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<IActionResult> RedirectToWalletTransaction(WalletId walletId, Transaction transaction)
private IActionResult RedirectToWalletTransaction(WalletId walletId, Transaction transaction)
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(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<IActionResult> 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<DerivationSchemeSettings>()
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
return paymentMethod;
}
private async Task<DerivationSchemeSettings> GetDerivationSchemeSettings(WalletId walletId)
{
var store = (await Repository.FindStore(walletId.StoreId, GetUserId()));
return GetDerivationSchemeSettings(walletId, store);
}
private static async Task<string> 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<BTCPayNetwork>(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;

View file

@ -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<Claim> claims = new List<Claim>();
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)
{

View file

@ -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)

View file

@ -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<IClaimsTransformation, ClaimTransformer>();
services.TryAddSingleton<LightningConfigurationProvider>();
services.TryAddSingleton<LanguageService>();
services.TryAddSingleton<NBXplorerDashboard>();
@ -224,7 +225,9 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IHostedService, TorServicesHostedService>();
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
services.AddTransient<IConfigureOptions<MvcOptions>, BTCPayClaimsFilter>();
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, OpenIdAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
services.TryAddSingleton<ExplorerClientProvider>();
services.TryAddSingleton<Bitpay>(o =>
@ -246,8 +249,8 @@ namespace BTCPayServer.Hosting
services.AddSingleton<EmailSenderFactory>();
// bundling
services.AddAuthorization(o => o.AddBTCPayPolicies().AddBTCPayRESTApiPolicies());
services.AddBtcPayServerAuthenticationSchemes(configuration);
services.AddAuthorization(o => o.AddBTCPayPolicies());
services.AddSingleton<IBundleProvider, ResourceBundleProvider>();
services.AddTransient<BundleOptions>(provider =>

View file

@ -39,7 +39,7 @@ namespace BTCPayServer.Models.StoreViewModels
[Display(Name = "Pair to")]
[Required]
public string SelectedStore
public string StoreId
{
get; set;
}

View file

@ -8,7 +8,7 @@ namespace BTCPayServer.Security
{
public static AuthenticationBuilder AddBitpayAuthentication(this AuthenticationBuilder builder)
{
builder.AddScheme<BitpayAuthenticationOptions, BitpayAuthenticationHandler>(Policies.BitpayAuthentication, o => { });
builder.AddScheme<BitpayAuthenticationOptions, BitpayAuthenticationHandler>(AuthenticationSchemes.Bitpay, o => { });
return builder;
}
}

View file

@ -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;
}
}

View file

@ -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<MvcOptions>
{
UserManager<ApplicationUser> _userManager;
StoreRepository _StoreRepository;
public BTCPayClaimsFilter(
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository)
{
_userManager = userManager;
_StoreRepository = storeRepository;
}
void IConfigureOptions<MvcOptions>.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());
}
}
}
}
}
}

View file

@ -41,63 +41,39 @@ namespace BTCPayServer.Security.Bitpay
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
List<Claim> claims = new List<Claim>();
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<Claim> claims)
private AuthenticateResult Success(string claimType, string claimValue, string authenticationType)
{
List<Claim> claims = new List<Claim>();
if (claimType != null)
claims.Add(new Claim(claimType, claimValue));
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, authenticationType)), authenticationType));
}
private async Task<string> 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<string>();
}
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<string> CheckLegacyAPIKey(HttpContext httpContext, string auth)
private async Task<string> GetStoreIdFromAuth(HttpContext httpContext, string auth)
{
var splitted = auth.Split(' ', StringSplitOptions.RemoveEmptyEntries);
if (splitted.Length != 2 || !splitted[0].Equals("Basic", StringComparison.OrdinalIgnoreCase))

View file

@ -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";
}
}

View file

@ -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<PolicyRequirement>
{
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;
}
}
}
}

View file

@ -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";
}
}

View file

@ -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<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
public ClaimTransformer(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> userManager,
StoreRepository storeRepository)
{
_HttpContext = httpContextAccessor.HttpContext;
_userManager = userManager;
_storeRepository = storeRepository;
}
public async Task<ClaimsPrincipal> 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;
}
}
}

View file

@ -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<PolicyRequirement>
{
private readonly HttpContext _HttpContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
public CookieAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> 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;
}
}
}
}

View file

@ -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<PolicyRequirement>
{
private readonly HttpContext _HttpContext;
private readonly UserManager<ApplicationUser> _userManager;
private readonly StoreRepository _storeRepository;
public OpenIdAuthorizationHandler(IHttpContextAccessor httpContextAccessor,
UserManager<ApplicationUser> 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);
}
}
}
}

View file

@ -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";
}
}
}

View file

@ -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; }
}
}

View file

@ -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;
}
}
}

View file

@ -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;

View file

@ -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";
}
}

View file

@ -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();
}

View file

@ -49,7 +49,7 @@
<div class="row mb-2">
<div class="col-lg-12 text-center">
<div class="btn-group">
<button class="btn btn-lg btn-primary" name="consent" id="consent-yes" type="submit" value="Yes">Authorize app</button>
<button class="btn btn-primary" name="consent" id="consent-yes" type="submit" value="Yes">Authorize app</button>
<button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
@ -57,7 +57,7 @@
<button class="dropdown-item" name="consent" id="consent-yes-temporary" type="submit" value="YesTemporary">Authorize app until session ends</button>
</div>
</div>
<button class="btn btn-lg btn-secondary" id="consent-no" name="consent" type="submit" value="No">Cancel</button>
<button class="btn btn-secondary" id="consent-no" name="consent" type="submit" value="No">Cancel</button>
</div>
</div>

View file

@ -39,7 +39,7 @@
{
<input type="hidden" asp-for="StoreId" />}
<div class="form-group">
<input type="submit" value="Request pairing" class="btn btn-primary" />
<input id="RequestPairing" type="submit" value="Request pairing" class="btn btn-primary" />
</div>
</form>
</div>

View file

@ -20,7 +20,7 @@
</div>
<div class="row">
<div class="col-md-8">
<a asp-action="CreateToken" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new token</a>
<a id="CreateNewToken" asp-action="CreateToken" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new token</a>
<table class="table table-sm table-responsive-md">
<thead>
<tr>

View file

@ -33,12 +33,12 @@
<div class="col-md-4">
<form asp-action="Pair" method="post">
<div class="form-group">
<label asp-for="SelectedStore"></label>
<select asp-for="SelectedStore" asp-items="@(new SelectList(Model.Stores,"Id","Name"))" class="form-control"></select>
<span asp-validation-for="SelectedStore" class="text-danger"></span>
<label asp-for="StoreId"></label>
<select asp-for="StoreId" asp-items="@(new SelectList(Model.Stores,"Id","Name"))" class="form-control"></select>
<span asp-validation-for="StoreId" class="text-danger"></span>
</div>
<input type="hidden" name="pairingCode" value="@Model.Id" />
<button type="submit" class="btn btn-secondary" title="Approve this pairing demand">Approve</button>
<button id="ApprovePairing" type="submit" class="btn btn-secondary" title="Approve this pairing demand">Approve</button>
</form>
</div>
<div class="col-md-4"></div>

View file

@ -3,7 +3,7 @@
<div class="nav flex-column nav-pills">
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Transactions)" asp-action="WalletTransactions" id="WalletTransactions">Transactions</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Send)" asp-action="WalletSend" id="WalletSend">Send</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan">Rescan</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan" id="WalletRescan">Rescan</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.PSBT)" asp-action="WalletPSBT">PSBT</a>
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Settings)" asp-action="WalletSettings" id="WalletSettings">Settings</a>
</div>