mirror of
https://codeberg.org/yeentown/barkey.git
synced 2025-07-08 04:54:32 +00:00
support fetching anonymous AP objects
This commit is contained in:
parent
9e8e08eb57
commit
b506dd564b
3 changed files with 37 additions and 18 deletions
|
@ -235,7 +235,7 @@ export class HttpRequestService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async getActivityJson(url: string, isLocalAddressAllowed = false): Promise<IObjectWithId> {
|
public async getActivityJson(url: string, isLocalAddressAllowed = false, allowAnonymous = false): Promise<IObjectWithId> {
|
||||||
this.apUtilityService.assertApUrl(url);
|
this.apUtilityService.assertApUrl(url);
|
||||||
|
|
||||||
const res = await this.send(url, {
|
const res = await this.send(url, {
|
||||||
|
@ -255,7 +255,11 @@ export class HttpRequestService {
|
||||||
|
|
||||||
// Make sure the object ID matches the final URL (which is where it actually exists).
|
// Make sure the object ID matches the final URL (which is where it actually exists).
|
||||||
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
|
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
|
||||||
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
|
if (allowAnonymous && activity.id == null) {
|
||||||
|
activity.id = res.url;
|
||||||
|
} else {
|
||||||
|
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
|
||||||
|
}
|
||||||
|
|
||||||
return activity as IObjectWithId;
|
return activity as IObjectWithId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,10 +184,11 @@ export class ApRequestService {
|
||||||
* Get AP object with http-signature
|
* Get AP object with http-signature
|
||||||
* @param user http-signature user
|
* @param user http-signature user
|
||||||
* @param url URL to fetch
|
* @param url URL to fetch
|
||||||
* @param followAlternate
|
* @param allowAnonymous If a fetched object lacks an ID, then it will be auto-generated from the final URL. (default: false)
|
||||||
|
* @param followAlternate Whether to resolve HTML responses to their referenced canonical AP endpoint. (default: true)
|
||||||
*/
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async signedGet(url: string, user: { id: MiUser['id'] }, followAlternate?: boolean): Promise<IObjectWithId> {
|
public async signedGet(url: string, user: { id: MiUser['id'] }, allowAnonymous = false, followAlternate?: boolean): Promise<IObjectWithId> {
|
||||||
this.apUtilityService.assertApUrl(url);
|
this.apUtilityService.assertApUrl(url);
|
||||||
|
|
||||||
const _followAlternate = followAlternate ?? true;
|
const _followAlternate = followAlternate ?? true;
|
||||||
|
@ -258,7 +259,7 @@ export class ApRequestService {
|
||||||
if (alternate) {
|
if (alternate) {
|
||||||
const href = alternate.getAttribute('href');
|
const href = alternate.getAttribute('href');
|
||||||
if (href && this.apUtilityService.haveSameAuthority(url, href)) {
|
if (href && this.apUtilityService.haveSameAuthority(url, href)) {
|
||||||
return await this.signedGet(href, user, false);
|
return await this.signedGet(href, user, allowAnonymous, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -275,7 +276,11 @@ export class ApRequestService {
|
||||||
|
|
||||||
// Make sure the object ID matches the final URL (which is where it actually exists).
|
// Make sure the object ID matches the final URL (which is where it actually exists).
|
||||||
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
|
// The caller (ApResolverService) will verify the ID against the original / entry URL, which ensures that all three match.
|
||||||
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
|
if (allowAnonymous && activity.id == null) {
|
||||||
|
activity.id = res.url;
|
||||||
|
} else {
|
||||||
|
this.apUtilityService.assertIdMatchesUrlAuthority(activity, res.url);
|
||||||
|
}
|
||||||
|
|
||||||
return activity as IObjectWithId;
|
return activity as IObjectWithId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,10 +63,12 @@ export class Resolver {
|
||||||
return this.recursionLimit;
|
return this.recursionLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async resolveCollection(value: string, allowAnonymous?: boolean): Promise<AnyCollection & IObjectWithId>;
|
||||||
|
public async resolveCollection(value: string | IObject, allowAnonymous?: boolean): Promise<AnyCollection>;
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolveCollection(value: string | IObject): Promise<ICollection | IOrderedCollection> {
|
public async resolveCollection(value: string | IObject, allowAnonymous?: boolean): Promise<AnyCollection> {
|
||||||
const collection = typeof value === 'string'
|
const collection = typeof value === 'string'
|
||||||
? await this.resolve(value)
|
? await this.resolve(value, allowAnonymous)
|
||||||
: value;
|
: value;
|
||||||
|
|
||||||
if (isCollectionOrOrderedCollection(collection)) {
|
if (isCollectionOrOrderedCollection(collection)) {
|
||||||
|
@ -103,25 +105,33 @@ export class Resolver {
|
||||||
return await this.resolve(id);
|
return await this.resolve(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async resolve(value: string | [string]): Promise<IObjectWithId>;
|
public async resolve(value: string | [string], allowAnonymous?: boolean): Promise<IObjectWithId>;
|
||||||
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject>;
|
public async resolve(value: string | IObject | [string | IObject], allowAnonymous?: boolean): Promise<IObject>;
|
||||||
|
/**
|
||||||
|
* Resolves a URL or object to an AP object.
|
||||||
|
* Tuples are expanded to their first element before anything else, and non-string inputs are returned as-is.
|
||||||
|
* Otherwise, the string URL is fetched and validated to represent a valid ActivityPub object.
|
||||||
|
* @param value The input value to resolve
|
||||||
|
* @param allowAnonymous Determines what to do if a response object lacks an ID field. If false (default), then an exception is thrown. If true, then the ID is populated from the final response URL.
|
||||||
|
*/
|
||||||
@bindThis
|
@bindThis
|
||||||
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
|
public async resolve(value: string | IObject | [string | IObject], allowAnonymous = false): Promise<IObject> {
|
||||||
value = fromTuple(value);
|
value = fromTuple(value);
|
||||||
|
|
||||||
|
// TODO try and remove this eventually, as it's a major security foot-gun
|
||||||
if (typeof value !== 'string') {
|
if (typeof value !== 'string') {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const host = this.utilityService.extractDbHost(value);
|
const host = this.utilityService.extractDbHost(value);
|
||||||
if (this.config.activityLogging.enabled && !this.utilityService.isSelfHost(host)) {
|
if (this.config.activityLogging.enabled && !this.utilityService.isSelfHost(host)) {
|
||||||
return await this._resolveLogged(value, host);
|
return await this._resolveLogged(value, host, allowAnonymous);
|
||||||
} else {
|
} else {
|
||||||
return await this._resolve(value, host);
|
return await this._resolve(value, host, allowAnonymous);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resolveLogged(requestUri: string, host: string): Promise<IObjectWithId> {
|
private async _resolveLogged(requestUri: string, host: string, allowAnonymous: boolean): Promise<IObjectWithId> {
|
||||||
const startTime = process.hrtime.bigint();
|
const startTime = process.hrtime.bigint();
|
||||||
|
|
||||||
const log = await this.apLogService.createFetchLog({
|
const log = await this.apLogService.createFetchLog({
|
||||||
|
@ -130,7 +140,7 @@ export class Resolver {
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this._resolve(requestUri, host, log);
|
const result = await this._resolve(requestUri, host, allowAnonymous, log);
|
||||||
|
|
||||||
log.accepted = true;
|
log.accepted = true;
|
||||||
log.result = 'ok';
|
log.result = 'ok';
|
||||||
|
@ -150,7 +160,7 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _resolve(value: string, host: string, log?: SkApFetchLog): Promise<IObjectWithId> {
|
private async _resolve(value: string, host: string, allowAnonymous: boolean, log?: SkApFetchLog): Promise<IObjectWithId> {
|
||||||
if (value.includes('#')) {
|
if (value.includes('#')) {
|
||||||
// URLs with fragment parts cannot be resolved correctly because
|
// URLs with fragment parts cannot be resolved correctly because
|
||||||
// the fragment part does not get transmitted over HTTP(S).
|
// the fragment part does not get transmitted over HTTP(S).
|
||||||
|
@ -181,8 +191,8 @@ export class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
const object = (this.user
|
const object = (this.user
|
||||||
? await this.apRequestService.signedGet(value, this.user)
|
? await this.apRequestService.signedGet(value, this.user, allowAnonymous)
|
||||||
: await this.httpRequestService.getActivityJson(value));
|
: await this.httpRequestService.getActivityJson(value, false, allowAnonymous));
|
||||||
|
|
||||||
if (log) {
|
if (log) {
|
||||||
const { object: objectOnly, context, contextHash } = extractObjectContext(object);
|
const { object: objectOnly, context, contextHash } = extractObjectContext(object);
|
||||||
|
|
Loading…
Add table
Reference in a new issue