[permissions V2] Upsert object and setting permissions (#11119)

Closes https://github.com/twentyhq/core-team-issues/issues/639
This commit is contained in:
Marie
2025-03-25 11:07:51 +01:00
committed by GitHub
parent 54e346a2aa
commit 4680bc740a
51 changed files with 985 additions and 205 deletions

View File

@ -1,15 +1,41 @@
import request from 'supertest';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util';
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
const client = request(`http://localhost:${APP_PORT}`);
async function assertPermissionDeniedForMemberWithMemberRole({
query,
}: {
query: { query: string };
}) {
await client
.post('/graphql')
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
}
describe('roles permissions', () => {
let adminRoleId: string;
let guestRoleId: string;
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
@ -17,7 +43,38 @@ describe('roles permissions', () => {
true,
);
const enablePermissionsV2Query = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
await makeGraphqlAPIRequest(enablePermissionsV2Query);
const query = {
query: `
query GetRoles {
getRoles {
label
id
}
}
`,
};
const resp = await client
.post('/graphql')
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
.send(query);
adminRoleId = resp.body.data.getRoles.find(
(role) => role.label === 'Admin',
).id;
guestRoleId = resp.body.data.getRoles.find(
(role) => role.label === 'Guest',
).id;
});
afterAll(async () => {
@ -27,7 +84,14 @@ describe('roles permissions', () => {
false,
);
const disablePermissionsV2Query = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
await makeGraphqlAPIRequest(disablePermissionsV2Query);
});
describe('getRoles', () => {
@ -116,19 +180,7 @@ describe('roles permissions', () => {
`,
};
await client
.post('/graphql')
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
});
@ -144,19 +196,7 @@ describe('roles permissions', () => {
`,
};
await client
.post('/graphql')
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(res.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
it('should throw a permission error when tries to update their own role (admin role)', async () => {
@ -260,4 +300,244 @@ describe('roles permissions', () => {
});
});
});
describe('createRole', () => {
it('should throw a permission error when user does not have permission to create roles (member role)', async () => {
const query = {
query: `
mutation CreateOneRole {
createOneRole(createRoleInput: {label: "test-role"}) {
id
}
}
`,
};
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
// TODO - to uncomment after deleteOneRole has been implemented
// it('should create a role when user has permission to create a role (admin role)', async () => {
// const query = {
// query: `
// mutation CreateOneRole {
// createOneRole(createRoleInput: {label: "Test role"}) {
// id
// }
// }
// `,
// };
// await client
// .post('/graphql')
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
// .send(query)
// .expect(200)
// .expect((res) => {
// expect(res.body.data).toBeDefined();
// expect(res.body.errors).toBeUndefined();
// });
// });
});
describe('updateRole', () => {
// let createdEditableRoleId: string;
beforeAll(async () => {
// TODO - to uncomment after deleteOneRole has been implemented
// const query = {
// query: `
// mutation CreateOneRole {
// createOneRole(createRoleInput: {label: "Test role 2"}) {
// id
// }
// }
// `,
// };
// await client
// .post('/graphql')
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
// .send(query)
// .then((res) => {
// createdEditableRoleId = res.body.data.createOneRole.id;
// });
});
describe('updateRole', () => {
it('should throw a permission error when user does not have permission to update roles (member role)', async () => {
const query = {
query: `
mutation UpdateOneRole {
updateOneRole(updateRoleInput: {id: "test-role-id", update: {label: "new-role-label"}}) {
id
}
}
`,
};
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
it('should throw an error when role is not editable', async () => {
const query = {
query: `
mutation UpdateOneRole {
updateOneRole(updateRoleInput: {id: "${adminRoleId}", update: {label: "new-role-label"}}) {
id
}
}
`,
};
await client
.post('/graphql')
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
);
expect(res.body.errors[0].extensions.code).toBe(
ErrorCode.FORBIDDEN,
);
});
});
});
describe('upsertObjectPermission', () => {
let listingObjectId = '';
beforeAll(async () => {
const { objectMetadataId: createdObjectId } =
await createListingCustomObject();
listingObjectId = createdObjectId;
});
afterAll(async () => {
await deleteOneObjectMetadataItem(listingObjectId);
});
const upsertObjectPermissionMutation = ({
objectMetadataId,
roleId,
}: {
objectMetadataId: string;
roleId: string;
}) => `
mutation UpsertObjectPermissions {
upsertOneObjectPermission(upsertObjectPermissionInput: {objectMetadataId: "${objectMetadataId}", roleId: "${roleId}", canUpdateObjectRecords: true}) {
id
}
}
`;
it('should throw a permission error when user does not have permission to upsert object permission (member role)', async () => {
const query = {
query: upsertObjectPermissionMutation({
objectMetadataId: listingObjectId,
roleId: guestRoleId,
}),
};
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
it('should throw an error when role is not editable', async () => {
const query = {
query: upsertObjectPermissionMutation({
objectMetadataId: listingObjectId,
roleId: adminRoleId,
}),
};
await client
.post('/graphql')
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
);
expect(res.body.errors[0].extensions.code).toBe(
ErrorCode.FORBIDDEN,
);
});
});
// TODO - to uncomment after deleteOneRole has been implemented
// it('should upsert a setting permission when user has permission to create a setting permission', async () => {
// const query = {
// query: upsertObjectPermissionMutation({
// objectMetadataId: listingObjectId,
// roleId: createdEditableRoleId,
// }),
// };
// await client
// .post('/graphql')
// .set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
// .send(query)
// .expect(200)
// .expect((res) => {
// expect(res.body.data).toBeDefined();
// expect(res.body.errors).toBeUndefined();
// });
// });
});
describe('upsertSettingPermission', () => {
const upsertSettingPermissionMutation = ({
roleId,
}: {
roleId: string;
}) => `
mutation UpsertSettingPermissions {
upsertOneSettingPermission(upsertSettingPermissionInput: {roleId: "${roleId}", setting: ${SettingPermissionType.DATA_MODEL}}) {
id
}
}
`;
it('should throw a permission error when user does not have permission to upsert object permission (member role)', async () => {
const query = {
query: upsertSettingPermissionMutation({
roleId: guestRoleId,
}),
};
await assertPermissionDeniedForMemberWithMemberRole({ query });
});
it('should throw an error when role is not editable', async () => {
const query = {
query: upsertSettingPermissionMutation({
roleId: adminRoleId,
}),
};
await client
.post('/graphql')
.set('Authorization', `Bearer ${ADMIN_ACCESS_TOKEN}`)
.send(query)
.expect(200)
.expect((res) => {
expect(res.body.data).toBeNull();
expect(res.body.errors).toBeDefined();
expect(res.body.errors[0].message).toBe(
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
);
expect(res.body.errors[0].extensions.code).toBe(
ErrorCode.FORBIDDEN,
);
});
});
});
});
});