feat: Always grant control permissions to pod owners

This commit is contained in:
Joachim Van Herwegen
2021-08-31 16:36:42 +02:00
parent 6c4ccb334d
commit 8f5d61911d
10 changed files with 224 additions and 6 deletions

View File

@@ -328,6 +328,43 @@ describe('A Solid server with IDP', (): void => {
res = await state.session.fetch(newWebId, patchOptions);
expect(res.status).toBe(205);
});
it('always has control over data in the pod.', async(): Promise<void> => {
const podBaseUrl = `${baseUrl}${podName}/`;
const brokenAcl = '<#authorization> a <http://www.w3.org/ns/auth/acl#Authorization> .';
// Make the acl file unusable
let res = await state.session.fetch(`${podBaseUrl}.acl`, {
method: 'PUT',
headers: { 'content-type': 'text/turtle' },
body: brokenAcl,
});
expect(res.status).toBe(205);
// The owner is locked out of their own pod due to a faulty acl file
res = await state.session.fetch(podBaseUrl);
expect(res.status).toBe(403);
const fixedAcl = `@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 <./>.`;
// Owner can still update the acl
res = await state.session.fetch(`${podBaseUrl}.acl`, {
method: 'PUT',
headers: { 'content-type': 'text/turtle' },
body: fixedAcl,
});
expect(res.status).toBe(205);
// Access is possible again
res = await state.session.fetch(podBaseUrl);
expect(res.status).toBe(200);
});
});
describe('setup', (): void => {

View File

@@ -8,6 +8,7 @@
"files-scs:config/http/middleware/no-websockets.json",
"files-scs:config/http/server-factory/no-websockets.json",
"files-scs:config/http/static/default.json",
"files-scs:config/identity/handler/default.json",
"files-scs:config/ldp/authentication/debug-auth-header.json",
"files-scs:config/ldp/authorization/webacl.json",
"files-scs:config/ldp/handler/default.json",

View File

@@ -0,0 +1,84 @@
import type { CredentialSet } from '../../../src/authentication/Credentials';
import { CredentialGroup } from '../../../src/authentication/Credentials';
import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader';
import type {
AccountSettings,
AccountStore,
} from '../../../src/identity/interaction/email-password/storage/AccountStore';
import type { AuxiliaryIdentifierStrategy } from '../../../src/ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { ResourceIdentifier } from '../../../src/ldp/representation/ResourceIdentifier';
describe('An OwnerPermissionReader', (): void => {
const owner = 'http://test.com/alice/profile/card#me';
const podBaseUrl = 'http://test.com/alice/';
let credentials: CredentialSet;
let identifier: ResourceIdentifier;
let settings: AccountSettings;
let accountStore: jest.Mocked<AccountStore>;
let aclStrategy: jest.Mocked<AuxiliaryIdentifierStrategy>;
let reader: OwnerPermissionReader;
beforeEach(async(): Promise<void> => {
credentials = { [CredentialGroup.agent]: { webId: owner }};
identifier = { path: `${podBaseUrl}.acl` };
settings = {
useIdp: true,
podBaseUrl,
};
accountStore = {
getSettings: jest.fn(async(webId: string): Promise<AccountSettings> => {
if (webId === owner) {
return settings;
}
throw new Error('No account');
}),
} as any;
aclStrategy = {
isAuxiliaryIdentifier: jest.fn((id): boolean => id.path.endsWith('.acl')),
} as any;
reader = new OwnerPermissionReader(accountStore, aclStrategy);
});
it('returns empty permissions for non-ACL resources.', async(): Promise<void> => {
identifier.path = podBaseUrl;
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
});
it('returns empty permissions if there is no agent WebID.', async(): Promise<void> => {
credentials = {};
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
});
it('returns empty permissions if the agent has no account.', async(): Promise<void> => {
credentials.agent!.webId = 'http://test.com/someone/else';
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
});
it('returns empty permissions if the account has no pod.', async(): Promise<void> => {
delete settings.podBaseUrl;
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
});
it('returns empty permissions if the target identifier is not in the pod.', async(): Promise<void> => {
identifier.path = 'http://somewhere.else/.acl';
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({});
});
it('returns full permissions if the owner is accessing an ACL resource in their pod.', async(): Promise<void> => {
await expect(reader.handle({ credentials, identifier })).resolves.toEqual({
[CredentialGroup.agent]: {
read: true,
write: true,
append: true,
create: true,
delete: true,
control: true,
},
});
});
});

View File

@@ -200,7 +200,7 @@ describe('A RegistrationManager', (): void => {
expect(podManager.createPod).toHaveBeenCalledTimes(1);
expect(podManager.createPod).toHaveBeenLastCalledWith({ path: `${baseUrl}${podName}/` }, podSettings, false);
expect(accountStore.create).toHaveBeenCalledTimes(1);
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: false });
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: false, podBaseUrl });
expect(accountStore.verify).toHaveBeenCalledTimes(1);
expect(accountStore.deleteAccount).toHaveBeenCalledTimes(0);
@@ -222,7 +222,7 @@ describe('A RegistrationManager', (): void => {
expect(ownershipValidator.handleSafe).toHaveBeenCalledTimes(1);
expect(ownershipValidator.handleSafe).toHaveBeenLastCalledWith({ webId });
expect(accountStore.create).toHaveBeenCalledTimes(1);
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: true });
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: true, podBaseUrl });
expect(identifierGenerator.generate).toHaveBeenCalledTimes(1);
expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName);
expect(podManager.createPod).toHaveBeenCalledTimes(1);
@@ -242,7 +242,7 @@ describe('A RegistrationManager', (): void => {
expect(ownershipValidator.handleSafe).toHaveBeenCalledTimes(1);
expect(ownershipValidator.handleSafe).toHaveBeenLastCalledWith({ webId });
expect(accountStore.create).toHaveBeenCalledTimes(1);
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: true });
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: true, podBaseUrl });
expect(identifierGenerator.generate).toHaveBeenCalledTimes(1);
expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName);
expect(podManager.createPod).toHaveBeenCalledTimes(1);
@@ -272,7 +272,10 @@ describe('A RegistrationManager', (): void => {
expect(identifierGenerator.generate).toHaveBeenCalledTimes(1);
expect(identifierGenerator.generate).toHaveBeenLastCalledWith(podName);
expect(accountStore.create).toHaveBeenCalledTimes(1);
expect(accountStore.create).toHaveBeenLastCalledWith(email, generatedWebID, password, { useIdp: true });
expect(accountStore.create).toHaveBeenLastCalledWith(email,
generatedWebID,
password,
{ useIdp: true, podBaseUrl });
expect(accountStore.verify).toHaveBeenCalledTimes(1);
expect(accountStore.verify).toHaveBeenLastCalledWith(email);
expect(podManager.createPod).toHaveBeenCalledTimes(1);
@@ -300,7 +303,10 @@ describe('A RegistrationManager', (): void => {
expect(podManager.createPod).toHaveBeenCalledTimes(1);
expect(podManager.createPod).toHaveBeenLastCalledWith({ path: baseUrl }, podSettings, true);
expect(accountStore.create).toHaveBeenCalledTimes(1);
expect(accountStore.create).toHaveBeenLastCalledWith(email, webId, password, { useIdp: false });
expect(accountStore.create).toHaveBeenLastCalledWith(email,
webId,
password,
{ useIdp: false, podBaseUrl: baseUrl });
expect(accountStore.verify).toHaveBeenCalledTimes(1);
expect(identifierGenerator.generate).toHaveBeenCalledTimes(0);