elsa-core/test/unit
Sipke Schoorstra 85e5fc083c
fix(external-authentication): scope role-deletion impact to the role's tenant (#8036)
* fix(external-authentication): scope role-deletion impact to the role's tenant

ExternalAuthenticationRoleDeletionDependencyContributor scanned every stored
connection with an empty ConnectionFilter and every configured connection
regardless of its tenant, so a role ID that exists in two tenants could report
another tenant's references as its own impact -- and a configuration entry
owned by another tenant could block a role deletion outright. Remediation had
the same reach: it loaded a dependency's connection by the caller-supplied
owner ID without checking which tenant owned it.

Impact, prevalidation and remediation now only see connections in the role's
tenant context, which is the tenant active on ITenantAccessor while the
role-deletion coordinator runs. Host-scoped connections stay in scope for every
tenant, because the connection registry resolves the host scope for every
signing-in tenant and the provisioner resolves a connection's default role IDs
in the signing-in user's tenant, so a host connection naming a role ID really
does reference that tenant's role. Configuration entries that leave the tenant
blank are host-scoped for the same reason the configuration source materializes
them there. A connection carrying another tenant's ID is out of scope in both
directions, and a connection loaded for remediation that is not in the role's
tenant is treated as absent, which fails the request rather than mutating it.

The stored connections are fetched per applicable scope so another tenant's
rows are never materialized, and both connection stores already honor
ConnectionFilter.Scope; the durable store now has a test pinning that, since
the tenant boundary rests on it.

Refs #8013

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): scan every tenant when deleting a tenant-agnostic role

Role stores expose tenant-agnostic roles (TenantId == "*") from every tenant, but the
role-deletion contributor derived its dependency scan boundary from the ambient tenant only,
so deleting an agnostic role while tenant A was active left references from other tenants
dangling. Resolve the role being deleted once per operation, through the active role store,
and scan every connection and configuration entry regardless of tenant when it is agnostic.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* refactor(external-authentication): share the active role store lookup

Extract the duplicated "active role store is the last registration"
resolution into a single ActiveRoleStore accessor and rename ToScope to
ToConnectionScope for clarity.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): read one connection snapshot and prefer the agnostic role

Reading the host and tenant scopes as two separate store queries let a connection
whose TenantId changed mid-flight fall between the reads and escape both, letting
role deletion proceed while a reference remained. FindConnectionsInRoleTenantScopeAsync
now reads one snapshot and filters it in memory. IsAgnosticRoleAsync resolved a role
by an unqualified ID lookup, which could return the ambient tenant's role instead of
an agnostic role sharing its ID, silently narrowing impact scanning and leaving
JIT-policy references in other tenants dangling; it now checks every role sharing the
ID and gives the agnostic scope deterministic precedence.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* test(external-authentication): correct the scope-filter test comment

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): fail closed when a role ID resolves to more than one role

A same-ID collision between a tenant-scoped role and an agnostic role can only
occur in MemoryRoleStore (durable persistence keys roles by ID alone). In that
case the coordinator's own deletion target is already ambiguous, so widening
or narrowing the scope by guessing is wrong in either direction; throw instead
of picking a side.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): scope role-deletion impact by the resolved role's tenant

Replace the isAgnosticRole flag with ResolveRoleTenantIdAsync, which returns the
resolved role's own TenantId and falls back to the ambient tenant only when the
role cannot be resolved. With multitenancy disabled the EF role store installs
no tenant query filter and can resolve a tenant-owned role by ID regardless of
the ambient tenant, so scoping by the ambient tenant alone left that role's
connection references out of scan while the coordinator deleted it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): require an agnostic replacement when remediating an agnostic role

Authorization for a replacement role still resolves through the ambient tenant's
role services, so a deletion initiated in tenant A could authorize a tenant-A-only
replacement and then write it into tenant B's connection policy, where that role
does not exist. When the deletion target is agnostic, require the replacement
role to be agnostic too.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): require agnostic replacements for host connections and reject ambiguous ones

Extend the agnostic-replacement requirement to host-scoped connections, since a host
connection is served to every signing-in tenant and a tenant-scoped replacement would
resolve in the authorizing tenant but fail to resolve in every other tenant it serves.
Recheck the replacement at removal time through the same agnostic-role resolution used
at validation, instead of trusting whichever same-ID role a plain FindAsync happens to
return, so a replacement collision introduced between validation and mutation is
rejected. Resolve IsAgnosticRoleAsync's candidate directly and return true only when
exactly one matching role is agnostic, so an ambiguous replacement ID is reported as
replacement_role_unavailable_or_unauthorized instead of escaping as an exception.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* fix(external-authentication): keep host-connection replacements allowed for default-tenant roles

