fix(mongodb): preserve and resolve tenant-agnostic ("*") entities#155
fix(mongodb): preserve and resolve tenant-agnostic ("*") entities#155arledesma wants to merge 2 commits into
Conversation
The MongoDb store overwrote the tenant-agnostic marker on write and excluded agnostic rows on read, so tenant-agnostic entities (e.g. CLR workflow definitions stamped by ClrWorkflowsProvider with "*") were persisted with a tenant-specific id and became unreachable when resolved under a specific tenant, throwing WorkflowDefinitionNotFoundException. Bring MongoDbStore to parity with the EFCore provider: - ApplyTenantId: don't overwrite TenantId when it is already "*" - GetQueryableCollection: include TenantId == "*" in the tenant filter Refs elsa-workflows/elsa-core#7691
|
@dotnet-policy-service agree company="ContraForce" |
|
@greptile review |
There was a problem hiding this comment.
Pull request overview
Fixes MongoDB multitenancy behavior so tenant-agnostic ("*") entities are preserved on write and resolvable from within a specific tenant on read, aligning MongoDB persistence behavior with the EFCore provider and the documented tenant-agnostic contract.
Changes:
- Update tenant-scoped query filtering to also include tenant-agnostic (
TenantId == "*") documents. - Prevent
ApplyTenantIdfrom overwriting documents already marked as tenant-agnostic ("*"), preserving the marker during inserts/updates.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var tenantId = tenant?.Id.EmptyToNull(); | ||
| queryable = queryable.Where(x => (x as Entity)!.TenantId == tenantId); | ||
| // Include tenant-agnostic ("*") rows so global entities (e.g. CLR workflow | ||
| // definitions) remain visible under a specific tenant, matching the EFCore provider. | ||
| queryable = queryable.Where(x => (x as Entity)!.TenantId == tenantId || (x as Entity)!.TenantId == Tenant.AgnosticTenantId); |
Greptile SummaryThis PR fixes MongoDB multi-tenancy handling to align with the EFCore provider's treatment of tenant-agnostic (
Confidence Score: 5/5Safe to merge — the two private methods are changed consistently, all delete overloads protect global entities, and the fix matches documented EFCore provider behaviour. The write fix, read fix, and delete guard are all internally consistent. The second commit in this PR directly addresses the delete-path concern raised in the prior review. Every DeleteWhereAsync call chain routes through one of the two patched terminal overloads, so no delete path regressed. Read and count operations correctly include '*' entities, keeping counts in sync with result sets. No files require special attention.
|
| Filename | Overview |
|---|---|
| src/modules/persistence/Elsa.Persistence.MongoDb/Common/MongoDbStore.cs | Three targeted fixes: ApplyTenantId now skips stamping entities already marked ""; GetQueryableCollection includes TenantId == "" in tenant-scoped reads (via new includeTenantAgnostic param, default true); both DeleteWhereAsync implementations explicitly pass includeTenantAgnostic: false so global entities cannot be destroyed by a tenant-scoped delete. All delete call chains route through the two patched overloads. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[GetQueryableCollection called] --> B{tenantAgnostic == true?}
B -- Yes --> C[Return unfiltered queryable\nall tenants + all '*' entities]
B -- No --> D{TDocument is Entity?}
D -- No --> E[Return unfiltered queryable]
D -- Yes --> F{includeTenantAgnostic?}
F -- true\ndefault reads --> G["Filter: TenantId == tenantId OR TenantId == '*'"]
F -- false\ndeletes --> H["Filter: TenantId == tenantId only"]
subgraph Callers
I[FindAsync / FindManyAsync / CountAsync / AnyAsync / ListAsync] --> |includeTenantAgnostic=true| F
J[DeleteWhereAsync both overloads] --> |includeTenantAgnostic=false| F
end
subgraph Write path
K[ApplyTenantId] --> L{entity.TenantId == '*'?}
L -- Yes --> M[Skip: preserve agnostic marker]
L -- No --> N[Stamp with tenantId.EmptyToNull]
end
Reviews (2): Last reviewed commit: "fix(mongodb): exclude tenant-agnostic ("..." | Re-trigger Greptile
…ed deletes GetQueryableCollection is shared by the read and delete paths. The read fix added an OR-branch for TenantId == "*", which also widened DeleteWhereAsync: a tenant-scoped delete could match and remove a shared "*" entity for every tenant (e.g. a CLR workflow definition deleted under one tenant). Add an includeTenantAgnostic flag (default true). Reads keep including "*" rows; both DeleteWhereAsync terminals pass includeTenantAgnostic: false so a tenant-scoped delete stays strict to the current tenant, matching the Dapper store. An explicit tenantAgnostic: true delete is unchanged, and the agnostic context (Tenant.Id == "*") can still manage "*" entities.
|
Addressed the delete-path concern in 362c5d1.
Fix: added an |
|
@greptile review |
The MongoDb store overwrote the tenant-agnostic marker on write and excluded agnostic rows on read, so tenant-agnostic entities (e.g. CLR workflow definitions stamped by ClrWorkflowsProvider with "*") were persisted with a tenant-specific id and became unreachable when resolved under a specific tenant, throwing WorkflowDefinitionNotFoundException.
Bring MongoDbStore to parity with the EFCore provider:
Behavior change / migration
This is an internal store fix (private methods only, no public API change), but it does change observable behavior to match the EFCore provider and the documented tenant-agnostic (
"*") contract:TenantId == "*"(global) rows, instead of strictTenantId == currentonly.ApplyTenantIdno longer overwrites an entity whoseTenantIdis already"*", so agnostic entities (e.g. CLR workflow definitions) stay agnostic instead of being stamped with the ambient tenant.Migration note for existing MongoDb deployments. Because of the previous behavior, agnostic entities were already persisted with
TenantId = null(the marker was clobbered on write). The new read filter adds|| TenantId == "*"; it does not match those legacynullrows. As a result:"*") and resolve under any tenant.nulland stay invisible under a specific tenant until corrected.To fix existing data, set
TenantId = "*"on the affected documents, for example for workflow definitions:
js db.workflow_definitions.updateMany( { TenantId: null }, { $set: { TenantId: "*" } } ) Alternatively, re-run workflow definition population so the agnostic marker is written under the corrected logic. Deployments that never used multitenancy, or that started fresh on this version, need no migration.
Refs #158