fix(device-core): allow workspace reads in readonly transactions
This commit is contained in:
@@ -505,7 +505,14 @@ async function transferDevice(client, actor, command) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findProjectWithCapability(client, actor, projectId, capability) {
|
export async function findProjectWithCapability(
|
||||||
|
client,
|
||||||
|
actor,
|
||||||
|
projectId,
|
||||||
|
capability,
|
||||||
|
{ lock = true } = {},
|
||||||
|
) {
|
||||||
|
const projectLockClause = lock ? "for share of p, os" : "";
|
||||||
const result = await client.query(
|
const result = await client.query(
|
||||||
`select p.id, p.owner_scope_id, p.project_key, p.name, p.description,
|
`select p.id, p.owner_scope_id, p.project_key, p.name, p.description,
|
||||||
p.lifecycle_state, p.created_at, p.updated_at,
|
p.lifecycle_state, p.created_at, p.updated_at,
|
||||||
@@ -514,7 +521,7 @@ export async function findProjectWithCapability(client, actor, projectId, capabi
|
|||||||
from device_projects p
|
from device_projects p
|
||||||
join device_owner_scopes os on os.id = p.owner_scope_id
|
join device_owner_scopes os on os.id = p.owner_scope_id
|
||||||
where p.id = $1
|
where p.id = $1
|
||||||
for share of p, os`,
|
${projectLockClause}`,
|
||||||
[projectId],
|
[projectId],
|
||||||
);
|
);
|
||||||
const project = result.rows[0];
|
const project = result.rows[0];
|
||||||
@@ -531,7 +538,7 @@ export async function findProjectWithCapability(client, actor, projectId, capabi
|
|||||||
from device_project_grants
|
from device_project_grants
|
||||||
where project_id = $1
|
where project_id = $1
|
||||||
order by created_at, id
|
order by created_at, id
|
||||||
for share`,
|
${lock ? "for share" : ""}`,
|
||||||
[projectId],
|
[projectId],
|
||||||
);
|
);
|
||||||
assertProjectCapability(
|
assertProjectCapability(
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ export async function getDeviceProjectWorkspace(client, actor, projectId) {
|
|||||||
actor,
|
actor,
|
||||||
projectId,
|
projectId,
|
||||||
"project.read",
|
"project.read",
|
||||||
|
{ lock: false },
|
||||||
);
|
);
|
||||||
const grantsResult = await client.query(
|
const grantsResult = await client.query(
|
||||||
`select id, principal_kind, principal_ref, project_role,
|
`select id, principal_kind, principal_ref, project_role,
|
||||||
|
|||||||
@@ -81,6 +81,19 @@ test("project workspace returns only masked identity projections", async () => {
|
|||||||
assert.equal(serialized.includes("raw-audit-payload"), false);
|
assert.equal(serialized.includes("raw-audit-payload"), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("project workspace authorization remains compatible with read-only transactions", async () => {
|
||||||
|
const queries = [];
|
||||||
|
const client = workspaceClient({ queries });
|
||||||
|
|
||||||
|
await getDeviceProjectWorkspace(client, actor, projectId);
|
||||||
|
|
||||||
|
assert.ok(queries.length > 0);
|
||||||
|
assert.equal(
|
||||||
|
queries.some((sql) => /\bfor\s+(?:no\s+key\s+)?(?:update|share)\b/i.test(sql)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("project read source never selects identifier digests or credential refs", async () => {
|
test("project read source never selects identifier digests or credential refs", async () => {
|
||||||
const source = await readFile(
|
const source = await readFile(
|
||||||
new URL("../src/project-query-repository.mjs", import.meta.url),
|
new URL("../src/project-query-repository.mjs", import.meta.url),
|
||||||
@@ -93,10 +106,11 @@ test("project read source never selects identifier digests or credential refs",
|
|||||||
assert.doesNotMatch(source, /\b(?:dae\.payload|dcr\.configuration)\b/);
|
assert.doesNotMatch(source, /\b(?:dae\.payload|dcr\.configuration)\b/);
|
||||||
});
|
});
|
||||||
|
|
||||||
function workspaceClient() {
|
function workspaceClient({ queries = [] } = {}) {
|
||||||
let grantReads = 0;
|
let grantReads = 0;
|
||||||
return {
|
return {
|
||||||
async query(sql) {
|
async query(sql) {
|
||||||
|
queries.push(sql);
|
||||||
if (/from device_projects p/.test(sql)) {
|
if (/from device_projects p/.test(sql)) {
|
||||||
return { rows: [projectGrantRow()] };
|
return { rows: [projectGrantRow()] };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user