diff --git a/src/authorization/WebAclAuthorizer.ts b/src/authorization/WebAclAuthorizer.ts index f01fe04cf..e7c6679d3 100644 --- a/src/authorization/WebAclAuthorizer.ts +++ b/src/authorization/WebAclAuthorizer.ts @@ -40,20 +40,21 @@ export class WebAclAuthorizer extends Authorizer { * Will throw an error if this is not the case. * @param input - Relevant data needed to check if access can be granted. */ - public async handle(input: AuthorizerArgs): Promise { - const store = await this.getAclRecursive(input.identifier); - if (await this.aclManager.isAclDocument(input.identifier)) { - // Solid, §4.3.3: "To discover, read, create, or modify an ACL auxiliary resource, an acl:agent MUST - // have acl:Control privileges per the ACL inheritance algorithm on the resource directly associated with it." - // https://solid.github.io/specification/protocol#auxiliary-resources-reserved - this.checkPermission(input.credentials, store, 'control'); - } else { - (Object.keys(input.permissions) as (keyof PermissionSet)[]).forEach((key): void => { - if (input.permissions[key]) { - this.checkPermission(input.credentials, store, key); - } - }); + public async handle({ identifier, permissions, credentials }: AuthorizerArgs): Promise { + // Solid, §4.3.3: "To discover, read, create, or modify an ACL auxiliary resource, an acl:agent MUST + // have acl:Control privileges per the ACL inheritance algorithm on the resource directly associated with it." + // https://solid.github.io/specification/protocol#auxiliary-resources-reserved + const modes = await this.aclManager.isAclDocument(identifier) ? + [ 'control' ] : + (Object.keys(permissions) as (keyof PermissionSet)[]).filter((key): boolean => permissions[key]); + + // Verify that all required modes are set for the given agent + this.logger.debug(`Checking if ${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`); + const store = await this.getAclRecursive(identifier); + for (const mode of modes) { + this.checkPermission(credentials, store, mode); } + this.logger.debug(`${credentials.webId} has ${modes.join()} permissions for ${identifier.path}`); } /** diff --git a/test/unit/authorization/WebAclAuthorizer.test.ts b/test/unit/authorization/WebAclAuthorizer.test.ts index cbc6728ce..f49fd805c 100644 --- a/test/unit/authorization/WebAclAuthorizer.test.ts +++ b/test/unit/authorization/WebAclAuthorizer.test.ts @@ -35,7 +35,7 @@ describe('A WebAclAuthorizer', (): void => { permissions = { read: true, append: false, - write: false, + write: true, }; credentials = {}; identifier = { path: 'http://test.com/foo' }; @@ -56,6 +56,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined(); }); @@ -70,6 +71,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/Agent')), quad(nn('auth'), nn(`${acl}default`), nn(identifierStrategy.getParentContainer(identifier).path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]), } as Representation; }; @@ -81,6 +83,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); credentials.webId = 'http://test.com/user'; await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined(); @@ -91,6 +94,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agentClass`), nn('http://xmlns.com/foaf/0.1/AuthenticatedAgent')), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(UnauthorizedHttpError); }); @@ -101,6 +105,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); await expect(authorizer.handle({ identifier, permissions, credentials })).resolves.toBeUndefined(); }); @@ -111,6 +116,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agent`), nn('http://test.com/differentUser')), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError); }); @@ -134,6 +140,7 @@ describe('A WebAclAuthorizer', (): void => { quad(nn('auth'), nn(`${acl}agent`), nn(credentials.webId!)), quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)), quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)), + quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Write`)), ]) } as Representation); identifier = await aclManager.getAclDocument(identifier); await expect(authorizer.handle({ identifier, permissions, credentials })).rejects.toThrow(ForbiddenHttpError);