mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 01:50:39 +09:00
Compare commits
No commits in common. "ce18c1a9e2e868c3032a0bdffb8bbf13095c9f2d" and "1580e3b7554bff0bfdfe4dd6d2669dd00429d50b" have entirely different histories.
ce18c1a9e2
...
1580e3b755
8 changed files with 11 additions and 168 deletions
|
|
@ -627,16 +627,8 @@ export function createHostClientHandlers(
|
|||
|
||||
return {
|
||||
// Config
|
||||
"config.get": gated("config.get", async (params, context) => {
|
||||
const scopedCompanyId = readNonEmptyString(context?.invocationScope?.companyId);
|
||||
const explicitCompanyId = Object.prototype.hasOwnProperty.call(params ?? {}, "companyId")
|
||||
? params.companyId ?? null
|
||||
: undefined;
|
||||
return services.config.get(
|
||||
explicitCompanyId === undefined
|
||||
? (scopedCompanyId ? { companyId: scopedCompanyId } : {})
|
||||
: { companyId: explicitCompanyId },
|
||||
);
|
||||
"config.get": gated("config.get", async (params) => {
|
||||
return services.config.get(params);
|
||||
}),
|
||||
|
||||
"localFolders.declarations": gated("localFolders.declarations", async (params) => {
|
||||
|
|
|
|||
|
|
@ -514,5 +514,4 @@ describe("startWorkerRpcHost runtime company context", () => {
|
|||
stdout.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,42 +0,0 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { createHostClientHandlers } from "../../../packages/plugins/sdk/src/host-client-factory.js";
|
||||
|
||||
describe("plugin config bridge scoping", () => {
|
||||
it("falls back to the invocation company scope for config.get when the worker omits companyId", async () => {
|
||||
const getConfig = vi.fn(async (params: { companyId?: string | null }) => ({
|
||||
scope: params.companyId ?? null,
|
||||
}));
|
||||
|
||||
const handlers = createHostClientHandlers({
|
||||
pluginId: "test.plugin",
|
||||
capabilities: [],
|
||||
services: {
|
||||
config: { get: getConfig },
|
||||
} as never,
|
||||
});
|
||||
|
||||
await expect(
|
||||
handlers["config.get"]({}, { invocationScope: { companyId: "company-a" } }),
|
||||
).resolves.toEqual({ scope: "company-a" });
|
||||
expect(getConfig).toHaveBeenCalledWith({ companyId: "company-a" });
|
||||
});
|
||||
|
||||
it("preserves an explicit global config.get request even inside a company-scoped invocation", async () => {
|
||||
const getConfig = vi.fn(async (params: { companyId?: string | null }) => ({
|
||||
scope: params.companyId ?? null,
|
||||
}));
|
||||
|
||||
const handlers = createHostClientHandlers({
|
||||
pluginId: "test.plugin",
|
||||
capabilities: [],
|
||||
services: {
|
||||
config: { get: getConfig },
|
||||
} as never,
|
||||
});
|
||||
|
||||
await expect(
|
||||
handlers["config.get"]({ companyId: null }, { invocationScope: { companyId: "company-a" } }),
|
||||
).resolves.toEqual({ scope: null });
|
||||
expect(getConfig).toHaveBeenCalledWith({ companyId: null });
|
||||
});
|
||||
});
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getConfig: vi.fn(),
|
||||
getConfigExactScope: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../services/plugin-registry.js", () => ({
|
||||
pluginRegistryService: () => ({
|
||||
getConfig: mocks.getConfig,
|
||||
getConfigExactScope: mocks.getConfigExactScope,
|
||||
}),
|
||||
}));
|
||||
|
||||
import { buildHostServices } from "../services/plugin-host-services.js";
|
||||
|
||||
function createEventBusStub() {
|
||||
return {
|
||||
forPlugin() {
|
||||
return {
|
||||
emit: vi.fn(),
|
||||
subscribe: vi.fn(),
|
||||
clear: vi.fn(),
|
||||
};
|
||||
},
|
||||
} as any;
|
||||
}
|
||||
|
||||
describe("plugin host services config scoping", () => {
|
||||
beforeEach(() => {
|
||||
mocks.getConfig.mockReset();
|
||||
mocks.getConfigExactScope.mockReset();
|
||||
});
|
||||
|
||||
it("does not fall back to legacy global config for company-scoped reads", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue(null);
|
||||
mocks.getConfig.mockResolvedValue({
|
||||
configJson: { tokenRef: "global-token-ref" },
|
||||
});
|
||||
|
||||
const services = buildHostServices(
|
||||
{} as never,
|
||||
"plugin-record-id",
|
||||
"paperclip.example",
|
||||
createEventBusStub(),
|
||||
);
|
||||
|
||||
await expect(
|
||||
services.config.get({ companyId: "company-a" }),
|
||||
).resolves.toEqual({});
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith("plugin-record-id", "company-a");
|
||||
expect(mocks.getConfig).not.toHaveBeenCalled();
|
||||
|
||||
services.dispose();
|
||||
});
|
||||
|
||||
it("still reads the exact global row for explicit global requests", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue({
|
||||
configJson: { tokenRef: "global-token-ref" },
|
||||
});
|
||||
|
||||
const services = buildHostServices(
|
||||
{} as never,
|
||||
"plugin-record-id",
|
||||
"paperclip.example",
|
||||
createEventBusStub(),
|
||||
);
|
||||
|
||||
await expect(
|
||||
services.config.get({ companyId: null }),
|
||||
).resolves.toEqual({ tokenRef: "global-token-ref" });
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith("plugin-record-id", null);
|
||||
|
||||
services.dispose();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
getConfigExactScope: vi.fn(),
|
||||
getConfig: vi.fn(),
|
||||
resolveSecretValue: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../services/plugin-registry.js", () => ({
|
||||
pluginRegistryService: () => ({
|
||||
getConfigExactScope: mocks.getConfigExactScope,
|
||||
getConfig: mocks.getConfig,
|
||||
}),
|
||||
}));
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ const manifest = {
|
|||
|
||||
describe("createPluginSecretsHandler runtime company scoping", () => {
|
||||
beforeEach(() => {
|
||||
mocks.getConfigExactScope.mockReset();
|
||||
mocks.getConfig.mockReset();
|
||||
mocks.resolveSecretValue.mockReset();
|
||||
});
|
||||
|
||||
|
|
@ -54,12 +54,12 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
await expect(handler.resolve({ secretRef })).rejects.toThrow(
|
||||
PLUGIN_SECRET_REFS_REQUIRE_COMPANY_MESSAGE,
|
||||
);
|
||||
expect(mocks.getConfigExactScope).not.toHaveBeenCalled();
|
||||
expect(mocks.getConfig).not.toHaveBeenCalled();
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a secret ref that is not referenced by that company's plugin config", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue({
|
||||
mocks.getConfig.mockResolvedValue({
|
||||
configJson: {
|
||||
apiKeyRef: "88888888-8888-4888-8888-888888888888",
|
||||
},
|
||||
|
|
@ -74,12 +74,12 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
await expect(handler.resolve({ secretRef, companyId })).rejects.toThrow(
|
||||
/not referenced by this company's plugin config/i,
|
||||
);
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.getConfig).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves only through the company plugin binding context from saved config", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue({
|
||||
mocks.getConfig.mockResolvedValue({
|
||||
configJson: {
|
||||
apiKeyRef: secretRef,
|
||||
},
|
||||
|
|
@ -102,20 +102,4 @@ describe("createPluginSecretsHandler runtime company scoping", () => {
|
|||
pluginId,
|
||||
});
|
||||
});
|
||||
|
||||
it("does not authorize a company-scoped secret resolution from legacy global config fallback", async () => {
|
||||
mocks.getConfigExactScope.mockResolvedValue(null);
|
||||
|
||||
const handler = createPluginSecretsHandler({
|
||||
db: {} as never,
|
||||
pluginId,
|
||||
manifest: manifest as never,
|
||||
});
|
||||
|
||||
await expect(handler.resolve({ secretRef, companyId })).rejects.toThrow(
|
||||
/not referenced by this company's plugin config/i,
|
||||
);
|
||||
expect(mocks.getConfigExactScope).toHaveBeenCalledWith(pluginId, companyId);
|
||||
expect(mocks.resolveSecretValue).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1054,10 +1054,7 @@ export function buildHostServices(
|
|||
return {
|
||||
config: {
|
||||
async get(params) {
|
||||
const companyId = Object.prototype.hasOwnProperty.call(params ?? {}, "companyId")
|
||||
? params.companyId ?? null
|
||||
: null;
|
||||
const configRow = await registry.getConfigExactScope(pluginId, companyId);
|
||||
const configRow = await registry.getConfig(pluginId, params?.companyId ?? null);
|
||||
return configRow?.configJson ?? {};
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -287,14 +287,6 @@ export function pluginRegistryService(db: Db) {
|
|||
|
||||
// ----- Config ---------------------------------------------------------
|
||||
|
||||
/** Retrieve a plugin's config at the exact requested scope. */
|
||||
getConfigExactScope: (pluginId: string, companyId?: string | null) =>
|
||||
db
|
||||
.select()
|
||||
.from(pluginConfig)
|
||||
.where(pluginConfigExactScopeCondition(pluginId, companyId))
|
||||
.then((rows) => rows[0] ?? null),
|
||||
|
||||
/** Retrieve a plugin's company-scoped config, or the legacy global fallback. */
|
||||
getConfig: async (pluginId: string, companyId?: string | null) => {
|
||||
if (companyId) {
|
||||
|
|
|
|||
|
|
@ -242,10 +242,7 @@ export function createPluginSecretsHandler(
|
|||
throw new Error(PLUGIN_SECRET_REFS_REQUIRE_COMPANY_MESSAGE);
|
||||
}
|
||||
|
||||
// Authorize secret refs only from the exact company-scoped config row.
|
||||
// Falling back to legacy/global config would let a live worker keep using
|
||||
// stale global refs after a company-scoped invocation takes over.
|
||||
const configRow = await registry.getConfigExactScope(pluginId, companyId);
|
||||
const configRow = await registry.getConfig(pluginId, companyId);
|
||||
const refsBySecret = extractSecretRefPathsFromConfig(
|
||||
configRow?.configJson ?? {},
|
||||
manifest?.instanceConfigSchema,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue