204 lines
6.6 KiB
C#
204 lines
6.6 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using NodaTime;
|
|
using System.Text.Json;
|
|
using DysonNetwork.Common.Models;
|
|
using DysonNetwork.Common.Services;
|
|
|
|
namespace DysonNetwork.Common.Services.Permission;
|
|
|
|
public class PermissionService<TDbContext> where TDbContext : DbContext
|
|
{
|
|
private readonly TDbContext _db;
|
|
private readonly ICacheService _cache;
|
|
private static readonly TimeSpan CacheExpiration = TimeSpan.FromMinutes(1);
|
|
|
|
private const string PermCacheKeyPrefix = "perm:";
|
|
private const string PermGroupCacheKeyPrefix = "perm-cg:";
|
|
private const string PermissionGroupPrefix = "perm-g:";
|
|
|
|
public PermissionService(TDbContext db, ICacheService cache)
|
|
{
|
|
_db = db;
|
|
_cache = cache;
|
|
}
|
|
|
|
private static string GetPermissionCacheKey(string actor, string area, string key) =>
|
|
PermCacheKeyPrefix + actor + ":" + area + ":" + key;
|
|
|
|
private static string GetGroupsCacheKey(string actor) =>
|
|
PermGroupCacheKeyPrefix + actor;
|
|
|
|
private static string GetPermissionGroupKey(string actor) =>
|
|
PermissionGroupPrefix + actor;
|
|
|
|
public async Task<bool> HasPermissionAsync(string actor, string area, string key)
|
|
{
|
|
var value = await GetPermissionAsync<bool>(actor, area, key);
|
|
return value;
|
|
}
|
|
|
|
public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
|
|
{
|
|
var cacheKey = GetPermissionCacheKey(actor, area, key);
|
|
|
|
var (hit, cachedValue) = await _cache.GetAsyncWithStatus<T>(cacheKey);
|
|
if (hit)
|
|
return cachedValue;
|
|
|
|
var now = SystemClock.Instance.GetCurrentInstant();
|
|
var groupsKey = GetGroupsCacheKey(actor);
|
|
|
|
var groupsId = await _cache.GetAsync<List<Guid>>(groupsKey);
|
|
if (groupsId == null)
|
|
{
|
|
groupsId = await _db.Set<PermissionGroupMember>()
|
|
.Where(n => n.Actor == actor)
|
|
.Where(n => n.ExpiredAt == null || n.ExpiredAt > now)
|
|
.Where(n => n.AffectedAt == null || n.AffectedAt <= now)
|
|
.Select(e => e.GroupId)
|
|
.ToListAsync();
|
|
|
|
await _cache.SetWithGroupsAsync(groupsKey, groupsId,
|
|
[GetPermissionGroupKey(actor)],
|
|
CacheExpiration);
|
|
}
|
|
|
|
var permission = await _db.Set<PermissionNode>()
|
|
.Where(n => (n.GroupId == null && n.Actor == actor) ||
|
|
(n.GroupId != null && groupsId.Contains(n.GroupId.Value)))
|
|
.Where(n => n.Key == key && n.Area == area)
|
|
.Where(n => n.ExpiredAt == null || n.ExpiredAt > now)
|
|
.Where(n => n.AffectedAt == null || n.AffectedAt <= now)
|
|
.FirstOrDefaultAsync();
|
|
|
|
var result = permission is not null ? DeserializePermissionValue<T>(permission.Value) : default;
|
|
|
|
await _cache.SetWithGroupsAsync(cacheKey, result,
|
|
[GetPermissionGroupKey(actor)],
|
|
CacheExpiration);
|
|
|
|
return result;
|
|
}
|
|
|
|
public async Task<PermissionNode> AddPermissionNode<T>(
|
|
string actor,
|
|
string area,
|
|
string key,
|
|
T value,
|
|
Instant? expiredAt = null,
|
|
Instant? affectedAt = null
|
|
)
|
|
{
|
|
if (value is null) throw new ArgumentNullException(nameof(value));
|
|
|
|
var node = new PermissionNode
|
|
{
|
|
Actor = actor,
|
|
Key = key,
|
|
Area = area,
|
|
Value = SerializePermissionValue(value),
|
|
ExpiredAt = expiredAt,
|
|
AffectedAt = affectedAt
|
|
};
|
|
|
|
_db.Set<PermissionNode>().Add(node);
|
|
await _db.SaveChangesAsync();
|
|
|
|
// Invalidate related caches
|
|
await InvalidatePermissionCacheAsync(actor, area, key);
|
|
|
|
return node;
|
|
}
|
|
|
|
public async Task<PermissionNode> AddPermissionNodeToGroup<T>(
|
|
PermissionGroup group,
|
|
string actor,
|
|
string area,
|
|
string key,
|
|
T value,
|
|
Instant? expiredAt = null,
|
|
Instant? affectedAt = null
|
|
)
|
|
{
|
|
if (value is null) throw new ArgumentNullException(nameof(value));
|
|
|
|
var node = new PermissionNode
|
|
{
|
|
Actor = actor,
|
|
Key = key,
|
|
Area = area,
|
|
Value = SerializePermissionValue(value),
|
|
ExpiredAt = expiredAt,
|
|
AffectedAt = affectedAt,
|
|
Group = group,
|
|
GroupId = group.Id
|
|
};
|
|
|
|
_db.Set<PermissionNode>().Add(node);
|
|
await _db.SaveChangesAsync();
|
|
|
|
// Invalidate related caches
|
|
await InvalidatePermissionCacheAsync(actor, area, key);
|
|
await _cache.RemoveAsync(GetGroupsCacheKey(actor));
|
|
await _cache.RemoveGroupAsync(GetPermissionGroupKey(actor));
|
|
|
|
return node;
|
|
}
|
|
|
|
public async Task RemovePermissionNode(string actor, string area, string key)
|
|
{
|
|
var node = await _db.Set<PermissionNode>()
|
|
.Where(n => n.Actor == actor && n.Area == area && n.Key == key)
|
|
.FirstOrDefaultAsync();
|
|
if (node is not null) _db.Set<PermissionNode>().Remove(node);
|
|
await _db.SaveChangesAsync();
|
|
|
|
// Invalidate cache
|
|
await InvalidatePermissionCacheAsync(actor, area, key);
|
|
}
|
|
|
|
public async Task RemovePermissionNodeFromGroup(PermissionGroup group, string actor, string area, string key)
|
|
{
|
|
var node = await _db.Set<PermissionNode>()
|
|
.Where(n => n.GroupId == group.Id)
|
|
.Where(n => n.Actor == actor && n.Area == area && n.Key == key)
|
|
.FirstOrDefaultAsync();
|
|
if (node is null) return;
|
|
_db.Set<PermissionNode>().Remove(node);
|
|
await _db.SaveChangesAsync();
|
|
|
|
// Invalidate caches
|
|
await InvalidatePermissionCacheAsync(actor, area, key);
|
|
await _cache.RemoveAsync(GetGroupsCacheKey(actor));
|
|
await _cache.RemoveGroupAsync(GetPermissionGroupKey(actor));
|
|
}
|
|
|
|
private async Task InvalidatePermissionCacheAsync(string actor, string area, string key)
|
|
{
|
|
var cacheKey = GetPermissionCacheKey(actor, area, key);
|
|
await _cache.RemoveAsync(cacheKey);
|
|
}
|
|
|
|
private static T? DeserializePermissionValue<T>(JsonDocument json)
|
|
{
|
|
return JsonSerializer.Deserialize<T>(json.RootElement.GetRawText());
|
|
}
|
|
|
|
private static JsonDocument SerializePermissionValue<T>(T obj)
|
|
{
|
|
var str = JsonSerializer.Serialize(obj);
|
|
return JsonDocument.Parse(str);
|
|
}
|
|
|
|
public static PermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
|
|
{
|
|
return new PermissionNode
|
|
{
|
|
Actor = actor,
|
|
Area = area,
|
|
Key = key,
|
|
Value = SerializePermissionValue(value),
|
|
};
|
|
}
|
|
}
|