[permissions] Enforce object-records permission checks in resolvers (#10304)
Closes https://github.com/twentyhq/core-team-issues/issues/393 - enforcing object-records permission checks in resolvers for now. we will move the logic to a lower level asap - add integration tests that will still be useful when we have moved the logic - introduce guest seeded role to test limited permissions on object-records
This commit is contained in:
@ -0,0 +1,31 @@
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import {
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
export const graphqlQueryRunnerExceptionHandler = (
|
||||
error: GraphqlQueryRunnerException,
|
||||
) => {
|
||||
switch (error.code) {
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST:
|
||||
case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION:
|
||||
case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
|
||||
case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
|
||||
case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,51 @@
|
||||
import { isDefined } from 'twenty-shared';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
export const handleDuplicateKeyError = (
|
||||
error: QueryFailedError,
|
||||
context: WorkspaceQueryRunnerOptions,
|
||||
) => {
|
||||
const indexNameMatch = error.message.match(/"([^"]+)"/);
|
||||
|
||||
if (indexNameMatch) {
|
||||
const indexName = indexNameMatch[1];
|
||||
|
||||
const deletedAtFieldMetadata =
|
||||
context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt'];
|
||||
|
||||
const affectedColumns =
|
||||
context.objectMetadataItemWithFieldMaps.indexMetadatas
|
||||
.find((index) => index.name === indexName)
|
||||
?.indexFieldMetadatas?.filter(
|
||||
(field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id,
|
||||
)
|
||||
.map((indexField) => {
|
||||
const fieldMetadata =
|
||||
context.objectMetadataItemWithFieldMaps.fieldsById[
|
||||
indexField.fieldMetadataId
|
||||
];
|
||||
|
||||
return fieldMetadata?.label;
|
||||
});
|
||||
|
||||
if (!isDefined(affectedColumns)) {
|
||||
throw new UserInputError(`A duplicate entry was detected`);
|
||||
}
|
||||
|
||||
const columnNames = affectedColumns.join(', ');
|
||||
|
||||
if (affectedColumns?.length === 1) {
|
||||
throw new UserInputError(
|
||||
`Duplicate ${columnNames}. Please set a unique one.`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new UserInputError(
|
||||
`A duplicate entry was detected. The combination of ${columnNames} must be unique.`,
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -0,0 +1,32 @@
|
||||
import {
|
||||
WorkspaceQueryRunnerException,
|
||||
WorkspaceQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import {
|
||||
ForbiddenError,
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
TimeoutError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
export const workspaceExceptionHandler = (
|
||||
error: WorkspaceQueryRunnerException,
|
||||
) => {
|
||||
switch (error.code) {
|
||||
case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
|
||||
case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
|
||||
throw new ForbiddenError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
|
||||
throw new TimeoutError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
}
|
||||
};
|
||||
@ -1,114 +1,35 @@
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
import { isDefined } from 'twenty-shared';
|
||||
|
||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import {
|
||||
WorkspaceQueryRunnerException,
|
||||
WorkspaceQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import {
|
||||
ForbiddenError,
|
||||
InternalServerError,
|
||||
NotFoundError,
|
||||
TimeoutError,
|
||||
UserInputError,
|
||||
} from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||
import { graphqlQueryRunnerExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util';
|
||||
import { handleDuplicateKeyError } from 'src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util';
|
||||
import { workspaceExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util';
|
||||
import { WorkspaceQueryRunnerException } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util';
|
||||
|
||||
export const workspaceQueryRunnerGraphqlApiExceptionHandler = (
|
||||
error: Error,
|
||||
context: WorkspaceQueryRunnerOptions,
|
||||
) => {
|
||||
if (error instanceof QueryFailedError) {
|
||||
if (
|
||||
error.message.includes('duplicate key value violates unique constraint')
|
||||
) {
|
||||
const indexNameMatch = error.message.match(/"([^"]+)"/);
|
||||
|
||||
if (indexNameMatch) {
|
||||
const indexName = indexNameMatch[1];
|
||||
|
||||
const deletedAtFieldMetadata =
|
||||
context.objectMetadataItemWithFieldMaps.fieldsByName['deletedAt'];
|
||||
|
||||
const affectedColumns =
|
||||
context.objectMetadataItemWithFieldMaps.indexMetadatas
|
||||
.find((index) => index.name === indexName)
|
||||
?.indexFieldMetadatas?.filter(
|
||||
(field) => field.fieldMetadataId !== deletedAtFieldMetadata?.id,
|
||||
)
|
||||
.map((indexField) => {
|
||||
const fieldMetadata =
|
||||
context.objectMetadataItemWithFieldMaps.fieldsById[
|
||||
indexField.fieldMetadataId
|
||||
];
|
||||
|
||||
return fieldMetadata?.label;
|
||||
});
|
||||
|
||||
if (!isDefined(affectedColumns)) {
|
||||
throw new UserInputError(`A duplicate entry was detected`);
|
||||
}
|
||||
|
||||
const columnNames = affectedColumns.join(', ');
|
||||
|
||||
if (affectedColumns?.length === 1) {
|
||||
throw new UserInputError(
|
||||
`Duplicate ${columnNames}. Please set a unique one.`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new UserInputError(
|
||||
`A duplicate entry was detected. The combination of ${columnNames} must be unique.`,
|
||||
);
|
||||
switch (true) {
|
||||
case error instanceof QueryFailedError: {
|
||||
if (
|
||||
error.message.includes('duplicate key value violates unique constraint')
|
||||
) {
|
||||
return handleDuplicateKeyError(error, context);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
|
||||
throw error;
|
||||
case error instanceof PermissionsException:
|
||||
return permissionGraphqlApiExceptionHandler(error);
|
||||
case error instanceof WorkspaceQueryRunnerException:
|
||||
return workspaceExceptionHandler(error);
|
||||
case error instanceof GraphqlQueryRunnerException:
|
||||
return graphqlQueryRunnerExceptionHandler(error);
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (error instanceof WorkspaceQueryRunnerException) {
|
||||
switch (error.code) {
|
||||
case WorkspaceQueryRunnerExceptionCode.DATA_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_UNIQUE_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT:
|
||||
case WorkspaceQueryRunnerExceptionCode.TOO_MANY_ROWS_AFFECTED:
|
||||
case WorkspaceQueryRunnerExceptionCode.NO_ROWS_AFFECTED:
|
||||
throw new ForbiddenError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT:
|
||||
throw new TimeoutError(error.message);
|
||||
case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof GraphqlQueryRunnerException) {
|
||||
switch (error.code) {
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST:
|
||||
case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION:
|
||||
case GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR:
|
||||
case GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT:
|
||||
case GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND:
|
||||
case GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT:
|
||||
throw new UserInputError(error.message);
|
||||
case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundError(error.message);
|
||||
default:
|
||||
throw new InternalServerError(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user