[permissions] Add SettingsPermissionGuard on data model and roles features (#10063)

Adding SettingsPermissionsGuard to execute permission check. 

The guard is added directly in resolver, either at resolver level (ex:
roles) or resolver-endpoint level (ex: metadata). this can be challenged
!
This commit is contained in:
Marie
2025-02-07 16:48:04 +01:00
committed by GitHub
parent 859e7c94f9
commit a24e411384
11 changed files with 144 additions and 64 deletions

View File

@ -0,0 +1,63 @@
import {
CanActivate,
ExecutionContext,
Injectable,
mixin,
Type,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { SettingsFeatures } 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 {
PermissionsException,
PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
export const SettingsPermissionsGuard = (
requiredPermission: SettingsFeatures,
): Type<CanActivate> => {
@Injectable()
class SettingsPermissionsMixin implements CanActivate {
constructor(
private readonly featureFlagService: FeatureFlagService,
private readonly permissionsService: PermissionsService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = GqlExecutionContext.create(context);
const workspaceId = ctx.getContext().req.workspace.id;
const permissionsEnabled = await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IsPermissionsEnabled,
workspaceId,
);
if (!permissionsEnabled) {
return true;
}
const userWorkspaceId = ctx.getContext().req.userWorkspaceId;
const hasPermission =
await this.permissionsService.userHasWorkspaceSettingPermission({
userWorkspaceId,
_setting: requiredPermission,
});
if (hasPermission === true) {
return true;
}
throw new PermissionsException(
'User is not authorized to perform this action',
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
}
return mixin(SettingsPermissionsMixin);
};

View File

@ -22,6 +22,7 @@ import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-m
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator'; import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@ -52,6 +53,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
TypeORMModule, TypeORMModule,
ActorModule, ActorModule,
ViewModule, ViewModule,
PermissionsModule,
], ],
services: [ services: [
IsFieldMetadataDefaultValue, IsFieldMetadataDefaultValue,

View File

@ -1,6 +1,7 @@
import { import {
BadRequestException, BadRequestException,
UnauthorizedException, UnauthorizedException,
UseFilters,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
@ -12,7 +13,7 @@ import {
Resolver, Resolver,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { FieldMetadataType } from 'twenty-shared'; import { FieldMetadataType, SettingsFeatures } from 'twenty-shared';
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';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@ -20,6 +21,7 @@ import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.typ
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
@ -34,10 +36,12 @@ import {
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; } from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service';
import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
@UseGuards(WorkspaceAuthGuard) @UseGuards(WorkspaceAuthGuard)
@Resolver(() => FieldMetadataDTO) @Resolver(() => FieldMetadataDTO)
@UseFilters(PermissionsGraphqlApiExceptionFilter)
export class FieldMetadataResolver { export class FieldMetadataResolver {
constructor( constructor(
private readonly fieldMetadataService: FieldMetadataService, private readonly fieldMetadataService: FieldMetadataService,
@ -68,6 +72,7 @@ export class FieldMetadataResolver {
); );
} }
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => FieldMetadataDTO) @Mutation(() => FieldMetadataDTO)
async createOneField( async createOneField(
@Args('input') input: CreateOneFieldMetadataInput, @Args('input') input: CreateOneFieldMetadataInput,
@ -83,6 +88,7 @@ export class FieldMetadataResolver {
} }
} }
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => FieldMetadataDTO) @Mutation(() => FieldMetadataDTO)
async updateOneField( async updateOneField(
@Args('input') input: UpdateOneFieldMetadataInput, @Args('input') input: UpdateOneFieldMetadataInput,
@ -98,6 +104,7 @@ export class FieldMetadataResolver {
} }
} }
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => FieldMetadataDTO) @Mutation(() => FieldMetadataDTO)
async deleteOneField( async deleteOneField(
@Args('input') input: DeleteOneFieldInput, @Args('input') input: DeleteOneFieldInput,

View File

@ -7,9 +7,12 @@ import {
PagingStrategies, PagingStrategies,
} from '@ptc-org/nestjs-query-graphql'; } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { SettingsFeatures } from 'twenty-shared';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
@ -20,6 +23,8 @@ import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metad
import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service'; import { ObjectMetadataMigrationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-migration.service';
import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service'; import { ObjectMetadataRelatedRecordsService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-related-records.service';
import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service'; import { ObjectMetadataRelationService } from 'src/engine/metadata-modules/object-metadata/services/object-metadata-relation.service';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module'; import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module';
import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; import { SearchModule } from 'src/engine/metadata-modules/search/search.module';
@ -51,6 +56,8 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
RemoteTableRelationsModule, RemoteTableRelationsModule,
SearchModule, SearchModule,
IndexMetadataModule, IndexMetadataModule,
FeatureFlagModule,
PermissionsModule,
], ],
services: [ services: [
ObjectMetadataService, ObjectMetadataService,
@ -71,11 +78,13 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
}, },
create: { create: {
many: { disabled: true }, many: { disabled: true },
guards: [SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)],
}, },
update: { disabled: true }, update: { disabled: true },
delete: { disabled: true }, delete: { disabled: true },
guards: [WorkspaceAuthGuard], guards: [WorkspaceAuthGuard],
interceptors: [ObjectMetadataGraphqlApiExceptionInterceptor], interceptors: [ObjectMetadataGraphqlApiExceptionInterceptor],
filters: [PermissionsGraphqlApiExceptionFilter],
}, },
], ],
}), }),

