Fix missing name validation on object names at update (#5434)
## Context as per title ## How was it tested? local (/metadata + in product)
This commit is contained in:
@ -1,8 +1,4 @@
|
|||||||
import {
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
ForbiddenException,
|
|
||||||
Injectable,
|
|
||||||
UnauthorizedException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BeforeCreateOneHook,
|
BeforeCreateOneHook,
|
||||||
@ -11,27 +7,6 @@ import {
|
|||||||
|
|
||||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||||
|
|
||||||
const coreObjectNames = [
|
|
||||||
'appToken',
|
|
||||||
'billingSubscription',
|
|
||||||
'billingSubscriptionItem',
|
|
||||||
'featureFlag',
|
|
||||||
'user',
|
|
||||||
'userWorkspace',
|
|
||||||
'workspace',
|
|
||||||
];
|
|
||||||
|
|
||||||
const reservedKeywords = [
|
|
||||||
...coreObjectNames,
|
|
||||||
'event',
|
|
||||||
'field',
|
|
||||||
'link',
|
|
||||||
'currency',
|
|
||||||
'fullName',
|
|
||||||
'address',
|
|
||||||
'links',
|
|
||||||
];
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
export class BeforeCreateOneObject<T extends CreateObjectInput>
|
||||||
implements BeforeCreateOneHook<T, any>
|
implements BeforeCreateOneHook<T, any>
|
||||||
@ -46,14 +21,6 @@ export class BeforeCreateOneObject<T extends CreateObjectInput>
|
|||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
|
||||||
reservedKeywords.includes(instance.input.nameSingular) ||
|
|
||||||
reservedKeywords.includes(instance.input.namePlural)
|
|
||||||
) {
|
|
||||||
throw new ForbiddenException(
|
|
||||||
'You cannot create an object with this name.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
instance.input.workspaceId = workspaceId;
|
instance.input.workspaceId = workspaceId;
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
|
|||||||
@ -58,7 +58,7 @@ import { createWorkspaceMigrationsForCustomObjectRelations } from 'src/engine/me
|
|||||||
import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util';
|
import { createWorkspaceMigrationsForRemoteObjectRelations } from 'src/engine/metadata-modules/object-metadata/utils/create-workspace-migrations-for-remote-object-relations.util';
|
||||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||||
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||||
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||||
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
|
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
|
||||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||||
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
@ -253,7 +253,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
objectMetadataInput.workspaceId,
|
objectMetadataInput.workspaceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
validateObjectMetadataInput(objectMetadataInput);
|
validateObjectMetadataInputOrThrow(objectMetadataInput);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
objectMetadataInput.nameSingular.toLowerCase() ===
|
objectMetadataInput.nameSingular.toLowerCase() ===
|
||||||
@ -427,6 +427,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
|||||||
input: UpdateOneObjectInput,
|
input: UpdateOneObjectInput,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
): Promise<ObjectMetadataEntity> {
|
): Promise<ObjectMetadataEntity> {
|
||||||
|
validateObjectMetadataInputOrThrow(input.update);
|
||||||
|
|
||||||
const updatedObject = await super.updateOne(input.id, input.update);
|
const updatedObject = await super.updateOne(input.id, input.update);
|
||||||
|
|
||||||
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
|
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
|
||||||
|
|||||||
@ -0,0 +1,63 @@
|
|||||||
|
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
|
import { validateObjectMetadataInputOrThrow } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
|
||||||
|
|
||||||
|
const validObjectInput: UpdateObjectPayload = {
|
||||||
|
labelPlural: 'Car',
|
||||||
|
labelSingular: 'Cars',
|
||||||
|
nameSingular: 'car',
|
||||||
|
namePlural: 'cars',
|
||||||
|
};
|
||||||
|
|
||||||
|
const reservedKeyword = 'user';
|
||||||
|
|
||||||
|
describe('validateObjectName', () => {
|
||||||
|
it('should not throw if names are valid', () => {
|
||||||
|
expect(() =>
|
||||||
|
validateObjectMetadataInputOrThrow(validObjectInput),
|
||||||
|
).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw is nameSingular has invalid characters', () => {
|
||||||
|
const invalidObjectInput = {
|
||||||
|
...validObjectInput,
|
||||||
|
nameSingular: 'μ',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateObjectMetadataInputOrThrow(invalidObjectInput),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw is namePlural has invalid characters', () => {
|
||||||
|
const invalidObjectInput = {
|
||||||
|
...validObjectInput,
|
||||||
|
namePlural: 'μ',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateObjectMetadataInputOrThrow(invalidObjectInput),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if nameSingular is a reserved keyword', async () => {
|
||||||
|
const invalidObjectInput = {
|
||||||
|
...validObjectInput,
|
||||||
|
nameSingular: reservedKeyword,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateObjectMetadataInputOrThrow(invalidObjectInput),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw if namePlural is a reserved keyword', async () => {
|
||||||
|
const invalidObjectInput = {
|
||||||
|
...validObjectInput,
|
||||||
|
namePlural: reservedKeyword,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
validateObjectMetadataInputOrThrow(invalidObjectInput),
|
||||||
|
).toThrow();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,27 +1,70 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||||
|
|
||||||
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
|
||||||
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
|
||||||
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
|
||||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||||
|
|
||||||
export const validateObjectMetadataInput = <
|
const coreObjectNames = [
|
||||||
|
'appToken',
|
||||||
|
'billingSubscription',
|
||||||
|
'billingSubscriptions',
|
||||||
|
'billingSubscriptionItem',
|
||||||
|
'billingSubscriptionItems',
|
||||||
|
'featureFlag',
|
||||||
|
'user',
|
||||||
|
'users',
|
||||||
|
'userWorkspace',
|
||||||
|
'userWorkspaces',
|
||||||
|
'workspace',
|
||||||
|
'workspaces',
|
||||||
|
];
|
||||||
|
|
||||||
|
const reservedKeywords = [
|
||||||
|
...coreObjectNames,
|
||||||
|
'event',
|
||||||
|
'events',
|
||||||
|
'field',
|
||||||
|
'fields',
|
||||||
|
'link',
|
||||||
|
'links',
|
||||||
|
'currency',
|
||||||
|
'currencies',
|
||||||
|
'fullName',
|
||||||
|
'fullNames',
|
||||||
|
'address',
|
||||||
|
'addresses',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const validateObjectMetadataInputOrThrow = <
|
||||||
T extends UpdateObjectPayload | CreateObjectInput,
|
T extends UpdateObjectPayload | CreateObjectInput,
|
||||||
>(
|
>(
|
||||||
objectMetadataInput: T,
|
objectMetadataInput: T,
|
||||||
): void => {
|
): void => {
|
||||||
try {
|
validateNameCharactersOrThrow(objectMetadataInput.nameSingular);
|
||||||
if (objectMetadataInput.nameSingular) {
|
validateNameCharactersOrThrow(objectMetadataInput.namePlural);
|
||||||
validateMetadataName(objectMetadataInput.nameSingular);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (objectMetadataInput.namePlural) {
|
validateNameIsNotReservedKeywordOrThrow(objectMetadataInput.nameSingular);
|
||||||
validateMetadataName(objectMetadataInput.namePlural);
|
validateNameIsNotReservedKeywordOrThrow(objectMetadataInput.namePlural);
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateNameIsNotReservedKeywordOrThrow = (name?: string) => {
|
||||||
|
if (name) {
|
||||||
|
if (reservedKeywords.includes(name)) {
|
||||||
|
throw new ForbiddenException(`The name "${name}" is not available`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateNameCharactersOrThrow = (name?: string) => {
|
||||||
|
try {
|
||||||
|
if (name) {
|
||||||
|
validateMetadataName(name);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof InvalidStringException) {
|
if (error instanceof InvalidStringException) {
|
||||||
throw new BadRequestException(
|
throw new BadRequestException(
|
||||||
`Characters used in name "${objectMetadataInput.nameSingular}" or "${objectMetadataInput.namePlural}" are not supported`,
|
`Characters used in name "${name}" are not supported`,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
Reference in New Issue
Block a user