[permissions] Enforce object-records permission checks in resolvers (#10304)

Closes https://github.com/twentyhq/core-team-issues/issues/393

- enforcing object-records permission checks in resolvers for now. we
will move the logic to a lower level asap
- add integration tests that will still be useful when we have moved the
logic
- introduce guest seeded role to test limited permissions on
object-records
This commit is contained in:
Marie
2025-02-19 11:21:03 +01:00
committed by GitHub
parent 33178fa8b2
commit 861face2a8
48 changed files with 1372 additions and 144 deletions

View File

@ -0,0 +1,85 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('createManyObjectRecordsPermissions', () => {
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: randomUUID(),
},
{
id: randomUUID(),
},
],
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ createPeople: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should create multiple object records when user has permission (admin role)', async () => {
const personId1 = randomUUID();
const personId2 = randomUUID();
const graphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.createPeople).toBeDefined();
expect(response.body.data.createPeople).toHaveLength(2);
expect(response.body.data.createPeople[0].id).toBe(personId1);
expect(response.body.data.createPeople[1].id).toBe(personId2);
});
});

View File

@ -0,0 +1,69 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('createOneObjectRecordsPermissions', () => {
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: randomUUID(),
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ createPerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should create an object record when user has permission (admin role)', async () => {
const personId = randomUUID();
const graphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.createPerson).toBeDefined();
expect(response.body.data.createPerson.id).toBe(personId);
});
});

View File

@ -0,0 +1,96 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('deleteManyObjectRecordsPermissions', () => {
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = deleteManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [randomUUID(), randomUUID()],
},
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ deletePeople: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should delete multiple object records when user has permission (admin role)', async () => {
const personId1 = randomUUID();
const personId2 = randomUUID();
const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
});
await makeGraphqlAPIRequest(createGraphqlOperation);
const deleteGraphqlOperation = deleteManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
});
const response = await makeGraphqlAPIRequest(deleteGraphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.deletePeople).toBeDefined();
expect(response.body.data.deletePeople).toHaveLength(2);
expect(response.body.data.deletePeople[0].id).toBe(personId1);
expect(response.body.data.deletePeople[1].id).toBe(personId2);
});
});

View File

@ -0,0 +1,78 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { deleteOneOperationFactory } from 'test/integration/graphql/utils/delete-one-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('deleteOneObjectRecordsPermissions', () => {
const personId = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
const createGraphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
await makeGraphqlAPIRequest(createGraphqlOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const personId = randomUUID();
const graphqlOperation = deleteOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ deletePerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should delete an object record when user has permission (admin role)', async () => {
const deleteGraphqlOperation = deleteOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequest(deleteGraphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.deletePerson).toBeDefined();
expect(response.body.data.deletePerson.id).toBe(personId);
});
});

View File

@ -0,0 +1,96 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { destroyManyOperationFactory } from 'test/integration/graphql/utils/destroy-many-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('destroyManyObjectRecordsPermissions', () => {
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = destroyManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [randomUUID(), randomUUID()],
},
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ destroyPeople: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should destroy multiple object records when user has permission (admin role)', async () => {
const personId1 = randomUUID();
const personId2 = randomUUID();
const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
});
await makeGraphqlAPIRequest(createGraphqlOperation);
const graphqlOperation = destroyManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.destroyPeople).toBeDefined();
expect(response.body.data.destroyPeople).toHaveLength(2);
expect(response.body.data.destroyPeople[0].id).toBe(personId1);
expect(response.body.data.destroyPeople[1].id).toBe(personId2);
});
});

View File

@ -0,0 +1,78 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('destroyOneObjectRecordsPermissions', () => {
const personId = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
const createGraphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
await makeGraphqlAPIRequest(createGraphqlOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const personId = randomUUID();
const graphqlOperation = destroyOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ destroyPerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should destroy an object record when user has permission (admin role)', async () => {
const graphqlOperation = destroyOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.destroyPerson).toBeDefined();
expect(response.body.data.destroyPerson.id).toBe(personId);
});
});

View File

@ -0,0 +1,112 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { restoreManyOperationFactory } from 'test/integration/graphql/utils/restore-many-operation-factory.util';
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('restoreManyObjectRecordsPermissions', () => {
const personId1 = randomUUID();
const personId2 = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
// Create people
const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
});
await makeGraphqlAPIRequest(createGraphqlOperation);
// Delete people
const deleteGraphqlOperation = deleteManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
});
await makeGraphqlAPIRequest(deleteGraphqlOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = restoreManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ restorePeople: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should restore multiple object records when user has permission (admin role)', async () => {
const graphqlOperation = restoreManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.restorePeople).toBeDefined();
expect(response.body.data.restorePeople).toHaveLength(2);
expect(response.body.data.restorePeople[0].id).toBe(personId1);
expect(response.body.data.restorePeople[1].id).toBe(personId2);
});
});

View File

