From 0d6b895df361fef2dd79208605f3610a756ab178 Mon Sep 17 00:00:00 2001 From: Joachim Van Herwegen Date: Fri, 20 Jan 2023 13:43:47 +0100 Subject: [PATCH] feat: Hash lock-related identifiers --- .../storage/key-value/storages/storages.json | 2 +- src/index.ts | 1 + src/storage/keyvalue/EncodingPathStorage.ts | 8 ++--- .../keyvalue/HashEncodingPathStorage.ts | 28 +++++++++++++++++ .../keyvalue/HashEncodingPathStorage.test.ts | 31 +++++++++++++++++++ 5 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 src/storage/keyvalue/HashEncodingPathStorage.ts create mode 100644 test/unit/storage/keyvalue/HashEncodingPathStorage.test.ts diff --git a/config/storage/key-value/storages/storages.json b/config/storage/key-value/storages/storages.json index b3fd70674..002675f67 100644 --- a/config/storage/key-value/storages/storages.json +++ b/config/storage/key-value/storages/storages.json @@ -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" } }, diff --git a/src/index.ts b/src/index.ts index 48f5cf3b2..bb5579513 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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'; diff --git a/src/storage/keyvalue/EncodingPathStorage.ts b/src/storage/keyvalue/EncodingPathStorage.ts index d92905bc7..da79e351e 100644 --- a/src/storage/keyvalue/EncodingPathStorage.ts +++ b/src/storage/keyvalue/EncodingPathStorage.ts @@ -10,8 +10,8 @@ import type { KeyValueStorage } from './KeyValueStorage'; * without having to worry about cleaning the input keys. */ export class EncodingPathStorage implements KeyValueStorage { - private readonly basePath: string; - private readonly source: KeyValueStorage; + protected readonly basePath: string; + protected readonly source: KeyValueStorage; public constructor(relativePath: string, source: KeyValueStorage) { this.source = source; @@ -53,7 +53,7 @@ export class EncodingPathStorage implements KeyValueStorage { /** * 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 implements KeyValueStorage { /** * 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'); } diff --git a/src/storage/keyvalue/HashEncodingPathStorage.ts b/src/storage/keyvalue/HashEncodingPathStorage.ts new file mode 100644 index 000000000..87e2d1f9e --- /dev/null +++ b/src/storage/keyvalue/HashEncodingPathStorage.ts @@ -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 extends EncodingPathStorage { + 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.'); + } +} diff --git a/test/unit/storage/keyvalue/HashEncodingPathStorage.test.ts b/test/unit/storage/keyvalue/HashEncodingPathStorage.test.ts new file mode 100644 index 000000000..41583ba30 --- /dev/null +++ b/test/unit/storage/keyvalue/HashEncodingPathStorage.test.ts @@ -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; + let source: KeyValueStorage; + let storage: HashEncodingPathStorage; + + beforeEach(async(): Promise => { + map = new Map(); + source = map as any; + storage = new HashEncodingPathStorage(relativePath, source); + }); + + it('hashes the keys.', async(): Promise => { + 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 => { + await storage.set('key', 'data'); + await expect(storage.entries().next()).rejects.toThrow(NotImplementedHttpError); + }); +});