elsa-core/src/modules/Elsa.Secrets.Persistence.EFCore/SecretConfiguration.cs
Sipke Schoorstra 440ffaf719
test(secrets): add File/InMemory/EF tenancy conformance matrix (#8143)
* test(secrets): add File/InMemory/EF tenancy conformance matrix

Shared ISecretRepository scenarios for per-tenant uniqueness, Get/List
isolation, deleted replace, and default-tenant duplicate rejection.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(secrets): enforce default-tenant uniqueness via empty TenantId

Stamp leftover null TenantId values to "" before rebuilding the
per-tenant unique index, matching Labels. Fail loud on leftover
duplicate names instead of silently deduping. New EF writes in the
default tenant persist "".

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(secrets): make default-tenant uniqueness migrate on SQLite

Use the schema-less Secrets table name, let CreateIndex fail loudly
when leftover duplicates remain, and isolate the tenant-aware EF model
cache from the non-tenant fixture.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* test(secrets): avoid Path.Combine drop in uniqueness migration tests

Reject rooted provider project paths and use Path.Join when locating
Elsa.sln so CodeQL does not warn about discarded path segments.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

* fix(secrets): enforce Oracle default-tenant uniqueness via NVL index

Oracle stores '' as NULL, so a TenantId IS NOT NULL filtered unique index
left default-tenant secret names uncovered. Use NVL(TenantId, CHR(1)) so
those rows share one index key. Stamp stays for preflight grouping; leftover
duplicates still fail loud with no silent dedupe.

Co-authored-by: Sipke Schoorstra <sipkeschoorstra@outlook.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-09-14 07:03:16 +02:00

36 lines
2.3 KiB
C#

using Elsa.Secrets.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Elsa.Secrets.Persistence.EFCore;
internal class SecretConfiguration : IEntityTypeConfiguration<Secret>
{
public void Configure(EntityTypeBuilder<Secret> builder)
{
builder.HasKey(x => x.Id);
builder.Ignore(x => x.Tags);
builder.Ignore(x => x.Versions);
builder.Ignore(x => x.LatestActiveVersion);
builder.Property<string>(SecretShadowPropertyNames.SerializedTags).HasColumnName("Tags").IsRequired();
builder.Property<string>(SecretShadowPropertyNames.SerializedVersions).HasColumnName("Versions").IsRequired();
builder.Property(x => x.Name).HasMaxLength(200).IsRequired();
builder.Property<string>(SecretShadowPropertyNames.NormalizedName).HasMaxLength(200).IsRequired();
builder.Property(x => x.DisplayName).HasMaxLength(200).IsRequired();
builder.Property(x => x.TypeName).HasMaxLength(100).IsRequired();
builder.Property(x => x.StoreName).HasMaxLength(100).IsRequired();
builder.Property(x => x.Scope).HasMaxLength(200);
builder.Property(x => x.Status).HasConversion<string>().HasMaxLength(32).IsRequired();
// Uniqueness is per tenant, matching Labels (and User, Role, Application).
// A global unique index would make the name a shared resource across tenants.
// SQL Server still emits a filtered unique index (TenantId IS NOT NULL); leftover
// nulls are stamped to "" first. Oracle stores '' as NULL, so that provider uses
// a function-based unique index on NVL(TenantId, CHR(1)) instead of a filter.
builder.HasIndex("TenantId", SecretShadowPropertyNames.NormalizedName).HasDatabaseName($"IX_{nameof(Secret)}_TenantId_{SecretShadowPropertyNames.NormalizedName}").IsUnique();
builder.HasIndex(x => x.TypeName).HasDatabaseName($"IX_{nameof(Secret)}_{nameof(Secret.TypeName)}");
builder.HasIndex(x => x.StoreName).HasDatabaseName($"IX_{nameof(Secret)}_{nameof(Secret.StoreName)}");
builder.HasIndex(x => x.Scope).HasDatabaseName($"IX_{nameof(Secret)}_{nameof(Secret.Scope)}");
builder.HasIndex(x => x.Status).HasDatabaseName($"IX_{nameof(Secret)}_{nameof(Secret.Status)}");
}
}