Move to a Claim based security

This commit is contained in:
nicolas.dorier 2018-04-30 02:33:42 +09:00
parent 3954ce2137
commit 1fc9a1a54b
18 changed files with 235 additions and 118 deletions

View file

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

View file

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

View file

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

View file

@ -19,7 +19,7 @@ using BTCPayServer.Logging;
namespace BTCPayServer.Controllers
{
[Authorize]
[Authorize(AuthenticationSchemes = "Identity.Application")]
[Route("[controller]/[action]")]
public class AccountController : Controller
{

View file

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

View file

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

View file

@ -24,7 +24,7 @@ using System.Globalization;
namespace BTCPayServer.Controllers
{
[Authorize]
[Authorize(AuthenticationSchemes = "Identity.Application")]
[Route("[controller]/[action]")]
public class ManageController : Controller
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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