mirror of
https://github.com/chylex/Minecraft-Phantom-Panel.git
synced 2025-05-08 03:34:03 +02:00
Add user and role permissions on web
This commit is contained in:
parent
8d3e4442d7
commit
0e6d506cb4
Packages.props
Server
Phantom.Server.Database.Postgres/Migrations
20221021125232_Permissions.Designer.cs20221021125232_Permissions.csApplicationDbContextModelSnapshot.cs
Phantom.Server.Database
Phantom.Server.Services
Phantom.Server.Web.Identity
Authorization
IdentityPermissions.csPermissionBasedPolicyHandler.csPermissionBasedPolicyRequirement.csPermissionManager.csPermissionView.razor
Data
Phantom.Server.Web.Identity.csprojPhantomIdentityConfigurator.csPhantomIdentityExtensions.csPhantom.Server.Web
Phantom.Server
@ -1,6 +1,7 @@
|
||||
<Project>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Update="Microsoft.AspNetCore.Components.Authorization" Version="7.0.0-rc.1.22427.2" />
|
||||
<PackageReference Update="Microsoft.AspNetCore.Components.Web" Version="7.0.0-rc.1.22427.2" />
|
||||
<PackageReference Update="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="7.0.0-rc.1.22427.2" />
|
||||
<PackageReference Update="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0-rc.1.22426.7" />
|
||||
|
466
Server/Phantom.Server.Database.Postgres/Migrations/20221021125232_Permissions.Designer.cs
generated
Normal file
466
Server/Phantom.Server.Database.Postgres/Migrations/20221021125232_Permissions.Designer.cs
generated
Normal file
@ -0,0 +1,466 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Phantom.Server.Database;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Phantom.Server.Database.Postgres.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20221021125232_Permissions")]
|
||||
partial class Permissions
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.0-rc.1.22426.7")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex");
|
||||
|
||||
b.ToTable("Roles", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("RoleClaims", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUser", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("character varying(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex");
|
||||
|
||||
b.ToTable("Users", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserClaims", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("UserLogins", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("UserRoles", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("UserTokens", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.AgentEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("AgentGuid")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("BuildVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("MaxInstances")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<ushort>("MaxMemory")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("ProtocolVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("AgentGuid");
|
||||
|
||||
b.ToTable("Agents", "agents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.AuditEventEntity", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<JsonDocument>("Data")
|
||||
.HasColumnType("jsonb");
|
||||
|
||||
b.Property<string>("EventType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SubjectId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SubjectType")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("UtcTime")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AuditEvents", "system");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.InstanceEntity", b =>
|
||||
{
|
||||
b.Property<Guid>("InstanceGuid")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<Guid>("AgentGuid")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("InstanceName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("JavaRuntimeGuid")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("JvmArguments")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("LaunchAutomatically")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<ushort>("MemoryAllocation")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("MinecraftServerKind")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("MinecraftVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("RconPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ServerPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("InstanceGuid");
|
||||
|
||||
b.ToTable("Instances", "agents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.PermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Permissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.RolePermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("RoleId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("RolePermissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.UserPermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("UserPermissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.AuditEventEntity", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", "User")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.RolePermissionEntity", b =>
|
||||
{
|
||||
b.HasOne("Phantom.Server.Database.Entities.PermissionEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.UserPermissionEntity", b =>
|
||||
{
|
||||
b.HasOne("Phantom.Server.Database.Entities.PermissionEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace Phantom.Server.Database.Postgres.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class Permissions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Permissions",
|
||||
schema: "identity",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Permissions", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RolePermissions",
|
||||
schema: "identity",
|
||||
columns: table => new
|
||||
{
|
||||
RoleId = table.Column<string>(type: "text", nullable: false),
|
||||
PermissionId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RolePermissions", x => new { x.RoleId, x.PermissionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Permissions_PermissionId",
|
||||
column: x => x.PermissionId,
|
||||
principalSchema: "identity",
|
||||
principalTable: "Permissions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RolePermissions_Roles_RoleId",
|
||||
column: x => x.RoleId,
|
||||
principalSchema: "identity",
|
||||
principalTable: "Roles",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "UserPermissions",
|
||||
schema: "identity",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(type: "text", nullable: false),
|
||||
PermissionId = table.Column<string>(type: "text", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_UserPermissions", x => new { x.UserId, x.PermissionId });
|
||||
table.ForeignKey(
|
||||
name: "FK_UserPermissions_Permissions_PermissionId",
|
||||
column: x => x.PermissionId,
|
||||
principalSchema: "identity",
|
||||
principalTable: "Permissions",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_UserPermissions_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalSchema: "identity",
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RolePermissions_PermissionId",
|
||||
schema: "identity",
|
||||
table: "RolePermissions",
|
||||
column: "PermissionId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_UserPermissions_PermissionId",
|
||||
schema: "identity",
|
||||
table: "UserPermissions",
|
||||
column: "PermissionId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RolePermissions",
|
||||
schema: "identity");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "UserPermissions",
|
||||
schema: "identity");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Permissions",
|
||||
schema: "identity");
|
||||
}
|
||||
}
|
||||
}
|
@ -328,6 +328,46 @@ namespace Phantom.Server.Database.Postgres.Migrations
|
||||
b.ToTable("Instances", "agents");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.PermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("Id")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.ToTable("Permissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.RolePermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("RoleId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("RoleId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("RolePermissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.UserPermissionEntity", b =>
|
||||
{
|
||||
b.Property<string>("UserId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PermissionId")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("UserId", "PermissionId");
|
||||
|
||||
b.HasIndex("PermissionId");
|
||||
|
||||
b.ToTable("UserPermissions", "identity");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<string>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
@ -387,6 +427,36 @@ namespace Phantom.Server.Database.Postgres.Migrations
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.RolePermissionEntity", b =>
|
||||
{
|
||||
b.HasOne("Phantom.Server.Database.Entities.PermissionEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Phantom.Server.Database.Entities.UserPermissionEntity", b =>
|
||||
{
|
||||
b.HasOne("Phantom.Server.Database.Entities.PermissionEntity", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("PermissionId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,10 @@ namespace Phantom.Server.Database;
|
||||
|
||||
[SuppressMessage("ReSharper", "AutoPropertyCanBeMadeGetOnly.Global")]
|
||||
public class ApplicationDbContext : IdentityDbContext {
|
||||
public DbSet<PermissionEntity> Permissions { get; set; } = null!;
|
||||
public DbSet<UserPermissionEntity> UserPermissions { get; set; } = null!;
|
||||
public DbSet<RolePermissionEntity> RolePermissions { get; set; } = null!;
|
||||
|
||||
public DbSet<AgentEntity> Agents { get; set; } = null!;
|
||||
public DbSet<InstanceEntity> Instances { get; set; } = null!;
|
||||
public DbSet<AuditEventEntity> AuditEvents { get; set; } = null!;
|
||||
@ -39,6 +43,18 @@ public class ApplicationDbContext : IdentityDbContext {
|
||||
builder.Entity<IdentityUserLogin<string>>().ToTable("UserLogins", schema: IdentitySchema);
|
||||
builder.Entity<IdentityUserToken<string>>().ToTable("UserTokens", schema: IdentitySchema);
|
||||
builder.Entity<IdentityUserClaim<string>>().ToTable("UserClaims", schema: IdentitySchema);
|
||||
|
||||
builder.Entity<UserPermissionEntity>(static b => {
|
||||
b.HasKey(static e => new { e.UserId, e.PermissionId });
|
||||
b.HasOne<IdentityUser>().WithMany().HasForeignKey(static e => e.UserId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasOne<PermissionEntity>().WithMany().HasForeignKey(static e => e.PermissionId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
builder.Entity<RolePermissionEntity>(static b => {
|
||||
b.HasKey(static e => new { e.RoleId, e.PermissionId });
|
||||
b.HasOne<IdentityRole>().WithMany().HasForeignKey(static e => e.RoleId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||
b.HasOne<PermissionEntity>().WithMany().HasForeignKey(static e => e.PermissionId).IsRequired().OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void ConfigureConventions(ModelConfigurationBuilder builder) {
|
||||
|
14
Server/Phantom.Server.Database/Entities/PermissionEntity.cs
Normal file
14
Server/Phantom.Server.Database/Entities/PermissionEntity.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Phantom.Server.Database.Entities;
|
||||
|
||||
[Table("Permissions", Schema = "identity")]
|
||||
public class PermissionEntity {
|
||||
[Key]
|
||||
public string Id { get; set; }
|
||||
|
||||
public PermissionEntity(string id) {
|
||||
Id = id;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Phantom.Server.Database.Entities;
|
||||
|
||||
[Table("RolePermissions", Schema = "identity")]
|
||||
public class RolePermissionEntity {
|
||||
public string RoleId { get; set; }
|
||||
public string PermissionId { get; set; }
|
||||
|
||||
public RolePermissionEntity(string roleId, string permissionId) {
|
||||
RoleId = roleId;
|
||||
PermissionId = permissionId;
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace Phantom.Server.Database.Entities;
|
||||
|
||||
[Table("UserPermissions", Schema = "identity")]
|
||||
public class UserPermissionEntity {
|
||||
public string UserId { get; set; }
|
||||
public string PermissionId { get; set; }
|
||||
|
||||
public UserPermissionEntity(string userId, string permissionId) {
|
||||
UserId = userId;
|
||||
PermissionId = permissionId;
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ public sealed partial class AuditLog {
|
||||
}
|
||||
|
||||
public void AddUserLoggedOutEvent(ClaimsPrincipal user) {
|
||||
var userId = GetUserId(user);
|
||||
var userId = identityLookup.GetAuthenticatedUserId(user);
|
||||
AddEvent(userId, AuditEventType.UserLoggedOut, userId ?? string.Empty);
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Server.Database;
|
||||
using Phantom.Server.Database.Entities;
|
||||
using Phantom.Server.Database.Enums;
|
||||
using Phantom.Server.Services.Users;
|
||||
using Phantom.Utils.Runtime;
|
||||
|
||||
namespace Phantom.Server.Services.Audit;
|
||||
@ -11,23 +11,21 @@ namespace Phantom.Server.Services.Audit;
|
||||
public sealed partial class AuditLog {
|
||||
private readonly CancellationToken cancellationToken;
|
||||
private readonly DatabaseProvider databaseProvider;
|
||||
private readonly IdentityLookup identityLookup;
|
||||
private readonly AuthenticationStateProvider authenticationStateProvider;
|
||||
private readonly TaskManager taskManager;
|
||||
|
||||
public AuditLog(ServiceConfiguration serviceConfiguration, DatabaseProvider databaseProvider, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager) {
|
||||
public AuditLog(ServiceConfiguration serviceConfiguration, DatabaseProvider databaseProvider, IdentityLookup identityLookup, AuthenticationStateProvider authenticationStateProvider, TaskManager taskManager) {
|
||||
this.cancellationToken = serviceConfiguration.CancellationToken;
|
||||
this.databaseProvider = databaseProvider;
|
||||
this.identityLookup = identityLookup;
|
||||
this.authenticationStateProvider = authenticationStateProvider;
|
||||
this.taskManager = taskManager;
|
||||
}
|
||||
|
||||
private static string? GetUserId(ClaimsPrincipal user) {
|
||||
return user.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
}
|
||||
|
||||
private async Task<string?> GetCurrentUserId() {
|
||||
|
||||
private async Task<string?> GetCurrentAuthenticatedUserId() {
|
||||
var authenticationState = await authenticationStateProvider.GetAuthenticationStateAsync();
|
||||
return authenticationState.User.Identity?.IsAuthenticated == true ? GetUserId(authenticationState.User) : null;
|
||||
return identityLookup.GetAuthenticatedUserId(authenticationState.User);
|
||||
}
|
||||
|
||||
private async Task AddEventToDatabase(AuditEventEntity eventEntity) {
|
||||
@ -42,7 +40,7 @@ public sealed partial class AuditLog {
|
||||
}
|
||||
|
||||
private async Task AddEvent(AuditEventType eventType, string subjectId, Dictionary<string, object?>? extra = null) {
|
||||
AddEvent(await GetCurrentUserId(), eventType, subjectId, extra);
|
||||
AddEvent(await GetCurrentAuthenticatedUserId(), eventType, subjectId, extra);
|
||||
}
|
||||
|
||||
public async Task<AuditEvent[]> GetEvents(int count, CancellationToken cancellationToken) {
|
||||
|
16
Server/Phantom.Server.Services/Users/IdentityLookup.cs
Normal file
16
Server/Phantom.Server.Services/Users/IdentityLookup.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Phantom.Server.Services.Users;
|
||||
|
||||
public sealed class IdentityLookup {
|
||||
private readonly UserManager<IdentityUser> userManager;
|
||||
|
||||
public IdentityLookup(UserManager<IdentityUser> userManager) {
|
||||
this.userManager = userManager;
|
||||
}
|
||||
|
||||
public string? GetAuthenticatedUserId(ClaimsPrincipal user) {
|
||||
return user.Identity is { IsAuthenticated: true } ? userManager.GetUserId(user) : null;
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
using System.Collections.Immutable;
|
||||
using Phantom.Server.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Server.Web.Identity.Authorization;
|
||||
|
||||
public sealed class IdentityPermissions {
|
||||
internal static IdentityPermissions None { get; } = new ();
|
||||
|
||||
private readonly ImmutableHashSet<string> permissionIds;
|
||||
|
||||
internal IdentityPermissions(IQueryable<string> permissionIdsQuery) {
|
||||
this.permissionIds = permissionIdsQuery.ToImmutableHashSet();
|
||||
}
|
||||
|
||||
private IdentityPermissions() {
|
||||
this.permissionIds = ImmutableHashSet<string>.Empty;
|
||||
}
|
||||
|
||||
public bool Check(Permission? permission) {
|
||||
while (permission != null) {
|
||||
if (!permissionIds.Contains(permission.Id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
permission = permission.Parent;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
namespace Phantom.Server.Web.Identity.Authorization;
|
||||
|
||||
sealed class PermissionBasedPolicyHandler : AuthorizationHandler<PermissionBasedPolicyRequirement> {
|
||||
private readonly PermissionManager permissionManager;
|
||||
|
||||
public PermissionBasedPolicyHandler(PermissionManager permissionManager) {
|
||||
this.permissionManager = permissionManager;
|
||||
}
|
||||
|
||||
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionBasedPolicyRequirement requirement) {
|
||||
if (permissionManager.CheckPermission(context.User, requirement.Permission)) {
|
||||
context.Succeed(requirement);
|
||||
}
|
||||
else {
|
||||
context.Fail(new AuthorizationFailureReason(this, "Missing permission: " + requirement.Permission.Id));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Phantom.Server.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Server.Web.Identity.Authorization;
|
||||
|
||||
sealed record PermissionBasedPolicyRequirement(Permission Permission) : IAuthorizationRequirement;
|
@ -0,0 +1,44 @@
|
||||
using System.Security.Claims;
|
||||
using Phantom.Server.Database;
|
||||
using Phantom.Server.Services.Users;
|
||||
using Phantom.Server.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Server.Web.Identity.Authorization;
|
||||
|
||||
public sealed class PermissionManager {
|
||||
private readonly ApplicationDbContext db;
|
||||
private readonly IdentityLookup identityLookup;
|
||||
private readonly Dictionary<string, IdentityPermissions> userIdsToPermissionIds = new ();
|
||||
|
||||
public PermissionManager(ApplicationDbContext db, IdentityLookup identityLookup) {
|
||||
this.db = db;
|
||||
this.identityLookup = identityLookup;
|
||||
}
|
||||
|
||||
private IdentityPermissions FetchPermissions(string userId) {
|
||||
var userPermissions = db.UserPermissions.Where(up => up.UserId == userId).Select(static up => up.PermissionId);
|
||||
var rolePermissions = db.UserRoles.Where(ur => ur.UserId == userId).Join(db.RolePermissions, static ur => ur.RoleId, static rp => rp.RoleId, static (ur, rp) => rp.PermissionId);
|
||||
return new IdentityPermissions(userPermissions.Union(rolePermissions));
|
||||
}
|
||||
|
||||
private IdentityPermissions GetPermissionsForUserId(string? userId) {
|
||||
if (userId == null) {
|
||||
return IdentityPermissions.None;
|
||||
}
|
||||
|
||||
if (userIdsToPermissionIds.TryGetValue(userId, out var userPermissions)) {
|
||||
return userPermissions;
|
||||
}
|
||||
else {
|
||||
return userIdsToPermissionIds[userId] = FetchPermissions(userId);
|
||||
}
|
||||
}
|
||||
|
||||
public IdentityPermissions GetPermissions(ClaimsPrincipal user) {
|
||||
return GetPermissionsForUserId(identityLookup.GetAuthenticatedUserId(user));
|
||||
}
|
||||
|
||||
public bool CheckPermission(ClaimsPrincipal user, Permission permission) {
|
||||
return GetPermissions(user).Check(permission);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@using Phantom.Server.Web.Identity.Data
|
||||
@inject PermissionManager PermissionManager
|
||||
|
||||
<AuthorizeView>
|
||||
<Authorized>
|
||||
@if (PermissionManager.CheckPermission(context.User, Permission)) {
|
||||
@ChildContent
|
||||
}
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter, EditorRequired]
|
||||
public Permission Permission { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
}
|
12
Server/Phantom.Server.Web.Identity/Data/Permission.cs
Normal file
12
Server/Phantom.Server.Web.Identity/Data/Permission.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace Phantom.Server.Web.Identity.Data;
|
||||
|
||||
public sealed record Permission(string Id, Permission? Parent) {
|
||||
private static readonly List<Permission> AllPermissions = new ();
|
||||
public static IEnumerable<Permission> All => AllPermissions;
|
||||
|
||||
private static Permission Register(string id, Permission? parent = null) {
|
||||
var permission = new Permission(id, parent);
|
||||
AllPermissions.Add(permission);
|
||||
return permission;
|
||||
}
|
||||
}
|
16
Server/Phantom.Server.Web.Identity/Data/Role.cs
Normal file
16
Server/Phantom.Server.Web.Identity/Data/Role.cs
Normal file
@ -0,0 +1,16 @@
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Phantom.Server.Web.Identity.Data;
|
||||
|
||||
public sealed record Role(string Name, ImmutableArray<Permission> Permissions) {
|
||||
private static readonly List<Role> AllRoles = new ();
|
||||
internal static IEnumerable<Role> All => AllRoles;
|
||||
|
||||
private static Role Register(string name, ImmutableArray<Permission> permissions) {
|
||||
var role = new Role(name, permissions);
|
||||
AllRoles.Add(role);
|
||||
return role;
|
||||
}
|
||||
|
||||
public static readonly Role Administrator = Register("Administrator", Permission.All.ToImmutableArray());
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Razor">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
@ -7,6 +7,11 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<SupportedPlatform Include="browser" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.Web" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -0,0 +1,100 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Phantom.Common.Logging;
|
||||
using Phantom.Server.Database;
|
||||
using Phantom.Server.Database.Entities;
|
||||
using Phantom.Server.Web.Identity.Data;
|
||||
using Phantom.Utils.Runtime;
|
||||
using Serilog;
|
||||
|
||||
namespace Phantom.Server.Web.Identity;
|
||||
|
||||
public sealed class PhantomIdentityConfigurator {
|
||||
private static readonly ILogger Logger = PhantomLogger.Create<PhantomIdentityConfigurator>();
|
||||
|
||||
public static async Task MigrateDatabase(IServiceProvider serviceProvider) {
|
||||
await using var scope = serviceProvider.CreateAsyncScope();
|
||||
await scope.ServiceProvider.GetRequiredService<PhantomIdentityConfigurator>().Initialize();
|
||||
}
|
||||
|
||||
private readonly ApplicationDbContext db;
|
||||
private readonly RoleManager<IdentityRole> roleManager;
|
||||
|
||||
public PhantomIdentityConfigurator(ApplicationDbContext db, RoleManager<IdentityRole> roleManager) {
|
||||
this.db = db;
|
||||
this.roleManager = roleManager;
|
||||
}
|
||||
|
||||
private async Task Initialize() {
|
||||
CreatePermissions();
|
||||
await CreateDefaultRoles();
|
||||
await AssignDefaultRolePermissions();
|
||||
await db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
private void CreatePermissions() {
|
||||
var existingPermissionIds = db.Permissions.Select(static p => p.Id).ToHashSet();
|
||||
var missingPermissionIds = GetMissingPermissionsOrdered(Permission.All, existingPermissionIds);
|
||||
|
||||
if (!missingPermissionIds.IsEmpty) {
|
||||
Logger.Information("Adding permissions: {Permissions}", string.Join(", ", missingPermissionIds));
|
||||
foreach (var permissionId in missingPermissionIds) {
|
||||
db.Permissions.Add(new PermissionEntity(permissionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateDefaultRoles() {
|
||||
foreach (var role in Role.All) {
|
||||
string name = role.Name;
|
||||
if (await roleManager.RoleExistsAsync(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger.Information("Creating default role {RoleName}.", name);
|
||||
var result = await roleManager.CreateAsync(new IdentityRole(name));
|
||||
|
||||
if (!result.Succeeded) {
|
||||
bool anyError = false;
|
||||
|
||||
foreach (var error in result.Errors) {
|
||||
Logger.Fatal("Error creating default role {RoleName}: {Error}", name, error.Description);
|
||||
anyError = true;
|
||||
}
|
||||
|
||||
if (!anyError) {
|
||||
Logger.Fatal("Error creating default role {RoleName} due to unknown error.", name);
|
||||
}
|
||||
|
||||
throw StopProcedureException.Instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AssignDefaultRolePermissions() {
|
||||
Logger.Information("Assigning default role permissions..");
|
||||
|
||||
foreach (var role in Role.All) {
|
||||
var roleEntity = await roleManager.FindByNameAsync(role.Name);
|
||||
if (roleEntity == null) {
|
||||
Logger.Fatal("Error assigning default role permissions, role {RoleName} not found.", role.Name);
|
||||
throw StopProcedureException.Instance;
|
||||
}
|
||||
|
||||
var existingPermissionIds = db.RolePermissions.Where(rp => rp.RoleId == roleEntity.Id).Select(static rp => rp.PermissionId).ToHashSet();
|
||||
var missingPermissionIds = GetMissingPermissionsOrdered(role.Permissions, existingPermissionIds);
|
||||
|
||||
if (!missingPermissionIds.IsEmpty) {
|
||||
Logger.Information("Assigning default permission to role {RoleName}: {Permissions}", role.Name, string.Join(", ", missingPermissionIds));
|
||||
foreach (var permissionId in missingPermissionIds) {
|
||||
db.RolePermissions.Add(new RolePermissionEntity(roleEntity.Id, permissionId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableArray<string> GetMissingPermissionsOrdered(IEnumerable<Permission> allPermissions, HashSet<string> existingPermissionIds) {
|
||||
return allPermissions.Select(static permission => permission.Id).Except(existingPermissionIds).Order().ToImmutableArray();
|
||||
}
|
||||
}
|
@ -1,18 +1,73 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Phantom.Server.Database;
|
||||
using Phantom.Server.Web.Identity.Authentication;
|
||||
using Phantom.Server.Web.Identity.Authorization;
|
||||
using Phantom.Server.Web.Identity.Data;
|
||||
|
||||
namespace Phantom.Server.Web.Identity;
|
||||
namespace Phantom.Server.Web.Identity;
|
||||
|
||||
public static class PhantomIdentityExtensions {
|
||||
public static void AddPhantomIdentity<TUser>(this IServiceCollection services) where TUser : class {
|
||||
public static void AddPhantomIdentity<TUser, TRole>(this IServiceCollection services) where TUser : class where TRole : class {
|
||||
services.AddIdentity<TUser, TRole>(ConfigureIdentity).AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
services.ConfigureApplicationCookie(ConfigureIdentityCookie);
|
||||
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
services.AddAuthorization(ConfigureAuthorization);
|
||||
|
||||
services.AddSingleton<PhantomLoginStore>();
|
||||
services.AddScoped<PhantomLoginManager>();
|
||||
|
||||
services.AddScoped<PhantomIdentityConfigurator>();
|
||||
services.AddScoped<IAuthorizationHandler, PermissionBasedPolicyHandler>();
|
||||
services.AddScoped<AuthenticationStateProvider, RevalidatingIdentityAuthenticationStateProvider<TUser>>();
|
||||
|
||||
services.AddTransient<PermissionManager>();
|
||||
}
|
||||
|
||||
|
||||
public static void UsePhantomIdentity(this IApplicationBuilder application) {
|
||||
application.UseAuthentication();
|
||||
application.UseAuthorization();
|
||||
application.UseMiddleware<PhantomIdentityMiddleware>();
|
||||
}
|
||||
|
||||
private static void ConfigureIdentity(IdentityOptions o) {
|
||||
o.SignIn.RequireConfirmedAccount = false;
|
||||
o.SignIn.RequireConfirmedEmail = false;
|
||||
o.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
o.Password.RequireLowercase = true;
|
||||
o.Password.RequireUppercase = true;
|
||||
o.Password.RequireDigit = true;
|
||||
o.Password.RequiredLength = 16;
|
||||
|
||||
o.Lockout.AllowedForNewUsers = true;
|
||||
o.Lockout.MaxFailedAccessAttempts = 10;
|
||||
o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);
|
||||
|
||||
o.Stores.MaxLengthForKeys = 128;
|
||||
}
|
||||
|
||||
private static void ConfigureIdentityCookie(CookieAuthenticationOptions o) {
|
||||
o.Cookie.Name = "Phantom.Identity";
|
||||
o.Cookie.HttpOnly = true;
|
||||
o.Cookie.SameSite = SameSiteMode.Lax;
|
||||
|
||||
o.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||
o.SlidingExpiration = true;
|
||||
|
||||
o.LoginPath = "/login";
|
||||
o.LogoutPath = "/logout";
|
||||
o.AccessDeniedPath = "/login";
|
||||
}
|
||||
|
||||
private static void ConfigureAuthorization(AuthorizationOptions o) {
|
||||
foreach (var permission in Permission.All) {
|
||||
o.AddPolicy(permission.Id, policy => policy.Requirements.Add(new PermissionBasedPolicyRequirement(permission)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,10 +6,14 @@
|
||||
<Found Context="routeData">
|
||||
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
|
||||
<NotAuthorized>
|
||||
@{
|
||||
@if (context.User.Identity is not { IsAuthenticated: true }) {
|
||||
var returnUrl = NavigationManager.ToBaseRelativePath(NavigationManager.Uri).TrimEnd('/');
|
||||
Nav.NavigateTo("login" + QueryString.Create("return", returnUrl), forceLoad: true);
|
||||
}
|
||||
else {
|
||||
<h1>Forbidden</h1>
|
||||
<p role="alert">You do not have permission to visit this page.</p>
|
||||
}
|
||||
</NotAuthorized>
|
||||
</AuthorizeRouteView>
|
||||
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Phantom.Server.Database;
|
||||
@ -25,22 +24,18 @@ public static class Launcher {
|
||||
if (builder.Environment.IsEnvironment("Local")) {
|
||||
builder.WebHost.UseStaticWebAssets();
|
||||
}
|
||||
|
||||
|
||||
configurator.ConfigureServices(builder.Services);
|
||||
|
||||
builder.Services.AddSingleton<IHostLifetime>(new NullLifetime());
|
||||
builder.Services.AddScoped<INavigation>(Navigation.Create(config.BasePath));
|
||||
|
||||
|
||||
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(config.KeyFolderPath));
|
||||
|
||||
builder.Services.AddDbContextPool<ApplicationDbContext>(dbOptionsBuilder, poolSize: 64);
|
||||
builder.Services.AddSingleton<DatabaseProvider>();
|
||||
|
||||
builder.Services.AddIdentity<IdentityUser, IdentityRole>(ConfigureIdentity).AddEntityFrameworkStores<ApplicationDbContext>();
|
||||
builder.Services.ConfigureApplicationCookie(ConfigureIdentityCookie);
|
||||
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddPhantomIdentity<IdentityUser>();
|
||||
builder.Services.AddPhantomIdentity<IdentityUser, IdentityRole>();
|
||||
|
||||
builder.Services.AddRazorPages(static options => options.RootDirectory = "/Layout");
|
||||
builder.Services.AddServerSideBlazor();
|
||||
@ -48,6 +43,7 @@ public static class Launcher {
|
||||
var application = builder.Build();
|
||||
|
||||
await MigrateDatabase(config, application.Services.GetRequiredService<DatabaseProvider>());
|
||||
await PhantomIdentityConfigurator.MigrateDatabase(application.Services);
|
||||
await configurator.LoadFromDatabase(application.Services);
|
||||
|
||||
return application;
|
||||
@ -65,8 +61,6 @@ public static class Launcher {
|
||||
|
||||
application.UseStaticFiles();
|
||||
application.UseRouting();
|
||||
application.UseAuthentication();
|
||||
application.UseAuthorization();
|
||||
application.UsePhantomIdentity();
|
||||
|
||||
application.MapControllers();
|
||||
@ -87,36 +81,6 @@ public static class Launcher {
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConfigureIdentity(IdentityOptions o) {
|
||||
o.SignIn.RequireConfirmedAccount = false;
|
||||
o.SignIn.RequireConfirmedEmail = false;
|
||||
o.SignIn.RequireConfirmedPhoneNumber = false;
|
||||
|
||||
o.Password.RequireLowercase = true;
|
||||
o.Password.RequireUppercase = true;
|
||||
o.Password.RequireDigit = true;
|
||||
o.Password.RequiredLength = 16;
|
||||
|
||||
o.Lockout.AllowedForNewUsers = true;
|
||||
o.Lockout.MaxFailedAccessAttempts = 10;
|
||||
o.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(1);
|
||||
|
||||
o.Stores.MaxLengthForKeys = 128;
|
||||
}
|
||||
|
||||
private static void ConfigureIdentityCookie(CookieAuthenticationOptions o) {
|
||||
o.Cookie.Name = "Phantom.Identity";
|
||||
o.Cookie.HttpOnly = true;
|
||||
o.Cookie.SameSite = SameSiteMode.Lax;
|
||||
|
||||
o.ExpireTimeSpan = TimeSpan.FromDays(30);
|
||||
o.SlidingExpiration = true;
|
||||
|
||||
o.LoginPath = "/login";
|
||||
o.LogoutPath = "/logout";
|
||||
o.AccessDeniedPath = "/login";
|
||||
}
|
||||
|
||||
private static async Task MigrateDatabase(Configuration config, DatabaseProvider databaseProvider) {
|
||||
var logger = config.Logger;
|
||||
|
||||
@ -136,7 +100,7 @@ public static class Launcher {
|
||||
}
|
||||
|
||||
public interface IConfigurator {
|
||||
void ConfigureServices( IServiceCollection services);
|
||||
void ConfigureServices(IServiceCollection services);
|
||||
Task LoadFromDatabase(IServiceProvider serviceProvider);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@
|
||||
@inject ServiceConfiguration ServiceConfiguration
|
||||
@inject PhantomLoginManager LoginManager
|
||||
@inject UserManager<IdentityUser> UserManager
|
||||
@inject RoleManager<IdentityRole> RoleManager
|
||||
@inject AuditLog AuditLog
|
||||
|
||||
<h1>Administrator Setup</h1>
|
||||
@ -68,11 +67,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
var createRolesResult = await CreateDefaultRoles();
|
||||
if (!createRolesResult.Succeeded) {
|
||||
form.SubmitModel.StopSubmitting(GetErrors(createRolesResult));
|
||||
}
|
||||
|
||||
var createUserResult = await CreateOrUpdateAdministrator();
|
||||
if (!createUserResult.Succeeded) {
|
||||
form.SubmitModel.StopSubmitting(GetErrors(createUserResult));
|
||||
@ -96,14 +90,6 @@
|
||||
return CryptographicOperations.FixedTimeEquals(formTokenBytes, ServiceConfiguration.AdministratorToken);
|
||||
}
|
||||
|
||||
private async Task<IdentityResult> CreateDefaultRoles() {
|
||||
if (!await RoleManager.RoleExistsAsync("Administrator")) {
|
||||
return await RoleManager.CreateAsync(new IdentityRole("Administrator"));
|
||||
}
|
||||
|
||||
return IdentityResult.Success;
|
||||
}
|
||||
|
||||
private async Task<IdentityResult> CreateOrUpdateAdministrator() {
|
||||
var existingUser = await UserManager.FindByNameAsync(form.Username);
|
||||
return existingUser == null ? await CreateAdministrator() : await UpdateAdministrator(existingUser);
|
||||
@ -118,7 +104,7 @@
|
||||
|
||||
await AuditLog.AddAdministratorUserCreatedEvent(newUser);
|
||||
|
||||
var addToRoleResult = await UserManager.AddToRoleAsync(newUser, "Administrator");
|
||||
var addToRoleResult = await UserManager.AddToRoleAsync(newUser, Role.Administrator.Name);
|
||||
return addToRoleResult;
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@
|
||||
@using Phantom.Server.Web.Components.Graphics
|
||||
@using Phantom.Server.Web.Components.Tables
|
||||
@using Phantom.Server.Web.Identity
|
||||
@using Phantom.Server.Web.Identity.Authorization
|
||||
@using Phantom.Server.Web.Identity.Data
|
||||
@using Phantom.Server.Web.Layout
|
||||
@using Phantom.Server.Web.Shared
|
||||
@attribute [Authorize]
|
||||
|
@ -74,6 +74,8 @@ try {
|
||||
);
|
||||
} catch (OperationCanceledException) {
|
||||
// Ignore.
|
||||
} catch (StopProcedureException) {
|
||||
// Ignore.
|
||||
} finally {
|
||||
cancellationTokenSource.Cancel();
|
||||
|
||||
|
@ -6,6 +6,7 @@ using Phantom.Server.Services.Agents;
|
||||
using Phantom.Server.Services.Audit;
|
||||
using Phantom.Server.Services.Instances;
|
||||
using Phantom.Server.Services.Rpc;
|
||||
using Phantom.Server.Services.Users;
|
||||
using Phantom.Utils.Runtime;
|
||||
using WebLauncher = Phantom.Server.Web.Launcher;
|
||||
|
||||
@ -33,7 +34,8 @@ sealed class WebConfigurator : WebLauncher.IConfigurator {
|
||||
services.AddSingleton<InstanceLogManager>();
|
||||
services.AddSingleton<MinecraftVersions>();
|
||||
services.AddSingleton<MessageToServerListenerFactory>();
|
||||
|
||||
|
||||
services.AddScoped<IdentityLookup>();
|
||||
services.AddScoped<AuditLog>();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user