mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-21 22:11:48 +01:00
Move to a Claim based security
This commit is contained in:
parent
3954ce2137
commit
1fc9a1a54b
18 changed files with 235 additions and 118 deletions
|
@ -4,6 +4,7 @@ using BTCPayServer.Payments;
|
|||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Tests.Mocks;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
|
@ -142,7 +143,7 @@ namespace BTCPayServer.Tests
|
|||
return _Host.Services.GetRequiredService<T>();
|
||||
}
|
||||
|
||||
public T GetController<T>(string userId = null) where T : Controller
|
||||
public T GetController<T>(string userId = null, string storeId = null) where T : Controller
|
||||
{
|
||||
var context = new DefaultHttpContext();
|
||||
context.Request.Host = new HostString("127.0.0.1");
|
||||
|
@ -152,6 +153,10 @@ namespace BTCPayServer.Tests
|
|||
{
|
||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }));
|
||||
}
|
||||
if(storeId != null)
|
||||
{
|
||||
context.SetStoreData(GetService<StoreRepository>().FindStore(storeId, userId).GetAwaiter().GetResult());
|
||||
}
|
||||
var scope = (IServiceScopeFactory)_Host.Services.GetService(typeof(IServiceScopeFactory));
|
||||
var provider = scope.CreateScope().ServiceProvider;
|
||||
context.RequestServices = provider;
|
||||
|
|
|
@ -56,7 +56,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public T GetController<T>() where T : Controller
|
||||
{
|
||||
return parent.PayTester.GetController<T>(UserId);
|
||||
return parent.PayTester.GetController<T>(UserId, StoreId);
|
||||
}
|
||||
|
||||
public async Task<StoresController> CreateStoreAsync()
|
||||
|
@ -78,10 +78,10 @@ namespace BTCPayServer.Tests
|
|||
public async Task RegisterDerivationSchemeAsync(string cryptoCode)
|
||||
{
|
||||
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
|
||||
var store = parent.PayTester.GetController<StoresController>(UserId);
|
||||
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
|
||||
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
|
||||
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + "-[legacy]");
|
||||
var vm = (StoreViewModel)((ViewResult)await store.UpdateStore(StoreId)).Model;
|
||||
var vm = (StoreViewModel)((ViewResult)store.UpdateStore(StoreId)).Model;
|
||||
vm.SpeedPolicy = SpeedPolicy.MediumSpeed;
|
||||
await store.UpdateStore(StoreId, vm);
|
||||
|
||||
|
@ -127,7 +127,7 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType)
|
||||
{
|
||||
var storeController = parent.PayTester.GetController<StoresController>(UserId);
|
||||
var storeController = this.GetController<StoresController>();
|
||||
await storeController.AddLightningNode(StoreId, new LightningNodeViewModel()
|
||||
{
|
||||
Url = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri :
|
||||
|
|
|
@ -306,9 +306,9 @@ namespace BTCPayServer.Tests
|
|||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
|
||||
Assert.IsType<ViewResult>(storeController.UpdateStore(user.StoreId).GetAwaiter().GetResult());
|
||||
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId, "BTC").GetAwaiter().GetResult());
|
||||
var storeController = user.GetController<StoresController>();
|
||||
Assert.IsType<ViewResult>(storeController.UpdateStore(user.StoreId));
|
||||
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId, "BTC"));
|
||||
|
||||
var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel()
|
||||
{
|
||||
|
@ -322,7 +322,7 @@ namespace BTCPayServer.Tests
|
|||
Url = tester.MerchantCharge.Client.Uri.AbsoluteUri
|
||||
}, "save", "BTC").GetAwaiter().GetResult());
|
||||
|
||||
var storeVm = Assert.IsType<Models.StoreViewModels.StoreViewModel>(Assert.IsType<ViewResult>(storeController.UpdateStore(user.StoreId).GetAwaiter().GetResult()).Model);
|
||||
var storeVm = Assert.IsType<Models.StoreViewModels.StoreViewModel>(Assert.IsType<ViewResult>(storeController.UpdateStore(user.StoreId)).Model);
|
||||
Assert.Single(storeVm.LightningNodes.Where(l => !string.IsNullOrEmpty(l.Address)));
|
||||
}
|
||||
}
|
||||
|
@ -468,7 +468,7 @@ namespace BTCPayServer.Tests
|
|||
acc.Register();
|
||||
acc.CreateStore();
|
||||
|
||||
var controller = tester.PayTester.GetController<StoresController>(acc.UserId);
|
||||
var controller = acc.GetController<StoresController>();
|
||||
var token = (RedirectToActionResult)controller.CreateToken(acc.StoreId, new Models.StoreViewModels.CreateTokenViewModel()
|
||||
{
|
||||
Facade = Facade.Merchant.ToString(),
|
||||
|
@ -685,8 +685,8 @@ namespace BTCPayServer.Tests
|
|||
|
||||
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange)
|
||||
{
|
||||
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
|
||||
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model;
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model;
|
||||
vm.PreferredExchange = exchange;
|
||||
storeController.UpdateStore(user.StoreId, vm).Wait();
|
||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||
|
@ -724,8 +724,8 @@ namespace BTCPayServer.Tests
|
|||
}, Facade.Merchant);
|
||||
|
||||
|
||||
var storeController = tester.PayTester.GetController<StoresController>(user.UserId);
|
||||
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId).Result).Model;
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (StoreViewModel)((ViewResult)storeController.UpdateStore(user.StoreId)).Model;
|
||||
Assert.Equal(1.0, vm.RateMultiplier);
|
||||
vm.RateMultiplier = 0.5;
|
||||
storeController.UpdateStore(user.StoreId, vm).Wait();
|
||||
|
@ -963,7 +963,7 @@ namespace BTCPayServer.Tests
|
|||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience(user.StoreId).Result).Model);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience(user.StoreId)).Model);
|
||||
vm.LightningMaxValue = "2 USD";
|
||||
vm.OnChainMinValue = "5 USD";
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(user.StoreId, vm).Result);
|
||||
|
|
|
@ -19,7 +19,7 @@ using BTCPayServer.Logging;
|
|||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Route("[controller]/[action]")]
|
||||
public class AccountController : Controller
|
||||
{
|
||||
|
|
|
@ -140,6 +140,8 @@ namespace BTCPayServer.Controllers
|
|||
.Where(us => us.ApplicationUserId == userId && us.Role == StoreRoles.Owner)
|
||||
.SelectMany(us => us.StoreData.Apps.Where(a => a.Id == appId))
|
||||
.FirstOrDefaultAsync();
|
||||
if (app == null)
|
||||
return null;
|
||||
if (type != null && type.Value.ToString() != app.AppType)
|
||||
return null;
|
||||
return app;
|
||||
|
|
|
@ -22,6 +22,7 @@ using BTCPayServer.Events;
|
|||
using NBXplorer;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -394,7 +395,7 @@ namespace BTCPayServer.Controllers
|
|||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice()
|
||||
{
|
||||
var stores = await GetStores(GetUserId());
|
||||
var stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), null);
|
||||
if (stores.Count() == 0)
|
||||
{
|
||||
StatusMessage = "Error: You need to create at least one store before creating a transaction";
|
||||
|
@ -409,14 +410,19 @@ namespace BTCPayServer.Controllers
|
|||
[BitpayAPIConstraint(false)]
|
||||
public async Task<IActionResult> CreateInvoice(CreateInvoiceModel model)
|
||||
{
|
||||
model.Stores = await GetStores(GetUserId(), model.StoreId);
|
||||
var stores = await _StoreRepository.GetStoresByUserId(GetUserId());
|
||||
model.Stores = new SelectList(stores, nameof(StoreData.Id), nameof(StoreData.StoreName), model.StoreId);
|
||||
var store = stores.FirstOrDefault(s => s.Id == model.StoreId);
|
||||
if(store == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "Store not found");
|
||||
}
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(model);
|
||||
}
|
||||
var store = await _StoreRepository.FindStore(model.StoreId, GetUserId());
|
||||
StatusMessage = null;
|
||||
if (store.Role != StoreRoles.Owner)
|
||||
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "You need to be owner of this store to create an invoice");
|
||||
return View(model);
|
||||
|
@ -461,11 +467,6 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<SelectList> GetStores(string userId, string storeId = null)
|
||||
{
|
||||
return new SelectList(await _StoreRepository.GetStoresByUserId(userId), nameof(StoreData.Id), nameof(StoreData.StoreName), storeId);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[BitpayAPIConstraint(false)]
|
||||
|
|
|
@ -24,7 +24,7 @@ using System.Globalization;
|
|||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Route("[controller]/[action]")]
|
||||
public class ManageController : Controller
|
||||
{
|
||||
|
|
|
@ -19,7 +19,7 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Authorize(Roles = Roles.ServerAdmin)]
|
||||
[Authorize(Policy = BTCPayServer.Security.Policies.CanModifyServerSettings.Key)]
|
||||
public class ServerController : Controller
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
|
|
|
@ -21,9 +21,9 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
[HttpGet]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, string cryptoCode)
|
||||
public IActionResult AddDerivationScheme(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
|
@ -60,7 +60,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
vm.ServerUrl = GetStoreUrl(storeId);
|
||||
vm.CryptoCode = cryptoCode;
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
|
@ -188,7 +188,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/lightning/{cryptoCode}")]
|
||||
public async Task<IActionResult> AddLightningNode(string storeId, string cryptoCode)
|
||||
public IActionResult AddLightningNode(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
LightningNodeViewModel vm = new LightningNodeViewModel();
|
||||
|
@ -59,7 +59,7 @@ namespace BTCPayServer.Controllers
|
|||
public async Task<IActionResult> AddLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
|
||||
{
|
||||
vm.CryptoCode = cryptoCode;
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||
|
|
|
@ -4,6 +4,7 @@ using BTCPayServer.Data;
|
|||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
|
@ -28,7 +29,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
[Route("stores")]
|
||||
[Authorize(AuthenticationSchemes = "Identity.Application")]
|
||||
[Authorize(Policy = StorePolicies.OwnStore)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings.Key)]
|
||||
[AutoValidateAntiforgeryToken]
|
||||
public partial class StoresController : Controller
|
||||
{
|
||||
|
@ -93,9 +94,9 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/wallet/{cryptoCode}")]
|
||||
public async Task<IActionResult> Wallet(string storeId, string cryptoCode)
|
||||
public IActionResult Wallet(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
WalletModel model = new WalletModel();
|
||||
|
@ -164,7 +165,7 @@ namespace BTCPayServer.Controllers
|
|||
public async Task<IActionResult> DeleteStoreUser(string storeId, string userId)
|
||||
{
|
||||
StoreUsersViewModel vm = new StoreUsersViewModel();
|
||||
var store = await _Repo.FindStore(storeId, userId);
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var user = await _UserManager.FindByIdAsync(userId);
|
||||
|
@ -173,7 +174,7 @@ namespace BTCPayServer.Controllers
|
|||
return View("Confirm", new ConfirmModel()
|
||||
{
|
||||
Title = $"Remove store user",
|
||||
Description = $"Are you sure to remove access to remove {store.Role} access to {user.Email}?",
|
||||
Description = $"Are you sure to remove access to remove access to {user.Email}?",
|
||||
Action = "Delete"
|
||||
});
|
||||
}
|
||||
|
@ -189,9 +190,9 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/checkout")]
|
||||
public async Task<IActionResult> CheckoutExperience(string storeId)
|
||||
public IActionResult CheckoutExperience(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
|
@ -229,7 +230,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
bool needUpdate = false;
|
||||
|
@ -271,9 +272,9 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
[HttpGet]
|
||||
[Route("{storeId}")]
|
||||
public async Task<IActionResult> UpdateStore(string storeId)
|
||||
public IActionResult UpdateStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
|
@ -338,7 +339,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
if (model.PreferredExchange != null)
|
||||
model.PreferredExchange = model.PreferredExchange.Trim().ToLowerInvariant();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
AddPaymentMethods(store, model);
|
||||
|
@ -450,10 +451,10 @@ namespace BTCPayServer.Controllers
|
|||
var userId = GetUserId();
|
||||
if (userId == null)
|
||||
return Unauthorized();
|
||||
var store = await _Repo.FindStore(storeId, userId);
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return Unauthorized();
|
||||
if (store.Role != StoreRoles.Owner)
|
||||
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
|
||||
{
|
||||
StatusMessage = "Error: You need to be owner of this store to request pairing codes";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
|
@ -535,7 +536,7 @@ namespace BTCPayServer.Controllers
|
|||
[Route("{storeId}/tokens/apikey")]
|
||||
public async Task<IActionResult> GenerateAPIKey(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
await _TokenRepository.GenerateLegacyAPIKey(storeId);
|
||||
|
@ -585,7 +586,7 @@ namespace BTCPayServer.Controllers
|
|||
if (store == null || pairing == null)
|
||||
return NotFound();
|
||||
|
||||
if (store.Role != StoreRoles.Owner)
|
||||
if (!store.HasClaim(Policies.CanModifyStoreSettings.Key))
|
||||
{
|
||||
StatusMessage = "Error: You can't approve a pairing without being owner of the store";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
|
|
|
@ -5,6 +5,7 @@ using System.Threading;
|
|||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
@ -37,9 +38,9 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}/delete")]
|
||||
public async Task<IActionResult> DeleteStore(string storeId)
|
||||
public IActionResult DeleteStore(string storeId)
|
||||
{
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
|
@ -67,7 +68,7 @@ namespace BTCPayServer.Controllers
|
|||
public async Task<IActionResult> DeleteStorePost(string storeId)
|
||||
{
|
||||
var userId = GetUserId();
|
||||
var store = await _Repo.FindStore(storeId, GetUserId());
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
await _Repo.RemoveStore(storeId, userId);
|
||||
|
@ -102,8 +103,8 @@ namespace BTCPayServer.Controllers
|
|||
Id = store.Id,
|
||||
Name = store.StoreName,
|
||||
WebSite = store.StoreWebsite,
|
||||
IsOwner = store.Role == StoreRoles.Owner,
|
||||
Balances = store.Role == StoreRoles.Owner ? balances[i].Select(t => t.Result).ToArray() : Array.Empty<string>()
|
||||
IsOwner = store.HasClaim(Policies.CanModifyStoreSettings.Key),
|
||||
Balances = store.HasClaim(Policies.CanModifyStoreSettings.Key) ? balances[i].Select(t => t.Result).ToArray() : Array.Empty<string>()
|
||||
});
|
||||
}
|
||||
return View(result);
|
||||
|
|
|
@ -15,6 +15,9 @@ using BTCPayServer.Services.Rates;
|
|||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using BTCPayServer.Services;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
|
@ -152,10 +155,35 @@ namespace BTCPayServer.Data
|
|||
}
|
||||
|
||||
[NotMapped]
|
||||
[Obsolete]
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public Claim[] GetClaims()
|
||||
{
|
||||
List<Claim> claims = new List<Claim>();
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
var role = Role;
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
if (role == StoreRoles.Owner)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanModifyStoreSettings.Key, Id));
|
||||
claims.Add(new Claim(Policies.CanUseStore.Key, Id));
|
||||
}
|
||||
if (role == StoreRoles.Guest)
|
||||
{
|
||||
claims.Add(new Claim(Policies.CanUseStore.Key, Id));
|
||||
}
|
||||
return claims.ToArray();
|
||||
}
|
||||
|
||||
public bool HasClaim(string claim)
|
||||
{
|
||||
return GetClaims().Any(c => c.Type == claim);
|
||||
}
|
||||
|
||||
public byte[] StoreBlob
|
||||
{
|
||||
get;
|
||||
|
|
|
@ -38,55 +38,13 @@ using Microsoft.Extensions.Caching.Memory;
|
|||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using Meziantou.AspNetCore.BundleTagHelpers;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Security;
|
||||
|
||||
namespace BTCPayServer.Hosting
|
||||
{
|
||||
public static class BTCPayServerServices
|
||||
{
|
||||
public class OwnStoreAuthorizationRequirement : IAuthorizationRequirement
|
||||
{
|
||||
public OwnStoreAuthorizationRequirement()
|
||||
{
|
||||
}
|
||||
|
||||
public OwnStoreAuthorizationRequirement(string role)
|
||||
{
|
||||
Role = role;
|
||||
}
|
||||
|
||||
public string Role
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
|
||||
public class OwnStoreHandler : AuthorizationHandler<OwnStoreAuthorizationRequirement>
|
||||
{
|
||||
StoreRepository _StoreRepository;
|
||||
UserManager<ApplicationUser> _UserManager;
|
||||
public OwnStoreHandler(StoreRepository storeRepository, UserManager<ApplicationUser> userManager)
|
||||
{
|
||||
_StoreRepository = storeRepository;
|
||||
_UserManager = userManager;
|
||||
}
|
||||
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OwnStoreAuthorizationRequirement requirement)
|
||||
{
|
||||
object storeId = null;
|
||||
if (!((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).RouteData.Values.TryGetValue("storeId", out storeId))
|
||||
context.Succeed(requirement);
|
||||
else if (storeId != null)
|
||||
{
|
||||
var user = _UserManager.GetUserId(((Microsoft.AspNetCore.Mvc.ActionContext)context.Resource).HttpContext.User);
|
||||
if (user != null)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, user);
|
||||
if (store != null)
|
||||
if (requirement.Role == null || requirement.Role == store.Role)
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public static IServiceCollection AddBTCPayServer(this IServiceCollection services)
|
||||
{
|
||||
services.AddDbContext<ApplicationDbContext>((provider, o) =>
|
||||
|
@ -160,6 +118,7 @@ namespace BTCPayServer.Hosting
|
|||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||
services.AddSingleton<IHostedService, RatesHostedService>();
|
||||
services.AddTransient<IConfigureOptions<MvcOptions>, BTCPayClaimsFilter>();
|
||||
|
||||
services.TryAddSingleton<ExplorerClientProvider>();
|
||||
services.TryAddSingleton<Bitpay>(o =>
|
||||
|
@ -172,27 +131,14 @@ namespace BTCPayServer.Hosting
|
|||
services.TryAddSingleton<IRateProviderFactory, BTCPayRateProviderFactory>();
|
||||
|
||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||
services.AddTransient<AccessTokenController>();
|
||||
services.AddTransient<InvoiceController>();
|
||||
// Add application services.
|
||||
services.AddTransient<IEmailSender, EmailSender>();
|
||||
|
||||
services.AddAuthorization(o =>
|
||||
{
|
||||
o.AddPolicy(StorePolicies.CanAccessStores, builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement());
|
||||
});
|
||||
|
||||
o.AddPolicy(StorePolicies.OwnStore, builder =>
|
||||
{
|
||||
builder.AddRequirements(new OwnStoreAuthorizationRequirement(StoreRoles.Owner));
|
||||
});
|
||||
});
|
||||
|
||||
// bundling
|
||||
|
||||
services.AddAuthorization(o => Policies.AddBTCPayPolicies(o));
|
||||
|
||||
services.AddBundles();
|
||||
services.AddTransient<BundleOptions>(provider =>
|
||||
{
|
||||
|
|
97
BTCPayServer/Security/BTCPayClaimsPrincipalFactory.cs
Normal file
97
BTCPayServer/Security/BTCPayClaimsPrincipalFactory.cs
Normal file
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
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)
|
||||
{
|
||||
var principal = context.HttpContext.User;
|
||||
if (!context.HttpContext.GetIsBitpayAPI())
|
||||
{
|
||||
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 claim = identity.Claims.FirstOrDefault(c => c.Type == ClaimTypes.NameIdentifier);
|
||||
if (claim != null)
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, claim.Value);
|
||||
context.HttpContext.SetStoreData(store);
|
||||
if (store != null)
|
||||
{
|
||||
identity.AddClaims(store.GetClaims());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public class BTCPayClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser>
|
||||
{
|
||||
IHttpContextAccessor httpContext;
|
||||
StoreRepository _StoreRepository;
|
||||
public BTCPayClaimsPrincipalFactory(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
IHttpContextAccessor httpContext,
|
||||
StoreRepository storeRepository,
|
||||
IOptions<IdentityOptions> options) : base(userManager, options)
|
||||
{
|
||||
this.httpContext = httpContext;
|
||||
_StoreRepository = storeRepository;
|
||||
}
|
||||
|
||||
public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
|
||||
{
|
||||
var ctx = (IActionContextAccessor)httpContext.HttpContext.RequestServices.GetService(typeof(IActionContextAccessor));
|
||||
var principal = await base.CreateAsync(user);
|
||||
if (ctx.ActionContext.HttpContext.GetIsBitpayAPI())
|
||||
return principal;
|
||||
var identity = ((ClaimsIdentity)principal.Identity);
|
||||
if (principal.IsInRole(Roles.ServerAdmin))
|
||||
{
|
||||
identity.AddClaim(new Claim(Policies.CanModifyServerSettings.Key, "true"));
|
||||
}
|
||||
if (ctx.ActionContext.RouteData.Values.TryGetValue("storeId", out var storeId))
|
||||
{
|
||||
var store = await _StoreRepository.FindStore((string)storeId, await UserManager.GetUserIdAsync(user));
|
||||
if (store != null)
|
||||
{
|
||||
identity.AddClaims(store.GetClaims());
|
||||
}
|
||||
}
|
||||
return principal;
|
||||
}
|
||||
}
|
||||
}
|
37
BTCPayServer/Security/Policies.cs
Normal file
37
BTCPayServer/Security/Policies.cs
Normal file
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace BTCPayServer.Security
|
||||
{
|
||||
public static class Policies
|
||||
{
|
||||
public static AuthorizationOptions AddBTCPayPolicies(this AuthorizationOptions options)
|
||||
{
|
||||
AddClaim(options, CanUseStore.Key);
|
||||
AddClaim(options, CanModifyStoreSettings.Key);
|
||||
AddClaim(options, CanModifyServerSettings.Key);
|
||||
return options;
|
||||
}
|
||||
|
||||
private static void AddClaim(AuthorizationOptions options, string key)
|
||||
{
|
||||
options.AddPolicy(key, o => o.RequireClaim(key));
|
||||
}
|
||||
|
||||
public class CanModifyServerSettings
|
||||
{
|
||||
public const string Key = "btcpay.store.canmodifyserversettings";
|
||||
}
|
||||
public class CanUseStore
|
||||
{
|
||||
public const string Key = "btcpay.store.canusestore";
|
||||
}
|
||||
public class CanModifyStoreSettings
|
||||
{
|
||||
public const string Key = "btcpay.store.canmodifystoresettings";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -44,7 +44,9 @@ 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();
|
||||
}
|
||||
|
@ -84,7 +86,9 @@ 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();
|
||||
}
|
||||
|
|
|
@ -5,11 +5,6 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class StorePolicies
|
||||
{
|
||||
public const string CanAccessStores = "CanAccessStore";
|
||||
public const string OwnStore = "OwnStore";
|
||||
}
|
||||
public class StoreRoles
|
||||
{
|
||||
public const string Owner = "Owner";
|
||||
|
|
Loading…
Add table
Reference in a new issue