summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJake Mannens <jake@asger.xyz>2023-09-28 03:14:35 +1000
committerJake Mannens <jake@asger.xyz>2023-09-28 03:14:35 +1000
commitbedcb6b176130fc2c6bd4657c8af4d407b64c970 (patch)
tree974a14bb03913e3a8083a633d9de61742c86e0a1
parentbc82b2dc2f7405c0fd4d179830412ea8209137b1 (diff)
Updated DB schema and configured ACLs to use SIDs
-rw-r--r--Acl.cs14
-rw-r--r--HBContext.cs18
-rw-r--r--HBObject.cs10
-rw-r--r--Migrations/20230920052204_Security.Designer.cs455
-rw-r--r--Migrations/20230920052204_Security.cs194
-rw-r--r--Migrations/HBContextModelSnapshot.cs125
-rw-r--r--Pages/Component/TagSelectDialog.razor2
-rw-r--r--Pages/Gallery.razor2
-rw-r--r--Pages/TagDefinitions.razor8
-rw-r--r--Principal.cs6
-rw-r--r--SecurityIdentifier.cs129
-rw-r--r--Services/MediaService.cs8
-rw-r--r--Services/PrincipalProvider.cs9
-rw-r--r--Services/SecurityService.cs37
14 files changed, 215 insertions, 802 deletions
diff --git a/Acl.cs b/Acl.cs
index 297144e..8beff0c 100644
--- a/Acl.cs
+++ b/Acl.cs
@@ -7,7 +7,9 @@ public enum AclRuleAction {
Deny
}
-public class Acl : HBObject {
+public class Acl {
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int AclId { get; set; }
[ForeignKey("ObjectId")]
public HBObject Subject { get; set; }
public List<AclRule> Rules { get; set; }
@@ -22,11 +24,13 @@ public class Acl<T> : Acl where T : Enum {
}
}
-public class AclRule : HBObject {
- public HBPrincipal Principal { get; set; }
- public AclRuleAction Action { get; set; }
+public class AclRule {
+ [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
+ public int AclRuleId { get; set; }
+ public SecurityIdentifier Principal { get; set; }
+ public AclRuleAction Action { get; set; }
[Column(TypeName = "bigint")]
- public ulong Permissions { get; set; }
+ public ulong Permissions { get; set; }
}
public class AclRule<T> : AclRule where T : Enum {
diff --git a/HBContext.cs b/HBContext.cs
index 4334495..2bb477e 100644
--- a/HBContext.cs
+++ b/HBContext.cs
@@ -9,10 +9,13 @@ enum HBObjectId {
AdminUser = -3
}
-public class HBContext : DbContext {
+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 {
public DbSet<HBObject> Objects { get; set; }
public DbSet<TagDefinition> TagDefinitions { get; set; }
public DbSet<Tag> Tags { get; set; }
@@ -47,19 +50,20 @@ public class HBContext : DbContext {
modelBuilder.Entity<Tag>().ToTable("Tags");
modelBuilder.Entity<Media>().ToTable("Media");
modelBuilder.Entity<UploadedFile>().ToTable("UploadedFiles");
+ modelBuilder.Entity<HBPrincipal>().ToTable("SecurityPrincipals");
// Seed internal tag definitions
// These should NEVER change
modelBuilder.Entity<TagDefinition>().HasData(new TagDefinition[] {
new() {
ObjectId = (int) HBObjectId.NsfwTag,
- Guid = NsfwTag,
+ Guid = HBObjectGuid.NsfwTag,
Source = TagSource.Internal,
Name = "nsfw"
},
new() {
ObjectId = (int) HBObjectId.IngestTag,
- Guid = IngestTag,
+ Guid = HBObjectGuid.IngestTag,
Source = TagSource.Internal,
Name = "ingest"
}
@@ -69,7 +73,9 @@ public class HBContext : DbContext {
modelBuilder.Entity<User>().HasData(new User[] {
new() {
ObjectId = (int) HBObjectId.AdminUser,
+ Guid = HBObjectGuid.AdminUser,
Name = "admin",
+ Sid = new SecurityIdentifier("S-1-5-18"),
PasswordHash = UserService.HashPassword("admin")
}
});
@@ -85,4 +91,10 @@ public class HBContext : DbContext {
.WithOne()
.HasForeignKey<Media>("CurrentUploadedFileId");
}
+
+ protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder) {
+ configurationBuilder
+ .Properties<SecurityIdentifier>()
+ .HaveConversion<SecurityIdentifierConverter>();
+ }
} \ No newline at end of file
diff --git a/HBObject.cs b/HBObject.cs
index ba1c226..2962adf 100644
--- a/HBObject.cs
+++ b/HBObject.cs
@@ -8,8 +8,10 @@ namespace HyperBooru;
public class HBObject {
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
- public int ObjectId { get; set; }
- public Guid Guid { get; set; } = Guid.NewGuid();
- public virtual List<Tag> Tags { get; set; } = new();
- public Acl? Acl { get; set; }
+ public int ObjectId { get; set; }
+ public Guid Guid { get; set; } = Guid.NewGuid();
+ public virtual List<Tag> Tags { get; set; } = new();
+ public Acl? Acl { get; set; }
+ public SecurityIdentifier Owner { get; set; } =
+ new SecurityIdentifier(WellKnownSidType.WorldSid);
} \ No newline at end of file
diff --git a/Migrations/20230920052204_Security.Designer.cs b/Migrations/20230920052204_Security.Designer.cs
deleted file mode 100644
index 8640c30..0000000
--- a/Migrations/20230920052204_Security.Designer.cs
+++ /dev/null
@@ -1,455 +0,0 @@
-// <auto-generated />
-using System;
-using HyperBooru;
-using Microsoft.EntityFrameworkCore;
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
-using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
-
-#nullable disable
-
-namespace HyperBooru.Migrations
-{
- [DbContext(typeof(HBContext))]
- [Migration("20230920052204_Security")]
- partial class Security
- {
- /// <inheritdoc />
- protected override void BuildTargetModel(ModelBuilder modelBuilder)
- {
-#pragma warning disable 612, 618
- modelBuilder
- .HasAnnotation("ProductVersion", "7.0.10")
- .HasAnnotation("Relational:MaxIdentifierLength", 63);
-
- NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
-
- modelBuilder.Entity("HyperBooru.HBObject", b =>
- {
- b.Property<int>("ObjectId")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ObjectId"));
-
- b.Property<Guid>("Guid")
- .HasColumnType("uuid");
-
- b.HasKey("ObjectId");
-
- b.HasIndex("Guid");
-
- b.ToTable("Objects", (string)null);
-
- b.UseTptMappingStrategy();
- });
-
- modelBuilder.Entity("HyperBooru.OcrData", b =>
- {
- b.Property<int>("OcrDataId")
- .ValueGeneratedOnAdd()
- .HasColumnType("integer");
-
- NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("OcrDataId"));
-
- b.Property<int>("MediaId")
- .HasColumnType("integer");
-
- b.Property<string>("SearchableText")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<string>("Text")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<DateTime>("Timestamp")
- .HasColumnType("timestamp with time zone");
-
- b.HasKey("OcrDataId");
-
- b.HasIndex("MediaId")
- .IsUnique();
-
- b.ToTable("OcrData");
- });
-
- modelBuilder.Entity("TagDefinitionTagDefinition", b =>
- {
- b.Property<int>("ImplicitTagsObjectId")
- .HasColumnType("integer");
-
- b.Property<int>("TagDefinitionObjectId")
- .HasColumnType("integer");
-
- b.HasKey("ImplicitTagsObjectId", "TagDefinitionObjectId");
-
- b.HasIndex("TagDefinitionObjectId");
-
- b.ToTable("TagDefinitionTagDefinition");
- });
-
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<int?>("SubjectTempId1")
- .HasColumnType("integer");
-
- b.ToTable("Acls");
- });
-
- modelBuilder.Entity("HyperBooru.AclRule", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<int?>("AclObjectId")
- .HasColumnType("integer");
-
- b.Property<int>("Action")
- .HasColumnType("integer");
-
- b.Property<long>("Permissions")
- .HasColumnType("bigint");
-
- b.Property<int>("PrincipalObjectId")
- .HasColumnType("integer");
-
- b.HasIndex("AclObjectId");
-
- b.HasIndex("PrincipalObjectId");
-
- b.ToTable("AclRules");
- });
-
- modelBuilder.Entity("HyperBooru.HBPrincipal", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnType("text");
-
- b.HasIndex("Name");
-
- b.ToTable("Principals");
- });
-
- modelBuilder.Entity("HyperBooru.Media", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<int>("CurrentUploadedFileId")
- .HasColumnType("integer");
-
- b.Property<string>("LongDescription")
- .HasColumnType("text");
-
- b.Property<string>("ShortDescription")
- .HasColumnType("text");
-
- b.HasIndex("CurrentUploadedFileId")
- .IsUnique();
-
- b.ToTable("Media", (string)null);
- });
-
- modelBuilder.Entity("HyperBooru.Tag", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<DateTime>("CreateTime")
- .HasColumnType("timestamp with time zone");
-
- b.Property<int>("TagDefinitionId")
- .HasColumnType("integer");
-
- b.Property<int>("TargetObjectId")
- .HasColumnType("integer");
-
- b.HasIndex("TagDefinitionId");
-
- b.HasIndex("TargetObjectId");
-
- b.ToTable("Tags", (string)null);
- });
-
- modelBuilder.Entity("HyperBooru.TagDefinition", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<string>("Alias")
- .HasColumnType("text");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<string>("Namespace")
- .HasColumnType("text");
-
- b.Property<int>("Source")
- .HasColumnType("integer");
-
- b.ToTable("TagDefinitions", (string)null);
-
- b.HasData(
- new
- {
- ObjectId = -1,
- Guid = new Guid("ebdad4f8-455a-4351-8017-1d4854d6fa38"),
- Name = "nsfw",
- Source = 0
- },
- new
- {
- ObjectId = -2,
- Guid = new Guid("ea212801-5bcc-4c0e-814f-fb9d30db58bc"),
- Name = "ingest",
- Source = 0
- });
- });
-
- modelBuilder.Entity("HyperBooru.UploadedFile", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<string>("Checksum")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<bool>("ChecksumVerified")
- .HasColumnType("boolean");
-
- b.Property<DateTime?>("CreateTime")
- .HasColumnType("timestamp with time zone");
-
- b.Property<string>("Filename")
- .HasColumnType("text");
-
- b.Property<int?>("Height")
- .HasColumnType("integer");
-
- b.Property<DateTime?>("LastAccessTime")
- .HasColumnType("timestamp with time zone");
-
- b.Property<DateTime?>("LastWriteTime")
- .HasColumnType("timestamp with time zone");
-
- b.Property<long>("Length")
- .HasColumnType("bigint");
-
- b.Property<int>("MediaObjectId")
- .HasColumnType("integer");
-
- b.Property<string>("MimeType")
- .IsRequired()
- .HasColumnType("text");
-
- b.Property<DateTime>("UploadTime")
- .HasColumnType("timestamp with time zone");
-
- b.Property<int?>("Width")
- .HasColumnType("integer");
-
- b.HasIndex("MediaObjectId");
-
- b.ToTable("UploadedFiles", (string)null);
- });
-
- modelBuilder.Entity("HyperBooru.Group", b =>
- {
- b.HasBaseType("HyperBooru.HBPrincipal");
-
- b.Property<int?>("HBPrincipalObjectId")
- .HasColumnType("integer");
-
- b.HasIndex("HBPrincipalObjectId");
-
- b.ToTable("Groups");
- });
-
- modelBuilder.Entity("HyperBooru.User", b =>
- {
- b.HasBaseType("HyperBooru.HBPrincipal");
-
- b.Property<string>("PasswordHash")
- .IsRequired()
- .HasColumnType("text");
-
- b.ToTable("Users");
-
- b.HasData(
- new
- {
- ObjectId = -3,
- Guid = new Guid("8fbbc2e9-0609-460a-ab3a-c3bdd10ab793"),
- Name = "admin",
- PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw=="
- });
- });
-
- modelBuilder.Entity("HyperBooru.OcrData", b =>
- {
- b.HasOne("HyperBooru.Media", "Media")
- .WithOne("OcrData")
- .HasForeignKey("HyperBooru.OcrData", "MediaId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Media");
- });
-
- modelBuilder.Entity("TagDefinitionTagDefinition", b =>
- {
- b.HasOne("HyperBooru.TagDefinition", null)
- .WithMany()
- .HasForeignKey("ImplicitTagsObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("HyperBooru.TagDefinition", null)
- .WithMany()
- .HasForeignKey("TagDefinitionObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.HasOne("HyperBooru.HBObject", "Subject")
- .WithOne("Acl")
- .HasForeignKey("HyperBooru.Acl", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Subject");
- });
-
- modelBuilder.Entity("HyperBooru.AclRule", b =>
- {
- b.HasOne("HyperBooru.Acl", null)
- .WithMany("Rules")
- .HasForeignKey("AclObjectId");
-
- b.HasOne("HyperBooru.HBPrincipal", "Principal")
- .WithMany()
- .HasForeignKey("PrincipalObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Principal");
- });
-
- modelBuilder.Entity("HyperBooru.Media", b =>
- {
- b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile")
- .WithOne()
- .HasForeignKey("HyperBooru.Media", "CurrentUploadedFileId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("HyperBooru.HBObject", null)
- .WithOne()
- .HasForeignKey("HyperBooru.Media", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("CurrentUploadedFile");
- });
-
- modelBuilder.Entity("HyperBooru.Tag", b =>
- {
- b.HasOne("HyperBooru.HBObject", null)
- .WithOne()
- .HasForeignKey("HyperBooru.Tag", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("HyperBooru.TagDefinition", "TagDefinition")
- .WithMany()
- .HasForeignKey("TagDefinitionId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("HyperBooru.HBObject", "Target")
- .WithMany("Tags")
- .HasForeignKey("TargetObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("TagDefinition");
-
- b.Navigation("Target");
- });
-
- modelBuilder.Entity("HyperBooru.TagDefinition", b =>
- {
- b.HasOne("HyperBooru.HBObject", null)
- .WithOne()
- .HasForeignKey("HyperBooru.TagDefinition", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("HyperBooru.UploadedFile", b =>
- {
- b.HasOne("HyperBooru.Media", "Media")
- .WithMany("UploadedFiles")
- .HasForeignKey("MediaObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.HasOne("HyperBooru.HBObject", null)
- .WithOne()
- .HasForeignKey("HyperBooru.UploadedFile", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Media");
- });
-
- modelBuilder.Entity("HyperBooru.Group", b =>
- {
- b.HasOne("HyperBooru.HBPrincipal", null)
- .WithMany("MemberOf")
- .HasForeignKey("HBPrincipalObjectId");
- });
-
- modelBuilder.Entity("HyperBooru.User", b =>
- {
- b.HasOne("HyperBooru.HBPrincipal", null)
- .WithOne()
- .HasForeignKey("HyperBooru.User", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
- });
-
- modelBuilder.Entity("HyperBooru.HBObject", b =>
- {
- b.Navigation("Acl");
-
- b.Navigation("Tags");
- });
-
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.Navigation("Rules");
- });
-
- modelBuilder.Entity("HyperBooru.HBPrincipal", b =>
- {
- b.Navigation("MemberOf");
- });
-
- modelBuilder.Entity("HyperBooru.Media", b =>
- {
- b.Navigation("OcrData");
-
- b.Navigation("UploadedFiles");
- });
-#pragma warning restore 612, 618
- }
- }
-}
diff --git a/Migrations/20230920052204_Security.cs b/Migrations/20230920052204_Security.cs
deleted file mode 100644
index 982cb97..0000000
--- a/Migrations/20230920052204_Security.cs
+++ /dev/null
@@ -1,194 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Migrations;
-
-#nullable disable
-
-namespace HyperBooru.Migrations
-{
- /// <inheritdoc />
- public partial class Security : Migration
- {
- /// <inheritdoc />
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropForeignKey(
- name: "FK_Users_Objects_ObjectId",
- table: "Users");
-
- migrationBuilder.DropIndex(
- name: "IX_Users_Username",
- table: "Users");
-
- migrationBuilder.DropColumn(
- name: "Username",
- table: "Users");
-
- migrationBuilder.CreateTable(
- name: "Acls",
- columns: table => new
- {
- ObjectId = table.Column<int>(type: "integer", nullable: false),
- SubjectTempId1 = table.Column<int>(type: "integer", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Acls", x => x.ObjectId);
- table.ForeignKey(
- name: "FK_Acls_Objects_ObjectId",
- column: x => x.ObjectId,
- principalTable: "Objects",
- principalColumn: "ObjectId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "Principals",
- columns: table => new
- {
- ObjectId = table.Column<int>(type: "integer", nullable: false),
- Name = table.Column<string>(type: "text", nullable: false)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Principals", x => x.ObjectId);
- });
-
- migrationBuilder.CreateTable(
- name: "AclRules",
- columns: table => new
- {
- ObjectId = table.Column<int>(type: "integer", nullable: false),
- PrincipalObjectId = table.Column<int>(type: "integer", nullable: false),
- Action = table.Column<int>(type: "integer", nullable: false),
- Permissions = table.Column<long>(type: "bigint", nullable: false),
- AclObjectId = table.Column<int>(type: "integer", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_AclRules", x => x.ObjectId);
- table.ForeignKey(
- name: "FK_AclRules_Acls_AclObjectId",
- column: x => x.AclObjectId,
- principalTable: "Acls",
- principalColumn: "ObjectId");
- table.ForeignKey(
- name: "FK_AclRules_Principals_PrincipalObjectId",
- column: x => x.PrincipalObjectId,
- principalTable: "Principals",
- principalColumn: "ObjectId",
- onDelete: ReferentialAction.Cascade);
- });
-
- migrationBuilder.CreateTable(
- name: "Groups",
- columns: table => new
- {
- ObjectId = table.Column<int>(type: "integer", nullable: false),
- HBPrincipalObjectId = table.Column<int>(type: "integer", nullable: true)
- },
- constraints: table =>
- {
- table.PrimaryKey("PK_Groups", x => x.ObjectId);
- table.ForeignKey(
- name: "FK_Groups_Principals_HBPrincipalObjectId",
- column: x => x.HBPrincipalObjectId,
- principalTable: "Principals",
- principalColumn: "ObjectId");
- });
-
- migrationBuilder.UpdateData(
- table: "Objects",
- keyColumn: "ObjectId",
- keyValue: -3,
- column: "Guid",
- value: new Guid("8fbbc2e9-0609-460a-ab3a-c3bdd10ab793"));
-
- migrationBuilder.InsertData(
- table: "Principals",
- columns: new[] { "ObjectId", "Name" },
- values: new object[] { -3, "admin" });
-
- migrationBuilder.CreateIndex(
- name: "IX_AclRules_AclObjectId",
- table: "AclRules",
- column: "AclObjectId");
-
- migrationBuilder.CreateIndex(
- name: "IX_AclRules_PrincipalObjectId",
- table: "AclRules",
- column: "PrincipalObjectId");
-
- migrationBuilder.CreateIndex(
- name: "IX_Groups_HBPrincipalObjectId",
- table: "Groups",
- column: "HBPrincipalObjectId");
-
- migrationBuilder.CreateIndex(
- name: "IX_Principals_Name",
- table: "Principals",
- column: "Name");
-
- migrationBuilder.AddForeignKey(
- name: "FK_Users_Principals_ObjectId",
- table: "Users",
- column: "ObjectId",
- principalTable: "Principals",
- principalColumn: "ObjectId",
- onDelete: ReferentialAction.Cascade);
- }
-
- /// <inheritdoc />
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- migrationBuilder.DropForeignKey(
- name: "FK_Users_Principals_ObjectId",
- table: "Users");
-
- migrationBuilder.DropTable(
- name: "AclRules");
-
- migrationBuilder.DropTable(
- name: "Groups");
-
- migrationBuilder.DropTable(
- name: "Acls");
-
- migrationBuilder.DropTable(
- name: "Principals");
-
- migrationBuilder.AddColumn<string>(
- name: "Username",
- table: "Users",
- type: "text",
- nullable: false,
- defaultValue: "");
-
- migrationBuilder.UpdateData(
- table: "Objects",
- keyColumn: "ObjectId",
- keyValue: -3,
- column: "Guid",
- value: new Guid("4fa948f4-7c45-4f81-bb6b-e417491e6c96"));
-
- migrationBuilder.UpdateData(
- table: "Users",
- keyColumn: "ObjectId",
- keyValue: -3,
- column: "Username",
- value: "admin");
-
- migrationBuilder.CreateIndex(
- name: "IX_Users_Username",
- table: "Users",
- column: "Username");
-
- migrationBuilder.AddForeignKey(
- name: "FK_Users_Objects_ObjectId",
- table: "Users",
- column: "ObjectId",
- principalTable: "Objects",
- principalColumn: "ObjectId",
- onDelete: ReferentialAction.Cascade);
- }
- }
-}
diff --git a/Migrations/HBContextModelSnapshot.cs b/Migrations/HBContextModelSnapshot.cs
index 5514b9b..16e6b48 100644
--- a/Migrations/HBContextModelSnapshot.cs
+++ b/Migrations/HBContextModelSnapshot.cs
@@ -69,7 +69,7 @@ namespace HyperBooru.Migrations
b.HasIndex("MediaId")
.IsUnique();
- b.ToTable("OcrData");
+ b.ToTable("OcrData", (string)null);
});
modelBuilder.Entity("TagDefinitionTagDefinition", b =>
@@ -84,53 +84,7 @@ namespace HyperBooru.Migrations
b.HasIndex("TagDefinitionObjectId");
- b.ToTable("TagDefinitionTagDefinition");
- });
-
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<int?>("SubjectTempId1")
- .HasColumnType("integer");
-
- b.ToTable("Acls");
- });
-
- modelBuilder.Entity("HyperBooru.AclRule", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<int?>("AclObjectId")
- .HasColumnType("integer");
-
- b.Property<int>("Action")
- .HasColumnType("integer");
-
- b.Property<long>("Permissions")
- .HasColumnType("bigint");
-
- b.Property<int>("PrincipalObjectId")
- .HasColumnType("integer");
-
- b.HasIndex("AclObjectId");
-
- b.HasIndex("PrincipalObjectId");
-
- b.ToTable("AclRules");
- });
-
- modelBuilder.Entity("HyperBooru.HBPrincipal", b =>
- {
- b.HasBaseType("HyperBooru.HBObject");
-
- b.Property<string>("Name")
- .IsRequired()
- .HasColumnType("text");
-
- b.HasIndex("Name");
-
- b.ToTable("Principals");
+ b.ToTable("TagDefinitionTagDefinition", (string)null);
});
modelBuilder.Entity("HyperBooru.Media", b =>
@@ -255,35 +209,29 @@ namespace HyperBooru.Migrations
b.ToTable("UploadedFiles", (string)null);
});
- modelBuilder.Entity("HyperBooru.Group", b =>
- {
- b.HasBaseType("HyperBooru.HBPrincipal");
-
- b.Property<int?>("HBPrincipalObjectId")
- .HasColumnType("integer");
-
- b.HasIndex("HBPrincipalObjectId");
-
- b.ToTable("Groups");
- });
-
modelBuilder.Entity("HyperBooru.User", b =>
{
- b.HasBaseType("HyperBooru.HBPrincipal");
+ b.HasBaseType("HyperBooru.HBObject");
b.Property<string>("PasswordHash")
.IsRequired()
.HasColumnType("text");
- b.ToTable("Users");
+ b.Property<string>("Username")
+ .IsRequired()
+ .HasColumnType("text");
+
+ b.HasIndex("Username");
+
+ b.ToTable("Users", (string)null);
b.HasData(
new
{
ObjectId = -3,
- Guid = new Guid("8fbbc2e9-0609-460a-ab3a-c3bdd10ab793"),
- Name = "admin",
- PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw=="
+ Guid = new Guid("4fa948f4-7c45-4f81-bb6b-e417491e6c96"),
+ PasswordHash = "P4geAuE2yX/PDRHuJSq74FF5vO782rWz5c0LAQPR8m45DEYAONhu1wYnAn60PSNyjocqEBdnCeKCJfK3sKyuWw==",
+ Username = "admin"
});
});
@@ -313,32 +261,6 @@ namespace HyperBooru.Migrations
.IsRequired();
});
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.HasOne("HyperBooru.HBObject", "Subject")
- .WithOne("Acl")
- .HasForeignKey("HyperBooru.Acl", "ObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Subject");
- });
-
- modelBuilder.Entity("HyperBooru.AclRule", b =>
- {
- b.HasOne("HyperBooru.Acl", null)
- .WithMany("Rules")
- .HasForeignKey("AclObjectId");
-
- b.HasOne("HyperBooru.HBPrincipal", "Principal")
- .WithMany()
- .HasForeignKey("PrincipalObjectId")
- .OnDelete(DeleteBehavior.Cascade)
- .IsRequired();
-
- b.Navigation("Principal");
- });
-
modelBuilder.Entity("HyperBooru.Media", b =>
{
b.HasOne("HyperBooru.UploadedFile", "CurrentUploadedFile")
@@ -407,16 +329,9 @@ namespace HyperBooru.Migrations
b.Navigation("Media");
});
- modelBuilder.Entity("HyperBooru.Group", b =>
- {
- b.HasOne("HyperBooru.HBPrincipal", null)
- .WithMany("MemberOf")
- .HasForeignKey("HBPrincipalObjectId");
- });
-
modelBuilder.Entity("HyperBooru.User", b =>
{
- b.HasOne("HyperBooru.HBPrincipal", null)
+ b.HasOne("HyperBooru.HBObject", null)
.WithOne()
.HasForeignKey("HyperBooru.User", "ObjectId")
.OnDelete(DeleteBehavior.Cascade)
@@ -425,21 +340,9 @@ namespace HyperBooru.Migrations
modelBuilder.Entity("HyperBooru.HBObject", b =>
{
- b.Navigation("Acl");
-
b.Navigation("Tags");
});
- modelBuilder.Entity("HyperBooru.Acl", b =>
- {
- b.Navigation("Rules");
- });
-
- modelBuilder.Entity("HyperBooru.HBPrincipal", b =>
- {
- b.Navigation("MemberOf");
- });
-
modelBuilder.Entity("HyperBooru.Media", b =>
{
b.Navigation("OcrData");
diff --git a/Pages/Component/TagSelectDialog.razor b/Pages/Component/TagSelectDialog.razor
index 7be5c43..fadebfa 100644
--- a/Pages/Component/TagSelectDialog.razor
+++ b/Pages/Component/TagSelectDialog.razor
@@ -82,7 +82,7 @@
int[] nsfwTags = Array.Empty<int>();
if(!userService.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ nsfwTags = tagService.TagsThatImply(HBObjectGuid.NsfwTag)
.Select(td => td.ObjectId)
.ToArray();
diff --git a/Pages/Gallery.razor b/Pages/Gallery.razor
index d473c28..e779e5b 100644
--- a/Pages/Gallery.razor
+++ b/Pages/Gallery.razor
@@ -112,7 +112,7 @@
}
private IEnumerable<Media> FilterMedia(IEnumerable<Media> media) {
- var nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ var nsfwTags = tagService.TagsThatImply(HBObjectGuid.NsfwTag)
.Select(td => td.ObjectId)
.ToArray();
diff --git a/Pages/TagDefinitions.razor b/Pages/TagDefinitions.razor
index f728631..e4803eb 100644
--- a/Pages/TagDefinitions.razor
+++ b/Pages/TagDefinitions.razor
@@ -53,7 +53,7 @@
<a href="javascript:;" @onclick=@(() => PromptImplicitTags(tagDef))>
Implicit Tags
</a>
- @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(HBContext.NsfwTag)) {
+ @if(tagDef.ImplicitTags.Select(td => td.Guid).Contains(HBObjectGuid.NsfwTag)) {
<a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, false))>Make SFW</a>
} else {
<a href="javascript:;" @onclick=@(() => SetNsfw(tagDef, true))>Make NSFW</a>
@@ -102,7 +102,7 @@
private void LoadTags() {
int[] nsfwTags = Array.Empty<int>();
if(!userService.ShowNsfw)
- nsfwTags = tagService.TagsThatImply(HBContext.NsfwTag)
+ nsfwTags = tagService.TagsThatImply(HBObjectGuid.NsfwTag)
.Select(td => td.ObjectId)
.ToArray();
@@ -169,9 +169,9 @@
private void SetNsfw(TagDefinition tagDef, bool nsfw) {
if(nsfw)
- tagService.AddImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ tagService.AddImplicitTag(tagDef.Guid, HBObjectGuid.NsfwTag);
else
- tagService.RemoveImplicitTag(tagDef.Guid, HBContext.NsfwTag);
+ tagService.RemoveImplicitTag(tagDef.Guid, HBObjectGuid.NsfwTag);
LoadTags();
StateHasChanged();
}
diff --git a/Principal.cs b/Principal.cs
index 18b82d0..553fbec 100644
--- a/Principal.cs
+++ b/Principal.cs
@@ -1,11 +1,13 @@
using Microsoft.EntityFrameworkCore;
+using System.Security.Principal;
namespace HyperBooru;
[Index(nameof(Name))]
public class HBPrincipal : HBObject {
- public string Name { get; set; }
- public List<Group> MemberOf { get; set; }
+ public string Name { get; set; }
+ public SecurityIdentifier Sid { get; set; }
+ public List<Group> MemberOf { get; set; }
}
public class User : HBPrincipal {
diff --git a/SecurityIdentifier.cs b/SecurityIdentifier.cs
new file mode 100644
index 0000000..81d5ae7
--- /dev/null
+++ b/SecurityIdentifier.cs
@@ -0,0 +1,129 @@
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using System.Numerics;
+using System.Runtime.InteropServices;
+using System.Text.RegularExpressions;
+
+namespace HyperBooru;
+
+public enum WellKnownSidType {
+ NullSid,
+ WorldSid,
+ LocalSid,
+ CreatorOwnerSid,
+ CreatorGroupSid
+}
+
+public class SecurityIdentifier {
+ public SidStruct SidStruct { get; private init; }
+
+ private static readonly Regex SddlRegex =
+ new(@"^S(-[0-9]+){2,}$", RegexOptions.Compiled);
+
+ private static readonly Dictionary<WellKnownSidType, string> wellKnownSidTypes = new() {
+ { HyperBooru.WellKnownSidType.NullSid, "S-1-0-0" },
+ { HyperBooru.WellKnownSidType.WorldSid, "S-1-1-0" },
+ { HyperBooru.WellKnownSidType.LocalSid, "S-1-2-0" },
+ { HyperBooru.WellKnownSidType.CreatorOwnerSid, "S-1-3-0" },
+ { HyperBooru.WellKnownSidType.CreatorGroupSid, "S-1-3-1" }
+ };
+
+ public SecurityIdentifier(WellKnownSidType wellKnownSidType)
+ : this(wellKnownSidTypes[wellKnownSidType]) {}
+
+ public SecurityIdentifier(string sddlForm) {
+ var match = SddlRegex.Match(sddlForm);
+ if(!match.Success)
+ throw new ArgumentException();
+
+ var values = match.Groups[1].Captures
+ .Select(v => uint.Parse(v.Value.Replace("-", "")))
+ .ToArray();
+
+ // Extremely roundabound way of converting a 32-bit
+ // integer to a 48-bit big-endian integer
+ byte[] identifierAuthority = BitConverter.GetBytes(values[1]);
+ if(BitConverter.IsLittleEndian)
+ Array.Reverse(identifierAuthority);
+ identifierAuthority = new byte[2]
+ .Concat(identifierAuthority)
+ .ToArray();
+
+ SidStruct = new SidStruct {
+ Revision = (byte) values[0],
+ SubAuthorityCount = (byte) (values.Count() - 2),
+ IdentifierAuthority = identifierAuthority,
+ SubAuthorities = values.Skip(2).ToArray()
+ };
+ }
+
+ public SecurityIdentifier(SidStruct sidStruct) =>
+ SidStruct = sidStruct;
+
+ public SecurityIdentifier(byte[] binaryForm) {
+ IntPtr p = IntPtr.Zero;
+
+ try {
+ p = Marshal.AllocHGlobal(binaryForm.Length);
+ Marshal.Copy(binaryForm, 0, p, binaryForm.Length);
+ SidStruct = Marshal.PtrToStructure<SidStruct>(p);
+ } finally {
+ Marshal.FreeHGlobal(p);
+ }
+ }
+
+ public byte[] BinaryForm {
+ get {
+ var size = Marshal.SizeOf(typeof(SidStruct));
+ byte[] array = new byte[size];
+
+ IntPtr p = IntPtr.Zero;
+
+ try {
+ p = Marshal.AllocHGlobal(size);
+ Marshal.StructureToPtr(SidStruct, p, true);
+ Marshal.Copy(p, array, 0, size);
+ } finally {
+ Marshal.FreeHGlobal(p);
+ }
+
+ return array;
+ }
+ }
+
+ public override string ToString() {
+ var identifierAuthority = new BigInteger(SidStruct.IdentifierAuthority, true, true);
+ var subAuthorities = string.Join('-',SidStruct.SubAuthorities.Select(sa => sa.ToString()));
+ if(!string.IsNullOrEmpty(subAuthorities))
+ subAuthorities = "-" + subAuthorities;
+ return $"S-{SidStruct.Revision}-{identifierAuthority}{subAuthorities}";
+ }
+
+ public static bool operator ==(SecurityIdentifier x, SecurityIdentifier y) =>
+ x.SidStruct.Equals(y.SidStruct);
+
+ public static bool operator !=(SecurityIdentifier x, SecurityIdentifier y) =>
+ !(x == y);
+
+ public override bool Equals(object? obj) =>
+ obj is null ? false : this == (SecurityIdentifier) obj;
+
+ public override int GetHashCode() =>
+ SidStruct.GetHashCode();
+}
+
+[StructLayout(LayoutKind.Sequential)]
+public struct SidStruct {
+ public byte Revision;
+ public byte SubAuthorityCount;
+ [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)]
+ public byte[] IdentifierAuthority;
+ [MarshalAs(UnmanagedType.ByValArray)]
+ public uint[] SubAuthorities;
+}
+
+public class SecurityIdentifierConverter : ValueConverter<SecurityIdentifier, byte[]> {
+ public SecurityIdentifierConverter()
+ : base(
+ v => v.BinaryForm,
+ v => new SecurityIdentifier(v)) {}
+} \ No newline at end of file
diff --git a/Services/MediaService.cs b/Services/MediaService.cs
index c779d8e..abc026f 100644
--- a/Services/MediaService.cs
+++ b/Services/MediaService.cs
@@ -63,13 +63,13 @@ public class MediaService : IMediaService {
.ThenInclude(t => t.TagDefinition)
.First(m => m.Guid == media.Guid);
var ingestTag = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
+ .First(td => td.Guid == HBObjectGuid.IngestTag);
if(ingest) {
- if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(HBContext.IngestTag))
+ if(!media.Tags.Select(t => t.TagDefinition.Guid).Contains(HBObjectGuid.IngestTag))
media.Tags.Add(new(ingestTag));
} else {
- media.Tags.RemoveAll(t => t.TagDefinition.Guid == HBContext.IngestTag);
+ media.Tags.RemoveAll(t => t.TagDefinition.Guid == HBObjectGuid.IngestTag);
}
db.SaveChanges();
@@ -151,7 +151,7 @@ public class MediaService : IMediaService {
if(media is null) {
var ingestTagDef = db.TagDefinitions
- .First(td => td.Guid == HBContext.IngestTag);
+ .First(td => td.Guid == HBObjectGuid.IngestTag);
media = new() {
CurrentUploadedFile = fileRecord,
diff --git a/Services/PrincipalProvider.cs b/Services/PrincipalProvider.cs
new file mode 100644
index 0000000..e75c6c7
--- /dev/null
+++ b/Services/PrincipalProvider.cs
@@ -0,0 +1,9 @@
+namespace HyperBooru.Services;
+
+public abstract class PrincipalProvider {
+ public abstract bool ValidatePassword(HBPrincipal principal, string password);
+
+ public abstract HBPrincipal GetPrincipal(string username);
+
+ public abstract Group[] GetAllGroups(HBPrincipal principal);
+}
diff --git a/Services/SecurityService.cs b/Services/SecurityService.cs
index f0ebd70..f1444c1 100644
--- a/Services/SecurityService.cs
+++ b/Services/SecurityService.cs
@@ -7,7 +7,7 @@ namespace HyperBooru.Services;
public class SecurityService {
private IDbContextFactory<HBContext> dbFactory;
- private MemoryCache<int, HBPrincipal> principalCache;
+ private MemoryCache<SidStruct, HBPrincipal> principalCache;
private MemoryCache<int, Acl> aclCache;
public SecurityService(IDbContextFactory<HBContext> dbFactory) {
@@ -17,11 +17,11 @@ public class SecurityService {
principalCache = new() {
MaxItems = 10_000,
MaxAge = TimeSpan.FromMinutes(10),
- DataSource = (int id) => {
+ DataSource = (SidStruct sid) => {
using var db = dbFactory.CreateDbContext();
return db.Principals
.Include(p => p.MemberOf)
- .FirstOrDefault(p => p.ObjectId == id);
+ .FirstOrDefault(p => p.Sid.SidStruct.Equals(sid));
}
};
@@ -32,7 +32,7 @@ public class SecurityService {
using var db = dbFactory.CreateDbContext();
return db.Acls
.Include(a => a.Rules)
- .FirstOrDefault(a => a.ObjectId == id);
+ .FirstOrDefault(a => a.AclId == id);
}
};
}
@@ -66,26 +66,27 @@ public class SecurityService {
if(acl is null)
return ulong.MaxValue;
- bool hasAllowRules = acl.Rules
- .Any(r => r.Action == AclRuleAction.Allow);
-
- ulong permissions = hasAllowRules ? 0 : ulong.MaxValue;
+ ulong permissions = 0;
var principals = GetGroupMemberShip(principal)
.Cast<HBPrincipal>()
.Concat(new[] { principal })
+ .Select(p => p.Sid)
.ToArray();
- acl.Rules.IntersectBy(principals, r => r.Principal);
+ var allowRules = acl.Rules.Where(r => r.Action == AclRuleAction.Allow);
+ var denyRules = acl.Rules.Where(r => r.Action == AclRuleAction.Deny);
- foreach(var rule in acl.Rules) {
+ foreach(var rule in allowRules) {
if(!principals.Contains(rule.Principal))
continue;
+ permissions |= rule.Permissions;
+ }
- if(rule.Action == AclRuleAction.Allow)
- permissions |= rule.Permissions;
- else
- permissions &= ~rule.Permissions;
+ foreach(var rule in denyRules) {
+ if(!principals.Contains(rule.Principal))
+ continue;
+ permissions &= ~rule.Permissions;
}
return permissions;
@@ -101,15 +102,15 @@ public class SecurityService {
while(true) {
var toAdd = groups
.SelectMany(g => g.MemberOf)
- .Select(g => g.ObjectId)
- .Where(id => !groups.Select(g => g.ObjectId).Contains(id))
+ .Select(g => g.Sid.SidStruct)
+ .Where(sid => !groups.Select(g => g.Sid.SidStruct).Contains(sid))
.ToArray();
if(toAdd.Count() == 0)
break;
- foreach(var id in toAdd)
- groups.Add((Group) principalCache[id]);
+ foreach(var sid in toAdd)
+ groups.Add((Group) principalCache[sid]);
}
return groups;