@fedify/vocab-tools 2.4.0-pr.936.41 → 2.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -0
- package/deno.json +1 -1
- package/dist/mod.cjs +570 -123
- package/dist/mod.d.cts +14 -0
- package/dist/mod.d.ts +14 -0
- package/dist/mod.js +570 -123
- package/dist/schema.yaml +15 -0
- package/package.json +2 -2
- package/src/__snapshots__/class.test.ts.deno.snap +54490 -12168
- package/src/__snapshots__/class.test.ts.node.snap +54490 -12168
- package/src/__snapshots__/class.test.ts.snap +54490 -12168
- package/src/class.ts +116 -0
- package/src/codec.ts +133 -34
- package/src/constructor.ts +16 -4
- package/src/inspector.ts +4 -4
- package/src/property.ts +343 -70
- package/src/schema.ts +16 -0
- package/src/schema.yaml +15 -0
- package/src/type.ts +20 -8
package/src/property.ts
CHANGED
|
@@ -29,6 +29,7 @@ async function* generateProperty(
|
|
|
29
29
|
moduleVarNames: ReadonlyMap<string, string>,
|
|
30
30
|
): AsyncIterable<string> {
|
|
31
31
|
const override = emitOverride(type.uri, types, property);
|
|
32
|
+
const trustOwner = type.trustEmbeddedObjects === false ? "null" : "this.id";
|
|
32
33
|
const doc = `\n/** ${property.description.replaceAll("\n", "\n * ")}\n */\n`;
|
|
33
34
|
const cachedPropertyBaseUrl = `(options as { baseUrl?: URL }).baseUrl ??
|
|
34
35
|
this._baseUrl ??
|
|
@@ -73,6 +74,9 @@ async function* generateProperty(
|
|
|
73
74
|
suppressError?: boolean,
|
|
74
75
|
tracerProvider?: TracerProvider,
|
|
75
76
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
77
|
+
gateways?: readonly (string | URL)[];
|
|
78
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
79
|
+
inheritPortableObjectVerifier?: boolean;
|
|
76
80
|
} = {},
|
|
77
81
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
78
82
|
const documentLoader =
|
|
@@ -85,11 +89,96 @@ async function* generateProperty(
|
|
|
85
89
|
${JSON.stringify(metadata.name)},
|
|
86
90
|
${JSON.stringify(metadata.version)},
|
|
87
91
|
);
|
|
92
|
+
// The fetched object uses the verifier given to this call (which falls
|
|
93
|
+
// back to the parent's own default) for its accessors by default,
|
|
94
|
+
// unless the call keeps its verifier to itself:
|
|
95
|
+
const childVerifier = options.inheritPortableObjectVerifier === false
|
|
96
|
+
? this._verifyPortableObject
|
|
97
|
+
: options.verifyPortableObject;
|
|
88
98
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
99
|
+
const dereferencePortable = async (
|
|
100
|
+
portableUrl: URL,
|
|
101
|
+
extra: {
|
|
102
|
+
inferredGateways?: readonly URL[];
|
|
103
|
+
response?: RemoteDocument;
|
|
104
|
+
contextLoader?: DocumentLoader;
|
|
105
|
+
} = {},
|
|
106
|
+
) => {
|
|
107
|
+
try {
|
|
108
|
+
const obj = await dereferencePortableIri(portableUrl, {
|
|
109
|
+
documentLoader,
|
|
110
|
+
contextLoader: extra.contextLoader ?? contextLoader,
|
|
111
|
+
tracerProvider,
|
|
112
|
+
gateways: options.gateways,
|
|
113
|
+
inferredGateways: extra.inferredGateways,
|
|
114
|
+
response: extra.response,
|
|
115
|
+
verifyPortableObject: options.verifyPortableObject,
|
|
116
|
+
referrer: {
|
|
117
|
+
object: this,
|
|
118
|
+
property: ${JSON.stringify(property.uri)},
|
|
119
|
+
},
|
|
120
|
+
suppressError: options.suppressError,
|
|
121
|
+
crossOrigin: options.crossOrigin,
|
|
122
|
+
span,
|
|
123
|
+
parse: (document, parseOptions) =>
|
|
124
|
+
this.#${property.singularName}_fromJsonLd(document, {
|
|
125
|
+
documentLoader,
|
|
126
|
+
contextLoader: parseOptions.contextLoader,
|
|
127
|
+
tracerProvider,
|
|
128
|
+
verifyPortableObject: childVerifier,
|
|
129
|
+
baseUrl: parseOptions.baseUrl,
|
|
130
|
+
}),
|
|
131
|
+
});
|
|
132
|
+
if (obj != null) {
|
|
133
|
+
span.setAttribute(
|
|
134
|
+
"activitypub.object.id",
|
|
135
|
+
(obj.id ?? portableUrl).href,
|
|
136
|
+
);
|
|
137
|
+
span.setAttribute(
|
|
138
|
+
"activitypub.object.type",
|
|
139
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
140
|
+
obj.constructor.typeId.href
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return obj;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
span.setStatus({
|
|
146
|
+
code: SpanStatusCode.ERROR,
|
|
147
|
+
message: String(e),
|
|
148
|
+
});
|
|
149
|
+
throw e;
|
|
150
|
+
} finally {
|
|
151
|
+
span.end();
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
if (isPortableIri(url)) {
|
|
155
|
+
// A reference in a portable actor's own document, e.g., its outbox,
|
|
156
|
+
// has no location hints, so the actor's gateways are used instead:
|
|
157
|
+
return await dereferencePortable(
|
|
158
|
+
url,
|
|
159
|
+
options.gateways == null
|
|
160
|
+
? { inferredGateways: getReferrerGateways(this, url) }
|
|
161
|
+
: {},
|
|
162
|
+
);
|
|
163
|
+
}
|
|
89
164
|
const lookupUrl = formatIri(url);
|
|
165
|
+
const portableMode = isPortableMode(this, options);
|
|
166
|
+
if (portableMode) {
|
|
167
|
+
const compatible = parseCompatibleEf61Reference(url);
|
|
168
|
+
if (compatible === null) {
|
|
169
|
+
span.end();
|
|
170
|
+
return rejectMalformedCompatibleReference(lookupUrl, options);
|
|
171
|
+
} else if (compatible != null) {
|
|
172
|
+
return await dereferencePortable(compatible.id, {
|
|
173
|
+
inferredGateways: [compatible.gateway],
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
90
177
|
let fetchResult: RemoteDocument;
|
|
91
178
|
try {
|
|
92
|
-
fetchResult = await documentLoader(lookupUrl
|
|
179
|
+
fetchResult = await documentLoader(lookupUrl, {
|
|
180
|
+
suppressError: options.suppressError,
|
|
181
|
+
});
|
|
93
182
|
} catch (error) {
|
|
94
183
|
span.setStatus({
|
|
95
184
|
code: SpanStatusCode.ERROR,
|
|
@@ -97,7 +186,7 @@ async function* generateProperty(
|
|
|
97
186
|
});
|
|
98
187
|
span.end();
|
|
99
188
|
if (options.suppressError) {
|
|
100
|
-
getLogger(["fedify", "vocab"]).
|
|
189
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
101
190
|
"Failed to fetch {url}: {error}",
|
|
102
191
|
{ error, url: lookupUrl }
|
|
103
192
|
);
|
|
@@ -106,52 +195,96 @@ async function* generateProperty(
|
|
|
106
195
|
throw error;
|
|
107
196
|
}
|
|
108
197
|
const { document, documentUrl } = fetchResult;
|
|
109
|
-
|
|
198
|
+
// In portable mode, the document may turn out to be a portable object,
|
|
199
|
+
// which has to be verified with the same context documents:
|
|
200
|
+
const snapshot = portableMode
|
|
201
|
+
? createSnapshotContextLoader(contextLoader, options.suppressError)
|
|
202
|
+
: null;
|
|
203
|
+
const scopedContext = createScopedContextLoader(
|
|
204
|
+
snapshot?.loader ?? contextLoader,
|
|
205
|
+
snapshot == null && options.suppressError,
|
|
206
|
+
);
|
|
110
207
|
try {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
208
|
+
let claim: PortableResponseClaim | null | undefined;
|
|
209
|
+
try {
|
|
210
|
+
const baseUrl = parseIri(documentUrl);
|
|
211
|
+
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
212
|
+
document,
|
|
213
|
+
{
|
|
214
|
+
documentLoader,
|
|
215
|
+
contextLoader: scopedContext.loader,
|
|
216
|
+
tracerProvider,
|
|
217
|
+
verifyPortableObject: childVerifier,
|
|
218
|
+
baseUrl,
|
|
219
|
+
}
|
|
220
|
+
);
|
|
221
|
+
if (snapshot == null) {
|
|
222
|
+
markUnverifiedPortableClaim(obj, url, documentUrl);
|
|
223
|
+
} else {
|
|
224
|
+
claim = getPortableResponseClaim(documentUrl, obj.id);
|
|
225
|
+
}
|
|
226
|
+
if (claim === undefined) {
|
|
227
|
+
if (
|
|
228
|
+
obj?.id != null &&
|
|
229
|
+
!isTrustedIriOrigin(options, obj.id, baseUrl)
|
|
230
|
+
) {
|
|
231
|
+
if (options.crossOrigin === "throw") {
|
|
232
|
+
throw new Error(
|
|
233
|
+
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
234
|
+
"than the document URL (" + baseUrl.href + "); refusing to return " +
|
|
235
|
+
"the object. If you want to bypass this check and are aware of" +
|
|
236
|
+
'the security implications, set the crossOrigin option to "trust".'
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
240
|
+
"The object's @id ({objectId}) has a different origin than the document " +
|
|
241
|
+
"URL ({documentUrl}); refusing to return the object. If you want to " +
|
|
242
|
+
"bypass this check and are aware of the security implications, " +
|
|
243
|
+
'set the crossOrigin option to "trust".',
|
|
244
|
+
{ ...fetchResult, objectId: obj.id.href },
|
|
245
|
+
);
|
|
246
|
+
span.end();
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
|
|
250
|
+
span.setAttribute(
|
|
251
|
+
"activitypub.object.type",
|
|
252
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
253
|
+
obj.constructor.typeId.href
|
|
122
254
|
);
|
|
255
|
+
span.end();
|
|
256
|
+
return obj;
|
|
123
257
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
258
|
+
} catch (e) {
|
|
259
|
+
if (options.suppressError) {
|
|
260
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
261
|
+
"Failed to parse {url}: {error}",
|
|
262
|
+
{ error: e, url: lookupUrl }
|
|
263
|
+
);
|
|
264
|
+
span.end();
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
span.setStatus({
|
|
268
|
+
code: SpanStatusCode.ERROR,
|
|
269
|
+
message: String(e),
|
|
270
|
+
});
|
|
271
|
+
span.end();
|
|
272
|
+
throw e;
|
|
132
273
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
);
|
|
139
|
-
return obj;
|
|
140
|
-
} catch (e) {
|
|
141
|
-
if (options.suppressError) {
|
|
142
|
-
getLogger(["fedify", "vocab"]).error(
|
|
143
|
-
"Failed to parse {url}: {error}",
|
|
144
|
-
{ error: e, url: lookupUrl }
|
|
145
|
-
);
|
|
146
|
-
return null;
|
|
274
|
+
// The document stands for a portable object, so it is verified as
|
|
275
|
+
// one instead of being trusted because of where it came from:
|
|
276
|
+
if (claim === null) {
|
|
277
|
+
span.end();
|
|
278
|
+
return rejectMalformedCompatibleReference(documentUrl, options);
|
|
147
279
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
280
|
+
return await dereferencePortable(claim.id, {
|
|
281
|
+
inferredGateways: claim.inferredGateways,
|
|
282
|
+
response: fetchResult,
|
|
283
|
+
contextLoader: snapshot?.loader,
|
|
151
284
|
});
|
|
152
|
-
throw e;
|
|
153
285
|
} finally {
|
|
154
|
-
|
|
286
|
+
scopedContext.release();
|
|
287
|
+
snapshot?.release();
|
|
155
288
|
}
|
|
156
289
|
});
|
|
157
290
|
}
|
|
@@ -162,6 +295,7 @@ async function* generateProperty(
|
|
|
162
295
|
documentLoader?: DocumentLoader,
|
|
163
296
|
contextLoader?: DocumentLoader,
|
|
164
297
|
tracerProvider?: TracerProvider,
|
|
298
|
+
verifyPortableObject?: PortableObjectVerifier,
|
|
165
299
|
baseUrl?: URL
|
|
166
300
|
}
|
|
167
301
|
): Promise<${getTypeNames(property.range, types)}> {
|
|
@@ -172,6 +306,7 @@ async function* generateProperty(
|
|
|
172
306
|
const tracerProvider = options.tracerProvider ??
|
|
173
307
|
this._tracerProvider ?? trace.getTracerProvider();
|
|
174
308
|
const baseUrl = options.baseUrl;
|
|
309
|
+
const verifyPortableObject = options.verifyPortableObject;
|
|
175
310
|
`;
|
|
176
311
|
if (
|
|
177
312
|
property.preprocessors != null &&
|
|
@@ -202,6 +337,7 @@ async function* generateProperty(
|
|
|
202
337
|
documentLoader,
|
|
203
338
|
contextLoader,
|
|
204
339
|
tracerProvider,
|
|
340
|
+
verifyPortableObject,
|
|
205
341
|
baseUrl,
|
|
206
342
|
});
|
|
207
343
|
if (_result instanceof Error) throw _result;
|
|
@@ -223,7 +359,13 @@ async function* generateProperty(
|
|
|
223
359
|
try {
|
|
224
360
|
return await ${rangeType.name}.fromJsonLd(
|
|
225
361
|
jsonLd,
|
|
226
|
-
{
|
|
362
|
+
{
|
|
363
|
+
documentLoader,
|
|
364
|
+
contextLoader,
|
|
365
|
+
tracerProvider,
|
|
366
|
+
verifyPortableObject,
|
|
367
|
+
baseUrl,
|
|
368
|
+
},
|
|
227
369
|
);
|
|
228
370
|
} catch (e) {
|
|
229
371
|
if (!(e instanceof TypeError)) throw e;
|
|
@@ -265,6 +407,21 @@ async function* generateProperty(
|
|
|
265
407
|
suppressError?: boolean,
|
|
266
408
|
tracerProvider?: TracerProvider,
|
|
267
409
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
410
|
+
gateways?: readonly (string | URL)[];
|
|
411
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
412
|
+
/**
|
|
413
|
+
* Whether objects that this call newly fetches use the
|
|
414
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
415
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
416
|
+
* applies to this call only, and fetched objects use this object's
|
|
417
|
+
* own default verifier, if any, as their default instead. Pass
|
|
418
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
419
|
+
* everything. Objects that were already cached in this object or
|
|
420
|
+
* embedded in it keep their defaults either way, and this option
|
|
421
|
+
* itself is not passed on.
|
|
422
|
+
* @since 2.4.0
|
|
423
|
+
*/
|
|
424
|
+
inheritPortableObjectVerifier?: boolean;
|
|
268
425
|
} = {}
|
|
269
426
|
): Promise<${getTypeNames(property.range, types)} | null> {
|
|
270
427
|
if (this._warning != null) {
|
|
@@ -274,20 +431,46 @@ async function* generateProperty(
|
|
|
274
431
|
);
|
|
275
432
|
}
|
|
276
433
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
434
|
+
if (
|
|
435
|
+
options.verifyPortableObject == null &&
|
|
436
|
+
this._verifyPortableObject != null
|
|
437
|
+
) {
|
|
438
|
+
options = {
|
|
439
|
+
...options,
|
|
440
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
277
443
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
278
444
|
if (!(v instanceof URL) &&
|
|
279
|
-
v.id != null &&
|
|
280
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
281
445
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
282
|
-
|
|
446
|
+
if (v.id == null) {
|
|
447
|
+
if (isUnsecuredPortableObject(this)) {
|
|
448
|
+
warnUnverifiableEmbeddedObject(this, ${
|
|
449
|
+
JSON.stringify(property.uri)
|
|
450
|
+
});
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
} else if (
|
|
454
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
455
|
+
isUnsecuredPortableObject(this) ||
|
|
456
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
457
|
+
) {
|
|
458
|
+
v = v.id;
|
|
459
|
+
}
|
|
283
460
|
}
|
|
284
461
|
if (v instanceof URL) {
|
|
285
462
|
const fetched =
|
|
286
463
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
287
464
|
if (fetched == null) return null;
|
|
465
|
+
// Not cached, so that it is verified when the portable object
|
|
466
|
+
// policy applies later:
|
|
467
|
+
if (isUnverifiedPortableClaim(fetched)) return fetched;
|
|
288
468
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
289
469
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
290
470
|
this._cachedJsonLd = undefined;
|
|
471
|
+
recordPortableReferrer(this, fetched, ${
|
|
472
|
+
JSON.stringify(property.uri)
|
|
473
|
+
});
|
|
291
474
|
return fetched;
|
|
292
475
|
}
|
|
293
476
|
`;
|
|
@@ -303,26 +486,43 @@ async function* generateProperty(
|
|
|
303
486
|
${JSON.stringify(property.compactName)}];
|
|
304
487
|
const doc = Array.isArray(prop) ? prop[0] : prop;
|
|
305
488
|
if (doc != null && typeof doc === "object" && "@context" in doc) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
489
|
+
try {
|
|
490
|
+
v = await this.#${property.singularName}_fromJsonLd(doc, {
|
|
491
|
+
...options,
|
|
492
|
+
// Keep the parent default for embedded objects.
|
|
493
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
494
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
495
|
+
});
|
|
496
|
+
} catch (error) {
|
|
497
|
+
if (!options.suppressError) throw error;
|
|
498
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
499
|
+
"Failed to parse embedded value of {property}: {error}",
|
|
500
|
+
{ property: ${JSON.stringify(property.uri)}, error },
|
|
501
|
+
);
|
|
502
|
+
return null;
|
|
503
|
+
}
|
|
314
504
|
}
|
|
315
505
|
}
|
|
316
506
|
`;
|
|
317
507
|
}
|
|
318
508
|
yield `
|
|
319
509
|
if (v?.id != null &&
|
|
320
|
-
|
|
510
|
+
((${
|
|
511
|
+
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
512
|
+
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
513
|
+
isUnsecuredPortableObject(this) ||
|
|
514
|
+
mustDereferencePortableObject(
|
|
515
|
+
this, ${trustOwner}, v.id, options,
|
|
516
|
+
)) &&
|
|
321
517
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
322
518
|
if (options.crossOrigin === "throw") {
|
|
323
519
|
throw new Error(
|
|
324
520
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
325
|
-
"origin than the property owner's @id (" +
|
|
521
|
+
"origin than the property owner's @id (" + ${
|
|
522
|
+
type.trustEmbeddedObjects === false
|
|
523
|
+
? '"untrusted metadata"'
|
|
524
|
+
: "(this.id?.href ?? null)"
|
|
525
|
+
} + "); " +
|
|
326
526
|
"refusing to return the object. If you want to bypass this " +
|
|
327
527
|
"check and are aware of the security implications, set the " +
|
|
328
528
|
'crossOrigin option to "trust".'
|
|
@@ -334,10 +534,15 @@ async function* generateProperty(
|
|
|
334
534
|
"return the object. If you want to bypass this check and are " +
|
|
335
535
|
"aware of the security implications, set the crossOrigin option " +
|
|
336
536
|
'to "trust".',
|
|
337
|
-
{ objectId: v.id.href, parentObjectId:
|
|
537
|
+
{ objectId: v.id.href, parentObjectId: ${
|
|
538
|
+
type.trustEmbeddedObjects === false
|
|
539
|
+
? '"untrusted metadata"'
|
|
540
|
+
: "(this.id?.href ?? null)"
|
|
541
|
+
} },
|
|
338
542
|
);
|
|
339
543
|
return null;
|
|
340
544
|
}
|
|
545
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
341
546
|
return v;
|
|
342
547
|
}
|
|
343
548
|
`;
|
|
@@ -370,6 +575,21 @@ async function* generateProperty(
|
|
|
370
575
|
suppressError?: boolean,
|
|
371
576
|
tracerProvider?: TracerProvider,
|
|
372
577
|
crossOrigin?: "ignore" | "throw" | "trust";
|
|
578
|
+
gateways?: readonly (string | URL)[];
|
|
579
|
+
verifyPortableObject?: PortableObjectVerifier;
|
|
580
|
+
/**
|
|
581
|
+
* Whether objects that this call newly fetches use the
|
|
582
|
+
* \`verifyPortableObject\` option of this call as their default
|
|
583
|
+
* verifier. Defaults to \`true\`. If \`false\`, the option
|
|
584
|
+
* applies to this call only, and fetched objects use this object's
|
|
585
|
+
* own default verifier, if any, as their default instead. Pass
|
|
586
|
+
* \`false\` for a temporary verifier, e.g., one that accepts
|
|
587
|
+
* everything. Objects that were already cached in this object or
|
|
588
|
+
* embedded in it keep their defaults either way, and this option
|
|
589
|
+
* itself is not passed on.
|
|
590
|
+
* @since 2.4.0
|
|
591
|
+
*/
|
|
592
|
+
inheritPortableObjectVerifier?: boolean;
|
|
373
593
|
} = {}
|
|
374
594
|
): AsyncIterable<${getTypeNames(property.range, types)}> {
|
|
375
595
|
if (this._warning != null) {
|
|
@@ -378,22 +598,51 @@ async function* generateProperty(
|
|
|
378
598
|
this._warning.values
|
|
379
599
|
);
|
|
380
600
|
}
|
|
601
|
+
if (
|
|
602
|
+
options.verifyPortableObject == null &&
|
|
603
|
+
this._verifyPortableObject != null
|
|
604
|
+
) {
|
|
605
|
+
options = {
|
|
606
|
+
...options,
|
|
607
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
608
|
+
};
|
|
609
|
+
}
|
|
381
610
|
const vs = this.${await getFieldName(property.uri)};
|
|
382
611
|
for (let i = 0; i < vs.length; i++) {
|
|
383
612
|
let v = vs[i];
|
|
384
613
|
if (!(v instanceof URL) &&
|
|
385
|
-
v.id != null &&
|
|
386
|
-
!isTrustedIriOrigin(options, v.id, this.id) &&
|
|
387
614
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
388
|
-
|
|
615
|
+
if (v.id == null) {
|
|
616
|
+
if (isUnsecuredPortableObject(this)) {
|
|
617
|
+
warnUnverifiableEmbeddedObject(this, ${
|
|
618
|
+
JSON.stringify(property.uri)
|
|
619
|
+
});
|
|
620
|
+
continue;
|
|
621
|
+
}
|
|
622
|
+
} else if (
|
|
623
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
624
|
+
isUnsecuredPortableObject(this) ||
|
|
625
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
626
|
+
) {
|
|
627
|
+
v = v.id;
|
|
628
|
+
}
|
|
389
629
|
}
|
|
390
630
|
if (v instanceof URL) {
|
|
391
631
|
const fetched =
|
|
392
632
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
393
633
|
if (fetched == null) continue;
|
|
634
|
+
// Not cached, so that it is verified when the portable object
|
|
635
|
+
// policy applies later:
|
|
636
|
+
if (isUnverifiedPortableClaim(fetched)) {
|
|
637
|
+
yield fetched;
|
|
638
|
+
continue;
|
|
639
|
+
}
|
|
394
640
|
vs[i] = fetched;
|
|
395
641
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
396
642
|
this._cachedJsonLd = undefined;
|
|
643
|
+
recordPortableReferrer(this, fetched, ${
|
|
644
|
+
JSON.stringify(property.uri)
|
|
645
|
+
});
|
|
397
646
|
yield fetched;
|
|
398
647
|
continue;
|
|
399
648
|
}
|
|
@@ -410,26 +659,45 @@ async function* generateProperty(
|
|
|
410
659
|
${JSON.stringify(property.compactName)}];
|
|
411
660
|
const obj = Array.isArray(prop) ? prop[i] : prop;
|
|
412
661
|
if (obj != null && typeof obj === "object" && "@context" in obj) {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
662
|
+
try {
|
|
663
|
+
v = await this.#${property.singularName}_fromJsonLd(obj, {
|
|
664
|
+
...options,
|
|
665
|
+
// Keep the parent default for embedded objects.
|
|
666
|
+
verifyPortableObject: this._verifyPortableObject,
|
|
667
|
+
baseUrl: ${cachedPropertyBaseUrl},
|
|
668
|
+
});
|
|
669
|
+
} catch (error) {
|
|
670
|
+
if (!options.suppressError) throw error;
|
|
671
|
+
getLogger(["fedify", "vocab"]).debug(
|
|
672
|
+
"Failed to parse embedded value of {property} at index {index}: {error}",
|
|
673
|
+
{ property: ${
|
|
674
|
+
JSON.stringify(property.uri)
|
|
675
|
+
}, index: i, error },
|
|
676
|
+
);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
421
679
|
}
|
|
422
680
|
}
|
|
423
681
|
`;
|
|
424
682
|
}
|
|
425
683
|
yield `
|
|
426
684
|
if (v?.id != null &&
|
|
427
|
-
|
|
685
|
+
((${
|
|
686
|
+
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
687
|
+
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
688
|
+
isUnsecuredPortableObject(this) ||
|
|
689
|
+
mustDereferencePortableObject(
|
|
690
|
+
this, ${trustOwner}, v.id, options,
|
|
691
|
+
)) &&
|
|
428
692
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
429
693
|
if (options.crossOrigin === "throw") {
|
|
430
694
|
throw new Error(
|
|
431
695
|
"The property object's @id (" + v.id.href + ") has a different " +
|
|
432
|
-
"origin than the property owner's @id (" +
|
|
696
|
+
"origin than the property owner's @id (" + ${
|
|
697
|
+
type.trustEmbeddedObjects === false
|
|
698
|
+
? '"untrusted metadata"'
|
|
699
|
+
: "(this.id?.href ?? null)"
|
|
700
|
+
} + "); " +
|
|
433
701
|
"refusing to return the object. If you want to bypass this " +
|
|
434
702
|
"check and are aware of the security implications, set the " +
|
|
435
703
|
'crossOrigin option to "trust".'
|
|
@@ -441,10 +709,15 @@ async function* generateProperty(
|
|
|
441
709
|
"return the object. If you want to bypass this check and are " +
|
|
442
710
|
"aware of the security implications, set the crossOrigin " +
|
|
443
711
|
'option to "trust".',
|
|
444
|
-
{ objectId: v.id.href, parentObjectId:
|
|
712
|
+
{ objectId: v.id.href, parentObjectId: ${
|
|
713
|
+
type.trustEmbeddedObjects === false
|
|
714
|
+
? '"untrusted metadata"'
|
|
715
|
+
: "(this.id?.href ?? null)"
|
|
716
|
+
} },
|
|
445
717
|
);
|
|
446
718
|
continue;
|
|
447
719
|
}
|
|
720
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
448
721
|
yield v;
|
|
449
722
|
}
|
|
450
723
|
}
|
package/src/schema.ts
CHANGED
|
@@ -47,6 +47,14 @@ export interface TypeSchema {
|
|
|
47
47
|
*/
|
|
48
48
|
entity: boolean;
|
|
49
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Whether this type's id can establish same-origin trust for its own
|
|
52
|
+
* embedded entity-valued properties. Defaults to true. Set to false for
|
|
53
|
+
* metadata whose id does not identify an independently fetched resource.
|
|
54
|
+
* Locally constructed and explicitly trusted values remain trusted.
|
|
55
|
+
*/
|
|
56
|
+
trustEmbeddedObjects?: boolean;
|
|
57
|
+
|
|
50
58
|
/**
|
|
51
59
|
* Whether the type omits `@type` in JSON-LD serialization. When `true`,
|
|
52
60
|
* the generated `toJsonLd()` method will not emit `@type` (or `type` in
|
|
@@ -77,6 +85,14 @@ export interface TypeSchema {
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
export interface PropertySchemaBase {
|
|
88
|
+
/**
|
|
89
|
+
* A context URL added to the default serialization context when this
|
|
90
|
+
* property's expanded IRI appears in the compacted document. Explicit
|
|
91
|
+
* caller contexts are unchanged. Populated properties with this option
|
|
92
|
+
* use the JSON-LD processor instead of compact serialization heuristics.
|
|
93
|
+
*/
|
|
94
|
+
extraContext?: string;
|
|
95
|
+
|
|
80
96
|
/**
|
|
81
97
|
* The singular form of the property name. It is used as the name of the
|
|
82
98
|
* generated property accessors.
|
package/src/schema.yaml
CHANGED
|
@@ -72,6 +72,14 @@ $defs:
|
|
|
72
72
|
property-base:
|
|
73
73
|
type: object
|
|
74
74
|
properties:
|
|
75
|
+
extraContext:
|
|
76
|
+
description: >-
|
|
77
|
+
A context URL added to default serialization when this property's
|
|
78
|
+
expanded IRI appears in the compacted document. Explicit caller
|
|
79
|
+
contexts are unchanged. Populated properties use the JSON-LD
|
|
80
|
+
processor instead of compact serialization heuristics.
|
|
81
|
+
type: string
|
|
82
|
+
format: uri
|
|
75
83
|
singularName:
|
|
76
84
|
description: >-
|
|
77
85
|
The singular form of the property name. It is used as the name of the
|
|
@@ -250,6 +258,13 @@ properties:
|
|
|
250
258
|
|
|
251
259
|
The extended subtypes must have the consistent value of this flag.
|
|
252
260
|
type: boolean
|
|
261
|
+
trustEmbeddedObjects:
|
|
262
|
+
description: >-
|
|
263
|
+
Whether this type's id establishes same-origin trust for its own
|
|
264
|
+
embedded entity-valued properties. Defaults to true. Set to false
|
|
265
|
+
for metadata ids that do not identify independently fetched resources.
|
|
266
|
+
Locally constructed and explicitly trusted values remain trusted.
|
|
267
|
+
type: boolean
|
|
253
268
|
typeless:
|
|
254
269
|
description: >-
|
|
255
270
|
Whether the type omits @type in JSON-LD serialization. When true,
|