Skip to content

Commit 902f4f4

Browse files
committed
fix(V2): block staged mutations of records with unresolvable owner
The ownership guard waved through UPDATE/DELETE staging for an existing record whenever no owner could be resolved, even on tables that carry an org_uid column (program, methodology, project, unit, stakeholder, label, aef_t1_submission). A row with a null org_uid could therefore be mutated despite the home-org-only policy. Require an owner when the model defines an org_uid column, so an unresolvable owner is rejected. Home-created records carry org_uid=home and still pass; tables without an org_uid column keep resolving ownership through their parent FK chain.
1 parent 95792e4 commit 902f4f4

1 file changed

Lines changed: 17 additions & 2 deletions

File tree

src/models/v2/staging-v2.model.js

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,19 @@ class StagingV2 extends Model {
214214
return plainRecord?.[fieldName] ?? plainRecord?.[StagingV2.camelToSnake(fieldName)];
215215
}
216216

217+
// True when the model itself carries an org_uid column (e.g. program,
218+
// methodology, project, unit, stakeholder, label, aef_t1_submission).
219+
// Such records are expected to resolve to an owning org, so when none can
220+
// be resolved (e.g. a null org_uid) the mutation is blocked rather than
221+
// waved through. Tables without an org_uid column resolve ownership via
222+
// their parent FK chain instead (see hasOwnershipFields).
223+
static modelHasOrgUidColumn(ModelClass) {
224+
const attrs = typeof ModelClass?.getAttributes === 'function'
225+
? ModelClass.getAttributes()
226+
: ModelClass?.rawAttributes;
227+
return Boolean(attrs && (attrs.orgUid || attrs.org_uid));
228+
}
229+
217230
static hasOwnershipFields(record, table) {
218231
const primaryKeyField = getV2PrimaryKeyField(table);
219232
const primaryKeyApiField = primaryKeyField?.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
@@ -364,11 +377,13 @@ class StagingV2 extends Model {
364377
const existingOwnerOrgUids = await StagingV2.collectOwnerOrgUids(
365378
existingRecord, options, new Set(), 0, false, existingUnresolved, tableExcludedFields,
366379
);
367-
const hasOwnershipChain = StagingV2.hasOwnershipFields(existingRecord, values.table);
380+
const requireOwner =
381+
StagingV2.hasOwnershipFields(existingRecord, values.table) ||
382+
StagingV2.modelHasOrgUidColumn(ModelClass);
368383
await StagingV2.assertOwnerOrgUidsAreHome(
369384
existingOwnerOrgUids,
370385
values.table,
371-
hasOwnershipChain,
386+
requireOwner,
372387
existingUnresolved,
373388
homeOrgUid,
374389
);

0 commit comments

Comments
 (0)