Skip to content

[permissions] Remove raw queries and restrict its usage #12360

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 2, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspa

const rows = await workspaceDataSource.query(
`SELECT id, "${richTextField.name}" FROM "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" WHERE "${richTextField.name}" IS NOT NULL`,
undefined, // parameters
undefined, // queryRunner
{
shouldBypassPermissionChecks: true,
},
);

this.logger.log(`Generating markdown for ${rows.length} records`);
Expand All @@ -251,6 +256,10 @@ export class MigrateRichTextContentPatchCommand extends ActiveOrSuspendedWorkspa
await workspaceDataSource.query(
`UPDATE "${schemaName}"."${computeTableName(objectMetadata.nameSingular, objectMetadata.isCustom)}" SET "${richTextField.name}V2Blocknote" = $1, "${richTextField.name}V2Markdown" = $2 WHERE id = $3`,
[blocknoteFieldValue, markdownFieldValue, row.id],
undefined, // queryRunner
{
shouldBypassPermissionChecks: true,
},
);
} catch (error) {
this.logger.log(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { InjectRepository } from '@nestjs/typeorm';

import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { FieldMetadataType } from 'twenty-shared/types';
import { Repository } from 'typeorm';

import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { generateDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/generate-default-value';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';

@Command({
name: 'upgrade:0-54:0-54-created-by-default-value',
Expand Down Expand Up @@ -59,12 +59,19 @@ export class FixCreatedByDefaultValueCommand extends ActiveOrSuspendedWorkspaces
);

const actualDefaultValue = (
await dataSource.query(`
await dataSource.query(
`
SELECT column_default FROM information_schema.columns
WHERE table_schema = '${schemaName}'
AND table_name = '${tableName}'
AND column_name = 'createdBySource';
`)
`,
undefined, // parameters
undefined, // queryRunner
{
shouldBypassPermissionChecks: true,
},
)
)?.[0]?.column_default;

if (actualDefaultValue !== null) {
Expand All @@ -75,12 +82,19 @@ export class FixCreatedByDefaultValueCommand extends ActiveOrSuspendedWorkspaces
FieldMetadataType.ACTOR,
) as ActorMetadata;

await dataSource.query(`
await dataSource.query(
`
ALTER TABLE "${schemaName}"."${tableName}"
ALTER COLUMN "createdBySource" SET DEFAULT ${createdByDefaultValues.source},
ALTER COLUMN "createdByName" SET DEFAULT ${createdByDefaultValues.name},
ALTER COLUMN "createdByContext" SET DEFAULT '${JSON.stringify(createdByDefaultValues.context)}';
`);
`,
undefined, // parameters
undefined, // queryRunner
{
shouldBypassPermissionChecks: true,
},
);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { DataSource } from 'typeorm';
const tableName = 'billingSubscription';

export const seedBillingSubscriptions = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clarifying name here and in other files as Datasource is not equal to WorkspaceDataSource, and does not require shouldBypassPermissionCheck indication.

For workspaceDataSource we have implemented permissions and overriden createQueryBuilder(). DataSource is not restricted to a workspace and can perform operations on core + on any workspace. Its usage is restricted to "system" operations.

schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { DataSource } from 'typeorm';
const tableName = 'featureFlag';

export const deleteFeatureFlags = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ export const DEV_SEED_USER_WORKSPACE_IDS = {
};

export const seedUserWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
Expand All @@ -41,11 +41,11 @@ export const seedUserWorkspaces = async (
};

export const deleteUserWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ export const DEMO_SEED_USER_IDS = {
TIM: '20202020-9e3b-46d4-a556-88b9ddc2b034',
};

export const seedUsers = async (
workspaceDataSource: DataSource,
schemaName: string,
) => {
await workspaceDataSource
export const seedUsers = async (dataSource: DataSource, schemaName: string) => {
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { DataSource } from 'typeorm';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { DataSource } from 'typeorm';

const tableName = 'workspace';

export const seedWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
Expand Down Expand Up @@ -36,11 +36,11 @@ export const seedWorkspaces = async (
};

export const deleteWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
const tableName = 'featureFlag';

export const seedFeatureFlags = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['key', 'workspaceId', 'value'])
Expand Down Expand Up @@ -50,11 +50,11 @@ export const seedFeatureFlags = async (
};

export const deleteFeatureFlags = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const DEV_SEED_USER_WORKSPACE_IDS = {
};

export const seedUserWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
Expand Down Expand Up @@ -53,7 +53,7 @@ export const seedUserWorkspaces = async (
},
];
}
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
Expand All @@ -63,11 +63,11 @@ export const seedUserWorkspaces = async (
};

export const deleteUserWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
await workspaceDataSource
await dataSource
.createQueryBuilder()
.delete()
.from(`${schemaName}.${tableName}`)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,8 @@ export const DEV_SEED_USER_IDS = {
PHIL: '20202020-7169-42cf-bc47-1cfef15264b8',
};

export const seedUsers = async (
workspaceDataSource: DataSource,
schemaName: string,
) => {
await workspaceDataSource
export const seedUsers = async (dataSource: DataSource, schemaName: string) => {
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import {
Brackets,
NotBrackets,
SelectQueryBuilder,
WhereExpressionBuilder,
} from 'typeorm';
import { Brackets, NotBrackets, WhereExpressionBuilder } from 'typeorm';

import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';

import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';

import { GraphqlQueryFilterFieldParser } from './graphql-query-filter-field.parser';

Expand All @@ -30,11 +26,11 @@ export class GraphqlQueryFilterConditionParser {

public parse(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryBuilder: SelectQueryBuilder<any>,
queryBuilder: WorkspaceSelectQueryBuilder<any>,
objectNameSingular: string,
filter: Partial<ObjectRecordFilter>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): SelectQueryBuilder<any> {
): WorkspaceSelectQueryBuilder<any> {
if (!filter || Object.keys(filter).length === 0) {
return queryBuilder;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
import {
FindOptionsWhere,
ObjectLiteral,
OrderByCondition,
SelectQueryBuilder,
} from 'typeorm';
import { FindOptionsWhere, ObjectLiteral, OrderByCondition } from 'typeorm';

import {
ObjectRecordFilter,
Expand All @@ -24,6 +19,7 @@ import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metada
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';

export class GraphqlQueryParser {
private fieldMetadataMapByName: FieldMetadataMap;
Expand Down Expand Up @@ -51,11 +47,11 @@ export class GraphqlQueryParser {

public applyFilterToBuilder(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryBuilder: SelectQueryBuilder<any>,
queryBuilder: WorkspaceSelectQueryBuilder<any>,
objectNameSingular: string,
recordFilter: Partial<ObjectRecordFilter>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): SelectQueryBuilder<any> {
): WorkspaceSelectQueryBuilder<any> {
return this.filterConditionParser.parse(
queryBuilder,
objectNameSingular,
Expand All @@ -65,10 +61,10 @@ export class GraphqlQueryParser {

public applyDeletedAtToBuilder(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryBuilder: SelectQueryBuilder<any>,
queryBuilder: WorkspaceSelectQueryBuilder<any>,
recordFilter: Partial<ObjectRecordFilter>,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): SelectQueryBuilder<any> {
): WorkspaceSelectQueryBuilder<any> {
if (this.checkForDeletedAtFilter(recordFilter)) {
queryBuilder.withDeleted();
}
Expand Down Expand Up @@ -104,12 +100,12 @@ export class GraphqlQueryParser {

public applyOrderToBuilder(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryBuilder: SelectQueryBuilder<any>,
queryBuilder: WorkspaceSelectQueryBuilder<any>,
orderBy: ObjectRecordOrderBy,
objectNameSingular: string,
isForwardPagination = true,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
): SelectQueryBuilder<any> {
): WorkspaceSelectQueryBuilder<any> {
const parsedOrderBys = this.orderFieldParser.parse(
orderBy,
objectNameSingular,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';

import { SelectQueryBuilder } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';

import { AGGREGATE_OPERATIONS } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { AggregationField } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-available-aggregations-from-object-fields.util';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { formatColumnNamesFromCompositeFieldAndSubfields } from 'src/engine/twenty-orm/utils/format-column-names-from-composite-field-and-subfield.util';

@Injectable()
Expand All @@ -15,7 +15,7 @@ export class ProcessAggregateHelper {
}: {
selectedAggregatedFields: Record<string, AggregationField>;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryBuilder: SelectQueryBuilder<any>;
queryBuilder: WorkspaceSelectQueryBuilder<any>;
}) => {
queryBuilder.select([]);

Expand Down
Loading
Loading