Rewrite authorization enforcement and simplify the code

This commit is contained in:
nicolas.dorier 2018-04-30 22:00:43 +09:00
parent 9339c7dff2
commit 21bbf49640
10 changed files with 127 additions and 97 deletions

View File

@ -54,9 +54,9 @@ namespace BTCPayServer.Tests
return CreateStoreAsync().GetAwaiter().GetResult(); return CreateStoreAsync().GetAwaiter().GetResult();
} }
public T GetController<T>() where T : Controller public T GetController<T>(bool setImplicitStore = true) where T : Controller
{ {
return parent.PayTester.GetController<T>(UserId, StoreId); return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
} }
public async Task<StoresController> CreateStoreAsync() public async Task<StoresController> CreateStoreAsync()
@ -83,7 +83,7 @@ namespace BTCPayServer.Tests
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]"); DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
var vm = (StoreViewModel)((ViewResult)store.UpdateStore(StoreId)).Model; var vm = (StoreViewModel)((ViewResult)store.UpdateStore(StoreId)).Model;
vm.SpeedPolicy = SpeedPolicy.MediumSpeed; vm.SpeedPolicy = SpeedPolicy.MediumSpeed;
await store.UpdateStore(StoreId, vm); await store.UpdateStore(vm);
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel() await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
{ {

View File

@ -469,7 +469,7 @@ namespace BTCPayServer.Tests
acc.CreateStore(); acc.CreateStore();
var controller = acc.GetController<StoresController>(); var controller = acc.GetController<StoresController>();
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel() var token = (RedirectToActionResult)controller.CreateToken(new Models.StoreViewModels.CreateTokenViewModel()
{ {
Facade = Facade.Merchant.ToString(), Facade = Facade.Merchant.ToString(),
Label = "bla", Label = "bla",
@ -630,13 +630,13 @@ namespace BTCPayServer.Tests
// Can generate API Key // Can generate API Key
var repo = tester.PayTester.GetService<TokenRepository>(); var repo = tester.PayTester.GetService<TokenRepository>();
Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult()); Assert.Empty(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().GenerateAPIKey(user.StoreId).GetAwaiter().GetResult()); Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().GenerateAPIKey().GetAwaiter().GetResult());
var apiKey = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult()); var apiKey = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
/////// ///////
// Generating a new one remove the previous // Generating a new one remove the previous
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().GenerateAPIKey(user.StoreId).GetAwaiter().GetResult()); Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().GenerateAPIKey().GetAwaiter().GetResult());
var apiKey2 = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult()); var apiKey2 = Assert.Single(repo.GetLegacyAPIKeys(user.StoreId).GetAwaiter().GetResult());
Assert.NotEqual(apiKey, apiKey2); Assert.NotEqual(apiKey, apiKey2);
//////// ////////
@ -688,7 +688,7 @@ namespace BTCPayServer.Tests
var storeController = user.GetController<StoresController>(); var storeController = user.GetController<StoresController>();
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model; var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model;
vm.PreferredExchange = exchange; vm.PreferredExchange = exchange;
storeController.UpdateStore(user.StoreId, vm).Wait(); storeController.UpdateStore(vm).Wait();
var invoice2 = user.BitPay.CreateInvoice(new Invoice() var invoice2 = user.BitPay.CreateInvoice(new Invoice()
{ {
Price = 5000.0, Price = 5000.0,
@ -728,7 +728,7 @@ namespace BTCPayServer.Tests
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model; var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model;
Assert.Equal(1.0, vm.RateMultiplier); Assert.Equal(1.0, vm.RateMultiplier);
vm.RateMultiplier = 0.5; vm.RateMultiplier = 0.5;
storeController.UpdateStore(user.StoreId, vm).Wait(); storeController.UpdateStore(vm).Wait();
var invoice2 = user.BitPay.CreateInvoice(new Invoice() var invoice2 = user.BitPay.CreateInvoice(new Invoice()
@ -963,10 +963,10 @@ namespace BTCPayServer.Tests
user.GrantAccess(); user.GrantAccess();
user.RegisterDerivationScheme("BTC"); user.RegisterDerivationScheme("BTC");
user.RegisterLightningNode("BTC", LightningConnectionType.Charge); user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience(user.StoreId)).Model); var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
vm.LightningMaxValue = "2 USD"; vm.LightningMaxValue = "2 USD";
vm.OnChainMinValue = "5 USD"; vm.OnChainMinValue = "5 USD";
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(user.StoreId, vm).Result); Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm).Result);
var invoice = user.BitPay.CreateInvoice(new Invoice() var invoice = user.BitPay.CreateInvoice(new Invoice()
{ {

View File

@ -16,10 +16,11 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Mails; using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Logging; using BTCPayServer.Logging;
using BTCPayServer.Security;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Route("[controller]/[action]")] [Route("[controller]/[action]")]
public class AccountController : Controller public class AccountController : Controller
{ {

View File

@ -356,7 +356,7 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("invoices")] [Route("invoices")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50) public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50)
{ {
@ -391,7 +391,7 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("invoices/create")] [Route("invoices/create")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice() public async Task<IActionResult> CreateInvoice()
{ {
@ -406,7 +406,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("invoices/create")] [Route("invoices/create")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model) public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
{ {
@ -468,7 +468,7 @@ namespace BTCPayServer.Controllers
} }
[HttpPost] [HttpPost]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
public IActionResult SearchInvoice(InvoicesModel invoices) public IActionResult SearchInvoice(InvoicesModel invoices)
{ {
@ -482,7 +482,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("invoices/invalidatepaid")] [Route("invoices/invalidatepaid")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[BitpayAPIConstraint(false)] [BitpayAPIConstraint(false)]
public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId) public async Task<IActionResult> InvalidatePaidInvoice(string invoiceId)
{ {

View File

@ -21,10 +21,11 @@ using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using BTCPayServer.Services.Mails; using BTCPayServer.Services.Mails;
using System.Globalization; using System.Globalization;
using BTCPayServer.Security;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Route("[controller]/[action]")] [Route("[controller]/[action]")]
public class ManageController : Controller public class ManageController : Controller
{ {

View File

@ -28,7 +28,7 @@ using System.Threading.Tasks;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Route("stores")] [Route("stores")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[Authorize(Policy = Policies.CanModifyStoreSettings.Key)] [Authorize(Policy = Policies.CanModifyStoreSettings.Key)]
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public partial class StoresController : Controller public partial class StoresController : Controller
@ -94,13 +94,10 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("{storeId}/wallet/{cryptoCode}")] [Route("{storeId}/wallet/{cryptoCode}")]
public IActionResult Wallet(string storeId, string cryptoCode) public IActionResult Wallet(string cryptoCode)
{ {
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
WalletModel model = new WalletModel(); WalletModel model = new WalletModel();
model.ServerUrl = GetStoreUrl(storeId); model.ServerUrl = GetStoreUrl(StoreData.Id);
model.CryptoCurrency = cryptoCode; model.CryptoCurrency = cryptoCode;
return View(model); return View(model);
} }
@ -112,17 +109,17 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("{storeId}/users")] [Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers(string storeId) public async Task<IActionResult> StoreUsers()
{ {
StoreUsersViewModel vm = new StoreUsersViewModel(); StoreUsersViewModel vm = new StoreUsersViewModel();
await FillUsers(storeId, vm); await FillUsers(vm);
return View(vm); return View(vm);
} }
private async Task FillUsers(string storeId, StoreUsersViewModel vm) private async Task FillUsers(StoreUsersViewModel vm)
{ {
var users = await _Repo.GetStoreUsers(storeId); var users = await _Repo.GetStoreUsers(StoreData.Id);
vm.StoreId = storeId; vm.StoreId = StoreData.Id;
vm.Users = users.Select(u => new StoreUsersViewModel.StoreUserViewModel() vm.Users = users.Select(u => new StoreUsersViewModel.StoreUserViewModel()
{ {
Email = u.Email, Email = u.Email,
@ -131,11 +128,20 @@ namespace BTCPayServer.Controllers
}).ToList(); }).ToList();
} }
public StoreData StoreData
{
get
{
return this.HttpContext.GetStoreData();
}
}
[HttpPost] [HttpPost]
[Route("{storeId}/users")] [Route("{storeId}/users")]
public async Task<IActionResult> StoreUsers(string storeId, StoreUsersViewModel vm) public async Task<IActionResult> StoreUsers(StoreUsersViewModel vm)
{ {
await FillUsers(storeId, vm); await FillUsers(vm);
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return View(vm); return View(vm);
@ -151,7 +157,7 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.Role), "Invalid role"); ModelState.AddModelError(nameof(vm.Role), "Invalid role");
return View(vm); return View(vm);
} }
if (!await _Repo.AddStoreUser(storeId, user.Id, vm.Role)) if (!await _Repo.AddStoreUser(StoreData.Id, user.Id, vm.Role))
{ {
ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store"); ModelState.AddModelError(nameof(vm.Email), "The user already has access to this store");
return View(vm); return View(vm);
@ -162,12 +168,9 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("{storeId}/users/{userId}/delete")] [Route("{storeId}/users/{userId}/delete")]
public async Task<IActionResult> DeleteStoreUser(string storeId, string userId) public async Task<IActionResult> DeleteStoreUser(string userId)
{ {
StoreUsersViewModel vm = new StoreUsersViewModel(); StoreUsersViewModel vm = new StoreUsersViewModel();
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var user = await _UserManager.FindByIdAsync(userId); var user = await _UserManager.FindByIdAsync(userId);
if (user == null) if (user == null)
return NotFound(); return NotFound();
@ -190,14 +193,11 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("{storeId}/checkout")] [Route("{storeId}/checkout")]
public IActionResult CheckoutExperience(string storeId) public IActionResult CheckoutExperience()
{ {
var store = HttpContext.GetStoreData(); var storeBlob = StoreData.GetStoreBlob();
if (store == null)
return NotFound();
var storeBlob = store.GetStoreBlob();
var vm = new CheckoutExperienceViewModel(); var vm = new CheckoutExperienceViewModel();
vm.SetCryptoCurrencies(_ExplorerProvider, store.GetDefaultCrypto()); vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto());
vm.SetLanguages(_LangService, storeBlob.DefaultLang); vm.SetLanguages(_LangService, storeBlob.DefaultLang);
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? ""; vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? ""; vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
@ -210,7 +210,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("{storeId}/checkout")] [Route("{storeId}/checkout")]
public async Task<IActionResult> CheckoutExperience(string storeId, CheckoutExperienceViewModel model) public async Task<IActionResult> CheckoutExperience(CheckoutExperienceViewModel model)
{ {
CurrencyValue lightningMaxValue = null; CurrencyValue lightningMaxValue = null;
if (!string.IsNullOrWhiteSpace(model.LightningMaxValue)) if (!string.IsNullOrWhiteSpace(model.LightningMaxValue))
@ -229,16 +229,12 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(model.OnChainMinValue), "Invalid on chain min value"); ModelState.AddModelError(nameof(model.OnChainMinValue), "Invalid on chain min value");
} }
} }
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
bool needUpdate = false; bool needUpdate = false;
var blob = store.GetStoreBlob(); var blob = StoreData.GetStoreBlob();
if (store.GetDefaultCrypto() != model.DefaultCryptoCurrency) if (StoreData.GetDefaultCrypto() != model.DefaultCryptoCurrency)
{ {
needUpdate = true; needUpdate = true;
store.SetDefaultCrypto(model.DefaultCryptoCurrency); StoreData.SetDefaultCrypto(model.DefaultCryptoCurrency);
} }
model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency); model.SetCryptoCurrencies(_ExplorerProvider, model.DefaultCryptoCurrency);
model.SetLanguages(_LangService, model.DefaultLang); model.SetLanguages(_LangService, model.DefaultLang);
@ -254,19 +250,19 @@ namespace BTCPayServer.Controllers
blob.OnChainMinValue = onchainMinValue; blob.OnChainMinValue = onchainMinValue;
blob.CustomLogo = string.IsNullOrWhiteSpace(model.CustomLogo) ? null : new Uri(model.CustomLogo, UriKind.Absolute); blob.CustomLogo = string.IsNullOrWhiteSpace(model.CustomLogo) ? null : new Uri(model.CustomLogo, UriKind.Absolute);
blob.CustomCSS = string.IsNullOrWhiteSpace(model.CustomCSS) ? null : new Uri(model.CustomCSS, UriKind.Absolute); blob.CustomCSS = string.IsNullOrWhiteSpace(model.CustomCSS) ? null : new Uri(model.CustomCSS, UriKind.Absolute);
if (store.SetStoreBlob(blob)) if (StoreData.SetStoreBlob(blob))
{ {
needUpdate = true; needUpdate = true;
} }
if (needUpdate) if (needUpdate)
{ {
await _Repo.UpdateStore(store); await _Repo.UpdateStore(StoreData);
StatusMessage = "Store successfully updated"; StatusMessage = "Store successfully updated";
} }
return RedirectToAction(nameof(CheckoutExperience), new return RedirectToAction(nameof(CheckoutExperience), new
{ {
storeId = storeId storeId = StoreData.Id
}); });
} }
@ -330,7 +326,7 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("{storeId}")] [Route("{storeId}")]
public async Task<IActionResult> UpdateStore(string storeId, StoreViewModel model) public async Task<IActionResult> UpdateStore(StoreViewModel model)
{ {
model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange); model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange);
if (!ModelState.IsValid) if (!ModelState.IsValid)
@ -339,29 +335,26 @@ namespace BTCPayServer.Controllers
} }
if (model.PreferredExchange != null) if (model.PreferredExchange != null)
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant(); model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
var store = HttpContext.GetStoreData(); AddPaymentMethods(StoreData, model);
if (store == null)
return NotFound();
AddPaymentMethods(store, model);
bool needUpdate = false; bool needUpdate = false;
if (store.SpeedPolicy != model.SpeedPolicy) if (StoreData.SpeedPolicy != model.SpeedPolicy)
{ {
needUpdate = true; needUpdate = true;
store.SpeedPolicy = model.SpeedPolicy; StoreData.SpeedPolicy = model.SpeedPolicy;
} }
if (store.StoreName != model.StoreName) if (StoreData.StoreName != model.StoreName)
{ {
needUpdate = true; needUpdate = true;
store.StoreName = model.StoreName; StoreData.StoreName = model.StoreName;
} }
if (store.StoreWebsite != model.StoreWebsite) if (StoreData.StoreWebsite != model.StoreWebsite)
{ {
needUpdate = true; needUpdate = true;
store.StoreWebsite = model.StoreWebsite; StoreData.StoreWebsite = model.StoreWebsite;
} }
var blob = store.GetStoreBlob(); var blob = StoreData.GetStoreBlob();
blob.NetworkFeeDisabled = !model.NetworkFee; blob.NetworkFeeDisabled = !model.NetworkFee;
blob.MonitoringExpiration = model.MonitoringExpiration; blob.MonitoringExpiration = model.MonitoringExpiration;
blob.InvoiceExpiration = model.InvoiceExpiration; blob.InvoiceExpiration = model.InvoiceExpiration;
@ -372,7 +365,7 @@ namespace BTCPayServer.Controllers
blob.SetRateMultiplier(model.RateMultiplier); blob.SetRateMultiplier(model.RateMultiplier);
if (store.SetStoreBlob(blob)) if (StoreData.SetStoreBlob(blob))
{ {
needUpdate = true; needUpdate = true;
} }
@ -389,13 +382,13 @@ namespace BTCPayServer.Controllers
if (needUpdate) if (needUpdate)
{ {
await _Repo.UpdateStore(store); await _Repo.UpdateStore(StoreData);
StatusMessage = "Store successfully updated"; StatusMessage = "Store successfully updated";
} }
return RedirectToAction(nameof(UpdateStore), new return RedirectToAction(nameof(UpdateStore), new
{ {
storeId = storeId storeId = StoreData.Id
}); });
} }
@ -416,10 +409,10 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("{storeId}/Tokens")] [Route("{storeId}/Tokens")]
public async Task<IActionResult> ListTokens(string storeId) public async Task<IActionResult> ListTokens()
{ {
var model = new TokensViewModel(); var model = new TokensViewModel();
var tokens = await _TokenRepository.GetTokensByStoreIdAsync(storeId); var tokens = await _TokenRepository.GetTokensByStoreIdAsync(StoreData.Id);
model.StatusMessage = StatusMessage; model.StatusMessage = StatusMessage;
model.Tokens = tokens.Select(t => new TokenViewModel() model.Tokens = tokens.Select(t => new TokenViewModel()
{ {
@ -429,7 +422,7 @@ namespace BTCPayServer.Controllers
Id = t.Value Id = t.Value
}).ToArray(); }).ToArray();
model.ApiKey = (await _TokenRepository.GetLegacyAPIKeys(storeId)).FirstOrDefault(); model.ApiKey = (await _TokenRepository.GetLegacyAPIKeys(StoreData.Id)).FirstOrDefault();
if (model.ApiKey == null) if (model.ApiKey == null)
model.EncodedApiKey = "*API Key*"; model.EncodedApiKey = "*API Key*";
else else
@ -440,24 +433,31 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("/api-tokens")] [Route("/api-tokens")]
[Route("{storeId}/Tokens/Create")] [Route("{storeId}/Tokens/Create")]
public async Task<IActionResult> CreateToken(string storeId, CreateTokenViewModel model) [AllowAnonymous]
public async Task<IActionResult> CreateToken(CreateTokenViewModel model)
{ {
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
return View(model); return View(model);
} }
model.Label = model.Label ?? String.Empty; model.Label = model.Label ?? String.Empty;
storeId = model.StoreId ?? storeId;
var userId = GetUserId(); var userId = GetUserId();
if (userId == null) if (userId == null)
return Unauthorized(); return Challenge(Policies.CookieAuthentication);
var store = HttpContext.GetStoreData();
if (store == null) var store = StoreData;
return Unauthorized(); var storeId = StoreData?.Id;
if (storeId == null)
{
storeId = model.StoreId;
store = await _Repo.FindStore(storeId, userId);
if (store == null)
return Challenge(Policies.CookieAuthentication);
}
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
{ {
StatusMessage = "Error: You need to be owner of this store to request pairing codes"; return Challenge(Policies.CookieAuthentication);
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
} }
var tokenRequest = new TokenRequest() var tokenRequest = new TokenRequest()
@ -498,11 +498,20 @@ namespace BTCPayServer.Controllers
[HttpGet] [HttpGet]
[Route("/api-tokens")] [Route("/api-tokens")]
[Route("{storeId}/Tokens/Create")] [Route("{storeId}/Tokens/Create")]
public async Task<IActionResult> CreateToken(string storeId) [AllowAnonymous]
public async Task<IActionResult> CreateToken()
{ {
var userId = GetUserId(); var userId = GetUserId();
if (string.IsNullOrWhiteSpace(userId)) if (string.IsNullOrWhiteSpace(userId))
return Unauthorized(); return Challenge(Policies.CookieAuthentication);
var storeId = StoreData?.Id;
if (StoreData != null)
{
if (!StoreData.HasClaim(Policies.CanModifyStoreSettings.Key))
{
return Challenge(Policies.CookieAuthentication);
}
}
var model = new CreateTokenViewModel(); var model = new CreateTokenViewModel();
model.Facade = "merchant"; model.Facade = "merchant";
ViewBag.HidePublicKey = storeId == null; ViewBag.HidePublicKey = storeId == null;
@ -511,20 +520,25 @@ namespace BTCPayServer.Controllers
model.StoreId = storeId; model.StoreId = storeId;
if (storeId == null) if (storeId == null)
{ {
model.Stores = new SelectList(await _Repo.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId); 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);
}
if (model.Stores.Count() == 0)
{
StatusMessage = "Error: You need to be owner of at least one store before pairing";
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
} }
return View(model); return View(model);
} }
[HttpPost] [HttpPost]
[Route("{storeId}/Tokens/Delete")] [Route("{storeId}/Tokens/Delete")]
public async Task<IActionResult> DeleteToken(string storeId, string tokenId) public async Task<IActionResult> DeleteToken(string tokenId)
{ {
var token = await _TokenRepository.GetToken(tokenId); var token = await _TokenRepository.GetToken(tokenId);
if (token == null || if (token == null ||
token.StoreId != storeId || token.StoreId != StoreData.Id ||
!await _TokenRepository.DeleteToken(tokenId)) !await _TokenRepository.DeleteToken(tokenId))
StatusMessage = "Failure to revoke this token"; StatusMessage = "Failure to revoke this token";
else else
@ -534,20 +548,24 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("{storeId}/tokens/apikey")] [Route("{storeId}/tokens/apikey")]
public async Task<IActionResult> GenerateAPIKey(string storeId) public async Task<IActionResult> GenerateAPIKey()
{ {
var store = HttpContext.GetStoreData(); var store = HttpContext.GetStoreData();
if (store == null) if (store == null)
return NotFound(); return NotFound();
await _TokenRepository.GenerateLegacyAPIKey(storeId); await _TokenRepository.GenerateLegacyAPIKey(StoreData.Id);
StatusMessage = "API Key re-generated"; StatusMessage = "API Key re-generated";
return RedirectToAction(nameof(ListTokens)); return RedirectToAction(nameof(ListTokens));
} }
[HttpGet] [HttpGet]
[Route("/api-access-request")] [Route("/api-access-request")]
[AllowAnonymous]
public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null) public async Task<IActionResult> RequestPairing(string pairingCode, string selectedStore = null)
{ {
var userId = GetUserId();
if (userId == null)
return Challenge(Policies.CookieAuthentication);
if (pairingCode == null) if (pairingCode == null)
return NotFound(); return NotFound();
var pairing = await _TokenRepository.GetPairingAsync(pairingCode); var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
@ -558,7 +576,7 @@ namespace BTCPayServer.Controllers
} }
else else
{ {
var stores = await _Repo.GetStoresByUserId(GetUserId()); var stores = await _Repo.GetStoresByUserId(userId);
return View(new PairingModel() return View(new PairingModel()
{ {
Id = pairing.Id, Id = pairing.Id,
@ -566,7 +584,7 @@ namespace BTCPayServer.Controllers
Label = pairing.Label, Label = pairing.Label,
SIN = pairing.SIN ?? "Server-Initiated Pairing", SIN = pairing.SIN ?? "Server-Initiated Pairing",
SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id, SelectedStore = selectedStore ?? stores.FirstOrDefault()?.Id,
Stores = stores.Select(s => new PairingModel.StoreViewModel() Stores = stores.Where(u => u.HasClaim(Policies.CanModifyStoreSettings.Key)).Select(s => new PairingModel.StoreViewModel()
{ {
Id = s.Id, Id = s.Id,
Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName Name = string.IsNullOrEmpty(s.StoreName) ? s.Id : s.StoreName
@ -577,19 +595,22 @@ namespace BTCPayServer.Controllers
[HttpPost] [HttpPost]
[Route("/api-access-request")] [Route("/api-access-request")]
[AllowAnonymous]
public async Task<IActionResult> Pair(string pairingCode, string selectedStore) public async Task<IActionResult> Pair(string pairingCode, string selectedStore)
{ {
if (pairingCode == null) if (pairingCode == null)
return NotFound(); return NotFound();
var store = await _Repo.FindStore(selectedStore, GetUserId()); var userId = GetUserId();
if (userId == null)
return Challenge(Policies.CookieAuthentication);
var store = await _Repo.FindStore(selectedStore, userId);
var pairing = await _TokenRepository.GetPairingAsync(pairingCode); var pairing = await _TokenRepository.GetPairingAsync(pairingCode);
if (store == null || pairing == null) if (store == null || pairing == null)
return NotFound(); return NotFound();
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key)) if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
{ {
StatusMessage = "Error: You can't approve a pairing without being owner of the store"; return Challenge(Policies.CookieAuthentication);
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
} }
var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id); var pairingResult = await _TokenRepository.PairWithStoreAsync(pairingCode, store.Id);
@ -615,6 +636,8 @@ namespace BTCPayServer.Controllers
private string GetUserId() private string GetUserId()
{ {
if (User.Identity.AuthenticationType != Policies.CookieAuthentication)
return null;
return _UserManager.GetUserId(User); return _UserManager.GetUserId(User);
} }
} }

View File

@ -16,7 +16,7 @@ using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
[Route("stores")] [Route("stores")]
[Authorize(AuthenticationSchemes = "Identity.Application")] [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[AutoValidateAntiforgeryToken] [AutoValidateAntiforgeryToken]
public partial class UserStoresController : Controller public partial class UserStoresController : Controller
{ {

View File

@ -261,7 +261,6 @@ namespace BTCPayServer.HostedServices
private async Task WaitPendingInvoices() private async Task WaitPendingInvoices()
{ {
await new SynchronizationContextRemover();
await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices()) await Task.WhenAll((await _InvoiceRepository.GetPendingInvoices())
.Select(id => Wait(id)).ToArray()); .Select(id => Wait(id)).ToArray());
_WaitingInvoices = null; _WaitingInvoices = null;
@ -269,8 +268,8 @@ namespace BTCPayServer.HostedServices
async Task StartLoop(CancellationToken cancellation) async Task StartLoop(CancellationToken cancellation)
{ {
await new SynchronizationContextRemover();
Logs.PayServer.LogInformation("Start watching invoices"); Logs.PayServer.LogInformation("Start watching invoices");
await Task.Delay(1).ConfigureAwait(false); // Small hack so that the caller does not block on GetConsumingEnumerable
try try
{ {
foreach (var invoiceId in _WatchRequests.GetConsumingEnumerable(cancellation)) foreach (var invoiceId in _WatchRequests.GetConsumingEnumerable(cancellation))

View File

@ -48,10 +48,15 @@ namespace BTCPayServer.Security
if (claim != null) if (claim != null)
{ {
var store = await _StoreRepository.FindStore((string)storeId, claim.Value); var store = await _StoreRepository.FindStore((string)storeId, claim.Value);
context.HttpContext.SetStoreData(store); if (store == null)
if (store != null) context.Result = new ChallengeResult(Policies.CookieAuthentication);
else
{ {
identity.AddClaims(store.GetClaims()); context.HttpContext.SetStoreData(store);
if (store != null)
{
identity.AddClaims(store.GetClaims());
}
} }
} }
} }

View File

@ -8,6 +8,7 @@ namespace BTCPayServer.Security
{ {
public static class Policies public static class Policies
{ {
public const string CookieAuthentication = "Identity.Application";
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options) public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
{ {
AddClaim(options, CanUseStore.Key); AddClaim(options, CanUseStore.Key);