diff --git a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs index 52bb9ae1..6c0f743c 100644 --- a/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs +++ b/src/Turnierplan.App.Test.Unit/Security/AccessValidatorTest.cs @@ -17,8 +17,8 @@ public void IsActionAllowed___When_Called_With_Basic_Target___Returns_Expected_R { var target = new Organization("Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); target.AddRoleAssignment(Role.Reader, principal); target.AddRoleAssignment(Role.Contributor, otherPrincipal); @@ -35,8 +35,8 @@ public void IsActionAllowed___When_Called_With_Indirect_Target___Returns_Expecte { var organization = new Organization("Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); organization.AddRoleAssignment(Role.Reader, principal); organization.AddRoleAssignment(Role.Contributor, otherPrincipal); @@ -66,8 +66,8 @@ public void IsActionAllowed___When_Called_With_Tournament_Target_And_Role_Assign var organization = new Organization("Test"); var folder = new Folder(organization, "Test"); - var principal = new Principal(PrincipalKind.User, "faa6d5d3-93ad-410e-bc81-171a04cf0130"); - var otherPrincipal = new Principal(PrincipalKind.User, "98f8cb8c-606f-47fc-805f-244210e1df51"); + var principal = new Principal(PrincipalKind.User, Guid.Parse("faa6d5d3-93ad-410e-bc81-171a04cf0130")); + var otherPrincipal = new Principal(PrincipalKind.User, Guid.Parse("98f8cb8c-606f-47fc-805f-244210e1df51")); folder.AddRoleAssignment(Role.Reader, principal); folder.AddRoleAssignment(Role.Contributor, otherPrincipal); diff --git a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html index 3ae767c7..d28ed5a3 100644 --- a/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html +++ b/src/Turnierplan.App/Client/src/app/portal/components/rbac-offcanvas/rbac-offcanvas.component.html @@ -39,7 +39,7 @@ } } - {{ assignment.principal.objectId }} + {{ assignment.principal.principalId }} @if (canDeleteAssignment(assignment)) { diff --git a/src/Turnierplan.App/Endpoints/EndpointBase.cs b/src/Turnierplan.App/Endpoints/EndpointBase.cs index b01201da..c2b83693 100644 --- a/src/Turnierplan.App/Endpoints/EndpointBase.cs +++ b/src/Turnierplan.App/Endpoints/EndpointBase.cs @@ -69,7 +69,7 @@ private void ConfigureAuthorization(RouteHandlerBuilder builder) ? [AuthenticationSchemes.AuthenticationSchemeApiKey, AuthenticationSchemes.AuthenticationSchemeSession] : [AuthenticationSchemes.AuthenticationSchemeSession]; - policy.RequireAssertion(context => context.User.Claims.Any(x => x.Type.Equals(ClaimTypes.ApiKeyId) || x.Type.Equals(ClaimTypes.UserId))); + policy.RequireClaim(ClaimTypes.PrincipalId); if (RequireAdministrator == true) { diff --git a/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs b/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs index 4d839bd5..e858c4eb 100644 --- a/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs +++ b/src/Turnierplan.App/Endpoints/Identity/IdentityEndpointBase.cs @@ -4,6 +4,7 @@ using Microsoft.IdentityModel.Tokens; using Turnierplan.App.Options; using Turnierplan.App.Security; +using Turnierplan.Core.RoleAssignment; using Turnierplan.Core.User; using ClaimTypes = Turnierplan.App.Security.ClaimTypes; @@ -36,6 +37,8 @@ protected string CreateTokenForUser(User user, bool isRefreshToken) claims.Add(new Claim(ClaimTypes.DisplayName, user.Name)); claims.Add(new Claim(ClaimTypes.EMailAddress, user.EMail)); claims.Add(new Claim(ClaimTypes.UserId, user.Id.ToString())); + claims.Add(new Claim(ClaimTypes.PrincipalId, user.PrincipalId.ToString())); + claims.Add(new Claim(ClaimTypes.PrincipalKind, nameof(PrincipalKind.User))); if (user.IsAdministrator) { diff --git a/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs b/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs index 4f0c0dc0..79de1cc4 100644 --- a/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs +++ b/src/Turnierplan.App/Endpoints/Organizations/GetOrganizationsEndpoint.cs @@ -27,8 +27,7 @@ private static async Task Handle( } else { - var userId = context.GetCurrentUserIdOrThrow(); - var principal = new Principal(PrincipalKind.User, userId.ToString()); + var principal = context.GetActivePrincipal(); organizations = await repository.GetByPrincipalAsync(principal).ConfigureAwait(false); } diff --git a/src/Turnierplan.App/Extensions/HttpContextExtensions.cs b/src/Turnierplan.App/Extensions/HttpContextExtensions.cs index b73f452a..f161d116 100644 --- a/src/Turnierplan.App/Extensions/HttpContextExtensions.cs +++ b/src/Turnierplan.App/Extensions/HttpContextExtensions.cs @@ -26,19 +26,27 @@ public static bool IsCurrentUserAdministrator(this HttpContext context) public static Principal GetActivePrincipal(this HttpContext context) { + PrincipalKind? kind = null; + Guid? principalId = null; + foreach (var claim in context.User.Claims) { - if (claim.Type.Equals(ClaimTypes.ApiKeyId)) + if (claim.Type.Equals(ClaimTypes.PrincipalKind)) { - return new Principal(PrincipalKind.ApiKey, claim.Value); + kind = Enum.Parse(claim.Value); } - if (claim.Type.Equals(ClaimTypes.UserId)) + if (claim.Type.Equals(ClaimTypes.PrincipalId)) { - return new Principal(PrincipalKind.User, claim.Value); + principalId = Guid.Parse(claim.Value); } } + if (kind.HasValue && principalId.HasValue) + { + return new Principal(kind.Value, principalId.Value); + } + throw new InvalidOperationException("Could not determine active principal."); } diff --git a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs index dcc03da9..a868b2e8 100644 --- a/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs +++ b/src/Turnierplan.App/Mapping/Rules/RoleAssignmentMappingRule.cs @@ -26,7 +26,7 @@ protected override RoleAssignmentDto Map(IMapper mapper, MappingContext context, Principal = new PrincipalDto { Kind = source.Principal.Kind, - ObjectId = source.Principal.ObjectId + PrincipalId = source.Principal.PrincipalId }, Description = source.Description, IsInherited = false diff --git a/src/Turnierplan.App/Models/PrincipalDto.cs b/src/Turnierplan.App/Models/PrincipalDto.cs index 0cdc2f42..cee5ca10 100644 --- a/src/Turnierplan.App/Models/PrincipalDto.cs +++ b/src/Turnierplan.App/Models/PrincipalDto.cs @@ -6,5 +6,5 @@ public sealed record PrincipalDto { public required PrincipalKind Kind { get; init; } - public required string ObjectId { get; init; } + public required Guid PrincipalId { get; init; } } diff --git a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs index 6da34734..96c26a76 100644 --- a/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs +++ b/src/Turnierplan.App/Security/ApiKeyAuthenticationHandler.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Turnierplan.Core.ApiKey; using Turnierplan.Core.PublicId; +using Turnierplan.Core.RoleAssignment; using Turnierplan.Dal; namespace Turnierplan.App.Security; @@ -79,7 +80,8 @@ protected override async Task HandleAuthenticateAsync() await _apiKeyRepository.UnitOfWork.SaveChangesAsync().ConfigureAwait(false); var identity = new ClaimsIdentity(claims: [ - new Claim(ClaimTypes.ApiKeyId, apiKey.Id.ToString()) + new Claim(ClaimTypes.PrincipalId, apiKey.PrincipalId.ToString()), + new Claim(ClaimTypes.PrincipalKind, nameof(PrincipalKind.ApiKey)) ]); var principal = new ClaimsPrincipal([ identity ]); diff --git a/src/Turnierplan.App/Security/ClaimTypes.cs b/src/Turnierplan.App/Security/ClaimTypes.cs index 2712d03f..64ad9898 100644 --- a/src/Turnierplan.App/Security/ClaimTypes.cs +++ b/src/Turnierplan.App/Security/ClaimTypes.cs @@ -3,9 +3,10 @@ namespace Turnierplan.App.Security; internal static class ClaimTypes { public const string Administrator = "adm"; - public const string ApiKeyId = "turnierplan_api_key_id"; public const string DisplayName = "name"; public const string EMailAddress = "mail"; + public const string PrincipalKind = "principalkind"; + public const string PrincipalId = "principalid"; public const string SecurityStamp = "sst"; public const string TokenType = "typ"; public const string UserId = "uid"; diff --git a/src/Turnierplan.Core/ApiKey/ApiKey.cs b/src/Turnierplan.Core/ApiKey/ApiKey.cs index 76e50c67..4db31f11 100644 --- a/src/Turnierplan.Core/ApiKey/ApiKey.cs +++ b/src/Turnierplan.Core/ApiKey/ApiKey.cs @@ -13,6 +13,7 @@ public ApiKey(Organization.Organization organization, string name, string? descr organization._apiKeys.Add(this); Id = 0; + PrincipalId = Guid.NewGuid(); PublicId = new PublicId.PublicId(); Organization = organization; Name = name; @@ -23,9 +24,10 @@ public ApiKey(Organization.Organization organization, string name, string? descr IsActive = true; } - internal ApiKey(long id, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) + internal ApiKey(long id, Guid principalId, PublicId.PublicId publicId, string name, string description, string secretHash, DateTime createdAt, DateTime expiryDate, bool isActive) { Id = id; + PrincipalId = principalId; PublicId = publicId; Name = name; Description = description; @@ -37,6 +39,8 @@ internal ApiKey(long id, PublicId.PublicId publicId, string name, string descrip public override long Id { get; protected set; } + public Guid PrincipalId { get; } + public PublicId.PublicId PublicId { get; } public Organization.Organization Organization { get; internal set; } = null!; diff --git a/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs b/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs index 9e74bf4c..d2e78f70 100644 --- a/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs +++ b/src/Turnierplan.Core/Extensions/PrincipalExtensions.cs @@ -6,11 +6,11 @@ public static class PrincipalExtensions { public static Principal AsPrincipal(this ApiKey.ApiKey apiKey) { - return new Principal(PrincipalKind.ApiKey, apiKey.Id.ToString()); + return new Principal(PrincipalKind.ApiKey, apiKey.PrincipalId); } public static Principal AsPrincipal(this User.User user) { - return new Principal(PrincipalKind.User, user.Id.ToString()); + return new Principal(PrincipalKind.User, user.PrincipalId); } } diff --git a/src/Turnierplan.Core/RoleAssignment/Principal.cs b/src/Turnierplan.Core/RoleAssignment/Principal.cs index 536bd2e4..713bd033 100644 --- a/src/Turnierplan.Core/RoleAssignment/Principal.cs +++ b/src/Turnierplan.Core/RoleAssignment/Principal.cs @@ -4,15 +4,15 @@ namespace Turnierplan.Core.RoleAssignment; public sealed record Principal { - public Principal(PrincipalKind kind, string objectId) + public Principal(PrincipalKind kind, Guid principalId) { Kind = kind; - ObjectId = objectId; + PrincipalId = principalId; } [JsonPropertyName("k")] public PrincipalKind Kind { get; } - [JsonPropertyName("oid")] - public string ObjectId { get; } + [JsonPropertyName("pid")] + public Guid PrincipalId { get; } } diff --git a/src/Turnierplan.Core/User/User.cs b/src/Turnierplan.Core/User/User.cs index ab8d908f..2a94a13d 100644 --- a/src/Turnierplan.Core/User/User.cs +++ b/src/Turnierplan.Core/User/User.cs @@ -9,6 +9,7 @@ public User(string name, string email) email = email.Trim(); Id = Guid.NewGuid(); + PrincipalId = Guid.NewGuid(); CreatedAt = DateTime.UtcNow; Name = name; EMail = email; @@ -19,9 +20,10 @@ public User(string name, string email) SecurityStamp = Guid.Empty; } - internal User(Guid id, DateTime createdAt, string name, string eMail, string normalizedEMail, string passwordHash, bool isAdministrator, DateTime lastPasswordChange, Guid securityStamp) + internal User(Guid id, Guid principalId, DateTime createdAt, string name, string eMail, string normalizedEMail, string passwordHash, bool isAdministrator, DateTime lastPasswordChange, Guid securityStamp) { Id = id; + PrincipalId = principalId; CreatedAt = createdAt; Name = name; EMail = eMail; @@ -34,6 +36,8 @@ internal User(Guid id, DateTime createdAt, string name, string eMail, string nor public override Guid Id { get; protected set; } + public Guid PrincipalId { get; } + public DateTime CreatedAt { get; } public string Name { get; set; } diff --git a/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs b/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs index f9792f9f..5ef6a5de 100644 --- a/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs +++ b/src/Turnierplan.Dal.Test.Unit/Converters/PrincipalConverterTest.cs @@ -7,11 +7,11 @@ namespace Turnierplan.Dal.Test.Unit.Converters; public sealed class PrincipalConverterTest { [Theory] - [InlineData(PrincipalKind.ApiKey, "123", "ApiKey:123")] + [InlineData(PrincipalKind.ApiKey, "81c42278-5046-4602-9e2b-e16dc7ad9b03", "ApiKey:81c42278-5046-4602-9e2b-e16dc7ad9b03")] [InlineData(PrincipalKind.User, "2e839c1b-04ea-43c9-9bd1-614bf9586859", "User:2e839c1b-04ea-43c9-9bd1-614bf9586859")] - public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKind kind, string objectId, string expectedResult) + public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKind kind, string principalId, string expectedResult) { - var principal = new Principal(kind, objectId); + var principal = new Principal(kind, Guid.Parse(principalId)); var result = PrincipalConverter.FormatPrincipal(principal); @@ -19,22 +19,24 @@ public void FormatPrincipal___When_Called___Produces_Expected_Result(PrincipalKi } [Theory] - [InlineData("ApiKey:123", PrincipalKind.ApiKey, "123")] + [InlineData("ApiKey:81c42278-5046-4602-9e2b-e16dc7ad9b03", PrincipalKind.ApiKey, "81c42278-5046-4602-9e2b-e16dc7ad9b03")] [InlineData("User:2e839c1b-04ea-43c9-9bd1-614bf9586859", PrincipalKind.User, "2e839c1b-04ea-43c9-9bd1-614bf9586859")] - public void ParsePrincipal___When_Called_With_Valid_String___Returns_Expected_Result(string representation, PrincipalKind expectedKind, string expectedObjectId) + public void ParsePrincipal___When_Called_With_Valid_String___Returns_Expected_Result(string representation, PrincipalKind expectedKind, string expectedPrincipalId) { var result = PrincipalConverter.ParsePrincipal(representation); result.Kind.Should().Be(expectedKind); - result.ObjectId.Should().Be(expectedObjectId); + result.PrincipalId.Should().Be(Guid.Parse(expectedPrincipalId)); } [Theory] [InlineData("ApiKey:")] - [InlineData("ApiKey:123 ")] + [InlineData("ApiKey:123")] [InlineData(" ApiKey:123")] - [InlineData("ApiKey:4285231c-cd63-4eb5-adb5-1604d00b2e8e")] + [InlineData("ApiKey:4285231c-cd63-4eb5-adb5-1604d00b2e8")] + [InlineData("User:")] [InlineData("User:123")] + [InlineData(" User:123")] [InlineData("User:4285231c-cd63-4eb5-adb5-1604d00b2e8")] public void ParsePrincipal___When_Called_With_Invalid_String___Throws_Exception(string representation) { diff --git a/src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs b/src/Turnierplan.Dal/Converters/PrincipalConverter.cs similarity index 64% rename from src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs rename to src/Turnierplan.Dal/Converters/PrincipalConverter.cs index a73302d8..9e4621c4 100644 --- a/src/Turnierplan.Dal/Converters/RoleAssignmentConverter.cs +++ b/src/Turnierplan.Dal/Converters/PrincipalConverter.cs @@ -14,21 +14,23 @@ public PrincipalConverter() internal static string FormatPrincipal(Principal principal) { - return $"{principal.Kind}:{principal.ObjectId}"; + return $"{principal.Kind}:{principal.PrincipalId}"; } internal static Principal ParsePrincipal(string input) { var match = PrincipalRegex().Match(input); - if (!match.Success || !Enum.TryParse(match.Groups["Kind"].Value, out var kind)) + if (!match.Success + || !Enum.TryParse(match.Groups["Kind"].Value, out var kind) + || !Guid.TryParse(match.Groups["PrincipalId"].Value, out var principalId)) { throw new TurnierplanException("Invalid principal string."); } - return new Principal(kind, match.Groups["ObjectId"].Value); + return new Principal(kind, principalId); } - [GeneratedRegex(@"^(?:(?ApiKey):(?\d+))$|^(?:(?User):(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}))$")] + [GeneratedRegex(@"^(?ApiKey|User):(?[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$")] private static partial Regex PrincipalRegex(); } diff --git a/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs index 6720878e..6dbde546 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/ApiKeyEntityTypeConfiguration.cs @@ -16,6 +16,12 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Id) .IsRequired(); + builder.Property(x => x.PrincipalId) + .IsRequired(); + + builder.HasIndex(x => x.PrincipalId) + .IsUnique(); + builder.Property(x => x.PublicId) .HasConversion(); diff --git a/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs b/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs index 56a15510..42d81879 100644 --- a/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs +++ b/src/Turnierplan.Dal/EntityConfigurations/UserEntityTypeConfiguration.cs @@ -15,6 +15,12 @@ public void Configure(EntityTypeBuilder builder) builder.Property(x => x.Id) .IsRequired(); + builder.Property(x => x.PrincipalId) + .IsRequired(); + + builder.HasIndex(x => x.PrincipalId) + .IsUnique(); + builder.Property(x => x.CreatedAt) .IsRequired(); diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs similarity index 99% rename from src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs rename to src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs index 24a40f37..5af528c7 100644 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.Designer.cs +++ b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.Designer.cs @@ -13,7 +13,7 @@ namespace Turnierplan.Dal.Migrations { [DbContext(typeof(TurnierplanContext))] - [Migration("20250615154535_Add_RBAC")] + [Migration("20250621085230_Add_RBAC")] partial class Add_RBAC { /// @@ -56,6 +56,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("bigint"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("PublicId") .HasColumnType("bigint"); @@ -67,6 +70,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.HasIndex("PublicId") .IsUnique(); @@ -676,6 +682,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("SecurityStamp") .HasColumnType("uuid"); @@ -684,6 +693,9 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedEMail") .IsUnique(); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Users", "turnierplan"); }); diff --git a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs similarity index 88% rename from src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs rename to src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs index 99d20e83..f86f03e4 100644 --- a/src/Turnierplan.Dal/Migrations/20250615154535_Add_RBAC.cs +++ b/src/Turnierplan.Dal/Migrations/20250621085230_Add_RBAC.cs @@ -40,6 +40,22 @@ protected override void Up(MigrationBuilder migrationBuilder) nullable: false, defaultValue: false); + migrationBuilder.AddColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "Users", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + + migrationBuilder.AddColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "ApiKeys", + type: "uuid", + nullable: false, + defaultValue: new Guid("00000000-0000-0000-0000-000000000000")); + migrationBuilder.CreateTable( name: "IAM_ApiKey", schema: "turnierplan", @@ -184,6 +200,25 @@ protected override void Up(MigrationBuilder migrationBuilder) onDelete: ReferentialAction.Cascade); }); + migrationBuilder.Sql(""" +UPDATE turnierplan."ApiKeys" SET "PrincipalId" = gen_random_uuid(); +UPDATE turnierplan."Users" SET "PrincipalId" = gen_random_uuid(); +"""); + + migrationBuilder.CreateIndex( + name: "IX_Users_PrincipalId", + schema: "turnierplan", + table: "Users", + column: "PrincipalId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiKeys_PrincipalId", + schema: "turnierplan", + table: "ApiKeys", + column: "PrincipalId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_IAM_ApiKey_ApiKeyId", schema: "turnierplan", @@ -227,8 +262,9 @@ protected override void Up(MigrationBuilder migrationBuilder) migrationBuilder.Sql(""" INSERT INTO turnierplan."IAM_Organization" ("Id", "OrganizationId", "CreatedAt", "Role", "Principal", "Description") -SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Organizations"."OwnerId"), '' -FROM turnierplan."Organizations"; +SELECT gen_random_uuid(), "Organizations"."Id", NOW(), 1000, ('User:' || "Users"."PrincipalId"), '' +FROM turnierplan."Organizations" +INNER JOIN turnierplan."Users" ON "Organizations"."OwnerId" = "Users"."Id"; """); migrationBuilder.DropColumn( @@ -264,11 +300,31 @@ protected override void Down(MigrationBuilder migrationBuilder) name: "IAM_Venue", schema: "turnierplan"); + migrationBuilder.DropIndex( + name: "IX_Users_PrincipalId", + schema: "turnierplan", + table: "Users"); + + migrationBuilder.DropIndex( + name: "IX_ApiKeys_PrincipalId", + schema: "turnierplan", + table: "ApiKeys"); + migrationBuilder.DropColumn( name: "IsAdministrator", schema: "turnierplan", table: "Users"); + migrationBuilder.DropColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PrincipalId", + schema: "turnierplan", + table: "ApiKeys"); + migrationBuilder.AddColumn( name: "OwnerId", schema: "turnierplan", diff --git a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs index 0b302773..ee029614 100644 --- a/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs +++ b/src/Turnierplan.Dal/Migrations/TurnierplanContextModelSnapshot.cs @@ -53,6 +53,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.Property("OrganizationId") .HasColumnType("bigint"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("PublicId") .HasColumnType("bigint"); @@ -64,6 +67,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("OrganizationId"); + b.HasIndex("PrincipalId") + .IsUnique(); + b.HasIndex("PublicId") .IsUnique(); @@ -673,6 +679,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) .IsRequired() .HasColumnType("text"); + b.Property("PrincipalId") + .HasColumnType("uuid"); + b.Property("SecurityStamp") .HasColumnType("uuid"); @@ -681,6 +690,9 @@ protected override void BuildModel(ModelBuilder modelBuilder) b.HasIndex("NormalizedEMail") .IsUnique(); + b.HasIndex("PrincipalId") + .IsUnique(); + b.ToTable("Users", "turnierplan"); });