mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Fix: If user get locked out, unlocking or deleting user fails
This is due to the fact our UserService is a singleton, and it had a reference on UserManager which is scoped. UserManager is caching user entities at the scope level. UserService then had a view completely unsynchronized with the database.
This commit is contained in:
@@ -86,7 +86,7 @@ namespace BTCPayServer.Controllers
|
|||||||
Id = user.Id,
|
Id = user.Id,
|
||||||
Email = user.Email,
|
Email = user.Email,
|
||||||
Verified = user.EmailConfirmed || !user.RequiresEmailConfirmation,
|
Verified = user.EmailConfirmed || !user.RequiresEmailConfirmation,
|
||||||
IsAdmin = _userService.IsRoleAdmin(roles)
|
IsAdmin = Roles.HasServerAdmin(roles)
|
||||||
};
|
};
|
||||||
return View(userVM);
|
return View(userVM);
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
var admins = await _UserManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||||
var roles = await _UserManager.GetRolesAsync(user);
|
var roles = await _UserManager.GetRolesAsync(user);
|
||||||
var wasAdmin = _userService.IsRoleAdmin(roles);
|
var wasAdmin = Roles.HasServerAdmin(roles);
|
||||||
if (!viewModel.IsAdmin && admins.Count == 1 && wasAdmin)
|
if (!viewModel.IsAdmin && admins.Count == 1 && wasAdmin)
|
||||||
{
|
{
|
||||||
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
|
TempData[WellKnownTempData.ErrorMessage] = "This is the only Admin, so their role can't be removed until another Admin is added.";
|
||||||
@@ -219,7 +219,7 @@ namespace BTCPayServer.Controllers
|
|||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
var roles = await _UserManager.GetRolesAsync(user);
|
var roles = await _UserManager.GetRolesAsync(user);
|
||||||
if (_userService.IsRoleAdmin(roles))
|
if (Roles.HasServerAdmin(roles))
|
||||||
{
|
{
|
||||||
if (await _userService.IsUserTheOnlyOneAdmin(user))
|
if (await _userService.IsUserTheOnlyOneAdmin(user))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public class Roles
|
public class Roles
|
||||||
{
|
{
|
||||||
public const string ServerAdmin = "ServerAdmin";
|
public const string ServerAdmin = "ServerAdmin";
|
||||||
|
public static bool HasServerAdmin(IList<string> roles)
|
||||||
|
{
|
||||||
|
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,14 @@ using BTCPayServer.Services.Stores;
|
|||||||
using BTCPayServer.Storage.Services;
|
using BTCPayServer.Storage.Services;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace BTCPayServer.Services
|
namespace BTCPayServer.Services
|
||||||
{
|
{
|
||||||
public class UserService
|
public class UserService
|
||||||
{
|
{
|
||||||
private readonly UserManager<ApplicationUser> _userManager;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly StoredFileRepository _storedFileRepository;
|
private readonly StoredFileRepository _storedFileRepository;
|
||||||
private readonly FileService _fileService;
|
private readonly FileService _fileService;
|
||||||
private readonly StoreRepository _storeRepository;
|
private readonly StoreRepository _storeRepository;
|
||||||
@@ -23,14 +24,14 @@ namespace BTCPayServer.Services
|
|||||||
private readonly ILogger<UserService> _logger;
|
private readonly ILogger<UserService> _logger;
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
UserManager<ApplicationUser> userManager,
|
IServiceProvider serviceProvider,
|
||||||
StoredFileRepository storedFileRepository,
|
StoredFileRepository storedFileRepository,
|
||||||
FileService fileService,
|
FileService fileService,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
ApplicationDbContextFactory applicationDbContextFactory,
|
ApplicationDbContextFactory applicationDbContextFactory,
|
||||||
ILogger<UserService> logger)
|
ILogger<UserService> logger)
|
||||||
{
|
{
|
||||||
_userManager = userManager;
|
_serviceProvider = serviceProvider;
|
||||||
_storedFileRepository = storedFileRepository;
|
_storedFileRepository = storedFileRepository;
|
||||||
_fileService = fileService;
|
_fileService = fileService;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
@@ -67,17 +68,19 @@ namespace BTCPayServer.Services
|
|||||||
}
|
}
|
||||||
public async Task<bool?> ToggleUser(string userId, DateTimeOffset? lockedOutDeadline)
|
public async Task<bool?> ToggleUser(string userId, DateTimeOffset? lockedOutDeadline)
|
||||||
{
|
{
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
var user = await userManager.FindByIdAsync(userId);
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (lockedOutDeadline is not null)
|
if (lockedOutDeadline is not null)
|
||||||
{
|
{
|
||||||
await _userManager.SetLockoutEnabledAsync(user, true);
|
await userManager.SetLockoutEnabledAsync(user, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var res = await _userManager.SetLockoutEndDateAsync(user, lockedOutDeadline);
|
var res = await userManager.SetLockoutEndDateAsync(user, lockedOutDeadline);
|
||||||
if (res.Succeeded)
|
if (res.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"User {user.Id} is now {(lockedOutDeadline is null ? "unlocked" : "locked")}");
|
_logger.LogInformation($"User {user.Id} is now {(lockedOutDeadline is null ? "unlocked" : "locked")}");
|
||||||
@@ -92,25 +95,31 @@ namespace BTCPayServer.Services
|
|||||||
|
|
||||||
public async Task<bool> IsAdminUser(string userId)
|
public async Task<bool> IsAdminUser(string userId)
|
||||||
{
|
{
|
||||||
return IsRoleAdmin(await _userManager.GetRolesAsync(new ApplicationUser() { Id = userId }));
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
return Roles.HasServerAdmin(await userManager.GetRolesAsync(new ApplicationUser() { Id = userId }));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> IsAdminUser(ApplicationUser user)
|
public async Task<bool> IsAdminUser(ApplicationUser user)
|
||||||
{
|
{
|
||||||
return IsRoleAdmin(await _userManager.GetRolesAsync(user));
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
return Roles.HasServerAdmin(await userManager.GetRolesAsync(user));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> SetAdminUser(string userId, bool enableAdmin)
|
public async Task<bool> SetAdminUser(string userId, bool enableAdmin)
|
||||||
{
|
{
|
||||||
var user = await _userManager.FindByIdAsync(userId);
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
var user = await userManager.FindByIdAsync(userId);
|
||||||
IdentityResult res;
|
IdentityResult res;
|
||||||
if (enableAdmin)
|
if (enableAdmin)
|
||||||
{
|
{
|
||||||
res = await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
res = await userManager.AddToRoleAsync(user, Roles.ServerAdmin);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
res = await _userManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
|
res = await userManager.RemoveFromRoleAsync(user, Roles.ServerAdmin);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.Succeeded)
|
if (res.Succeeded)
|
||||||
@@ -127,6 +136,9 @@ namespace BTCPayServer.Services
|
|||||||
|
|
||||||
public async Task DeleteUserAndAssociatedData(ApplicationUser user)
|
public async Task DeleteUserAndAssociatedData(ApplicationUser user)
|
||||||
{
|
{
|
||||||
|
using var scope = _serviceProvider.CreateScope();
|
||||||
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
|
||||||
var userId = user.Id;
|
var userId = user.Id;
|
||||||
var files = await _storedFileRepository.GetFiles(new StoredFileRepository.FilesQuery()
|
var files = await _storedFileRepository.GetFiles(new StoredFileRepository.FilesQuery()
|
||||||
{
|
{
|
||||||
@@ -135,8 +147,8 @@ namespace BTCPayServer.Services
|
|||||||
|
|
||||||
await Task.WhenAll(files.Select(file => _fileService.RemoveFile(file.Id, userId)));
|
await Task.WhenAll(files.Select(file => _fileService.RemoveFile(file.Id, userId)));
|
||||||
|
|
||||||
user = await _userManager.FindByIdAsync(userId);
|
user = await userManager.FindByIdAsync(userId);
|
||||||
var res = await _userManager.DeleteAsync(user);
|
var res = await userManager.DeleteAsync(user);
|
||||||
if (res.Succeeded)
|
if (res.Succeeded)
|
||||||
{
|
{
|
||||||
_logger.LogInformation($"User {user.Id} was successfully deleted");
|
_logger.LogInformation($"User {user.Id} was successfully deleted");
|
||||||
@@ -149,21 +161,17 @@ namespace BTCPayServer.Services
|
|||||||
await _storeRepository.CleanUnreachableStores();
|
await _storeRepository.CleanUnreachableStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsRoleAdmin(IList<string> roles)
|
|
||||||
{
|
|
||||||
return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> IsUserTheOnlyOneAdmin(ApplicationUser user)
|
public async Task<bool> IsUserTheOnlyOneAdmin(ApplicationUser user)
|
||||||
{
|
{
|
||||||
var isUserAdmin = await IsAdminUser(user);
|
using var scope = _serviceProvider.CreateScope();
|
||||||
if (!isUserAdmin)
|
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
|
||||||
|
var roles = await userManager.GetRolesAsync(user);
|
||||||
|
if (!Roles.HasServerAdmin(roles))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
var adminUsers = await userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
||||||
var adminUsers = await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin);
|
|
||||||
var enabledAdminUsers = adminUsers
|
var enabledAdminUsers = adminUsers
|
||||||
.Where(applicationUser => !IsDisabled(applicationUser))
|
.Where(applicationUser => !IsDisabled(applicationUser))
|
||||||
.Select(applicationUser => applicationUser.Id).ToList();
|
.Select(applicationUser => applicationUser.Id).ToList();
|
||||||
|
|||||||
Reference in New Issue
Block a user