feat: Change OwnershipValidator to create own token

It also no longer checks the issuer triple
since that is independent of ownership.
This commit is contained in:
Joachim Van Herwegen
2021-05-25 10:48:41 +02:00
parent df33b6dc47
commit 6bfe1bdccc
11 changed files with 179 additions and 134 deletions

View File

@@ -14,17 +14,13 @@ describe('A RegistrationHandler', (): void => {
const email = 'alice@test.email';
let request: HttpRequest;
const response: HttpResponse = {} as any;
let provider: Provider;
const provider: Provider = {} as any;
let ownershipValidator: OwnershipValidator;
let accountStore: AccountStore;
let interactionCompleter: InteractionCompleter;
let handler: RegistrationHandler;
beforeEach(async(): Promise<void> => {
provider = {
interactionDetails: jest.fn().mockResolvedValue({ uid: '123456' }),
} as any;
ownershipValidator = {
handleSafe: jest.fn(),
} as any;

View File

@@ -1,70 +0,0 @@
import fetch from '@rdfjs/fetch';
import type { DatasetResponse } from '@rdfjs/fetch-lite';
import { DataFactory } from 'n3';
import type { Quad } from 'n3';
import type { DatasetCore } from 'rdf-js';
import { IssuerOwnershipValidator } from '../../../../../src/identity/interaction/util/IssuerOwnershipValidator';
import { SOLID } from '../../../../../src/util/Vocabularies';
const { literal, namedNode, quad } = DataFactory;
jest.mock('@rdfjs/fetch');
function quadToString(qq: Quad): string {
const subPred = `<${qq.subject.value}> <${qq.predicate.value}>`;
if (qq.object.termType === 'Literal') {
return `${subPred} "${qq.object.value}"`;
}
return `${subPred} <${qq.object.value}>`;
}
describe('An IssuerOwnershipValidator', (): void => {
const fetchMock: jest.Mock = fetch as any;
const issuer = 'http://test.com/foo/';
const webId = 'http://alice.test.com/#me';
const interactionId = 'interaction!!';
let rawResponse: DatasetResponse<DatasetCore>;
let dataset: DatasetCore;
let triples: Quad[];
const issuerTriple = quad(namedNode(webId), SOLID.terms.oidcIssuer, namedNode(issuer));
const tokenTriple = quad(namedNode(webId), SOLID.terms.oidcIssuerRegistrationToken, literal(interactionId));
let validator: IssuerOwnershipValidator;
beforeEach(async(): Promise<void> => {
triples = [];
dataset = {
has: (qq: Quad): boolean => triples.some((triple): boolean => triple.equals(qq)),
} as any;
rawResponse = {
dataset: async(): Promise<DatasetCore> => dataset,
} as any;
fetchMock.mockReturnValue(rawResponse);
validator = new IssuerOwnershipValidator(issuer);
});
it('errors if the expected triples are missing.', async(): Promise<void> => {
const prom = validator.handle({ webId, interactionId });
await expect(prom).rejects.toThrow(quadToString(issuerTriple));
await expect(prom).rejects.toThrow(quadToString(tokenTriple));
});
it('only requests the needed triples.', async(): Promise<void> => {
triples = [ issuerTriple ];
let prom = validator.handle({ webId, interactionId });
await expect(prom).rejects.not.toThrow(quadToString(issuerTriple));
await expect(prom).rejects.toThrow(quadToString(tokenTriple));
triples = [ tokenTriple ];
prom = validator.handle({ webId, interactionId });
await expect(prom).rejects.toThrow(quadToString(issuerTriple));
await expect(prom).rejects.not.toThrow(quadToString(tokenTriple));
});
it('resolves if all required triples are present.', async(): Promise<void> => {
triples = [ issuerTriple, tokenTriple ];
await expect(validator.handle({ webId, interactionId })).resolves.toBeUndefined();
});
});

View File

@@ -0,0 +1,91 @@
import fetch from '@rdfjs/fetch';
import type { DatasetResponse } from '@rdfjs/fetch-lite';
import { DataFactory } from 'n3';
import type { Quad } from 'n3';
import type { DatasetCore } from 'rdf-js';
import { v4 } from 'uuid';
import { TokenOwnershipValidator } from '../../../../../src/identity/interaction/util/TokenOwnershipValidator';
import type { ExpiringStorage } from '../../../../../src/storage/keyvalue/ExpiringStorage';
import { SOLID } from '../../../../../src/util/Vocabularies';
const { literal, namedNode, quad } = DataFactory;
jest.mock('@rdfjs/fetch');
jest.mock('uuid');
function quadToString(qq: Quad): string {
const subPred = `<${qq.subject.value}> <${qq.predicate.value}>`;
if (qq.object.termType === 'Literal') {
return `${subPred} "${qq.object.value}"`;
}
return `${subPred} <${qq.object.value}>`;
}
describe('A TokenOwnershipValidator', (): void => {
const fetchMock: jest.Mock = fetch as any;
const webId = 'http://alice.test.com/#me';
const token = 'randomlyGeneratedToken';
let rawResponse: DatasetResponse<DatasetCore>;
let dataset: DatasetCore;
let triples: Quad[];
const tokenTriple = quad(namedNode(webId), SOLID.terms.oidcIssuerRegistrationToken, literal(token));
let storage: ExpiringStorage<string, string>;
let validator: TokenOwnershipValidator;
beforeEach(async(): Promise<void> => {
const now = Date.now();
jest.spyOn(Date, 'now').mockReturnValue(now);
(v4 as jest.Mock).mockReturnValue(token);
triples = [];
dataset = {
has: (qq: Quad): boolean => triples.some((triple): boolean => triple.equals(qq)),
} as any;
rawResponse = {
dataset: async(): Promise<DatasetCore> => dataset,
} as any;
const map = new Map<string, any>();
storage = {
get: jest.fn().mockImplementation((key: string): any => map.get(key)),
set: jest.fn().mockImplementation((key: string, value: any): any => map.set(key, value)),
delete: jest.fn().mockImplementation((key: string): any => map.delete(key)),
} as any;
fetchMock.mockReturnValue(rawResponse);
validator = new TokenOwnershipValidator(storage);
});
it('errors if no token is stored in the storage.', async(): Promise<void> => {
// Even if the token is in the WebId, it will error since it's not in the storage
triples = [ tokenTriple ];
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
expect(fetch).toHaveBeenCalledTimes(0);
});
it('errors if the expected triple is missing.', async(): Promise<void> => {
// First call will add the token to the storage
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
expect(fetch).toHaveBeenCalledTimes(0);
// Second call will fetch the WebId
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
expect(fetch).toHaveBeenCalledTimes(1);
});
it('resolves if the WebId contains the verification triple.', async(): Promise<void> => {
triples = [ tokenTriple ];
// First call will add the token to the storage
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
// Second call will succeed since it has the verification triple
await expect(validator.handle({ webId })).resolves.toBeUndefined();
});
it('fails if the WebId contains the wrong verification triple.', async(): Promise<void> => {
triples = [ quad(namedNode(webId), SOLID.terms.oidcIssuerRegistrationToken, literal('wrongToken')) ];
// First call will add the token to the storage
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
// Second call will fail since it has the wrong verification triple
await expect(validator.handle({ webId })).rejects.toThrow(quadToString(tokenTriple));
});
});