177 lines
6.8 KiB
C#
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");
|
|
}
|
|
}
|
|
} |