Skip to content

Commit

Permalink
feat: Replace acl specific permissions with generic permissions
Browse files Browse the repository at this point in the history
This required AuxiliaryStrategy to have a new function
indicating if the auxiliary resource just used its associated resource authorization
or its own.
  • Loading branch information
joachimvh committed Sep 28, 2021
1 parent 5104cd5 commit 7f8b923
Show file tree
Hide file tree
Showing 46 changed files with 221 additions and 152 deletions.
2 changes: 1 addition & 1 deletion config/default.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/memory.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/dynamic.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/dynamic.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/example-https-file.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/file.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/file-no-setup.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/file.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/file.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/file.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
3 changes: 1 addition & 2 deletions config/ldp/README.md
Expand Up @@ -30,5 +30,4 @@ Contains a list of metadata writers that will be run on outgoing responses.
## Modes
Determines which modes are needed for requests,
by default this is based on the used HTTP method.
* *acl*: The default setup with specific support for accessing .acl documents.
* *no-acl*: Same as above but interprets .acl documents as any other document.
* *default*: Bases required modes on HTTP method.
18 changes: 0 additions & 18 deletions config/ldp/modes/acl.json

This file was deleted.

Expand Up @@ -2,7 +2,7 @@
"@context": "https://linkedsoftwaredependencies.org/bundles/npm/@solid/community-server/^1.0.0/components/context.jsonld",
"@graph": [
{
"comment": "Extracts the required permissions based on the HTTP method.",
"comment": "Determines required modes based on HTTP methods.",
"@id": "urn:solid-server:default:ModesExtractor",
"@type": "WaterfallHandler",
"handlers": [
Expand Down
2 changes: 1 addition & 1 deletion config/memory-subdomains.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/memory.json",
"files-scs:config/storage/key-value/resource-store.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/path-routing.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/regex.json",
"files-scs:config/storage/key-value/memory.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/sparql-endpoint-no-setup.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/sparql.json",
"files-scs:config/storage/key-value/memory.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
2 changes: 1 addition & 1 deletion config/sparql-endpoint.json
Expand Up @@ -18,7 +18,7 @@
"files-scs:config/ldp/handler/default.json",
"files-scs:config/ldp/metadata-parser/default.json",
"files-scs:config/ldp/metadata-writer/default.json",
"files-scs:config/ldp/modes/acl.json",
"files-scs:config/ldp/modes/default.json",
"files-scs:config/storage/backend/sparql.json",
"files-scs:config/storage/key-value/memory.json",
"files-scs:config/storage/middleware/default.json",
Expand Down
1 change: 1 addition & 0 deletions config/util/auxiliary/strategies/acl.json
Expand Up @@ -15,6 +15,7 @@
"@type": "RdfValidator",
"converter": { "@id": "urn:solid-server:default:RepresentationConverter" }
},
"ownAuthorization": true,
"requiredInRoot": true
},
{
Expand Down
3 changes: 2 additions & 1 deletion src/authorization/AllStaticReader.ts
Expand Up @@ -16,7 +16,8 @@ export class AllStaticReader extends PermissionReader {
read: allow,
write: allow,
append: allow,
control: allow,
create: allow,
delete: allow,
});
}

