mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00

The previous package was outdated, preventing us from updating TS. This one also lints YAML and JSON, and applies many more rules to the test files, explaining all the changes in this PR.
270 lines
8.6 KiB
TypeScript
270 lines
8.6 KiB
TypeScript
import { promises as fsPromises } from 'fs';
|
|
import fetch from 'cross-fetch';
|
|
import type { App, ResourceStore } from '../../src/';
|
|
import { BasicRepresentation, isSystemError, joinFilePath, joinUrl } from '../../src/';
|
|
import { AclHelper } from '../util/AclHelper';
|
|
import { deleteResource, getResource, postResource, putResource } from '../util/FetchUtil';
|
|
import { getPort } from '../util/Util';
|
|
import {
|
|
getDefaultVariables,
|
|
getPresetConfigPath,
|
|
getTestConfigPath,
|
|
getTestFolder,
|
|
instantiateFromConfig,
|
|
removeFolder,
|
|
} from './Config';
|
|
|
|
const port = getPort('LpdHandlerWithAuth');
|
|
const baseUrl = `http://localhost:${port}/`;
|
|
|
|
const rootFilePath = getTestFolder('full-config-acl');
|
|
const stores: [string, any][] = [
|
|
[ 'in-memory storage', {
|
|
storeConfig: 'storage/backend/memory.json',
|
|
teardown: jest.fn(),
|
|
}],
|
|
[ 'on-disk storage', {
|
|
storeConfig: 'storage/backend/file.json',
|
|
teardown: async(): Promise<void> => removeFolder(rootFilePath),
|
|
}],
|
|
];
|
|
|
|
describe.each(stores)('An LDP handler with auth using %s', (name, { storeConfig, teardown }): void => {
|
|
let app: App;
|
|
let store: ResourceStore;
|
|
let aclHelper: AclHelper;
|
|
const permanent = `${baseUrl}document.txt`;
|
|
|
|
beforeAll(async(): Promise<void> => {
|
|
const variables = {
|
|
...getDefaultVariables(port, baseUrl),
|
|
'urn:solid-server:default:variable:rootFilePath': rootFilePath,
|
|
};
|
|
|
|
// Create and start the server
|
|
const instances = await instantiateFromConfig(
|
|
'urn:solid-server:test:Instances',
|
|
[
|
|
getPresetConfigPath(storeConfig),
|
|
getTestConfigPath('ldp-with-auth.json'),
|
|
],
|
|
variables,
|
|
) as Record<string, any>;
|
|
({ app, store } = instances);
|
|
|
|
await app.start();
|
|
|
|
// Create test helper for manipulating acl
|
|
aclHelper = new AclHelper(store);
|
|
});
|
|
|
|
beforeEach(async(): Promise<void> => {
|
|
// Set the root acl file to allow everything and create a single document
|
|
await store.setRepresentation({ path: permanent }, new BasicRepresentation('PERMANENT', 'text/plain'));
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { read: true, write: true, append: true, control: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
default: true,
|
|
});
|
|
});
|
|
|
|
afterAll(async(): Promise<void> => {
|
|
await teardown();
|
|
await app.stop();
|
|
});
|
|
|
|
it('can add a document, read it and delete it if allowed.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { read: true, write: true, append: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
default: true,
|
|
});
|
|
|
|
// PUT
|
|
const document = `${baseUrl}test.txt`;
|
|
await putResource(document, { contentType: 'text/plain', body: 'TESTDATA', exists: false });
|
|
|
|
// GET
|
|
const response = await getResource(document);
|
|
await expect(response.text()).resolves.toBe('TESTDATA');
|
|
expect(response.headers.get('wac-allow')).toBe('user="append read write",public="append read write"');
|
|
|
|
// DELETE
|
|
await deleteResource(document);
|
|
});
|
|
|
|
it('can not add a file to the store if not allowed.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { read: true, write: true, append: true },
|
|
agentClass: 'authenticated',
|
|
accessTo: true,
|
|
default: true,
|
|
});
|
|
|
|
// PUT fail
|
|
const documentUrl = `${baseUrl}test.txt`;
|
|
const response = await fetch(documentUrl, { method: 'PUT' });
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('can not add/delete if only read is allowed.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { read: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
default: true,
|
|
});
|
|
|
|
// PUT fail
|
|
const document = `${baseUrl}test.txt`;
|
|
let response = await fetch(document, { method: 'PUT' });
|
|
expect(response.status).toBe(401);
|
|
|
|
// GET permanent file
|
|
response = await getResource(permanent);
|
|
await expect(response.text()).resolves.toBe('PERMANENT');
|
|
expect(response.headers.get('wac-allow')).toBe('user="read",public="read"');
|
|
|
|
// DELETE fail
|
|
response = await fetch(permanent, { method: 'DELETE' });
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('can add files but not write to them if append is allowed.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { append: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
default: true,
|
|
});
|
|
|
|
// POST
|
|
const slug = 'slug';
|
|
let response = await postResource(baseUrl, { contentType: 'text/plain', slug, body: 'SLUGDATA' });
|
|
const document = response.headers.get('location')!;
|
|
|
|
// PUT fail
|
|
response = await fetch(document, { method: 'PUT' });
|
|
expect(response.status).toBe(401);
|
|
|
|
// DELETE fail
|
|
response = await fetch(document, { method: 'DELETE' });
|
|
expect(response.status).toBe(401);
|
|
|
|
// Clean up resource
|
|
await store.deleteResource({ path: document });
|
|
});
|
|
|
|
it('can not access an acl file if no control rights are provided.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { read: true, write: true, append: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
});
|
|
|
|
const response = await fetch(`${baseUrl}.acl`);
|
|
expect(response.status).toBe(401);
|
|
});
|
|
|
|
it('can only access an acl file if control rights are provided.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { control: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
});
|
|
|
|
const response = await fetch(`${baseUrl}.acl`);
|
|
expect(response.status).toBe(200);
|
|
expect(response.headers.get('wac-allow'))
|
|
.toBe('user="append control read write",public="append control read write"');
|
|
|
|
// Close response
|
|
await response.text();
|
|
});
|
|
|
|
it('returns the legacy WWW-Authenticate header on 401 requests.', async(): Promise<void> => {
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: {},
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
});
|
|
|
|
const response = await fetch(`${baseUrl}.acl`);
|
|
expect(response.status).toBe(401);
|
|
expect(response.headers.get('www-authenticate')).toBe('Bearer scope="openid webid"');
|
|
});
|
|
|
|
it('supports file paths with spaces.', async(): Promise<void> => {
|
|
// Specific problem for file store, so only do this if the rootFilePath folder exists
|
|
try {
|
|
await fsPromises.lstat(rootFilePath);
|
|
} catch (error: unknown) {
|
|
if (isSystemError(error) && error.code === 'ENOENT') {
|
|
return;
|
|
}
|
|
throw error;
|
|
}
|
|
|
|
// Correct identifier when there are spaces
|
|
const identifier = { path: `${baseUrl}with%20spaces.txt` };
|
|
|
|
// Set acl specifically for this identifier
|
|
const acl = `@prefix acl: <http://www.w3.org/ns/auth/acl#>.
|
|
@prefix foaf: <http://xmlns.com/foaf/0.1/>.
|
|
<#authorization>
|
|
a acl:Authorization;
|
|
acl:agentClass foaf:Agent;
|
|
acl:mode acl:Read;
|
|
acl:accessTo <${identifier.path}>.`;
|
|
|
|
// Prevent other access to make sure there is no hierarchy bug
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: {},
|
|
agentClass: 'agent',
|
|
default: true,
|
|
});
|
|
|
|
// Write files manually to make sure there are spaces
|
|
await fsPromises.writeFile(joinFilePath(rootFilePath, 'with spaces.txt.acl'), acl);
|
|
await fsPromises.writeFile(joinFilePath(rootFilePath, 'with spaces.txt'), 'valid data');
|
|
|
|
// GET file
|
|
const response = await getResource(identifier.path);
|
|
expect(await response.text()).toContain('valid data');
|
|
});
|
|
|
|
it('prevents creation of intermediate intermediate containers if they are not allowed.', async(): Promise<void> => {
|
|
const url = joinUrl(baseUrl, 'foo/bar/');
|
|
// Not allowed since there are no append permissions on the base container
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { write: true },
|
|
agentClass: 'agent',
|
|
default: true,
|
|
});
|
|
let response = await fetch(url, { method: 'PUT' });
|
|
expect(response.status).toBe(401);
|
|
|
|
// Not allowed since there are no write permissions for the target
|
|
await aclHelper.setSimpleAcl(baseUrl, {
|
|
permissions: { append: true },
|
|
agentClass: 'agent',
|
|
accessTo: true,
|
|
});
|
|
response = await fetch(url, { method: 'PUT' });
|
|
expect(response.status).toBe(401);
|
|
|
|
// This covers all required permissions
|
|
await aclHelper.setSimpleAcl(baseUrl, [
|
|
{ permissions: { append: true },
|
|
agentClass: 'agent',
|
|
accessTo: true },
|
|
{ permissions: { write: true },
|
|
agentClass: 'agent',
|
|
default: true },
|
|
]);
|
|
await putResource(url, { contentType: 'text/plain', exists: false });
|
|
});
|
|
});
|