mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Fix ensure hashed directory for extension (#7188)
* Fix ensure hashed directory for extension Earlier open lens versions stored extension_data directory based on path of extension's package.json. This causes problems because extensions have moved to new location. This ensures backward compatibility that extension will get the same directory than before the change. Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Reconstruct the key for old hashed directories Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Remove unnecessary return Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Use sync version of random bytes Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Add migration to new type of key Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Fix global override for random bytes to not return a promise Signed-off-by: Juho Heikka <juho.heikka@gmail.com> * Make registeredExtensions a dependency Signed-off-by: Juho Heikka <juho.heikka@gmail.com> --------- Signed-off-by: Juho Heikka <juho.heikka@gmail.com>
This commit is contained in:
parent
b3ae5a0446
commit
d8acff67d1
@ -6,7 +6,7 @@
|
||||
import { getGlobalOverride } from "../test-utils/get-global-override";
|
||||
import randomBytesInjectable from "./random-bytes.injectable";
|
||||
|
||||
export default getGlobalOverride(randomBytesInjectable, () => async (size) => {
|
||||
export default getGlobalOverride(randomBytesInjectable, () => (size) => {
|
||||
const res = Buffer.alloc(size);
|
||||
|
||||
for (let i = 0; i < size; i += 1) {
|
||||
|
||||
@ -4,13 +4,12 @@
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { randomBytes } from "crypto";
|
||||
import { promisify } from "util";
|
||||
|
||||
export type RandomBytes = (size: number) => Promise<Buffer>;
|
||||
export type RandomBytes = (size: number) => Buffer;
|
||||
|
||||
const randomBytesInjectable = getInjectable({
|
||||
id: "random-bytes",
|
||||
instantiate: (): RandomBytes => promisify(randomBytes),
|
||||
instantiate: (): RandomBytes => randomBytes,
|
||||
causesSideEffects: true,
|
||||
});
|
||||
|
||||
|
||||
@ -3,17 +3,18 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
|
||||
import { getOrInsertWithAsync } from "../../../common/utils";
|
||||
import { getOrInsert } from "../../../common/utils";
|
||||
import randomBytesInjectable from "../../../common/utils/random-bytes.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||
import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||
import getHashInjectable from "./get-hash.injectable";
|
||||
import getPathToLegacyPackageJsonInjectable from "./get-path-to-legacy-package-json.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
export type EnsureHashedDirectoryForExtension = (extensionName: string, registeredExtensions: ObservableMap<string, string>) => Promise<string>;
|
||||
export type EnsureHashedDirectoryForExtension = (extensionName: string) => Promise<string>;
|
||||
|
||||
const ensureHashedDirectoryForExtensionInjectable = getInjectable({
|
||||
id: "ensure-hashed-directory-for-extension",
|
||||
@ -24,14 +25,27 @@ const ensureHashedDirectoryForExtensionInjectable = getInjectable({
|
||||
const directoryForExtensionData = di.inject(directoryForExtensionDataInjectable);
|
||||
const ensureDirectory = di.inject(ensureDirInjectable);
|
||||
const getHash = di.inject(getHashInjectable);
|
||||
const getPathToLegacyPackageJson = di.inject(getPathToLegacyPackageJsonInjectable);
|
||||
const registeredExtensions = di.inject(registeredExtensionsInjectable);
|
||||
|
||||
return async (extensionName, registeredExtensions) => {
|
||||
const dirPath = await getOrInsertWithAsync(registeredExtensions, extensionName, async () => {
|
||||
const salt = (await randomBytes(32)).toString("hex");
|
||||
return async (extensionName) => {
|
||||
let dirPath: string;
|
||||
|
||||
const legacyDirPath = getPathToLegacyPackageJson(extensionName);
|
||||
const hashedDirectoryForLegacyDirPath = registeredExtensions.get(legacyDirPath);
|
||||
|
||||
if (hashedDirectoryForLegacyDirPath) {
|
||||
registeredExtensions.set(extensionName, hashedDirectoryForLegacyDirPath);
|
||||
registeredExtensions.delete(legacyDirPath);
|
||||
dirPath = hashedDirectoryForLegacyDirPath;
|
||||
} else {
|
||||
const salt = randomBytes(32).toString("hex");
|
||||
const hashedName = getHash(`${extensionName}/${salt}`);
|
||||
|
||||
return joinPaths(directoryForExtensionData, hashedName);
|
||||
});
|
||||
const hashedExtensionDirectory = joinPaths(directoryForExtensionData, hashedName);
|
||||
|
||||
dirPath = getOrInsert(registeredExtensions, extensionName, hashedExtensionDirectory);
|
||||
}
|
||||
|
||||
await ensureDirectory(dirPath);
|
||||
|
||||
|
||||
@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { runInAction } from "mobx";
|
||||
import { getDiForUnitTesting } from "../../../main/getDiForUnitTesting";
|
||||
import type { EnsureHashedDirectoryForExtension } from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import ensureDirInjectable from "../../../common/fs/ensure-dir.injectable";
|
||||
import directoryForExtensionDataInjectable from "./directory-for-extension-data.injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
describe("ensure-hashed-directory-for-extension", () => {
|
||||
let ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension;
|
||||
let ensureDirMock: jest.Mock;
|
||||
let registeredExtensions: ObservableMap<string, string>;
|
||||
|
||||
beforeEach(() => {
|
||||
const di = getDiForUnitTesting({ doGeneralOverrides: true });
|
||||
|
||||
ensureDirMock = jest.fn();
|
||||
|
||||
di.override(ensureDirInjectable, () => ensureDirMock);
|
||||
di.override(directoryForExtensionDataInjectable, () => "some-directory-for-extension-data");
|
||||
di.override(directoryForUserDataInjectable, () => "/some-directory-for-user-data");
|
||||
|
||||
ensureHashedDirectoryForExtension = di.inject(
|
||||
ensureHashedDirectoryForExtensionInjectable,
|
||||
);
|
||||
|
||||
registeredExtensions = di.inject(registeredExtensionsInjectable);
|
||||
});
|
||||
|
||||
it("given registered extension exists, returns existing directory", async () => {
|
||||
runInAction(() => {
|
||||
registeredExtensions.set("some-extension-name", "some-directory");
|
||||
});
|
||||
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory");
|
||||
});
|
||||
|
||||
it("given registered extension does not exist, returns random directory", async () => {
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory-for-extension-data/a37a1cfefc0391af3733f23cb6b29443f596a2b8ffe6d116c35df7bc3cd99ef6");
|
||||
});
|
||||
|
||||
describe("given extension directory was saved based on extension's package.json path", () => {
|
||||
beforeEach(() => {
|
||||
runInAction(() => {
|
||||
registeredExtensions.set("/some-directory-for-user-data/node_modules/some-extension-name/package.json", "some-directory");
|
||||
});
|
||||
ensureDirMock.mockClear();
|
||||
});
|
||||
|
||||
it("returns existing directory", async () => {
|
||||
const actual = await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(actual).toBe("some-directory");
|
||||
});
|
||||
|
||||
it("ensure dir is called with some directory", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(ensureDirMock).toHaveBeenCalledWith("some-directory");
|
||||
});
|
||||
|
||||
it("is migrated to use the extension name as key", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(registeredExtensions.get("some-extension-name")).toEqual("some-directory");
|
||||
});
|
||||
|
||||
it("old key is removed", async () => {
|
||||
await ensureHashedDirectoryForExtension(
|
||||
"some-extension-name",
|
||||
);
|
||||
|
||||
expect(registeredExtensions.has("/some-directory-for-user-data/node_modules/some-extension-name/package.json")).toEqual(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectionToken } from "@ogre-tools/injectable";
|
||||
import type { MigrationDeclaration } from "../../../common/base-store/migrations.injectable";
|
||||
|
||||
export const fileSystemProvisionerStoreInjectionToken = getInjectionToken<MigrationDeclaration>({
|
||||
id: "file-system-provisioner-store-injection-token",
|
||||
});
|
||||
@ -14,6 +14,7 @@ import { persistStateToConfigInjectionToken } from "../../../common/base-store/s
|
||||
import getBasenameOfPathInjectable from "../../../common/path/get-basename.injectable";
|
||||
import { enlistMessageChannelListenerInjectionToken } from "../../../common/utils/channel/enlist-message-channel-listener-injection-token";
|
||||
import ensureHashedDirectoryForExtensionInjectable from "./ensure-hashed-directory-for-extension.injectable";
|
||||
import { registeredExtensionsInjectable } from "./registered-extensions.injectable";
|
||||
|
||||
const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
id: "file-system-provisioner-store",
|
||||
@ -30,6 +31,7 @@ const fileSystemProvisionerStoreInjectable = getInjectable({
|
||||
enlistMessageChannelListener: di.inject(enlistMessageChannelListenerInjectionToken),
|
||||
shouldDisableSyncInListener: di.inject(shouldBaseStoreDisableSyncInIpcListenerInjectionToken),
|
||||
ensureHashedDirectoryForExtension: di.inject(ensureHashedDirectoryForExtensionInjectable),
|
||||
registeredExtensions: di.inject(registeredExtensionsInjectable),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
|
||||
import { action, makeObservable, observable } from "mobx";
|
||||
import type { ObservableMap } from "mobx";
|
||||
import { action, makeObservable } from "mobx";
|
||||
import type { BaseStoreDependencies } from "../../../common/base-store/base-store";
|
||||
import { BaseStore } from "../../../common/base-store/base-store";
|
||||
import type { LensExtensionId } from "../../lens-extension";
|
||||
@ -16,11 +17,10 @@ interface FSProvisionModel {
|
||||
|
||||
interface Dependencies extends BaseStoreDependencies {
|
||||
ensureHashedDirectoryForExtension: EnsureHashedDirectoryForExtension;
|
||||
registeredExtensions: ObservableMap<LensExtensionId, string>;
|
||||
}
|
||||
|
||||
export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
readonly registeredExtensions = observable.map<LensExtensionId, string>();
|
||||
|
||||
constructor(protected readonly dependencies: Dependencies) {
|
||||
super(dependencies, {
|
||||
configName: "lens-filesystem-provisioner-store",
|
||||
@ -37,17 +37,17 @@ export class FileSystemProvisionerStore extends BaseStore<FSProvisionModel> {
|
||||
* @returns path to the folder that the extension can safely write files to.
|
||||
*/
|
||||
async requestDirectory(extensionName: string): Promise<string> {
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(extensionName, this.registeredExtensions);
|
||||
return this.dependencies.ensureHashedDirectoryForExtension(extensionName);
|
||||
}
|
||||
|
||||
@action
|
||||
protected fromStore({ extensions }: FSProvisionModel = { extensions: {}}): void {
|
||||
this.registeredExtensions.merge(extensions);
|
||||
this.dependencies.registeredExtensions.merge(extensions);
|
||||
}
|
||||
|
||||
toJSON(): FSProvisionModel {
|
||||
return toJS({
|
||||
extensions: Object.fromEntries(this.registeredExtensions),
|
||||
extensions: Object.fromEntries(this.dependencies.registeredExtensions),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import directoryForUserDataInjectable from "../../../common/app-paths/directory-for-user-data/directory-for-user-data.injectable";
|
||||
import joinPathsInjectable from "../../../common/path/join-paths.injectable";
|
||||
|
||||
export type GetPathToLegacyPackageJson = (extensionName: string) => string;
|
||||
|
||||
const getPathToLegacyPackageJson = getInjectable({
|
||||
id: "get-path-to-legacy-package-json",
|
||||
|
||||
instantiate: (di): GetPathToLegacyPackageJson => {
|
||||
const directoryForUserData = di.inject(directoryForUserDataInjectable);
|
||||
const joinPaths = di.inject(joinPathsInjectable);
|
||||
|
||||
return (extensionName: string) => joinPaths(directoryForUserData, "node_modules", extensionName, "package.json");
|
||||
},
|
||||
});
|
||||
|
||||
export default getPathToLegacyPackageJson;
|
||||
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Copyright (c) OpenLens Authors. All rights reserved.
|
||||
* Licensed under MIT License. See LICENSE in root directory for more information.
|
||||
*/
|
||||
import { getInjectable } from "@ogre-tools/injectable";
|
||||
import { observable } from "mobx";
|
||||
import type { LensExtensionId } from "../../lens-extension";
|
||||
|
||||
export const registeredExtensionsInjectable = getInjectable({
|
||||
id: "registered-extensions",
|
||||
instantiate: () => observable.map<LensExtensionId, string>(),
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user