[permissions] Add read field permission check layer (part 1) (#13376)
In this PR, behind a feature flag, we add a permission layer check based on the read permission. It is done by computing a map of an object's fields, where keys are the column names and values the fieldMetadata id, making them comparable to the restricted fields ids list stored in the permission cache. For mutations (create, update, delete, destroy), we need to check the read permission on the returned field, as they may differ from the updated field. The write field permission will be tackled in a different PR.
This commit is contained in:
+47
-3
@@ -17,6 +17,7 @@ import {
|
||||
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 { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
@@ -68,7 +69,19 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
|
||||
): Promise<InsertResult> {
|
||||
if (!executionArgs.args.upsert) {
|
||||
return await executionArgs.repository.insert(executionArgs.args.data);
|
||||
const { objectMetadataItemWithFieldMaps } = executionArgs.options;
|
||||
|
||||
const selectedColumns = buildColumnsToReturn({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
return await executionArgs.repository.insert(
|
||||
executionArgs.args.data,
|
||||
undefined,
|
||||
selectedColumns,
|
||||
);
|
||||
}
|
||||
|
||||
return this.performUpsertOperation(executionArgs);
|
||||
@@ -78,12 +91,20 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
|
||||
): Promise<InsertResult> {
|
||||
const { objectMetadataItemWithFieldMaps } = executionArgs.options;
|
||||
|
||||
const selectedColumns = buildColumnsToSelect({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const conflictingFields = this.getConflictingFields(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
const existingRecords = await this.findExistingRecords(
|
||||
executionArgs,
|
||||
conflictingFields,
|
||||
selectedColumns,
|
||||
);
|
||||
|
||||
const { recordsToUpdate, recordsToInsert } = this.categorizeRecords(
|
||||
@@ -98,17 +119,25 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
raw: [],
|
||||
};
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
await this.processRecordsToUpdate({
|
||||
partialRecordsToUpdate: recordsToUpdate,
|
||||
repository: executionArgs.repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
result,
|
||||
columnsToReturn,
|
||||
});
|
||||
|
||||
await this.processRecordsToInsert({
|
||||
recordsToInsert,
|
||||
repository: executionArgs.repository,
|
||||
result,
|
||||
columnsToReturn,
|
||||
});
|
||||
|
||||
return result;
|
||||
@@ -161,6 +190,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
fullPath: string;
|
||||
column: string;
|
||||
}[],
|
||||
selectedColumns: Record<string, boolean>,
|
||||
): Promise<Partial<ObjectRecord>[]> {
|
||||
const { objectMetadataItemWithFieldMaps } = executionArgs.options;
|
||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||
@@ -176,7 +206,11 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
queryBuilder.orWhere(condition);
|
||||
});
|
||||
|
||||
return await queryBuilder.getMany();
|
||||
return await queryBuilder
|
||||
.setFindOptions({
|
||||
select: selectedColumns,
|
||||
})
|
||||
.getMany();
|
||||
}
|
||||
|
||||
private getValueFromPath(
|
||||
@@ -265,11 +299,13 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
result,
|
||||
columnsToReturn,
|
||||
}: {
|
||||
partialRecordsToUpdate: Partial<ObjectRecord>[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
result: InsertResult;
|
||||
columnsToReturn: string[];
|
||||
}): Promise<void> {
|
||||
for (const partialRecordToUpdate of partialRecordsToUpdate) {
|
||||
const recordId = partialRecordToUpdate.id as string;
|
||||
@@ -284,6 +320,8 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
await repository.update(
|
||||
recordId,
|
||||
partialRecordToUpdateWithoutCreatedByUpdate,
|
||||
undefined,
|
||||
columnsToReturn,
|
||||
);
|
||||
|
||||
result.identifiers.push({ id: recordId });
|
||||
@@ -295,13 +333,19 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
recordsToInsert,
|
||||
repository,
|
||||
result,
|
||||
columnsToReturn,
|
||||
}: {
|
||||
recordsToInsert: Partial<ObjectRecord>[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
result: InsertResult;
|
||||
columnsToReturn: string[];
|
||||
}): Promise<void> {
|
||||
if (recordsToInsert.length > 0) {
|
||||
const insertResult = await repository.insert(recordsToInsert);
|
||||
const insertResult = await repository.insert(
|
||||
recordsToInsert,
|
||||
undefined,
|
||||
columnsToReturn,
|
||||
);
|
||||
|
||||
result.identifiers.push(...insertResult.identifiers);
|
||||
result.generatedMaps.push(...insertResult.generatedMaps);
|
||||
|
||||
+21
-5
@@ -12,6 +12,7 @@ import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-qu
|
||||
import { CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||
@@ -29,12 +30,27 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
||||
|
||||
const { roleId } = executionArgs;
|
||||
|
||||
const selectedColumns = buildColumnsToReturn({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const objectRecords: InsertResult = !executionArgs.args.upsert
|
||||
? await executionArgs.repository.insert(executionArgs.args.data)
|
||||
: await executionArgs.repository.upsert(executionArgs.args.data, {
|
||||
conflictPaths: ['id'],
|
||||
skipUpdateIfNoValuesChanged: true,
|
||||
});
|
||||
? await executionArgs.repository.insert(
|
||||
executionArgs.args.data,
|
||||
undefined,
|
||||
selectedColumns,
|
||||
)
|
||||
: await executionArgs.repository.upsert(
|
||||
executionArgs.args.data,
|
||||
{
|
||||
conflictPaths: ['id'],
|
||||
skipUpdateIfNoValuesChanged: true,
|
||||
},
|
||||
undefined,
|
||||
selectedColumns,
|
||||
);
|
||||
|
||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
|
||||
+6
-8
@@ -35,7 +35,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
||||
FindManyResolverArgs,
|
||||
IConnection<ObjectRecord>
|
||||
> {
|
||||
constructor(private readonly processAggregateHelper: ProcessAggregateHelper) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
@@ -109,13 +109,11 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
||||
appliedFilters,
|
||||
);
|
||||
|
||||
this.processAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder(
|
||||
{
|
||||
selectedAggregatedFields:
|
||||
executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
|
||||
queryBuilder: aggregateQueryBuilder,
|
||||
},
|
||||
);
|
||||
ProcessAggregateHelper.addSelectedAggregatedFieldsQueriesToQueryBuilder({
|
||||
selectedAggregatedFields:
|
||||
executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
|
||||
queryBuilder: aggregateQueryBuilder,
|
||||
});
|
||||
|
||||
const limit =
|
||||
executionArgs.args.first ?? executionArgs.args.last ?? QUERY_MAX_RECORDS;
|
||||
|
||||
Reference in New Issue
Block a user