mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-17 01:56:27 +08:00
fix: enforce Doc.Read permission on workspace histories field (#15192)
The histories() resolver was returning document edit history (including editor names, emails, and timestamps) without checking Doc.Read permission first. This let any workspace member view history for private docs they weren't given access to, by passing an arbitrary document guid. Added the same permission check already used by WorkspaceDocResolver.doc() and recoverDoc() in this file. Fixes #15179 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Added permission checks when viewing document history so only authorized users can access snapshot histories. * Prevented workspace collaborators without read access from querying histories for private documents. * **Tests** * Added an end-to-end test to verify history access is denied when the required permission is missing. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -306,3 +306,50 @@ e2e('should require Doc.Read to query workspace page meta', async t => {
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
e2e('should require Doc.Read to query doc histories', async t => {
|
||||
const owner = await app.signup();
|
||||
const member = await app.createUser();
|
||||
await app.login(member);
|
||||
|
||||
await app.switchUser(owner);
|
||||
const workspace = await app.create(Mockers.Workspace, {
|
||||
owner: { id: owner.id },
|
||||
});
|
||||
await app.create(Mockers.WorkspaceUser, {
|
||||
workspaceId: workspace.id,
|
||||
userId: member.id,
|
||||
type: WorkspaceRole.Collaborator,
|
||||
});
|
||||
|
||||
const docSnapshot = await app.create(Mockers.DocSnapshot, {
|
||||
workspaceId: workspace.id,
|
||||
user: owner,
|
||||
});
|
||||
const doc = await app.create(Mockers.DocMeta, {
|
||||
workspaceId: workspace.id,
|
||||
docId: docSnapshot.id,
|
||||
title: 'private-doc',
|
||||
defaultRole: DocRole.None,
|
||||
});
|
||||
|
||||
await app.switchUser(member);
|
||||
await t.throwsAsync(
|
||||
app.gql({
|
||||
query: {
|
||||
id: 'workspaceDocHistoriesPermissionCheck',
|
||||
op: 'workspaceDocHistoriesPermissionCheck',
|
||||
query: `
|
||||
query {
|
||||
workspace(id: "${workspace.id}") {
|
||||
histories(guid: "${doc.docId}") {
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
} satisfies GraphQLQuery,
|
||||
variables: undefined,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
@@ -42,6 +42,7 @@ export class DocHistoryResolver {
|
||||
|
||||
@ResolveField(() => [DocHistoryType])
|
||||
async histories(
|
||||
@CurrentUser() user: CurrentUser,
|
||||
@Parent() workspace: WorkspaceType,
|
||||
@Args('guid') guid: string,
|
||||
@Args({ name: 'before', type: () => GraphQLISODateTime, nullable: true })
|
||||
@@ -51,6 +52,8 @@ export class DocHistoryResolver {
|
||||
): Promise<DocHistoryType[]> {
|
||||
const docId = new DocID(guid, workspace.id);
|
||||
|
||||
await this.ac.user(user.id).doc(docId).assert('Doc.Read');
|
||||
|
||||
const histories = await this.workspace.listDocHistories(
|
||||
workspace.id,
|
||||
docId.guid,
|
||||
|
||||
Reference in New Issue
Block a user