:drunk: AI trying to fix bugs
This commit is contained in:
100
DysonNetwork.Common/Services/Permission/PermissionMiddleware.cs
Normal file
100
DysonNetwork.Common/Services/Permission/PermissionMiddleware.cs
Normal file
@ -0,0 +1,100 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DysonNetwork.Common.Extensions;
|
||||
using Microsoft.AspNetCore.Http.Extensions;
|
||||
|
||||
namespace DysonNetwork.Common.Services.Permission;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = true)]
|
||||
public class RequiredPermissionAttribute(string area, string key) : Attribute
|
||||
{
|
||||
public string Area { get; set; } = area;
|
||||
public string Key { get; } = key;
|
||||
}
|
||||
|
||||
public class PermissionMiddleware<TDbContext> where TDbContext : DbContext
|
||||
{
|
||||
private readonly RequestDelegate _next;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public PermissionMiddleware(RequestDelegate next, IServiceProvider serviceProvider)
|
||||
{
|
||||
_next = next;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext httpContext)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var permissionService = new PermissionService<TDbContext>(
|
||||
scope.ServiceProvider.GetRequiredService<TDbContext>(),
|
||||
scope.ServiceProvider.GetRequiredService<ICacheService>()
|
||||
);
|
||||
|
||||
var endpoint = httpContext.GetEndpoint();
|
||||
var attr = endpoint?.Metadata.OfType<RequiredPermissionAttribute>().FirstOrDefault();
|
||||
|
||||
if (attr != null)
|
||||
{
|
||||
if (httpContext.User.Identity?.IsAuthenticated != true)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await httpContext.Response.WriteAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
var currentUserId = httpContext.User.GetUserId();
|
||||
if (currentUserId == Guid.Empty)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await httpContext.Response.WriteAsync("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Check for superuser from PassClient
|
||||
// if (currentUser.IsSuperuser)
|
||||
// {
|
||||
// await _next(httpContext);
|
||||
// return;
|
||||
// }
|
||||
|
||||
var actor = $"user:{currentUserId}";
|
||||
var hasPermission = await permissionService.HasPermissionAsync(actor, attr.Area, attr.Key);
|
||||
|
||||
if (!hasPermission)
|
||||
{
|
||||
httpContext.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await httpContext.Response.WriteAsync("Forbidden");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await _next.Invoke(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
public static class PermissionServiceExtensions
|
||||
{
|
||||
public static IServiceCollection AddPermissionService<TDbContext>(this IServiceCollection services)
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
services.AddScoped<PermissionService<TDbContext>>(sp =>
|
||||
new PermissionService<TDbContext>(
|
||||
sp.GetRequiredService<TDbContext>(),
|
||||
sp.GetRequiredService<ICacheService>()
|
||||
));
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static IApplicationBuilder UsePermissionMiddleware<TDbContext>(this IApplicationBuilder builder)
|
||||
where TDbContext : DbContext
|
||||
{
|
||||
return builder.UseMiddleware<PermissionMiddleware<TDbContext>>(builder.ApplicationServices);
|
||||
}
|
||||
}
|
203
DysonNetwork.Common/Services/Permission/PermissionService.cs
Normal file
203
DysonNetwork.Common/Services/Permission/PermissionService.cs
Normal file
@ -0,0 +1,203 @@
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user