mirror of
https://github.com/CommunitySolidServer/CommunitySolidServer.git
synced 2024-10-03 14:55:10 +00:00
Merge branch 'main' into versions/6.0.0
This commit is contained in:
34
src/authorization/permissions/CreateModesExtractor.ts
Normal file
34
src/authorization/permissions/CreateModesExtractor.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import type { Operation } from '../../http/Operation';
|
||||
import type { ResourceSet } from '../../storage/ResourceSet';
|
||||
import { ModesExtractor } from './ModesExtractor';
|
||||
import type { AccessMap } from './Permissions';
|
||||
import { AccessMode } from './Permissions';
|
||||
|
||||
/**
|
||||
* Adds the `create` access mode to the result of the source in case the target resource does not exist.
|
||||
*/
|
||||
export class CreateModesExtractor extends ModesExtractor {
|
||||
private readonly source: ModesExtractor;
|
||||
private readonly resourceSet: ResourceSet;
|
||||
|
||||
public constructor(source: ModesExtractor, resourceSet: ResourceSet) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.resourceSet = resourceSet;
|
||||
}
|
||||
|
||||
public async canHandle(operation: Operation): Promise<void> {
|
||||
await this.source.canHandle(operation);
|
||||
}
|
||||
|
||||
public async handle(operation: Operation): Promise<AccessMap> {
|
||||
const accessMap = await this.source.handle(operation);
|
||||
|
||||
if (!accessMap.hasEntry(operation.target, AccessMode.create) &&
|
||||
!await this.resourceSet.hasResource(operation.target)) {
|
||||
accessMap.add(operation.target, AccessMode.create);
|
||||
}
|
||||
|
||||
return accessMap;
|
||||
}
|
||||
}
|
||||
44
src/authorization/permissions/DeleteParentExtractor.ts
Normal file
44
src/authorization/permissions/DeleteParentExtractor.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import type { Operation } from '../../http/Operation';
|
||||
import type { ResourceSet } from '../../storage/ResourceSet';
|
||||
import type { IdentifierStrategy } from '../../util/identifiers/IdentifierStrategy';
|
||||
import { ModesExtractor } from './ModesExtractor';
|
||||
import type { AccessMap } from './Permissions';
|
||||
import { AccessMode } from './Permissions';
|
||||
|
||||
/**
|
||||
* In case a resource is being deleted but does not exist,
|
||||
* the server response code depends on the access modes the agent has on the parent container.
|
||||
* In case the agent has read access on the parent container, a 404 should be returned,
|
||||
* otherwise it should be 401/403.
|
||||
*
|
||||
* This class adds support for this by requiring read access on the parent container
|
||||
* in case the target resource does not exist.
|
||||
*/
|
||||
export class DeleteParentExtractor extends ModesExtractor {
|
||||
private readonly source: ModesExtractor;
|
||||
private readonly resourceSet: ResourceSet;
|
||||
private readonly identifierStrategy: IdentifierStrategy;
|
||||
|
||||
public constructor(source: ModesExtractor, resourceSet: ResourceSet, identifierStrategy: IdentifierStrategy) {
|
||||
super();
|
||||
this.source = source;
|
||||
this.resourceSet = resourceSet;
|
||||
this.identifierStrategy = identifierStrategy;
|
||||
}
|
||||
|
||||
public async canHandle(operation: Operation): Promise<void> {
|
||||
await this.source.canHandle(operation);
|
||||
}
|
||||
|
||||
public async handle(operation: Operation): Promise<AccessMap> {
|
||||
const accessMap = await this.source.handle(operation);
|
||||
const { target } = operation;
|
||||
if (accessMap.get(target)?.has(AccessMode.delete) &&
|
||||
!this.identifierStrategy.isRootContainer(target) &&
|
||||
!await this.resourceSet.hasResource(target)) {
|
||||
const parent = this.identifierStrategy.getParentContainer(target);
|
||||
accessMap.add(parent, new Set([ AccessMode.read ]));
|
||||
}
|
||||
return accessMap;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ export * from './authorization/access/AgentGroupAccessChecker';
|
||||
|
||||
// Authorization/Permissions
|
||||
export * from './authorization/permissions/AclPermission';
|
||||
export * from './authorization/permissions/CreateModesExtractor';
|
||||
export * from './authorization/permissions/DeleteParentExtractor';
|
||||
export * from './authorization/permissions/IntermediateCreateExtractor';
|
||||
export * from './authorization/permissions/ModesExtractor';
|
||||
export * from './authorization/permissions/MethodModesExtractor';
|
||||
|
||||
@@ -17,6 +17,17 @@ const attemptDefaults: Required<AttemptSettings> = { retryCount: -1, retryDelay:
|
||||
const PREFIX_RW = '__RW__';
|
||||
const PREFIX_LOCK = '__L__';
|
||||
|
||||
export interface RedisSettings {
|
||||
/* Override default namespacePrefixes (used to prefix keys in Redis) */
|
||||
namespacePrefix: string;
|
||||
/* Username used for AUTH on the Redis server */
|
||||
username?: string;
|
||||
/* Password used for AUTH on the Redis server */
|
||||
password?: string;
|
||||
/* The number of the database to use */
|
||||
db?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Redis Locker that can be used as both:
|
||||
* * a Read Write Locker that uses a (single) Redis server to store the locks and counts.
|
||||
@@ -51,11 +62,24 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
private readonly redisRw: RedisReadWriteLock;
|
||||
private readonly redisLock: RedisResourceLock;
|
||||
private readonly attemptSettings: Required<AttemptSettings>;
|
||||
private readonly namespacePrefix: string;
|
||||
private finalized = false;
|
||||
|
||||
public constructor(redisClient = '127.0.0.1:6379', attemptSettings: AttemptSettings = {}) {
|
||||
this.redis = this.createRedisClient(redisClient);
|
||||
/**
|
||||
* Creates a new RedisClient
|
||||
* @param redisClient - Redis connection string of a standalone Redis node
|
||||
* @param attemptSettings - Override default AttemptSettings
|
||||
* @param redisSettings - Addition settings used to create the Redis client or to interact with the Redis server
|
||||
*/
|
||||
public constructor(
|
||||
redisClient = '127.0.0.1:6379',
|
||||
attemptSettings: AttemptSettings = {},
|
||||
redisSettings: RedisSettings = { namespacePrefix: '' },
|
||||
) {
|
||||
const { namespacePrefix, ...options } = redisSettings;
|
||||
this.redis = this.createRedisClient(redisClient, options);
|
||||
this.attemptSettings = { ...attemptDefaults, ...attemptSettings };
|
||||
this.namespacePrefix = namespacePrefix;
|
||||
|
||||
// Register lua scripts
|
||||
for (const [ name, script ] of Object.entries(REDIS_LUA_SCRIPTS)) {
|
||||
@@ -71,7 +95,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
* @param redisClientString - A string that contains either a host address and a
|
||||
* port number like '127.0.0.1:6379' or just a port number like '6379'.
|
||||
*/
|
||||
private createRedisClient(redisClientString: string): Redis {
|
||||
private createRedisClient(redisClientString: string, options: Omit<RedisSettings, 'namespacePrefix'>): Redis {
|
||||
if (redisClientString.length > 0) {
|
||||
// Check if port number or ip with port number
|
||||
// Definitely not perfect, but configuring this is only for experienced users
|
||||
@@ -83,7 +107,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
}
|
||||
const port = Number(match[2]);
|
||||
const host = match[1];
|
||||
return new Redis(port, host);
|
||||
return new Redis(port, host, options);
|
||||
}
|
||||
throw new Error(`Empty redisClientString provided!\n
|
||||
Please provide a port number like '6379' or a host address and a port number like '127.0.0.1:6379'`);
|
||||
@@ -95,7 +119,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
* @returns A scoped Redis key that allows cleanup afterwards without affecting other keys.
|
||||
*/
|
||||
private getReadWriteKey(identifier: ResourceIdentifier): string {
|
||||
return `${PREFIX_RW}${identifier.path}`;
|
||||
return `${this.namespacePrefix}${PREFIX_RW}${identifier.path}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +128,7 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
* @returns A scoped Redis key that allows cleanup afterwards without affecting other keys.
|
||||
*/
|
||||
private getResourceKey(identifier: ResourceIdentifier): string {
|
||||
return `${PREFIX_LOCK}${identifier.path}`;
|
||||
return `${this.namespacePrefix}${PREFIX_LOCK}${identifier.path}`;
|
||||
}
|
||||
|
||||
/* ReadWriteLocker methods */
|
||||
@@ -200,12 +224,12 @@ export class RedisLocker implements ReadWriteLocker, ResourceLocker, Initializab
|
||||
* Remove any lock still open
|
||||
*/
|
||||
private async clearLocks(): Promise<void> {
|
||||
const keysRw = await this.redisRw.keys(`${PREFIX_RW}*`);
|
||||
const keysRw = await this.redisRw.keys(`${this.namespacePrefix}${PREFIX_RW}*`);
|
||||
if (keysRw.length > 0) {
|
||||
await this.redisRw.del(...keysRw);
|
||||
}
|
||||
|
||||
const keysLock = await this.redisLock.keys(`${PREFIX_LOCK}*`);
|
||||
const keysLock = await this.redisLock.keys(`${this.namespacePrefix}${PREFIX_LOCK}*`);
|
||||
if (keysLock.length > 0) {
|
||||
await this.redisLock.del(...keysLock);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user