NonInventPurchasingSystem/CPRNIMS.Domain/Services/Account/RoleAuthorizationCache.cs
2026-01-26 14:21:31 +08:00

177 lines
6.8 KiB
C#

using CPRNIMS.Infrastructure.Database;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace CPRNIMS.Domain.Services.Account
{
public interface IRoleAuthorizationCache
{
Task<List<string>> GetAllowedRoleIdsAsync(string controller);
Task<bool> UserHasAccessAsync(string userId, string controller);
Task<List<string>> GetUserRoleIdsAsync(string userId);
void InvalidateCache(string controller);
void InvalidateUserCache(string userId);
void InvalidateAllCache();
}
public class RoleAuthorizationCache : IRoleAuthorizationCache
{
private readonly IMemoryCache _cache;
private readonly NonInventoryDbContext _dbContext;
private readonly ILogger<RoleAuthorizationCache> _logger;
private const string CONTROLLER_ROLES_PREFIX = "controller_roles_";
private const string USER_ROLES_PREFIX = "user_roles_";
private const int CACHE_DURATION_MINUTES = 30;
public RoleAuthorizationCache(
IMemoryCache cache,
NonInventoryDbContext dbContext,
ILogger<RoleAuthorizationCache> logger)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
/// <summary>
/// Get all role IDs that have access to a specific controller (CACHED)
/// </summary>
public async Task<List<string>> GetAllowedRoleIdsAsync(string controller)
{
if (string.IsNullOrWhiteSpace(controller))
throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));
var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
entry.SetPriority(CacheItemPriority.High);
_logger.LogInformation("Cache MISS: Loading role permissions for controller: {Controller}", controller);
var roleIds = await _dbContext.AuthorizeRoles
.Where(ar => ar.IsActive && ar.Controller == controller)
.Select(ar => ar.RoleId)
.Distinct()
.ToListAsync();
_logger.LogInformation("Cached {Count} roles for controller: {Controller}", roleIds.Count, controller);
return roleIds ?? new List<string>();
});
}
/// <summary>
/// Get all role IDs for a specific user (CACHED)
/// </summary>
public async Task<List<string>> GetUserRoleIdsAsync(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentException("User ID cannot be null or empty", nameof(userId));
var cacheKey = $"{USER_ROLES_PREFIX}{userId}";
return await _cache.GetOrCreateAsync(cacheKey, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(CACHE_DURATION_MINUTES);
entry.SetPriority(CacheItemPriority.High);
_logger.LogInformation("Cache MISS: Loading roles for user: {UserId}", userId);
var userRoleIds = await _dbContext.UserRoles
.Where(ur => ur.UserId == userId)
.Select(ur => ur.RoleId)
.ToListAsync();
_logger.LogInformation("Cached {Count} roles for user: {UserId}", userRoleIds.Count, userId);
return userRoleIds ?? new List<string>();
});
}
/// <summary>
/// Check if a specific user has access to a controller (FULLY CACHED)
/// </summary>
public async Task<bool> UserHasAccessAsync(string userId, string controller)
{
if (string.IsNullOrWhiteSpace(userId))
throw new ArgumentException("User ID cannot be null or empty", nameof(userId));
if (string.IsNullOrWhiteSpace(controller))
throw new ArgumentException("Controller name cannot be null or empty", nameof(controller));
try
{
// Get allowed role IDs from cache (or database if cache miss)
var allowedRoleIds = await GetAllowedRoleIdsAsync(controller);
if (!allowedRoleIds.Any())
{
_logger.LogWarning("No roles configured for controller: {Controller}", controller);
return false;
}
// Get user's role IDs from cache (or database if cache miss)
var userRoleIds = await GetUserRoleIdsAsync(userId);
if (!userRoleIds.Any())
{
_logger.LogWarning("User {UserId} has no roles assigned", userId);
return false;
}
// Check if user has any of the required roles (IN-MEMORY operation, no database!)
var hasAccess = userRoleIds.Any(userRole => allowedRoleIds.Contains(userRole));
_logger.LogDebug("User {UserId} access to {Controller}: {HasAccess}", userId, controller, hasAccess);
return hasAccess;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error checking access for user {UserId} on controller {Controller}",
userId, controller);
throw;
}
}
/// <summary>
/// Invalidate cache for a specific controller
/// </summary>
public void InvalidateCache(string controller)
{
if (string.IsNullOrWhiteSpace(controller))
return;
var cacheKey = $"{CONTROLLER_ROLES_PREFIX}{controller}";
_cache.Remove(cacheKey);
_logger.LogInformation("Cache invalidated for controller: {Controller}", controller);
}
/// <summary>
/// Invalidate cache for a specific user (call this when user roles change)
/// </summary>
public void InvalidateUserCache(string userId)
{
if (string.IsNullOrWhiteSpace(userId))
return;
var cacheKey = $"{USER_ROLES_PREFIX}{userId}";
_cache.Remove(cacheKey);
_logger.LogInformation("Cache invalidated for user: {UserId}", userId);
}
/// <summary>
/// Invalidate all controller role caches
/// </summary>
public void InvalidateAllCache()
{
_logger.LogWarning("All cache invalidation requested - consider implementing a cache key tracking system");
}
}
}