diff --git a/app/lib/error-handler.js b/app/lib/error-handler.js index c7d30e31..db79a262 100644 --- a/app/lib/error-handler.js +++ b/app/lib/error-handler.js @@ -53,8 +53,7 @@ exports.bodyParser = function (err, req, res, next) { exports.requestValidation = function (err, req, res, next) { if (err.status && err.message) { - logger.warn('Request failed validation'); - logger.info(JSON.stringify(err)); + logger.warn('Request failed validation: %s', JSON.stringify(err)); res.status(err.status).send(err.message); } else { next(err); diff --git a/app/lib/logger.js b/app/lib/logger.js index 8597c750..31ebf254 100644 --- a/app/lib/logger.js +++ b/app/lib/logger.js @@ -12,17 +12,21 @@ const logLevels = { debug: 5, }; +// Shared formats applied in every mode. `splat()` enables printf-style +// interpolation (e.g. logger.warn('Bad request: %s', body)); without it winston +// leaves the `%s` token uninterpolated and drops the extra argument. +const baseFormats = [ + winston.format.timestamp(), + winston.format.errors({ stack: true }), + winston.format.splat(), +]; + // Use detailed format for debug/verbose levels, cleaner one-liner format otherwise const consoleFormat = config.logging.logLevel === 'debug' || config.logging.logLevel === 'verbose' - ? winston.format.combine( - winston.format.timestamp(), - winston.format.errors({ stack: true }), - winston.format.prettyPrint(), - ) + ? winston.format.combine(...baseFormats, winston.format.prettyPrint()) : winston.format.combine( - winston.format.timestamp(), - winston.format.errors({ stack: true }), + ...baseFormats, winston.format.printf( (info) => `${info.timestamp} [${info.level.toUpperCase()}] ${info.message}`, ), diff --git a/app/tests/api/analytics/analytics-includeRefs.spec.js b/app/tests/api/analytics/analytics-includeRefs.spec.js index c1e98b03..d5e5094a 100644 --- a/app/tests/api/analytics/analytics-includeRefs.spec.js +++ b/app/tests/api/analytics/analytics-includeRefs.spec.js @@ -25,11 +25,11 @@ const analyticData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--test-data-component-1', + x_mitre_data_component_ref: 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', name: 'perm-1', channel: 'perm-1', }, @@ -65,7 +65,7 @@ const dataComponentData = { }, }, stix: { - id: 'x-mitre-data-component--test-data-component-1', + id: 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', name: 'test-data-component', spec_version: '2.1', type: 'x-mitre-data-component', @@ -89,8 +89,8 @@ describe('Analytics API - includeRefs Parameter', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -115,7 +115,9 @@ describe('Analytics API - includeRefs Parameter', function () { createdDataComponent = res.body; expect(createdDataComponent).toBeDefined(); - expect(createdDataComponent.stix.id).toBe('x-mitre-data-component--test-data-component-1'); + expect(createdDataComponent.stix.id).toBe( + 'x-mitre-data-component--3d6c9f1b-7f8a-4f2e-9b1a-2c3d4e5f6a7b', + ); }); it('Setup: Create analytic for testing', async function () { @@ -318,7 +320,9 @@ describe('Analytics API - includeRefs Parameter', function () { stix: { ...analyticData.stix, name: 'analytic-without-refs', - x_mitre_log_source_references: [], + // Omit log source references entirely; the ADM schema requires the + // array to be non-empty when present. + x_mitre_log_source_references: undefined, created: new Date().toISOString(), modified: new Date().toISOString(), }, @@ -359,7 +363,9 @@ describe('Analytics API - includeRefs Parameter', function () { name: 'analytic-with-bad-ref', x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--non-existent', + // Valid STIX id format, but no such data component exists -> 404 in beforeCreate + x_mitre_data_component_ref: + 'x-mitre-data-component--ffffffff-ffff-4fff-8fff-ffffffffffff', name: 'perm-1', channel: 'perm-1', }, @@ -384,7 +390,7 @@ describe('Analytics API - includeRefs Parameter', function () { ...dataComponentData, stix: { ...dataComponentData.stix, - id: 'x-mitre-data-component--no-ext-refs', + id: 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', name: 'data-component-no-ext-refs', external_references: [], created: new Date().toISOString(), @@ -406,7 +412,8 @@ describe('Analytics API - includeRefs Parameter', function () { name: 'analytic-with-no-ext-ref-data-component', x_mitre_log_source_references: [ { - x_mitre_data_component_ref: 'x-mitre-data-component--no-ext-refs', + x_mitre_data_component_ref: + 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d', name: 'perm-1', channel: 'perm-1', }, @@ -435,7 +442,8 @@ describe('Analytics API - includeRefs Parameter', function () { const analytic = analytics[0]; const dataComponentRef = analytic.workspace.embedded_relationships.find( (rel) => - rel.stix_id === 'x-mitre-data-component--no-ext-refs' && rel.direction === 'outbound', + rel.stix_id === 'x-mitre-data-component--a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' && + rel.direction === 'outbound', ); expect(dataComponentRef).toBeDefined(); // Even without external_references, the system assigns an attack_id diff --git a/app/tests/api/analytics/analytics-pagination.spec.js b/app/tests/api/analytics/analytics-pagination.spec.js index 7bd8fc37..56b841cd 100644 --- a/app/tests/api/analytics/analytics-pagination.spec.js +++ b/app/tests/api/analytics/analytics-pagination.spec.js @@ -17,7 +17,7 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -36,6 +36,9 @@ const options = { prefix: 'x-mitre-analytic', baseUrl: '/api/analytics', label: 'Analytics', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(analyticsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/analytics/analytics.spec.js b/app/tests/api/analytics/analytics.spec.js index fea3c096..4348e4ed 100644 --- a/app/tests/api/analytics/analytics.spec.js +++ b/app/tests/api/analytics/analytics.spec.js @@ -30,7 +30,7 @@ const initialObjectData = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -57,6 +57,10 @@ describe('Analytics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); @@ -393,7 +397,7 @@ describe('Analytics API', function () { name: 'Network Connection Creation Detection Strategy', spec_version: '2.1', type: 'x-mitre-detection-strategy', - description: 'Strategy for detecting network connections', + // Note: the x-mitre-detection-strategy ADM schema does not define a description field object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', diff --git a/app/tests/api/assets/assets.spec.js b/app/tests/api/assets/assets.spec.js index 9d1db830..c8b9be55 100644 --- a/app/tests/api/assets/assets.spec.js +++ b/app/tests/api/assets/assets.spec.js @@ -24,16 +24,16 @@ const initialObjectData = { spec_version: '2.1', type: 'x-mitre-asset', description: 'This is an asset.', - x_mitre_sectors: ['sector placeholder 1'], + x_mitre_sectors: ['Electric'], x_mitre_related_assets: [ { name: 'related asset 1', - related_asset_sectors: ['related asset sector placeholder 1'], + related_asset_sectors: ['Water and Wastewater'], description: 'This is a related asset', }, { name: 'related asset 2', - related_asset_sectors: ['related asset sector placeholder 2'], + related_asset_sectors: ['Manufacturing'], description: 'This is another related asset', }, ], @@ -49,8 +49,8 @@ describe('Assets API', function () { let passportCookie; before(async function () { - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Establish the database connection diff --git a/app/tests/api/attack-objects/attack-objects-1.json b/app/tests/api/attack-objects/attack-objects-1.json index ca745e3c..74cab74e 100644 --- a/app/tests/api/attack-objects/attack-objects-1.json +++ b/app/tests/api/attack-objects/attack-objects-1.json @@ -110,11 +110,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -140,11 +141,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0002", - "external_id": "TX0002" + "url": "https://attack.mitre.org/techniques/T9002", + "external_id": "T9002" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -170,11 +172,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0003", - "external_id": "TX0003" + "url": "https://attack.mitre.org/techniques/T9003", + "external_id": "T9003" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -204,11 +207,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0004", - "external_id": "TX0004" + "url": "https://attack.mitre.org/techniques/T9004", + "external_id": "T9004" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -238,11 +242,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0005", - "external_id": "TX0005" + "url": "https://attack.mitre.org/techniques/T9005", + "external_id": "T9005" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -257,8 +262,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0001", - "url": "https://attack.mitre.org/tactics/TAX0001" + "external_id": "TA9001", + "url": "https://attack.mitre.org/tactics/TA9001" } ], "id": "x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357", @@ -280,8 +285,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0002", - "url": "https://attack.mitre.org/tactics/TAX0002" + "external_id": "TA9002", + "url": "https://attack.mitre.org/tactics/TA9002" } ], "id": "x-mitre-tactic--953fd636-2af2-4cad-adc8-5d7903295dba", @@ -303,8 +308,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0003", - "url": "https://attack.mitre.org/tactics/TAX0003" + "external_id": "TA9003", + "url": "https://attack.mitre.org/tactics/TA9003" } ], "id": "x-mitre-tactic--c1768fcd-abe2-462f-95cc-bfedbc8c64c6", @@ -326,8 +331,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0004", - "url": "https://attack.mitre.org/tactics/TAX0004" + "external_id": "TA9004", + "url": "https://attack.mitre.org/tactics/TA9004" } ], "id": "x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52", @@ -349,8 +354,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0005", - "url": "https://attack.mitre.org/tactics/TAX0005" + "external_id": "TA9005", + "url": "https://attack.mitre.org/tactics/TA9005" } ], "id": "x-mitre-tactic--0b518521-aae3-4169-9f7a-cdf8455a2d14", @@ -372,8 +377,8 @@ "external_references": [ { "source_name": "mitre-mobile-attack", - "external_id": "TAX0006", - "url": "https://attack.mitre.org/tactics/TAX0006" + "external_id": "TA9006", + "url": "https://attack.mitre.org/tactics/TA9006" } ], "id": "x-mitre-tactic--f5a8c28b-3002-4d5b-9571-3f68b2b57e29", @@ -396,8 +401,8 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "SX3333", - "url": "https://attack.mitre.org/software/SX3333" + "external_id": "S9001", + "url": "https://attack.mitre.org/software/S9001" }, { "source_name": "source-1", "external_id": "s1" } ], @@ -405,9 +410,10 @@ "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.1", "x_mitre_aliases": ["software-1"], - "x_mitre_platforms": ["platform-1"], + "x_mitre_platforms": ["Android"], "x_mitre_contributors": ["contributor-1", "contributor-2"], "x_mitre_domains": ["mobile-attack"], + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -420,13 +426,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "GX1111", - "url": "https://attack.mitre.org/groups/GX1111" + "external_id": "G9001", + "url": "https://attack.mitre.org/groups/G9001" }, { "source_name": "source-1", "external_id": "s1" } ], "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", + "x_mitre_version": "1.1", + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -439,14 +447,16 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "T9999", - "url": "https://attack.mitre.org/mitigations/T9999" + "external_id": "M9001", + "url": "https://attack.mitre.org/mitigations/M9001" }, { "source_name": "source-1", "external_id": "s1" } ], "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.1", + "x_mitre_domains": ["enterprise-attack"], + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -459,6 +469,7 @@ "target_ref": "attack-pattern--757471d4-d931-4109-82dd-cdd50c04744e", "object_marking_refs": ["marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce"], "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", + "x_mitre_attack_spec_version": "2.1.0", "created": "2022-06-01T00:00:00.000Z", "modified": "2022-06-01T00:00:00.000Z" }, @@ -482,8 +493,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/attack-objects/attack-objects-2.json b/app/tests/api/attack-objects/attack-objects-2.json index ae13f878..cc065b43 100644 --- a/app/tests/api/attack-objects/attack-objects-2.json +++ b/app/tests/api/attack-objects/attack-objects-2.json @@ -54,11 +54,12 @@ "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_data_sources": ["Process: Process Creation"], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -84,8 +85,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/attack-objects/attack-objects-pagination.spec.js b/app/tests/api/attack-objects/attack-objects-pagination.spec.js index 4a8cf0ff..206320fa 100644 --- a/app/tests/api/attack-objects/attack-objects-pagination.spec.js +++ b/app/tests/api/attack-objects/attack-objects-pagination.spec.js @@ -13,15 +13,12 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_data_sources: ['data-source-1', 'data-source-2'], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_platforms: ['Linux', 'macOS'], }, }; @@ -32,6 +29,9 @@ const options = { baseUrl: '/api/attack-objects', label: 'Attack Objects', state: 'work-in-progress', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/attack-objects/attack-objects.spec.js b/app/tests/api/attack-objects/attack-objects.spec.js index 3724b6ba..287d8bc4 100644 --- a/app/tests/api/attack-objects/attack-objects.spec.js +++ b/app/tests/api/attack-objects/attack-objects.spec.js @@ -28,8 +28,12 @@ const malwareObject = { description: 'This is a malware type of software, with a URL that it should not have (https://attack.mitre.org/software/SW0001)', is_family: false, + external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--c2a0b8f8-51d4-4702-8e42-ce7a65235bce'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', + x_mitre_aliases: ['software-2'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-mk', 'contributor-cm'], x_mitre_domains: ['mobile-attack'], created: '2023-03-01T00:00:00.000Z', @@ -57,8 +61,8 @@ describe('ATT&CK Objects API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -156,9 +160,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(0); }); - it('GET /api/attack-objects returns the group with ATT&CK ID GX1111', async function () { + it('GET /api/attack-objects returns the group with ATT&CK ID G9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=GX1111') + .get('/api/attack-objects?attackId=G9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -171,9 +175,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(1); }); - it('GET /api/attack-objects returns the software with ATT&CK ID SX3333', async function () { + it('GET /api/attack-objects returns the software with ATT&CK ID S9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=SX3333') + .get('/api/attack-objects?attackId=S9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -186,9 +190,9 @@ describe('ATT&CK Objects API', function () { expect(attackObjects.length).toBe(1); }); - it('GET /api/attack-objects returns the technique with ATT&CK ID TX0001', async function () { + it('GET /api/attack-objects returns the technique with ATT&CK ID T9001', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=TX0001') + .get('/api/attack-objects?attackId=T9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) @@ -203,7 +207,7 @@ describe('ATT&CK Objects API', function () { it('GET /api/attack-objects returns the objects with the requested ATT&CK IDs', async function () { const res = await request(app) - .get('/api/attack-objects?attackId=GX1111&attackId=SX3333&attackId=TX0001') + .get('/api/attack-objects?attackId=G9001&attackId=S9001&attackId=T9001') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) diff --git a/app/tests/api/campaigns/campaigns.spec.js b/app/tests/api/campaigns/campaigns.spec.js index 30f21032..c500ecc8 100644 --- a/app/tests/api/campaigns/campaigns.spec.js +++ b/app/tests/api/campaigns/campaigns.spec.js @@ -86,8 +86,8 @@ describe('Campaigns API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/collection-bundles/collection-bundles.spec.js b/app/tests/api/collection-bundles/collection-bundles.spec.js index d3928f72..4ae586a5 100644 --- a/app/tests/api/collection-bundles/collection-bundles.spec.js +++ b/app/tests/api/collection-bundles/collection-bundles.spec.js @@ -430,6 +430,108 @@ const collectionData6 = { }, }; +const attackIdsByObjectId = new Map([ + ['attack-pattern--2204c371-6100-4ae0-82f3-25c07c29772a', 'T9001'], + ['attack-pattern--82f04b1e-5371-4a6f-be06-411f0f43b483', 'T9002'], + ['attack-pattern--14fbfb6a-c4d9-4c3b-a7ef-f8df23e3b22b', 'T9003'], + ['attack-pattern--fb4f094c-ad39-4dba-b459-5e314f6d6c8d', 'T9004'], + ['attack-pattern--44fc382e-0b71-4f5d-9110-fb2e35452d98', 'T9005'], + ['course-of-action--25dc1ce8-eb55-4333-ae30-a7cb4f5894a1', 'M9001'], + ['course-of-action--e944670c-d03a-4e93-a21c-b3d4c53ec4c9', 'M9002'], + ['malware--04227b24-7817-4de1-9050-b7b1b57f5866', 'S9001'], + ['intrusion-set--d69e568e-9ac8-4c08-b32c-d93b43ba9172', 'G9001'], +]); + +function setAttackExternalReference(stixObject, sourceName, externalId, urlSegment) { + const existingReferences = Array.isArray(stixObject.external_references) + ? stixObject.external_references.slice(1) + : []; + + stixObject.external_references = [ + { + source_name: sourceName, + external_id: externalId, + url: `https://attack.mitre.org/${urlSegment}/${externalId}`, + }, + ...existingReferences, + ]; +} + +function normalizeBundleObjectForAdm(stixObject) { + if (stixObject.type === 'x-mitre-collection') { + stixObject.x_mitre_version = stixObject.x_mitre_version || '1.0'; + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + return; + } + + if (stixObject.type === 'attack-pattern') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'techniques'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + stixObject.kill_chain_phases = [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }]; + stixObject.x_mitre_data_sources = ['Process: Process Creation']; + stixObject.x_mitre_platforms = ['Linux']; + delete stixObject.x_mitre_impact_type; + return; + } + + if (stixObject.type === 'course-of-action') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'mitigations'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + return; + } + + if (stixObject.type === 'malware') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'software'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['mobile-attack']; + stixObject.x_mitre_platforms = ['Android']; + stixObject.is_family = false; + return; + } + + if (stixObject.type === 'intrusion-set') { + const externalId = attackIdsByObjectId.get(stixObject.id); + if (externalId) { + setAttackExternalReference(stixObject, 'mitre-attack', externalId, 'groups'); + } + stixObject.x_mitre_attack_spec_version = + stixObject.x_mitre_attack_spec_version || currentAttackSpecVersion; + stixObject.x_mitre_domains = ['enterprise-attack']; + stixObject.aliases = [ + stixObject.name, + ...(stixObject.aliases || []).filter((alias) => alias !== stixObject.name), + ]; + } +} + +function normalizeBundleForAdm(bundle) { + for (const stixObject of bundle.objects) { + normalizeBundleObjectForAdm(stixObject); + } +} + +normalizeBundleForAdm(collectionBundleData); +normalizeBundleForAdm(collectionBundleData2); +normalizeBundleForAdm(collectionBundleData4); +normalizeBundleForAdm(collectionBundleData5); +collectionData6.stix.x_mitre_version = '1.0'; +collectionData6.stix.x_mitre_attack_spec_version = currentAttackSpecVersion; + describe('Collection Bundles Basic API', function () { let app; let passportCookie; @@ -442,8 +544,8 @@ describe('Collection Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; reusable valid fixture objects are normalized for ADM below + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -572,7 +674,7 @@ describe('Collection Bundles Basic API', function () { collection1 = response.body; expect(collection1).toBeDefined(); expect(collection1.workspace.import_categories.additions.length).toBe(8); - expect(collection1.workspace.import_categories.errors.length).toBe(4); + expect(collection1.workspace.import_categories.errors.length).toBe(5); }); it('POST /api/collection-bundles does not show a successful preview with a duplicate collection bundle', async function () { @@ -625,7 +727,7 @@ describe('Collection Bundles Basic API', function () { expect(collection2).toBeDefined(); expect(collection2.workspace.import_categories.changes.length).toBe(1); expect(collection2.workspace.import_categories.duplicates.length).toBe(6); - expect(collection2.workspace.import_categories.errors.length).toBe(4); + expect(collection2.workspace.import_categories.errors.length).toBe(5); }); it('GET /api/references returns the malware added reference', async function () { @@ -824,6 +926,10 @@ describe('Collection Bundles Streaming API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation; reusable valid fixture objects are normalized for ADM below + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/collection-indexes/collection-indexes.spec.js b/app/tests/api/collection-indexes/collection-indexes.spec.js index 4d99cb03..7e29d610 100644 --- a/app/tests/api/collection-indexes/collection-indexes.spec.js +++ b/app/tests/api/collection-indexes/collection-indexes.spec.js @@ -74,8 +74,8 @@ describe('Collection Indexes Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/collections/collections.spec.js b/app/tests/api/collections/collections.spec.js index c3dc9bed..55a1fdd9 100644 --- a/app/tests/api/collections/collections.spec.js +++ b/app/tests/api/collections/collections.spec.js @@ -39,6 +39,7 @@ const initialCollectionData = { // external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', x_mitre_contents: [], }, }; @@ -96,7 +97,7 @@ const softwareData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -115,7 +116,7 @@ describe('Collections (x-mitre-collection) Basic API', function () { await databaseConfiguration.checkSystemConfiguration(); // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/data-components/data-components-pagination.spec.js b/app/tests/api/data-components/data-components-pagination.spec.js index 2ebfc4d8..30b0282e 100644 --- a/app/tests/api/data-components/data-components-pagination.spec.js +++ b/app/tests/api/data-components/data-components-pagination.spec.js @@ -33,6 +33,9 @@ const options = { prefix: 'x-mitre-data-component', baseUrl: '/api/data-components', label: 'Data Components', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(dataComponentsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/data-components/data-components.spec.js b/app/tests/api/data-components/data-components.spec.js index 520222b6..6637c8a0 100644 --- a/app/tests/api/data-components/data-components.spec.js +++ b/app/tests/api/data-components/data-components.spec.js @@ -55,8 +55,8 @@ describe('Data Components API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/data-sources/data-sources-pagination.spec.js b/app/tests/api/data-sources/data-sources-pagination.spec.js index 436d283f..a63fcf62 100644 --- a/app/tests/api/data-sources/data-sources-pagination.spec.js +++ b/app/tests/api/data-sources/data-sources-pagination.spec.js @@ -23,6 +23,9 @@ const options = { prefix: 'x-mitre-data-source', baseUrl: '/api/data-sources', label: 'Data Sources', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(dataSourcesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/data-sources/data-sources.spec.js b/app/tests/api/data-sources/data-sources.spec.js index 27aee363..3cdf4c07 100644 --- a/app/tests/api/data-sources/data-sources.spec.js +++ b/app/tests/api/data-sources/data-sources.spec.js @@ -33,7 +33,7 @@ const initialDataSourceData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_platforms: ['macOS', 'Office Suite', 'Identity Provider', 'Linux', 'Network Devices'], - x_mitre_collection_layers: ['duis', 'laboris'], + x_mitre_collection_layers: ['Host', 'Network'], x_mitre_contributors: ['Herbert Examplecontributor'], x_mitre_domains: ['enterprise-attack'], x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', @@ -109,8 +109,8 @@ describe('Data Sources API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js index e8946a9e..97c57055 100644 --- a/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-pagination.spec.js @@ -30,6 +30,9 @@ const options = { prefix: 'x-mitre-detection-strategy', baseUrl: '/api/detection-strategies', label: 'Detection Strategies', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(detectionStrategiesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/detection-strategies/detection-strategies-spec.js b/app/tests/api/detection-strategies/detection-strategies-spec.js index 72595866..3c041f8e 100644 --- a/app/tests/api/detection-strategies/detection-strategies-spec.js +++ b/app/tests/api/detection-strategies/detection-strategies-spec.js @@ -30,8 +30,8 @@ const initialPostContentForDetectionStrategy = { x_mitre_version: '1.0', x_mitre_domains: ['enterprise-attack'], x_mitre_analytic_refs: [ - 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match! - 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match! + 'x-mitre-analytic--12345678-1234-4234-8234-123456789000', // must match! + 'x-mitre-analytic--12345678-1234-4234-8234-123456789012', // must match! ], }, }; @@ -43,7 +43,7 @@ const initialPostContentForAnalytic1 = { }, }, stix: { - id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789000', // must match the analytic's embedded ref! + id: 'x-mitre-analytic--12345678-1234-4234-8234-123456789000', // must match the analytic's embedded ref! name: 'analytic-1', spec_version: '2.1', type: 'x-mitre-analytic', @@ -52,7 +52,7 @@ const initialPostContentForAnalytic1 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -74,7 +74,7 @@ const initialPostContentForAnalytic2 = { }, }, stix: { - id: 'x-mitre-analytic--12345678-1234-1234-1234-123456789012', // must match the analytic's embedded ref! + id: 'x-mitre-analytic--12345678-1234-4234-8234-123456789012', // must match the analytic's embedded ref! name: 'analytic-2', spec_version: '2.1', type: 'x-mitre-analytic', @@ -83,7 +83,7 @@ const initialPostContentForAnalytic2 = { object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.0', - x_mitre_platforms: ['windows'], + x_mitre_platforms: ['Windows'], x_mitre_domains: ['enterprise-attack'], x_mitre_mutable_elements: [ { @@ -110,8 +110,8 @@ describe('Detection Strategies API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/groups/groups-input-validation.spec.js b/app/tests/api/groups/groups-input-validation.spec.js index f3bbbd25..a1f306c8 100644 --- a/app/tests/api/groups/groups-input-validation.spec.js +++ b/app/tests/api/groups/groups-input-validation.spec.js @@ -94,8 +94,8 @@ describe('Groups API Input Validation', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/groups/groups-pagination.spec.js b/app/tests/api/groups/groups-pagination.spec.js index 5dcdf283..4f399540 100644 --- a/app/tests/api/groups/groups-pagination.spec.js +++ b/app/tests/api/groups/groups-pagination.spec.js @@ -23,6 +23,9 @@ const options = { prefix: 'intrustion-set', baseUrl: '/api/groups', label: 'Groups', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(groupsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/groups/groups.query.json b/app/tests/api/groups/groups.query.json deleted file mode 100644 index 82bd3eeb..00000000 --- a/app/tests/api/groups/groups.query.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "workspace": { - "workflow": {} - }, - "stix": { - "spec_version": "2.1", - "type": "intrusion-set", - "description": "This is a group.", - "external_references": [], - "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "x_mitre_version": "1.0" - } -} diff --git a/app/tests/api/groups/groups.query.spec.js b/app/tests/api/groups/groups.query.spec.js index 86d87c9a..59771324 100644 --- a/app/tests/api/groups/groups.query.spec.js +++ b/app/tests/api/groups/groups.query.spec.js @@ -1,5 +1,3 @@ -const fs = require('fs').promises; - const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); @@ -17,15 +15,28 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const userAccountsService = require('../../../services/system/user-accounts-service'); const groupsService = require('../../../services/stix/groups-service'); +// Base group used to derive all of the seeded query fixtures. Each created group +// deep-clones this and overrides only the fields a given test cares about. +const baseGroup = { + workspace: { + workflow: {}, + }, + stix: { + spec_version: '2.1', + type: 'intrusion-set', + description: 'This is a group.', + external_references: [], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + x_mitre_version: '1.0', + x_mitre_domains: ['enterprise-attack'], + }, +}; + function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function readJson(path) { - const data = await fs.readFile(require.resolve(path)); - return JSON.parse(data); -} - async function configureAndLoadGroups(baseGroup, userAccountId1, userAccountId2) { // Helper: create a group from config async function createGroup(overrides, userAccountId) { @@ -144,8 +155,8 @@ describe('Groups API Queries', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -157,7 +168,6 @@ describe('Groups API Queries', function () { userAccount1 = await userAccountsService.create(userAccountData1); userAccount2 = await userAccountsService.create(userAccountData2); - const baseGroup = await readJson('./groups.query.json'); await configureAndLoadGroups(baseGroup, userAccount1.id, userAccount2.id); }); diff --git a/app/tests/api/groups/groups.spec.js b/app/tests/api/groups/groups.spec.js index 17902cf0..c8d7fd3e 100644 --- a/app/tests/api/groups/groups.spec.js +++ b/app/tests/api/groups/groups.spec.js @@ -80,8 +80,8 @@ describe('Groups API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index b188211c..8c51c12e 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -25,6 +25,7 @@ const initialObjectData = { spec_version: '2.1', type: 'identity', description: 'This is an identity.', + external_references: [{ source_name: 'source-1', external_id: 's1' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -41,8 +42,8 @@ describe('Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/marking-definitions/marking-definitions.spec.js b/app/tests/api/marking-definitions/marking-definitions.spec.js index 055fa8db..39c4d594 100644 --- a/app/tests/api/marking-definitions/marking-definitions.spec.js +++ b/app/tests/api/marking-definitions/marking-definitions.spec.js @@ -40,8 +40,8 @@ describe('Marking Definitions API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/matrices/matrices.spec.js b/app/tests/api/matrices/matrices.spec.js index a02987c4..a77adfd4 100644 --- a/app/tests/api/matrices/matrices.spec.js +++ b/app/tests/api/matrices/matrices.spec.js @@ -40,7 +40,7 @@ const initialObjectData = { 'x-mitre-tactic--daa4cbb1-b4f4-4723-a824-7f1efd6e0592', 'x-mitre-tactic--d679bca2-e57d-4935-8650-8031c87a4400', ], - x_mitre_domains: ['mitre-attack'], + x_mitre_domains: ['enterprise-attack'], x_mitre_version: '1.0', }, }; @@ -60,8 +60,8 @@ describe('Matrices API', function () { // Initialize the express app app = await require('../../../index').initializeApp(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Log into the app diff --git a/app/tests/api/mitigations/mitigations-pagination.spec.js b/app/tests/api/mitigations/mitigations-pagination.spec.js index f7eb5b6e..cf4ec5d8 100644 --- a/app/tests/api/mitigations/mitigations-pagination.spec.js +++ b/app/tests/api/mitigations/mitigations-pagination.spec.js @@ -24,6 +24,9 @@ const options = { prefix: 'course-of-action', baseUrl: '/api/mitigations', label: 'Mitigations', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(mitigationsService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/mitigations/mitigations.spec.js b/app/tests/api/mitigations/mitigations.spec.js index 7d5b2da1..191de6f6 100644 --- a/app/tests/api/mitigations/mitigations.spec.js +++ b/app/tests/api/mitigations/mitigations.spec.js @@ -44,8 +44,8 @@ describe('Mitigations API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/notes/notes.spec.js b/app/tests/api/notes/notes.spec.js index c31c7769..6493d747 100644 --- a/app/tests/api/notes/notes.spec.js +++ b/app/tests/api/notes/notes.spec.js @@ -44,8 +44,8 @@ describe('Notes API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/recent-activity/recent-activity.spec.js b/app/tests/api/recent-activity/recent-activity.spec.js index 7f2a20cc..1c7e9819 100644 --- a/app/tests/api/recent-activity/recent-activity.spec.js +++ b/app/tests/api/recent-activity/recent-activity.spec.js @@ -30,8 +30,8 @@ describe('Recent Activity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this suite seeds existing STIX bundle fixtures + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/references/references.spec.js b/app/tests/api/references/references.spec.js index b73df6d5..c4d2d08f 100644 --- a/app/tests/api/references/references.spec.js +++ b/app/tests/api/references/references.spec.js @@ -45,8 +45,8 @@ describe('References API', function () { // Wait until the Reference indexes are created await Reference.init(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation for consistency with STIX-object API suites + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/relationships/relationships-pagination.spec.js b/app/tests/api/relationships/relationships-pagination.spec.js index 194ebcc6..de2f5fd9 100644 --- a/app/tests/api/relationships/relationships-pagination.spec.js +++ b/app/tests/api/relationships/relationships-pagination.spec.js @@ -1,5 +1,8 @@ const relationshipsService = require('../../../services/stix/relationships-service'); const PaginationTests = require('../../shared/pagination'); +const config = require('../../../config/config'); + +config.validateRequests.withOpenApi = true; // modified and created properties will be set before calling REST API // stix.id property will be created by REST API @@ -28,6 +31,17 @@ const options = { prefix: 'relationship', baseUrl: '/api/relationships', label: 'Relationships', + validateWithAdm: true, +}; +const relationshipsPaginationService = { + async create(data, options) { + delete data.stix.name; + return relationshipsService.create(data, options); + }, }; -const paginationTests = new PaginationTests(relationshipsService, initialObjectData, options); +const paginationTests = new PaginationTests( + relationshipsPaginationService, + initialObjectData, + options, +); paginationTests.executeTests(); diff --git a/app/tests/api/relationships/relationships.spec.js b/app/tests/api/relationships/relationships.spec.js index 8c0884a3..a18e7f05 100644 --- a/app/tests/api/relationships/relationships.spec.js +++ b/app/tests/api/relationships/relationships.spec.js @@ -53,8 +53,8 @@ describe('Relationships API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/reports/reports.spec.js b/app/tests/api/reports/reports.spec.js index 0ea647bd..e3e696be 100644 --- a/app/tests/api/reports/reports.spec.js +++ b/app/tests/api/reports/reports.spec.js @@ -70,8 +70,8 @@ describe('Reports API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/session/session.spec.js b/app/tests/api/session/session.spec.js index 01a05b60..3fccac23 100644 --- a/app/tests/api/session/session.spec.js +++ b/app/tests/api/session/session.spec.js @@ -18,8 +18,8 @@ describe('Session API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation for consistency with STIX-object API suites + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/software/software-pagination.spec.js b/app/tests/api/software/software-pagination.spec.js index 2d9a038f..b0a5a2e4 100644 --- a/app/tests/api/software/software-pagination.spec.js +++ b/app/tests/api/software/software-pagination.spec.js @@ -1,5 +1,8 @@ const softwareService = require('../../../services/stix/software-service'); const PaginationTests = require('../../shared/pagination'); +const config = require('../../../config/config'); + +config.validateRequests.withOpenApi = true; // modified and created properties will be set before calling REST API // stix.id property will be created by REST API @@ -19,7 +22,7 @@ const initialObjectData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -29,6 +32,7 @@ const options = { prefix: 'software', baseUrl: '/api/software', label: 'Software', + validateWithAdm: true, }; const paginationTests = new PaginationTests(softwareService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/software/software.spec.js b/app/tests/api/software/software.spec.js index 2ceb91fe..d6894487 100644 --- a/app/tests/api/software/software.spec.js +++ b/app/tests/api/software/software.spec.js @@ -31,7 +31,7 @@ const initialObjectData = { created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', x_mitre_version: '1.1', x_mitre_aliases: ['software-1'], - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Android'], x_mitre_contributors: ['contributor-1', 'contributor-2'], x_mitre_domains: ['mobile-attack'], }, @@ -61,8 +61,8 @@ describe('Software API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/stix-bundles/stix-bundles-old.spec.js b/app/tests/api/stix-bundles/stix-bundles-old.spec.js index 649faf98..893ba0c3 100644 --- a/app/tests/api/stix-bundles/stix-bundles-old.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles-old.spec.js @@ -1,6 +1,7 @@ const request = require('supertest'); const { expect } = require('expect'); +const config = require('../../../config/config'); const logger = require('../../../lib/logger'); logger.level = 'debug'; @@ -629,6 +630,121 @@ const initialObjectData = { ], }; +function normalizeLegacyBundleFixtureForAdmImport(bundle) { + const counters = { + 'attack-pattern': 9000, + 'course-of-action': 9000, + malware: 9000, + 'intrusion-set': 9000, + 'x-mitre-data-source': 9000, + }; + + for (const stixObject of bundle.objects) { + if ( + Array.isArray(stixObject.external_references) && + stixObject.external_references.length === 0 && + stixObject.type !== 'intrusion-set' + ) { + delete stixObject.external_references; + } + + if (stixObject.type !== 'marking-definition' && stixObject.type !== 'note') { + stixObject.x_mitre_attack_spec_version = config.app.attackSpecVersion; + } + + switch (stixObject.type) { + case 'x-mitre-collection': + stixObject.x_mitre_version = '1.0'; + break; + + case 'identity': + break; + + case 'attack-pattern': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `T${counters[stixObject.type]}`; + stixObject.kill_chain_phases = stixObject.kill_chain_phases.map((phase) => ({ + ...phase, + kill_chain_name: stixObject.x_mitre_domains.includes(mobileDomain) + ? 'mitre-mobile-attack' + : stixObject.x_mitre_domains.includes(icsDomain) + ? 'mitre-ics-attack' + : 'mitre-attack', + phase_name: 'execution', + })); + stixObject.x_mitre_platforms = ['Windows', 'Linux']; + delete stixObject.x_mitre_impact_type; + break; + + case 'course-of-action': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `M${counters[stixObject.type]}`; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'malware': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `S${counters[stixObject.type]}`; + stixObject.is_family = false; + stixObject.x_mitre_aliases = [stixObject.name, ...stixObject.x_mitre_aliases]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_platforms = ['Windows']; + break; + + case 'intrusion-set': + counters[stixObject.type] += 1; + if ( + !Array.isArray(stixObject.external_references) || + stixObject.external_references.length === 0 + ) { + stixObject.external_references = [ + { source_name: 'mitre-attack', external_id: `G${counters[stixObject.type]}` }, + ]; + } + stixObject.aliases = [stixObject.name, ...stixObject.aliases]; + stixObject.x_mitre_domains = [enterpriseDomain]; + break; + + case 'campaign': + stixObject.aliases = [stixObject.name, ...stixObject.aliases]; + stixObject.external_references.push( + { source_name: 'Article 1', description: 'First seen citation.' }, + { source_name: 'Article 2', description: 'Last seen citation.' }, + ); + stixObject.revoked = false; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'relationship': + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + break; + + case 'x-mitre-data-source': + counters[stixObject.type] += 1; + stixObject.external_references[0].external_id = `DS${counters[stixObject.type]}`; + stixObject.description = `${stixObject.name} data source.`; + stixObject.x_mitre_collection_layers = ['Host']; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_version = '1.0'; + break; + + case 'x-mitre-data-component': + stixObject.description = `${stixObject.name} data component.`; + stixObject.x_mitre_domains = [enterpriseDomain]; + stixObject.x_mitre_modified_by_ref = mitreIdentityId; + stixObject.x_mitre_version = '1.0'; + break; + + default: + break; + } + } +} + +normalizeLegacyBundleFixtureForAdmImport(initialObjectData); + // function printBundleCount(bundle) { // const count = { // techniques: 0, @@ -670,6 +786,10 @@ describe('STIX Bundles Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); + // Enable ADM validation for the legacy bundle import path + config.validateRequests.withAttackDataModel = true; + config.validateRequests.withOpenApi = true; + // Initialize the express app app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/stix-bundles/stix-bundles.spec.js b/app/tests/api/stix-bundles/stix-bundles.spec.js index fe0c43b6..98b5c656 100644 --- a/app/tests/api/stix-bundles/stix-bundles.spec.js +++ b/app/tests/api/stix-bundles/stix-bundles.spec.js @@ -69,7 +69,7 @@ const login = require('../../shared/login'); const enterpriseDomain = 'enterprise-attack'; const icsDomain = 'ics-attack'; -const collectionId = 'x-mitre-collection--b0b12345-aaaa-bbbb-cccc-dddddddddddd'; +const collectionId = 'x-mitre-collection--b0b12345-aaaa-4bbb-8ccc-dddddddddddd'; const collectionTimestamp = new Date().toISOString(); const markingDefinitionId = 'marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'; @@ -104,65 +104,75 @@ const newSpecBundleData = { spec_version: '2.1', type: 'x-mitre-collection', description: 'Test collection for new ATT&CK specification features', - external_references: [], object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_version: '1.0', x_mitre_contents: [ // Techniques - { object_ref: 'attack-pattern--new-ent-001', object_modified: '2024-01-15T10:00:00.000Z' }, - { object_ref: 'attack-pattern--new-ent-002', object_modified: '2024-01-15T10:00:00.000Z' }, - { object_ref: 'attack-pattern--new-ics-001', object_modified: '2024-01-15T10:00:00.000Z' }, + { + object_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', + object_modified: '2024-01-15T10:00:00.000Z', + }, + { + object_ref: 'attack-pattern--22222222-2222-4222-8222-222222222222', + object_modified: '2024-01-15T10:00:00.000Z', + }, + { + object_ref: 'attack-pattern--33333333-3333-4333-8333-333333333333', + object_modified: '2024-01-15T10:00:00.000Z', + }, // Analytics { - object_ref: 'x-mitre-analytic--new-ana-001', + object_ref: 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-analytic--new-ana-002', + object_ref: 'x-mitre-analytic--55555555-5555-4555-8555-555555555555', object_modified: '2024-01-15T10:00:00.000Z', }, // Detection Strategies { - object_ref: 'x-mitre-detection-strategy--new-ds-001', + object_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-detection-strategy--new-ds-002', + object_ref: 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-detection-strategy--new-ds-003', + object_ref: 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', object_modified: '2024-01-15T10:00:00.000Z', }, // Data Components { - object_ref: 'x-mitre-data-component--new-dc-001', + object_ref: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-data-component--new-dc-002', + object_ref: 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', object_modified: '2024-01-15T10:00:00.000Z', }, // Data Sources { - object_ref: 'x-mitre-data-source--new-ds-src-001', + object_ref: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'x-mitre-data-source--new-ds-src-002', + object_ref: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', object_modified: '2024-01-15T10:00:00.000Z', }, // Relationships { - object_ref: 'relationship--new-ds-detects-tech-001', + object_ref: 'relationship--dddddddd-dddd-4ddd-8ddd-dddddddddddd', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'relationship--new-ds-detects-tech-002', + object_ref: 'relationship--eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', object_modified: '2024-01-15T10:00:00.000Z', }, { - object_ref: 'relationship--new-dc-detects-tech-dep', + object_ref: 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', object_modified: '2024-01-15T10:00:00.000Z', }, // Supporting objects (identity and marking-definition should also be in x_mitre_contents) @@ -176,7 +186,7 @@ const newSpecBundleData = { // ======================================== { type: 'attack-pattern', - id: 'attack-pattern--new-ent-001', + id: 'attack-pattern--11111111-1111-4111-8111-111111111111', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Technique 1', @@ -186,13 +196,14 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9001' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, }, { type: 'attack-pattern', - id: 'attack-pattern--new-ent-002', + id: 'attack-pattern--22222222-2222-4222-8222-222222222222', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Technique 2', @@ -202,13 +213,14 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9002' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'persistence' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, }, { type: 'attack-pattern', - id: 'attack-pattern--new-ics-001', + id: 'attack-pattern--33333333-3333-4333-8333-333333333333', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Technique 1', @@ -218,6 +230,7 @@ const newSpecBundleData = { created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'T9003' }], kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain, icsDomain], x_mitre_version: '1.0', x_mitre_is_subtechnique: false, @@ -228,7 +241,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-analytic', - id: 'x-mitre-analytic--new-ana-001', + id: 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Process Execution Analytic', @@ -239,16 +252,18 @@ const newSpecBundleData = { external_references: [ { source_name: 'mitre-attack', - external_id: 'ANA-001', - url: 'https://attack.mitre.org/detectionstrategies/DS-002#ANA-001', + external_id: 'AN0001', + url: 'https://attack.mitre.org/detectionstrategies/DET0002#AN0001', }, ], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_platforms: ['Windows'], x_mitre_version: '1.0', }, { type: 'x-mitre-analytic', - id: 'x-mitre-analytic--new-ana-002', + id: 'x-mitre-analytic--55555555-5555-4555-8555-555555555555', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Persistence Mechanism Analytic', @@ -256,9 +271,11 @@ const newSpecBundleData = { spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'ANA-002' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'AN0002' }], // Note: No URL, meaning it's not attached to a detection strategy, meaning we don't want it in the bundle + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_platforms: ['Windows'], x_mitre_version: '1.0', }, @@ -267,46 +284,52 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-001', + id: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 1 - Detects Technique via Relationship', - description: 'This detection strategy detects attack-pattern--new-ent-001', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-001' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0001' }], + x_mitre_analytic_refs: ['x-mitre-analytic--44444444-4444-4444-8444-444444444444'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - // Note: No x_mitre_domains - this is inferred from relationships }, { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-002', + id: 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 2 - References Analytic', - description: 'This detection strategy references x-mitre-analytic--new-ana-001', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-002' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0002' }], + x_mitre_analytic_refs: ['x-mitre-analytic--44444444-4444-4444-8444-444444444444'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_analytic_refs: ['x-mitre-analytic--new-ana-001'], - // Note: No x_mitre_domains - this is inferred from analytic refs }, { type: 'x-mitre-detection-strategy', - id: 'x-mitre-detection-strategy--new-ds-003', + id: 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Detection Strategy 3 - Not Included (orphaned)', - description: 'This detection strategy should NOT be included - no technique or analytic', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [{ source_name: 'mitre-attack', external_id: 'DS-003' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'DET0003' }], + x_mitre_analytic_refs: ['x-mitre-analytic--55555555-5555-4555-8555-555555555555'], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - // Note: No detects relationship, no analytic refs, so this should NOT appear in bundle + // Note: No detects relationship, and its analytic lacks a URL, so this should NOT appear in bundle }, // ======================================== @@ -314,7 +337,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-data-component', - id: 'x-mitre-data-component--new-dc-001', + id: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Data Component', @@ -323,13 +346,15 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DC9001' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_data_source_ref: 'x-mitre-data-source--new-ds-src-001', + x_mitre_data_source_ref: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', }, { type: 'x-mitre-data-component', - id: 'x-mitre-data-component--new-dc-002', + id: 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Data Component', @@ -338,9 +363,11 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DC9002' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, x_mitre_domains: [icsDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', - x_mitre_data_source_ref: 'x-mitre-data-source--new-ds-src-002', + x_mitre_data_source_ref: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', }, // ======================================== @@ -348,7 +375,7 @@ const newSpecBundleData = { // ======================================== { type: 'x-mitre-data-source', - id: 'x-mitre-data-source--new-ds-src-001', + id: 'x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'Enterprise Data Source', @@ -357,12 +384,15 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DS9001' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_collection_layers: ['Host'], x_mitre_domains: [enterpriseDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', }, { type: 'x-mitre-data-source', - id: 'x-mitre-data-source--new-ds-src-002', + id: 'x-mitre-data-source--cccccccc-cccc-4ccc-8ccc-cccccccccccc', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', name: 'ICS Data Source', @@ -371,7 +401,10 @@ const newSpecBundleData = { object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, external_references: [{ source_name: 'mitre-attack', external_id: 'DS9002' }], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_collection_layers: ['Host'], x_mitre_domains: [icsDomain], + x_mitre_modified_by_ref: mitreIdentityId, x_mitre_version: '1.0', }, @@ -382,49 +415,52 @@ const newSpecBundleData = { // Valid 'detects' relationship: Detection Strategy → Technique { type: 'relationship', - id: 'relationship--new-ds-detects-tech-001', + id: 'relationship--dddddddd-dddd-4ddd-8ddd-dddddddddddd', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-detection-strategy--new-ds-001', - target_ref: 'attack-pattern--new-ent-001', + source_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + target_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', description: 'Detection strategy detects enterprise technique 1', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // Valid 'detects' relationship: Detection Strategy → Technique (different technique) { type: 'relationship', - id: 'relationship--new-ds-detects-tech-002', + id: 'relationship--eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-detection-strategy--new-ds-001', - target_ref: 'attack-pattern--new-ent-002', + source_ref: 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + target_ref: 'attack-pattern--22222222-2222-4222-8222-222222222222', description: 'Detection strategy detects enterprise technique 2', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // DEPRECATED 'detects' relationship: Data Component → Technique (should be IGNORED) { type: 'relationship', - id: 'relationship--new-dc-detects-tech-dep', + id: 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', created: '2024-01-15T10:00:00.000Z', modified: '2024-01-15T10:00:00.000Z', relationship_type: 'detects', - source_ref: 'x-mitre-data-component--new-dc-001', - target_ref: 'attack-pattern--new-ent-001', + source_ref: 'x-mitre-data-component--99999999-9999-4999-8999-999999999999', + target_ref: 'attack-pattern--11111111-1111-4111-8111-111111111111', description: 'DEPRECATED: Data component detects technique (should be ignored)', spec_version: '2.1', object_marking_refs: [markingDefinitionId], created_by_ref: mitreIdentityId, - external_references: [], + x_mitre_attack_spec_version: config.app.attackSpecVersion, + x_mitre_modified_by_ref: mitreIdentityId, }, // ======================================== @@ -437,11 +473,14 @@ const newSpecBundleData = { modified: '2017-06-01T00:00:00.000Z', name: 'The MITRE Corporation', identity_class: 'organization', + object_marking_refs: [markingDefinitionId], spec_version: '2.1', + x_mitre_attack_spec_version: config.app.attackSpecVersion, }, { type: 'marking-definition', id: markingDefinitionId, + created_by_ref: mitreIdentityId, created: '2017-06-01T00:00:00.000Z', definition_type: 'statement', definition: { @@ -465,8 +504,8 @@ describe('STIX Bundles New Specification API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the imported bundle fixture is ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -570,7 +609,7 @@ describe('STIX Bundles New Specification API', function () { // The new spec maintains a clean separation: deprecated patterns are excluded // even if both endpoints exist in the bundle as primary objects const deprecatedRelationship = stixBundle.objects.find( - (o) => o.id === 'relationship--new-dc-detects-tech-dep', + (o) => o.id === 'relationship--ffffffff-ffff-4fff-8fff-ffffffffffff', ); expect(deprecatedRelationship).toBeUndefined(); @@ -578,7 +617,7 @@ describe('STIX Bundles New Specification API', function () { const validDetectsRels = stixBundle.objects.filter( (o) => o.type === 'relationship' && o.relationship_type === 'detects', ); - expect(validDetectsRels.length).toBe(2); // Only DS-001 detects relationships + expect(validDetectsRels.length).toBe(2); // Only DET0001 detects relationships validDetectsRels.forEach((rel) => { expect(rel.source_ref).toMatch(/^x-mitre-detection-strategy--/); }); @@ -596,8 +635,10 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-001 is included because it has 'detects' relationships to in-scope techniques - const ds001 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-001'); + // Verify DET0001 is included because it has 'detects' relationships to in-scope techniques + const ds001 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', + ); expect(ds001).toBeDefined(); expect(ds001.name).toBe('Detection Strategy 1 - Detects Technique via Relationship'); expect(ds001.x_mitre_domains).toEqual([enterpriseDomain]); @@ -607,7 +648,7 @@ describe('STIX Bundles New Specification API', function () { (o) => o.type === 'relationship' && o.relationship_type === 'detects' && - o.source_ref === 'x-mitre-detection-strategy--new-ds-001', + o.source_ref === 'x-mitre-detection-strategy--66666666-6666-4666-8666-666666666666', ); expect(ds001DetectsRels.length).toBe(2); // Detects two techniques }); @@ -624,15 +665,21 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-002 is included because it references an in-scope analytic via x_mitre_analytic_refs - const ds002 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-002'); + // Verify DET0002 is included because it references an in-scope analytic via x_mitre_analytic_refs + const ds002 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--77777777-7777-4777-8777-777777777777', + ); expect(ds002).toBeDefined(); expect(ds002.name).toBe('Detection Strategy 2 - References Analytic'); - expect(ds002.x_mitre_analytic_refs).toContain('x-mitre-analytic--new-ana-001'); + expect(ds002.x_mitre_analytic_refs).toContain( + 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', + ); expect(ds002.x_mitre_domains).toEqual([enterpriseDomain]); // Verify the referenced analytic is in the bundle - const analytic = stixBundle.objects.find((o) => o.id === 'x-mitre-analytic--new-ana-001'); + const analytic = stixBundle.objects.find( + (o) => o.id === 'x-mitre-analytic--44444444-4444-4444-8444-444444444444', + ); expect(analytic).toBeDefined(); }); @@ -648,8 +695,10 @@ describe('STIX Bundles New Specification API', function () { const stixBundle = res.body; - // Verify DS-003 is NOT included (orphaned - no technique or analytic reference) - const ds003 = stixBundle.objects.find((o) => o.id === 'x-mitre-detection-strategy--new-ds-003'); + // Verify DET0003 is NOT included (orphaned - no technique or analytic reference) + const ds003 = stixBundle.objects.find( + (o) => o.id === 'x-mitre-detection-strategy--88888888-8888-4888-8888-888888888888', + ); expect(ds003).toBeUndefined(); }); @@ -668,7 +717,7 @@ describe('STIX Bundles New Specification API', function () { const dataSources = stixBundle.objects.filter((o) => o.type === 'x-mitre-data-source'); expect(dataSources.length).toBe(1); // Only new-ds-src-001 (enterprise) - expect(dataSources[0].id).toBe('x-mitre-data-source--new-ds-src-001'); + expect(dataSources[0].id).toBe('x-mitre-data-source--bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb'); }); it('GET /api/stix-bundles without includeDataSources excludes data sources', async function () { @@ -711,7 +760,7 @@ describe('STIX Bundles New Specification API', function () { // Only 1 technique should be in ICS (new-ics-001) const techniques = stixBundle.objects.filter((o) => o.type === 'attack-pattern'); expect(techniques.length).toBe(1); - expect(techniques[0].id).toBe('attack-pattern--new-ics-001'); + expect(techniques[0].id).toBe('attack-pattern--33333333-3333-4333-8333-333333333333'); // No analytics in ICS domain const analytics = stixBundle.objects.filter((o) => o.type === 'x-mitre-analytic'); @@ -720,7 +769,9 @@ describe('STIX Bundles New Specification API', function () { // Only ICS data component (new-dc-002) const dataComponents = stixBundle.objects.filter((o) => o.type === 'x-mitre-data-component'); expect(dataComponents.length).toBe(1); - expect(dataComponents[0].id).toBe('x-mitre-data-component--new-dc-002'); + expect(dataComponents[0].id).toBe( + 'x-mitre-data-component--aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', + ); // No detection strategies (none detect ICS techniques or reference ICS analytics) const detectionStrategies = stixBundle.objects.filter( diff --git a/app/tests/api/system-configuration/create-object-identity.spec.js b/app/tests/api/system-configuration/create-object-identity.spec.js index 50ca8dc0..3942d785 100644 --- a/app/tests/api/system-configuration/create-object-identity.spec.js +++ b/app/tests/api/system-configuration/create-object-identity.spec.js @@ -22,7 +22,7 @@ const initialTacticData = { spec_version: '2.1', type: 'x-mitre-tactic', description: 'This is a tactic. yellow.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'TA9001' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -55,8 +55,8 @@ describe('Create Object with Organization Identity API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/system-configuration/system-configuration.spec.js b/app/tests/api/system-configuration/system-configuration.spec.js index 7ff9f09d..99be4fdf 100644 --- a/app/tests/api/system-configuration/system-configuration.spec.js +++ b/app/tests/api/system-configuration/system-configuration.spec.js @@ -37,8 +37,8 @@ describe('System Configuration API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index d810918b..217f7844 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -24,7 +24,7 @@ const initialObjectData = { spec_version: '2.1', type: 'x-mitre-tactic', description: 'This is a tactic. yellow.', - external_references: [{ source_name: 'source-1', external_id: 's1' }], + external_references: [{ source_name: 'mitre-attack', external_id: 'TA9001' }], object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], }, }; @@ -41,8 +41,8 @@ describe('Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/tactics/tactics.techniques.json b/app/tests/api/tactics/tactics.techniques.json index e93207cd..5c158855 100644 --- a/app/tests/api/tactics/tactics.techniques.json +++ b/app/tests/api/tactics/tactics.techniques.json @@ -84,21 +84,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enlil" + "phase_name": "execution" }, { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0001", - "external_id": "TX0001" + "url": "https://attack.mitre.org/techniques/T9001", + "external_id": "T9001" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -118,17 +118,17 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0002", - "external_id": "TX0002" + "url": "https://attack.mitre.org/techniques/T9002", + "external_id": "T9002" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -148,17 +148,17 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enki" + "phase_name": "defense-evasion" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0003", - "external_id": "TX0003" + "url": "https://attack.mitre.org/techniques/T9003", + "external_id": "T9003" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -178,21 +178,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "enlil" + "phase_name": "execution" }, { "kill_chain_name": "mitre-attack", - "phase_name": "enki" + "phase_name": "defense-evasion" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0004", - "external_id": "TX0004" + "url": "https://attack.mitre.org/techniques/T9004", + "external_id": "T9004" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -212,21 +212,21 @@ "kill_chain_phases": [ { "kill_chain_name": "mitre-attack", - "phase_name": "nanna-suen" + "phase_name": "collection" }, { "kill_chain_name": "mitre-attack", - "phase_name": "nabu" + "phase_name": "persistence" } ], "external_references": [ { "source_name": "mitre-attack", - "url": "https://attack.mitre.org/techniques/TX0005", - "external_id": "TX0005" + "url": "https://attack.mitre.org/techniques/T9005", + "external_id": "T9005" } ], - "x_mitre_data_sources": [], + "x_mitre_is_subtechnique": false, "x_mitre_version": "1.0", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", @@ -241,15 +241,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0001", - "url": "https://attack.mitre.org/tactics/TAX0001" + "external_id": "TA9001", + "url": "https://attack.mitre.org/tactics/TA9001" } ], "id": "x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357", "modified": "2022-04-01T06:07:08.000Z", "name": "Enlil", "type": "x-mitre-tactic", - "x_mitre_shortname": "enlil", + "x_mitre_shortname": "execution", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -264,15 +264,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0002", - "url": "https://attack.mitre.org/tactics/TAX0002" + "external_id": "TA9002", + "url": "https://attack.mitre.org/tactics/TA9002" } ], "id": "x-mitre-tactic--953fd636-2af2-4cad-adc8-5d7903295dba", "modified": "2022-04-01T06:07:08.000Z", "name": "Enki", "type": "x-mitre-tactic", - "x_mitre_shortname": "enki", + "x_mitre_shortname": "defense-evasion", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -287,15 +287,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0003", - "url": "https://attack.mitre.org/tactics/TAX0003" + "external_id": "TA9003", + "url": "https://attack.mitre.org/tactics/TA9003" } ], "id": "x-mitre-tactic--c1768fcd-abe2-462f-95cc-bfedbc8c64c6", "modified": "2022-03-01T06:07:08.000Z", "name": "Ianna", "type": "x-mitre-tactic", - "x_mitre_shortname": "inanna", + "x_mitre_shortname": "discovery", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -310,15 +310,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0004", - "url": "https://attack.mitre.org/tactics/TAX0004" + "external_id": "TA9004", + "url": "https://attack.mitre.org/tactics/TA9004" } ], "id": "x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52", "modified": "2022-02-01T06:07:08.000Z", "name": "Nabu", "type": "x-mitre-tactic", - "x_mitre_shortname": "nabu", + "x_mitre_shortname": "persistence", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -333,15 +333,15 @@ "external_references": [ { "source_name": "mitre-attack", - "external_id": "TAX0005", - "url": "https://attack.mitre.org/tactics/TAX0005" + "external_id": "TA9005", + "url": "https://attack.mitre.org/tactics/TA9005" } ], "id": "x-mitre-tactic--0b518521-aae3-4169-9f7a-cdf8455a2d14", "modified": "2022-01-01T06:07:08.000Z", "name": "Nanna-Suen", "type": "x-mitre-tactic", - "x_mitre_shortname": "nanna-suen", + "x_mitre_shortname": "collection", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -356,15 +356,15 @@ "external_references": [ { "source_name": "mitre-mobile-attack", - "external_id": "TAX0006", - "url": "https://attack.mitre.org/tactics/TAX0006" + "external_id": "TA9006", + "url": "https://attack.mitre.org/tactics/TA9006" } ], "id": "x-mitre-tactic--f5a8c28b-3002-4d5b-9571-3f68b2b57e29", "modified": "2022-09-09T01:02:03.000Z", "name": "Nabu", "type": "x-mitre-tactic", - "x_mitre_shortname": "nabu", + "x_mitre_shortname": "persistence", "x_mitre_attack_spec_version": "2.1.0", "x_mitre_modified_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "x_mitre_version": "1.0", @@ -390,8 +390,7 @@ "created": "2020-01-01T00:00:00.000Z", "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", "definition_type": "statement", - "spec_version": "2.1", - "x_mitre_domains": ["ics-attack"] + "spec_version": "2.1" } ] } diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 7f28a49d..e13bc0a4 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -30,8 +30,8 @@ describe('Tactics with Techniques API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the imported bundle fixture is ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app @@ -64,8 +64,12 @@ describe('Tactics with Techniques API', function () { expect(Array.isArray(tactics)).toBe(true); expect(tactics.length).toBe(6); - tactic1 = tactics.find((t) => t.stix.x_mitre_shortname === 'enlil'); - tactic2 = tactics.find((t) => t.stix.x_mitre_shortname === 'nabu'); + tactic1 = tactics.find( + (t) => t.stix.id === 'x-mitre-tactic--d932e995-5207-4347-88ec-b52b32762357', + ); + tactic2 = tactics.find( + (t) => t.stix.id === 'x-mitre-tactic--60cf8617-223d-47db-b15e-0cdf3c1d6f52', + ); }); it('GET /api/techniques should return the preloaded techniques', async function () { diff --git a/app/tests/api/teams/teams-invalid.spec.js b/app/tests/api/teams/teams-invalid.spec.js index 148e8f0e..57e02108 100644 --- a/app/tests/api/teams/teams-invalid.spec.js +++ b/app/tests/api/teams/teams-invalid.spec.js @@ -7,10 +7,17 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const teams = require('./teams.invalid.json'); - const login = require('../../shared/login'); +// Invalid team payloads — each is missing a required field. Used to assert the +// API rejects malformed input with a 400. +const teams = [ + { + description: 'no name', + userIDs: [], + }, +]; + describe('Teams API Test Invalid Data', function () { let app; let passportCookie; @@ -23,8 +30,8 @@ describe('Teams API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/teams/teams.invalid.json b/app/tests/api/teams/teams.invalid.json deleted file mode 100644 index 330ffc3f..00000000 --- a/app/tests/api/teams/teams.invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "description": "no name", - "userIDs": [] - } -] diff --git a/app/tests/api/teams/teams.spec.js b/app/tests/api/teams/teams.spec.js index d8c8359f..a56f9c05 100644 --- a/app/tests/api/teams/teams.spec.js +++ b/app/tests/api/teams/teams.spec.js @@ -51,8 +51,8 @@ describe('Teams API', function () { const user1 = new UserAccount(exampleUser); await user1.save(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/teams/teams.valid.json b/app/tests/api/teams/teams.valid.json deleted file mode 100644 index 8694fdeb..00000000 --- a/app/tests/api/teams/teams.valid.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "name": "team1", - "description": "exampleTeam", - "userIDs": [] - } -] diff --git a/app/tests/api/techniques/techniques-pagination.spec.js b/app/tests/api/techniques/techniques-pagination.spec.js index 4ca8934d..19fea222 100644 --- a/app/tests/api/techniques/techniques-pagination.spec.js +++ b/app/tests/api/techniques/techniques-pagination.spec.js @@ -16,12 +16,10 @@ const initialObjectData = { // Removed external_references - backend generates ATT&CK IDs and external references object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_data_sources: ['data-source-1', 'data-source-2'], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_platforms: ['Linux', 'macOS'], }, }; @@ -29,6 +27,9 @@ const options = { prefix: 'attack-pattern', baseUrl: '/api/techniques', label: 'Techniques', + // The seeded fixture is ADM-compliant; pin validation on so this suite does + // not inherit the flag from whichever spec ran before it. + validateWithAdm: true, }; const paginationTests = new PaginationTests(techniquesService, initialObjectData, options); paginationTests.executeTests(); diff --git a/app/tests/api/techniques/techniques.convert.spec.js b/app/tests/api/techniques/techniques.convert.spec.js index 8798ed42..f900b696 100644 --- a/app/tests/api/techniques/techniques.convert.spec.js +++ b/app/tests/api/techniques/techniques.convert.spec.js @@ -36,7 +36,7 @@ describe('Techniques Convert API', function () { await database.initializeConnection(); await databaseConfiguration.checkSystemConfiguration(); - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = false; app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.query.json b/app/tests/api/techniques/techniques.query.json deleted file mode 100644 index fb6d51f2..00000000 --- a/app/tests/api/techniques/techniques.query.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "workspace": { - "workflow": {} - }, - "stix": { - "spec_version": "2.1", - "type": "attack-pattern", - "description": "This is a technique.", - "external_references": [{ "source_name": "source-1", "external_id": "s1" }], - "object_marking_refs": ["marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168"], - "created_by_ref": "identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5", - "kill_chain_phases": [{ "kill_chain_name": "kill-chain-name-1", "phase_name": "phase-1" }], - "x_mitre_data_sources": ["data-source-1", "data-source-2"], - "x_mitre_detection": "detection text", - "x_mitre_is_subtechnique": false, - "x_mitre_impact_type": ["impact-1"], - "x_mitre_platforms": ["platform-1", "platform-2"] - } -} diff --git a/app/tests/api/techniques/techniques.query.spec.js b/app/tests/api/techniques/techniques.query.spec.js index 7e83f092..ad0a02d3 100644 --- a/app/tests/api/techniques/techniques.query.spec.js +++ b/app/tests/api/techniques/techniques.query.spec.js @@ -1,5 +1,3 @@ -const fs = require('fs').promises; - const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); @@ -16,15 +14,33 @@ const databaseConfiguration = require('../../../lib/database-configuration'); const techniquesService = require('../../../services/stix/techniques-service'); +// Base technique used to derive all of the seeded query fixtures. Each created +// technique deep-clones this and overrides only the fields a given test cares +// about (deprecated/revoked status, workflow state, domain, platform). +const baseTechnique = { + workspace: { + workflow: {}, + }, + stix: { + spec_version: '2.1', + type: 'attack-pattern', + description: 'This is a technique.', + external_references: [{ source_name: 'source-1', external_id: 's1' }], + object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], + created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], + x_mitre_detection: 'detection text', + x_mitre_is_subtechnique: false, + x_mitre_version: '1.0', + x_mitre_domains: ['enterprise-attack'], + x_mitre_platforms: ['Linux', 'macOS'], + }, +}; + function asyncWait(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } -async function readJson(path) { - const data = await fs.readFile(require.resolve(path)); - return JSON.parse(data); -} - async function configureAndLoadTechniques(baseTechnique) { // Helper: create a technique from config async function createTechnique(overrides) { @@ -49,12 +65,13 @@ async function configureAndLoadTechniques(baseTechnique) { // technique 1: x_mitre_deprecated,revoked undefined, state undefined const technique1 = await createTechnique({}); - // technique 2: x_mitre_deprecated = false, state = work-in-progress, mobile-attack domain + // technique 2: x_mitre_deprecated = false, state = work-in-progress, mobile-attack domain. + // Adds a unique platform ('Windows') so the platform-filter test can target it. await createTechnique({ stix: { x_mitre_deprecated: false, x_mitre_domains: ['mobile-attack'], - x_mitre_platforms: [...baseTechnique.stix.x_mitre_platforms, 'platform-3'], + x_mitre_platforms: [...baseTechnique.stix.x_mitre_platforms, 'Windows'], }, workspace: { workflow: { state: 'work-in-progress' } }, }); @@ -136,14 +153,13 @@ describe('Techniques Query API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the seeded fixtures below are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app app = await require('../../../index').initializeApp(); - const baseTechnique = await readJson('./techniques.query.json'); await configureAndLoadTechniques(baseTechnique); // Log into the app @@ -286,7 +302,7 @@ describe('Techniques Query API', function () { it('GET /api/techniques should return techniques containing the platform', async function () { const res = await request(app) - .get('/api/techniques?platform=platform-3') + .get('/api/techniques?platform=Windows') .set('Accept', 'application/json') .set('Cookie', `${passportCookie.name}=${passportCookie.value}`) .expect(200) diff --git a/app/tests/api/techniques/techniques.revoke.spec.js b/app/tests/api/techniques/techniques.revoke.spec.js index a691b266..54cb4859 100644 --- a/app/tests/api/techniques/techniques.revoke.spec.js +++ b/app/tests/api/techniques/techniques.revoke.spec.js @@ -24,9 +24,9 @@ const initialObjectData = { description: 'This technique will be revoked.', object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'execution' }], x_mitre_is_subtechnique: false, - x_mitre_platforms: ['platform-1'], + x_mitre_platforms: ['Linux'], }, }; @@ -38,7 +38,7 @@ describe('Techniques Revoke API', function () { await database.initializeConnection(); await databaseConfiguration.checkSystemConfiguration(); - config.validateRequests.withAttackDataModel = false; + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; app = await require('../../../index').initializeApp(); diff --git a/app/tests/api/techniques/techniques.spec.js b/app/tests/api/techniques/techniques.spec.js index a5505fea..7bf95fdc 100644 --- a/app/tests/api/techniques/techniques.spec.js +++ b/app/tests/api/techniques/techniques.spec.js @@ -24,16 +24,19 @@ const initialObjectData = { spec_version: '2.1', type: 'attack-pattern', description: 'This is a technique. Orange.', - // external_references: [ + // external_references and stix.id are populated by the REST API object_marking_refs: ['marking-definition--fa42a846-8d90-4e51-bc29-71d5b4802168'], created_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', - kill_chain_phases: [{ kill_chain_name: 'kill-chain-name-1', phase_name: 'phase-1' }], - x_mitre_modified_by_ref: 'identity--d6424da5-85a0-496e-ae17-494499271108', + // ADM requires a kill_chain_name from the ATT&CK enum. The `impact` tactic is + // required for x_mitre_impact_type to be permitted. + kill_chain_phases: [{ kill_chain_name: 'mitre-attack', phase_name: 'impact' }], + // ADM requires x_mitre_modified_by_ref to be the MITRE ATT&CK identity + x_mitre_modified_by_ref: 'identity--c78cb6e5-0c4b-4611-8297-d1b8b55e40b5', // x_mitre_data_sources: ['data-source-1', 'data-source-2'], // TODO field is deprecated x_mitre_detection: 'detection text', x_mitre_is_subtechnique: false, - x_mitre_impact_type: ['impact-1'], - x_mitre_platforms: ['platform-1', 'platform-2'], + x_mitre_impact_type: ['Availability'], + x_mitre_platforms: ['Linux'], x_mitre_network_requirements: true, }, }; @@ -50,8 +53,8 @@ describe('Techniques Basic API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; the request payloads in this spec are ADM-compliant + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/techniques/techniques.tactics.spec.js b/app/tests/api/techniques/techniques.tactics.spec.js index bb0e1d05..02442162 100644 --- a/app/tests/api/techniques/techniques.tactics.spec.js +++ b/app/tests/api/techniques/techniques.tactics.spec.js @@ -30,8 +30,12 @@ describe('Techniques with Tactics API', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation. NOTE: this suite seeds via the collection-bundle + // import path, which records per-object ADM issues (import-fidelity contract) + // rather than rejecting them — so the bundle below is imported even though it + // is not fully ADM-compliant. This suite exercises the technique<->tactic + // relationship endpoints, not request-level ADM enforcement. + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/user-accounts/user-accounts-invalid.spec.js b/app/tests/api/user-accounts/user-accounts-invalid.spec.js index ab30c81f..d7d8a155 100644 --- a/app/tests/api/user-accounts/user-accounts-invalid.spec.js +++ b/app/tests/api/user-accounts/user-accounts-invalid.spec.js @@ -7,10 +7,27 @@ logger.level = 'debug'; const database = require('../../../lib/database-in-memory'); const databaseConfiguration = require('../../../lib/database-configuration'); -const userAccounts = require('./user-accounts.invalid.json'); - const login = require('../../shared/login'); +// Invalid user account payloads — each violates a required-field, enum, type, or +// business rule. Used to assert the API rejects malformed input with a 400. +const userAccounts = [ + { email: 'user1@test.com', username: 'user missing status and role' }, + { email: 'user1@test.com', displayName: 'user missing username', status: 'pending' }, + { email: 'user2@test.com', username: 'user invalid status', status: 'abcde', role: 'editor' }, + { email: 'user3@test.com', username: 'user invalid role', status: 'active', role: 'xyzzy' }, + { + email: 'user4@test.com', + username: 'user inactive cannot have role', + status: 'inactive', + role: 'admin', + }, + { email: 5, username: 'user has number for email', status: 'active', role: 'editor' }, + { email: 'user6@test.com', username: 6, status: 'active', role: 'editor' }, + { email: 'user7@test.com', username: 'user has number for status', status: 7, role: 'editor' }, + { email: 'user8@test.com', username: 'user has number for role', status: 'active', role: 8 }, +]; + describe('User Accounts API Test Invalid Data', function () { let app; let passportCookie; @@ -23,8 +40,8 @@ describe('User Accounts API Test Invalid Data', function () { // Check for a valid database configuration await databaseConfiguration.checkSystemConfiguration(); - // Disable ADM validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/user-accounts/user-accounts.invalid.json b/app/tests/api/user-accounts/user-accounts.invalid.json deleted file mode 100644 index 45ac12dd..00000000 --- a/app/tests/api/user-accounts/user-accounts.invalid.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "email": "user1@test.com", - "username": "user missing status and role" - }, - { - "email": "user1@test.com", - "displayName": "user missing username", - "status": "pending" - }, - { - "email": "user2@test.com", - "username": "user invalid status", - "status": "abcde", - "role": "editor" - }, - { - "email": "user3@test.com", - "username": "user invalid role", - "status": "active", - "role": "xyzzy" - }, - { - "email": "user4@test.com", - "username": "user inactive cannot have role", - "status": "inactive", - "role": "admin" - }, - { - "email": 5, - "username": "user has number for email", - "status": "active", - "role": "editor" - }, - { - "email": "user6@test.com", - "username": 6, - "status": "active", - "role": "editor" - }, - { - "email": "user7@test.com", - "username": "user has number for status", - "status": 7, - "role": "editor" - }, - { - "email": "user8@test.com", - "username": "user has number for role", - "status": "active", - "role": 8 - } -] diff --git a/app/tests/api/user-accounts/user-accounts.spec.js b/app/tests/api/user-accounts/user-accounts.spec.js index 27e7c059..8b254e23 100644 --- a/app/tests/api/user-accounts/user-accounts.spec.js +++ b/app/tests/api/user-accounts/user-accounts.spec.js @@ -38,8 +38,8 @@ describe('User Accounts API', function () { await UserAccount.init(); await Team.init(); - // Disable validation for tests - config.validateRequests.withAttackDataModel = false; + // Enable ADM validation; this non-STIX payload spec should not inherit a disabled flag + config.validateRequests.withAttackDataModel = true; config.validateRequests.withOpenApi = true; // Initialize the express app diff --git a/app/tests/api/user-accounts/user-accounts.valid.json b/app/tests/api/user-accounts/user-accounts.valid.json deleted file mode 100644 index 9ac791a6..00000000 --- a/app/tests/api/user-accounts/user-accounts.valid.json +++ /dev/null @@ -1,33 +0,0 @@ -[ - { - "email": "user1@test.com", - "username": "user1@test.com", - "displayName": "User 1", - "status": "active", - "role": "visitor" - }, - { - "email": "user2@test.com", - "username": "user2@test.com", - "displayName": "User 2", - "status": "active", - "role": "editor" - }, - { - "email": "user3@test.com", - "username": "user3@test.com", - "displayName": "User 3", - "status": "active", - "role": "admin" - }, - { - "email": "user4@test.com", - "username": "user4", - "status": "inactive" - }, - { - "email": "user5@test.com", - "username": "user5", - "status": "pending" - } -] diff --git a/app/tests/shared/pagination.js b/app/tests/shared/pagination.js index 8832d633..be3b28de 100644 --- a/app/tests/shared/pagination.js +++ b/app/tests/shared/pagination.js @@ -2,6 +2,7 @@ const request = require('supertest'); const { expect } = require('expect'); const _ = require('lodash'); +const config = require('../../config/config'); const logger = require('../../lib/logger'); logger.level = 'debug'; @@ -19,6 +20,9 @@ function PaginationTests(service, initialObjectAData, options) { prefix: options.prefix ?? 'test-object', baseUrl: options.baseUrl, label: options.label ?? 'TestObjects', + // Optional: pin ADM request validation for this suite. Left undefined, the + // suite inherits whatever the shared config singleton currently holds. + validateWithAdm: options.validateWithAdm, }; this.options.stateQuery = options.state ? `&state=${options.state}` : ''; @@ -51,6 +55,13 @@ PaginationTests.prototype.executeTests = function () { let passportCookie; before(async function () { + // Pin ADM validation state (when the caller specified one) so the suite + // does not depend on whichever spec ran last leaving the shared config + // singleton in a particular state. + if (self.options.validateWithAdm !== undefined) { + config.validateRequests.withAttackDataModel = self.options.validateWithAdm; + } + // Establish the database connection // Use an in-memory database that we spin up for the test await database.initializeConnection();