Skip to content

Commit 3f00628

Browse files
Address copilot pass 5 on #2
JSON-LD lets `controller` be a string, object with @id/id, OR an array of either form. The previous code assumed string|@id-object, so an array silently produced an undefined expected controller and disabled the mismatch check entirely. Added normalizeControllers() helper that handles all four shapes (string, object-with-@id, object-with-id-shorthand, array) and returns a list of absolutized IRIs. Both the profile's outer controller and each VM's controller are normalized through it; a VM passes if any of its controllers is in the profile's expected controller set. Verified against five edge cases: array containing the WebID (passes), object-with-id shorthand (passes), VM matching second array entry (passes), VM matching neither (correctly fails), and VM with array-valued controller (passes when intersection is non-empty).
1 parent c1fe422 commit 3f00628

1 file changed

Lines changed: 32 additions & 11 deletions

File tree

lib/lws-cid.js

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -132,15 +132,17 @@ export function runLwsCidChecks(profile, { webIdUrl }) {
132132
// to `@id`. Most profiles are self-controlled (controller === @id)
133133
// so the two are equal; for delegated-control profiles, VMs are
134134
// expected to be controlled by whoever the profile says controls
135-
// it, not by the WebID itself.
136-
const expectedCtrlSource = controller !== undefined
137-
? (typeof controller === 'string' ? controller : controller['@id'])
138-
: id;
139-
const expectedCtrl = absolutize(expectedCtrlSource, requestedNoHash);
135+
// it. JSON-LD allows `controller` to be a string, an object with
136+
// `@id`/`id`, or an array of either form — each VM's controller
137+
// need only match one of them.
138+
const expectedCtrls = normalizeControllers(
139+
controller !== undefined ? controller : id,
140+
requestedNoHash,
141+
);
140142
const seenIds = new Set();
141143
let allOk = true;
142144
for (const [i, vm] of vms.entries()) {
143-
const probs = validateVerificationMethod(vm, requestedNoHash, expectedCtrl);
145+
const probs = validateVerificationMethod(vm, requestedNoHash, expectedCtrls);
144146
if (probs.length === 0) continue;
145147
allOk = false;
146148
out.push({
@@ -316,7 +318,7 @@ function expandsToCid(termValue, ctx) {
316318
return false;
317319
}
318320

319-
function validateVerificationMethod(vm, baseUrl, controllerUrl) {
321+
function validateVerificationMethod(vm, baseUrl, expectedCtrls) {
320322
const probs = [];
321323
if (typeof vm !== 'object' || vm === null) {
322324
return ['entry is not an object'];
@@ -326,10 +328,11 @@ function validateVerificationMethod(vm, baseUrl, controllerUrl) {
326328
if (!vm.type) probs.push('missing type');
327329
if (!vm.controller) {
328330
probs.push('missing controller');
329-
} else if (controllerUrl) {
330-
const ctrlAbs = absolutize(typeof vm.controller === 'string' ? vm.controller : vm.controller['@id'], baseUrl);
331-
if (ctrlAbs !== controllerUrl) {
332-
probs.push(`controller mismatch: ${ctrlAbs}${controllerUrl}`);
331+
} else if (expectedCtrls && expectedCtrls.length > 0) {
332+
const vmCtrls = normalizeControllers(vm.controller, baseUrl);
333+
const matched = vmCtrls.some((c) => expectedCtrls.includes(c));
334+
if (!matched) {
335+
probs.push(`controller mismatch: ${vmCtrls.join(', ') || '(empty)'} ∉ {${expectedCtrls.join(', ')}}`);
333336
}
334337
}
335338
if (!vm.publicKeyJwk && !vm.publicKeyMultibase) {
@@ -338,6 +341,24 @@ function validateVerificationMethod(vm, baseUrl, controllerUrl) {
338341
return probs;
339342
}
340343

344+
/**
345+
* Normalize a JSON-LD controller value (string | { '@id' | 'id': string }
346+
* | array of either) to a list of absolutized IRI strings.
347+
*/
348+
function normalizeControllers(value, baseUrl) {
349+
if (value === undefined || value === null) return [];
350+
const list = Array.isArray(value) ? value : [value];
351+
const out = [];
352+
for (const v of list) {
353+
let iri;
354+
if (typeof v === 'string') iri = v;
355+
else if (v && typeof v === 'object') iri = v['@id'] ?? v.id;
356+
if (typeof iri !== 'string' || iri.length === 0) continue;
357+
out.push(absolutize(iri, baseUrl));
358+
}
359+
return out;
360+
}
361+
341362
function asArray(v) {
342363
if (v === undefined || v === null) return [];
343364
return Array.isArray(v) ? v : [v];

0 commit comments

Comments
 (0)