mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Remove agent/user permission differentiation
This was only used for the WAC-Allow header and greatly simplifies how we use permissions.
This commit is contained in:
parent
6ad5c0c797
commit
c46d01d3d7
@ -68,6 +68,7 @@ These changes are relevant if you wrote custom modules for the server that depen
|
|||||||
- `OwnerPermissionReader` input parameter `aclStrategy` was renamed to `authStrategy`.
|
- `OwnerPermissionReader` input parameter `aclStrategy` was renamed to `authStrategy`.
|
||||||
- `TemplatedResourcesGenerator` has been renamed to `BaseResourcesGenerator` and has a different interface now.
|
- `TemplatedResourcesGenerator` has been renamed to `BaseResourcesGenerator` and has a different interface now.
|
||||||
- `CredentialSet` was replaced by a single `Credentials` interface.
|
- `CredentialSet` was replaced by a single `Credentials` interface.
|
||||||
|
`PermissionSet` and `Permission` were merged into a single interface.
|
||||||
This impacts all authentication and authorization related classes.
|
This impacts all authentication and authorization related classes.
|
||||||
- `HttpServerFactory.startServer` function was renamed to `createServer` and is no longer expected to start the server.
|
- `HttpServerFactory.startServer` function was renamed to `createServer` and is no longer expected to start the server.
|
||||||
|
|
||||||
|
@ -19,12 +19,12 @@ import { ACL } from '../util/Vocabularies';
|
|||||||
import { getAccessControlledResources } from './AcpUtil';
|
import { getAccessControlledResources } from './AcpUtil';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import type { AclPermission } from './permissions/AclPermission';
|
import type { AclPermissionSet } from './permissions/AclPermissionSet';
|
||||||
import { AclMode } from './permissions/AclPermission';
|
import { AclMode } from './permissions/AclPermissionSet';
|
||||||
import { AccessMode } from './permissions/Permissions';
|
import { AccessMode } from './permissions/Permissions';
|
||||||
import type { PermissionMap, PermissionSet } from './permissions/Permissions';
|
import type { PermissionMap, PermissionSet } from './permissions/Permissions';
|
||||||
|
|
||||||
const modesMap: Record<string, Readonly<(keyof AclPermission)[]>> = {
|
const modesMap: Record<string, Readonly<(keyof AclPermissionSet)[]>> = {
|
||||||
[ACL.Read]: [ AccessMode.read ],
|
[ACL.Read]: [ AccessMode.read ],
|
||||||
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
|
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
|
||||||
[ACL.Append]: [ AccessMode.append ],
|
[ACL.Append]: [ AccessMode.append ],
|
||||||
@ -85,15 +85,11 @@ export class AcpReader extends PermissionReader {
|
|||||||
}
|
}
|
||||||
const modes = allowAccessModes(policies, context);
|
const modes = allowAccessModes(policies, context);
|
||||||
|
|
||||||
// We don't do a separate ACP run for public and agent credentials
|
const permissionSet: PermissionSet = { };
|
||||||
// as that is only relevant for the WAC-Allow header.
|
for (const aclMode of modes) {
|
||||||
// All permissions are put in the `agent` field of the PermissionSet,
|
if (aclMode in modesMap) {
|
||||||
// as the actual field used does not matter for authorization.
|
for (const mode of modesMap[aclMode]) {
|
||||||
const permissionSet: PermissionSet = { agent: {}};
|
permissionSet[mode as AccessMode] = true;
|
||||||
for (const mode of modes) {
|
|
||||||
if (mode in modesMap) {
|
|
||||||
for (const permission of modesMap[mode]) {
|
|
||||||
permissionSet.agent![permission as AccessMode] = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import { permissionSetKeys } from './permissions/Permissions';
|
import type { PermissionSet, PermissionMap } from './permissions/Permissions';
|
||||||
import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PermissionReader which sets all permissions to true or false
|
* PermissionReader which sets all permissions to true or false
|
||||||
* independently of the identifier and requested permissions.
|
* independently of the identifier and requested permissions.
|
||||||
*/
|
*/
|
||||||
export class AllStaticReader extends PermissionReader {
|
export class AllStaticReader extends PermissionReader {
|
||||||
private readonly permissions: Permission;
|
private readonly permissionSet: PermissionSet;
|
||||||
|
|
||||||
public constructor(allow: boolean) {
|
public constructor(allow: boolean) {
|
||||||
super();
|
super();
|
||||||
this.permissions = Object.freeze({
|
this.permissionSet = Object.freeze({
|
||||||
read: allow,
|
read: allow,
|
||||||
write: allow,
|
write: allow,
|
||||||
append: allow,
|
append: allow,
|
||||||
@ -24,18 +23,9 @@ export class AllStaticReader extends PermissionReader {
|
|||||||
|
|
||||||
public async handle({ requestedModes }: PermissionReaderInput): Promise<PermissionMap> {
|
public async handle({ requestedModes }: PermissionReaderInput): Promise<PermissionMap> {
|
||||||
const availablePermissions = new IdentifierMap<PermissionSet>();
|
const availablePermissions = new IdentifierMap<PermissionSet>();
|
||||||
const permissions = this.createPermissions();
|
|
||||||
for (const [ identifier ] of requestedModes) {
|
for (const [ identifier ] of requestedModes) {
|
||||||
availablePermissions.set(identifier, permissions);
|
availablePermissions.set(identifier, this.permissionSet);
|
||||||
}
|
}
|
||||||
return availablePermissions;
|
return availablePermissions;
|
||||||
}
|
}
|
||||||
|
|
||||||
private createPermissions(): PermissionSet {
|
|
||||||
const result: PermissionSet = {};
|
|
||||||
for (const group of permissionSetKeys) {
|
|
||||||
result[group] = this.permissions;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,9 @@ import type { MapEntry } from '../util/map/MapUtil';
|
|||||||
import { modify } from '../util/map/MapUtil';
|
import { modify } from '../util/map/MapUtil';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import { AclMode } from './permissions/AclPermission';
|
import { AclMode } from './permissions/AclPermissionSet';
|
||||||
import type { AclPermission } from './permissions/AclPermission';
|
import type { AclPermissionSet } from './permissions/AclPermissionSet';
|
||||||
import type { AccessMap, AccessMode, PermissionMap, PermissionSet } from './permissions/Permissions';
|
import type { AccessMap, AccessMode, PermissionSet, PermissionMap } from './permissions/Permissions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the permission for authorization resources (such as ACL or ACR).
|
* Determines the permission for authorization resources (such as ACL or ACR).
|
||||||
@ -64,17 +64,13 @@ export class AuthAuxiliaryReader extends PermissionReader {
|
|||||||
* Updates the permissions for an authorization resource
|
* Updates the permissions for an authorization resource
|
||||||
* by interpreting the Control access mode as allowing full access.
|
* by interpreting the Control access mode as allowing full access.
|
||||||
*/
|
*/
|
||||||
protected interpretControl(identifier: ResourceIdentifier, permissionSet: PermissionSet = {}): PermissionSet {
|
protected interpretControl(identifier: ResourceIdentifier, permissionSet: AclPermissionSet = {}): PermissionSet {
|
||||||
const authSet: PermissionSet = {};
|
const { control } = permissionSet;
|
||||||
for (const [ group, permissions ] of Object.entries(permissionSet) as [ keyof PermissionSet, AclPermission ][]) {
|
return {
|
||||||
const { control } = permissions;
|
read: control,
|
||||||
authSet[group] = {
|
append: control,
|
||||||
read: control,
|
write: control,
|
||||||
append: control,
|
control,
|
||||||
write: control,
|
} as AclPermissionSet;
|
||||||
control,
|
|
||||||
} as AclPermission;
|
|
||||||
}
|
|
||||||
return authSet;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import { filter } from '../util/IterableUtil';
|
|||||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import type { AclPermission } from './permissions/AclPermission';
|
import type { AclPermissionSet } from './permissions/AclPermissionSet';
|
||||||
import type { PermissionMap } from './permissions/Permissions';
|
import type { PermissionMap } from './permissions/Permissions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,14 +51,14 @@ export class OwnerPermissionReader extends PermissionReader {
|
|||||||
for (const auth of auths) {
|
for (const auth of auths) {
|
||||||
if (this.identifierStrategy.contains(podBaseUrl, auth, true)) {
|
if (this.identifierStrategy.contains(podBaseUrl, auth, true)) {
|
||||||
this.logger.debug(`Granting Control permissions to owner on ${auth.path}`);
|
this.logger.debug(`Granting Control permissions to owner on ${auth.path}`);
|
||||||
result.set(auth, { agent: {
|
result.set(auth, {
|
||||||
read: true,
|
read: true,
|
||||||
write: true,
|
write: true,
|
||||||
append: true,
|
append: true,
|
||||||
create: true,
|
create: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
control: true,
|
control: true,
|
||||||
} as AclPermission });
|
} as AclPermissionSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -6,7 +6,7 @@ import type { MapEntry } from '../util/map/MapUtil';
|
|||||||
import { modify } from '../util/map/MapUtil';
|
import { modify } from '../util/map/MapUtil';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import type { PermissionMap, Permission, PermissionSet, AccessMap } from './permissions/Permissions';
|
import type { PermissionMap, PermissionSet, AccessMap } from './permissions/Permissions';
|
||||||
import { AccessMode } from './permissions/Permissions';
|
import { AccessMode } from './permissions/Permissions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,20 +80,16 @@ export class ParentContainerReader extends PermissionReader {
|
|||||||
private addContainerPermissions(resourceSet?: PermissionSet, containerSet?: PermissionSet): PermissionSet {
|
private addContainerPermissions(resourceSet?: PermissionSet, containerSet?: PermissionSet): PermissionSet {
|
||||||
resourceSet = resourceSet ?? {};
|
resourceSet = resourceSet ?? {};
|
||||||
containerSet = containerSet ?? {};
|
containerSet = containerSet ?? {};
|
||||||
// Already copying the `permissionSet` here since the loop only iterates over the container entries.
|
|
||||||
// It is possible `resourceSet` contains a key that `containerSet` does not contain.
|
return this.interpretContainerPermission(resourceSet, containerSet);
|
||||||
const resultSet: PermissionSet = { ...resourceSet };
|
|
||||||
for (const [ group, containerPerms ] of Object.entries(containerSet) as [ keyof PermissionSet, Permission ][]) {
|
|
||||||
resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPerms);
|
|
||||||
}
|
|
||||||
return resultSet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines the create and delete permissions for the given resource permissions
|
* Determines the create and delete permissions for the given resource permissions
|
||||||
* based on those of its parent container.
|
* based on those of its parent container.
|
||||||
*/
|
*/
|
||||||
private interpretContainerPermission(resourcePermission: Permission, containerPermission: Permission): Permission {
|
private interpretContainerPermission(resourcePermission: PermissionSet, containerPermission: PermissionSet):
|
||||||
|
PermissionSet {
|
||||||
const mergedPermission = { ...resourcePermission };
|
const mergedPermission = { ...resourcePermission };
|
||||||
|
|
||||||
// https://solidproject.org/TR/2021/wac-20210711:
|
// https://solidproject.org/TR/2021/wac-20210711:
|
||||||
|
@ -38,12 +38,12 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
|||||||
for (const [ identifier, modes ] of requestedModes.entrySets()) {
|
for (const [ identifier, modes ] of requestedModes.entrySets()) {
|
||||||
const modeString = [ ...modes ].join(',');
|
const modeString = [ ...modes ].join(',');
|
||||||
this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modeString} permissions for ${identifier.path}`);
|
this.logger.debug(`Checking if ${credentials.agent?.webId} has ${modeString} permissions for ${identifier.path}`);
|
||||||
const permissions = availablePermissions.get(identifier) ?? {};
|
const permissionSet = availablePermissions.get(identifier) ?? {};
|
||||||
for (const mode of modes) {
|
for (const mode of modes) {
|
||||||
try {
|
try {
|
||||||
this.requireModePermission(credentials, permissions, mode);
|
this.requireModePermission(credentials, permissionSet, mode);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
await this.reportAccessError(identifier, modes, permissions, error);
|
await this.reportAccessError(identifier, modes, permissionSet, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.logger.debug(`${JSON.stringify(credentials)} has ${modeString} permissions for ${identifier.path}`);
|
this.logger.debug(`${JSON.stringify(credentials)} has ${modeString} permissions for ${identifier.path}`);
|
||||||
@ -58,8 +58,8 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
|||||||
* Otherwise, deny access based on existing grounds.
|
* Otherwise, deny access based on existing grounds.
|
||||||
*/
|
*/
|
||||||
private async reportAccessError(identifier: ResourceIdentifier, modes: ReadonlySet<AccessMode>,
|
private async reportAccessError(identifier: ResourceIdentifier, modes: ReadonlySet<AccessMode>,
|
||||||
permissions: PermissionSet, cause: unknown): Promise<never> {
|
permissionSet: PermissionSet, cause: unknown): Promise<never> {
|
||||||
const exposeExistence = this.hasModePermission(permissions, AccessMode.read);
|
const exposeExistence = permissionSet[AccessMode.read];
|
||||||
if (exposeExistence && !modes.has(AccessMode.create) && !await this.resourceSet.hasResource(identifier)) {
|
if (exposeExistence && !modes.has(AccessMode.create) && !await this.resourceSet.hasResource(identifier)) {
|
||||||
throw new NotFoundHttpError();
|
throw new NotFoundHttpError();
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
|||||||
* @param mode - Which mode is requested.
|
* @param mode - Which mode is requested.
|
||||||
*/
|
*/
|
||||||
private requireModePermission(credentials: Credentials, permissionSet: PermissionSet, mode: AccessMode): void {
|
private requireModePermission(credentials: Credentials, permissionSet: PermissionSet, mode: AccessMode): void {
|
||||||
if (!this.hasModePermission(permissionSet, mode)) {
|
if (!permissionSet[mode]) {
|
||||||
if (this.isAuthenticated(credentials)) {
|
if (this.isAuthenticated(credentials)) {
|
||||||
this.logger.warn(`Agent ${credentials.agent!.webId} has no ${mode} permissions`);
|
this.logger.warn(`Agent ${credentials.agent!.webId} has no ${mode} permissions`);
|
||||||
throw new ForbiddenHttpError();
|
throw new ForbiddenHttpError();
|
||||||
@ -90,18 +90,6 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if one of the Permissions in the PermissionSet grants permission to use the given mode.
|
|
||||||
*/
|
|
||||||
private hasModePermission(permissionSet: PermissionSet, mode: AccessMode): boolean {
|
|
||||||
for (const permissions of Object.values(permissionSet)) {
|
|
||||||
if (permissions[mode]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
|
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
|
||||||
* @param credentials - Credentials to check.
|
* @param credentials - Credentials to check.
|
||||||
|
@ -2,7 +2,7 @@ import { UnionHandler } from '../util/handlers/UnionHandler';
|
|||||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||||
import { getDefault } from '../util/map/MapUtil';
|
import { getDefault } from '../util/map/MapUtil';
|
||||||
import type { PermissionReader } from './PermissionReader';
|
import type { PermissionReader } from './PermissionReader';
|
||||||
import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions';
|
import type { PermissionMap, PermissionSet } from './permissions/Permissions';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Combines the results of multiple PermissionReaders.
|
* Combines the results of multiple PermissionReaders.
|
||||||
@ -26,22 +26,16 @@ export class UnionPermissionReader extends UnionHandler<PermissionReader> {
|
|||||||
*/
|
*/
|
||||||
private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void {
|
private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void {
|
||||||
for (const [ identifier, permissionSet ] of permissionMap) {
|
for (const [ identifier, permissionSet ] of permissionMap) {
|
||||||
for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) {
|
const resultSet = getDefault(result, identifier, (): PermissionSet => ({}));
|
||||||
const resultSet = getDefault(result, identifier, (): PermissionSet => ({}));
|
result.set(identifier, this.mergePermissions(permissionSet, resultSet));
|
||||||
resultSet[credential] = this.mergePermissions(permission, resultSet[credential]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds the given permissions to the result object according to the combination rules of the class.
|
* Adds the given permissions to the result object according to the combination rules of the class.
|
||||||
*/
|
*/
|
||||||
private mergePermissions(permissions?: Permission, result: Permission = {}): Permission {
|
private mergePermissions(permissions: PermissionSet, result: PermissionSet): PermissionSet {
|
||||||
if (!permissions) {
|
for (const [ key, value ] of Object.entries(permissions) as [ keyof PermissionSet, boolean | undefined ][]) {
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const [ key, value ] of Object.entries(permissions) as [ keyof Permission, boolean | undefined ][]) {
|
|
||||||
if (typeof value !== 'undefined' && result[key] !== false) {
|
if (typeof value !== 'undefined' && result[key] !== false) {
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,13 @@ import { ACL, RDF } from '../util/Vocabularies';
|
|||||||
import type { AccessChecker } from './access/AccessChecker';
|
import type { AccessChecker } from './access/AccessChecker';
|
||||||
import type { PermissionReaderInput } from './PermissionReader';
|
import type { PermissionReaderInput } from './PermissionReader';
|
||||||
import { PermissionReader } from './PermissionReader';
|
import { PermissionReader } from './PermissionReader';
|
||||||
import type { AclPermission } from './permissions/AclPermission';
|
import type { AclPermissionSet } from './permissions/AclPermissionSet';
|
||||||
import { AclMode } from './permissions/AclPermission';
|
import { AclMode } from './permissions/AclPermissionSet';
|
||||||
import type { PermissionMap } from './permissions/Permissions';
|
import type { PermissionMap } from './permissions/Permissions';
|
||||||
import { AccessMode } from './permissions/Permissions';
|
import { AccessMode } from './permissions/Permissions';
|
||||||
|
|
||||||
// Maps WebACL-specific modes to generic access modes.
|
// Maps WebACL-specific modes to generic access modes.
|
||||||
const modesMap: Record<string, Readonly<(keyof AclPermission)[]>> = {
|
const modesMap: Record<string, Readonly<(keyof AclPermissionSet)[]>> = {
|
||||||
[ACL.Read]: [ AccessMode.read ],
|
[ACL.Read]: [ AccessMode.read ],
|
||||||
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
|
[ACL.Write]: [ AccessMode.append, AccessMode.write ],
|
||||||
[ACL.Append]: [ AccessMode.append ],
|
[ACL.Append]: [ AccessMode.append ],
|
||||||
@ -81,14 +81,9 @@ export class WebAclReader extends PermissionReader {
|
|||||||
Promise<PermissionMap> {
|
Promise<PermissionMap> {
|
||||||
const result: PermissionMap = new IdentifierMap();
|
const result: PermissionMap = new IdentifierMap();
|
||||||
for (const [ store, aclIdentifiers ] of aclMap) {
|
for (const [ store, aclIdentifiers ] of aclMap) {
|
||||||
// WebACL requires knowledge of both the public and agent-specific permissions for the WAC-Allow header.
|
const permissionSet = await this.determinePermissions(store, credentials);
|
||||||
const publicPermissions = await this.determinePermissions(store, {});
|
|
||||||
const agentPermissions = credentials.agent ? await this.determinePermissions(store, credentials) : {};
|
|
||||||
for (const identifier of aclIdentifiers) {
|
for (const identifier of aclIdentifiers) {
|
||||||
result.set(identifier, {
|
result.set(identifier, permissionSet);
|
||||||
public: publicPermissions,
|
|
||||||
agent: agentPermissions,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,8 +95,8 @@ export class WebAclReader extends PermissionReader {
|
|||||||
* @param acl - Store containing all relevant authorization triples.
|
* @param acl - Store containing all relevant authorization triples.
|
||||||
* @param credentials - Credentials to find the permissions for.
|
* @param credentials - Credentials to find the permissions for.
|
||||||
*/
|
*/
|
||||||
private async determinePermissions(acl: Store, credentials: Credentials): Promise<AclPermission> {
|
private async determinePermissions(acl: Store, credentials: Credentials): Promise<AclPermissionSet> {
|
||||||
const aclPermissions: AclPermission = {};
|
const aclPermissions: AclPermissionSet = {};
|
||||||
|
|
||||||
// Apply all ACL rules
|
// Apply all ACL rules
|
||||||
const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null);
|
const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import type { Permission } from './Permissions';
|
import type { PermissionSet } from './Permissions';
|
||||||
|
|
||||||
export enum AclMode {
|
export enum AclMode {
|
||||||
control = 'control',
|
control = 'control',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a control field to the permissions to specify this WAC-specific value
|
// Adds a control field to the permissions to specify this WAC-specific value
|
||||||
export type AclPermission = Permission & {
|
export type AclPermissionSet = PermissionSet & {
|
||||||
[mode in AclMode]?: boolean;
|
[mode in AclMode]?: boolean;
|
||||||
};
|
};
|
@ -19,21 +19,7 @@ export type AccessMap = IdentifierSetMultiMap<AccessMode>;
|
|||||||
/**
|
/**
|
||||||
* A data interface indicating which permissions are required (based on the context).
|
* A data interface indicating which permissions are required (based on the context).
|
||||||
*/
|
*/
|
||||||
export type Permission = Partial<Record<AccessMode, boolean>>;
|
export type PermissionSet = Partial<Record<AccessMode, boolean>>;
|
||||||
|
|
||||||
/**
|
|
||||||
* The keys that can be used in a {@link PermissionSet};
|
|
||||||
*/
|
|
||||||
export const permissionSetKeys = [ 'public', 'agent' ] as const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Contains the public permissions and those specific for the agent.
|
|
||||||
* There is no good reason to subdivide permissions per type of credentials
|
|
||||||
* since credentials are a combination of multiple factors.
|
|
||||||
* The only reason is the WAC-Allow header which requires this subdivision,
|
|
||||||
* which is why we make that same division here.
|
|
||||||
*/
|
|
||||||
export type PermissionSet = Partial<Record<typeof permissionSetKeys[number], Permission>>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PermissionSet per identifier.
|
* PermissionSet per identifier.
|
||||||
|
@ -15,7 +15,7 @@ export * from './authorization/access/AgentClassAccessChecker';
|
|||||||
export * from './authorization/access/AgentGroupAccessChecker';
|
export * from './authorization/access/AgentGroupAccessChecker';
|
||||||
|
|
||||||
// Authorization/Permissions
|
// Authorization/Permissions
|
||||||
export * from './authorization/permissions/AclPermission';
|
export * from './authorization/permissions/AclPermissionSet';
|
||||||
export * from './authorization/permissions/CreateModesExtractor';
|
export * from './authorization/permissions/CreateModesExtractor';
|
||||||
export * from './authorization/permissions/DeleteParentExtractor';
|
export * from './authorization/permissions/DeleteParentExtractor';
|
||||||
export * from './authorization/permissions/IntermediateCreateExtractor';
|
export * from './authorization/permissions/IntermediateCreateExtractor';
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import type { Credentials } from '../authentication/Credentials';
|
import type { Credentials } from '../authentication/Credentials';
|
||||||
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||||
import type { PermissionReader } from '../authorization/PermissionReader';
|
import type { PermissionReader } from '../authorization/PermissionReader';
|
||||||
import { AclMode } from '../authorization/permissions/AclPermission';
|
import type { AclPermissionSet } from '../authorization/permissions/AclPermissionSet';
|
||||||
import type { AclPermission } from '../authorization/permissions/AclPermission';
|
import { AclMode } from '../authorization/permissions/AclPermissionSet';
|
||||||
import type { ModesExtractor } from '../authorization/permissions/ModesExtractor';
|
import type { ModesExtractor } from '../authorization/permissions/ModesExtractor';
|
||||||
import type { PermissionSet } from '../authorization/permissions/Permissions';
|
|
||||||
import { AccessMode } from '../authorization/permissions/Permissions';
|
import { AccessMode } from '../authorization/permissions/Permissions';
|
||||||
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
|
import type { ResponseDescription } from '../http/output/response/ResponseDescription';
|
||||||
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
|
import type { RepresentationMetadata } from '../http/representation/RepresentationMetadata';
|
||||||
@ -63,19 +62,32 @@ export class WacAllowHttpHandler extends OperationHttpHandler {
|
|||||||
|
|
||||||
const permissionSet = availablePermissions.get(operation.target);
|
const permissionSet = availablePermissions.get(operation.target);
|
||||||
if (permissionSet) {
|
if (permissionSet) {
|
||||||
this.logger.debug('Adding WAC-Allow metadata.');
|
const user: AclPermissionSet = permissionSet;
|
||||||
this.addWacAllowMetadata(metadata, permissionSet);
|
let everyone: AclPermissionSet;
|
||||||
|
if (!credentials.agent?.webId) {
|
||||||
|
// User is not authenticated so public permissions are the same as agent permissions
|
||||||
|
this.logger.debug('User is not authenticated so has public permissions');
|
||||||
|
everyone = user;
|
||||||
|
} else {
|
||||||
|
// Need to determine public permissions
|
||||||
|
this.logger.debug('Determining public permissions');
|
||||||
|
const permissionMap = await this.permissionReader.handleSafe({ credentials: {}, requestedModes });
|
||||||
|
everyone = permissionMap.get(operation.target) ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug('Adding WAC-Allow metadata');
|
||||||
|
this.addWacAllowMetadata(metadata, everyone, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private addWacAllowMetadata(metadata: RepresentationMetadata, permissionSet: PermissionSet): void {
|
/**
|
||||||
const user: AclPermission = permissionSet.agent ?? {};
|
* Converts the found permissions to triples and puts them in the metadata.
|
||||||
const everyone: AclPermission = permissionSet.public ?? {};
|
*/
|
||||||
|
private addWacAllowMetadata(metadata: RepresentationMetadata, everyone: AclPermissionSet, user: AclPermissionSet):
|
||||||
|
void {
|
||||||
const modes = new Set<AccessMode>([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]);
|
const modes = new Set<AccessMode>([ ...Object.keys(user), ...Object.keys(everyone) ] as AccessMode[]);
|
||||||
|
|
||||||
for (const mode of modes) {
|
for (const mode of modes) {
|
||||||
if (VALID_ACL_MODES.has(mode)) {
|
if (VALID_ACL_MODES.has(mode)) {
|
||||||
const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control';
|
const capitalizedMode = mode.charAt(0).toUpperCase() + mode.slice(1) as 'Read' | 'Write' | 'Append' | 'Control';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import 'jest-rdf';
|
import 'jest-rdf';
|
||||||
import { fetch } from 'cross-fetch';
|
import { fetch } from 'cross-fetch';
|
||||||
import { Parser } from 'n3';
|
import { Parser } from 'n3';
|
||||||
import type { AclPermission } from '../../src/authorization/permissions/AclPermission';
|
import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet';
|
||||||
import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
import type { ResourceStore } from '../../src/storage/ResourceStore';
|
||||||
@ -59,7 +59,7 @@ async function expectPatch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Creates/updates a resource with the given data and permissions
|
// Creates/updates a resource with the given data and permissions
|
||||||
async function setResource(path: string, turtle: string, permissions: AclPermission): Promise<void> {
|
async function setResource(path: string, turtle: string, permissions: AclPermissionSet): Promise<void> {
|
||||||
const url = joinUrl(baseUrl, path);
|
const url = joinUrl(baseUrl, path);
|
||||||
await store.setRepresentation({ path: url }, new BasicRepresentation(turtle, 'text/turtle'));
|
await store.setRepresentation({ path: url }, new BasicRepresentation(turtle, 'text/turtle'));
|
||||||
await aclHelper.setSimpleAcl(url, { permissions, agentClass: 'agent', accessTo: true });
|
await aclHelper.setSimpleAcl(url, { permissions, agentClass: 'agent', accessTo: true });
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import fetch from 'cross-fetch';
|
import fetch from 'cross-fetch';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
import type { AclPermission } from '../../src/authorization/permissions/AclPermission';
|
import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet';
|
||||||
import { AccessMode as AM } from '../../src/authorization/permissions/Permissions';
|
import { AccessMode as AM } from '../../src/authorization/permissions/Permissions';
|
||||||
import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation';
|
import { BasicRepresentation } from '../../src/http/representation/BasicRepresentation';
|
||||||
import type { App } from '../../src/init/App';
|
import type { App } from '../../src/init/App';
|
||||||
@ -120,12 +120,12 @@ const table: [string, string, AM[], AM[] | undefined, string, string, number, nu
|
|||||||
];
|
];
|
||||||
/* eslint-enable no-multi-spaces */
|
/* eslint-enable no-multi-spaces */
|
||||||
|
|
||||||
function toPermission(modes: AM[]): AclPermission {
|
function toPermission(modes: AM[]): AclPermissionSet {
|
||||||
return Object.fromEntries(modes.map((mode): [AM, boolean] => [ mode, true ]));
|
return Object.fromEntries(modes.map((mode): [AM, boolean] => [ mode, true ]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setWebAclPermissions(store: ResourceStore, target: string, permissions: AclPermission,
|
async function setWebAclPermissions(store: ResourceStore, target: string, permissions: AclPermissionSet,
|
||||||
childPermissions: AclPermission): Promise<void> {
|
childPermissions: AclPermissionSet): Promise<void> {
|
||||||
const aclHelper = new AclHelper(store);
|
const aclHelper = new AclHelper(store);
|
||||||
await aclHelper.setSimpleAcl(target, [
|
await aclHelper.setSimpleAcl(target, [
|
||||||
{ permissions, agentClass: 'agent', accessTo: true },
|
{ permissions, agentClass: 'agent', accessTo: true },
|
||||||
@ -133,8 +133,8 @@ async function setWebAclPermissions(store: ResourceStore, target: string, permis
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setAcpPermissions(store: ResourceStore, target: string, permissions: AclPermission,
|
async function setAcpPermissions(store: ResourceStore, target: string, permissions: AclPermissionSet,
|
||||||
childPermissions: AclPermission): Promise<void> {
|
childPermissions: AclPermissionSet): Promise<void> {
|
||||||
const acpHelper = new AcpHelper(store);
|
const acpHelper = new AcpHelper(store);
|
||||||
const publicMatcher = acpHelper.createMatcher({ publicAgent: true });
|
const publicMatcher = acpHelper.createMatcher({ publicAgent: true });
|
||||||
const policies = [ acpHelper.createPolicy({
|
const policies = [ acpHelper.createPolicy({
|
||||||
@ -157,7 +157,7 @@ const port = getPort('PermissionTable');
|
|||||||
const baseUrl = `http://localhost:${port}/`;
|
const baseUrl = `http://localhost:${port}/`;
|
||||||
|
|
||||||
type AuthFunctionType = (store: ResourceStore, target: string,
|
type AuthFunctionType = (store: ResourceStore, target: string,
|
||||||
permissions: AclPermission, childPermissions: AclPermission) => Promise<void>;
|
permissions: AclPermissionSet, childPermissions: AclPermissionSet) => Promise<void>;
|
||||||
|
|
||||||
const rootFilePath = getTestFolder('permissionTable');
|
const rootFilePath = getTestFolder('permissionTable');
|
||||||
const stores: [string, string, { configs: string[]; authFunction: AuthFunctionType; teardown: () => Promise<void> }][] =
|
const stores: [string, string, { configs: string[]; authFunction: AuthFunctionType; teardown: () => Promise<void> }][] =
|
||||||
|
@ -74,8 +74,8 @@ describe('An AcpReader', (): void => {
|
|||||||
[{ path: baseUrl }, AccessMode.read ],
|
[{ path: baseUrl }, AccessMode.read ],
|
||||||
[ target, AccessMode.read ]]);
|
[ target, AccessMode.read ]]);
|
||||||
const expectedPermissions = new IdentifierMap([
|
const expectedPermissions = new IdentifierMap([
|
||||||
[{ path: baseUrl }, { agent: { read: true }}],
|
[{ path: baseUrl }, { read: true }],
|
||||||
[ target, { agent: {}}]]);
|
[ target, {}]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,8 +100,8 @@ describe('An AcpReader', (): void => {
|
|||||||
[{ path: baseUrl }, AccessMode.read ],
|
[{ path: baseUrl }, AccessMode.read ],
|
||||||
[ target, AccessMode.read ]]);
|
[ target, AccessMode.read ]]);
|
||||||
const expectedPermissions = new IdentifierMap([
|
const expectedPermissions = new IdentifierMap([
|
||||||
[{ path: baseUrl }, { agent: {}}],
|
[{ path: baseUrl }, {}],
|
||||||
[ target, { agent: { read: true }}]]);
|
[ target, { read: true }]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -133,8 +133,8 @@ describe('An AcpReader', (): void => {
|
|||||||
[{ path: baseUrl }, AccessMode.read ],
|
[{ path: baseUrl }, AccessMode.read ],
|
||||||
[ target, AccessMode.read ]]);
|
[ target, AccessMode.read ]]);
|
||||||
const expectedPermissions = new IdentifierMap([
|
const expectedPermissions = new IdentifierMap([
|
||||||
[{ path: baseUrl }, { agent: { control: true }}],
|
[{ path: baseUrl }, { control: true }],
|
||||||
[ target, { agent: { read: true, append: true }}]]);
|
[ target, { read: true, append: true }]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -155,9 +155,9 @@ describe('An AcpReader', (): void => {
|
|||||||
[ target1, AccessMode.read ],
|
[ target1, AccessMode.read ],
|
||||||
[ target2, AccessMode.read ]]);
|
[ target2, AccessMode.read ]]);
|
||||||
const expectedPermissions = new IdentifierMap([
|
const expectedPermissions = new IdentifierMap([
|
||||||
[{ path: baseUrl }, { agent: {}}],
|
[{ path: baseUrl }, {}],
|
||||||
[ target1, { agent: { read: true }}],
|
[ target1, { read: true }],
|
||||||
[ target2, { agent: { read: true }}]]);
|
[ target2, { read: true }]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
expect(acrStore.getRepresentation).toHaveBeenCalledTimes(3);
|
expect(acrStore.getRepresentation).toHaveBeenCalledTimes(3);
|
||||||
expect(acrStore.getRepresentation)
|
expect(acrStore.getRepresentation)
|
||||||
@ -182,7 +182,7 @@ describe('An AcpReader', (): void => {
|
|||||||
acp:issuer <http://example.com/idp>.
|
acp:issuer <http://example.com/idp>.
|
||||||
`, baseUrl);
|
`, baseUrl);
|
||||||
const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]);
|
const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]);
|
||||||
let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: {}}]]);
|
let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, {}]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
|
|
||||||
credentials = {
|
credentials = {
|
||||||
@ -190,7 +190,7 @@ describe('An AcpReader', (): void => {
|
|||||||
client: { clientId: 'http://client.example.com/#me' },
|
client: { clientId: 'http://client.example.com/#me' },
|
||||||
issuer: { url: 'http://example.com/idp' },
|
issuer: { url: 'http://example.com/idp' },
|
||||||
};
|
};
|
||||||
expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: { read: true }}]]);
|
expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { read: true }]]);
|
||||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { AllStaticReader } from '../../../src/authorization/AllStaticReader';
|
import { AllStaticReader } from '../../../src/authorization/AllStaticReader';
|
||||||
import type { Permission } from '../../../src/authorization/permissions/Permissions';
|
import type { PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||||
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
||||||
import { compareMaps } from '../../util/Util';
|
import { compareMaps } from '../../util/Util';
|
||||||
|
|
||||||
function getPermissions(allow: boolean): Permission {
|
function getPermissions(allow: boolean): PermissionSet {
|
||||||
return {
|
return {
|
||||||
read: allow,
|
read: allow,
|
||||||
write: allow,
|
write: allow,
|
||||||
@ -27,13 +27,11 @@ describe('An AllStaticReader', (): void => {
|
|||||||
let authorizer = new AllStaticReader(true);
|
let authorizer = new AllStaticReader(true);
|
||||||
const requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.read ]]);
|
const requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.read ]]);
|
||||||
let result = await authorizer.handle({ credentials, requestedModes });
|
let result = await authorizer.handle({ credentials, requestedModes });
|
||||||
compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(true),
|
compareMaps(result, new IdentifierMap([[ identifier, getPermissions(true) ]]));
|
||||||
agent: getPermissions(true) }]]));
|
|
||||||
|
|
||||||
authorizer = new AllStaticReader(false);
|
authorizer = new AllStaticReader(false);
|
||||||
|
|
||||||
result = await authorizer.handle({ credentials, requestedModes });
|
result = await authorizer.handle({ credentials, requestedModes });
|
||||||
compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(false),
|
compareMaps(result, new IdentifierMap([[ identifier, getPermissions(false) ]]));
|
||||||
agent: getPermissions(false) }]]));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||||
import { AuthAuxiliaryReader } from '../../../src/authorization/AuthAuxiliaryReader';
|
import { AuthAuxiliaryReader } from '../../../src/authorization/AuthAuxiliaryReader';
|
||||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
import { AclMode } from '../../../src/authorization/permissions/AclPermissionSet';
|
||||||
import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||||
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
|
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
|
||||||
@ -40,10 +40,10 @@ describe('An AuthAuxiliaryReader', (): void => {
|
|||||||
it('requires control permissions on the subject resource to do everything.', async(): Promise<void> => {
|
it('requires control permissions on the subject resource to do everything.', async(): Promise<void> => {
|
||||||
requestedModes.set(acl1, AccessMode.read);
|
requestedModes.set(acl1, AccessMode.read);
|
||||||
requestedModes.set(acl2, AccessMode.read);
|
requestedModes.set(acl2, AccessMode.read);
|
||||||
sourceResult.set(subject1, { public: { control: true }} as PermissionSet);
|
sourceResult.set(subject1, { control: true } as PermissionSet);
|
||||||
|
|
||||||
const result = await reader.handle({ requestedModes, credentials });
|
const result = await reader.handle({ requestedModes, credentials });
|
||||||
expect(result.get(acl1)).toEqual({ public: { read: true, append: true, write: true, control: true }});
|
expect(result.get(acl1)).toEqual({ read: true, append: true, write: true, control: true });
|
||||||
expect(result.get(acl2)).toEqual({ });
|
expect(result.get(acl2)).toEqual({ });
|
||||||
|
|
||||||
const updatedMap = new IdentifierMap();
|
const updatedMap = new IdentifierMap();
|
||||||
@ -59,10 +59,10 @@ describe('An AuthAuxiliaryReader', (): void => {
|
|||||||
requestedModes.set(acl1, AccessMode.read);
|
requestedModes.set(acl1, AccessMode.read);
|
||||||
requestedModes.set(subject1, AccessMode.write);
|
requestedModes.set(subject1, AccessMode.write);
|
||||||
|
|
||||||
const resultSet = { public: { read: true, write: true, control: true }} as PermissionSet;
|
const resultSet = { read: true, write: true, control: true } as PermissionSet;
|
||||||
sourceResult.set(subject1, resultSet);
|
sourceResult.set(subject1, resultSet);
|
||||||
const resultMap: PermissionMap = new IdentifierMap([
|
const resultMap: PermissionMap = new IdentifierMap([
|
||||||
[ acl1, { public: { read: true, write: true, control: true, append: true }} as PermissionSet ],
|
[ acl1, { read: true, write: true, control: true, append: true } as PermissionSet ],
|
||||||
[ subject1, resultSet ],
|
[ subject1, resultSet ],
|
||||||
]);
|
]);
|
||||||
compareMaps(await reader.handle({ credentials, requestedModes }), resultMap);
|
compareMaps(await reader.handle({ credentials, requestedModes }), resultMap);
|
||||||
|
@ -15,7 +15,7 @@ describe('An AuxiliaryReader', (): void => {
|
|||||||
const subjectIdentifier = { path: 'http://test.com/foo' };
|
const subjectIdentifier = { path: 'http://test.com/foo' };
|
||||||
const auxiliaryIdentifier1 = { path: 'http://test.com/foo.dummy1' };
|
const auxiliaryIdentifier1 = { path: 'http://test.com/foo.dummy1' };
|
||||||
const auxiliaryIdentifier2 = { path: 'http://test.com/foo.dummy2' };
|
const auxiliaryIdentifier2 = { path: 'http://test.com/foo.dummy2' };
|
||||||
const permissionSet: PermissionSet = { agent: { read: true }};
|
const permissionSet: PermissionSet = { read: true };
|
||||||
let source: jest.Mocked<PermissionReader>;
|
let source: jest.Mocked<PermissionReader>;
|
||||||
let strategy: jest.Mocked<AuxiliaryStrategy>;
|
let strategy: jest.Mocked<AuxiliaryStrategy>;
|
||||||
let reader: AuxiliaryReader;
|
let reader: AuxiliaryReader;
|
||||||
@ -76,7 +76,7 @@ describe('An AuxiliaryReader', (): void => {
|
|||||||
[ auxiliaryIdentifier2, AccessMode.read ],
|
[ auxiliaryIdentifier2, AccessMode.read ],
|
||||||
[ subjectIdentifier, AccessMode.delete ],
|
[ subjectIdentifier, AccessMode.delete ],
|
||||||
]);
|
]);
|
||||||
const resultSet = { agent: { read: true, write: true, delete: true }};
|
const resultSet = { read: true, write: true, delete: true };
|
||||||
source.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ subjectIdentifier, resultSet ]]));
|
source.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ subjectIdentifier, resultSet ]]));
|
||||||
const permissionMap: PermissionMap = new IdentifierMap([
|
const permissionMap: PermissionMap = new IdentifierMap([
|
||||||
[ subjectIdentifier, resultSet ],
|
[ subjectIdentifier, resultSet ],
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||||
import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader';
|
import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader';
|
||||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
import { AclMode } from '../../../src/authorization/permissions/AclPermissionSet';
|
||||||
import type { AccessMap } from '../../../src/authorization/permissions/Permissions';
|
import type { AccessMap } from '../../../src/authorization/permissions/Permissions';
|
||||||
import type { AuxiliaryIdentifierStrategy } from '../../../src/http/auxiliary/AuxiliaryIdentifierStrategy';
|
import type { AuxiliaryIdentifierStrategy } from '../../../src/http/auxiliary/AuxiliaryIdentifierStrategy';
|
||||||
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
|
import type { ResourceIdentifier } from '../../../src/http/representation/ResourceIdentifier';
|
||||||
@ -81,14 +81,14 @@ describe('An OwnerPermissionReader', (): void => {
|
|||||||
it('returns full permissions if the owner is accessing an ACL resource in their pod.', async(): Promise<void> => {
|
it('returns full permissions if the owner is accessing an ACL resource in their pod.', async(): Promise<void> => {
|
||||||
compareMaps(await reader.handle({ credentials, requestedModes }), new IdentifierMap([[
|
compareMaps(await reader.handle({ credentials, requestedModes }), new IdentifierMap([[
|
||||||
identifier,
|
identifier,
|
||||||
{ agent: {
|
{
|
||||||
read: true,
|
read: true,
|
||||||
write: true,
|
write: true,
|
||||||
append: true,
|
append: true,
|
||||||
create: true,
|
create: true,
|
||||||
delete: true,
|
delete: true,
|
||||||
control: true,
|
control: true,
|
||||||
}},
|
},
|
||||||
]]));
|
]]));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -26,7 +26,7 @@ describe('A ParentContainerReader', (): void => {
|
|||||||
beforeEach(async(): Promise<void> => {
|
beforeEach(async(): Promise<void> => {
|
||||||
requestedModes = new IdentifierSetMultiMap();
|
requestedModes = new IdentifierSetMultiMap();
|
||||||
|
|
||||||
sourceResult = new IdentifierMap([[{ path: joinUrl(baseUrl, 'test') }, { public: { read: true }}]]);
|
sourceResult = new IdentifierMap([[{ path: joinUrl(baseUrl, 'test') }, { read: true }]]);
|
||||||
|
|
||||||
source = { handleSafe: jest.fn().mockResolvedValue(sourceResult) } as any;
|
source = { handleSafe: jest.fn().mockResolvedValue(sourceResult) } as any;
|
||||||
reader = new ParentContainerReader(source, identifierStrategy);
|
reader = new ParentContainerReader(source, identifierStrategy);
|
||||||
@ -35,10 +35,10 @@ describe('A ParentContainerReader', (): void => {
|
|||||||
it('requires parent append permissions to create resources.', async(): Promise<void> => {
|
it('requires parent append permissions to create resources.', async(): Promise<void> => {
|
||||||
requestedModes.set(target1, new Set([ AccessMode.create ]));
|
requestedModes.set(target1, new Set([ AccessMode.create ]));
|
||||||
requestedModes.set(target2, new Set([ AccessMode.create ]));
|
requestedModes.set(target2, new Set([ AccessMode.create ]));
|
||||||
sourceResult.set(parent1, { public: { append: true }});
|
sourceResult.set(parent1, { append: true });
|
||||||
|
|
||||||
const result = await reader.handle({ requestedModes, credentials });
|
const result = await reader.handle({ requestedModes, credentials });
|
||||||
expect(result.get(target1)).toEqual({ public: { create: true }});
|
expect(result.get(target1)).toEqual({ create: true });
|
||||||
expect(result.get(target2)).toEqual({ });
|
expect(result.get(target2)).toEqual({ });
|
||||||
|
|
||||||
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
||||||
@ -54,15 +54,15 @@ describe('A ParentContainerReader', (): void => {
|
|||||||
requestedModes.set(target1, new Set([ AccessMode.delete ]));
|
requestedModes.set(target1, new Set([ AccessMode.delete ]));
|
||||||
requestedModes.set(target2, new Set([ AccessMode.delete ]));
|
requestedModes.set(target2, new Set([ AccessMode.delete ]));
|
||||||
requestedModes.set(target3, new Set([ AccessMode.delete ]));
|
requestedModes.set(target3, new Set([ AccessMode.delete ]));
|
||||||
sourceResult.set(parent1, { public: { write: true }});
|
sourceResult.set(parent1, { write: true });
|
||||||
sourceResult.set(parent2, { public: { write: true }});
|
sourceResult.set(parent2, { write: true });
|
||||||
sourceResult.set(target1, { public: { write: true }});
|
sourceResult.set(target1, { write: true });
|
||||||
sourceResult.set(target3, { public: { write: true }});
|
sourceResult.set(target3, { write: true });
|
||||||
|
|
||||||
const result = await reader.handle({ requestedModes, credentials });
|
const result = await reader.handle({ requestedModes, credentials });
|
||||||
expect(result.get(target1)).toEqual({ public: { delete: true, write: true }});
|
expect(result.get(target1)).toEqual({ delete: true, write: true });
|
||||||
expect(result.get(target2)).toEqual({ public: {}});
|
expect(result.get(target2)).toEqual({ });
|
||||||
expect(result.get(target3)).toEqual({ public: { write: true }});
|
expect(result.get(target3)).toEqual({ write: true });
|
||||||
|
|
||||||
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
||||||
updatedMap.set(parent1, AccessMode.write);
|
updatedMap.set(parent1, AccessMode.write);
|
||||||
@ -76,14 +76,14 @@ describe('A ParentContainerReader', (): void => {
|
|||||||
it('does not allow create/delete if the source explicitly forbids it.', async(): Promise<void> => {
|
it('does not allow create/delete if the source explicitly forbids it.', async(): Promise<void> => {
|
||||||
requestedModes.set(target1, new Set([ AccessMode.create, AccessMode.delete ]));
|
requestedModes.set(target1, new Set([ AccessMode.create, AccessMode.delete ]));
|
||||||
requestedModes.set(target2, new Set([ AccessMode.create, AccessMode.delete ]));
|
requestedModes.set(target2, new Set([ AccessMode.create, AccessMode.delete ]));
|
||||||
sourceResult.set(parent1, { public: { write: true, append: true }});
|
sourceResult.set(parent1, { write: true, append: true });
|
||||||
sourceResult.set(parent2, { public: { write: true, append: true }});
|
sourceResult.set(parent2, { write: true, append: true });
|
||||||
sourceResult.set(target1, { public: { write: true }});
|
sourceResult.set(target1, { write: true });
|
||||||
sourceResult.set(target2, { public: { write: true, create: false, delete: false }});
|
sourceResult.set(target2, { write: true, create: false, delete: false });
|
||||||
|
|
||||||
const result = await reader.handle({ requestedModes, credentials });
|
const result = await reader.handle({ requestedModes, credentials });
|
||||||
expect(result.get(target1)).toEqual({ public: { write: true, create: true, delete: true }});
|
expect(result.get(target1)).toEqual({ write: true, create: true, delete: true });
|
||||||
expect(result.get(target2)).toEqual({ public: { write: true, create: false, delete: false }});
|
expect(result.get(target2)).toEqual({ write: true, create: false, delete: false });
|
||||||
|
|
||||||
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
||||||
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
|
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
|
||||||
@ -96,12 +96,12 @@ describe('A ParentContainerReader', (): void => {
|
|||||||
it('combines the modes with the parent resource if it is also being requested.', async(): Promise<void> => {
|
it('combines the modes with the parent resource if it is also being requested.', async(): Promise<void> => {
|
||||||
requestedModes.set(target1, AccessMode.create);
|
requestedModes.set(target1, AccessMode.create);
|
||||||
requestedModes.set(parent1, AccessMode.write);
|
requestedModes.set(parent1, AccessMode.write);
|
||||||
sourceResult.set(parent1, { public: { write: true, append: true }});
|
sourceResult.set(parent1, { write: true, append: true });
|
||||||
sourceResult.set(target1, { public: { write: true }});
|
sourceResult.set(target1, { write: true });
|
||||||
|
|
||||||
const result = await reader.handle({ requestedModes, credentials });
|
const result = await reader.handle({ requestedModes, credentials });
|
||||||
expect(result.get(target1)).toEqual({ public: { write: true, create: true, delete: true }});
|
expect(result.get(target1)).toEqual({ write: true, create: true, delete: true });
|
||||||
expect(result.get(parent1)).toEqual({ public: { write: true, append: true }});
|
expect(result.get(parent1)).toEqual({ write: true, append: true });
|
||||||
|
|
||||||
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
const updatedMap = new IdentifierSetMultiMap(requestedModes);
|
||||||
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
|
updatedMap.set(parent1, new Set([ AccessMode.write, AccessMode.append ]));
|
||||||
|
@ -10,7 +10,7 @@ import { compareMaps } from '../../util/Util';
|
|||||||
|
|
||||||
describe('A PathBasedReader', (): void => {
|
describe('A PathBasedReader', (): void => {
|
||||||
const baseUrl = 'http://test.com/foo/';
|
const baseUrl = 'http://test.com/foo/';
|
||||||
const permissionSet: PermissionSet = { agent: { read: true }};
|
const permissionSet: PermissionSet = { read: true };
|
||||||
let readers: jest.Mocked<PermissionReader>[];
|
let readers: jest.Mocked<PermissionReader>[];
|
||||||
let reader: PathBasedReader;
|
let reader: PathBasedReader;
|
||||||
|
|
||||||
|
@ -34,10 +34,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
||||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||||
);
|
);
|
||||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: true }]]);
|
||||||
public: { read: true, write: false },
|
|
||||||
agent: { write: true },
|
|
||||||
}]]);
|
|
||||||
await expect(authorizer.handle(input)).resolves.toBeUndefined();
|
await expect(authorizer.handle(input)).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -45,9 +42,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
||||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||||
);
|
);
|
||||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: false }]]);
|
||||||
public: { read: true, write: false },
|
|
||||||
}]]);
|
|
||||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -56,9 +51,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
input.requestedModes = new IdentifierSetMultiMap<AccessMode>(
|
||||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||||
);
|
);
|
||||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
input.availablePermissions = new IdentifierMap([[ identifier, { read: true, write: false }]]);
|
||||||
public: { read: true, write: false },
|
|
||||||
}]]);
|
|
||||||
await expect(authorizer.handle(input)).rejects.toThrow(ForbiddenHttpError);
|
await expect(authorizer.handle(input)).rejects.toThrow(ForbiddenHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -69,9 +62,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
it('throws a 404 in case the target resource does not exist and would not be written to.', async(): Promise<void> => {
|
it('throws a 404 in case the target resource does not exist and would not be written to.', async(): Promise<void> => {
|
||||||
resourceSet.hasResource.mockResolvedValueOnce(false);
|
resourceSet.hasResource.mockResolvedValueOnce(false);
|
||||||
input.requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.delete ]]);
|
input.requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.delete ]]);
|
||||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
input.availablePermissions = new IdentifierMap([[ identifier, { read: true }]]);
|
||||||
public: { read: true },
|
|
||||||
}]]);
|
|
||||||
await expect(authorizer.handle(input)).rejects.toThrow(NotFoundHttpError);
|
await expect(authorizer.handle(input)).rejects.toThrow(NotFoundHttpError);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,14 +75,8 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
[ identifier2, AccessMode.write ],
|
[ identifier2, AccessMode.write ],
|
||||||
]);
|
]);
|
||||||
input.availablePermissions = new IdentifierMap([
|
input.availablePermissions = new IdentifierMap([
|
||||||
[ identifier, {
|
[ identifier, { read: true, write: true }],
|
||||||
public: { read: true, write: false },
|
[ identifier2, { read: false, write: true }],
|
||||||
agent: { write: true },
|
|
||||||
}],
|
|
||||||
[ identifier2, {
|
|
||||||
public: { read: false },
|
|
||||||
agent: { write: true },
|
|
||||||
}],
|
|
||||||
]);
|
]);
|
||||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||||
});
|
});
|
||||||
@ -105,10 +90,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
|||||||
[ identifier2, AccessMode.write ],
|
[ identifier2, AccessMode.write ],
|
||||||
]);
|
]);
|
||||||
input.availablePermissions = new IdentifierMap([
|
input.availablePermissions = new IdentifierMap([
|
||||||
[ identifier, {
|
[ identifier, { read: true, write: true }],
|
||||||
public: { read: true, write: false },
|
|
||||||
agent: { write: true },
|
|
||||||
}],
|
|
||||||
]);
|
]);
|
||||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||||
});
|
});
|
||||||
|
@ -30,45 +30,42 @@ describe('A UnionPermissionReader', (): void => {
|
|||||||
it('only uses the results of readers that can handle the input.', async(): Promise<void> => {
|
it('only uses the results of readers that can handle the input.', async(): Promise<void> => {
|
||||||
readers[0].canHandle.mockRejectedValue(new Error('bad request'));
|
readers[0].canHandle.mockRejectedValue(new Error('bad request'));
|
||||||
readers[0].handle.mockResolvedValue(
|
readers[0].handle.mockResolvedValue(
|
||||||
new IdentifierMap([[ identifier, { agent: { read: true }}]]),
|
new IdentifierMap([[ identifier, { read: true }]]),
|
||||||
);
|
);
|
||||||
readers[1].handle.mockResolvedValue(
|
readers[1].handle.mockResolvedValue(
|
||||||
new IdentifierMap([[ identifier, { agent: { write: true }}]]),
|
new IdentifierMap([[ identifier, { write: true }]]),
|
||||||
);
|
);
|
||||||
compareMaps(await unionReader.handle(input),
|
compareMaps(await unionReader.handle(input),
|
||||||
new IdentifierMap([[ identifier, { agent: { write: true }}]]));
|
new IdentifierMap([[ identifier, { write: true }]]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('combines results.', async(): Promise<void> => {
|
it('combines results.', async(): Promise<void> => {
|
||||||
const identifier2 = { path: 'http://example.com/foo2' };
|
const identifier2 = { path: 'http://example.com/foo2' };
|
||||||
const identifier3 = { path: 'http://example.com/foo3' };
|
const identifier3 = { path: 'http://example.com/foo3' };
|
||||||
readers[0].handle.mockResolvedValue(new IdentifierMap([
|
readers[0].handle.mockResolvedValue(new IdentifierMap([
|
||||||
[ identifier, { agent: { read: true }, public: undefined }],
|
[ identifier, { read: true }],
|
||||||
[ identifier2, { agent: { write: true }}],
|
[ identifier2, { write: true }],
|
||||||
[ identifier3, { agent: { append: false }, public: { delete: true }}],
|
[ identifier3, { append: false }],
|
||||||
]));
|
]));
|
||||||
readers[1].handle.mockResolvedValue(new IdentifierMap<PermissionSet>([
|
readers[1].handle.mockResolvedValue(new IdentifierMap<PermissionSet>([
|
||||||
[ identifier, { agent: { write: true }, public: { read: false }}],
|
[ identifier, { write: true }],
|
||||||
[ identifier2, { public: { read: false }}],
|
|
||||||
]));
|
]));
|
||||||
compareMaps(await unionReader.handle(input), new IdentifierMap([
|
compareMaps(await unionReader.handle(input), new IdentifierMap([
|
||||||
[ identifier, { agent: { read: true, write: true }, public: { read: false }}],
|
[ identifier, { read: true, write: true }],
|
||||||
[ identifier2, { agent: { write: true }, public: { read: false }}],
|
[ identifier2, { write: true }],
|
||||||
[ identifier3, { agent: { append: false }, public: { delete: true }}],
|
[ identifier3, { append: false }],
|
||||||
]));
|
]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merges same fields using false > true > undefined.', async(): Promise<void> => {
|
it('merges same fields using false > true > undefined.', async(): Promise<void> => {
|
||||||
readers[0].handle.mockResolvedValue(new IdentifierMap(
|
readers[0].handle.mockResolvedValue(new IdentifierMap(
|
||||||
[[ identifier,
|
[[ identifier, { read: true, write: false, append: undefined, create: true, delete: undefined }]],
|
||||||
{ agent: { read: true, write: false, append: undefined, create: true, delete: undefined }}]],
|
|
||||||
));
|
));
|
||||||
readers[1].handle.mockResolvedValue(new IdentifierMap(
|
readers[1].handle.mockResolvedValue(new IdentifierMap(
|
||||||
[[ identifier, { agent:
|
[[ identifier, { read: false, write: true, append: true, create: true, delete: undefined }]],
|
||||||
{ read: false, write: true, append: true, create: true, delete: undefined }}]],
|
));
|
||||||
|
compareMaps(await unionReader.handle(input), new IdentifierMap(
|
||||||
|
[[ identifier, { read: false, write: false, append: true, create: true }]],
|
||||||
));
|
));
|
||||||
compareMaps(await unionReader.handle(input), new IdentifierMap([[ identifier, {
|
|
||||||
agent: { read: false, write: false, append: true, create: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { DataFactory } from 'n3';
|
|||||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||||
import type { AccessChecker } from '../../../src/authorization/access/AccessChecker';
|
import type { AccessChecker } from '../../../src/authorization/access/AccessChecker';
|
||||||
import type { PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
import type { PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
||||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
import { AclMode } from '../../../src/authorization/permissions/AclPermissionSet';
|
||||||
import type { AccessMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
import type { AccessMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||||
import { WebAclReader } from '../../../src/authorization/WebAclReader';
|
import { WebAclReader } from '../../../src/authorization/WebAclReader';
|
||||||
@ -77,10 +77,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
|
|
||||||
it('returns undefined permissions for undefined credentials.', async(): Promise<void> => {
|
it('returns undefined permissions for undefined credentials.', async(): Promise<void> => {
|
||||||
input.credentials = {};
|
input.credentials = {};
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {}]]));
|
||||||
public: {},
|
|
||||||
agent: {},
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads the accessTo value of the acl resource.', async(): Promise<void> => {
|
it('reads the accessTo value of the acl resource.', async(): Promise<void> => {
|
||||||
@ -90,10 +87,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth'), nn(`${acl}accessTo`), nn(identifier.path)),
|
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}Read`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]]));
|
||||||
public: { read: true },
|
|
||||||
agent: { read: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('ignores accessTo fields pointing to different resources.', async(): Promise<void> => {
|
it('ignores accessTo fields pointing to different resources.', async(): Promise<void> => {
|
||||||
@ -103,10 +97,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth'), nn(`${acl}accessTo`), nn('somewhereElse')),
|
quad(nn('auth'), nn(`${acl}accessTo`), nn('somewhereElse')),
|
||||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {}]]));
|
||||||
public: {},
|
|
||||||
agent: {},
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handles all valid modes and ignores other ones.', async(): Promise<void> => {
|
it('handles all valid modes and ignores other ones.', async(): Promise<void> => {
|
||||||
@ -117,10 +108,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)),
|
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]]));
|
||||||
public: { read: true },
|
|
||||||
agent: { read: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('reads the default value of a parent if there is no direct acl resource.', async(): Promise<void> => {
|
it('reads the default value of a parent if there is no direct acl resource.', async(): Promise<void> => {
|
||||||
@ -131,10 +119,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth'), nn(`${acl}default`), nn(identifierStrategy.getParentContainer(identifier).path)),
|
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}Read`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { read: true }]]));
|
||||||
public: { read: true },
|
|
||||||
agent: { read: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not use default authorizations for the resource itself.', async(): Promise<void> => {
|
it('does not use default authorizations for the resource itself.', async(): Promise<void> => {
|
||||||
@ -148,10 +133,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth2'), nn(`${acl}accessTo`), nn(identifier.path)),
|
quad(nn('auth2'), nn(`${acl}accessTo`), nn(identifier.path)),
|
||||||
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
|
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, { append: true }]]));
|
||||||
public: { append: true },
|
|
||||||
agent: { append: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('re-throws ResourceStore errors as internal errors.', async(): Promise<void> => {
|
it('re-throws ResourceStore errors as internal errors.', async(): Promise<void> => {
|
||||||
@ -170,9 +152,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
|
|
||||||
it('ignores rules where no access is granted.', async(): Promise<void> => {
|
it('ignores rules where no access is granted.', async(): Promise<void> => {
|
||||||
credentials.agent = { webId: 'http://test.com/user' };
|
credentials.agent = { webId: 'http://test.com/user' };
|
||||||
// CredentialGroup.public gets true on auth1, CredentialGroup.agent on auth2
|
accessChecker.handleSafe.mockImplementation(async({ rule }): Promise<boolean> => rule.value !== 'auth1');
|
||||||
accessChecker.handleSafe.mockImplementation(async({ rule, credentials: cred }): Promise<boolean> =>
|
|
||||||
(rule.value === 'auth1') === !cred.agent?.webId);
|
|
||||||
|
|
||||||
store.getRepresentation.mockResolvedValue(new BasicRepresentation([
|
store.getRepresentation.mockResolvedValue(new BasicRepresentation([
|
||||||
quad(nn('auth1'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
quad(nn('auth1'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||||
@ -183,10 +163,7 @@ describe('A WebAclReader', (): void => {
|
|||||||
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
|
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
|
||||||
], INTERNAL_QUADS));
|
], INTERNAL_QUADS));
|
||||||
|
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap<PermissionSet>([[ identifier, {
|
compareMaps(await reader.handle(input), new IdentifierMap<PermissionSet>([[ identifier, { append: true }]]));
|
||||||
public: { read: true },
|
|
||||||
agent: { append: true },
|
|
||||||
}]]));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('combines ACL representation requests for resources when possible.', async(): Promise<void> => {
|
it('combines ACL representation requests for resources when possible.', async(): Promise<void> => {
|
||||||
@ -224,9 +201,9 @@ describe('A WebAclReader', (): void => {
|
|||||||
input.requestedModes.set(identifier3, new Set([ AccessMode.append ]));
|
input.requestedModes.set(identifier3, new Set([ AccessMode.append ]));
|
||||||
|
|
||||||
compareMaps(await reader.handle(input), new IdentifierMap([
|
compareMaps(await reader.handle(input), new IdentifierMap([
|
||||||
[ identifier, { public: { read: true }, agent: { read: true }}],
|
[ identifier, { read: true }],
|
||||||
[ identifier2, { public: { read: true }, agent: { read: true }}],
|
[ identifier2, { read: true }],
|
||||||
[ identifier3, { public: { append: true }, agent: { append: true }}],
|
[ identifier3, { append: true }],
|
||||||
]));
|
]));
|
||||||
// http://example.com/.acl and http://example.com/bar/.acl
|
// http://example.com/.acl and http://example.com/bar/.acl
|
||||||
expect(store.getRepresentation).toHaveBeenCalledTimes(2);
|
expect(store.getRepresentation).toHaveBeenCalledTimes(2);
|
||||||
|
@ -18,7 +18,7 @@ describe('An AuthorizingHttpHandler', (): void => {
|
|||||||
const target = { path: 'http://example.com/foo' };
|
const target = { path: 'http://example.com/foo' };
|
||||||
const requestedModes: AccessMap = new IdentifierSetMultiMap<AccessMode>([[ target, AccessMode.read ]]);
|
const requestedModes: AccessMap = new IdentifierSetMultiMap<AccessMode>([[ target, AccessMode.read ]]);
|
||||||
const availablePermissions: PermissionMap = new IdentifierMap(
|
const availablePermissions: PermissionMap = new IdentifierMap(
|
||||||
[[ target, { public: { read: true }}]],
|
[[ target, { read: true }]],
|
||||||
);
|
);
|
||||||
const request: HttpRequest = {} as any;
|
const request: HttpRequest = {} as any;
|
||||||
const response: HttpResponse = {} as any;
|
const response: HttpResponse = {} as any;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'jest-rdf';
|
import 'jest-rdf';
|
||||||
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
||||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||||
import type { AclPermission } from '../../../src/authorization/permissions/AclPermission';
|
|
||||||
import type { ModesExtractor } from '../../../src/authorization/permissions/ModesExtractor';
|
import type { ModesExtractor } from '../../../src/authorization/permissions/ModesExtractor';
|
||||||
import type { Operation } from '../../../src/http/Operation';
|
import type { Operation } from '../../../src/http/Operation';
|
||||||
import { OkResponseDescription } from '../../../src/http/output/response/OkResponseDescription';
|
import { OkResponseDescription } from '../../../src/http/output/response/OkResponseDescription';
|
||||||
@ -56,15 +55,14 @@ describe('A WacAllowHttpHandler', (): void => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('adds permission metadata.', async(): Promise<void> => {
|
it('adds permission metadata.', async(): Promise<void> => {
|
||||||
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ target, {
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
agent: { read: true, write: true, control: false } as AclPermission,
|
[[ target, { read: true, write: true, append: false }]],
|
||||||
public: { read: true, write: false },
|
));
|
||||||
}]]));
|
|
||||||
|
|
||||||
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
||||||
expect(output.metadata!.quads()).toHaveLength(3);
|
expect(output.metadata!.quads()).toHaveLength(4);
|
||||||
expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]);
|
expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]);
|
||||||
expect(output.metadata!.get(AUTH.terms.publicMode)).toEqualRdfTerm(ACL.terms.Read);
|
expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]);
|
||||||
|
|
||||||
expect(source.handleSafe).toHaveBeenCalledTimes(1);
|
expect(source.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
expect(source.handleSafe).toHaveBeenLastCalledWith({ operation, request, response });
|
expect(source.handleSafe).toHaveBeenLastCalledWith({ operation, request, response });
|
||||||
@ -79,21 +77,59 @@ describe('A WacAllowHttpHandler', (): void => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds no permissions for credential groups that are not defined.', async(): Promise<void> => {
|
it('determines public permissions separately in case of an authenticated request.', async(): Promise<void> => {
|
||||||
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ target, {}]]));
|
credentialsExtractor.handleSafe.mockResolvedValue({ agent: { webId: 'http://example.com/#me' }});
|
||||||
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
|
[[ target, { read: true, write: true, append: false }]],
|
||||||
|
));
|
||||||
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
|
[[ target, { read: true, write: false, append: true }]],
|
||||||
|
));
|
||||||
|
|
||||||
|
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
||||||
|
expect(output.metadata!.quads()).toHaveLength(4);
|
||||||
|
expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Write ]);
|
||||||
|
expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([ ACL.terms.Read, ACL.terms.Append ]);
|
||||||
|
|
||||||
|
expect(source.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
|
expect(source.handleSafe).toHaveBeenLastCalledWith({ operation, request, response });
|
||||||
|
expect(credentialsExtractor.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
|
expect(credentialsExtractor.handleSafe).toHaveBeenLastCalledWith(request);
|
||||||
|
expect(modesExtractor.handleSafe).toHaveBeenCalledTimes(1);
|
||||||
|
expect(modesExtractor.handleSafe).toHaveBeenLastCalledWith(operation);
|
||||||
|
expect(permissionReader.handleSafe).toHaveBeenCalledTimes(2);
|
||||||
|
expect(permissionReader.handleSafe).toHaveBeenNthCalledWith(1, {
|
||||||
|
credentials: { agent: { webId: 'http://example.com/#me' }},
|
||||||
|
requestedModes: await modesExtractor.handleSafe.mock.results[0].value,
|
||||||
|
});
|
||||||
|
expect(permissionReader.handleSafe).toHaveBeenNthCalledWith(2, {
|
||||||
|
credentials: {},
|
||||||
|
requestedModes: await modesExtractor.handleSafe.mock.results[0].value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds no permissions if none of them are on the target.', async(): Promise<void> => {
|
||||||
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
|
[[{ path: 'http://example/other' }, { read: true, write: false }]],
|
||||||
|
));
|
||||||
|
|
||||||
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
||||||
expect(output.metadata!.quads()).toHaveLength(0);
|
expect(output.metadata!.quads()).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('adds no permissions if none of them are on the target.', async(): Promise<void> => {
|
it('adds no public permissions if the second call has no results for the target.', async(): Promise<void> => {
|
||||||
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap([[{ path: 'http://example/other' }, {
|
credentialsExtractor.handleSafe.mockResolvedValue({ agent: { webId: 'http://example.com/#me' }});
|
||||||
agent: { read: true, write: true, control: false } as AclPermission,
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
public: { read: true, write: false },
|
[[ target, { read: true, write: false }]],
|
||||||
}]]));
|
));
|
||||||
|
permissionReader.handleSafe.mockResolvedValueOnce(new IdentifierMap(
|
||||||
|
[[{ path: 'http://example/other' }, { read: false, write: true }]],
|
||||||
|
));
|
||||||
|
|
||||||
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
await expect(handler.handle({ operation, request, response })).resolves.toEqual(output);
|
||||||
expect(output.metadata!.quads()).toHaveLength(0);
|
expect(output.metadata!.quads()).toHaveLength(1);
|
||||||
|
expect(output.metadata!.getAll(AUTH.terms.userMode)).toEqualRdfTermArray([ ACL.terms.Read ]);
|
||||||
|
expect(output.metadata!.getAll(AUTH.terms.publicMode)).toEqualRdfTermArray([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('immediately returns the source output if the operation method is not GET or HEAD.', async(): Promise<void> => {
|
it('immediately returns the source output if the operation method is not GET or HEAD.', async(): Promise<void> => {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import type { ResourceStore } from '../../src/';
|
import type { ResourceStore } from '../../src/';
|
||||||
import { BasicRepresentation } from '../../src/';
|
import { BasicRepresentation } from '../../src/';
|
||||||
import type { AclPermission } from '../../src/authorization/permissions/AclPermission';
|
import type { AclPermissionSet } from '../../src/authorization/permissions/AclPermissionSet';
|
||||||
|
|
||||||
export type AclHelperInput = {
|
export type AclHelperInput = {
|
||||||
permissions: AclPermission;
|
permissions: AclPermissionSet;
|
||||||
agentClass?: 'agent' | 'authenticated';
|
agentClass?: 'agent' | 'authenticated';
|
||||||
agent?: string;
|
agent?: string;
|
||||||
accessTo?: boolean;
|
accessTo?: boolean;
|
||||||
@ -39,7 +39,7 @@ export class AclHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const perm of [ 'Read', 'Append', 'Write', 'Control' ]) {
|
for (const perm of [ 'Read', 'Append', 'Write', 'Control' ]) {
|
||||||
if (option.permissions[perm.toLowerCase() as keyof AclPermission]) {
|
if (option.permissions[perm.toLowerCase() as keyof AclPermissionSet]) {
|
||||||
acl.push(`;\n acl:mode acl:${perm}`);
|
acl.push(`;\n acl:mode acl:${perm}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user