diff options
| author | Jake Mannens <jake@asger.xyz> | 2023-09-29 16:20:23 +1000 |
|---|---|---|
| committer | Jake Mannens <jake@asger.xyz> | 2023-09-29 16:28:00 +1000 |
| commit | c5ff0b57a12b605a5ae5ae8a92ce7a4e8eaec77a (patch) | |
| tree | a59c3ef10b277a7733a274f107472dc8f00cfc9a | |
| parent | 76e4bf609c3d196bd20619188a317fca66f4a04a (diff) | |
Separated HBPrincipal into IPrincipal and LocalPrincipal
| -rw-r--r-- | HBContext.cs | 9 | ||||
| -rw-r--r-- | LocalPrincipal.cs | 48 | ||||
| -rw-r--r-- | Principal.cs | 12 | ||||
| -rw-r--r-- | PrincipalProviders/LocalPrincipalProvider.cs | 69 | ||||
| -rw-r--r-- | Services/PrincipalProvider.cs | 24 | ||||
| -rw-r--r-- | Services/SecurityService.cs | 54 | ||||
| -rw-r--r-- | Services/UserService.cs | 13 |
7 files changed, 90 insertions, 139 deletions
diff --git a/HBContext.cs b/HBContext.cs index 2d44bc5..705efe4 100644 --- a/HBContext.cs +++ b/HBContext.cs @@ -1,18 +1,17 @@ using Microsoft.EntityFrameworkCore; using HyperBooru.Services; +using HyperBooru.PrincipalProviders; namespace HyperBooru; enum HBObjectId { NsfwTag = -1, IngestTag = -2, - AdminUser = -3 } public static class HBObjectGuid { public static readonly Guid NsfwTag = new("EBDAD4F8-455A-4351-8017-1D4854D6FA38"); public static readonly Guid IngestTag = new("EA212801-5BCC-4C0E-814F-FB9D30DB58BC"); - public static readonly Guid AdminUser = new("0623F137-F9D7-456C-A79F-C866B556FBF6"); } public class HBContext : DbContext { @@ -50,7 +49,7 @@ public class HBContext : DbContext { modelBuilder.Entity<Tag>().ToTable("Tags"); modelBuilder.Entity<Media>().ToTable("Media"); modelBuilder.Entity<UploadedFile>().ToTable("UploadedFiles"); - modelBuilder.Entity<Principal>().ToTable("SecurityPrincipals"); + modelBuilder.Entity<LocalPrincipal>().ToTable("SecurityPrincipals"); // Seed internal tag definitions // These should NEVER change @@ -72,11 +71,9 @@ public class HBContext : DbContext { // Seed initial admin user modelBuilder.Entity<LocalUser>().HasData(new LocalUser[] { new() { - ObjectId = (int) HBObjectId.AdminUser, - Guid = HBObjectGuid.AdminUser, Name = "admin", Sid = new SecurityIdentifier("S-1-5-18"), - PasswordHash = UserService.HashPassword("admin") + PasswordHash = LocalPrincipalProvider.HashPassword("admin") } }); diff --git a/LocalPrincipal.cs b/LocalPrincipal.cs index 28a2721..36428ef 100644 --- a/LocalPrincipal.cs +++ b/LocalPrincipal.cs @@ -1,49 +1,17 @@ -namespace HyperBooru; +using Microsoft.EntityFrameworkCore; -public class LocalPrincipal : HBObject { +namespace HyperBooru; + +[Index(nameof(Name))] +[Index(nameof(Sid))] +public class LocalPrincipal : IPrincipal { public string Name { get; set; } public SecurityIdentifier Sid { get; set; } public List<LocalGroup> MemberOf { get; set; } - - public static implicit operator Principal(LocalPrincipal principal) => - new() { - Name = principal.Name, - Sid = principal.Sid, - }; - - public static implicit operator LocalPrincipal(Principal principal) => - new() { - Name = principal.Name, - Sid = principal.Sid - }; } -public class LocalUser : LocalPrincipal { +public class LocalUser : LocalPrincipal, IUser { public string PasswordHash { get; set; } - - public static implicit operator User(LocalUser user) => - new() { - Name = user.Name, - Sid = user.Sid, - }; - - public static implicit operator LocalUser(User user) => - new() { - Name = user.Name, - Sid = user.Sid - }; } -public class LocalGroup : LocalPrincipal { - public static implicit operator Group(LocalGroup group) => - new() { - Name = group.Name, - Sid = group.Sid, - }; - - public static implicit operator LocalGroup(Group group) => - new() { - Name = group.Name, - Sid = group.Sid - }; -} +public class LocalGroup : LocalPrincipal, IGroup {} diff --git a/Principal.cs b/Principal.cs index 677f926..3e1a607 100644 --- a/Principal.cs +++ b/Principal.cs @@ -1,14 +1,10 @@ -using Microsoft.EntityFrameworkCore; -using System.Security.Principal; +namespace HyperBooru; -namespace HyperBooru; - -[Index(nameof(Name))] -public class Principal { +public interface IPrincipal { public string Name { get; set; } public SecurityIdentifier Sid { get; set; } } -public class User : Principal {} +public interface IUser : IPrincipal {} -public class Group : Principal {}
\ No newline at end of file +public interface IGroup : IPrincipal {}
\ No newline at end of file diff --git a/PrincipalProviders/LocalPrincipalProvider.cs b/PrincipalProviders/LocalPrincipalProvider.cs index 7bee800..8035ce8 100644 --- a/PrincipalProviders/LocalPrincipalProvider.cs +++ b/PrincipalProviders/LocalPrincipalProvider.cs @@ -1,4 +1,5 @@ using HyperBooru.Services; +using Microsoft.AspNetCore.Cryptography.KeyDerivation; using Microsoft.EntityFrameworkCore; namespace HyperBooru.PrincipalProviders; @@ -9,41 +10,63 @@ public class LocalPrincipalProvider : PrincipalProvider { public LocalPrincipalProvider(IDbContextFactory<HBContext> dbFactory) => this.dbFactory = dbFactory; - public override Principal? GetPrincipal(string name) { + public override IPrincipal? GetPrincipal(string name) { using var db = dbFactory.CreateDbContext(); + return db.Principals.FirstOrDefault(p => p.Name == name); + } - LocalPrincipal? principal = db.Principals.FirstOrDefault(p => p.Name == name); - if(principal is null) - return null; + public override IUser? GetUser(string name) { + using var db = dbFactory.CreateDbContext(); + return db.Users.FirstOrDefault(p => p.Name == name); + } - return principal; + public override IGroup? GetGroup(string name) { + using var db = dbFactory.CreateDbContext(); + return db.Groups.FirstOrDefault(p => p.Name == name); } - public override User? GetUser(string name) { + public override IGroup[] GetGroups(IPrincipal principal, bool recurse) { using var db = dbFactory.CreateDbContext(); - LocalUser? user = db.Users.FirstOrDefault(p => p.Name == name); - if(user is null) - return null; + List<LocalGroup> groups = db.Principals + .First(p => p.Sid == principal.Sid) + .MemberOf; - return user; - } + if(!recurse) + return groups.ToArray(); - public override Group? GetGroup(string name) { - using var db = dbFactory.CreateDbContext(); + var allGroups = db.Groups + .Include(g => g.MemberOf) + .ToArray(); - LocalGroup? group = db.Groups.FirstOrDefault(p => p.Name == name); - if(group is null) - return null; + groups = allGroups + .IntersectBy(groups.Select(g => g.Sid), g => g.Sid) + .ToList(); - return group; - } + while(true) { + var toAdd = groups + .SelectMany(g => g.MemberOf) + .ExceptBy(groups.Select(g => g.Sid), g => g.Sid) + .ToArray(); - public override Group[] GetGroups(Principal principal, bool recurse) { - throw new NotImplementedException(); - } + if(toAdd.Count() == 0) + break; + + groups.AddRange(toAdd); + } - public override bool ValidatePassword(User principal, string password) { - throw new NotImplementedException(); + return groups.ToArray(); } + + public override bool ValidatePassword(IUser user, string password) => + ((LocalUser) user).PasswordHash == HashPassword(password); + + public static string HashPassword(string password) => + Convert.ToBase64String( + KeyDerivation.Pbkdf2( + password, + Array.Empty<byte>(), + KeyDerivationPrf.HMACSHA512, + 100_000, + 512 / 8)); } diff --git a/Services/PrincipalProvider.cs b/Services/PrincipalProvider.cs index 6991c64..0c35007 100644 --- a/Services/PrincipalProvider.cs +++ b/Services/PrincipalProvider.cs @@ -1,23 +1,23 @@ namespace HyperBooru.Services; public interface IPrincipalProvider { - public Principal? GetPrincipal(string name); - public User? GetUser(string name); - public Group? GetGroup(string name); + public IPrincipal? GetPrincipal(string name); + public IUser? GetUser(string name); + public IGroup? GetGroup(string name); - public Group[] GetGroups(Principal principal); - public Group[] GetGroups(Principal principal, bool recurse); + public IGroup[] GetGroups(IPrincipal principal); + public IGroup[] GetGroups(IPrincipal principal, bool recurse); - public bool ValidatePassword(User user, string password); + public bool ValidatePassword(IUser user, string password); } public abstract class PrincipalProvider : IPrincipalProvider { - public abstract Principal? GetPrincipal(string name); - public abstract User? GetUser(string name); - public abstract Group? GetGroup(string name); + public abstract IPrincipal? GetPrincipal(string name); + public abstract IUser? GetUser(string name); + public abstract IGroup? GetGroup(string name); - public Group[] GetGroups(Principal principal) => GetGroups(principal, false); - public abstract Group[] GetGroups(Principal principal, bool recurse); + public IGroup[] GetGroups(IPrincipal principal) => GetGroups(principal, false); + public abstract IGroup[] GetGroups(IPrincipal principal, bool recurse); - public abstract bool ValidatePassword(User user, string password); + public abstract bool ValidatePassword(IUser user, string password); } diff --git a/Services/SecurityService.cs b/Services/SecurityService.cs index 8c97c7b..48f2d3e 100644 --- a/Services/SecurityService.cs +++ b/Services/SecurityService.cs @@ -7,21 +7,23 @@ namespace HyperBooru.Services; public class SecurityService { private IDbContextFactory<HBContext> dbFactory; - private MemoryCache<SidStruct, Principal> principalCache; - private MemoryCache<int, Acl> aclCache; + private MemoryCache<SidStruct, IGroup[]> membershipCache; + private MemoryCache<int, Acl> aclCache; - public SecurityService(IDbContextFactory<HBContext> dbFactory) { - this.dbFactory = dbFactory; + IPrincipalProvider principalProvider; + + public SecurityService( + IDbContextFactory<HBContext> dbFactory, + IPrincipalProvider principalProvider) { + + this.dbFactory = dbFactory; + this.principalProvider = principalProvider; // TODO: preload the principal cache - principalCache = new() { - MaxItems = 10_000, + membershipCache = new() { + MaxItems = 1000, 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)); } }; @@ -39,7 +41,7 @@ public class SecurityService { public IEnumerable<HBObject> Filter( IEnumerable<HBObject> objects, - Principal principal, + IPrincipal principal, ulong permissions) { foreach(var obj in objects) { @@ -51,7 +53,7 @@ public class SecurityService { public IEnumerable<HBObject> Filter<T>( IEnumerable<HBObject> objects, - Principal principal, + IPrincipal principal, T permissions) where T : Enum => Filter(objects, principal, permissions); @@ -62,14 +64,14 @@ public class SecurityService { /// <param name="acl"> /// ACL to resolve (returns a bitmask consisting of all 1's if this field is null) /// </param> - private ulong GetPermissions(Acl? acl, Principal principal) { + private ulong GetPermissions(Acl? acl, IPrincipal principal) { if(acl is null) return ulong.MaxValue; ulong permissions = 0; var principals = GetGroupMemberShip(principal) - .Cast<Principal>() + .Cast<IPrincipal>() .Concat(new[] { principal }) .Select(p => p.Sid) .ToArray(); @@ -91,28 +93,4 @@ public class SecurityService { return permissions; } - - /// <summary> - /// Recursively get all groups of which the specified principal - /// is a member, including implicit memberships. - /// </summary> - private List<Group> GetGroupMemberShip(Principal 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; - } } diff --git a/Services/UserService.cs b/Services/UserService.cs index c333c4f..d2abea3 100644 --- a/Services/UserService.cs +++ b/Services/UserService.cs @@ -1,6 +1,4 @@ -using Microsoft.AspNetCore.Cryptography.KeyDerivation; - -namespace HyperBooru.Services; +namespace HyperBooru.Services; public interface IUserService { public bool ShowNsfw { get; set; } @@ -20,13 +18,4 @@ public class UserService : IUserService { public event EventHandler<bool> ShowNsfwChanged; private bool showNsfw = false; - - public static string HashPassword(string password) => - Convert.ToBase64String( - KeyDerivation.Pbkdf2( - password, - Array.Empty<byte>(), - KeyDerivationPrf.HMACSHA512, - 100_000, - 512 / 8)); } |
