feat: Hash lock-related identifiers

This commit is contained in:
Joachim Van Herwegen
2023-01-20 13:43:47 +01:00
parent 7dc5eede43
commit 0d6b895df3
5 changed files with 65 additions and 5 deletions

View File

@@ -4,7 +4,7 @@
{
"comment": "Used for internal storage by the locker.",
"@id": "urn:solid-server:default:LockStorage",
"@type": "EncodingPathStorage",
"@type": "HashEncodingPathStorage",
"relativePath": "/locks/",
"source": { "@id": "urn:solid-server:default:BackendKeyValueStorage" }
},

View File

@@ -388,6 +388,7 @@ export * from './storage/conversion/TypedRepresentationConverter';
// Storage/KeyValue
export * from './storage/keyvalue/EncodingPathStorage';
export * from './storage/keyvalue/ExpiringStorage';
export * from './storage/keyvalue/HashEncodingPathStorage';
export * from './storage/keyvalue/JsonFileStorage';
export * from './storage/keyvalue/JsonResourceStorage';
export * from './storage/keyvalue/KeyValueStorage';

View File

@@ -10,8 +10,8 @@ import type { KeyValueStorage } from './KeyValueStorage';
* without having to worry about cleaning the input keys.
*/
export class EncodingPathStorage<T> implements KeyValueStorage<string, T> {
private readonly basePath: string;
private readonly source: KeyValueStorage<string, T>;
protected readonly basePath: string;
protected readonly source: KeyValueStorage<string, T>;
public constructor(relativePath: string, source: KeyValueStorage<string, T>) {
this.source = source;
@@ -53,7 +53,7 @@ export class EncodingPathStorage<T> implements KeyValueStorage<string, T> {
/**
* Converts a key into a path for internal storage.
*/
private keyToPath(key: string): string {
protected keyToPath(key: string): string {
const encodedKey = Buffer.from(key).toString('base64');
return joinUrl(this.basePath, encodedKey);
}
@@ -61,7 +61,7 @@ export class EncodingPathStorage<T> implements KeyValueStorage<string, T> {
/**
* Converts an internal storage path string into the original path key.
*/
private pathToKey(path: string): string {
protected pathToKey(path: string): string {
const buffer = Buffer.from(path.slice(this.basePath.length), 'base64');
return buffer.toString('utf-8');
}

View File

@@ -0,0 +1,28 @@
import { createHash } from 'crypto';
import { getLoggerFor } from '../../logging/LogUtil';
import { NotImplementedHttpError } from '../../util/errors/NotImplementedHttpError';
import { joinUrl } from '../../util/PathUtil';
import { EncodingPathStorage } from './EncodingPathStorage';
/**
* A variant of the {@link EncodingPathStorage} that hashes the key instead of converting to base64 encoding.
*
* This class was created specifically to prevent the issue of identifiers being too long when storing data:
* https://github.com/CommunitySolidServer/CommunitySolidServer/issues/1013
*
* This should eventually be replaced by a more structural approach once internal storage has been refactored
* and data migration from older versions and formats is supported.
*/
export class HashEncodingPathStorage<T> extends EncodingPathStorage<T> {
protected readonly logger = getLoggerFor(this);
protected keyToPath(key: string): string {
const hash = createHash('sha256').update(key).digest('hex');
this.logger.debug(`Hashing key ${key} to ${hash}`);
return joinUrl(this.basePath, hash);
}
protected pathToKey(): string {
throw new NotImplementedHttpError('Hash keys cannot be converted back.');
}
}

View File

@@ -0,0 +1,31 @@
import { HashEncodingPathStorage } from '../../../../src/storage/keyvalue/HashEncodingPathStorage';
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
describe('A HashEncodingPathStorage', (): void => {
const relativePath = '/container/';
let map: Map<string, string>;
let source: KeyValueStorage<string, string>;
let storage: HashEncodingPathStorage<string>;
beforeEach(async(): Promise<void> => {
map = new Map<string, string>();
source = map as any;
storage = new HashEncodingPathStorage<string>(relativePath, source);
});
it('hashes the keys.', async(): Promise<void> => {
const key = 'key';
const hash = '2c70e12b7a0646f92279f427c7b38e7334d8e5389cff167a1dc30e73f826b683';
const data = 'data';
await storage.set(key, data);
expect(map.size).toBe(1);
expect(map.get(`${relativePath}${hash}`)).toBe(data);
await expect(storage.get(key)).resolves.toBe(data);
});
it('errors when paths should be converted back to keys.', async(): Promise<void> => {
await storage.set('key', 'data');
await expect(storage.entries().next()).rejects.toThrow(NotImplementedHttpError);
});
});