View File

@ -1,4 +1,4 @@
import { UseGuards } from '@nestjs/common'; import { UseFilters, UseGuards } from '@nestjs/common';
import { import {
Args, Args,
Context, Context,
@ -8,9 +8,12 @@ import {
Resolver, Resolver,
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import { SettingsFeatures } from 'twenty-shared';
import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
@ -21,9 +24,11 @@ import {
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util'; import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
@UseGuards(WorkspaceAuthGuard) @UseGuards(WorkspaceAuthGuard)
@Resolver(() => ObjectMetadataDTO) @Resolver(() => ObjectMetadataDTO)
@UseFilters(PermissionsGraphqlApiExceptionFilter)
export class ObjectMetadataResolver { export class ObjectMetadataResolver {
constructor( constructor(
private readonly objectMetadataService: ObjectMetadataService, private readonly objectMetadataService: ObjectMetadataService,
@ -66,6 +71,7 @@ export class ObjectMetadataResolver {
); );
} }
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => ObjectMetadataDTO) @Mutation(() => ObjectMetadataDTO)
async deleteOneObject( async deleteOneObject(
@Args('input') input: DeleteOneObjectInput, @Args('input') input: DeleteOneObjectInput,
@ -81,6 +87,7 @@ export class ObjectMetadataResolver {
} }
} }
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => ObjectMetadataDTO) @Mutation(() => ObjectMetadataDTO)
async updateOneObject( async updateOneObject(
@Args('input') input: UpdateOneObjectInput, @Args('input') input: UpdateOneObjectInput,

View File

@ -3,10 +3,6 @@ import { Injectable } from '@nestjs/common';
import { SettingsFeatures } from 'twenty-shared'; import { SettingsFeatures } from 'twenty-shared';
import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service'; import { EnvironmentService } from 'src/engine/core-modules/environment/environment.service';
import {
PermissionsException,
PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@Injectable() @Injectable()
@ -40,25 +36,22 @@ export class PermissionsService {
); );
} }
public async validateUserHasWorkspaceSettingPermissionOrThrow({ public async userHasWorkspaceSettingPermission({
userWorkspaceId, userWorkspaceId,
setting, _setting,
}: { }: {
userWorkspaceId: string; userWorkspaceId: string;
setting: SettingsFeatures; _setting: SettingsFeatures;
}): Promise<void> { }): Promise<boolean> {
const [roleOfUserWorkspace] = await this.userRoleService const [roleOfUserWorkspace] = await this.userRoleService
.getRolesByUserWorkspaces([userWorkspaceId]) .getRolesByUserWorkspaces([userWorkspaceId])
.then((roles) => roles?.get(userWorkspaceId) ?? []); .then((roles) => roles?.get(userWorkspaceId) ?? []);
if (roleOfUserWorkspace?.canUpdateAllSettings === true) { if (roleOfUserWorkspace?.canUpdateAllSettings === true) {
return; return true;
} }
throw new PermissionsException( return false;
`User does not have permission to access this setting: ${setting}`,
PermissionsExceptionCode.PERMISSION_DENIED,
);
} }
public async isPermissionsEnabled(): Promise<boolean> { public async isPermissionsEnabled(): Promise<boolean> {

View File

@ -1,3 +1,5 @@
import { Catch, ExceptionFilter } from '@nestjs/common';
import { import {
ForbiddenError, ForbiddenError,
InternalServerError, InternalServerError,
@ -7,16 +9,15 @@ import {
PermissionsExceptionCode, PermissionsExceptionCode,
} from 'src/engine/metadata-modules/permissions/permissions.exception'; } from 'src/engine/metadata-modules/permissions/permissions.exception';
export const permissionsGraphqlApiExceptionHandler = (error: Error) => { @Catch(PermissionsException)
if (error instanceof PermissionsException) { export class PermissionsGraphqlApiExceptionFilter implements ExceptionFilter {
switch (error.code) { catch(exception: PermissionsException) {
switch (exception.code) {
case PermissionsExceptionCode.PERMISSION_DENIED: case PermissionsExceptionCode.PERMISSION_DENIED:
case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN: case PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN:
throw new ForbiddenError(error.message); throw new ForbiddenError(exception.message);
default: default:
throw new InternalServerError(error.message); throw new InternalServerError(exception.message);
} }
} }
}
throw error;
};

View File

@ -5,12 +5,17 @@ import {
PagingStrategies, PagingStrategies,
} from '@ptc-org/nestjs-query-graphql'; } from '@ptc-org/nestjs-query-graphql';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { SettingsFeatures } from 'twenty-shared';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { RelationMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/relation-metadata/interceptors/relation-metadata-graphql-api-exception.interceptor'; import { RelationMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/relation-metadata/interceptors/relation-metadata-graphql-api-exception.interceptor';
import { RelationMetadataResolver } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.resolver'; import { RelationMetadataResolver } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.resolver';
import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
@ -39,6 +44,8 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
WorkspaceMigrationModule, WorkspaceMigrationModule,
WorkspaceCacheStorageModule, WorkspaceCacheStorageModule,
WorkspaceMetadataVersionModule, WorkspaceMetadataVersionModule,
FeatureFlagModule,
PermissionsModule,
], ],
services: [RelationMetadataService], services: [RelationMetadataService],
resolvers: [ resolvers: [
@ -48,11 +55,15 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
ServiceClass: RelationMetadataService, ServiceClass: RelationMetadataService,
CreateDTOClass: CreateRelationInput, CreateDTOClass: CreateRelationInput,
pagingStrategy: PagingStrategies.CURSOR, pagingStrategy: PagingStrategies.CURSOR,
create: { many: { disabled: true } }, create: {
many: { disabled: true },
guards: [SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL)],
},
update: { disabled: true }, update: { disabled: true },
delete: { disabled: true }, delete: { disabled: true },
guards: [WorkspaceAuthGuard], guards: [WorkspaceAuthGuard],
interceptors: [RelationMetadataGraphqlApiExceptionInterceptor], interceptors: [RelationMetadataGraphqlApiExceptionInterceptor],
filters: [PermissionsGraphqlApiExceptionFilter],
}, },
], ],
}), }),

