fix(pairing): treat operator.admin as satisfying operator.write

This commit is contained in:
vignesh07
2026-02-21 17:55:22 -08:00
committed by Vignesh
parent a37e12eabc
commit 426d97797d
3 changed files with 15 additions and 5 deletions

View File

@@ -168,7 +168,7 @@ describe("device pairing tokens", () => {
expect(mismatch.reason).toBe("token-mismatch"); expect(mismatch.reason).toBe("token-mismatch");
}); });
test("accepts operator.read requests with an operator.admin token scope", async () => { test("accepts operator.read/operator.write requests with an operator.admin token scope", async () => {
const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-")); const baseDir = await mkdtemp(join(tmpdir(), "openclaw-device-pairing-"));
await setupPairedOperatorDevice(baseDir, ["operator.admin"]); await setupPairedOperatorDevice(baseDir, ["operator.admin"]);
const paired = await getPairedDevice("device-1", baseDir); const paired = await getPairedDevice("device-1", baseDir);
@@ -183,14 +183,14 @@ describe("device pairing tokens", () => {
}); });
expect(readOk.ok).toBe(true); expect(readOk.ok).toBe(true);
const writeMismatch = await verifyDeviceToken({ const writeOk = await verifyDeviceToken({
deviceId: "device-1", deviceId: "device-1",
token, token,
role: "operator", role: "operator",
scopes: ["operator.write"], scopes: ["operator.write"],
baseDir, baseDir,
}); });
expect(writeMismatch).toEqual({ ok: false, reason: "scope-mismatch" }); expect(writeOk.ok).toBe(true);
}); });
test("treats multibyte same-length token input as mismatch without throwing", async () => { test("treats multibyte same-length token input as mismatch without throwing", async () => {

View File

@@ -26,14 +26,21 @@ describe("roleScopesAllow", () => {
).toBe(true); ).toBe(true);
}); });
it("keeps non-read operator scopes explicit", () => { it("treats operator.write as satisfied by write/admin scopes", () => {
expect(
roleScopesAllow({
role: "operator",
requestedScopes: ["operator.write"],
allowedScopes: ["operator.write"],
}),
).toBe(true);
expect( expect(
roleScopesAllow({ roleScopesAllow({
role: "operator", role: "operator",
requestedScopes: ["operator.write"], requestedScopes: ["operator.write"],
allowedScopes: ["operator.admin"], allowedScopes: ["operator.admin"],
}), }),
).toBe(false); ).toBe(true);
}); });
it("uses strict matching for non-operator roles", () => { it("uses strict matching for non-operator roles", () => {

View File

@@ -22,6 +22,9 @@ function operatorScopeSatisfied(requestedScope: string, granted: Set<string>): b
granted.has(OPERATOR_ADMIN_SCOPE) granted.has(OPERATOR_ADMIN_SCOPE)
); );
} }
if (requestedScope === OPERATOR_WRITE_SCOPE) {
return granted.has(OPERATOR_WRITE_SCOPE) || granted.has(OPERATOR_ADMIN_SCOPE);
}
return granted.has(requestedScope); return granted.has(requestedScope);
} }