Switch Owned value types to ComplexProperty which is better fit.

This requires fluent configuration. Also made some conversion registration context wide.
This commit is contained in:
eelke 2026-02-28 08:18:30 +01:00
parent 07393f57fc
commit 1a8c63808a
11 changed files with 93 additions and 54 deletions

View file

@ -0,0 +1,12 @@
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace IdentityShroud.Core;
public class DekIdConverter : ValueConverter<DekId, Guid>
{
public DekIdConverter()
: base(id => id.Id, guid => new DekId(guid))
{
}
}

View file

@ -0,0 +1,12 @@
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace IdentityShroud.Core;
public class KekIdConverter : ValueConverter<KekId, Guid>
{
public KekIdConverter()
: base(id => id.Id, guid => new KekId(guid))
{
}
}

View file

@ -1,7 +1,7 @@
using System.Linq.Expressions;
using IdentityShroud.Core.Model;
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@ -23,39 +23,6 @@ public class Db(
public virtual DbSet<RealmKey> Keys { get; set; }
public virtual DbSet<RealmDek> Deks { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var dekIdConverter = new ValueConverter<DekId, Guid>(
id => id.Id,
guid => new DekId(guid));
var kekIdConverter = new ValueConverter<KekId, Guid>(
id => id.Id,
guid => new KekId(guid));
modelBuilder.Entity<RealmDek>()
.Property(d => d.Id)
.HasConversion(dekIdConverter);
modelBuilder.Entity<RealmDek>()
.OwnsOne(d => d.KeyData, keyData =>
{
keyData.Property(k => k.KekId).HasConversion(kekIdConverter);
});
modelBuilder.Entity<RealmKey>()
.OwnsOne(k => k.Key, key =>
{
key.Property(k => k.KekId).HasConversion(kekIdConverter);
});
modelBuilder.Entity<ClientSecret>()
.OwnsOne(c => c.Secret, secret =>
{
secret.Property(s => s.DekId).HasConversion(dekIdConverter);
});
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql("<connection string>");
@ -71,6 +38,18 @@ public class Db(
{
optionsBuilder.UseLoggerFactory(loggerFactory);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(Db).Assembly);
}
protected override void ConfigureConventions(ModelConfigurationBuilder b)
{
base.ConfigureConventions(b);
b.Properties<DekId>().HaveConversion<DekIdConverter>();
b.Properties<KekId>().HaveConversion<KekIdConverter>();
}
}

View file

@ -0,0 +1,2 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=efcore_005Cconverters/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -2,6 +2,8 @@ using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using IdentityShroud.Core.Contracts;
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace IdentityShroud.Core.Model;
@ -13,5 +15,15 @@ public class ClientSecret
public Guid ClientId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? RevokedAt { get; set; }
public required EncryptedValue Secret { get; set; }
public EncryptedValue? Secret { get; set; }
}
public class ClientSecretConfiguration : IEntityTypeConfiguration<ClientSecret>
{
public void Configure(EntityTypeBuilder<ClientSecret> b)
{
b.ToTable("client_secret");
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.Secret);
}
}

View file

@ -1,13 +1,14 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace IdentityShroud.Core.Model;
[Table("realm")]
public class Realm
{
public Guid Id { get; set; }
/// <summary>
/// Note this is part of the url we should encourage users to keep it short but we do not want to limit them too much
@ -27,14 +28,4 @@ public class Realm
/// Can be overriden per client
/// </summary>
public string DefaultSignatureAlgorithm { get; set; } = JsonWebAlgorithm.RS256;
}
[Table("realm_dek")]
public record RealmDek
{
public required DekId Id { get; init; }
public required bool Active { get; set; }
public required string Algorithm { get; init; }
public required EncryptedDek KeyData { get; init; }
public required Guid RealmId { get; init; }
}
}

View file

@ -0,0 +1,24 @@
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace IdentityShroud.Core.Model;
public record RealmDek
{
public required DekId Id { get; init; }
public required bool Active { get; set; }
public required string Algorithm { get; init; }
public required EncryptedDek KeyData { get; init; }
public required Guid RealmId { get; init; }
}
public class RealmDekConfiguration : IEntityTypeConfiguration<RealmDek>
{
public void Configure(EntityTypeBuilder<RealmDek> b)
{
b.ToTable("realm_dek");
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.KeyData, e => e.IsRequired());
}
}

View file

@ -2,17 +2,14 @@ using System.ComponentModel.DataAnnotations.Schema;
using IdentityShroud.Core.Contracts;
using IdentityShroud.Core.Security;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace IdentityShroud.Core.Model;
[Table("realm_key")]
public record RealmKey
{
public required Guid Id { get; init; }
public required string KeyType { get; init; }
public required EncryptedDek Key { get; init; }
public required DateTime CreatedAt { get; init; }
public DateTime? RevokedAt { get; set; }
@ -22,6 +19,15 @@ public record RealmKey
/// are more comfortable replacing keys by using priority then directly deactivating the old key.
/// </summary>
public int Priority { get; set; } = 10;
}
public class RealmKeyConfiguration : IEntityTypeConfiguration<RealmKey>
{
public void Configure(EntityTypeBuilder<RealmKey> b)
{
b.ToTable("realm_key");
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.Key, e => e.IsRequired());
}
}

View file

@ -2,5 +2,4 @@ using Microsoft.EntityFrameworkCore;
namespace IdentityShroud.Core.Security;
[Owned]
public record EncryptedDek(KekId KekId, byte[] Value);

View file

@ -2,7 +2,6 @@ using Microsoft.EntityFrameworkCore;
namespace IdentityShroud.Core.Security;
[Owned]
public record EncryptedValue(DekId DekId, byte[] Value);

View file

@ -26,6 +26,9 @@
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/DotNetCliExePath/@EntryValue">/home/eelke/.dotnet/dotnet</s:String>
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">/home/eelke/.dotnet/sdk/10.0.102/MSBuild.dll</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=c1fa4888_002D88fd_002D4859_002D8288_002D37c12fb8ef13/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
&lt;/SessionState&gt;</s:String>
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=ead9ca22_002Dfc70_002D4ddf_002Db4c7_002D534498815537/@EntryIndexedValue">&lt;SessionState ContinuousTestingMode="0" IsActive="True" Name="All tests from Solution" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session"&gt;
&lt;Solution /&gt;
&lt;/SessionState&gt;</s:String>