mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Add acl link header writer
This writer will add a link to the corresponding acl file for all LDP requests.
This commit is contained in:
parent
153d2d9fe4
commit
2c3300028e
@ -39,6 +39,12 @@
|
|||||||
"LinkRelMetadataWriter:_linkRelMap_value": "type"
|
"LinkRelMetadataWriter:_linkRelMap_value": "type"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "AclLinkMetadataWriter",
|
||||||
|
"AclLinkMetadataWriter:_aclManager": {
|
||||||
|
"@id": "urn:solid-server:default:AclManager"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -23,6 +23,7 @@ export * from './init/RootContainerInitializer';
|
|||||||
export * from './init/ServerInitializer';
|
export * from './init/ServerInitializer';
|
||||||
|
|
||||||
// LDP/HTTP/Metadata
|
// LDP/HTTP/Metadata
|
||||||
|
export * from './ldp/http/metadata/AclLinkMetadataWriter';
|
||||||
export * from './ldp/http/metadata/BasicMetadataExtractor';
|
export * from './ldp/http/metadata/BasicMetadataExtractor';
|
||||||
export * from './ldp/http/metadata/ConstantMetadataWriter';
|
export * from './ldp/http/metadata/ConstantMetadataWriter';
|
||||||
export * from './ldp/http/metadata/ContentTypeParser';
|
export * from './ldp/http/metadata/ContentTypeParser';
|
||||||
|
25
src/ldp/http/metadata/AclLinkMetadataWriter.ts
Normal file
25
src/ldp/http/metadata/AclLinkMetadataWriter.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { AclManager } from '../../../authorization/AclManager';
|
||||||
|
import type { HttpResponse } from '../../../server/HttpResponse';
|
||||||
|
import { addHeader } from '../../../util/HeaderUtil';
|
||||||
|
import type { RepresentationMetadata } from '../../representation/RepresentationMetadata';
|
||||||
|
import { MetadataWriter } from './MetadataWriter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A MetadataWriter that always adds a rel="acl" link header to a response.
|
||||||
|
* The `rel` parameter can be used if a different `rel` value is needed (such as http://www.w3.org/ns/solid/terms#acl).
|
||||||
|
*/
|
||||||
|
export class AclLinkMetadataWriter extends MetadataWriter {
|
||||||
|
private readonly aclManager: AclManager;
|
||||||
|
private readonly rel: string;
|
||||||
|
|
||||||
|
public constructor(aclManager: AclManager, rel = 'acl') {
|
||||||
|
super();
|
||||||
|
this.aclManager = aclManager;
|
||||||
|
this.rel = rel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async handle(input: { response: HttpResponse; metadata: RepresentationMetadata }): Promise<void> {
|
||||||
|
const identifier = await this.aclManager.getAclDocument({ path: input.metadata.identifier.value });
|
||||||
|
addHeader(input.response, 'Link', `<${identifier.path}>; rel="${this.rel}"`);
|
||||||
|
}
|
||||||
|
}
|
@ -76,7 +76,8 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeUrn, se
|
|||||||
response = await resourceHelper.getResource(id);
|
response = await resourceHelper.getResource(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getBuffer().toString()).toContain('TESTFILE2');
|
expect(response._getBuffer().toString()).toContain('TESTFILE2');
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE file
|
// DELETE file
|
||||||
await resourceHelper.deleteResource(id);
|
await resourceHelper.deleteResource(id);
|
||||||
@ -109,7 +110,8 @@ describe.each(stores)('An LDP handler with auth using %s', (name, { storeUrn, se
|
|||||||
// GET permanent file
|
// GET permanent file
|
||||||
response = await resourceHelper.getResource('http://test.com/permanent.txt');
|
response = await resourceHelper.getResource('http://test.com/permanent.txt');
|
||||||
expect(response._getBuffer().toString()).toContain('TEST');
|
expect(response._getBuffer().toString()).toContain('TEST');
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<http://test.com/permanent.txt.acl>; rel="acl"`);
|
||||||
|
|
||||||
// Try to delete permanent file
|
// Try to delete permanent file
|
||||||
response = await resourceHelper.deleteResource('http://test.com/permanent.txt', true);
|
response = await resourceHelper.deleteResource('http://test.com/permanent.txt', true);
|
||||||
|
@ -66,6 +66,7 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
const data = response._getData().toString();
|
const data = response._getData().toString();
|
||||||
expect(data).toContain(`<> a ldp:Container`);
|
expect(data).toContain(`<> a ldp:Container`);
|
||||||
expect(response.getHeaders().link).toContain(`<${LDP.Container}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Container}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${BASE}/.acl>; rel="acl"`);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a file to the store, read it and delete it.', async():
|
it('can add a file to the store, read it and delete it.', async():
|
||||||
@ -80,7 +81,8 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
response = await resourceHelper.getResource(id);
|
response = await resourceHelper.getResource(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
expect(response.getHeaders()['accept-patch']).toBe('application/sparql-update');
|
expect(response.getHeaders()['accept-patch']).toBe('application/sparql-update');
|
||||||
expect(response.getHeaders()['ms-author-via']).toBe('SPARQL');
|
expect(response.getHeaders()['ms-author-via']).toBe('SPARQL');
|
||||||
|
|
||||||
@ -99,7 +101,8 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
response = await resourceHelper.getResource(id);
|
response = await resourceHelper.getResource(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
expect(response._getBuffer().toString()).toContain('TESTFILE0');
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// PUT
|
// PUT
|
||||||
response = await resourceHelper.replaceResource(
|
response = await resourceHelper.replaceResource(
|
||||||
@ -110,7 +113,8 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
response = await resourceHelper.getResource(id);
|
response = await resourceHelper.getResource(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getBuffer().toString()).toContain('TESTFILE1');
|
expect(response._getBuffer().toString()).toContain('TESTFILE1');
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
await resourceHelper.deleteResource(id);
|
await resourceHelper.deleteResource(id);
|
||||||
@ -125,9 +129,10 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
// GET
|
// GET
|
||||||
response = await resourceHelper.getContainer(id);
|
response = await resourceHelper.getContainer(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.getHeaders().link).toEqual(
|
expect(response.getHeaders().link).toContain(`<${LDP.Container}>; rel="type"`);
|
||||||
[ `<${LDP.Container}>; rel="type"`, `<${LDP.BasicContainer}>; rel="type"`, `<${LDP.Resource}>; rel="type"` ],
|
expect(response.getHeaders().link).toContain(`<${LDP.BasicContainer}>; rel="type"`);
|
||||||
);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
await resourceHelper.deleteResource(id);
|
await resourceHelper.deleteResource(id);
|
||||||
@ -147,7 +152,8 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
// GET File
|
// GET File
|
||||||
response = await resourceHelper.getResource(id);
|
response = await resourceHelper.getResource(id);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
await resourceHelper.deleteResource(id);
|
await resourceHelper.deleteResource(id);
|
||||||
@ -218,9 +224,10 @@ describe.each(stores)('An LDP handler without auth using %s', (name, { storeUrn,
|
|||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response._getData()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/subfolder0/> .');
|
expect(response._getData()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/subfolder0/> .');
|
||||||
expect(response._getData()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/testfile0.txt> .');
|
expect(response._getData()).toContain('<http://www.w3.org/ns/ldp#contains> <http://test.com/testfolder3/testfile0.txt> .');
|
||||||
expect(response.getHeaders().link).toEqual(
|
expect(response.getHeaders().link).toContain(`<${LDP.Container}>; rel="type"`);
|
||||||
[ `<${LDP.Container}>; rel="type"`, `<${LDP.BasicContainer}>; rel="type"`, `<${LDP.Resource}>; rel="type"` ],
|
expect(response.getHeaders().link).toContain(`<${LDP.BasicContainer}>; rel="type"`);
|
||||||
);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${folderId}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
await resourceHelper.deleteResource(fileId);
|
await resourceHelper.deleteResource(fileId);
|
||||||
|
@ -47,7 +47,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
expect(response._getData()).toContain(
|
expect(response._getData()).toContain(
|
||||||
'<http://test.com/s> <http://test.com/p> <http://test.com/o>.',
|
'<http://test.com/s> <http://test.com/p> <http://test.com/o>.',
|
||||||
);
|
);
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
response = await performRequest(handler, requestUrl, 'DELETE', {}, []);
|
response = await performRequest(handler, requestUrl, 'DELETE', {}, []);
|
||||||
@ -103,7 +104,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
expect(response._getData()).toContain(
|
expect(response._getData()).toContain(
|
||||||
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
|
'<http://test.com/s2> <http://test.com/p2> <http://test.com/o2>.',
|
||||||
);
|
);
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
let triples = parser.parse(response._getData());
|
let triples = parser.parse(response._getData());
|
||||||
expect(triples).toBeRdfIsomorphic([
|
expect(triples).toBeRdfIsomorphic([
|
||||||
@ -190,7 +192,8 @@ describe('An integrated AuthenticatedLdpHandler', (): void => {
|
|||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
expect(response.statusCode).toBe(200);
|
expect(response.statusCode).toBe(200);
|
||||||
expect(response.getHeaders().link).toBe(`<${LDP.Resource}>; rel="type"`);
|
expect(response.getHeaders().link).toContain(`<${LDP.Resource}>; rel="type"`);
|
||||||
|
expect(response.getHeaders().link).toContain(`<${id}.acl>; rel="acl"`);
|
||||||
const parser = new Parser();
|
const parser = new Parser();
|
||||||
const triples = parser.parse(response._getData());
|
const triples = parser.parse(response._getData());
|
||||||
expect(triples).toBeRdfIsomorphic([
|
expect(triples).toBeRdfIsomorphic([
|
||||||
|
@ -46,6 +46,11 @@
|
|||||||
"PassthroughStore:_source": {
|
"PassthroughStore:_source": {
|
||||||
"@id": "urn:solid-server:default:MemoryResourceStore"
|
"@id": "urn:solid-server:default:MemoryResourceStore"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@id": "urn:solid-server:default:AclManager",
|
||||||
|
"@type": "UrlBasedAclManager",
|
||||||
|
"comment": "Needed for AclLinkMetadataWriter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
28
test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts
Normal file
28
test/unit/ldp/http/metadata/AclLinkMetadataWriter.test.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { createResponse } from 'node-mocks-http';
|
||||||
|
import type { AclManager } from '../../../../../src/authorization/AclManager';
|
||||||
|
import { AclLinkMetadataWriter } from '../../../../../src/ldp/http/metadata/AclLinkMetadataWriter';
|
||||||
|
import { RepresentationMetadata } from '../../../../../src/ldp/representation/RepresentationMetadata';
|
||||||
|
import type { ResourceIdentifier } from '../../../../../src/ldp/representation/ResourceIdentifier';
|
||||||
|
|
||||||
|
describe('An AclLinkMetadataWriter', (): void => {
|
||||||
|
const manager = {
|
||||||
|
getAclDocument: async(id: ResourceIdentifier): Promise<ResourceIdentifier> => ({ path: `${id.path}.acl` }),
|
||||||
|
} as AclManager;
|
||||||
|
const identifier = { path: 'http://test.com/foo' };
|
||||||
|
|
||||||
|
it('adds the acl link header.', async(): Promise<void> => {
|
||||||
|
const writer = new AclLinkMetadataWriter(manager);
|
||||||
|
const response = createResponse();
|
||||||
|
const metadata = new RepresentationMetadata(identifier);
|
||||||
|
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(response.getHeaders()).toEqual({ link: `<${identifier.path}.acl>; rel="acl"` });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can use a custom rel attribute.', async(): Promise<void> => {
|
||||||
|
const writer = new AclLinkMetadataWriter(manager, 'http://www.w3.org/ns/solid/terms#acl');
|
||||||
|
const response = createResponse();
|
||||||
|
const metadata = new RepresentationMetadata(identifier);
|
||||||
|
await expect(writer.handle({ response, metadata })).resolves.toBeUndefined();
|
||||||
|
expect(response.getHeaders()).toEqual({ link: `<${identifier.path}.acl>; rel="http://www.w3.org/ns/solid/terms#acl"` });
|
||||||
|
});
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user