summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2023-09-29 16:20:23 +1000
committerJake Mannens <jake@asger.xyz>2023-09-29 16:28:00 +1000
commitc5ff0b57a12b605a5ae5ae8a92ce7a4e8eaec77a (patch)
treea59c3ef10b277a7733a274f107472dc8f00cfc9a
parent76e4bf609c3d196bd20619188a317fca66f4a04a (diff)
Separated HBPrincipal into IPrincipal and LocalPrincipal
-rw-r--r--HBContext.cs9
-rw-r--r--LocalPrincipal.cs48
-rw-r--r--Principal.cs12
-rw-r--r--PrincipalProviders/LocalPrincipalProvider.cs69
-rw-r--r--Services/PrincipalProvider.cs24
-rw-r--r--Services/SecurityService.cs54
-rw-r--r--Services/UserService.cs13
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));
}