diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index a734b15ac..ed069302d 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -169,6 +169,10 @@ namespace BTCPayServer.Tests #endif var conf = confBuilder.Build(); _Host = new WebHostBuilder() + .UseDefaultServiceProvider(options => + { + options.ValidateScopes = true; + }) .UseConfiguration(conf) .UseContentRoot(FindBTCPayServerDirectory()) .UseWebRoot(Path.Combine(FindBTCPayServerDirectory(), "wwwroot")) @@ -284,10 +288,7 @@ namespace BTCPayServer.Tests public string IntegratedLightning { get; internal set; } public bool InContainer { get; internal set; } - public T GetService() - { - return _Host.Services.GetRequiredService(); - } + public T GetService() => _Host.Services.GetRequiredService(); public IServiceProvider ServiceProvider => _Host.Services; diff --git a/BTCPayServer.Tests/PlaywrightTests.cs b/BTCPayServer.Tests/PlaywrightTests.cs index 899595093..fb9239b7f 100644 --- a/BTCPayServer.Tests/PlaywrightTests.cs +++ b/BTCPayServer.Tests/PlaywrightTests.cs @@ -28,6 +28,7 @@ using ExchangeSharp; using LNURL; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Playwright; using static Microsoft.Playwright.Assertions; using NBitcoin; @@ -200,7 +201,8 @@ namespace BTCPayServer.Tests await s.Page.FillAsync("#Email", changedEmail); await s.ClickPagePrimary(); await s.FindAlertMessage(); - var manager = tester.PayTester.GetService>(); + using var scope = tester.PayTester.ServiceProvider.CreateScope(); + var manager = scope.ServiceProvider.GetRequiredService>(); Assert.NotNull(await manager.FindByNameAsync(changedEmail)); Assert.NotNull(await manager.FindByEmailAsync(changedEmail)); } diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index d1f559393..e1bbe5d0d 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -26,6 +26,7 @@ using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using NBitcoin; using NBitcoin.DataEncoders; using NBitcoin.Payment; @@ -58,7 +59,8 @@ namespace BTCPayServer.Tests public async Task MakeAdmin(bool isAdmin = true) { - var userManager = parent.PayTester.GetService>(); + using var scope = parent.PayTester.ServiceProvider.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); var u = await userManager.FindByIdAsync(UserId); if (isAdmin) await userManager.AddToRoleAsync(u, Roles.ServerAdmin); diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index 3c824f3d8..829bdf2af 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -15,7 +15,6 @@ using BTCPayServer.Data; using BTCPayServer.Plugins.Webhooks.Controllers; using BTCPayServer.Security; using BTCPayServer.Security.Greenfield; -using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; @@ -38,26 +37,13 @@ using WebhookDeliveryData = BTCPayServer.Client.Models.WebhookDeliveryData; namespace BTCPayServer.Controllers.Greenfield { - public class BTCPayServerClientFactory : IBTCPayServerClientFactory + public class BTCPayServerClientFactory( + StoreRepository storeRepository, + IOptionsMonitor identityOptions, + IServiceProvider serviceProvider, + IServiceScopeFactory servicesScopeFactory) + : IBTCPayServerClientFactory { - private readonly StoreRepository _storeRepository; - private readonly IOptionsMonitor _identityOptions; - private readonly UserManager _userManager; - - private readonly IServiceProvider _serviceProvider; - - public BTCPayServerClientFactory( - StoreRepository storeRepository, - IOptionsMonitor identityOptions, - UserManager userManager, - IServiceProvider serviceProvider) - { - _storeRepository = storeRepository; - _identityOptions = identityOptions; - _userManager = userManager; - _serviceProvider = serviceProvider; - } - public Task Create(string userId, params string[] storeIds) { return Create(userId, storeIds, new DefaultHttpContext() @@ -74,17 +60,19 @@ namespace BTCPayServer.Controllers.Greenfield public async Task Create(string userId, string[] storeIds, HttpContext context) { + using var scope = servicesScopeFactory.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); if (!string.IsNullOrEmpty(userId)) { - var user = await _userManager.FindByIdAsync(userId); + var user = await userManager.FindByIdAsync(userId); List claims = new List { - new Claim(_identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, userId), + new Claim(identityOptions.CurrentValue.ClaimsIdentity.UserIdClaimType, userId), new Claim(GreenfieldConstants.ClaimTypes.Permission, Permission.Create(Policies.Unrestricted).ToString()) }; - claims.AddRange((await _userManager.GetRolesAsync(user)).Select(s => - new Claim(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s))); + claims.AddRange((await userManager.GetRolesAsync(user)).Select(s => + new Claim(identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, s))); context.User = new ClaimsPrincipal(new ClaimsIdentity(claims, $"Local{GreenfieldConstants.AuthenticationType}WithUser")); @@ -95,22 +83,22 @@ namespace BTCPayServer.Controllers.Greenfield new ClaimsPrincipal(new ClaimsIdentity( new List() { - new(_identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, Roles.ServerAdmin) + new(identityOptions.CurrentValue.ClaimsIdentity.RoleClaimType, Roles.ServerAdmin) }, $"Local{GreenfieldConstants.AuthenticationType}")); } if (storeIds?.Any() is true) { - context.SetStoreData(await _storeRepository.FindStore(storeIds.First())); - context.SetStoresData(await _storeRepository.GetStoresByUserId(userId, storeIds)); + context.SetStoreData(await storeRepository.FindStore(storeIds.First())); + context.SetStoresData(await storeRepository.GetStoresByUserId(userId, storeIds)); } else { - context.SetStoresData(await _storeRepository.GetStoresByUserId(userId)); + context.SetStoresData(await storeRepository.GetStoresByUserId(userId)); } - return ActivatorUtilities.CreateInstance(_serviceProvider, + return ActivatorUtilities.CreateInstance(serviceProvider, new LocalHttpContextAccessor() { HttpContext = context }); } diff --git a/BTCPayServer/Controllers/UIInvoiceController.cs b/BTCPayServer/Controllers/UIInvoiceController.cs index b5d7d24ec..f4eccf933 100644 --- a/BTCPayServer/Controllers/UIInvoiceController.cs +++ b/BTCPayServer/Controllers/UIInvoiceController.cs @@ -39,7 +39,6 @@ namespace BTCPayServer.Controllers public partial class UIInvoiceController : Controller { readonly InvoiceRepository _InvoiceRepository; - private readonly WalletRepository _walletRepository; readonly RateFetcher _RateProvider; readonly StoreRepository _StoreRepository; readonly UserManager _UserManager; @@ -71,14 +70,12 @@ namespace BTCPayServer.Controllers public UIInvoiceController( InvoiceRepository invoiceRepository, - WalletRepository walletRepository, DisplayFormatter displayFormatter, CurrencyNameTable currencyNameTable, UserManager userManager, RateFetcher rateProvider, StoreRepository storeRepository, EventAggregator eventAggregator, - ContentSecurityPolicies csp, BTCPayNetworkProvider networkProvider, PayoutMethodHandlerDictionary payoutHandlers, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary, @@ -105,7 +102,6 @@ namespace BTCPayServer.Controllers _CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable)); _StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository)); _InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository)); - _walletRepository = walletRepository; _RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider)); _UserManager = userManager; _EventAggregator = eventAggregator; diff --git a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs index a79a272b4..26402a7a4 100644 --- a/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs +++ b/BTCPayServer/Data/Payouts/LightningLike/LightningLikePayoutHandler.cs @@ -43,15 +43,13 @@ namespace BTCPayServer.Data.Payouts.LightningLike public const string LightningLikePayoutHandlerClearnetNamedClient = nameof(LightningLikePayoutHandlerClearnetNamedClient); private readonly IHttpClientFactory _httpClientFactory; - private readonly UserService _userService; - private readonly IAuthorizationService _authorizationService; public LightningLikePayoutHandler( PayoutMethodId payoutMethodId, IOptions options, BTCPayNetwork network, PaymentMethodHandlerDictionary paymentHandlers, - IHttpClientFactory httpClientFactory, UserService userService, IAuthorizationService authorizationService) + IHttpClientFactory httpClientFactory) { _paymentHandlers = paymentHandlers; Network = network; @@ -59,8 +57,6 @@ namespace BTCPayServer.Data.Payouts.LightningLike _options = options; PaymentMethodId = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode); _httpClientFactory = httpClientFactory; - _userService = userService; - _authorizationService = authorizationService; Currency = network.CryptoCode; } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index fb1d1529a..9329de902 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -427,7 +427,7 @@ o.GetRequiredService>().ToDictionary(o => o.P services.AddSingleton(); services.AddSingleton(); - services.AddScoped(); + services.AddSingleton(); RegisterExchangeRecommendations(services); services.AddSingleton(); diff --git a/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs index 60d974d48..830358008 100644 --- a/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs +++ b/BTCPayServer/Plugins/Emails/HostedServices/UserEventTriggerHostedService.cs @@ -11,6 +11,7 @@ using BTCPayServer.Services; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; using Microsoft.AspNetCore.Identity; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json.Linq; using QRCoder; @@ -18,14 +19,12 @@ namespace BTCPayServer.Plugins.Emails.HostedServices; public class UserEventHostedService( EventAggregator eventAggregator, - UserManager userManager, + IServiceScopeFactory serviceScopeFactory, ISettingsAccessor serverSettings, NotificationSender notificationSender, Logs logs) : EventHostedServiceBase(eventAggregator, logs) { - public UserManager UserManager { get; } = userManager; - protected override void SubscribeToEvents() { SubscribeAny(); @@ -117,7 +116,9 @@ public class UserEventHostedService( private async Task CreateTriggerEvent(string trigger, JObject model, ApplicationUser user) { - var admins = await UserManager.GetUsersInRoleAsync(Roles.ServerAdmin); + using var scope = serviceScopeFactory.CreateScope(); + var userManager = scope.ServiceProvider.GetRequiredService>(); + var admins = await userManager.GetUsersInRoleAsync(Roles.ServerAdmin); var adminMailboxes = string.Join(", ", admins.Select(a => a.GetMailboxAddress().ToString()).ToArray()); model["Admins"] = new JObject() { diff --git a/BTCPayServer/Plugins/Subscriptions/SubscriptionHostedService.cs b/BTCPayServer/Plugins/Subscriptions/SubscriptionHostedService.cs index b4e013cfb..34c04f052 100644 --- a/BTCPayServer/Plugins/Subscriptions/SubscriptionHostedService.cs +++ b/BTCPayServer/Plugins/Subscriptions/SubscriptionHostedService.cs @@ -18,6 +18,7 @@ using BTCPayServer.Services.Rates; using Dapper; using Microsoft.AspNetCore.Routing; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using static BTCPayServer.Data.Subscriptions.SubscriberData; @@ -30,7 +31,7 @@ public class SubscriptionHostedService( EventAggregator eventAggregator, ApplicationDbContextFactory applicationDbContextFactory, SettingsRepository settingsRepository, - UIInvoiceController invoiceController, + IServiceScopeFactory scopeFactory, CurrencyNameTable currencyNameTable, LinkGenerator linkGenerator, Logs logger) : EventHostedServiceBase(eventAggregator, logger), IPeriodicTask @@ -162,6 +163,8 @@ public class SubscriptionHostedService( if (amount > 0) { + using var scope = scopeFactory.CreateScope(); + var invoiceController = scope.ServiceProvider.GetRequiredService(); var request = await invoiceController.CreateInvoiceCoreRaw(new() { Currency = plan.Currency, diff --git a/BTCPayServer/Services/BTCPayServerSecurityStampValidator.cs b/BTCPayServer/Services/BTCPayServerSecurityStampValidator.cs index 0c178e8ac..8ab27da32 100644 --- a/BTCPayServer/Services/BTCPayServerSecurityStampValidator.cs +++ b/BTCPayServer/Services/BTCPayServerSecurityStampValidator.cs @@ -26,6 +26,10 @@ public class BTCPayServerSecurityStampValidator( ConcurrentDictionary _DisabledUsers = new ConcurrentDictionary(); public bool HasAny => !_DisabledUsers.IsEmpty; + /// + /// Note that you also need to invalidate the security stamp of the user + /// + /// public void Add(string user) { _DisabledUsers.TryAdd(user, DateTimeOffset.UtcNow); diff --git a/BTCPayServer/Services/Notifications/NotificationSender.cs b/BTCPayServer/Services/Notifications/NotificationSender.cs index 31a7a8861..f420e169e 100644 --- a/BTCPayServer/Services/Notifications/NotificationSender.cs +++ b/BTCPayServer/Services/Notifications/NotificationSender.cs @@ -6,6 +6,7 @@ using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Data; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; namespace BTCPayServer.Services.Notifications { @@ -13,25 +14,15 @@ namespace BTCPayServer.Services.Notifications { public string UserId { get; set; } } - public class NotificationSender + public class NotificationSender(ApplicationDbContextFactory contextFactory, IServiceScopeFactory scopeFactory, NotificationManager notificationManager) { - private readonly ApplicationDbContextFactory _contextFactory; - private readonly UserManager _userManager; - private readonly NotificationManager _notificationManager; - - public NotificationSender(ApplicationDbContextFactory contextFactory, UserManager userManager, NotificationManager notificationManager) - { - _contextFactory = contextFactory; - _userManager = userManager; - _notificationManager = notificationManager; - } public async Task SendNotification(INotificationScope scope, BaseNotification notification) { ArgumentNullException.ThrowIfNull(scope); ArgumentNullException.ThrowIfNull(notification); var users = await GetUsers(scope, notification.Identifier); - await using (var db = _contextFactory.CreateContext()) + await using (var db = contextFactory.CreateContext()) { foreach (var uid in users) { @@ -48,7 +39,7 @@ namespace BTCPayServer.Services.Notifications } await db.SaveChangesAsync(); } - _notificationManager.InvalidateNotificationCache(users); + notificationManager.InvalidateNotificationCache(users); } public BaseNotification GetBaseNotification(NotificationData notificationData) @@ -58,7 +49,7 @@ namespace BTCPayServer.Services.Notifications private async Task GetUsers(INotificationScope scope, string notificationIdentifier) { - await using var ctx = _contextFactory.CreateContext(); + await using var ctx = contextFactory.CreateContext(); var split = notificationIdentifier.Split('_', StringSplitOptions.None); var terms = new List(); @@ -71,8 +62,8 @@ namespace BTCPayServer.Services.Notifications { case AdminScope _: { - query = _userManager.GetUsersInRoleAsync(Roles.ServerAdmin).Result.AsQueryable(); - + using var s = scopeFactory.CreateScope(); + query = s.ServiceProvider.GetService>().GetUsersInRoleAsync(Roles.ServerAdmin).Result.AsQueryable(); break; } case StoreScope s: