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.
111 lines
4.8 KiB
TypeScript
111 lines
4.8 KiB
TypeScript
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
|
import { getLoggerFor } from '../logging/LogUtil';
|
|
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
|
import { IdentifierMap, IdentifierSetMultiMap } from '../util/map/IdentifierMap';
|
|
import type { MapEntry } from '../util/map/MapUtil';
|
|
import { modify } from '../util/map/MapUtil';
|
|
import type { PermissionReaderInput } from './PermissionReader';
|
|
import { PermissionReader } from './PermissionReader';
|
|
import type { AccessMap, PermissionMap, PermissionSet } from './permissions/Permissions';
|
|
import { AccessMode } from './permissions/Permissions';
|
|
|
|
/**
|
|
* Determines `delete` and `create` permissions for those resources that need it
|
|
* by making sure the parent container has the required permissions.
|
|
*
|
|
* Create requires `append` permissions on the parent container.
|
|
* Delete requires `write` permissions on both the parent container and the resource itself.
|
|
*/
|
|
export class ParentContainerReader extends PermissionReader {
|
|
protected readonly logger = getLoggerFor(this);
|
|
|
|
private readonly reader: PermissionReader;
|
|
private readonly identifierStrategy: IdentifierStrategy;
|
|
|
|
public constructor(reader: PermissionReader, identifierStrategy: IdentifierStrategy) {
|
|
super();
|
|
this.reader = reader;
|
|
this.identifierStrategy = identifierStrategy;
|
|
}
|
|
|
|
public async handle({ requestedModes, credentials }: PermissionReaderInput): Promise<PermissionMap> {
|
|
// Finds the entries for which we require parent container permissions
|
|
const containerMap = this.findParents(requestedModes);
|
|
|
|
// Merges the necessary parent container modes with the already requested modes
|
|
const combinedModes = modify(new IdentifierSetMultiMap(requestedModes), { add: containerMap.values() });
|
|
const result = await this.reader.handleSafe({ requestedModes: combinedModes, credentials });
|
|
|
|
// Updates the create/delete permissions based on the parent container permissions
|
|
for (const [ identifier, [ container ]] of containerMap) {
|
|
this.logger.debug(`Determining ${identifier.path} create and delete permissions based on ${container.path}`);
|
|
result.set(identifier, this.addContainerPermissions(result.get(identifier), result.get(container)));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Finds the identifiers for which we need parent permissions.
|
|
* Values are the parent identifier and the permissions they need.
|
|
*/
|
|
private findParents(requestedModes: AccessMap): IdentifierMap<MapEntry<AccessMap>> {
|
|
const containerMap = new IdentifierMap<[ResourceIdentifier, Set<AccessMode>]>();
|
|
for (const [ identifier, modes ] of requestedModes.entrySets()) {
|
|
if (modes.has(AccessMode.create) || modes.has(AccessMode.delete)) {
|
|
const container = this.identifierStrategy.getParentContainer(identifier);
|
|
containerMap.set(identifier, [ container, this.getParentModes(modes) ]);
|
|
}
|
|
}
|
|
return containerMap;
|
|
}
|
|
|
|
/**
|
|
* Determines which permissions are required on the parent container.
|
|
*/
|
|
private getParentModes(modes: ReadonlySet<AccessMode>): Set<AccessMode> {
|
|
const containerModes: Set<AccessMode> = new Set();
|
|
if (modes.has(AccessMode.create)) {
|
|
containerModes.add(AccessMode.append);
|
|
}
|
|
if (modes.has(AccessMode.delete)) {
|
|
containerModes.add(AccessMode.write);
|
|
}
|
|
return containerModes;
|
|
}
|
|
|
|
/**
|
|
* Merges the container permission set into the resource permission set
|
|
* based on the parent container rules for create/delete permissions.
|
|
*/
|
|
private addContainerPermissions(resourceSet?: PermissionSet, containerSet?: PermissionSet): PermissionSet {
|
|
resourceSet = resourceSet ?? {};
|
|
containerSet = containerSet ?? {};
|
|
|
|
return this.interpretContainerPermission(resourceSet, containerSet);
|
|
}
|
|
|
|
/**
|
|
* Determines the create and delete permissions for the given resource permissions
|
|
* based on those of its parent container.
|
|
*/
|
|
private interpretContainerPermission(resourcePermission: PermissionSet, containerPermission: PermissionSet):
|
|
PermissionSet {
|
|
const mergedPermission = { ...resourcePermission };
|
|
|
|
// https://solidproject.org/TR/2021/wac-20210711:
|
|
// When an operation requests to create a resource as a member of a container resource,
|
|
// the server MUST match an Authorization allowing the acl:Append or acl:Write access privilege
|
|
// on the container for new members.
|
|
mergedPermission.create = containerPermission.append && resourcePermission.create !== false;
|
|
|
|
// https://solidproject.org/TR/2021/wac-20210711:
|
|
// When an operation requests to delete a resource,
|
|
// the server MUST match Authorizations allowing the acl:Write access privilege
|
|
// on the resource and the containing container.
|
|
mergedPermission.delete = resourcePermission.write && containerPermission.write &&
|
|
resourcePermission.delete !== false;
|
|
|
|
return mergedPermission;
|
|
}
|
|
}
|