Merge branch 'fix/schema-edit-approval-gate' into 'main'
fix(schema): enforce approvalRequired on PUT/PATCH schema endpoints See merge request 2-6/2-6-4/terravault/ordinis!181
This commit is contained in:
+24
@@ -126,6 +126,30 @@ public class DictionaryDefinitionService {
|
|||||||
var existing = findByName(name);
|
var existing = findByName(name);
|
||||||
var prevSchema = existing.getSchemaJson();
|
var prevSchema = existing.getSchemaJson();
|
||||||
|
|
||||||
|
// Approval gate: dictionaries marked approvalRequired must route SCHEMA
|
||||||
|
// changes through the schema-draft workflow. Non-schema metadata (display
|
||||||
|
// name, description, locales, projection flag) are still allowed inline —
|
||||||
|
// those don't affect record validation. Two backdoors closed here:
|
||||||
|
// 1. Editing schemaJson on an approvalRequired dict bypassed review
|
||||||
|
// (the SchemaDrivenForm "edit schema" button hits PUT /{name},
|
||||||
|
// which had no gate). Reported 2026-05-14.
|
||||||
|
// 2. Same request could simultaneously flip approvalRequired:true→false
|
||||||
|
// (lines below) and rewrite schemaJson — review-bypass via toggle.
|
||||||
|
// We block both: if the dict is currently approvalRequired and the
|
||||||
|
// request either changes schemaJson or tries to flip the flag off,
|
||||||
|
// reject with 422 approval_required.
|
||||||
|
boolean schemaChanged = !java.util.Objects.equals(prevSchema, req.schemaJson());
|
||||||
|
boolean tryingToDisableApproval =
|
||||||
|
req.approvalRequired() != null && !req.approvalRequired();
|
||||||
|
if (existing.isApprovalRequired() && (schemaChanged || tryingToDisableApproval)) {
|
||||||
|
throw OrdinisException.unprocessableEntity(
|
||||||
|
"approval_required",
|
||||||
|
"Schema changes require the draft workflow on this dictionary. "
|
||||||
|
+ "Use POST /api/v1/admin/dictionaries/" + name
|
||||||
|
+ "/schema-drafts. Toggling approvalRequired off is also "
|
||||||
|
+ "blocked here — needs an explicit admin endpoint.");
|
||||||
|
}
|
||||||
|
|
||||||
existing.setDisplayName(req.displayName());
|
existing.setDisplayName(req.displayName());
|
||||||
existing.setDescription(req.description());
|
existing.setDescription(req.description());
|
||||||
existing.setSchemaJson(req.schemaJson());
|
existing.setSchemaJson(req.schemaJson());
|
||||||
|
|||||||
+15
@@ -89,6 +89,21 @@ public class SchemaPatchService {
|
|||||||
throw OrdinisException.notFound(
|
throw OrdinisException.notFound(
|
||||||
"dictionary_not_found", "Dictionary not found: " + dictName);
|
"dictionary_not_found", "Dictionary not found: " + dictName);
|
||||||
}
|
}
|
||||||
|
// Approval gate: dictionaries marked approvalRequired must route ALL
|
||||||
|
// schema changes — even non-breaking inline patches — through the
|
||||||
|
// schema-draft maker-checker workflow. Otherwise a maker could bypass
|
||||||
|
// review by relabeling fields (description / title) or expanding
|
||||||
|
// numeric/enum ranges. Records service already enforces this for row
|
||||||
|
// CRUD (see DictionaryRecordService line ~270); this closes the same
|
||||||
|
// gap for schema patches. Reported 2026-05-14 — schema edit on an
|
||||||
|
// approvalRequired dict was applying inline without review.
|
||||||
|
if (d.isApprovalRequired()) {
|
||||||
|
throw OrdinisException.unprocessableEntity(
|
||||||
|
"approval_required",
|
||||||
|
"Schema changes require the draft workflow on this dictionary. "
|
||||||
|
+ "Use POST /api/v1/admin/dictionaries/" + dictName
|
||||||
|
+ "/schema-drafts instead.");
|
||||||
|
}
|
||||||
if (!d.getSchemaVersion().equals(req.expectedHeadVersion())) {
|
if (!d.getSchemaVersion().equals(req.expectedHeadVersion())) {
|
||||||
throw OrdinisException.conflict(
|
throw OrdinisException.conflict(
|
||||||
"version_mismatch",
|
"version_mismatch",
|
||||||
|
|||||||
Reference in New Issue
Block a user