[permissions] Add permission gates on workspaceMember (#10447)
- Adding permission gates on workspaceMember to only allow user with admin permissions OR users attempting to update or delete themself to perform write operations on workspaceMember object - Reverting some changes to treat workflow objects as regular metadata objects (any user can interact with them) - (fix) Block updates on soft deleted records
This commit is contained in:
@ -1,11 +1,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
import {
|
import { capitalize, PermissionsOnAllObjectRecords } from 'twenty-shared';
|
||||||
capitalize,
|
|
||||||
isObjectRecordUnderObjectRecordsPermissions,
|
|
||||||
PermissionsOnAllObjectRecords,
|
|
||||||
} from 'twenty-shared';
|
|
||||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
import { DataSource, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
@ -103,12 +99,9 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
featureFlagsMap[FeatureFlagKey.IsPermissionsEnabled] &&
|
featureFlagsMap[FeatureFlagKey.IsPermissionsEnabled] &&
|
||||||
isObjectRecordUnderObjectRecordsPermissions({
|
!objectMetadataItemWithFieldMaps.isSystem
|
||||||
isCustom: objectMetadataItemWithFieldMaps.isCustom,
|
|
||||||
nameSingular: objectMetadataItemWithFieldMaps.nameSingular,
|
|
||||||
})
|
|
||||||
) {
|
) {
|
||||||
await this.validateCustomObjectPermissionsOrThrow({
|
await this.validateObjectRecordPermissionsOrThrow({
|
||||||
operationName,
|
operationName,
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
@ -230,7 +223,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async validateCustomObjectPermissionsOrThrow({
|
private async validateObjectRecordPermissionsOrThrow({
|
||||||
operationName,
|
operationName,
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GraphqlQueryBaseResolverService,
|
GraphqlQueryBaseResolverService,
|
||||||
GraphqlQueryResolverExecutionArgs,
|
GraphqlQueryResolverExecutionArgs,
|
||||||
@ -9,6 +11,10 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu
|
|||||||
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
|
||||||
|
import {
|
||||||
|
GraphqlQueryRunnerException,
|
||||||
|
GraphqlQueryRunnerExceptionCode,
|
||||||
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
@ -49,6 +55,13 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isEmpty(formattedExistingRecords)) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'Records not found',
|
||||||
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const tableName = computeTableName(
|
const tableName = computeTableName(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
objectMetadataItemWithFieldMaps.isCustom,
|
objectMetadataItemWithFieldMaps.isCustom,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import isEmpty from 'lodash.isempty';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GraphqlQueryBaseResolverService,
|
GraphqlQueryBaseResolverService,
|
||||||
GraphqlQueryResolverExecutionArgs,
|
GraphqlQueryResolverExecutionArgs,
|
||||||
@ -53,6 +55,13 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isEmpty(formattedExistingRecords)) {
|
||||||
|
throw new GraphqlQueryRunnerException(
|
||||||
|
'Record not found',
|
||||||
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
||||||
.update(data)
|
.update(data)
|
||||||
.where({ id: executionArgs.args.id })
|
.where({ id: executionArgs.args.id })
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.createMany`)
|
||||||
|
export class WorkspaceMemberCreateManyPreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: CreateManyResolverArgs,
|
||||||
|
): Promise<CreateManyResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.createOne`)
|
||||||
|
export class WorkspaceMemberCreateOnePreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: CreateOneResolverArgs,
|
||||||
|
): Promise<CreateOneResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,17 +1,32 @@
|
|||||||
import { MethodNotAllowedException } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
@WorkspaceQueryHook(`workspaceMember.deleteMany`)
|
@WorkspaceQueryHook(`workspaceMember.deleteMany`)
|
||||||
export class WorkspaceMemberDeleteManyPreQueryHook
|
export class WorkspaceMemberDeleteManyPreQueryHook
|
||||||
implements WorkspaceQueryHookInstance
|
implements WorkspaceQueryHookInstance
|
||||||
{
|
{
|
||||||
constructor() {}
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
async execute(): Promise<DeleteManyResolverArgs> {
|
async execute(
|
||||||
throw new MethodNotAllowedException('Method not allowed.');
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: DeleteManyResolverArgs,
|
||||||
|
): Promise<DeleteManyResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,25 +5,40 @@ import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runne
|
|||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
@WorkspaceQueryHook(`workspaceMember.deleteOne`)
|
@WorkspaceQueryHook(`workspaceMember.deleteOne`)
|
||||||
export class WorkspaceMemberDeleteOnePreQueryHook
|
export class WorkspaceMemberDeleteOnePreQueryHook
|
||||||
implements WorkspaceQueryHookInstance
|
implements WorkspaceQueryHookInstance
|
||||||
{
|
{
|
||||||
constructor(private readonly twentyORMManager: TwentyORMManager) {}
|
constructor(
|
||||||
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
// There is no need to validate the user's access to the workspace member since we don't have permission yet.
|
|
||||||
async execute(
|
async execute(
|
||||||
authContext: AuthContext,
|
authContext: AuthContext,
|
||||||
objectName: string,
|
objectName: string,
|
||||||
payload: DeleteOneResolverArgs,
|
payload: DeleteOneResolverArgs,
|
||||||
): Promise<DeleteOneResolverArgs> {
|
): Promise<DeleteOneResolverArgs> {
|
||||||
|
const targettedWorkspaceMemberId = payload.id;
|
||||||
|
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
targettedWorkspaceMemberId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const attachmentRepository =
|
const attachmentRepository =
|
||||||
await this.twentyORMManager.getRepository<AttachmentWorkspaceEntity>(
|
await this.twentyORMManager.getRepository<AttachmentWorkspaceEntity>(
|
||||||
'attachment',
|
'attachment',
|
||||||
);
|
);
|
||||||
|
|
||||||
const authorId = payload.id;
|
const authorId = targettedWorkspaceMemberId;
|
||||||
|
|
||||||
await attachmentRepository.delete({
|
await attachmentRepository.delete({
|
||||||
authorId,
|
authorId,
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { DeleteManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.destroyMany`)
|
||||||
|
export class WorkspaceMemberDestroyManyPreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: DeleteManyResolverArgs,
|
||||||
|
): Promise<DeleteManyResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.destroyOne`)
|
||||||
|
export class WorkspaceMemberDestroyOnePreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: DeleteOneResolverArgs,
|
||||||
|
): Promise<DeleteOneResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
targettedWorkspaceMemberId: payload.id,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { isDefined } from 'twenty-shared';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
import { SettingsPermissions } from 'src/engine/metadata-modules/permissions/constants/settings-permissions.constants';
|
||||||
|
import {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
PermissionsExceptionMessage,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
import { ApiKeyWorkspaceEntity } from 'src/modules/api-key/standard-objects/api-key.workspace-entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceMemberPreQueryHookService {
|
||||||
|
constructor(
|
||||||
|
private readonly permissionsService: PermissionsService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validateWorkspaceMemberUpdatePermissionOrThrow({
|
||||||
|
userWorkspaceId,
|
||||||
|
workspaceMemberId,
|
||||||
|
targettedWorkspaceMemberId,
|
||||||
|
workspaceId,
|
||||||
|
apiKey,
|
||||||
|
}: {
|
||||||
|
userWorkspaceId?: string;
|
||||||
|
workspaceMemberId?: string;
|
||||||
|
targettedWorkspaceMemberId?: string;
|
||||||
|
workspaceId: string;
|
||||||
|
apiKey?: ApiKeyWorkspaceEntity | null;
|
||||||
|
}) {
|
||||||
|
const featureFlagsMap =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
|
const isPermissionsEnabled =
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsPermissionsEnabled];
|
||||||
|
|
||||||
|
if (!isPermissionsEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDefined(apiKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!userWorkspaceId) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.USER_WORKSPACE_NOT_FOUND,
|
||||||
|
PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isDefined(targettedWorkspaceMemberId) &&
|
||||||
|
workspaceMemberId === targettedWorkspaceMemberId
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
await this.permissionsService.userHasWorkspaceSettingPermission({
|
||||||
|
userWorkspaceId,
|
||||||
|
workspaceId,
|
||||||
|
_setting: SettingsPermissions.WORKSPACE_MEMBERS,
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,33 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { WorkspaceMemberCreateManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-create-many.pre-query.hook';
|
||||||
|
import { WorkspaceMemberCreateOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-create-one.pre-query.hook';
|
||||||
import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook';
|
import { WorkspaceMemberDeleteManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-many.pre-query.hook';
|
||||||
import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook';
|
import { WorkspaceMemberDeleteOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-delete-one.pre-query.hook';
|
||||||
|
import { WorkspaceMemberDestroyManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-destroy-many.pre-query.hook';
|
||||||
|
import { WorkspaceMemberDestroyOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-destroy-one.pre-query.hook';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
import { WorkspaceMemberRestoreManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-restore-many.pre-query.hook';
|
||||||
|
import { WorkspaceMemberRestoreOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-restore-one.pre-query.hook';
|
||||||
|
import { WorkspaceMemberUpdateManyPreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-update-many.pre-query.hook';
|
||||||
|
import { WorkspaceMemberUpdateOnePreQueryHook } from 'src/modules/workspace-member/query-hooks/workspace-member-update-one.pre-query.hook';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
|
WorkspaceMemberPreQueryHookService,
|
||||||
|
WorkspaceMemberCreateOnePreQueryHook,
|
||||||
|
WorkspaceMemberCreateManyPreQueryHook,
|
||||||
WorkspaceMemberDeleteOnePreQueryHook,
|
WorkspaceMemberDeleteOnePreQueryHook,
|
||||||
WorkspaceMemberDeleteManyPreQueryHook,
|
WorkspaceMemberDeleteManyPreQueryHook,
|
||||||
|
WorkspaceMemberDestroyOnePreQueryHook,
|
||||||
|
WorkspaceMemberDestroyManyPreQueryHook,
|
||||||
|
WorkspaceMemberRestoreOnePreQueryHook,
|
||||||
|
WorkspaceMemberRestoreManyPreQueryHook,
|
||||||
|
WorkspaceMemberUpdateOnePreQueryHook,
|
||||||
|
WorkspaceMemberUpdateManyPreQueryHook,
|
||||||
],
|
],
|
||||||
|
imports: [FeatureFlagModule, PermissionsModule],
|
||||||
})
|
})
|
||||||
export class WorkspaceMemberQueryHookModule {}
|
export class WorkspaceMemberQueryHookModule {}
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { RestoreManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.restoreMany`)
|
||||||
|
export class WorkspaceMemberRestoreManyPreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: RestoreManyResolverArgs,
|
||||||
|
): Promise<RestoreManyResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { RestoreOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.restoreOne`)
|
||||||
|
export class WorkspaceMemberRestoreOnePreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: RestoreOneResolverArgs,
|
||||||
|
): Promise<RestoreOneResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
targettedWorkspaceMemberId: payload.id,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.updateMany`)
|
||||||
|
export class WorkspaceMemberUpdateManyPreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: UpdateManyResolverArgs,
|
||||||
|
): Promise<UpdateManyResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import { WorkspaceQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
import { UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { WorkspaceMemberPreQueryHookService } from 'src/modules/workspace-member/query-hooks/workspace-member-pre-query-hook.service';
|
||||||
|
|
||||||
|
@WorkspaceQueryHook(`workspaceMember.updateOne`)
|
||||||
|
export class WorkspaceMemberUpdateOnePreQueryHook
|
||||||
|
implements WorkspaceQueryHookInstance
|
||||||
|
{
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberPreQueryHookService: WorkspaceMemberPreQueryHookService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async execute(
|
||||||
|
authContext: AuthContext,
|
||||||
|
objectName: string,
|
||||||
|
payload: UpdateOneResolverArgs,
|
||||||
|
): Promise<UpdateOneResolverArgs> {
|
||||||
|
await this.workspaceMemberPreQueryHookService.validateWorkspaceMemberUpdatePermissionOrThrow(
|
||||||
|
{
|
||||||
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
|
targettedWorkspaceMemberId: payload.id,
|
||||||
|
workspaceId: authContext.workspace.id,
|
||||||
|
apiKey: authContext.apiKey,
|
||||||
|
workspaceMemberId: authContext.workspaceMemberId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,211 @@
|
|||||||
|
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 { makeGraphqlAPIRequestWithMemberRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-member-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 { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.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 { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
|
const WORKSPACE_MEMBER_GQL_FIELDS = `
|
||||||
|
id
|
||||||
|
name {
|
||||||
|
firstName
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
describe('workspace members 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('updateOne', () => {
|
||||||
|
it('should allow update when user is updating themself (member role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
data: {
|
||||||
|
name: {
|
||||||
|
firstName: 'Jony',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({
|
||||||
|
updateWorkspaceMember: {
|
||||||
|
id: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
name: {
|
||||||
|
firstName: 'Jony',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.body.errors).toBeUndefined();
|
||||||
|
});
|
||||||
|
it('should throw when user does not have permission (member role)', async () => {
|
||||||
|
const graphqlOperation = updateOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
|
||||||
|
data: {
|
||||||
|
name: {
|
||||||
|
firstName: 'Not Tim',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ updateWorkspaceMember: 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteOne', () => {
|
||||||
|
afterEach(async () => {
|
||||||
|
// Restore the deleted user to maintain test isolation
|
||||||
|
const restoreOperation = restoreOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
});
|
||||||
|
|
||||||
|
await makeGraphqlAPIRequest(restoreOperation);
|
||||||
|
});
|
||||||
|
it('should allow delete when user is deleting themself (member role)', async () => {
|
||||||
|
const deleteOperation = deleteOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const deleteResponse =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(deleteOperation);
|
||||||
|
|
||||||
|
expect(deleteResponse.body.data).toStrictEqual({
|
||||||
|
deleteWorkspaceMember: {
|
||||||
|
id: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
name: {
|
||||||
|
firstName: 'Jony',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(deleteResponse.body.errors).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when user does not have permission (member role)', async () => {
|
||||||
|
const graphqlOperation = deleteOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(graphqlOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({ deleteWorkspaceMember: 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('restoreOne', () => {
|
||||||
|
it('should allow restore when user is restoring themself (member role)', async () => {
|
||||||
|
const restoreOperation = restoreOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(restoreOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({
|
||||||
|
restoreWorkspaceMember: {
|
||||||
|
id: DEV_SEED_WORKSPACE_MEMBER_IDS.JONY,
|
||||||
|
name: {
|
||||||
|
firstName: 'Jony',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(response.body.errors).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw when user does not have permission (member role)', async () => {
|
||||||
|
const restoreOperation = restoreOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
recordId: DEV_SEED_WORKSPACE_MEMBER_IDS.TIM,
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(restoreOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({
|
||||||
|
restoreWorkspaceMember: 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createOne', () => {
|
||||||
|
it('should throw when user does not have permission (member role)', async () => {
|
||||||
|
const createOperation = createOneOperationFactory({
|
||||||
|
objectMetadataSingularName: 'workspaceMember',
|
||||||
|
gqlFields: WORKSPACE_MEMBER_GQL_FIELDS,
|
||||||
|
data: {
|
||||||
|
userId: 'cc80c2e9-3002-46ac-bcc6-24e524713f21',
|
||||||
|
name: {
|
||||||
|
firstName: 'New',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const response =
|
||||||
|
await makeGraphqlAPIRequestWithMemberRole(createOperation);
|
||||||
|
|
||||||
|
expect(response.body.data).toStrictEqual({
|
||||||
|
createWorkspaceMember: 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,21 @@
|
|||||||
|
import { ASTNode, print } from 'graphql';
|
||||||
|
import request from 'supertest';
|
||||||
|
|
||||||
|
type GraphqlOperation = {
|
||||||
|
query: ASTNode;
|
||||||
|
variables?: Record<string, unknown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const makeGraphqlAPIRequestWithMemberRole = (
|
||||||
|
graphqlOperation: GraphqlOperation,
|
||||||
|
) => {
|
||||||
|
const client = request(`http://localhost:${APP_PORT}`);
|
||||||
|
|
||||||
|
return client
|
||||||
|
.post('/graphql')
|
||||||
|
.set('Authorization', `Bearer ${MEMBER_ACCESS_TOKEN}`)
|
||||||
|
.send({
|
||||||
|
query: print(graphqlOperation.query),
|
||||||
|
variables: graphqlOperation.variables || {},
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,5 +1,4 @@
|
|||||||
export * from './fieldMetadata';
|
export * from './fieldMetadata';
|
||||||
export * from './image';
|
export * from './image';
|
||||||
export * from './permissions';
|
|
||||||
export * from './strings';
|
export * from './strings';
|
||||||
export * from './validation';
|
export * from './validation';
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
export * from './isObjectRecordUnderObjectRecordsPermissions';
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
import { STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS } from 'src/constants';
|
|
||||||
|
|
||||||
export const isObjectRecordUnderObjectRecordsPermissions = ({
|
|
||||||
isCustom,
|
|
||||||
nameSingular,
|
|
||||||
}: {
|
|
||||||
isCustom: boolean;
|
|
||||||
nameSingular: string;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
isCustom ||
|
|
||||||
STANDARD_OBJECT_RECORDS_UNDER_OBJECT_RECORDS_PERMISSIONS.includes(
|
|
||||||
nameSingular,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user