Fix insert new record with RLS (#17164)
## Context Now that RLS predicates are applied, creating a record through the FE (which is empty by default) is failing if your role has predicates and your input does not respect them (which will always be true since, as said above, input will be pretty much empty) ## Implementation - Moved isMatching* filters to twenty-shared - Implemented isMatchingRlsPredicates utils in the backend (ORM) to check before insertion/update if the record is matching the current user role Rls predicates, reusing the isMatching* filters utils moved to twenty-shared - Frontend now applies RLS predicates before creating a new record (similarly to what we do with view filters) Note: It seems composite were not properly handled with view-filter insertion logic, since I'm reusing the util for now, the issue remains for RLS and will need to be addressed
This commit is contained in:
+2
@@ -145,6 +145,8 @@ describe('WorkspaceRepository', () => {
|
||||
canSoftDeleteObjectRecords: false,
|
||||
canDestroyObjectRecords: false,
|
||||
restrictedFields: {},
|
||||
rowLevelPermissionPredicates: [],
|
||||
rowLevelPermissionPredicateGroups: [],
|
||||
},
|
||||
};
|
||||
mockQueryRunner = {} as QueryRunner;
|
||||
|
||||
+23
@@ -31,6 +31,7 @@ import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util';
|
||||
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||
import { validateRLSPredicatesForRecords } from 'src/engine/twenty-orm/utils/validate-rls-predicates-for-records.util';
|
||||
|
||||
export class WorkspaceInsertQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@@ -178,6 +179,8 @@ export class WorkspaceInsertQueryBuilder<
|
||||
this.expressionMap.valuesSet = updatedValues;
|
||||
}
|
||||
|
||||
this.validateRLSPredicatesForInsert();
|
||||
|
||||
const result = await super.execute();
|
||||
const eventSelectQueryBuilder = (
|
||||
this.connection.manager as WorkspaceEntityManager
|
||||
@@ -269,6 +272,26 @@ export class WorkspaceInsertQueryBuilder<
|
||||
}
|
||||
}
|
||||
|
||||
private validateRLSPredicatesForInsert(): void {
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const valuesToInsert = Array.isArray(this.expressionMap.valuesSet)
|
||||
? this.expressionMap.valuesSet
|
||||
: [this.expressionMap.valuesSet];
|
||||
|
||||
validateRLSPredicatesForRecords({
|
||||
records: valuesToInsert,
|
||||
objectMetadata,
|
||||
internalContext: this.internalContext,
|
||||
authContext: this.authContext,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
});
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
|
||||
+60
@@ -37,6 +37,7 @@ import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
import { formatTwentyOrmEventToDatabaseBatchEvent } from 'src/engine/twenty-orm/utils/format-twenty-orm-event-to-database-batch-event.util';
|
||||
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||
import { validateRLSPredicatesForRecords } from 'src/engine/twenty-orm/utils/validate-rls-predicates-for-records.util';
|
||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||
|
||||
export class WorkspaceUpdateQueryBuilder<
|
||||
@@ -175,6 +176,21 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
|
||||
this.applyRowLevelPermissionPredicates();
|
||||
|
||||
const valuesSet = this.expressionMap.valuesSet ?? {};
|
||||
const updatedRecords: T[] = formattedBefore.map(
|
||||
(record, index) =>
|
||||
({
|
||||
...record,
|
||||
...(Array.isArray(valuesSet)
|
||||
? (valuesSet[index] ?? valuesSet[0] ?? {})
|
||||
: valuesSet),
|
||||
}) as T,
|
||||
);
|
||||
|
||||
this.validateRLSPredicatesForUpdate({
|
||||
updatedRecords,
|
||||
});
|
||||
|
||||
const result = await super.execute();
|
||||
|
||||
const after = await eventSelectQueryBuilder.getMany();
|
||||
@@ -323,12 +339,34 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
}));
|
||||
}
|
||||
|
||||
const beforeRecordById = new Map<string, T>();
|
||||
|
||||
for (const beforeRecord of formattedBefore) {
|
||||
if (isDefined(beforeRecord.id)) {
|
||||
beforeRecordById.set(beforeRecord.id, beforeRecord);
|
||||
}
|
||||
}
|
||||
|
||||
for (const input of this.manyInputs) {
|
||||
this.expressionMap.valuesSet = input.partialEntity;
|
||||
this.where({ id: input.criteria });
|
||||
|
||||
this.applyRowLevelPermissionPredicates();
|
||||
|
||||
const beforeRecord = beforeRecordById.get(input.criteria);
|
||||
const updatedRecords = beforeRecord
|
||||
? [
|
||||
{
|
||||
...beforeRecord,
|
||||
...input.partialEntity,
|
||||
} as T,
|
||||
]
|
||||
: [];
|
||||
|
||||
this.validateRLSPredicatesForUpdate({
|
||||
updatedRecords,
|
||||
});
|
||||
|
||||
const result = await super.execute();
|
||||
|
||||
results.push(result);
|
||||
@@ -521,4 +559,26 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
featureFlagMap: this.featureFlagMap,
|
||||
});
|
||||
}
|
||||
|
||||
private validateRLSPredicatesForUpdate({
|
||||
updatedRecords,
|
||||
}: {
|
||||
updatedRecords: T[];
|
||||
}): void {
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
validateRLSPredicatesForRecords({
|
||||
records: updatedRecords,
|
||||
objectMetadata,
|
||||
internalContext: this.internalContext,
|
||||
authContext: this.authContext,
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
errorMessage:
|
||||
'Updated record does not satisfy row-level security constraints of your current role',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user