using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Memory; using System.Data; namespace HyperBooru.Services; public class SecurityService { private IDbContextFactory dbFactory; private Group[] groups; private Acl[] acls; public SecurityService(IDbContextFactory dbFactory) { this.dbFactory = dbFactory; Reload(); } public void Reload() { using var db = dbFactory.CreateDbContext(); groups = db.Groups .Include(g => g.MemberOf) .ToArray(); acls = db.Acls .Include(a => a.Rules) .ThenInclude(r => r.Principal) .ToArray(); } public IEnumerable Filter( IEnumerable objects, HBPrincipal principal, ulong permissions) { foreach(var obj in objects) { var perms = GetPermissions(obj.Acl, principal); if((perms & permissions) == permissions) yield return obj; } } public IEnumerable Filter( IEnumerable objects, HBPrincipal principal, T permissions) where T : Enum => Filter(objects, principal, permissions); private ulong GetPermissions(Acl? acl, HBPrincipal principal) { if(acl is null) return ulong.MaxValue; bool hasAllowRules = acl.Rules .Any(r => r.Action == AclRuleAction.Allow); ulong permissions = hasAllowRules ? 0 : ulong.MaxValue; var principals = GetGroupMemberShip(principal) .Cast() .Concat(new[] { principal }) .ToArray(); acl.Rules.IntersectBy(principals, r => r.Principal); foreach(var rule in acl.Rules) { if(!principals.Contains(rule.Principal)) continue; if(rule.Action == AclRuleAction.Allow) permissions |= rule.Permissions; else permissions &= ~rule.Permissions; } return permissions; } private List GetGroupMemberShip(HBPrincipal principal) { var groups = principal.MemberOf.ToList(); while(true) { var toAdd = this.groups .Where(g => !groups.Contains(g)) .ToArray(); if(toAdd.Count() == 0) break; groups.AddRange(toAdd); } return groups; } }