remove assertActivityMatchesUrls in favor of three-way same-authority checks

This commit is contained in:
Hazelnoot 2025-02-22 14:12:05 -05:00
parent 14a81b4f85
commit a568333ecd
13 changed files with 318 additions and 172 deletions

View file

@ -0,0 +1,180 @@
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { UtilityService } from '@/core/UtilityService.js';
import type { IObject } from '@/core/activitypub/type.js';
import type { EnvService } from '@/core/EnvService.js';
import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
describe(ApUtilityService, () => {
let serviceUnderTest: ApUtilityService;
let env: Record<string, string>;
beforeEach(() => {
const utilityService = {
punyHostPSLDomain(input: string) {
const host = new URL(input).host;
const parts = host.split('.');
return `${parts[parts.length - 2]}.${parts[parts.length - 1]}`;
},
} as unknown as UtilityService;
env = {};
const envService = {
env,
} as unknown as EnvService;
serviceUnderTest = new ApUtilityService(utilityService, envService);
});
describe('assertIdMatchesUrlAuthority', () => {
it('should return when input matches', () => {
const object = { id: 'https://first.example.com' } as IObject;
const url = 'https://second.example.com';
expect(() => {
serviceUnderTest.assertIdMatchesUrlAuthority(object, url);
}).not.toThrow();
});
it('should throw when id is missing', () => {
const object = { id: undefined } as IObject;
const url = 'https://second.example.com';
expect(() => {
serviceUnderTest.assertIdMatchesUrlAuthority(object, url);
}).toThrow();
});
it('should throw when id does not match', () => {
const object = { id: 'https://other-domain.com' } as IObject;
const url = 'https://second.example.com';
expect(() => {
serviceUnderTest.assertIdMatchesUrlAuthority(object, url);
}).toThrow();
});
});
describe('haveSameAuthority', () => {
it('should return true when URLs match', () => {
const url = 'https://example.com';
const result = serviceUnderTest.haveSameAuthority(url, url);
expect(result).toBeTruthy();
});
it('should return true when URLs have same host', () => {
const first = 'https://example.com/first';
const second = 'https://example.com/second';
const result = serviceUnderTest.haveSameAuthority(first, second);
expect(result).toBeTruthy();
});
it('should return true when URLs have same authority', () => {
const first = 'https://first.example.com/first';
const second = 'https://second.example.com/second';
const result = serviceUnderTest.haveSameAuthority(first, second);
expect(result).toBeTruthy();
});
it('should return false when URLs have different authority', () => {
const first = 'https://first.com';
const second = 'https://second.com';
const result = serviceUnderTest.haveSameAuthority(first, second);
expect(result).toBeFalsy();
});
});
describe('findSameAuthorityUrl', () => {
it('should return null when input is undefined', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com', undefined);
expect(result).toBeNull();
});
it('should return null when input is empty array', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com', []);
expect(result).toBeNull();
});
it('should return return url if string input matches', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', 'https://example.com/2');
expect(result).toBe('https://example.com/2');
});
it('should return return url if object input matches', () => {
const input = {
href: 'https://example.com/2',
} as IObject;
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', input);
expect(result).toBe('https://example.com/2');
});
it('should return return url if string[] input matches', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', ['https://example.com/2']);
expect(result).toBe('https://example.com/2');
});
it('should return return url if object[] input matches', () => {
const input = {
href: 'https://example.com/2',
} as IObject;
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', [input]);
expect(result).toBe('https://example.com/2');
});
it('should skip invalid entries', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', [{} as IObject, 'https://example.com/2']);
expect(result).toBe('https://example.com/2');
});
it('should return first match', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', ['https://example.com/2', 'https://example.com/3']);
expect(result).toBe('https://example.com/2');
});
it('should skip invalid scheme', () => {
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', ['file://example.com/1', 'https://example.com/2']);
expect(result).toBe('https://example.com/2');
});
it('should skip HTTP in production', () => {
env.NODE_ENV = 'production';
// noinspection HttpUrlsUsage
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', ['http://example.com/1', 'https://example.com/2']);
expect(result).toBe('https://example.com/2');
});
it('should allow HTTP in non-prod', () => {
env.NODE_ENV = 'test';
// noinspection HttpUrlsUsage
const result = serviceUnderTest.findSameAuthorityUrl('https://example.com/1', ['http://example.com/1', 'https://example.com/2']);
// noinspection HttpUrlsUsage
expect(result).toBe('http://example.com/1');
});
});
});

View file

@ -1,55 +0,0 @@
/*
* SPDX-FileCopyrightText: dakkar and sharkey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { describe, expect, test } from '@jest/globals';
import type { IObject } from '@/core/activitypub/type.js';
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
function assertOne(activity: IObject, good = 'http://good') {
// return a function so we can use `.toThrow`
return () => assertActivityMatchesUrls(activity, [good]);
}
describe('assertActivityMatchesUrls', () => {
it('should throw when no ids are URLs', () => {
expect(assertOne({ type: 'Test', id: 'bad' }, 'bad')).toThrow(/bad Activity/);
});
test('id', () => {
expect(assertOne({ type: 'Test', id: 'http://bad' })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', id: 'http://good' })).not.toThrow();
});
test('simple url', () => {
expect(assertOne({ type: 'Test', url: 'http://bad' })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: 'http://good' })).not.toThrow();
});
test('array of urls', () => {
expect(assertOne({ type: 'Test', url: ['http://bad'] })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: ['http://bad', 'http://other'] })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: ['http://good'] })).not.toThrow();
expect(assertOne({ type: 'Test', url: ['http://bad', 'http://good'] })).not.toThrow();
});
test('array of objects', () => {
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://bad' }] })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://bad' }, { type: 'Test', href: 'http://other' }] })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://good' }] })).not.toThrow();
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://bad' }, { type: 'Test', href: 'http://good' }] })).not.toThrow();
});
test('mixed array', () => {
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://bad' }, 'http://other'] })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', url: [{ type: 'Test', href: 'http://bad' }, 'http://good'] })).not.toThrow();
expect(assertOne({ type: 'Test', url: ['http://bad', { type: 'Test', href: 'http://good' }] })).not.toThrow();
});
test('id and url', () => {
expect(assertOne({ type: 'Test', id: 'http://other', url: 'http://bad' })).toThrow(/bad Activity/);
expect(assertOne({ type: 'Test', id: 'http://bad', url: 'http://good' })).not.toThrow();
expect(assertOne({ type: 'Test', id: 'http://good', url: 'http://bad' })).not.toThrow();
});
});