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
Changes from 10 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
@@ -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`);
@@ -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(
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',
@@ -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) {
@@ -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
@@ -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}`, [
Original file line number Diff line number Diff line change
@@ -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}`)
Original file line number Diff line number Diff line change
@@ -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'])
@@ -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}`)
Original file line number Diff line number Diff line change
@@ -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}`, [
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}`, [
@@ -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}`)
Original file line number Diff line number Diff line change
@@ -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'])
@@ -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}`)
Original file line number Diff line number Diff line change
@@ -17,7 +17,7 @@ export const DEV_SEED_USER_WORKSPACE_IDS = {
};

export const seedUserWorkspaces = async (
workspaceDataSource: DataSource,
dataSource: DataSource,
schemaName: string,
workspaceId: string,
) => {
@@ -53,7 +53,7 @@ export const seedUserWorkspaces = async (
},
];
}
await workspaceDataSource
await dataSource
.createQueryBuilder()
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'userId', 'workspaceId'])
@@ -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}`)
Original file line number Diff line number Diff line change
@@ -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}`, [
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';

@@ -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;
}
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,
@@ -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;
@@ -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,
@@ -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();
}
@@ -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,
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()
@@ -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([]);

Loading