mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-04 16:10:21 +00:00
Gateway: align pairing scope checks for read access
This commit is contained in:
55
src/shared/operator-scope-compat.test.ts
Normal file
55
src/shared/operator-scope-compat.test.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { roleScopesAllow } from "./operator-scope-compat.js";
|
||||
|
||||
describe("roleScopesAllow", () => {
|
||||
it("treats operator.read as satisfied by read/write/admin scopes", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.read"],
|
||||
allowedScopes: ["operator.read"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.read"],
|
||||
allowedScopes: ["operator.write"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.read"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps non-read operator scopes explicit", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.write"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("uses strict matching for non-operator roles", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "node",
|
||||
requestedScopes: ["system.run"],
|
||||
allowedScopes: ["operator.admin", "system.run"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "node",
|
||||
requestedScopes: ["system.run"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
46
src/shared/operator-scope-compat.ts
Normal file
46
src/shared/operator-scope-compat.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
const OPERATOR_ROLE = "operator";
|
||||
const OPERATOR_ADMIN_SCOPE = "operator.admin";
|
||||
const OPERATOR_READ_SCOPE = "operator.read";
|
||||
const OPERATOR_WRITE_SCOPE = "operator.write";
|
||||
|
||||
function normalizeScopeList(scopes: readonly string[]): string[] {
|
||||
const out = new Set<string>();
|
||||
for (const scope of scopes) {
|
||||
const trimmed = scope.trim();
|
||||
if (trimmed) {
|
||||
out.add(trimmed);
|
||||
}
|
||||
}
|
||||
return [...out];
|
||||
}
|
||||
|
||||
function operatorScopeSatisfied(requestedScope: string, granted: Set<string>): boolean {
|
||||
if (requestedScope === OPERATOR_READ_SCOPE) {
|
||||
return (
|
||||
granted.has(OPERATOR_READ_SCOPE) ||
|
||||
granted.has(OPERATOR_WRITE_SCOPE) ||
|
||||
granted.has(OPERATOR_ADMIN_SCOPE)
|
||||
);
|
||||
}
|
||||
return granted.has(requestedScope);
|
||||
}
|
||||
|
||||
export function roleScopesAllow(params: {
|
||||
role: string;
|
||||
requestedScopes: readonly string[];
|
||||
allowedScopes: readonly string[];
|
||||
}): boolean {
|
||||
const requested = normalizeScopeList(params.requestedScopes);
|
||||
if (requested.length === 0) {
|
||||
return true;
|
||||
}
|
||||
const allowed = normalizeScopeList(params.allowedScopes);
|
||||
if (allowed.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const allowedSet = new Set(allowed);
|
||||
if (params.role.trim() !== OPERATOR_ROLE) {
|
||||
return requested.every((scope) => allowedSet.has(scope));
|
||||
}
|
||||
return requested.every((scope) => operatorScopeSatisfied(scope, allowedSet));
|
||||
}
|
||||
Reference in New Issue
Block a user