-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Same role name with diferent tenants #782
Comments
@neozhu FYI, even if you add another Role with the same name, you can't assign that role to a user. You also can't remove the role if you happen to manually set it in DB. I solved this by implementing a custom UserStore and UserManager. public class MultiTenantUserStore : UserStore<
ApplicationUser,
ApplicationRole,
ApplicationDbContext,
string,
ApplicationUserClaim,
ApplicationUserRole,
ApplicationUserLogin,
ApplicationUserToken,
ApplicationRoleClaim>
{
public MultiTenantUserStore(ApplicationDbContext context)
: base(context)
{
}
public override async Task AddToRoleAsync(ApplicationUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
throw new ArgumentNullException(nameof(user));
if (string.IsNullOrWhiteSpace(normalizedRoleName))
throw new ArgumentException("Value cannot be null or empty.", nameof(normalizedRoleName));
var tenantId = user.TenantId;
// Find the role within the user's tenant
var roleEntity = await Context.Roles
.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName && r.TenantId == tenantId, cancellationToken);
if (roleEntity == null)
throw new InvalidOperationException($"Role '{normalizedRoleName}' does not exist in the user's tenant.");
// Check if the user is already in the role
var userRole = await Context.UserRoles
.FirstOrDefaultAsync(ur => ur.UserId == user.Id && ur.RoleId == roleEntity.Id && ur.TenantId == tenantId, cancellationToken);
if (userRole != null)
return; // User is already in the role
// Create a new user-role link
userRole = new ApplicationUserRole
{
UserId = user.Id,
RoleId = roleEntity.Id,
TenantId = tenantId
};
Context.UserRoles.Add(userRole);
}
public override async Task<bool> IsInRoleAsync(ApplicationUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
throw new ArgumentNullException(nameof(user));
if (string.IsNullOrWhiteSpace(normalizedRoleName))
throw new ArgumentException("Value cannot be null or empty.", nameof(normalizedRoleName));
var tenantId = user.TenantId;
// Find the role within the user's tenant
var role = await Context.Roles
.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName && r.TenantId == tenantId, cancellationToken);
if (role == null)
return false;
// Check if the user has this role
var userRole = await Context.UserRoles
.FirstOrDefaultAsync(ur => ur.UserId == user.Id && ur.RoleId == role.Id && ur.TenantId == tenantId, cancellationToken);
return userRole != null;
}
public override async Task RemoveFromRoleAsync(ApplicationUser user, string normalizedRoleName, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
ThrowIfDisposed();
if (user == null)
throw new ArgumentNullException(nameof(user));
if (string.IsNullOrWhiteSpace(normalizedRoleName))
throw new ArgumentException("Value cannot be null or empty.", nameof(normalizedRoleName));
var tenantId = user.TenantId;
// Find the role within the user's tenant
var role = await Context.Roles
.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName && r.TenantId == tenantId, cancellationToken);
if (role != null)
{
// Find the user-role link within the tenant
var userRole = await Context.UserRoles
.FirstOrDefaultAsync(ur => ur.UserId == user.Id && ur.RoleId == role.Id && ur.TenantId == tenantId, cancellationToken);
if (userRole != null)
{
Context.UserRoles.Remove(userRole);
}
}
}
}
public class MultiTenantUserManager : UserManager<ApplicationUser>
{
private readonly RoleManager<ApplicationRole> _roleManager;
public MultiTenantUserManager(
IUserStore<ApplicationUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<ApplicationUser> passwordHasher,
IEnumerable<IUserValidator<ApplicationUser>> userValidators,
IEnumerable<IPasswordValidator<ApplicationUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IServiceProvider services,
RoleManager<ApplicationRole> roleManager,
ILogger<UserManager<ApplicationUser>> logger)
: base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, services, logger)
{
_roleManager = roleManager;
}
public override async Task<IdentityResult> AddToRolesAsync(ApplicationUser user, IEnumerable<string> roles)
{
var tenantId = user.TenantId;
var normalizedRoleNames = roles.Select(NormalizeName).ToList();
var tenantRoles = await _roleManager.Roles
.Where(r => normalizedRoleNames.Contains(r.NormalizedName) && r.TenantId == tenantId)
.ToListAsync();
if (tenantRoles.Count != roles.Count())
{
var missingRoles = roles.Except(tenantRoles.Select(r => r.Name), StringComparer.OrdinalIgnoreCase);
return IdentityResult.Failed(new IdentityError
{
Code = "RoleNotFound",
Description = $"Roles '{string.Join(", ", missingRoles)}' do not exist in the user's tenant."
});
}
foreach (var role in tenantRoles)
{
var result = await AddToRoleAsync(user, role.Name);
if (!result.Succeeded)
{
return result;
}
}
return IdentityResult.Success;
}
public override async Task<IdentityResult> AddToRoleAsync(ApplicationUser user, string roleName)
{
var tenantId = user.TenantId;
var normalizedRoleName = NormalizeName(roleName);
var role = await _roleManager.Roles
.FirstOrDefaultAsync(r => r.NormalizedName == normalizedRoleName && r.TenantId == tenantId);
if (role == null)
{
return IdentityResult.Failed(new IdentityError
{
Code = "RoleNotFound",
Description = $"Role '{roleName}' does not exist in the user's tenant."
});
}
if (await IsInRoleAsync(user, role.Name, tenantId))
{
return IdentityResult.Failed(new IdentityError
{
Code = "UserAlreadyInRole",
Description = $"User is already in role '{roleName}'."
});
}
var userRoleStore = GetUserRoleStore();
await userRoleStore.AddToRoleAsync(user, role.NormalizedName, CancellationToken.None);
return await UpdateUserAsync(user);
}
// Override IsInRoleAsync to include TenantId
public async Task<bool> IsInRoleAsync(ApplicationUser user, string roleName, string tenantId)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
if (string.IsNullOrEmpty(roleName))
throw new ArgumentException("Value cannot be null or empty.", nameof(roleName));
var normalizedRoleName = NormalizeName(roleName);
var userRoleStore = GetUserRoleStore();
var roles = await userRoleStore.GetRolesAsync(user, CancellationToken.None);
// Since roles can have the same name across different tenants, we need to ensure we only consider roles within the user's tenant
var isInRole = await _roleManager.Roles.AnyAsync(r =>
r.NormalizedName == normalizedRoleName &&
r.TenantId == tenantId &&
Context.UserRoles.Any(ur => ur.UserId == user.Id && ur.RoleId == r.Id && ur.TenantId == tenantId));
return isInRole;
}
public override async Task<IdentityResult> RemoveFromRoleAsync(ApplicationUser user, string role)
{
var normalizedRoleName = NormalizeName(role);
var userRoleStore = GetUserRoleStore();
await userRoleStore.RemoveFromRoleAsync(user, normalizedRoleName, CancellationToken.None);
return await UpdateUserAsync(user);
}
private IUserRoleStore<ApplicationUser> GetUserRoleStore()
{
if (Store is IUserRoleStore<ApplicationUser> userRoleStore)
{
return userRoleStore;
}
else
{
throw new NotSupportedException("The user store does not implement IUserRoleStore<ApplicationUser>.");
}
}
private ApplicationDbContext Context
{
get
{
var store = Store as UserStore<ApplicationUser, ApplicationRole, ApplicationDbContext, string, ApplicationUserClaim, ApplicationUserRole, ApplicationUserLogin, ApplicationUserToken, ApplicationRoleClaim>;
return store?.Context;
}
}
} Then register it this way: services.AddScoped<IUserStore<ApplicationUser>, MultiTenantUserStore>();
services.AddScoped<UserManager<ApplicationUser>, MultiTenantUserManager>(); |
Thank you for providing the code! I’ve fixed the issue, I appreciate your support. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi
What is the purpose of adding Tenant to the role?
I am trying to add the expensive Admin role but to the Master tenant and unfortunately I get an error that such a role already exists.
Bogdan
The text was updated successfully, but these errors were encountered: