-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[permissions] Add permission gates on workspace-invitations #10394
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
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 { 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'; | ||
|
||
const client = request(`http://localhost:${APP_PORT}`); | ||
|
||
describe('api key and webhooks permissions', () => { | ||
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); | ||
}); | ||
describe('generateApiKeyToken', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
const queryData = { | ||
query: ` | ||
mutation generateApiKeyToken { | ||
generateApiKeyToken(apiKeyId: "test-api-key-id", expiresAt: "2025-01-01T00:00:00Z") { | ||
token | ||
} | ||
} | ||
`, | ||
}; | ||
|
||
await client | ||
.post('/graphql') | ||
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`) | ||
.send(queryData) | ||
.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); | ||
}); | ||
}); | ||
}); | ||
Comment on lines
+31
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Missing positive test case to verify admin role can successfully generate API key token |
||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,245 @@ | ||
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 { createCustomTextFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/create-custom-text-field-metadata.util'; | ||
import { createOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/create-one-field-metadata-factory.util'; | ||
import { deleteOneFieldMetadataItemFactory } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata-factory.util'; | ||
import { deleteFieldMetadata } from 'test/integration/metadata/suites/field-metadata/utils/delete-one-field-metadata.util'; | ||
import { updateOneFieldMetadataFactory } from 'test/integration/metadata/suites/field-metadata/utils/update-one-field-metadata-factory.util'; | ||
import { createOneObjectMetadataFactory } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata-factory.util'; | ||
import { createListingCustomObject } from 'test/integration/metadata/suites/object-metadata/utils/create-test-object-metadata.util'; | ||
import { deleteOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata-factory.util'; | ||
import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/object-metadata/utils/delete-one-object-metadata.util'; | ||
import { updateOneObjectMetadataItemFactory } from 'test/integration/metadata/suites/object-metadata/utils/update-one-object-metadata-factory.util'; | ||
import { makeMetadataAPIRequestWithMemberRole } from 'test/integration/metadata/suites/utils/make-metadata-api-request-with-member-role.util'; | ||
import { FieldMetadataType } from 'twenty-shared'; | ||
|
||
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('datamodel permissions', () => { | ||
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); | ||
}); | ||
describe('fieldMetadata', () => { | ||
let listingObjectId = ''; | ||
let testFieldId = ''; | ||
|
||
beforeAll(async () => { | ||
const { objectMetadataId: createdObjectId } = | ||
await createListingCustomObject(); | ||
|
||
listingObjectId = createdObjectId; | ||
|
||
const { fieldMetadataId: createdFieldMetadaId } = | ||
await createCustomTextFieldMetadata(createdObjectId); | ||
|
||
testFieldId = createdFieldMetadaId; | ||
}); | ||
afterAll(async () => { | ||
await deleteFieldMetadata(testFieldId); | ||
await deleteOneObjectMetadataItem(listingObjectId); | ||
}); | ||
Comment on lines
+54
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Consider wrapping cleanup operations in try/catch to ensure cleanup runs even if one operation fails |
||
describe('createOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const FIELD_NAME = 'testFieldForCreateOne'; | ||
const createFieldInput = { | ||
name: FIELD_NAME, | ||
label: 'Test Field For CreateOne', | ||
type: FieldMetadataType.TEXT, | ||
objectMetadataId: listingObjectId, | ||
}; | ||
|
||
// Act | ||
const graphqlOperation = createOneFieldMetadataFactory({ | ||
input: { field: createFieldInput }, | ||
gqlFields: ` | ||
id | ||
name | ||
`, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
|
||
describe('updateOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const updateFieldInput = { | ||
name: 'updatedName', | ||
label: 'Updated Name', | ||
}; | ||
|
||
const graphqlOperation = updateOneFieldMetadataFactory({ | ||
input: { id: testFieldId, update: updateFieldInput }, | ||
gqlFields: ` | ||
id | ||
name | ||
`, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
|
||
describe('deleteOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const graphqlOperation = deleteOneFieldMetadataItemFactory({ | ||
idToDelete: testFieldId, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('objectMetadata', () => { | ||
describe('createOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const graphqlOperation = createOneObjectMetadataFactory({ | ||
gqlFields: ` | ||
id | ||
`, | ||
input: { | ||
object: { | ||
labelPlural: 'Test Objects', | ||
labelSingular: 'Test Object', | ||
namePlural: 'testObjects', | ||
nameSingular: 'testObject', | ||
}, | ||
}, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
|
||
describe('update and delete a custom object', () => { | ||
let listingObjectId = ''; | ||
|
||
beforeAll(async () => { | ||
const { objectMetadataId: createdObjectId } = | ||
await createListingCustomObject(); | ||
|
||
listingObjectId = createdObjectId; | ||
}); | ||
afterAll(async () => { | ||
await deleteOneObjectMetadataItem(listingObjectId); | ||
}); | ||
describe('updateOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const graphqlOperation = updateOneObjectMetadataItemFactory({ | ||
gqlFields: ` | ||
id | ||
`, | ||
Comment on lines
+196
to
+198
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style: Inconsistent indentation in gqlFields template literal compared to other similar queries |
||
input: { | ||
idToUpdate: listingObjectId, | ||
updatePayload: { | ||
labelPlural: 'Updated Test Objects', | ||
labelSingular: 'Updated Test Object', | ||
}, | ||
}, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
describe('deleteOne', () => { | ||
it('should throw a permission error when user does not have permission (member role)', async () => { | ||
// Arrange | ||
const graphqlOperation = deleteOneObjectMetadataItemFactory({ | ||
idToDelete: listingObjectId, | ||
}); | ||
|
||
const response = | ||
await makeMetadataAPIRequestWithMemberRole(graphqlOperation); | ||
|
||
// Assert | ||
expect(response.body.data).toBeNull(); | ||
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, | ||
); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
logic: Test suite only covers generateApiKeyToken - missing test cases for webhook-related permissions that are mentioned in PR title