View File

@ -1,9 +1,13 @@
import { UseGuards } from '@nestjs/common'; import { UseFilters, UseGuards } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql'; import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { SettingsFeatures } from 'twenty-shared';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input'; import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input';
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto'; import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service'; import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
@ -11,11 +15,13 @@ import { relationMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-
@UseGuards(WorkspaceAuthGuard) @UseGuards(WorkspaceAuthGuard)
@Resolver() @Resolver()
@UseFilters(PermissionsGraphqlApiExceptionFilter)
export class RelationMetadataResolver { export class RelationMetadataResolver {
constructor( constructor(
private readonly relationMetadataService: RelationMetadataService, private readonly relationMetadataService: RelationMetadataService,
) {} ) {}
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.DATA_MODEL))
@Mutation(() => RelationMetadataDTO) @Mutation(() => RelationMetadataDTO)
async deleteOneRelation( async deleteOneRelation(
@Args('input') input: DeleteOneRelationInput, @Args('input') input: DeleteOneRelationInput,

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity'; import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module'; import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@ -17,6 +18,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
UserRoleModule, UserRoleModule,
PermissionsModule, PermissionsModule,
UserWorkspaceModule, UserWorkspaceModule,
FeatureFlagModule,
], ],
providers: [RoleService, RoleResolver], providers: [RoleService, RoleResolver],
exports: [RoleService], exports: [RoleService],

View File

@ -1,3 +1,4 @@
import { UseFilters, UseGuards } from '@nestjs/common';
import { import {
Args, Args,
Mutation, Mutation,
@ -12,70 +13,48 @@ import { isDefined, SettingsFeatures } from 'twenty-shared';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service'; import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service'; import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { permissionsGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception-handler'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto'; import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { RoleService } from 'src/engine/metadata-modules/role/role.service'; import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service'; import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Resolver(() => RoleDTO) @Resolver(() => RoleDTO)
@UseGuards(SettingsPermissionsGuard(SettingsFeatures.ROLES))
@UseFilters(PermissionsGraphqlApiExceptionFilter)
export class RoleResolver { export class RoleResolver {
constructor( constructor(
private readonly userRoleService: UserRoleService, private readonly userRoleService: UserRoleService,
private readonly permissionsService: PermissionsService,
private readonly roleService: RoleService, private readonly roleService: RoleService,
private readonly userWorkspaceService: UserWorkspaceService, private readonly userWorkspaceService: UserWorkspaceService,
) {} ) {}
@Query(() => [RoleDTO]) @Query(() => [RoleDTO])
async getRoles( async getRoles(@AuthWorkspace() workspace: Workspace): Promise<RoleDTO[]> {
@AuthUserWorkspaceId() userWorkspaceId: string, const roles = await this.roleService.getWorkspaceRoles(workspace.id);
@AuthWorkspace() workspace: Workspace,
): Promise<RoleDTO[]> {
try {
await this.permissionsService.validateUserHasWorkspaceSettingPermissionOrThrow(
{
userWorkspaceId,
setting: SettingsFeatures.ROLES,
},
);
const roles = await this.roleService.getWorkspaceRoles(workspace.id); return roles.map((role) => ({
id: role.id,
return roles.map((role) => ({ label: role.label,
id: role.id, canUpdateAllSettings: role.canUpdateAllSettings,
label: role.label, description: role.description,
canUpdateAllSettings: role.canUpdateAllSettings, workspaceId: role.workspaceId,
description: role.description, createdAt: role.createdAt,
workspaceId: role.workspaceId, updatedAt: role.updatedAt,
createdAt: role.createdAt, isEditable: role.isEditable,
updatedAt: role.updatedAt, userWorkspaceRoles: role.userWorkspaceRoles,
isEditable: role.isEditable, }));
userWorkspaceRoles: role.userWorkspaceRoles,
}));
} catch (error) {
return permissionsGraphqlApiExceptionHandler(error);
}
} }
@Mutation(() => WorkspaceMember) @Mutation(() => WorkspaceMember)
async updateWorkspaceMemberRole( async updateWorkspaceMemberRole(
@AuthUserWorkspaceId() currentUserWorkspaceId: string,
@AuthWorkspace() workspace: Workspace, @AuthWorkspace() workspace: Workspace,
@Args('workspaceMemberId') workspaceMemberId: string, @Args('workspaceMemberId') workspaceMemberId: string,
@Args('roleId', { type: () => String, nullable: true }) @Args('roleId', { type: () => String, nullable: true })
roleId: string | null, roleId: string | null,
): Promise<WorkspaceMember> { ): Promise<WorkspaceMember> {
await this.permissionsService.validateUserHasWorkspaceSettingPermissionOrThrow(
{
userWorkspaceId: currentUserWorkspaceId,
setting: SettingsFeatures.ROLES,
},
);
const workspaceMember = const workspaceMember =
await this.userWorkspaceService.getWorkspaceMemberOrThrow({ await this.userWorkspaceService.getWorkspaceMemberOrThrow({
workspaceMemberId, workspaceMemberId,