feat(server): doc level permission (#9760)

close CLOUD-89 CLOUD-90 CLOUD-91 CLOUD-92
This commit is contained in:
Brooooooklyn
2025-02-05 07:06:57 +00:00
parent 64de83b13d
commit abeff8bb1a
36 changed files with 2257 additions and 324 deletions
@@ -3,7 +3,8 @@ import { PrismaClient } from '@prisma/client';
import ava, { TestFn } from 'ava';
import { Config } from '../../base/config';
import { Permission, PublicPageMode } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { PublicPageMode } from '../../models/common';
import { PageModel } from '../../models/page';
import { type User, UserModel } from '../../models/user';
import { type Workspace, WorkspaceModel } from '../../models/workspace';
@@ -131,7 +132,7 @@ test('should grant a member to access a page', async t => {
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.false(hasAccess);
// grant write permission
@@ -139,20 +140,20 @@ test('should grant a member to access a page', async t => {
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.true(hasAccess);
hasAccess = await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.true(hasAccess);
// delete member
@@ -174,14 +175,14 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
)
);
@@ -193,14 +194,14 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.true(
await t.context.page.isMember(
workspace.id,
'page1',
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
)
);
t.false(
@@ -208,7 +209,7 @@ test('should change the page owner', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
)
);
});
@@ -221,7 +222,7 @@ test('should not delete owner from page', async t => {
workspace.id,
'page1',
user.id,
Permission.Owner
WorkspaceRole.Owner
);
const count = await t.context.page.deleteMember(
workspace.id,
@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import { EmailAlreadyUsed, EventBus } from '../../base';
import { Permission } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceMemberStatus } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
@@ -263,7 +263,7 @@ test('should trigger user.deleted event', async t => {
public: false,
},
},
type: Permission.Owner,
type: WorkspaceRole.Owner,
status: WorkspaceMemberStatus.Accepted,
},
},
@@ -4,7 +4,7 @@ import ava, { TestFn } from 'ava';
import Sinon from 'sinon';
import { Config, EventBus } from '../../base';
import { Permission } from '../../models/common';
import { WorkspaceRole } from '../../core/permission';
import { UserModel } from '../../models/user';
import { WorkspaceModel } from '../../models/workspace';
import { createTestingModule, initTestingDB } from '../utils';
@@ -92,25 +92,25 @@ test('should workspace owner has all permissions', async t => {
let allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
user.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -127,32 +127,32 @@ test('should workspace admin has all permissions except owner', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Admin,
type: WorkspaceRole.Admin,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -169,32 +169,32 @@ test('should workspace write has write and read permissions', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Write,
type: WorkspaceRole.Collaborator,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, true);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -211,32 +211,26 @@ test('should workspace read has read permission only', async t => {
data: {
workspaceId: workspace.id,
userId: otherUser.id,
type: Permission.Read,
type: WorkspaceRole.Collaborator,
status: WorkspaceMemberStatus.Accepted,
},
});
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, true);
});
@@ -252,25 +246,25 @@ test('should user not in workspace has no permissions', async t => {
let allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Owner
WorkspaceRole.Owner
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Admin
WorkspaceRole.Admin
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Write
WorkspaceRole.Collaborator
);
t.is(allowed, false);
allowed = await t.context.workspace.isMember(
workspace.id,
otherUser.id,
Permission.Read
WorkspaceRole.Collaborator
);
t.is(allowed, false);
});
@@ -313,7 +307,7 @@ test('should grant member with read permission and Pending status by default', a
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
// grant again should do nothing
@@ -344,18 +338,18 @@ test('should grant Pending status member to Accepted status', async t => {
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -370,27 +364,27 @@ test('should grant new owner and change exists owner to admin', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Accepted);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Owner,
WorkspaceRole.Owner,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Owner);
t.is(member2.type, WorkspaceRole.Owner);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
// check old owner
const owner = await t.context.workspace.getMember(workspace.id, user.id);
t.is(owner!.type, Permission.Admin);
t.is(owner!.type, WorkspaceRole.Admin);
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
});
@@ -405,23 +399,23 @@ test('should grant write permission on exists member', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Accepted);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Write,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Write);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -436,23 +430,23 @@ test('should grant UnderReview status member to Accepted status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.UnderReview);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -467,23 +461,23 @@ test('should grant NeedMoreSeat status member to Pending status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Pending);
});
@@ -498,23 +492,23 @@ test('should grant NeedMoreSeatAndReview status member to UnderReview status', a
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.UnderReview);
});
@@ -532,19 +526,19 @@ test('should grant Pending status member to write permission and Accepted status
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.Pending);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Write,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
// TODO(fengmk2): fix this
// t.is(member2.type, Permission.Write);
// t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
});
@@ -559,23 +553,23 @@ test('should grant no thing on invalid status', async t => {
const member1 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
t.is(member1.workspaceId, workspace.id);
t.is(member1.userId, otherUser.id);
t.is(member1.type, Permission.Read);
t.is(member1.type, WorkspaceRole.Collaborator);
t.is(member1.status, WorkspaceMemberStatus.NeedMoreSeat);
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.workspaceId, workspace.id);
t.is(member2.userId, otherUser.id);
t.is(member2.type, Permission.Read);
t.is(member2.type, WorkspaceRole.Collaborator);
t.is(member2.status, WorkspaceMemberStatus.NeedMoreSeat);
});
@@ -590,7 +584,7 @@ test('should get the accepted status workspace member', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const member = await t.context.workspace.getMember(
@@ -599,7 +593,7 @@ test('should get the accepted status workspace member', async t => {
);
t.is(member!.workspaceId, workspace.id);
t.is(member!.userId, otherUser.id);
t.is(member!.type, Permission.Read);
t.is(member!.type, WorkspaceRole.Collaborator);
t.is(member!.status, WorkspaceMemberStatus.Accepted);
});
@@ -614,7 +608,7 @@ test('should get any status workspace member, including pending and accepted', a
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
const member = await t.context.workspace.getMemberInAnyStatus(
@@ -623,7 +617,7 @@ test('should get any status workspace member, including pending and accepted', a
);
t.is(member!.workspaceId, workspace.id);
t.is(member!.userId, otherUser.id);
t.is(member!.type, Permission.Read);
t.is(member!.type, WorkspaceRole.Collaborator);
t.is(member!.status, WorkspaceMemberStatus.Pending);
});
@@ -635,7 +629,7 @@ test('should get workspace owner by workspace id', async t => {
const owner = await t.context.workspace.getOwner(workspace.id);
t.is(owner!.workspaceId, workspace.id);
t.is(owner!.userId, user.id);
t.is(owner!.type, Permission.Owner);
t.is(owner!.type, WorkspaceRole.Owner);
t.is(owner!.status, WorkspaceMemberStatus.Accepted);
t.truthy(owner!.user);
t.deepEqual(owner!.user, user);
@@ -658,27 +652,27 @@ test('should find workspace admin by workspace id', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Admin,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Accepted
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
// pending member should not be admin
await t.context.workspace.grantMember(
workspace.id,
otherUser3.id,
Permission.Admin,
WorkspaceRole.Admin,
WorkspaceMemberStatus.Pending
);
const members = await t.context.workspace.findAdmins(workspace.id);
t.is(members.length, 1);
t.is(members[0].workspaceId, workspace.id);
t.is(members[0].userId, otherUser1.id);
t.is(members[0].type, Permission.Admin);
t.is(members[0].type, WorkspaceRole.Admin);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
});
@@ -710,13 +704,13 @@ test('should the workspace member total count, including pending and accepted',
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const count = await t.context.workspace.getMemberTotalCount(workspace.id);
@@ -737,13 +731,13 @@ test('should the workspace member used count, only count the accepted member', a
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
const count = await t.context.workspace.getMemberUsedCount(workspace.id);
@@ -855,7 +849,7 @@ test('should delete workspace member in Pending, Accepted status', async t => {
const member2 = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
t.is(member2.status, WorkspaceMemberStatus.Accepted);
@@ -874,7 +868,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
const member = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.UnderReview
);
t.is(member.status, WorkspaceMemberStatus.UnderReview);
@@ -919,7 +913,7 @@ test('should trigger workspace.members.requestDeclined event when delete workspa
const member = await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
t.is(member.status, WorkspaceMemberStatus.NeedMoreSeatAndReview);
@@ -970,19 +964,19 @@ test('should refresh member seat status', async t => {
await t.context.workspace.grantMember(
workspace.id,
otherUser1.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeatAndReview
);
await t.context.workspace.grantMember(
workspace.id,
otherUser2.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Pending
);
await t.context.workspace.grantMember(
workspace.id,
otherUser3.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.NeedMoreSeat
);
let count = await t.context.db.workspaceUserPermission.count({
@@ -1043,30 +1037,30 @@ test('should find the workspace members order by type:desc and createdAt:asc', a
await t.context.workspace.grantMember(
workspace.id,
otherUser.id,
Permission.Read,
WorkspaceRole.Collaborator,
WorkspaceMemberStatus.Accepted
);
}
let members = await t.context.workspace.findMembers(workspace.id);
t.is(members.length, 8);
t.is(members[0].type, Permission.Owner);
t.is(members[0].type, WorkspaceRole.Owner);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
for (let i = 1; i < 8; i++) {
t.is(members[i].type, Permission.Read);
t.is(members[i].type, WorkspaceRole.Collaborator);
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
}
members = await t.context.workspace.findMembers(workspace.id, { take: 100 });
t.is(members.length, 11);
t.is(members[0].type, Permission.Owner);
t.is(members[0].type, WorkspaceRole.Owner);
t.is(members[0].status, WorkspaceMemberStatus.Accepted);
for (let i = 1; i < 11; i++) {
t.is(members[i].type, Permission.Read);
t.is(members[i].type, WorkspaceRole.Collaborator);
t.is(members[i].status, WorkspaceMemberStatus.Accepted);
}
// skip should work
members = await t.context.workspace.findMembers(workspace.id, { skip: 5 });
t.is(members.length, 6);
t.is(members[0].type, Permission.Read);
t.is(members[0].type, WorkspaceRole.Collaborator);
});
test('should get the workspace member invitation', async t => {