Files
twenty/packages/twenty-server/src/engine/twenty-orm/repository/permissions.utils.ts
Marie a9e73c6340 [permissions] Add permissions check layer in entityManager (#11818)
First and main step of
https://github.com/twentyhq/core-team-issues/issues/747

We are implementing a permission check layer in our custom
WorkspaceEntityManager by overriding all the db-executing methods (this
PR only overrides some as a POC, the rest will be done in the next PR).
Our custom repositories call entity managers under the hood to interact
with the db so this solves the repositories case too.
This is still behind the feature flag IsPermissionsV2Enabled.

In the next PR
- finish overriding all the methods required in WorkspaceEntityManager
- add tests
2025-05-05 14:06:54 +00:00

115 lines
3.1 KiB
TypeScript

import { ObjectRecordsPermissions } from 'twenty-shared/types';
import { QueryExpressionMap } from 'typeorm/query-builder/QueryExpressionMap';
import {
PermissionsException,
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
const mainEntity = expressionMap.aliases[0].metadata.name;
const operationType = expressionMap.queryType;
return {
mainEntity,
operationType,
};
};
export type OperationType =
| 'select'
| 'insert'
| 'update'
| 'delete'
| 'restore'
| 'soft-delete';
export const validateOperationIsPermittedOrThrow = ({
entityName,
operationType,
objectRecordsPermissions,
objectMetadataMaps,
}: {
entityName: string;
operationType: OperationType;
objectRecordsPermissions: ObjectRecordsPermissions;
objectMetadataMaps: ObjectMetadataMaps;
}) => {
const objectMetadataIdForEntity =
objectMetadataMaps.idByNameSingular[entityName];
const objectMetadataIsSystem =
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
if (objectMetadataIsSystem) {
return;
}
const permissionsForEntity = objectRecordsPermissions[entityName];
switch (operationType) {
case 'select':
if (!permissionsForEntity?.canRead) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
break;
case 'insert':
case 'update':
if (!permissionsForEntity?.canUpdate) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
break;
case 'delete':
if (!permissionsForEntity?.canDestroy) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
break;
case 'restore':
case 'soft-delete':
if (!permissionsForEntity?.canSoftDelete) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
);
}
break;
default:
throw new PermissionsException(
PermissionsExceptionMessage.UNKNOWN_OPERATION_NAME,
PermissionsExceptionCode.UNKNOWN_OPERATION_NAME,
);
}
};
export const validateQueryIsPermittedOrThrow = (
expressionMap: QueryExpressionMap,
objectRecordsPermissions: ObjectRecordsPermissions,
objectMetadataMaps: ObjectMetadataMaps,
shouldBypassPermissionChecks: boolean,
) => {
if (shouldBypassPermissionChecks) {
return;
}
const { mainEntity, operationType } =
getTargetEntityAndOperationType(expressionMap);
validateOperationIsPermittedOrThrow({
entityName: mainEntity,
operationType: operationType as OperationType,
objectRecordsPermissions,
objectMetadataMaps,
});
};