Skip to content

Commit

Permalink
[backend/frontend] Invalidate empty CSV mappers (OpenCTI-Platform#4644)
Browse files Browse the repository at this point in the history
  • Loading branch information
labo-flg authored Dec 21, 2023
1 parent af8e9c5 commit 85b46be
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ const CsvMapperForm: FunctionComponent<CsvMapperFormProps> = ({ csvMapper, onSub
};

// -- ERRORS --
const [hasError, setHasError] = useState<boolean>(false);
// on edit mode, csvMapper.errors might be set; on create mode backend validation is not done yet so error is null
const [hasError, setHasError] = useState<boolean>(!!csvMapper.errors?.length || csvMapper.representations.length === 0);
let errors: Map<string, string> = new Map();
const handleRepresentationErrors = (key: string, value: boolean) => {
errors = { ...errors, [key]: value };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export const isValidTargetType = (representation: CsvMapperRepresentation) => {
};

export const validate = async (context: AuthContext, mapper: BasicStoreEntityCsvMapper) => {
// consider empty csv mapper as invalid to avoid being used in the importer
if (mapper.representations.length === 0) {
throw Error(`CSV Mapper '${mapper.name}' has no representation`);
}

await Promise.all(Array.from(mapper.representations.entries()).map(async ([idx, representation]) => {
// Validate target type
isValidTargetType(representation);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { assert, describe, expect, it } from 'vitest';
import { csvMapperMockSimpleDifferentEntities } from '../../data/csv-mapper-mock-simple-different-entities';
import { validate } from '../../../src/modules/internal/csvMapper/csvMapper-utils';
import { testContext } from '../../utils/testQuery';
import type { BasicStoreEntityCsvMapper } from '../../../src/modules/internal/csvMapper/csvMapper-types';

describe('CSV Mapper', () => {
it('validate a valid mapper', async () => {
await validate(testContext, {
...csvMapperMockSimpleDifferentEntities as BasicStoreEntityCsvMapper,
name: 'Valid Mapper'
});
assert(true);
});
it('invalidate a invalid mapper', async () => {
const mapper = csvMapperMockSimpleDifferentEntities as BasicStoreEntityCsvMapper;
await expect(() => validate(testContext, {
...mapper,
name: 'Invalid Mapper',
representations: [], // cannot have 0 representations
})).rejects.toThrowError('CSV Mapper \'Invalid Mapper\' has no representation');

await expect(() => validate(testContext, {
...mapper,
name: 'Invalid Mapper',
representations: [
{
...mapper.representations[0],
attributes: [], // missing attribute
},
mapper.representations[1],
]
})).rejects.toThrowError(/missing values for required attribute : name/);

// TODO: cover more validation tests
});
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import { describe, expect, it } from 'vitest';
import { csvMapperMockSimpleEntity } from "./simple-entity-test/csv-mapper-mock-simple-entity";
import { isNotEmptyField } from "../../../src/database/utils";
import { csvMapperMockSimpleEntity } from './simple-entity-test/csv-mapper-mock-simple-entity';
import { isNotEmptyField } from '../../../src/database/utils';

import '../../../src/modules';
import { csvMapperMockSimpleRelationship } from "./simple-relationship-test/csv-mapper-mock-simple-relationship";
import { csvMapperMockSimpleEntityWithRef } from "./simple-entity-with-ref-test/csv-mapper-mock-simple-entity-with-ref";
import { columnNameToIdx } from "../../../src/parser/csv-helper";
import { csvMapperMockRealUseCase } from "./real-use-case/csv-mapper-mock-real-use-case";
import {
csvMapperMockSimpleDifferentEntities
} from "./simple-different-entities-test/csv-mapper-mock-simple-different-entities";
import { csvMapperMockSimpleSighting } from "./simple-sighting-test/csv-mapper-mock-simple-sighting";
import { bundleProcess } from "../../../src/parser/csv-bundler";
import { ADMIN_USER, testContext } from "../../utils/testQuery";
import { csvMapperMockSimpleSkipLine } from "./simple-skip-line-test/csv-mapper-mock-simple-skip-line";
import { csvMapperMockSimpleRelationship } from './simple-relationship-test/csv-mapper-mock-simple-relationship';
import { csvMapperMockSimpleEntityWithRef } from './simple-entity-with-ref-test/csv-mapper-mock-simple-entity-with-ref';
import { columnNameToIdx } from '../../../src/parser/csv-helper';
import { csvMapperMockRealUseCase } from './real-use-case/csv-mapper-mock-real-use-case';
import { csvMapperMockSimpleDifferentEntities } from '../../data/csv-mapper-mock-simple-different-entities';
import { csvMapperMockSimpleSighting } from './simple-sighting-test/csv-mapper-mock-simple-sighting';
import { bundleProcess } from '../../../src/parser/csv-bundler';
import { ADMIN_USER, testContext } from '../../utils/testQuery';
import { csvMapperMockSimpleSkipLine } from './simple-skip-line-test/csv-mapper-mock-simple-skip-line';

describe('CSV-HELPER', () => {
it('Column name to idx', async () => {
Expand All @@ -35,15 +33,15 @@ describe('CSV-HELPER', () => {
idx = columnNameToIdx('AJD');
expect(idx)
.toBe(939);
})
});
});

describe('CSV-PARSER', () => {
it('Parse CSV - Simple entity', async () => {
const filPath = './tests/02-integration/05-parser/simple-entity-test/Threat-Actor-Group_list.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleEntity);

const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(5);
expect(objects.filter((o) => isNotEmptyField(o.name)).length)
Expand All @@ -59,7 +57,7 @@ describe('CSV-PARSER', () => {
const filPath = './tests/02-integration/05-parser/simple-relationship-test/Threat-Actor-Group_PART-OF_list.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleRelationship);

const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(6);
expect(objects.filter((o) => o.type === 'threat-actor').length)
Expand All @@ -71,7 +69,7 @@ describe('CSV-PARSER', () => {
const filPath = './tests/02-integration/05-parser/simple-sighting-test/Threat-Actor-Group_SIGHTING_org.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleSighting);

const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(3);
expect(objects.filter((o) => o.type === 'threat-actor').length)
Expand All @@ -85,7 +83,7 @@ describe('CSV-PARSER', () => {
const filPath = './tests/02-integration/05-parser/simple-entity-with-ref-test/Threat-Actor-Group_with-ref.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleEntityWithRef);

const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(3);
const label = objects.filter((o) => o.type === 'label')[0];
Expand All @@ -110,7 +108,7 @@ describe('CSV-PARSER', () => {
const filPath = './tests/02-integration/05-parser/simple-different-entities-test/Threat-Actor-Group_or_Organization.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleDifferentEntities);

const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(2);
expect(objects.filter((o) => o.type === 'threat-actor').length)
Expand All @@ -122,7 +120,7 @@ describe('CSV-PARSER', () => {
const filPath = './tests/02-integration/05-parser/real-use-case/schema incidents.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockRealUseCase);

const objects = bundle.objects;
const { objects } = bundle;
const incidents = objects.filter((o) => o.type === 'incident');
expect(incidents.length)
.toBe(118);
Expand All @@ -148,7 +146,7 @@ describe('CSV-PARSER', () => {
it('Parse CSV - Simple skip line test on Simple entity ', async () => {
const filPath = './tests/02-integration/05-parser/simple-skip-line-test/Threat-Actor-Group_list_skip_line.csv';
const bundle = await bundleProcess(testContext, ADMIN_USER, filPath, csvMapperMockSimpleSkipLine);
const objects = bundle.objects;
const { objects } = bundle;
expect(objects.length)
.toBe(5);
expect(objects.filter((o) => isNotEmptyField(o.name)).length)
Expand All @@ -160,4 +158,4 @@ describe('CSV-PARSER', () => {
expect(threatActorWithTypes.threat_actor_types.length)
.toBe(2);
});
})
});
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { ENTITY_TYPE_THREAT_ACTOR_GROUP } from '../../../../src/schema/stixDomainObject';
import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../../../src/modules/organization/organization-types';
import {
type BasicStoreEntityCsvMapper,
CsvMapperRepresentationType, Operator
} from '../../../../src/modules/internal/csvMapper/csvMapper-types';
import { ENTITY_TYPE_THREAT_ACTOR_GROUP } from '../../src/schema/stixDomainObject';
import { ENTITY_TYPE_IDENTITY_ORGANIZATION } from '../../src/modules/organization/organization-types';
import { type BasicStoreEntityCsvMapper, CsvMapperRepresentationType, Operator } from '../../src/modules/internal/csvMapper/csvMapper-types';

export const csvMapperMockSimpleDifferentEntities: Partial<BasicStoreEntityCsvMapper> = {
id: 'mapper-mock-simple-different-entities',
Expand Down Expand Up @@ -51,4 +48,4 @@ export const csvMapperMockSimpleDifferentEntities: Partial<BasicStoreEntityCsvMa
]
}
]
}
};

0 comments on commit 85b46be

Please sign in to comment.