using HyperBooru.Util; using Microsoft.EntityFrameworkCore; using System.Data; namespace HyperBooru.Services; public class SecurityService { private IDbContextFactory dbFactory; private MemoryCache principalCache; private MemoryCache aclCache; public SecurityService(IDbContextFactory dbFactory) { this.dbFactory = dbFactory; // TODO: preload the principal cache principalCache = new() { MaxItems = 10_000, MaxAge = TimeSpan.FromMinutes(10), DataSource = (SidStruct sid) => { using var db = dbFactory.CreateDbContext(); return db.Principals .Include(p => p.MemberOf) .FirstOrDefault(p => p.Sid.SidStruct.Equals(sid)); } }; aclCache = new() { MaxItems = 1000, MaxAge = TimeSpan.FromMinutes(10), DataSource = (int id) => { using var db = dbFactory.CreateDbContext(); return db.Acls .Include(a => a.Rules) .FirstOrDefault(a => a.AclId == id); } }; } 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); /// /// Resolve the specified ACL and return a bitmask representing /// all the permissions the specified principal has. /// /// /// ACL to resolve (returns a bitmask consisting of all 1's if this field is null) /// private ulong GetPermissions(Acl? acl, HBPrincipal principal) { if(acl is null) return ulong.MaxValue; ulong permissions = 0; var principals = GetGroupMemberShip(principal) .Cast() .Concat(new[] { principal }) .Select(p => p.Sid) .ToArray(); var allowRules = acl.Rules.Where(r => r.Action == AclRuleAction.Allow); var denyRules = acl.Rules.Where(r => r.Action == AclRuleAction.Deny); foreach(var rule in allowRules) { if(!principals.Contains(rule.Principal)) continue; permissions |= rule.Permissions; } foreach(var rule in denyRules) { if(!principals.Contains(rule.Principal)) continue; permissions &= ~rule.Permissions; } return permissions; } /// /// Recursively get all groups of which the specified principal /// is a member, including implicit memberships. /// private List GetGroupMemberShip(HBPrincipal principal) { var groups = principal.MemberOf.ToList(); while(true) { var toAdd = groups .SelectMany(g => g.MemberOf) .Select(g => g.Sid.SidStruct) .Where(sid => !groups.Select(g => g.Sid.SidStruct).Contains(sid)) .ToArray(); if(toAdd.Count() == 0) break; foreach(var sid in toAdd) groups.Add((Group) principalCache[sid]); } return groups; } }