@ -0,0 +1,88 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { deleteOneOperationFactory } from 'test/integration/graphql/utils/delete-one-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { restoreOneOperationFactory } from 'test/integration/graphql/utils/restore-one-operation-factory.util';
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('restoreOneObjectRecordsPermissions', () => {
const personId = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
// Create a person
const createGraphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
await makeGraphqlAPIRequest(createGraphqlOperation);
// Delete the person
const deleteGraphqlOperation = deleteOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
await makeGraphqlAPIRequest(deleteGraphqlOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = restoreOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ restorePerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should restore an object record when user has permission (admin role)', async () => {
const graphqlOperation = restoreOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.restorePerson).toBeDefined();
expect(response.body.data.restorePerson.id).toBe(personId);
});
});

View File

@ -0,0 +1,102 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('updateManyObjectRecordsPermissions', () => {
const personId1 = randomUUID();
const personId2 = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
const createGraphqlOperation = createManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
data: [
{
id: personId1,
},
{
id: personId2,
},
],
});
await makeGraphqlAPIRequest(createGraphqlOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = updateManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [randomUUID(), randomUUID()],
},
},
data: {
jobTitle: 'Architect',
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ updatePeople: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should update multiple object records when user has permission (admin role)', async () => {
const graphqlOperation = updateManyOperationFactory({
objectMetadataSingularName: 'person',
objectMetadataPluralName: 'people',
gqlFields: PERSON_GQL_FIELDS,
filter: {
id: {
in: [personId1, personId2],
},
},
data: {
jobTitle: 'Architect',
},
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.updatePeople).toBeDefined();
expect(response.body.data.updatePeople).toHaveLength(2);
expect(response.body.data.updatePeople[0].jobTitle).toBe('Architect');
expect(response.body.data.updatePeople[1].jobTitle).toBe('Architect');
});
});

View File

@ -0,0 +1,88 @@
import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
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 { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
describe('updateOneObjectRecordsPermissions', () => {
const personId = randomUUID();
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
true,
);
await makeGraphqlAPIRequest(enablePermissionsQuery);
const createPersonOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
jobTitle: 'Software Engineer',
},
});
await makeGraphqlAPIRequest(createPersonOperation);
});
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsEnabled',
false,
);
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
it('should throw a permission error when user does not have permission (guest role)', async () => {
const personId = randomUUID();
const graphqlOperation = updateOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
data: {
jobTitle: 'Senior Software Engineer',
},
});
const response = await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
expect(response.body.data).toStrictEqual({ updatePerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
it('should update an object record when user has permission (admin role)', async () => {
const graphqlOperation = updateOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
recordId: personId,
data: {
jobTitle: 'Senior Software Engineer',
},
});
const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined();
expect(response.body.data.updatePerson).toBeDefined();
expect(response.body.data.updatePerson.id).toBe(personId);
expect(response.body.data.updatePerson.jobTitle).toBe(
'Senior Software Engineer',
);
});
});

View File

@ -0,0 +1,21 @@
import { ASTNode, print } from 'graphql';
import request from 'supertest';
type GraphqlOperation = {
query: ASTNode;
variables?: Record<string, unknown>;
};
export const makeGraphqlAPIRequestWithGuestRole = (
graphqlOperation: GraphqlOperation,
) => {
const client = request(`http://localhost:${APP_PORT}`);
return client
.post('/graphql')
.set('Authorization', `Bearer ${GUEST_ACCESS_TOKEN}`)
.send({
query: print(graphqlOperation.query),
variables: graphqlOperation.variables || {},
});
};

View File

@ -0,0 +1,27 @@
import gql from 'graphql-tag';
import { capitalize } from 'twenty-shared';
type RestoreManyOperationFactoryParams = {
objectMetadataSingularName: string;
objectMetadataPluralName: string;
gqlFields: string;
filter: object;
};
export const restoreManyOperationFactory = ({
objectMetadataSingularName,
objectMetadataPluralName,
gqlFields,
filter,
}: RestoreManyOperationFactoryParams) => ({
query: gql`
mutation Restore${capitalize(objectMetadataPluralName)}($filter: ${capitalize(objectMetadataSingularName)}FilterInput!) {
restore${capitalize(objectMetadataPluralName)}(filter: $filter) {
${gqlFields}
}
}
`,
variables: {
filter,
},
});

View File

@ -0,0 +1,25 @@
import gql from 'graphql-tag';
import { capitalize } from 'twenty-shared';
type RestoreOneOperationFactoryParams = {
objectMetadataSingularName: string;
gqlFields: string;
recordId: string;
};
export const restoreOneOperationFactory = ({
objectMetadataSingularName,
gqlFields,
recordId,
}: RestoreOneOperationFactoryParams) => ({
query: gql`
mutation Restore${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: ID!) {
restore${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id) {
${gqlFields}
}
}
`,
variables: {
[`${objectMetadataSingularName}Id`]: recordId,
},
});