Revert the host-scope replacement guard added for host-scoped connections.
IdentityProviderConnectionManagementService forces every managed connection
to host scope, and in a deployment without multitenancy roles are created
scoped to the default tenant rather than agnostic, so requiring an agnostic
replacement for host-scoped connections would make every replacement
remediation impossible in the default deployment. The replacement guard
applies only when the deletion target itself is agnostic, as before.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 22:34:44 -07:00
..
Elsa.Activities.UnitTests Preserve StateMachine composite transition continuations 2026-08-31 04:51:34 +02:00
Elsa.AI.Abstractions.UnitTests Implement Weaver AI Copilot core (#7523) 2026-06-04 11:35:44 +02:00
Elsa.AI.Copilot.UnitTests Use Copilot SDK for Weaver agent loop (#7700) 2026-06-08 00:35:47 +02:00
Elsa.AI.Host.UnitTests Increase AI host test coverage 2026-06-27 04:04:39 +02:00
Elsa.AI.Persistence.EFCore.UnitTests chore: apply safe dependency upgrades (#7896) 2026-07-26 21:42:27 +02:00
Elsa.Api.Common.UnitTests fix(auth): validate wildcard permission patterns and warn on deny-list stripping (#7997) 2026-08-27 11:45:54 +02:00
Elsa.Bpmn.Interchange.UnitTests feat(bpmn): interchange endpoints (analyze, import, export) (#7954) 2026-08-18 05:20:42 +02:00
Elsa.Bpmn.UnitTests feat(core): let a container withdraw work it scheduled but must not run (#7967) 2026-08-20 23:29:46 +02:00
Elsa.Common.UnitTests refactor: update package reference configuration 2026-08-03 13:15:18 +02:00
Elsa.Dashboard.Api.UnitTests [codex] Extract dashboard contributors into companion modules (#7692) 2026-06-05 14:30:52 +02:00
Elsa.Diagnostics.ConsoleLogs.UnitTests refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Elsa.Diagnostics.OpenTelemetry.UnitTests [codex] Fix console log metadata and type resolution (#7542) 2026-05-30 22:52:01 +02:00
Elsa.Diagnostics.StructuredLogs.Persistence.Relational.UnitTests Fix structured log diagnostics registration 2026-05-17 08:42:23 +02:00
Elsa.Diagnostics.StructuredLogs.UnitTests refactor(auth)!: retire the legacy permission constants and duplicate descriptor types (#7987) 2026-08-25 06:04:32 +02:00
Elsa.Expressions.UnitTests [codex] Harden C# expression host-code execution (#7519) 2026-05-21 00:50:25 +02:00
Elsa.ExternalAuthentication.UnitTests fix(external-authentication): scope role-deletion impact to the role's tenant (#8036) 2026-09-06 22:34:44 -07:00
Elsa.Features.UnitTests fix(features): support features introduced during Module.Apply() (#7966) 2026-08-20 23:53:07 +02:00
Elsa.Hosting.Management.UnitTests feat: introduce HTTP webhooks module 2026-08-03 02:08:15 +02:00
Elsa.Http.UnitTests Fix scheduling startup backlog catch-up 2026-06-21 19:43:09 +02:00
Elsa.Identity.UnitTests fix(identity): publish the role security notification after role deletion (#8026) 2026-09-06 19:44:36 -07:00
Elsa.Mediator.UnitTests [codex] Fix diagnostics live feed regressions (#7548) 2026-05-31 09:40:02 +02:00
Elsa.Persistence.EFCore.UnitTests test: cover Oracle bulk upsert SQL generation 2026-07-11 14:35:36 +02:00
Elsa.Persistence.VNext.UnitTests feat(secrets)!: scope secrets to tenants (#7991) 2026-08-26 01:56:55 +02:00
Elsa.Platform.Integration.UnitTests Add Platform integration 2026-06-18 18:27:27 +02:00
Elsa.Resilience.Core.UnitTests test(resilience): cover Elsa.Resilience.Core and lift its coverage gate off the Debug/Release seam (#7971) 2026-08-21 03:01:51 +02:00
Elsa.SasTokens.UnitTests [codex] Harden initial security remediation slice (#7495) 2026-05-22 00:45:47 +02:00
Elsa.Scheduling.UnitTests address greptile review feedback (greploop iteration 1) 2026-06-21 19:43:09 +02:00
Elsa.Secrets.UnitTests feat(secrets)!: scope secrets to tenants (#7991) 2026-08-26 01:56:55 +02:00
Elsa.Shells.Api.Tests test: fix two intermittent test failures (#7957) (#7965) 2026-08-20 23:30:38 +02:00
Elsa.Tenants.UnitTests fix: restore request services after tenant middleware exceptions 2026-07-30 09:54:00 +03:00
Elsa.UserTasks.Persistence.ConformanceTests feat(user-tasks)!: migrate endpoints to structured permissions (#7999) 2026-08-27 12:06:52 +02:00
Elsa.UserTasks.Persistence.EFCore.UnitTests feat(user-tasks)!: migrate endpoints to structured permissions (#7999) 2026-08-27 12:06:52 +02:00
Elsa.UserTasks.UnitTests feat(user-tasks)!: migrate endpoints to structured permissions (#7999) 2026-08-27 12:06:52 +02:00
Elsa.Workflows.Api.UnitTests Honor label filters when listing workflow definitions (#8035) 2026-09-05 22:13:16 -07:00
Elsa.Workflows.Core.UnitTests Merge remote-tracking branch 'origin/release/3.8.0' 2026-09-06 00:06:14 +02:00
Elsa.Workflows.Management.UnitTests Add output converter support at binding boundaries 2026-08-21 22:22:46 +02:00
Elsa.Workflows.Runtime.UnitTests fix(runtime): let a trigger index payloads under per-payload stimulus names (#7950) 2026-08-17 23:43:59 +02:00
Directory.Build.props Add coverage enforcement for test projects (#6950) 2025-10-07 19:59:53 +02:00