[permissions] Return all columns by default for upsert (#13587)

This commit is contained in:
Marie
2025-08-04 16:49:56 +02:00
committed by GitHub
parent 72ffee39a7
commit 2cebb72447
3 changed files with 40 additions and 17 deletions
@@ -170,7 +170,7 @@ export class WorkspaceEntityManager extends EntityManager {
entity:
| QueryDeepPartialEntityWithNestedRelationFields<Entity>
| QueryDeepPartialEntityWithNestedRelationFields<Entity>[],
selectedColumns: string[] = [],
selectedColumns: string[] | '*' = '*',
permissionOptions?: PermissionOptions,
): Promise<InsertResult> {
const metadata = this.connection.getMetadata(target);
@@ -197,7 +197,7 @@ export class WorkspaceEntityManager extends EntityManager {
shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions;
},
selectedColumns: string[] = [],
selectedColumns: string[] | '*' = '*',
): Promise<InsertResult> {
const metadata = this.connection.getMetadata(target);
let options;
@@ -269,7 +269,7 @@ export class WorkspaceEntityManager extends EntityManager {
| unknown,
partialEntity: QueryDeepPartialEntity<Entity>,
permissionOptions?: PermissionOptions,
selectedColumns: string[] = [],
selectedColumns: string[] | '*' = '*',
): Promise<UpdateResult> {
const metadata = this.connection.getMetadata(target);
@@ -324,7 +324,7 @@ export class WorkspaceEntityManager extends EntityManager {
partialEntity: QueryDeepPartialEntity<Entity>;
}[],
permissionOptions?: PermissionOptions,
selectedColumns?: string[],
selectedColumns: string[] | '*' = '*',
): Promise<UpdateResult> {
const metadata = this.connection.getMetadata(target);
@@ -346,7 +346,7 @@ export class WorkspaceEntityManager extends EntityManager {
propertyPath: string,
value: number | string,
permissionOptions?: PermissionOptions,
selectedColumns: string[] = [],
selectedColumns: string[] | '*' = '*',
): Promise<UpdateResult> {
const metadata = this.connection.getMetadata(target);
const column = metadata.findColumnWithPropertyPath(propertyPath);
@@ -947,7 +947,7 @@ export class WorkspaceEntityManager extends EntityManager {
propertyPath: string,
value: number | string,
permissionOptions?: PermissionOptions,
selectedColumns: string[] = [],
selectedColumns: string[] | '*' = '*',
): Promise<UpdateResult> {
const metadata = this.connection.getMetadata(target);
const column = metadata.findColumnWithPropertyPath(propertyPath);
@@ -49,7 +49,7 @@ export const validateOperationIsPermittedOrThrow = ({
operationType: OperationType;
objectRecordsPermissions: ObjectRecordsPermissions;
objectMetadataMaps: ObjectMetadataMaps;
selectedColumns: string[];
selectedColumns: string[] | '*';
isFieldPermissionsEnabled?: boolean;
allFieldsSelected: boolean;
updatedColumns: string[];
@@ -198,7 +198,7 @@ export const validateQueryIsPermittedOrThrow = ({
(select) => select.selection === mainEntity,
);
let selectedColumns: string[] = [];
let selectedColumns: string[] | '*' = [];
let updatedColumns: string[] = [];
if (isFieldPermissionsEnabled) {
@@ -248,15 +248,19 @@ const validateReadFieldPermissionOrThrow = ({
allFieldsSelected,
}: {
restrictedFields: RestrictedFields;
selectedColumns: string[];
selectedColumns: string[] | '*';
columnNameToFieldMetadataIdMap: Record<string, string>;
allFieldsSelected?: boolean;
}) => {
if (isEmpty(restrictedFields)) {
const noReadRestrictions =
isEmpty(restrictedFields) ||
Object.values(restrictedFields).every((field) => field.canRead !== false);
if (noReadRestrictions) {
return;
}
if (allFieldsSelected) {
if (allFieldsSelected || selectedColumns === '*') {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSION_DENIED,
PermissionsExceptionCode.PERMISSION_DENIED,
@@ -321,7 +325,7 @@ const getSelectedColumnsFromExpressionMap = ({
expressionMap: QueryExpressionMap;
allFieldsSelected: boolean;
}) => {
let selectedColumns: string[] = [];
let selectedColumns: string[] | '*' = [];
if (
['update', 'insert', 'delete', 'soft-delete', 'restore'].includes(
@@ -333,7 +337,8 @@ const getSelectedColumnsFromExpressionMap = ({
'Returning columns are not set for update query',
);
}
selectedColumns = [expressionMap.returning].flat();
selectedColumns =
expressionMap.returning === '*' ? '*' : [expressionMap.returning].flat();
} else if (!allFieldsSelected) {
selectedColumns = getSelectedColumnsFromExpressionMapSelects(
expressionMap.selects,
@@ -16,6 +16,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
import { QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type';
import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type';
import { RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import {
TwentyORMException,
TwentyORMExceptionCode,
@@ -29,7 +30,6 @@ import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/wo
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
export class WorkspaceInsertQueryBuilder<
T extends ObjectLiteral,
@@ -155,7 +155,7 @@ export class WorkspaceInsertQueryBuilder<
const afterResult = await eventSelectQueryBuilder.getMany();
const formattedResult = formatResult<T[]>(
const formattedResultForEvent = formatResult<T[]>(
afterResult,
objectMetadata,
this.internalContext.objectMetadataMaps,
@@ -165,12 +165,30 @@ export class WorkspaceInsertQueryBuilder<
action: DatabaseEventAction.CREATED,
objectMetadataItem: objectMetadata,
workspaceId: this.internalContext.workspaceId,
entities: formattedResult,
entities: formattedResultForEvent,
authContext: this.authContext,
});
// TypeORM returns all entity columns for insertions
const resultWithoutInsertionExtraColumns = result.raw.map(
(rawResult: Record<string, string>) =>
Object.keys(rawResult)
.filter((key) => this.expressionMap.returning.includes(key))
.reduce((filtered: Record<string, string>, key) => {
filtered[key] = rawResult[key];
return filtered;
}, {}),
);
const formattedResult = formatResult<T[]>(
resultWithoutInsertionExtraColumns,
objectMetadata,
this.internalContext.objectMetadataMaps,
);
return {
raw: afterResult,
raw: resultWithoutInsertionExtraColumns,
generatedMaps: formattedResult,
identifiers: result.identifiers,
};