mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
feat: Update Credentials typings to support client/issuer
This commit is contained in:
@@ -5,8 +5,7 @@ import type { HttpRequest } from '../server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import { matchesAuthorizationScheme } from '../util/HeaderUtil';
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
export class BearerWebIdExtractor extends CredentialsExtractor {
|
||||
@@ -25,13 +24,18 @@ export class BearerWebIdExtractor extends CredentialsExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(request: HttpRequest): Promise<CredentialSet> {
|
||||
public async handle(request: HttpRequest): Promise<Credentials> {
|
||||
const { headers: { authorization }} = request;
|
||||
|
||||
try {
|
||||
const { webid: webId } = await this.verify(authorization!);
|
||||
this.logger.info(`Verified WebID via Bearer access token: ${webId}`);
|
||||
return { [CredentialGroup.agent]: { webId }};
|
||||
const { webid: webId, client_id: clientId, iss: issuer } = await this.verify(authorization!);
|
||||
this.logger.info(`Verified credentials via Bearer access token. WebID: ${webId
|
||||
}, client ID: ${clientId}, issuer: ${issuer}`);
|
||||
const credentials: Credentials = { agent: { webId }, issuer: { url: issuer }};
|
||||
if (clientId) {
|
||||
credentials.client = { clientId };
|
||||
}
|
||||
return credentials;
|
||||
} catch (error: unknown) {
|
||||
const message = `Error verifying WebID via Bearer access token: ${(error as Error).message}`;
|
||||
this.logger.warn(message);
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
/**
|
||||
* Credentials identifying an entity accessing or owning data.
|
||||
*/
|
||||
export interface Credential {
|
||||
webId?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specific groups that can have credentials.
|
||||
*/
|
||||
export enum CredentialGroup {
|
||||
public = 'public',
|
||||
agent = 'agent',
|
||||
}
|
||||
|
||||
/**
|
||||
* A combination of multiple credentials, where their group is specified by the key.
|
||||
*/
|
||||
export type CredentialSet = Partial<Record<CredentialGroup, Credential>>;
|
||||
export type Credentials = {
|
||||
agent?: { webId: string };
|
||||
client?: { clientId: string };
|
||||
issuer?: { url: string };
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { HttpRequest } from '../server/HttpRequest';
|
||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
|
||||
/**
|
||||
* Responsible for extracting credentials from an incoming request.
|
||||
*/
|
||||
export abstract class CredentialsExtractor extends AsyncHandler<HttpRequest, CredentialSet> {}
|
||||
export abstract class CredentialsExtractor extends AsyncHandler<HttpRequest, Credentials> {}
|
||||
|
||||
@@ -6,8 +6,7 @@ import type { HttpRequest } from '../server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import { matchesAuthorizationScheme } from '../util/HeaderUtil';
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
@@ -33,7 +32,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
public async handle(request: HttpRequest): Promise<CredentialSet> {
|
||||
public async handle(request: HttpRequest): Promise<Credentials> {
|
||||
const { headers: { authorization, dpop }, method } = request;
|
||||
if (!dpop) {
|
||||
throw new BadRequestHttpError('No DPoP header specified.');
|
||||
@@ -46,7 +45,7 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
||||
// Validate the Authorization and DPoP header headers
|
||||
// and extract the WebID provided by the client
|
||||
try {
|
||||
const { webid: webId } = await this.verify(
|
||||
const { webid: webId, client_id: clientId, iss: issuer } = await this.verify(
|
||||
authorization!,
|
||||
{
|
||||
header: dpop as string,
|
||||
@@ -54,8 +53,13 @@ export class DPoPWebIdExtractor extends CredentialsExtractor {
|
||||
url: originalUrl.path,
|
||||
},
|
||||
);
|
||||
this.logger.info(`Verified WebID via DPoP-bound access token: ${webId}`);
|
||||
return { [CredentialGroup.agent]: { webId }};
|
||||
this.logger.info(`Verified WebID via DPoP-bound access token. WebID: ${webId
|
||||
}, client ID: ${clientId}, issuer: ${issuer}`);
|
||||
const credentials: Credentials = { agent: { webId }, issuer: { url: issuer }};
|
||||
if (clientId) {
|
||||
credentials.client = { clientId };
|
||||
}
|
||||
return credentials;
|
||||
} catch (error: unknown) {
|
||||
const message = `Error verifying WebID via DPoP-bound access token: ${(error as Error).message}`;
|
||||
this.logger.warn(message);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
* Extracts the public credentials, to be used for data everyone has access to.
|
||||
* Extracts the "public credentials", to be used for data everyone has access to.
|
||||
* This class mainly exists so a {@link Credentials} is still generated in case the token parsing fails.
|
||||
*/
|
||||
export class PublicCredentialsExtractor extends CredentialsExtractor {
|
||||
public async handle(): Promise<CredentialSet> {
|
||||
return { [CredentialGroup.public]: {}};
|
||||
public async handle(): Promise<Credentials> {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { UnionHandler } from '../util/handlers/UnionHandler';
|
||||
import type { CredentialGroup, Credential, CredentialSet } from './Credentials';
|
||||
|
||||
import type { Credentials } from './Credentials';
|
||||
import type { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
@@ -13,15 +12,22 @@ export class UnionCredentialsExtractor extends UnionHandler<CredentialsExtractor
|
||||
super(extractors);
|
||||
}
|
||||
|
||||
public async combine(results: CredentialSet[]): Promise<CredentialSet> {
|
||||
public async combine(results: Credentials[]): Promise<Credentials> {
|
||||
// Combine all the results into a single object
|
||||
return results.reduce((result, credential): CredentialSet => {
|
||||
for (const [ key, value ] of Object.entries(credential) as [ CredentialGroup, Credential ][]) {
|
||||
if (value) {
|
||||
result[key] = value;
|
||||
}
|
||||
return results.reduce((result, credentials): Credentials => {
|
||||
for (const key of Object.keys(credentials) as (keyof Credentials)[]) {
|
||||
this.setValue(result, key, credentials[key]);
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that makes sure the typings are correct.
|
||||
*/
|
||||
private setValue<T extends keyof Credentials>(credentials: Credentials, key: T, value?: Credentials[T]): void {
|
||||
if (value) {
|
||||
credentials[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { Credential, CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
@@ -8,17 +7,17 @@ import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
* (useful for development or debugging purposes).
|
||||
*/
|
||||
export class UnsecureConstantCredentialsExtractor extends CredentialsExtractor {
|
||||
private readonly credentials: CredentialSet;
|
||||
private readonly credentials: Credentials;
|
||||
private readonly logger = getLoggerFor(this);
|
||||
|
||||
public constructor(agent: string);
|
||||
public constructor(agent: Credential);
|
||||
public constructor(agent: string | Credential) {
|
||||
public constructor(agent: Credentials['agent']);
|
||||
public constructor(agent: string | Credentials['agent']) {
|
||||
super();
|
||||
this.credentials = { [CredentialGroup.agent]: typeof agent === 'string' ? { webId: agent } : agent };
|
||||
this.credentials = { agent: typeof agent === 'string' ? { webId: agent } : agent };
|
||||
}
|
||||
|
||||
public async handle(): Promise<CredentialSet> {
|
||||
public async handle(): Promise<Credentials> {
|
||||
this.logger.info(`Agent unsecurely claims to be ${this.credentials.agent!.webId}`);
|
||||
return this.credentials;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,7 @@ import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { HttpRequest } from '../server/HttpRequest';
|
||||
import { NotImplementedHttpError } from '../util/errors/NotImplementedHttpError';
|
||||
import { matchesAuthorizationScheme } from '../util/HeaderUtil';
|
||||
import { CredentialGroup } from './Credentials';
|
||||
import type { CredentialSet } from './Credentials';
|
||||
import type { Credentials } from './Credentials';
|
||||
import { CredentialsExtractor } from './CredentialsExtractor';
|
||||
|
||||
/**
|
||||
@@ -19,9 +18,9 @@ export class UnsecureWebIdExtractor extends CredentialsExtractor {
|
||||
}
|
||||
}
|
||||
|
||||
public async handle({ headers }: HttpRequest): Promise<CredentialSet> {
|
||||
public async handle({ headers }: HttpRequest): Promise<Credentials> {
|
||||
const webId = /^WebID\s+(.*)/ui.exec(headers.authorization!)![1];
|
||||
this.logger.info(`Agent unsecurely claims to be ${webId}`);
|
||||
return { [CredentialGroup.agent]: { webId }};
|
||||
return { agent: { webId }};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { IAccessControlledResource } from '@solid/access-control-policy/dis
|
||||
import type { IContext } from '@solid/access-control-policy/dist/type/i_context';
|
||||
import type { IPolicy } from '@solid/access-control-policy/dist/type/i_policy';
|
||||
import type { Store } from 'n3';
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
@@ -70,7 +70,7 @@ export class AcpReader extends PermissionReader {
|
||||
* @param credentials - Credentials that are trying to access the resource.
|
||||
* @param resourceCache - Cache used to store ACR data.
|
||||
*/
|
||||
private async extractPermissions(target: ResourceIdentifier, credentials: CredentialSet,
|
||||
private async extractPermissions(target: ResourceIdentifier, credentials: Credentials,
|
||||
resourceCache: IdentifierMap<IAccessControlledResource[]>): Promise<PermissionSet> {
|
||||
const context = this.createContext(target, credentials);
|
||||
const policies: IPolicy[] = [];
|
||||
@@ -108,10 +108,12 @@ export class AcpReader extends PermissionReader {
|
||||
/**
|
||||
* Creates an ACP context targeting the given identifier with the provided credentials.
|
||||
*/
|
||||
private createContext(target: ResourceIdentifier, credentials: CredentialSet): IContext {
|
||||
private createContext(target: ResourceIdentifier, credentials: Credentials): IContext {
|
||||
return {
|
||||
target: target.path,
|
||||
agent: credentials.agent?.webId,
|
||||
client: credentials.client?.clientId,
|
||||
issuer: credentials.issuer?.url,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { CredentialGroup, CredentialSet } from '../authentication/Credentials';
|
||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||
import type { PermissionReaderInput } from './PermissionReader';
|
||||
import { PermissionReader } from './PermissionReader';
|
||||
import { permissionSetKeys } from './permissions/Permissions';
|
||||
import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions';
|
||||
|
||||
/**
|
||||
@@ -22,18 +22,18 @@ export class AllStaticReader extends PermissionReader {
|
||||
});
|
||||
}
|
||||
|
||||
public async handle({ credentials, requestedModes }: PermissionReaderInput): Promise<PermissionMap> {
|
||||
public async handle({ requestedModes }: PermissionReaderInput): Promise<PermissionMap> {
|
||||
const availablePermissions = new IdentifierMap<PermissionSet>();
|
||||
const permissions = this.createPermissions(credentials);
|
||||
const permissions = this.createPermissions();
|
||||
for (const [ identifier ] of requestedModes) {
|
||||
availablePermissions.set(identifier, permissions);
|
||||
}
|
||||
return availablePermissions;
|
||||
}
|
||||
|
||||
private createPermissions(credentials: CredentialSet): PermissionSet {
|
||||
private createPermissions(): PermissionSet {
|
||||
const result: PermissionSet = {};
|
||||
for (const group of Object.keys(credentials) as CredentialGroup[]) {
|
||||
for (const group of permissionSetKeys) {
|
||||
result[group] = this.permissions;
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { CredentialGroup } from '../authentication/Credentials';
|
||||
import type { AuxiliaryStrategy } from '../http/auxiliary/AuxiliaryStrategy';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
@@ -67,7 +66,7 @@ export class AuthAuxiliaryReader extends PermissionReader {
|
||||
*/
|
||||
protected interpretControl(identifier: ResourceIdentifier, permissionSet: PermissionSet = {}): PermissionSet {
|
||||
const authSet: PermissionSet = {};
|
||||
for (const [ group, permissions ] of Object.entries(permissionSet) as [ CredentialGroup, AclPermission ][]) {
|
||||
for (const [ group, permissions ] of Object.entries(permissionSet) as [ keyof PermissionSet, AclPermission ][]) {
|
||||
const { control } = permissions;
|
||||
authSet[group] = {
|
||||
read: control,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||
import type { AccessMap, PermissionMap } from './permissions/Permissions';
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface AuthorizerInput {
|
||||
/**
|
||||
* Credentials of the entity that wants to use the resource.
|
||||
*/
|
||||
credentials: CredentialSet;
|
||||
credentials: Credentials;
|
||||
/**
|
||||
* Requested access modes per resource.
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import { CredentialGroup } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../http/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import type { AccountSettings, AccountStore } from '../identity/interaction/email-password/storage/AccountStore';
|
||||
@@ -52,7 +51,7 @@ export class OwnerPermissionReader extends PermissionReader {
|
||||
for (const auth of auths) {
|
||||
if (this.identifierStrategy.contains(podBaseUrl, auth, true)) {
|
||||
this.logger.debug(`Granting Control permissions to owner on ${auth.path}`);
|
||||
result.set(auth, { [CredentialGroup.agent]: {
|
||||
result.set(auth, { agent: {
|
||||
read: true,
|
||||
write: true,
|
||||
append: true,
|
||||
@@ -69,7 +68,7 @@ export class OwnerPermissionReader extends PermissionReader {
|
||||
* Find the base URL of the pod the given credentials own.
|
||||
* Will throw an error if none can be found.
|
||||
*/
|
||||
private async findPodBaseUrl(credentials: CredentialSet): Promise<ResourceIdentifier> {
|
||||
private async findPodBaseUrl(credentials: Credentials): Promise<ResourceIdentifier> {
|
||||
if (!credentials.agent?.webId) {
|
||||
throw new NotImplementedHttpError('Only authenticated agents could be owners');
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { CredentialGroup } from '../authentication/Credentials';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { IdentifierStrategy } from '../util/identifiers/IdentifierStrategy';
|
||||
@@ -84,8 +83,8 @@ export class ParentContainerReader extends PermissionReader {
|
||||
// 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.
|
||||
const resultSet: PermissionSet = { ...resourceSet };
|
||||
for (const [ group, containerPermission ] of Object.entries(containerSet) as [ CredentialGroup, Permission ][]) {
|
||||
resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPermission);
|
||||
for (const [ group, containerPerms ] of Object.entries(containerSet) as [ keyof PermissionSet, Permission ][]) {
|
||||
resultSet[group] = this.interpretContainerPermission(resourceSet[group] ?? {}, containerPerms);
|
||||
}
|
||||
return resultSet;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
import type { ResourceSet } from '../storage/ResourceSet';
|
||||
@@ -75,7 +75,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
||||
* @param permissionSet - PermissionSet describing the available permissions of the credentials.
|
||||
* @param mode - Which mode is requested.
|
||||
*/
|
||||
private requireModePermission(credentials: CredentialSet, permissionSet: PermissionSet, mode: AccessMode): void {
|
||||
private requireModePermission(credentials: Credentials, permissionSet: PermissionSet, mode: AccessMode): void {
|
||||
if (!this.hasModePermission(permissionSet, mode)) {
|
||||
if (this.isAuthenticated(credentials)) {
|
||||
this.logger.warn(`Agent ${credentials.agent!.webId} has no ${mode} permissions`);
|
||||
@@ -106,7 +106,7 @@ export class PermissionBasedAuthorizer extends Authorizer {
|
||||
* Checks whether the agent is authenticated (logged in) or not (public/anonymous).
|
||||
* @param credentials - Credentials to check.
|
||||
*/
|
||||
private isAuthenticated(credentials: CredentialSet): boolean {
|
||||
private isAuthenticated(credentials: Credentials): boolean {
|
||||
return typeof credentials.agent?.webId === 'string';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import { AsyncHandler } from '../util/handlers/AsyncHandler';
|
||||
import type { AccessMap, PermissionMap } from './permissions/Permissions';
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface PermissionReaderInput {
|
||||
/**
|
||||
* Credentials of the entity requesting access to resources.
|
||||
*/
|
||||
credentials: CredentialSet;
|
||||
credentials: Credentials;
|
||||
/**
|
||||
* For each credential, the reader will check which of the given per-resource access modes are available.
|
||||
* However, non-exhaustive information about other access modes and resources can still be returned.
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import type { CredentialGroup } from '../authentication/Credentials';
|
||||
import { UnionHandler } from '../util/handlers/UnionHandler';
|
||||
import { IdentifierMap } from '../util/map/IdentifierMap';
|
||||
import { getDefault } from '../util/map/MapUtil';
|
||||
import type { PermissionReader } from './PermissionReader';
|
||||
import type { Permission, PermissionMap } from './permissions/Permissions';
|
||||
import type { Permission, PermissionMap, PermissionSet } from './permissions/Permissions';
|
||||
|
||||
/**
|
||||
* Combines the results of multiple PermissionReaders.
|
||||
@@ -27,7 +26,7 @@ export class UnionPermissionReader extends UnionHandler<PermissionReader> {
|
||||
*/
|
||||
private mergePermissionMaps(permissionMap: PermissionMap, result: PermissionMap): void {
|
||||
for (const [ identifier, permissionSet ] of permissionMap) {
|
||||
for (const [ credential, permission ] of Object.entries(permissionSet) as [CredentialGroup, Permission][]) {
|
||||
for (const [ credential, permission ] of Object.entries(permissionSet) as [keyof PermissionSet, Permission][]) {
|
||||
const resultSet = getDefault(result, identifier, {});
|
||||
resultSet[credential] = this.mergePermissions(permission, resultSet[credential]);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Store } from 'n3';
|
||||
import type { Credential, CredentialSet } from '../authentication/Credentials';
|
||||
import { CredentialGroup } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import type { AuxiliaryIdentifierStrategy } from '../http/auxiliary/AuxiliaryIdentifierStrategy';
|
||||
import type { ResourceIdentifier } from '../http/representation/ResourceIdentifier';
|
||||
import { getLoggerFor } from '../logging/LogUtil';
|
||||
@@ -78,17 +77,17 @@ export class WebAclReader extends PermissionReader {
|
||||
* @param aclMap - A map containing stores of ACL data linked to their relevant identifiers.
|
||||
* @param credentials - Credentials to check permissions for.
|
||||
*/
|
||||
private async findPermissions(aclMap: Map<Store, ResourceIdentifier[]>, credentials: CredentialSet):
|
||||
private async findPermissions(aclMap: Map<Store, ResourceIdentifier[]>, credentials: Credentials):
|
||||
Promise<PermissionMap> {
|
||||
const result: PermissionMap = new IdentifierMap();
|
||||
for (const [ store, aclIdentifiers ] of aclMap) {
|
||||
// WebACL only supports public and agent permissions
|
||||
const publicPermissions = await this.determinePermissions(store, credentials.public);
|
||||
const agentPermissions = await this.determinePermissions(store, credentials.agent);
|
||||
// WebACL requires knowledge of both the public and agent-specific permissions for the WAC-Allow header.
|
||||
const publicPermissions = await this.determinePermissions(store, {});
|
||||
const agentPermissions = credentials.agent ? await this.determinePermissions(store, credentials) : {};
|
||||
for (const identifier of aclIdentifiers) {
|
||||
result.set(identifier, {
|
||||
[CredentialGroup.public]: publicPermissions,
|
||||
[CredentialGroup.agent]: agentPermissions,
|
||||
public: publicPermissions,
|
||||
agent: agentPermissions,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -98,20 +97,16 @@ export class WebAclReader extends PermissionReader {
|
||||
|
||||
/**
|
||||
* Determines the available permissions for the given credentials.
|
||||
* Will deny all permissions if credentials are not defined
|
||||
* @param acl - Store containing all relevant authorization triples.
|
||||
* @param credential - Credentials to find the permissions for.
|
||||
* @param credentials - Credentials to find the permissions for.
|
||||
*/
|
||||
private async determinePermissions(acl: Store, credential?: Credential): Promise<AclPermission> {
|
||||
private async determinePermissions(acl: Store, credentials: Credentials): Promise<AclPermission> {
|
||||
const aclPermissions: AclPermission = {};
|
||||
if (!credential) {
|
||||
return aclPermissions;
|
||||
}
|
||||
|
||||
// Apply all ACL rules
|
||||
const aclRules = acl.getSubjects(RDF.type, ACL.Authorization, null);
|
||||
for (const rule of aclRules) {
|
||||
const hasAccess = await this.accessChecker.handleSafe({ acl, rule, credential });
|
||||
const hasAccess = await this.accessChecker.handleSafe({ acl, rule, credentials });
|
||||
if (hasAccess) {
|
||||
// Set all allowed modes to true
|
||||
const modes = acl.getObjects(rule, ACL.mode, null);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Store, Term } from 'n3';
|
||||
import type { Credential } from '../../authentication/Credentials';
|
||||
import type { Credentials } from '../../authentication/Credentials';
|
||||
import { AsyncHandler } from '../../util/handlers/AsyncHandler';
|
||||
|
||||
/**
|
||||
@@ -19,7 +19,7 @@ export interface AccessCheckerArgs {
|
||||
rule: Term;
|
||||
|
||||
/**
|
||||
* Credential of the entity that wants to use the resource.
|
||||
* Credentials of the entity that wants to use the resource.
|
||||
*/
|
||||
credential: Credential;
|
||||
credentials: Credentials;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import { AccessChecker } from './AccessChecker';
|
||||
* Checks if the given WebID has been given access.
|
||||
*/
|
||||
export class AgentAccessChecker extends AccessChecker {
|
||||
public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise<boolean> {
|
||||
if (typeof credential.webId === 'string') {
|
||||
return acl.countQuads(rule, ACL.terms.agent, credential.webId, null) !== 0;
|
||||
public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise<boolean> {
|
||||
if (typeof credentials.agent?.webId === 'string') {
|
||||
return acl.countQuads(rule, ACL.terms.agent, credentials.agent.webId, null) !== 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,13 @@ import { AccessChecker } from './AccessChecker';
|
||||
* Checks access based on the agent class.
|
||||
*/
|
||||
export class AgentClassAccessChecker extends AccessChecker {
|
||||
public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise<boolean> {
|
||||
public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise<boolean> {
|
||||
// Check if unauthenticated agents have access
|
||||
if (acl.countQuads(rule, ACL.terms.agentClass, FOAF.terms.Agent, null) !== 0) {
|
||||
return true;
|
||||
}
|
||||
// Check if the agent is authenticated and if authenticated agents have access
|
||||
if (typeof credential.webId === 'string') {
|
||||
if (typeof credentials.agent?.webId === 'string') {
|
||||
return acl.countQuads(rule, ACL.terms.agentClass, ACL.terms.AuthenticatedAgent, null) !== 0;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -16,9 +16,9 @@ export class AgentGroupAccessChecker extends AccessChecker {
|
||||
super();
|
||||
}
|
||||
|
||||
public async handle({ acl, rule, credential }: AccessCheckerArgs): Promise<boolean> {
|
||||
if (typeof credential.webId === 'string') {
|
||||
const { webId } = credential;
|
||||
public async handle({ acl, rule, credentials }: AccessCheckerArgs): Promise<boolean> {
|
||||
if (typeof credentials.agent?.webId === 'string') {
|
||||
const { webId } = credentials.agent;
|
||||
const groups = acl.getObjects(rule, ACL.terms.agentGroup, null);
|
||||
|
||||
return await promiseSome(groups.map(async(group: Term): Promise<boolean> =>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { CredentialGroup } from '../../authentication/Credentials';
|
||||
import type { IdentifierMap, IdentifierSetMultiMap } from '../../util/map/IdentifierMap';
|
||||
|
||||
/**
|
||||
@@ -23,9 +22,18 @@ export type AccessMap = IdentifierSetMultiMap<AccessMode>;
|
||||
export type Permission = Partial<Record<AccessMode, boolean>>;
|
||||
|
||||
/**
|
||||
* Permission per CredentialGroup.
|
||||
* The keys that can be used in a {@link PermissionSet};
|
||||
*/
|
||||
export type PermissionSet = Partial<Record<CredentialGroup, Permission>>;
|
||||
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.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../authentication/Credentials';
|
||||
import type { Credentials } from '../authentication/Credentials';
|
||||
import type { CredentialsExtractor } from '../authentication/CredentialsExtractor';
|
||||
import type { Authorizer } from '../authorization/Authorizer';
|
||||
import type { PermissionReader } from '../authorization/PermissionReader';
|
||||
@@ -60,14 +60,14 @@ export class AuthorizingHttpHandler extends OperationHttpHandler {
|
||||
|
||||
public async handle(input: OperationHttpHandlerInput): Promise<ResponseDescription> {
|
||||
const { request, operation } = input;
|
||||
const credentials: CredentialSet = await this.credentialsExtractor.handleSafe(request);
|
||||
const credentials: Credentials = await this.credentialsExtractor.handleSafe(request);
|
||||
this.logger.verbose(`Extracted credentials: ${JSON.stringify(credentials)}`);
|
||||
|
||||
const requestedModes = await this.modesExtractor.handleSafe(operation);
|
||||
this.logger.verbose(`Retrieved required modes: ${[ ...requestedModes ].join(',')}`);
|
||||
this.logger.verbose(`Retrieved required modes: ${[ ...requestedModes.entrySets() ]}`);
|
||||
|
||||
const availablePermissions = await this.permissionReader.handleSafe({ credentials, requestedModes });
|
||||
this.logger.verbose(`Available permissions are ${JSON.stringify(availablePermissions)}`);
|
||||
this.logger.verbose(`Available permissions are ${[ ...availablePermissions.entries() ]}`);
|
||||
|
||||
try {
|
||||
await this.authorizer.handleSafe({ credentials, requestedModes, availablePermissions });
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
rules: {
|
||||
'unicorn/filename-case': 'off',
|
||||
},
|
||||
};
|
||||
@@ -1,4 +0,0 @@
|
||||
import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier';
|
||||
|
||||
const solidTokenVerifier = jest.fn().mockResolvedValue({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me' });
|
||||
export const createSolidTokenVerifier = jest.fn((): SolidTokenVerifierFunction => solidTokenVerifier);
|
||||
@@ -19,9 +19,6 @@ import { IdentityTestState } from './IdentityTestState';
|
||||
const port = getPort('Identity');
|
||||
const baseUrl = `http://localhost:${port}/`;
|
||||
|
||||
// Undo the global access token verifier mock
|
||||
jest.unmock('@solid/access-token-verifier');
|
||||
|
||||
// Don't send actual e-mails
|
||||
jest.mock('nodemailer');
|
||||
|
||||
|
||||
@@ -1,15 +1,24 @@
|
||||
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
||||
import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier';
|
||||
import type { SolidAccessTokenPayload } from '@solid/access-token-verifier/dist/type/SolidAccessTokenPayload';
|
||||
import { BearerWebIdExtractor } from '../../../src/authentication/BearerWebIdExtractor';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
|
||||
const solidTokenVerifier = createSolidTokenVerifier() as jest.MockedFunction<any>;
|
||||
let clientId: string | undefined;
|
||||
const solidTokenVerifier = jest.fn(async(): Promise<SolidAccessTokenPayload> =>
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me', client_id: clientId }));
|
||||
jest.mock('@solid/access-token-verifier', (): any =>
|
||||
({ createSolidTokenVerifier: (): SolidTokenVerifierFunction => solidTokenVerifier }));
|
||||
|
||||
describe('A BearerWebIdExtractor', (): void => {
|
||||
const webIdExtractor = new BearerWebIdExtractor();
|
||||
|
||||
beforeEach((): void => {
|
||||
clientId = undefined;
|
||||
});
|
||||
|
||||
afterEach((): void => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
@@ -56,9 +65,19 @@ describe('A BearerWebIdExtractor', (): void => {
|
||||
expect(solidTokenVerifier).toHaveBeenCalledWith('Bearer token-1234');
|
||||
});
|
||||
|
||||
it('returns the extracted WebID.', async(): Promise<void> => {
|
||||
it('returns the extracted credentials.', async(): Promise<void> => {
|
||||
const result = webIdExtractor.handleSafe(request);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||
await expect(result).resolves.toEqual(
|
||||
{ agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }},
|
||||
);
|
||||
});
|
||||
|
||||
it('also returns the clientID if defined.', async(): Promise<void> => {
|
||||
clientId = 'http://client.example.com/#me';
|
||||
const result = webIdExtractor.handleSafe(request);
|
||||
await expect(result).resolves.toEqual(
|
||||
{ agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }, client: { clientId }},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,7 +105,7 @@ describe('A BearerWebIdExtractor', (): void => {
|
||||
} as any as HttpRequest;
|
||||
|
||||
beforeEach((): void => {
|
||||
solidTokenVerifier.mockImplementationOnce((): void => {
|
||||
solidTokenVerifier.mockImplementationOnce((): never => {
|
||||
throw new Error('invalid');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
import { createSolidTokenVerifier } from '@solid/access-token-verifier';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { SolidTokenVerifierFunction } from '@solid/access-token-verifier';
|
||||
import type { SolidAccessTokenPayload } from '@solid/access-token-verifier/dist/type/SolidAccessTokenPayload';
|
||||
import { DPoPWebIdExtractor } from '../../../src/authentication/DPoPWebIdExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
import { StaticAsyncHandler } from '../../util/StaticAsyncHandler';
|
||||
|
||||
const solidTokenVerifier = createSolidTokenVerifier() as jest.MockedFunction<any>;
|
||||
let clientId: string | undefined;
|
||||
const solidTokenVerifier = jest.fn(async(): Promise<SolidAccessTokenPayload> =>
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
({ aud: 'solid', exp: 1234, iat: 1234, iss: 'example.com/idp', webid: 'http://alice.example/card#me', client_id: clientId }));
|
||||
jest.mock('@solid/access-token-verifier', (): any =>
|
||||
({ createSolidTokenVerifier: (): SolidTokenVerifierFunction => solidTokenVerifier }));
|
||||
|
||||
describe('A DPoPWebIdExtractor', (): void => {
|
||||
const targetExtractor = new StaticAsyncHandler(true, { path: 'http://example.org/foo/bar' });
|
||||
@@ -84,9 +89,17 @@ describe('A DPoPWebIdExtractor', (): void => {
|
||||
expect(solidTokenVerifier).toHaveBeenCalledWith('DPoP token-1234', { header: 'token-5678', method: 'GET', url: 'http://example.org/foo/bar' });
|
||||
});
|
||||
|
||||
it('returns the extracted WebID.', async(): Promise<void> => {
|
||||
it('returns the extracted credentials.', async(): Promise<void> => {
|
||||
const result = webIdExtractor.handleSafe(request);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||
await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }});
|
||||
});
|
||||
|
||||
it('also returns the clientID if defined.', async(): Promise<void> => {
|
||||
clientId = 'http://client.example.com/#me';
|
||||
const result = webIdExtractor.handleSafe(request);
|
||||
await expect(result).resolves.toEqual(
|
||||
{ agent: { webId: 'http://alice.example/card#me' }, issuer: { url: 'example.com/idp' }, client: { clientId }},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,7 +129,7 @@ describe('A DPoPWebIdExtractor', (): void => {
|
||||
} as any as HttpRequest;
|
||||
|
||||
beforeEach((): void => {
|
||||
solidTokenVerifier.mockImplementationOnce((): void => {
|
||||
solidTokenVerifier.mockImplementationOnce((): never => {
|
||||
throw new Error('invalid');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { PublicCredentialsExtractor } from '../../../src/authentication/PublicCredentialsExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
|
||||
@@ -8,6 +7,6 @@ describe('A PublicCredentialsExtractor', (): void => {
|
||||
it('returns the empty credentials.', async(): Promise<void> => {
|
||||
const headers = {};
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.public]: {}});
|
||||
await expect(result).resolves.toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
||||
import { UnionCredentialsExtractor } from '../../../src/authentication/UnionCredentialsExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
|
||||
describe('A UnionCredentialsExtractor', (): void => {
|
||||
const agent: CredentialSet = { [CredentialGroup.agent]: { webId: 'http://test.com/#me' }};
|
||||
const everyone: CredentialSet = { [CredentialGroup.public]: {}};
|
||||
const agent: Credentials = { agent: { webId: 'http://user.example.com/#me' }};
|
||||
const client: Credentials = { client: { clientId: 'http://client.example.com/#me' }};
|
||||
const request: HttpRequest = {} as any;
|
||||
let extractors: jest.Mocked<CredentialsExtractor>[];
|
||||
let extractor: UnionCredentialsExtractor;
|
||||
@@ -19,7 +18,7 @@ describe('A UnionCredentialsExtractor', (): void => {
|
||||
} as any,
|
||||
{
|
||||
canHandle: jest.fn(),
|
||||
handle: jest.fn().mockResolvedValue(everyone),
|
||||
handle: jest.fn().mockResolvedValue(client),
|
||||
} as any,
|
||||
];
|
||||
|
||||
@@ -28,26 +27,26 @@ describe('A UnionCredentialsExtractor', (): void => {
|
||||
|
||||
it('combines the results of the extractors.', async(): Promise<void> => {
|
||||
await expect(extractor.handle(request)).resolves.toEqual({
|
||||
[CredentialGroup.agent]: agent.agent,
|
||||
[CredentialGroup.public]: {},
|
||||
agent: agent.agent,
|
||||
client: client.client,
|
||||
});
|
||||
});
|
||||
|
||||
it('ignores undefined values.', async(): Promise<void> => {
|
||||
extractors[1].handle.mockResolvedValueOnce({
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: undefined,
|
||||
client: client.client,
|
||||
agent: undefined,
|
||||
});
|
||||
await expect(extractor.handle(request)).resolves.toEqual({
|
||||
[CredentialGroup.agent]: agent.agent,
|
||||
[CredentialGroup.public]: {},
|
||||
agent: agent.agent,
|
||||
client: client.client,
|
||||
});
|
||||
});
|
||||
|
||||
it('skips erroring handlers.', async(): Promise<void> => {
|
||||
extractors[0].handle.mockRejectedValueOnce(new Error('error'));
|
||||
await expect(extractor.handle(request)).resolves.toEqual({
|
||||
[CredentialGroup.public]: {},
|
||||
client: client.client,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { UnsecureConstantCredentialsExtractor } from '../../../src/authentication/UnsecureConstantCredentialsExtractor';
|
||||
|
||||
describe('An UnsecureConstantCredentialsExtractor', (): void => {
|
||||
it('extracts a constant WebID.', async(): Promise<void> => {
|
||||
const agent = 'http://alice.example/card#me';
|
||||
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
||||
await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: { webId: agent }});
|
||||
await expect(extractor.handle()).resolves.toEqual({ agent: { webId: agent }});
|
||||
});
|
||||
|
||||
it('extracts constant credentials.', async(): Promise<void> => {
|
||||
const agent = {};
|
||||
const agent = { webId: 'http://example.com/#me' };
|
||||
const extractor = new UnsecureConstantCredentialsExtractor(agent);
|
||||
await expect(extractor.handle()).resolves.toEqual({ [CredentialGroup.agent]: agent });
|
||||
await expect(extractor.handle()).resolves.toEqual({ agent });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { UnsecureWebIdExtractor } from '../../../src/authentication/UnsecureWebIdExtractor';
|
||||
import type { HttpRequest } from '../../../src/server/HttpRequest';
|
||||
import { NotImplementedHttpError } from '../../../src/util/errors/NotImplementedHttpError';
|
||||
@@ -23,12 +22,12 @@ describe('An UnsecureWebIdExtractor', (): void => {
|
||||
it('returns the authorization header as WebID if specified.', async(): Promise<void> => {
|
||||
const headers = { authorization: 'WebID http://alice.example/card#me' };
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||
await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }});
|
||||
});
|
||||
|
||||
it('returns the authorization header as WebID if specified with a lowercase token.', async(): Promise<void> => {
|
||||
const headers = { authorization: 'webid http://alice.example/card#me' };
|
||||
const result = extractor.handleSafe({ headers } as HttpRequest);
|
||||
await expect(result).resolves.toEqual({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }});
|
||||
await expect(result).resolves.toEqual({ agent: { webId: 'http://alice.example/card#me' }});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Parser } from 'n3';
|
||||
import type { Quad } from 'rdf-js';
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import { AcpReader } from '../../../src/authorization/AcpReader';
|
||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
import type { AuxiliaryStrategy } from '../../../src/http/auxiliary/AuxiliaryStrategy';
|
||||
@@ -30,7 +30,7 @@ function toQuads(turtle: string, baseIRI: string): Quad[] {
|
||||
|
||||
describe('An AcpReader', (): void => {
|
||||
const baseUrl = 'http://example.com/';
|
||||
let credentials: CredentialSet;
|
||||
let credentials: Credentials;
|
||||
// Subject identifiers are used as keys, values are the output of their corresponding ACR resource
|
||||
let dataMap: Record<string, Quad[]>;
|
||||
let acrStrategy: AuxiliaryStrategy;
|
||||
@@ -39,7 +39,7 @@ describe('An AcpReader', (): void => {
|
||||
let acpReader: AcpReader;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
credentials = { public: {}};
|
||||
credentials = {};
|
||||
dataMap = {};
|
||||
|
||||
acrStrategy = new SimpleSuffixStrategy(acrSuffix);
|
||||
@@ -167,4 +167,30 @@ describe('An AcpReader', (): void => {
|
||||
expect(acrStore.getRepresentation)
|
||||
.toHaveBeenCalledWith(acrStrategy.getAuxiliaryIdentifier({ path: baseUrl }), { type: { [INTERNAL_QUADS]: 1 }});
|
||||
});
|
||||
|
||||
it('correctly puts the credentials in the context.', async(): Promise<void> => {
|
||||
dataMap[baseUrl] = toQuads(`
|
||||
[]
|
||||
acp:resource <./> ;
|
||||
acp:accessControl [ acp:apply _:policy ].
|
||||
_:policy
|
||||
acp:allow acl:Read;
|
||||
acp:allOf _:matcher.
|
||||
_:matcher
|
||||
acp:agent <http://example.com/#me>;
|
||||
acp:client <http://client.example.com/#me>;
|
||||
acp:issuer <http://example.com/idp>.
|
||||
`, baseUrl);
|
||||
const requestedModes = new IdentifierSetMultiMap([[{ path: baseUrl }, AccessMode.read ]]);
|
||||
let expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: {}}]]);
|
||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||
|
||||
credentials = {
|
||||
agent: { webId: 'http://example.com/#me' },
|
||||
client: { clientId: 'http://client.example.com/#me' },
|
||||
issuer: { url: 'http://example.com/idp' },
|
||||
};
|
||||
expectedPermissions = new IdentifierMap([[{ path: baseUrl }, { agent: { read: true }}]]);
|
||||
compareMaps(await acpReader.handle({ credentials, requestedModes }), expectedPermissions);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { AllStaticReader } from '../../../src/authorization/AllStaticReader';
|
||||
import type { Permission } from '../../../src/authorization/permissions/Permissions';
|
||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -16,7 +15,7 @@ function getPermissions(allow: boolean): Permission {
|
||||
}
|
||||
|
||||
describe('An AllStaticReader', (): void => {
|
||||
const credentials = { [CredentialGroup.agent]: {}};
|
||||
const credentials = {};
|
||||
const identifier = { path: 'http://test.com/resource' };
|
||||
|
||||
it('can handle everything.', async(): Promise<void> => {
|
||||
@@ -28,11 +27,13 @@ describe('An AllStaticReader', (): void => {
|
||||
let authorizer = new AllStaticReader(true);
|
||||
const requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.read ]]);
|
||||
let result = await authorizer.handle({ credentials, requestedModes });
|
||||
compareMaps(result, new IdentifierMap([[ identifier, { [CredentialGroup.agent]: getPermissions(true) }]]));
|
||||
compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(true),
|
||||
agent: getPermissions(true) }]]));
|
||||
|
||||
authorizer = new AllStaticReader(false);
|
||||
|
||||
result = await authorizer.handle({ credentials, requestedModes });
|
||||
compareMaps(result, new IdentifierMap([[ identifier, { [CredentialGroup.agent]: getPermissions(false) }]]));
|
||||
compareMaps(result, new IdentifierMap([[ identifier, { public: getPermissions(false),
|
||||
agent: getPermissions(false) }]]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import { AuthAuxiliaryReader } from '../../../src/authorization/AuthAuxiliaryReader';
|
||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
||||
@@ -16,7 +16,7 @@ describe('An AuthAuxiliaryReader', (): void => {
|
||||
const acl1 = { path: joinUrl(subject1.path, '.acl') };
|
||||
const subject2 = { path: joinUrl(baseUrl, 'bar/') };
|
||||
const acl2 = { path: joinUrl(subject2.path, '.acl') };
|
||||
const credentials: CredentialSet = { public: {}};
|
||||
const credentials: Credentials = {};
|
||||
let requestedModes: AccessMap;
|
||||
let sourceResult: PermissionMap;
|
||||
let aclStrategy: jest.Mocked<AuxiliaryStrategy>;
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { AuxiliaryReader } from '../../../src/authorization/AuxiliaryReader';
|
||||
import type { PermissionReaderInput, PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||
import type { AccessMap, PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -16,7 +15,7 @@ describe('An AuxiliaryReader', (): void => {
|
||||
const subjectIdentifier = { path: 'http://test.com/foo' };
|
||||
const auxiliaryIdentifier1 = { path: 'http://test.com/foo.dummy1' };
|
||||
const auxiliaryIdentifier2 = { path: 'http://test.com/foo.dummy2' };
|
||||
const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }};
|
||||
const permissionSet: PermissionSet = { agent: { read: true }};
|
||||
let source: jest.Mocked<PermissionReader>;
|
||||
let strategy: jest.Mocked<AuxiliaryStrategy>;
|
||||
let reader: AuxiliaryReader;
|
||||
@@ -77,7 +76,7 @@ describe('An AuxiliaryReader', (): void => {
|
||||
[ auxiliaryIdentifier2, AccessMode.read ],
|
||||
[ subjectIdentifier, AccessMode.delete ],
|
||||
]);
|
||||
const resultSet = { [CredentialGroup.agent]: { read: true, write: true, delete: true }};
|
||||
const resultSet = { agent: { read: true, write: true, delete: true }};
|
||||
source.handleSafe.mockResolvedValueOnce(new IdentifierMap([[ subjectIdentifier, resultSet ]]));
|
||||
const permissionMap: PermissionMap = new IdentifierMap([
|
||||
[ subjectIdentifier, resultSet ],
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import { OwnerPermissionReader } from '../../../src/authorization/OwnerPermissionReader';
|
||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
||||
import type { AccessMap } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -16,7 +15,7 @@ import { compareMaps } from '../../util/Util';
|
||||
describe('An OwnerPermissionReader', (): void => {
|
||||
const owner = 'http://example.com/alice/profile/card#me';
|
||||
const podBaseUrl = 'http://example.com/alice/';
|
||||
let credentials: CredentialSet;
|
||||
let credentials: Credentials;
|
||||
let identifier: ResourceIdentifier;
|
||||
let requestedModes: AccessMap;
|
||||
let settings: AccountSettings;
|
||||
@@ -26,7 +25,7 @@ describe('An OwnerPermissionReader', (): void => {
|
||||
let reader: OwnerPermissionReader;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
credentials = { [CredentialGroup.agent]: { webId: owner }};
|
||||
credentials = { agent: { webId: owner }};
|
||||
|
||||
identifier = { path: `${podBaseUrl}.acl` };
|
||||
|
||||
@@ -82,7 +81,7 @@ describe('An OwnerPermissionReader', (): 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([[
|
||||
identifier,
|
||||
{ [CredentialGroup.agent]: {
|
||||
{ agent: {
|
||||
read: true,
|
||||
write: true,
|
||||
append: true,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import { ParentContainerReader } from '../../../src/authorization/ParentContainerReader';
|
||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||
import type { AccessMap, PermissionMap } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -16,7 +16,7 @@ describe('A ParentContainerReader', (): void => {
|
||||
const target2 = { path: joinUrl(parent2.path, 'bar') };
|
||||
const parent3 = { path: joinUrl(baseUrl, 'baz/') };
|
||||
const target3 = { path: joinUrl(parent3.path, 'baz') };
|
||||
const credentials: CredentialSet = { public: {}};
|
||||
const credentials: Credentials = {};
|
||||
let requestedModes: AccessMap;
|
||||
let sourceResult: PermissionMap;
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy(baseUrl);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import { PathBasedReader } from '../../../src/authorization/PathBasedReader';
|
||||
import type { PermissionReader, PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
||||
import type { PermissionMap, PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -11,7 +10,7 @@ import { compareMaps } from '../../util/Util';
|
||||
|
||||
describe('A PathBasedReader', (): void => {
|
||||
const baseUrl = 'http://test.com/foo/';
|
||||
const permissionSet: PermissionSet = { [CredentialGroup.agent]: { read: true }};
|
||||
const permissionSet: PermissionSet = { agent: { read: true }};
|
||||
let readers: jest.Mocked<PermissionReader>[];
|
||||
let reader: PathBasedReader;
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { AuthorizerInput } from '../../../src/authorization/Authorizer';
|
||||
import { PermissionBasedAuthorizer } from '../../../src/authorization/PermissionBasedAuthorizer';
|
||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -36,8 +35,8 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||
);
|
||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
[CredentialGroup.agent]: { write: true },
|
||||
public: { read: true, write: false },
|
||||
agent: { write: true },
|
||||
}]]);
|
||||
await expect(authorizer.handle(input)).resolves.toBeUndefined();
|
||||
});
|
||||
@@ -47,7 +46,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||
);
|
||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
public: { read: true, write: false },
|
||||
}]]);
|
||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||
});
|
||||
@@ -58,7 +57,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
[[ identifier, AccessMode.read ], [ identifier, AccessMode.write ]],
|
||||
);
|
||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
public: { read: true, write: false },
|
||||
}]]);
|
||||
await expect(authorizer.handle(input)).rejects.toThrow(ForbiddenHttpError);
|
||||
});
|
||||
@@ -71,7 +70,7 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
resourceSet.hasResource.mockResolvedValueOnce(false);
|
||||
input.requestedModes = new IdentifierSetMultiMap<AccessMode>([[ identifier, AccessMode.delete ]]);
|
||||
input.availablePermissions = new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true },
|
||||
public: { read: true },
|
||||
}]]);
|
||||
await expect(authorizer.handle(input)).rejects.toThrow(NotFoundHttpError);
|
||||
});
|
||||
@@ -86,12 +85,12 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
]);
|
||||
input.availablePermissions = new IdentifierMap([
|
||||
[ identifier, {
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
[CredentialGroup.agent]: { write: true },
|
||||
public: { read: true, write: false },
|
||||
agent: { write: true },
|
||||
}],
|
||||
[ identifier2, {
|
||||
[CredentialGroup.public]: { read: false },
|
||||
[CredentialGroup.agent]: { write: true },
|
||||
public: { read: false },
|
||||
agent: { write: true },
|
||||
}],
|
||||
]);
|
||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||
@@ -107,8 +106,8 @@ describe('A PermissionBasedAuthorizer', (): void => {
|
||||
]);
|
||||
input.availablePermissions = new IdentifierMap([
|
||||
[ identifier, {
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
[CredentialGroup.agent]: { write: true },
|
||||
public: { read: true, write: false },
|
||||
agent: { write: true },
|
||||
}],
|
||||
]);
|
||||
await expect(authorizer.handle(input)).rejects.toThrow(UnauthorizedHttpError);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { PermissionReader, PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
||||
import type { PermissionSet } from '../../../src/authorization/permissions/Permissions';
|
||||
import { AccessMode } from '../../../src/authorization/permissions/Permissions';
|
||||
@@ -31,45 +30,45 @@ describe('A UnionPermissionReader', (): 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].handle.mockResolvedValue(
|
||||
new IdentifierMap([[ identifier, { [CredentialGroup.agent]: { read: true }}]]),
|
||||
new IdentifierMap([[ identifier, { agent: { read: true }}]]),
|
||||
);
|
||||
readers[1].handle.mockResolvedValue(
|
||||
new IdentifierMap([[ identifier, { [CredentialGroup.agent]: { write: true }}]]),
|
||||
new IdentifierMap([[ identifier, { agent: { write: true }}]]),
|
||||
);
|
||||
compareMaps(await unionReader.handle(input),
|
||||
new IdentifierMap([[ identifier, { [CredentialGroup.agent]: { write: true }}]]));
|
||||
new IdentifierMap([[ identifier, { agent: { write: true }}]]));
|
||||
});
|
||||
|
||||
it('combines results.', async(): Promise<void> => {
|
||||
const identifier2 = { path: 'http://example.com/foo2' };
|
||||
const identifier3 = { path: 'http://example.com/foo3' };
|
||||
readers[0].handle.mockResolvedValue(new IdentifierMap([
|
||||
[ identifier, { [CredentialGroup.agent]: { read: true }, [CredentialGroup.public]: undefined }],
|
||||
[ identifier2, { [CredentialGroup.agent]: { write: true }}],
|
||||
[ identifier3, { [CredentialGroup.agent]: { append: false }, [CredentialGroup.public]: { delete: true }}],
|
||||
[ identifier, { agent: { read: true }, public: undefined }],
|
||||
[ identifier2, { agent: { write: true }}],
|
||||
[ identifier3, { agent: { append: false }, public: { delete: true }}],
|
||||
]));
|
||||
readers[1].handle.mockResolvedValue(new IdentifierMap<PermissionSet>([
|
||||
[ identifier, { [CredentialGroup.agent]: { write: true }, [CredentialGroup.public]: { read: false }}],
|
||||
[ identifier2, { [CredentialGroup.public]: { read: false }}],
|
||||
[ identifier, { agent: { write: true }, public: { read: false }}],
|
||||
[ identifier2, { public: { read: false }}],
|
||||
]));
|
||||
compareMaps(await unionReader.handle(input), new IdentifierMap([
|
||||
[ identifier, { [CredentialGroup.agent]: { read: true, write: true }, [CredentialGroup.public]: { read: false }}],
|
||||
[ identifier2, { [CredentialGroup.agent]: { write: true }, [CredentialGroup.public]: { read: false }}],
|
||||
[ identifier3, { [CredentialGroup.agent]: { append: false }, [CredentialGroup.public]: { delete: true }}],
|
||||
[ identifier, { agent: { read: true, write: true }, public: { read: false }}],
|
||||
[ identifier2, { agent: { write: true }, public: { read: false }}],
|
||||
[ identifier3, { agent: { append: false }, public: { delete: true }}],
|
||||
]));
|
||||
});
|
||||
|
||||
it('merges same fields using false > true > undefined.', async(): Promise<void> => {
|
||||
readers[0].handle.mockResolvedValue(new IdentifierMap(
|
||||
[[ identifier,
|
||||
{ [CredentialGroup.agent]: { 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(
|
||||
[[ identifier, { [CredentialGroup.agent]:
|
||||
[[ identifier, { agent:
|
||||
{ read: false, write: true, append: true, create: true, delete: undefined }}]],
|
||||
));
|
||||
compareMaps(await unionReader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.agent]: { read: false, write: false, append: true, create: true },
|
||||
agent: { read: false, write: false, append: true, create: true },
|
||||
}]]));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { DataFactory } from 'n3';
|
||||
import type { CredentialSet } from '../../../src/authentication/Credentials';
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { Credentials } from '../../../src/authentication/Credentials';
|
||||
import type { AccessChecker } from '../../../src/authorization/access/AccessChecker';
|
||||
import type { PermissionReaderInput } from '../../../src/authorization/PermissionReader';
|
||||
import { AclMode } from '../../../src/authorization/permissions/AclPermission';
|
||||
@@ -36,14 +35,14 @@ describe('A WebAclReader', (): void => {
|
||||
let resourceSet: jest.Mocked<ResourceSet>;
|
||||
let store: jest.Mocked<ResourceStore>;
|
||||
const identifierStrategy = new SingleRootIdentifierStrategy('http://example.com/');
|
||||
let credentials: CredentialSet;
|
||||
let credentials: Credentials;
|
||||
let identifier: ResourceIdentifier;
|
||||
let accessMap: AccessMap;
|
||||
let input: PermissionReaderInput;
|
||||
let accessChecker: jest.Mocked<AccessChecker>;
|
||||
|
||||
beforeEach(async(): Promise<void> => {
|
||||
credentials = { [CredentialGroup.public]: {}, [CredentialGroup.agent]: {}};
|
||||
credentials = { agent: { webId: 'http://example.com/#me' }};
|
||||
identifier = { path: 'http://example.com/foo' };
|
||||
|
||||
accessMap = new IdentifierSetMultiMap([
|
||||
@@ -79,8 +78,8 @@ describe('A WebAclReader', (): void => {
|
||||
it('returns undefined permissions for undefined credentials.', async(): Promise<void> => {
|
||||
input.credentials = {};
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: {},
|
||||
public: {},
|
||||
agent: {},
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -92,8 +91,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
], INTERNAL_QUADS));
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
public: { read: true },
|
||||
agent: { read: true },
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -105,8 +104,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
], INTERNAL_QUADS));
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: {},
|
||||
[CredentialGroup.agent]: {},
|
||||
public: {},
|
||||
agent: {},
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -119,8 +118,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}fakeMode1`)),
|
||||
], INTERNAL_QUADS));
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
public: { read: true },
|
||||
agent: { read: true },
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -133,8 +132,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth'), nn(`${acl}mode`), nn(`${acl}Read`)),
|
||||
], INTERNAL_QUADS));
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { read: true },
|
||||
public: { read: true },
|
||||
agent: { read: true },
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -150,8 +149,8 @@ describe('A WebAclReader', (): void => {
|
||||
quad(nn('auth2'), nn(`${acl}mode`), nn(`${acl}Append`)),
|
||||
], INTERNAL_QUADS));
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([[ identifier, {
|
||||
[CredentialGroup.public]: { append: true },
|
||||
[CredentialGroup.agent]: { append: true },
|
||||
public: { append: true },
|
||||
agent: { append: true },
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -172,8 +171,8 @@ describe('A WebAclReader', (): void => {
|
||||
it('ignores rules where no access is granted.', async(): Promise<void> => {
|
||||
credentials.agent = { webId: 'http://test.com/user' };
|
||||
// CredentialGroup.public gets true on auth1, CredentialGroup.agent on auth2
|
||||
accessChecker.handleSafe.mockImplementation(async({ rule, credential: cred }): Promise<boolean> =>
|
||||
(rule.value === 'auth1') === !cred.webId);
|
||||
accessChecker.handleSafe.mockImplementation(async({ rule, credentials: cred }): Promise<boolean> =>
|
||||
(rule.value === 'auth1') === !cred.agent?.webId);
|
||||
|
||||
store.getRepresentation.mockResolvedValue(new BasicRepresentation([
|
||||
quad(nn('auth1'), nn(`${rdf}type`), nn(`${acl}Authorization`)),
|
||||
@@ -185,8 +184,8 @@ describe('A WebAclReader', (): void => {
|
||||
], INTERNAL_QUADS));
|
||||
|
||||
compareMaps(await reader.handle(input), new IdentifierMap<PermissionSet>([[ identifier, {
|
||||
[CredentialGroup.public]: { read: true },
|
||||
[CredentialGroup.agent]: { append: true },
|
||||
public: { read: true },
|
||||
agent: { append: true },
|
||||
}]]));
|
||||
});
|
||||
|
||||
@@ -225,9 +224,9 @@ describe('A WebAclReader', (): void => {
|
||||
input.requestedModes.set(identifier3, new Set([ AccessMode.append ]));
|
||||
|
||||
compareMaps(await reader.handle(input), new IdentifierMap([
|
||||
[ identifier, { [CredentialGroup.public]: { read: true }, [CredentialGroup.agent]: { read: true }}],
|
||||
[ identifier2, { [CredentialGroup.public]: { read: true }, [CredentialGroup.agent]: { read: true }}],
|
||||
[ identifier3, { [CredentialGroup.public]: { append: true }, [CredentialGroup.agent]: { append: true }}],
|
||||
[ identifier, { public: { read: true }, agent: { read: true }}],
|
||||
[ identifier2, { public: { read: true }, agent: { read: true }}],
|
||||
[ identifier3, { public: { append: true }, agent: { append: true }}],
|
||||
]));
|
||||
// http://example.com/.acl and http://example.com/bar/.acl
|
||||
expect(store.getRepresentation).toHaveBeenCalledTimes(2);
|
||||
|
||||
@@ -16,17 +16,17 @@ describe('A AgentAccessChecker', (): void => {
|
||||
});
|
||||
|
||||
it('returns true if a match is found for the given WebID.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credential: { webId }};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credentials: { agent: { webId }}};
|
||||
await expect(checker.handle(input)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if no match is found.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: { webId }};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: { agent: { webId }}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if the credentials contain no WebID.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credential: {}};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('match'), credentials: {}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -16,22 +16,22 @@ describe('An AgentClassAccessChecker', (): void => {
|
||||
});
|
||||
|
||||
it('returns true if the rule contains foaf:agent as supported class.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('agentMatch'), credential: {}};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('agentMatch'), credentials: {}};
|
||||
await expect(checker.handle(input)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('returns true for authenticated users with an acl:AuthenticatedAgent rule.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credential: { webId }};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credentials: { agent: { webId }}};
|
||||
await expect(checker.handle(input)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('returns false for unauthenticated users with an acl:AuthenticatedAgent rule.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credential: {}};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('authenticatedMatch'), credentials: {}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if no class rule is found.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: {}};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: {}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -33,17 +33,17 @@ describe('An AgentGroupAccessChecker', (): void => {
|
||||
});
|
||||
|
||||
it('returns true if the WebID is a valid group member.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: { webId }};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credentials: { agent: { webId }}};
|
||||
await expect(checker.handle(input)).resolves.toBe(true);
|
||||
});
|
||||
|
||||
it('returns false if the WebID is not a valid group member.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credential: { webId }};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('noMatch'), credentials: { agent: { webId }}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('returns false if there are no WebID credentials.', async(): Promise<void> => {
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credential: {}};
|
||||
const input: AccessCheckerArgs = { acl, rule: namedNode('groupMatch'), credentials: {}};
|
||||
await expect(checker.handle(input)).resolves.toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'jest-rdf';
|
||||
import { CredentialGroup } from '../../../../../src/authentication/Credentials';
|
||||
import type { AclPermission } from '../../../../../src/authorization/permissions/AclPermission';
|
||||
import { WebAclMetadataCollector } from '../../../../../src/http/ldp/metadata/WebAclMetadataCollector';
|
||||
import type { Operation } from '../../../../../src/http/Operation';
|
||||
@@ -45,7 +44,7 @@ describe('A WebAclMetadataCollector', (): void => {
|
||||
|
||||
it('adds no metadata if the method is wrong.', async(): Promise<void> => {
|
||||
operation.availablePermissions = new IdentifierMap(
|
||||
[[ target, { [CredentialGroup.public]: { read: true, write: false }}]],
|
||||
[[ target, { public: { read: true, write: false }}]],
|
||||
);
|
||||
operation.method = 'DELETE';
|
||||
await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined();
|
||||
@@ -54,8 +53,8 @@ describe('A WebAclMetadataCollector', (): void => {
|
||||
|
||||
it('adds corresponding metadata for all permissions present.', async(): Promise<void> => {
|
||||
operation.availablePermissions = new IdentifierMap([[ target, {
|
||||
[CredentialGroup.agent]: { read: true, write: true, control: false } as AclPermission,
|
||||
[CredentialGroup.public]: { read: true, write: false },
|
||||
agent: { read: true, write: true, control: false } as AclPermission,
|
||||
public: { read: true, write: false },
|
||||
}]]);
|
||||
await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined();
|
||||
expect(metadata.quads()).toHaveLength(3);
|
||||
@@ -65,8 +64,8 @@ describe('A WebAclMetadataCollector', (): void => {
|
||||
|
||||
it('ignores unknown modes.', async(): Promise<void> => {
|
||||
operation.availablePermissions = new IdentifierMap([[ target, {
|
||||
[CredentialGroup.agent]: { read: true, create: true },
|
||||
[CredentialGroup.public]: { read: true },
|
||||
agent: { read: true, create: true },
|
||||
public: { read: true },
|
||||
}]]);
|
||||
await expect(writer.handle({ metadata, operation })).resolves.toBeUndefined();
|
||||
expect(metadata.quads()).toHaveLength(2);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { CredentialGroup } from '../../../src/authentication/Credentials';
|
||||
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
|
||||
import type { Authorizer } from '../../../src/authorization/Authorizer';
|
||||
import type { PermissionReader } from '../../../src/authorization/PermissionReader';
|
||||
@@ -15,11 +14,11 @@ import { ForbiddenHttpError } from '../../../src/util/errors/ForbiddenHttpError'
|
||||
import { IdentifierMap, IdentifierSetMultiMap } from '../../../src/util/map/IdentifierMap';
|
||||
|
||||
describe('An AuthorizingHttpHandler', (): void => {
|
||||
const credentials = { [CredentialGroup.public]: {}};
|
||||
const credentials = { };
|
||||
const target = { path: 'http://test.com/foo' };
|
||||
const requestedModes: AccessMap = new IdentifierSetMultiMap<AccessMode>([[ target, AccessMode.read ]]);
|
||||
const availablePermissions: PermissionMap = new IdentifierMap(
|
||||
[[ target, { [CredentialGroup.public]: { read: true }}]],
|
||||
[[ target, { public: { read: true }}]],
|
||||
);
|
||||
const request: HttpRequest = {} as any;
|
||||
const response: HttpResponse = {} as any;
|
||||
|
||||
Reference in New Issue
Block a user