Expand Down
11 changes: 8 additions & 3 deletions src/authorization/AuxiliaryReader.ts
@@ -1,4 +1,4 @@
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { AuxiliaryStrategy } from '../ldp/auxiliary/AuxiliaryStrategy';
import type { PermissionSet } from '../ldp/permissions/Permissions';
import { getLoggerFor } from '../logging/LogUtil';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
Expand All @@ -15,9 +15,9 @@ export class AuxiliaryReader extends PermissionReader {
protected readonly logger = getLoggerFor(this);

private readonly resourceReader: PermissionReader;
private readonly auxiliaryStrategy: AuxiliaryIdentifierStrategy;
private readonly auxiliaryStrategy: AuxiliaryStrategy;

public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryIdentifierStrategy) {
public constructor(resourceReader: PermissionReader, auxiliaryStrategy: AuxiliaryStrategy) {
super();
this.resourceReader = resourceReader;
this.auxiliaryStrategy = auxiliaryStrategy;
Expand All @@ -44,6 +44,11 @@ export class AuxiliaryReader extends PermissionReader {
if (!this.auxiliaryStrategy.isAuxiliaryIdentifier(auxiliaryAuth.identifier)) {
throw new NotImplementedHttpError('AuxiliaryAuthorizer only supports auxiliary resources.');
}

if (this.auxiliaryStrategy.usesOwnAuthorization(auxiliaryAuth.identifier)) {
throw new NotImplementedHttpError('Auxiliary resource uses its own permissions.');
}

return {
...auxiliaryAuth,
identifier: this.auxiliaryStrategy.getAssociatedIdentifier(auxiliaryAuth.identifier),
Expand Down
68 changes: 46 additions & 22 deletions src/authorization/WebAclReader.ts
Expand Up @@ -3,7 +3,9 @@ import { Store } from 'n3';
import { CredentialGroup } from '../authentication/Credentials';
import type { Credential, CredentialSet } from '../authentication/Credentials';
import type { AuxiliaryIdentifierStrategy } from '../ldp/auxiliary/AuxiliaryIdentifierStrategy';
import type { Permission, PermissionSet } from '../ldp/permissions/Permissions';
import { AclMode } from '../ldp/permissions/AclPermission';
import type { AclPermission } from '../ldp/permissions/AclPermission';
import type { PermissionSet } from '../ldp/permissions/Permissions';
import { AccessMode } from '../ldp/permissions/Permissions';
import type { Representation } from '../ldp/representation/Representation';
import type { ResourceIdentifier } from '../ldp/representation/ResourceIdentifier';
Expand All @@ -14,19 +16,18 @@ import { createErrorMessage } from '../util/errors/ErrorUtil';
import { ForbiddenHttpError } from '../util/errors/ForbiddenHttpError';
import { InternalServerError } from '../util/errors/InternalServerError';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
import { readableToQuads } from '../util/StreamUtil';
import { ACL, RDF } from '../util/Vocabularies';
import type { AccessChecker } from './access-checkers/AccessChecker';
import type { PermissionReaderInput } from './PermissionReader';
import { PermissionReader } from './PermissionReader';

const modesMap: Record<string, AccessMode> = {
const modesMap: Record<string, keyof AclPermission> = {
[ACL.Read]: AccessMode.read,
[ACL.Write]: AccessMode.write,
[ACL.Append]: AccessMode.append,
[ACL.Control]: AccessMode.control,
[ACL.Control]: AclMode.control,
} as const;

/**
Expand All @@ -50,12 +51,6 @@ export class WebAclReader extends PermissionReader {
this.accessChecker = accessChecker;
}

public async canHandle({ identifier }: PermissionReaderInput): Promise<void> {
if (this.aclStrategy.isAuxiliaryIdentifier(identifier)) {
throw new NotImplementedHttpError('WebAclAuthorizer does not support permissions on auxiliary resources.');
}
}

/**
* Checks if an agent is allowed to execute the requested actions.
* Will throw an error if this is not the case.
Expand All @@ -66,24 +61,28 @@ export class WebAclReader extends PermissionReader {
// Determine the required access modes
this.logger.debug(`Retrieving permissions of ${credentials.agent?.webId} for ${identifier.path}`);

const isAcl = this.aclStrategy.isAuxiliaryIdentifier(identifier);
const mainIdentifier = isAcl ? this.aclStrategy.getAssociatedIdentifier(identifier) : identifier;

// Determine the full authorization for the agent granted by the applicable ACL
const acl = await this.getAclRecursive(identifier);
return this.createPermissions(credentials, acl);
const acl = await this.getAclRecursive(mainIdentifier);
return this.createPermissions(credentials, acl, isAcl);
}

/**
* Creates an Authorization object based on the quads found in the ACL.
* @param credentials - Credentials to check permissions for.
* @param acl - Store containing all relevant authorization triples.
* @param isAcl - If the target resource is an acl document.
*/
private async createPermissions(credentials: CredentialSet, acl: Store):
private async createPermissions(credentials: CredentialSet, acl: Store, isAcl: boolean):
Promise<PermissionSet> {
const publicPermissions = await this.determinePermissions(acl, credentials.public);
const agentPermissions = await this.determinePermissions(acl, credentials.agent);

return {
[CredentialGroup.agent]: agentPermissions,
[CredentialGroup.public]: publicPermissions,
[CredentialGroup.agent]: this.updateAclPermissions(agentPermissions, isAcl),
[CredentialGroup.public]: this.updateAclPermissions(publicPermissions, isAcl),
};
}

Expand All @@ -93,10 +92,10 @@ export class WebAclReader extends PermissionReader {
* @param acl - Store containing all relevant authorization triples.
* @param credentials - Credentials to find the permissions for.
*/
private async determinePermissions(acl: Store, credentials?: Credential): Promise<Permission> {
const permissions: Permission = {};
private async determinePermissions(acl: Store, credentials?: Credential): Promise<AclPermission> {
const aclPermissions: AclPermission = {};
if (!credentials) {
return permissions;
return aclPermissions;
}

// Apply all ACL rules
Expand All @@ -108,18 +107,43 @@ export class WebAclReader extends PermissionReader {
const modes = acl.getObjects(rule, ACL.mode, null);
for (const { value: mode } of modes) {
if (mode in modesMap) {
permissions[modesMap[mode]] = true;
aclPermissions[modesMap[mode]] = true;
}
}
}
}

if (permissions.write) {
if (aclPermissions.write) {
// Write permission implies Append permission
permissions.append = true;
aclPermissions.append = true;
}

return permissions;
return aclPermissions;
}

/**
* Sets the correct values for non-acl permissions such as create and delete.
* Also adds the correct values to indicate that having control permission
* implies having read/write/etc. on the acl resource.
*
* The main reason for keeping the control value is so we can correctly set the WAC-Allow header later.
*/
private updateAclPermissions(aclPermissions: AclPermission, isAcl: boolean): AclPermission {
if (isAcl) {
return {
read: aclPermissions.control,
append: aclPermissions.control,
write: aclPermissions.control,
create: aclPermissions.control,
delete: aclPermissions.control,
control: aclPermissions.control,
};
}
return {
...aclPermissions,
create: aclPermissions.write,
delete: aclPermissions.write,
};
}

/**
Expand Down
1 change: 0 additions & 1 deletion src/index.ts
Expand Up @@ -154,7 +154,6 @@ export * from './ldp/operations/PostOperationHandler';
export * from './ldp/operations/PutOperationHandler';

// LDP/Permissions
export * from './ldp/permissions/AclModesExtractor';
export * from './ldp/permissions/Permissions';
export * from './ldp/permissions/ModesExtractor';
export * from './ldp/permissions/MethodModesExtractor';
Expand Down
6 changes: 6 additions & 0 deletions src/ldp/auxiliary/AuxiliaryStrategy.ts
Expand Up @@ -9,6 +9,12 @@ import type { AuxiliaryIdentifierStrategy } from './AuxiliaryIdentifierStrategy'
* supported by this strategy.
*/
export interface AuxiliaryStrategy extends AuxiliaryIdentifierStrategy {
/**
* Whether this auxiliary resources uses its own authorization instead of the associated resource authorization.
* @param identifier - Identifier of the auxiliary resource.
*/
usesOwnAuthorization: (identifier: ResourceIdentifier) => boolean;

/**
* Whether the root storage container requires this auxiliary resource to be present.
* If yes, this means they can't be deleted individually from such a container.
Expand Down
8 changes: 7 additions & 1 deletion src/ldp/auxiliary/ComposedAuxiliaryStrategy.ts
Expand Up @@ -14,13 +14,15 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
private readonly identifierStrategy: AuxiliaryIdentifierStrategy;
private readonly metadataGenerator?: MetadataGenerator;
private readonly validator?: Validator;
private readonly ownAuthorization: boolean;
private readonly requiredInRoot: boolean;

public constructor(identifierStrategy: AuxiliaryIdentifierStrategy, metadataGenerator?: MetadataGenerator,
validator?: Validator, requiredInRoot = false) {
validator?: Validator, ownAuthorization = false, requiredInRoot = false) {
this.identifierStrategy = identifierStrategy;
this.metadataGenerator = metadataGenerator;
this.validator = validator;
this.ownAuthorization = ownAuthorization;
this.requiredInRoot = requiredInRoot;
}

Expand All @@ -40,6 +42,10 @@ export class ComposedAuxiliaryStrategy implements AuxiliaryStrategy {
return this.identifierStrategy.getAssociatedIdentifier(identifier);
}

public usesOwnAuthorization(): boolean {
return this.ownAuthorization;
}

public isRequiredInRoot(): boolean {
return this.requiredInRoot;
}
Expand Down
5 changes: 5 additions & 0 deletions src/ldp/auxiliary/RoutingAuxiliaryStrategy.ts
Expand Up @@ -18,6 +18,11 @@ export class RoutingAuxiliaryStrategy extends RoutingAuxiliaryIdentifierStrategy
super(sources);
}

public usesOwnAuthorization(identifier: ResourceIdentifier): boolean {
const source = this.getMatchingSource(identifier);
return source.usesOwnAuthorization(identifier);
}

public isRequiredInRoot(identifier: ResourceIdentifier): boolean {
const source = this.getMatchingSource(identifier);
return source.isRequiredInRoot(identifier);
Expand Down

0 comments on commit 7f8b923

Please sign in to comment.