123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
import { describe, expect, test } from "vitest";
|
|
import {
|
|
MAX_PLUGIN_CONSENT_CAPABILITY_ITEMS,
|
|
MAX_PLUGIN_CONSENT_CAPABILITY_NAME_LENGTH,
|
|
parsePluginCapabilityGrantArray,
|
|
parsePluginCapabilityNameArray,
|
|
readPluginConsentJsonObject,
|
|
readPluginConsentUrlEncodedFormData,
|
|
} from "./capabilityConsentPayload.server";
|
|
|
|
describe("plugin capability consent payload helpers", () => {
|
|
test("parses valid capability name arrays", () => {
|
|
expect(parsePluginCapabilityNameArray(["http.outbound", "log"])).toEqual({
|
|
ok: true,
|
|
capabilityNames: ["http.outbound", "log"],
|
|
});
|
|
});
|
|
|
|
test("rejects malformed capability name arrays", () => {
|
|
const cases: unknown[] = [
|
|
[],
|
|
Array.from({ length: MAX_PLUGIN_CONSENT_CAPABILITY_ITEMS + 1 }, (_, index) => `capability.${index}`),
|
|
["http.outbound", "http.outbound"],
|
|
[" http.outbound"],
|
|
["x".repeat(MAX_PLUGIN_CONSENT_CAPABILITY_NAME_LENGTH + 1)],
|
|
["log", null],
|
|
];
|
|
|
|
for (const value of cases) {
|
|
expect(parsePluginCapabilityNameArray(value)).toEqual({ ok: false });
|
|
}
|
|
});
|
|
|
|
test("parses valid capability grant arrays", () => {
|
|
expect(parsePluginCapabilityGrantArray([{ capabilityName: "http.outbound", scopeJson: { allowedHosts: ["api.example.com"] } }])).toEqual({
|
|
ok: true,
|
|
grants: [{ capabilityName: "http.outbound", scopeJson: { allowedHosts: ["api.example.com"] } }],
|
|
});
|
|
});
|
|
|
|
test("rejects duplicate capability grants", () => {
|
|
expect(
|
|
parsePluginCapabilityGrantArray([
|
|
{ capabilityName: "http.outbound", scopeJson: { allowedHosts: ["api.example.com"] } },
|
|
{ capabilityName: "http.outbound", scopeJson: { allowedHosts: ["api2.example.com"] } },
|
|
])
|
|
).toEqual({ ok: false });
|
|
});
|
|
|
|
test("rejects unsafe capability grant scopes", () => {
|
|
expect(parsePluginCapabilityGrantArray([{ capabilityName: "http.outbound", scopeJson: ["api.example.com"] }])).toEqual({ ok: false });
|
|
expect(parsePluginCapabilityGrantArray([{ capabilityName: "http.outbound", scopeJson: { __proto__: { polluted: true } } }])).toEqual({ ok: false });
|
|
expect(parsePluginCapabilityGrantArray([{ capabilityName: "http.outbound", scopeJson: { note: "x".repeat(16 * 1024) } }])).toEqual({ ok: false });
|
|
});
|
|
|
|
test("reads bounded JSON objects", async () => {
|
|
const response = await readPluginConsentJsonObject(
|
|
new Request("http://localhost/plugins", {
|
|
method: "POST",
|
|
body: JSON.stringify({ pluginId: "com.acme.hello" }),
|
|
headers: { "content-type": "application/json; charset=utf-8" },
|
|
})
|
|
);
|
|
|
|
expect(response).toEqual({ ok: true, value: { pluginId: "com.acme.hello" } });
|
|
});
|
|
|
|
test("rejects JSON object bodies without an application/json content type", async () => {
|
|
for (const headers of [{ "content-type": "text/plain" }, {}]) {
|
|
await expect(
|
|
readPluginConsentJsonObject(
|
|
new Request("http://localhost/plugins", {
|
|
method: "POST",
|
|
body: JSON.stringify({ pluginId: "com.acme.hello" }),
|
|
headers,
|
|
})
|
|
)
|
|
).resolves.toEqual({ ok: false, error: "unsupported_media_type" });
|
|
}
|
|
});
|
|
|
|
test("rejects oversized request bodies before parsing", async () => {
|
|
const response = await readPluginConsentUrlEncodedFormData(
|
|
new Request("http://localhost/plugins", {
|
|
method: "POST",
|
|
body: "op=enable&id=pl-1",
|
|
headers: {
|
|
"content-type": "application/x-www-form-urlencoded",
|
|
"content-length": String(129 * 1024),
|
|
},
|
|
})
|
|
);
|
|
|
|
expect(response).toEqual({ ok: false });
|
|
});
|
|
|
|
test("accepts only exact urlencoded form content types", async () => {
|
|
const valid = await readPluginConsentUrlEncodedFormData(
|
|
new Request("http://localhost/plugins", {
|
|
method: "POST",
|
|
body: "op=enable&id=pl-1",
|
|
headers: { "content-type": "application/x-www-form-urlencoded; charset=utf-8" },
|
|
})
|
|
);
|
|
expect(valid.ok).toBe(true);
|
|
if (valid.ok) {
|
|
expect(valid.formData.get("op")).toBe("enable");
|
|
}
|
|
|
|
for (const contentType of ["text/plain; application/x-www-form-urlencoded", "application/x-www-form-urlencoded-malformed"]) {
|
|
await expect(
|
|
readPluginConsentUrlEncodedFormData(
|
|
new Request("http://localhost/plugins", {
|
|
method: "POST",
|
|
body: "op=enable&id=pl-1",
|
|
headers: { "content-type": contentType },
|
|
})
|
|
)
|
|
).resolves.toEqual({ ok: false });
|
|
}
|
|
});
|
|
});
|