@fedify/vocab-tools 2.4.0-dev.2056 → 2.4.0-dev.2065
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/deno.json +1 -1
- package/dist/mod.cjs +181 -58
- package/dist/mod.js +181 -58
- package/package.json +1 -1
- package/src/__snapshots__/class.test.ts.deno.snap +21022 -7593
- package/src/__snapshots__/class.test.ts.node.snap +21022 -7593
- package/src/__snapshots__/class.test.ts.snap +21022 -7593
- package/src/class.ts +13 -0
- package/src/constructor.ts +1 -0
- package/src/property.ts +172 -58
package/src/class.ts
CHANGED
|
@@ -19,8 +19,21 @@ const INTERNAL_RUNTIME_IMPORTS = [
|
|
|
19
19
|
"normalizeJsonLdIris",
|
|
20
20
|
].join(",\n ");
|
|
21
21
|
const PORTABLE_DEREFERENCE_IMPORTS = [
|
|
22
|
+
"copyPortableProvenance",
|
|
23
|
+
"createSnapshotContextLoader",
|
|
22
24
|
"dereferencePortableIri",
|
|
25
|
+
"getPortableResponseClaim",
|
|
23
26
|
"isPortableIri",
|
|
27
|
+
"isPortableMode",
|
|
28
|
+
"isUnsecuredPortableObject",
|
|
29
|
+
"isUnverifiedPortableClaim",
|
|
30
|
+
"markUnverifiedPortableClaim",
|
|
31
|
+
"mustDereferencePortableObject",
|
|
32
|
+
"parseCompatibleEf61Reference",
|
|
33
|
+
"type PortableResponseClaim",
|
|
34
|
+
"recordPortableReferrer",
|
|
35
|
+
"rejectMalformedCompatibleReference",
|
|
36
|
+
"warnUnverifiableEmbeddedObject",
|
|
24
37
|
].join(",\n ");
|
|
25
38
|
const SIGNED_REPRESENTATION_IMPORTS = [
|
|
26
39
|
"enterSignedValueScope",
|
package/src/constructor.ts
CHANGED
package/src/property.ts
CHANGED
|
@@ -89,14 +89,27 @@ async function* generateProperty(
|
|
|
89
89
|
${JSON.stringify(metadata.version)},
|
|
90
90
|
);
|
|
91
91
|
return await tracer.startActiveSpan("activitypub.lookup_object", async (span) => {
|
|
92
|
-
|
|
92
|
+
const dereferencePortable = async (
|
|
93
|
+
portableUrl: URL,
|
|
94
|
+
extra: {
|
|
95
|
+
inferredGateways?: readonly URL[];
|
|
96
|
+
response?: RemoteDocument;
|
|
97
|
+
contextLoader?: DocumentLoader;
|
|
98
|
+
} = {},
|
|
99
|
+
) => {
|
|
93
100
|
try {
|
|
94
|
-
const obj = await dereferencePortableIri(
|
|
101
|
+
const obj = await dereferencePortableIri(portableUrl, {
|
|
95
102
|
documentLoader,
|
|
96
|
-
contextLoader,
|
|
103
|
+
contextLoader: extra.contextLoader ?? contextLoader,
|
|
97
104
|
tracerProvider,
|
|
98
105
|
gateways: options.gateways,
|
|
106
|
+
inferredGateways: extra.inferredGateways,
|
|
107
|
+
response: extra.response,
|
|
99
108
|
verifyPortableObject: options.verifyPortableObject,
|
|
109
|
+
referrer: {
|
|
110
|
+
object: this,
|
|
111
|
+
property: ${JSON.stringify(property.uri)},
|
|
112
|
+
},
|
|
100
113
|
suppressError: options.suppressError,
|
|
101
114
|
crossOrigin: options.crossOrigin,
|
|
102
115
|
span,
|
|
@@ -109,7 +122,10 @@ async function* generateProperty(
|
|
|
109
122
|
}),
|
|
110
123
|
});
|
|
111
124
|
if (obj != null) {
|
|
112
|
-
span.setAttribute(
|
|
125
|
+
span.setAttribute(
|
|
126
|
+
"activitypub.object.id",
|
|
127
|
+
(obj.id ?? portableUrl).href,
|
|
128
|
+
);
|
|
113
129
|
span.setAttribute(
|
|
114
130
|
"activitypub.object.type",
|
|
115
131
|
// @ts-ignore: obj.constructor always has a typeId.
|
|
@@ -126,8 +142,21 @@ async function* generateProperty(
|
|
|
126
142
|
} finally {
|
|
127
143
|
span.end();
|
|
128
144
|
}
|
|
129
|
-
}
|
|
145
|
+
};
|
|
146
|
+
if (isPortableIri(url)) return await dereferencePortable(url);
|
|
130
147
|
const lookupUrl = formatIri(url);
|
|
148
|
+
const portableMode = isPortableMode(this, options);
|
|
149
|
+
if (portableMode) {
|
|
150
|
+
const compatible = parseCompatibleEf61Reference(url);
|
|
151
|
+
if (compatible === null) {
|
|
152
|
+
span.end();
|
|
153
|
+
return rejectMalformedCompatibleReference(lookupUrl, options);
|
|
154
|
+
} else if (compatible != null) {
|
|
155
|
+
return await dereferencePortable(compatible.id, {
|
|
156
|
+
inferredGateways: [compatible.gateway],
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
131
160
|
let fetchResult: RemoteDocument;
|
|
132
161
|
try {
|
|
133
162
|
fetchResult = await documentLoader(lookupUrl);
|
|
@@ -147,52 +176,90 @@ async function* generateProperty(
|
|
|
147
176
|
throw error;
|
|
148
177
|
}
|
|
149
178
|
const { document, documentUrl } = fetchResult;
|
|
150
|
-
|
|
179
|
+
// In portable mode, the document may turn out to be a portable object,
|
|
180
|
+
// which has to be verified with the same context documents:
|
|
181
|
+
const snapshot = portableMode
|
|
182
|
+
? createSnapshotContextLoader(contextLoader)
|
|
183
|
+
: null;
|
|
151
184
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
let claim: PortableResponseClaim | null | undefined;
|
|
186
|
+
try {
|
|
187
|
+
const baseUrl = parseIri(documentUrl);
|
|
188
|
+
const obj = await this.#${property.singularName}_fromJsonLd(
|
|
189
|
+
document,
|
|
190
|
+
{
|
|
191
|
+
documentLoader,
|
|
192
|
+
contextLoader: snapshot?.loader ?? contextLoader,
|
|
193
|
+
tracerProvider,
|
|
194
|
+
baseUrl,
|
|
195
|
+
}
|
|
196
|
+
);
|
|
197
|
+
if (snapshot == null) {
|
|
198
|
+
markUnverifiedPortableClaim(obj, url, documentUrl);
|
|
199
|
+
} else {
|
|
200
|
+
claim = getPortableResponseClaim(documentUrl, obj.id);
|
|
201
|
+
}
|
|
202
|
+
if (claim === undefined) {
|
|
203
|
+
if (
|
|
204
|
+
obj?.id != null &&
|
|
205
|
+
!isTrustedIriOrigin(options, obj.id, baseUrl)
|
|
206
|
+
) {
|
|
207
|
+
if (options.crossOrigin === "throw") {
|
|
208
|
+
throw new Error(
|
|
209
|
+
"The object's @id (" + obj.id.href + ") has a different origin " +
|
|
210
|
+
"than the document URL (" + baseUrl.href + "); refusing to return " +
|
|
211
|
+
"the object. If you want to bypass this check and are aware of" +
|
|
212
|
+
'the security implications, set the crossOrigin option to "trust".'
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
getLogger(["fedify", "vocab"]).warn(
|
|
216
|
+
"The object's @id ({objectId}) has a different origin than the document " +
|
|
217
|
+
"URL ({documentUrl}); refusing to return the object. If you want to " +
|
|
218
|
+
"bypass this check and are aware of the security implications, " +
|
|
219
|
+
'set the crossOrigin option to "trust".',
|
|
220
|
+
{ ...fetchResult, objectId: obj.id.href },
|
|
221
|
+
);
|
|
222
|
+
span.end();
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
span.setAttribute("activitypub.object.id", (obj.id ?? url).href);
|
|
226
|
+
span.setAttribute(
|
|
227
|
+
"activitypub.object.type",
|
|
228
|
+
// @ts-ignore: obj.constructor always has a typeId.
|
|
229
|
+
obj.constructor.typeId.href
|
|
163
230
|
);
|
|
231
|
+
span.end();
|
|
232
|
+
return obj;
|
|
164
233
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
"
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
234
|
+
} catch (e) {
|
|
235
|
+
if (options.suppressError) {
|
|
236
|
+
getLogger(["fedify", "vocab"]).error(
|
|
237
|
+
"Failed to parse {url}: {error}",
|
|
238
|
+
{ error: e, url: lookupUrl }
|
|
239
|
+
);
|
|
240
|
+
span.end();
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
span.setStatus({
|
|
244
|
+
code: SpanStatusCode.ERROR,
|
|
245
|
+
message: String(e),
|
|
246
|
+
});
|
|
247
|
+
span.end();
|
|
248
|
+
throw e;
|
|
173
249
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
);
|
|
180
|
-
return obj;
|
|
181
|
-
} catch (e) {
|
|
182
|
-
if (options.suppressError) {
|
|
183
|
-
getLogger(["fedify", "vocab"]).error(
|
|
184
|
-
"Failed to parse {url}: {error}",
|
|
185
|
-
{ error: e, url: lookupUrl }
|
|
186
|
-
);
|
|
187
|
-
return null;
|
|
250
|
+
// The document stands for a portable object, so it is verified as
|
|
251
|
+
// one instead of being trusted because of where it came from:
|
|
252
|
+
if (claim === null) {
|
|
253
|
+
span.end();
|
|
254
|
+
return rejectMalformedCompatibleReference(documentUrl, options);
|
|
188
255
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
256
|
+
return await dereferencePortable(claim.id, {
|
|
257
|
+
inferredGateways: claim.inferredGateways,
|
|
258
|
+
response: fetchResult,
|
|
259
|
+
contextLoader: snapshot?.loader,
|
|
192
260
|
});
|
|
193
|
-
throw e;
|
|
194
261
|
} finally {
|
|
195
|
-
|
|
262
|
+
snapshot?.release();
|
|
196
263
|
}
|
|
197
264
|
});
|
|
198
265
|
}
|
|
@@ -319,18 +386,35 @@ async function* generateProperty(
|
|
|
319
386
|
if (this.${await getFieldName(property.uri)}.length < 1) return null;
|
|
320
387
|
let v = this.${await getFieldName(property.uri)}[0];
|
|
321
388
|
if (!(v instanceof URL) &&
|
|
322
|
-
v.id != null &&
|
|
323
|
-
!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
|
|
324
389
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
325
|
-
|
|
390
|
+
if (v.id == null) {
|
|
391
|
+
if (isUnsecuredPortableObject(this)) {
|
|
392
|
+
warnUnverifiableEmbeddedObject(this, ${
|
|
393
|
+
JSON.stringify(property.uri)
|
|
394
|
+
});
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
} else if (
|
|
398
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
399
|
+
isUnsecuredPortableObject(this) ||
|
|
400
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
401
|
+
) {
|
|
402
|
+
v = v.id;
|
|
403
|
+
}
|
|
326
404
|
}
|
|
327
405
|
if (v instanceof URL) {
|
|
328
406
|
const fetched =
|
|
329
407
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
330
408
|
if (fetched == null) return null;
|
|
409
|
+
// Not cached, so that it is verified when the portable object
|
|
410
|
+
// policy applies later:
|
|
411
|
+
if (isUnverifiedPortableClaim(fetched)) return fetched;
|
|
331
412
|
this.${await getFieldName(property.uri)}[0] = fetched;
|
|
332
413
|
this.${await getFieldName(property.uri, "#_trust")}.add(0);
|
|
333
414
|
this._cachedJsonLd = undefined;
|
|
415
|
+
recordPortableReferrer(this, fetched, ${
|
|
416
|
+
JSON.stringify(property.uri)
|
|
417
|
+
});
|
|
334
418
|
return fetched;
|
|
335
419
|
}
|
|
336
420
|
`;
|
|
@@ -360,9 +444,13 @@ async function* generateProperty(
|
|
|
360
444
|
}
|
|
361
445
|
yield `
|
|
362
446
|
if (v?.id != null &&
|
|
363
|
-
${
|
|
447
|
+
((${
|
|
364
448
|
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
365
|
-
}!isTrustedIriOrigin(options, v.id, ${trustOwner})
|
|
449
|
+
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
450
|
+
isUnsecuredPortableObject(this) ||
|
|
451
|
+
mustDereferencePortableObject(
|
|
452
|
+
this, ${trustOwner}, v.id, options,
|
|
453
|
+
)) &&
|
|
366
454
|
!this.${await getFieldName(property.uri, "#_trust")}.has(0)) {
|
|
367
455
|
if (options.crossOrigin === "throw") {
|
|
368
456
|
throw new Error(
|
|
@@ -370,7 +458,7 @@ async function* generateProperty(
|
|
|
370
458
|
"origin than the property owner's @id (" + ${
|
|
371
459
|
type.trustEmbeddedObjects === false
|
|
372
460
|
? '"untrusted metadata"'
|
|
373
|
-
: "this.id
|
|
461
|
+
: "(this.id?.href ?? null)"
|
|
374
462
|
} + "); " +
|
|
375
463
|
"refusing to return the object. If you want to bypass this " +
|
|
376
464
|
"check and are aware of the security implications, set the " +
|
|
@@ -386,11 +474,12 @@ async function* generateProperty(
|
|
|
386
474
|
{ objectId: v.id.href, parentObjectId: ${
|
|
387
475
|
type.trustEmbeddedObjects === false
|
|
388
476
|
? '"untrusted metadata"'
|
|
389
|
-
: "this.id
|
|
477
|
+
: "(this.id?.href ?? null)"
|
|
390
478
|
} },
|
|
391
479
|
);
|
|
392
480
|
return null;
|
|
393
481
|
}
|
|
482
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
394
483
|
return v;
|
|
395
484
|
}
|
|
396
485
|
`;
|
|
@@ -437,18 +526,38 @@ async function* generateProperty(
|
|
|
437
526
|
for (let i = 0; i < vs.length; i++) {
|
|
438
527
|
let v = vs[i];
|
|
439
528
|
if (!(v instanceof URL) &&
|
|
440
|
-
v.id != null &&
|
|
441
|
-
!isTrustedIriOrigin(options, v.id, ${trustOwner}) &&
|
|
442
529
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
443
|
-
|
|
530
|
+
if (v.id == null) {
|
|
531
|
+
if (isUnsecuredPortableObject(this)) {
|
|
532
|
+
warnUnverifiableEmbeddedObject(this, ${
|
|
533
|
+
JSON.stringify(property.uri)
|
|
534
|
+
});
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
} else if (
|
|
538
|
+
!isTrustedIriOrigin(options, v.id, ${trustOwner}) ||
|
|
539
|
+
isUnsecuredPortableObject(this) ||
|
|
540
|
+
mustDereferencePortableObject(this, ${trustOwner}, v.id, options)
|
|
541
|
+
) {
|
|
542
|
+
v = v.id;
|
|
543
|
+
}
|
|
444
544
|
}
|
|
445
545
|
if (v instanceof URL) {
|
|
446
546
|
const fetched =
|
|
447
547
|
await this.#fetch${pascalCase(property.singularName)}(v, options);
|
|
448
548
|
if (fetched == null) continue;
|
|
549
|
+
// Not cached, so that it is verified when the portable object
|
|
550
|
+
// policy applies later:
|
|
551
|
+
if (isUnverifiedPortableClaim(fetched)) {
|
|
552
|
+
yield fetched;
|
|
553
|
+
continue;
|
|
554
|
+
}
|
|
449
555
|
vs[i] = fetched;
|
|
450
556
|
this.${await getFieldName(property.uri, "#_trust")}.add(i);
|
|
451
557
|
this._cachedJsonLd = undefined;
|
|
558
|
+
recordPortableReferrer(this, fetched, ${
|
|
559
|
+
JSON.stringify(property.uri)
|
|
560
|
+
});
|
|
452
561
|
yield fetched;
|
|
453
562
|
continue;
|
|
454
563
|
}
|
|
@@ -479,9 +588,13 @@ async function* generateProperty(
|
|
|
479
588
|
}
|
|
480
589
|
yield `
|
|
481
590
|
if (v?.id != null &&
|
|
482
|
-
${
|
|
591
|
+
((${
|
|
483
592
|
type.trustEmbeddedObjects === false ? "" : "this.id != null && "
|
|
484
|
-
}!isTrustedIriOrigin(options, v.id, ${trustOwner})
|
|
593
|
+
}!isTrustedIriOrigin(options, v.id, ${trustOwner})) ||
|
|
594
|
+
isUnsecuredPortableObject(this) ||
|
|
595
|
+
mustDereferencePortableObject(
|
|
596
|
+
this, ${trustOwner}, v.id, options,
|
|
597
|
+
)) &&
|
|
485
598
|
!this.${await getFieldName(property.uri, "#_trust")}.has(i)) {
|
|
486
599
|
if (options.crossOrigin === "throw") {
|
|
487
600
|
throw new Error(
|
|
@@ -489,7 +602,7 @@ async function* generateProperty(
|
|
|
489
602
|
"origin than the property owner's @id (" + ${
|
|
490
603
|
type.trustEmbeddedObjects === false
|
|
491
604
|
? '"untrusted metadata"'
|
|
492
|
-
: "this.id
|
|
605
|
+
: "(this.id?.href ?? null)"
|
|
493
606
|
} + "); " +
|
|
494
607
|
"refusing to return the object. If you want to bypass this " +
|
|
495
608
|
"check and are aware of the security implications, set the " +
|
|
@@ -505,11 +618,12 @@ async function* generateProperty(
|
|
|
505
618
|
{ objectId: v.id.href, parentObjectId: ${
|
|
506
619
|
type.trustEmbeddedObjects === false
|
|
507
620
|
? '"untrusted metadata"'
|
|
508
|
-
: "this.id
|
|
621
|
+
: "(this.id?.href ?? null)"
|
|
509
622
|
} },
|
|
510
623
|
);
|
|
511
624
|
continue;
|
|
512
625
|
}
|
|
626
|
+
recordPortableReferrer(this, v, ${JSON.stringify(property.uri)});
|
|
513
627
|
yield v;
|
|
514
628
|
}
|
|
515
629
|
}
|