:drunk: AI trying to fix bugs

This commit is contained in:
2025-07-06 21:15:30 +08:00
parent 3391c08c04
commit 7d1f096e87
70 changed files with 681 additions and 66945 deletions

View 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);
}
}

View 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),
};
}
}