From 0cce60ddfa467513b59c38a61865af8241af4b78 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 28 Sep 2023 13:53:05 -0400 Subject: [PATCH 01/19] starting to refactor tactics service --- app/repository/tactics-repository.js | 13 +++ app/services/tactics-service.js | 163 +-------------------------- 2 files changed, 19 insertions(+), 157 deletions(-) create mode 100644 app/repository/tactics-repository.js diff --git a/app/repository/tactics-repository.js b/app/repository/tactics-repository.js new file mode 100644 index 00000000..4a1c6683 --- /dev/null +++ b/app/repository/tactics-repository.js @@ -0,0 +1,13 @@ +'use strict'; + +const BaseRepository = require('./_base.repository'); +const Tactic = require('../models/tactic-model'); + +class TacticsRepository extends BaseRepository { + + constructor() { + super(Tactic); + } +} + +module.exports = new TacticsRepository(); diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 1df10755..1a9899ea 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -20,166 +20,15 @@ const errors = { }; exports.errors = errors; -exports.retrieveAll = function(options, callback) { - // Build the query - const query = {}; - if (!options.includeRevoked) { - query['stix.revoked'] = { $in: [null, false] }; - } - if (!options.includeDeprecated) { - query['stix.x_mitre_deprecated'] = { $in: [null, false] }; - } - if (typeof options.state !== 'undefined') { - if (Array.isArray(options.state)) { - query['workspace.workflow.state'] = { $in: options.state }; - } - else { - query['workspace.workflow.state'] = options.state; - } - } - if (typeof options.domain !== 'undefined') { - if (Array.isArray(options.domain)) { - query['stix.x_mitre_domains'] = { $in: options.domain }; - } - else { - query['stix.x_mitre_domains'] = options.domain; - } - } - if (typeof options.lastUpdatedBy !== 'undefined') { - query['workspace.workflow.created_by_user_account'] = lastUpdatedByQueryHelper(options.lastUpdatedBy); - } +const BaseService = require('./_base.service'); +const TacticsRepository = require('../repository/tactics-repository'); - // Build the aggregation - // - Group the documents by stix.id, sorted by stix.modified - // - Use the last document in each group (according to the value of stix.modified) - // - Then apply query, skip and limit options - const aggregation = [ - { $sort: { 'stix.id': 1, 'stix.modified': 1 } }, - { $group: { _id: '$stix.id', document: { $last: '$$ROOT' }}}, - { $replaceRoot: { newRoot: '$document' }}, - { $sort: { 'stix.id': 1 }}, - { $match: query } - ]; - - if (typeof options.search !== 'undefined') { - options.search = regexValidator.sanitizeRegex(options.search); - const match = { $match: { $or: [ - { 'stix.name': { '$regex': options.search, '$options': 'i' }}, - { 'stix.description': { '$regex': options.search, '$options': 'i' }}, - { 'workspace.attack_id': { '$regex': options.search, '$options': 'i' }} - ]}}; - aggregation.push(match); - } - - const facet = { - $facet: { - totalCount: [ { $count: 'totalCount' }], - documents: [ ] - } - }; - if (options.offset) { - facet.$facet.documents.push({ $skip: options.offset }); - } - else { - facet.$facet.documents.push({ $skip: 0 }); - } - if (options.limit) { - facet.$facet.documents.push({ $limit: options.limit }); - } - aggregation.push(facet); - - // Retrieve the documents - Tactic.aggregate(aggregation, function(err, results) { - if (err) { - return callback(err); - } - else { - identitiesService.addCreatedByAndModifiedByIdentitiesToAll(results[0].documents) - .then(function() { - if (options.includePagination) { - let derivedTotalCount = 0; - if (results[0].totalCount.length > 0) { - derivedTotalCount = results[0].totalCount[0].totalCount; - } - const returnValue = { - pagination: { - total: derivedTotalCount, - offset: options.offset, - limit: options.limit - }, - data: results[0].documents - }; - return callback(null, returnValue); - } - else { - return callback(null, results[0].documents); - } - }); - } - }); -}; - -exports.retrieveById = function(stixId, options, callback) { - // versions=all Retrieve all tactics with the stixId - // versions=latest Retrieve the tactics with the latest modified date for this stixId - - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); +class TacticsService extends BaseService { + constructor () { + super(TacticsRepository, Tactic); } +} - if (options.versions === 'all') { - Tactic.find({'stix.id': stixId}) - .lean() - .exec(function (err, tactics) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } else { - return callback(err); - } - } else { - identitiesService.addCreatedByAndModifiedByIdentitiesToAll(tactics) - .then(() => callback(null, tactics)); - } - }); - } - else if (options.versions === 'latest') { - Tactic.findOne({ 'stix.id': stixId }) - .sort('-stix.modified') - .lean() - .exec(function(err, tactic) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } - } - else { - // Note: document is null if not found - if (tactic) { - identitiesService.addCreatedByAndModifiedByIdentities(tactic) - .then(() => callback(null, [ tactic ])); - } - else { - return callback(null, []); - } - } - }); - } - else { - const error = new Error(errors.invalidQueryStringParameter); - error.parameterName = 'versions'; - return callback(error); - } -}; exports.retrieveVersionById = function(stixId, modified, callback) { // Retrieve the versions of the tactic with the matching stixId and modified date From 3e2b6becbe2d02416d6c5e78f09676c612b98dce Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 28 Sep 2023 13:58:42 -0400 Subject: [PATCH 02/19] removed all redundant functions from tactics service --- app/services/tactics-service.js | 196 -------------------------------- 1 file changed, 196 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 1a9899ea..534de449 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -29,202 +29,6 @@ class TacticsService extends BaseService { } } - -exports.retrieveVersionById = function(stixId, modified, callback) { - // Retrieve the versions of the tactic with the matching stixId and modified date - - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!modified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); - } - - Tactic.findOne({ 'stix.id': stixId, 'stix.modified': modified }, function(err, tactic) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } - } - else { - // Note: document is null if not found - if (tactic) { - identitiesService.addCreatedByAndModifiedByIdentities(tactic) - .then(() => callback(null, tactic)); - } - else { - return callback(); - } - } - }); -}; - -exports.createIsAsync = true; -exports.create = async function(data, options) { - // This function handles two use cases: - // 1. This is a completely new object. Create a new object and generate the stix.id if not already - // provided. Set both stix.created_by_ref and stix.x_mitre_modified_by_ref to the organization identity. - // 2. This is a new version of an existing object. Create a new object with the specified id. - // Set stix.x_mitre_modified_by_ref to the organization identity. - - // Create the document - const tactic = new Tactic(data); - - options = options || {}; - if (!options.import) { - // Set the ATT&CK Spec Version - tactic.stix.x_mitre_attack_spec_version = tactic.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - - // Record the user account that created the object - if (options.userAccountId) { - tactic.workspace.workflow.created_by_user_account = options.userAccountId; - } - - // Set the default marking definitions - await attackObjectsService.setDefaultMarkingDefinitions(tactic); - - // Get the organization identity - const organizationIdentityRef = await systemConfigurationService.retrieveOrganizationIdentityRef(); - - // Check for an existing object - let existingObject; - if (tactic.stix.id) { - existingObject = await Tactic.findOne({ 'stix.id': tactic.stix.id }); - } - - if (existingObject) { - // New version of an existing object - // Only set the x_mitre_modified_by_ref property - tactic.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } - else { - // New object - // Assign a new STIX id if not already provided - tactic.stix.id = tactic.stix.id || `x-mitre-tactic--${uuid.v4()}`; - - // Set the created_by_ref and x_mitre_modified_by_ref properties - tactic.stix.created_by_ref = organizationIdentityRef; - tactic.stix.x_mitre_modified_by_ref = organizationIdentityRef; - } - } - - // Save the document in the database - try { - const savedTactic = await tactic.save(); - return savedTactic; - } - catch(err) { - if (err.name === 'MongoServerError' && err.code === 11000) { - // 11000 = Duplicate index - const error = new Error(errors.duplicateId); - throw error; - } - else { - throw err; - } - } -}; - -exports.updateFull = function(stixId, stixModified, data, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!stixModified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); - } - - Tactic.findOne({ 'stix.id': stixId, 'stix.modified': stixModified }, function(err, document) { - if (err) { - if (err.name === 'CastError') { - var error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } - } - else if (!document) { - // document not found - return callback(null); - } - else { - // Copy data to found document and save - Object.assign(document, data); - document.save(function(err, savedDocument) { - if (err) { - if (err.name === 'MongoServerError' && err.code === 11000) { - // 11000 = Duplicate index - var error = new Error(errors.duplicateId); - return callback(error); - } - else { - return callback(err); - } - } - else { - return callback(null, savedDocument); - } - }); - } - }); -}; - -exports.deleteVersionById = function (stixId, stixModified, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (!stixModified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - return callback(error); - } - - Tactic.findOneAndRemove({ 'stix.id': stixId, 'stix.modified': stixModified }, function (err, tactic) { - if (err) { - return callback(err); - } else { - //Note: tactic is null if not found - return callback(null, tactic); - } - }); -}; - -exports.deleteById = function (stixId, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - Tactic.deleteMany({ 'stix.id': stixId }, function (err, tactic) { - if (err) { - return callback(err); - } else { - //Note: tactic is null if not found - return callback(null, tactic); - } - }); -}; - function techniqueMatchesTactic(tactic) { return function(technique) { // A tactic matches if the technique has a kill chain phase such that: From 4c1b365ca9ed48d89101b077b483123e64052cca Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 28 Sep 2023 14:05:00 -0400 Subject: [PATCH 03/19] moved methods inside class definition --- app/services/tactics-service.js | 133 ++++++++++++++++---------------- 1 file changed, 68 insertions(+), 65 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 534de449..b4879c55 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -27,84 +27,87 @@ class TacticsService extends BaseService { constructor () { super(TacticsRepository, Tactic); } -} -function techniqueMatchesTactic(tactic) { - return function(technique) { - // A tactic matches if the technique has a kill chain phase such that: - // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) - // 2. The phase's phase_name matches the tactic's x_mitre_shortname + async techniqueMatchesTactic(tactic) { + return function(technique) { + // A tactic matches if the technique has a kill chain phase such that: + // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) + // 2. The phase's phase_name matches the tactic's x_mitre_shortname - // Convert the tactic's domain names to kill chain names - const tacticKillChainNames = tactic.stix.x_mitre_domains.map(domain => config.domainToKillChainMap[domain]); - return technique.stix.kill_chain_phases.some(phase => phase.phase_name === tactic.stix.x_mitre_shortname && tacticKillChainNames.includes(phase.kill_chain_name)); + // Convert the tactic's domain names to kill chain names + const tacticKillChainNames = tactic.stix.x_mitre_domains.map(domain => config.domainToKillChainMap[domain]); + return technique.stix.kill_chain_phases.some(phase => phase.phase_name === tactic.stix.x_mitre_shortname && tacticKillChainNames.includes(phase.kill_chain_name)); + } } -} - -function getPageOfData(data, options) { - const startPos = options.offset; - const endPos = (options.limit === 0) ? data.length : Math.min(options.offset + options.limit, data.length); - return data.slice(startPos, endPos); -} - -let retrieveAllTechniques; -exports.retrieveTechniquesForTactic = async function(stixId, modified, options) { - // Late binding to avoid circular dependency between modules - if (!retrieveAllTechniques) { - const techniquesService = require('./techniques-service'); - retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); - } + async getPageOfData(data, options) { + const startPos = options.offset; + const endPos = (options.limit === 0) ? data.length : Math.min(options.offset + options.limit, data.length); - // Retrieve the techniques associated with the tactic (the tactic identified by stixId and modified date) - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - throw error; + return data.slice(startPos, endPos); } - if (!modified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - throw error; - } + let retrieveAllTechniques; + async retrieveTechniquesForTactic (stixId, modified, options) { + // Late binding to avoid circular dependency between modules + if (!retrieveAllTechniques) { + const techniquesService = require('./techniques-service'); + retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); + } - try { - const tactic = await Tactic.findOne({ 'stix.id': stixId, 'stix.modified': modified }); + // Retrieve the techniques associated with the tactic (the tactic identified by stixId and modified date) + if (!stixId) { + const error = new Error(errors.missingParameter); + error.parameterName = 'stixId'; + throw error; + } - // Note: document is null if not found - if (!tactic) { - return null; + if (!modified) { + const error = new Error(errors.missingParameter); + error.parameterName = 'modified'; + throw error; } - else { - const allTechniques = await retrieveAllTechniques({}); - const filteredTechniques = allTechniques.filter(techniqueMatchesTactic(tactic)); - const pagedResults = getPageOfData(filteredTechniques, options); - - if (options.includePagination) { - const returnValue = { - pagination: { - total: pagedResults.length, - offset: options.offset, - limit: options.limit - }, - data: pagedResults - }; - return returnValue; + + try { + const tactic = await Tactic.findOne({ 'stix.id': stixId, 'stix.modified': modified }); + + // Note: document is null if not found + if (!tactic) { + return null; } else { - return pagedResults; + const allTechniques = await retrieveAllTechniques({}); + const filteredTechniques = allTechniques.filter(techniqueMatchesTactic(tactic)); + const pagedResults = getPageOfData(filteredTechniques, options); + + if (options.includePagination) { + const returnValue = { + pagination: { + total: pagedResults.length, + offset: options.offset, + limit: options.limit + }, + data: pagedResults + }; + return returnValue; + } + else { + return pagedResults; + } } } - } - catch(err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - throw error; - } - else { - throw err; + catch(err) { + if (err.name === 'CastError') { + const error = new Error(errors.badlyFormattedParameter); + error.parameterName = 'stixId'; + throw error; + } + else { + throw err; + } } } -}; + +} + +module.exports = new TacticsService(); \ No newline at end of file From 108d7f50bb24bc65bdbb26b0f240827914c6a538 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 28 Sep 2023 14:06:02 -0400 Subject: [PATCH 04/19] redefined functions as async --- app/services/tactics-service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index b4879c55..b3740b16 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -26,6 +26,7 @@ const TacticsRepository = require('../repository/tactics-repository'); class TacticsService extends BaseService { constructor () { super(TacticsRepository, Tactic); + this.retrieveAllTechniques = null; } async techniqueMatchesTactic(tactic) { @@ -47,10 +48,9 @@ class TacticsService extends BaseService { return data.slice(startPos, endPos); } - let retrieveAllTechniques; async retrieveTechniquesForTactic (stixId, modified, options) { // Late binding to avoid circular dependency between modules - if (!retrieveAllTechniques) { + if (!this.retrieveAllTechniques) { const techniquesService = require('./techniques-service'); retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); } From 447d7284df224b7bbe71701fd2041ae41f3ae210 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 28 Sep 2023 16:29:13 -0400 Subject: [PATCH 05/19] refactoring tests for tactics service --- app/tests/api/tactics/tactics.spec.js | 501 ++++++++++---------------- 1 file changed, 181 insertions(+), 320 deletions(-) diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index a20f3f89..f8d77bbe 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -50,204 +50,146 @@ describe('Tactics API', function () { passportCookie = await login.loginAnonymous(app); }); - it('GET /api/tactics returns an empty array of tactics', function (done) { - request(app) + it('GET /api/tactics returns an empty array of tactics', async function () { + const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get an empty array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(0); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get an empty array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(0); }); - it('POST /api/tactics does not create an empty tactic', function (done) { + it('POST /api/tactics does not create an empty tactic', async function () { const body = { }; - request(app) + const res = await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(400) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(400); }); let tactic1; - it('POST /api/tactics creates a tactic', function (done) { + it('POST /api/tactics creates a tactic', async function () { const timestamp = new Date().toISOString(); initialObjectData.stix.created = timestamp; initialObjectData.stix.modified = timestamp; const body = initialObjectData; - request(app) + const res = await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created tactic - tactic1 = res.body; - expect(tactic1).toBeDefined(); - expect(tactic1.stix).toBeDefined(); - expect(tactic1.stix.id).toBeDefined(); - expect(tactic1.stix.created).toBeDefined(); - expect(tactic1.stix.modified).toBeDefined(); - expect(tactic1.stix.created_by_ref).toBeDefined(); - expect(tactic1.stix.x_mitre_modified_by_ref).toBeDefined(); - expect(tactic1.stix.created_by_ref).toBe(tactic1.stix.x_mitre_modified_by_ref); - expect(tactic1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created tactic + tactic1 = res.body; + expect(tactic1).toBeDefined(); + expect(tactic1.stix).toBeDefined(); + expect(tactic1.stix.id).toBeDefined(); + expect(tactic1.stix.created).toBeDefined(); + expect(tactic1.stix.modified).toBeDefined(); + expect(tactic1.stix.created_by_ref).toBeDefined(); + expect(tactic1.stix.x_mitre_modified_by_ref).toBeDefined(); + expect(tactic1.stix.created_by_ref).toBe(tactic1.stix.x_mitre_modified_by_ref); + expect(tactic1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); }); - it('GET /api/tactics returns the added tactic', function (done) { - request(app) + it('GET /api/tactics returns the added tactic', async function () { + const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(1); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(1); }); - it('GET /api/tactics/:id should not return a tactic when the id cannot be found', function (done) { - request(app) + it('GET /api/tactics/:id should not return a tactic when the id cannot be found', async function () { + const res = await request(app) .get('/api/tactics/not-an-id') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(404) - .end(function (err, res) { - if (err) { - done(err); - } else { - done(); - } - }); + .expect(404); }); - it('GET /api/tactics/:id returns the added tactic', function (done) { - request(app) + it('GET /api/tactics/:id returns the added tactic', async function () { + const res = await request(app) .get('/api/tactics/' + tactic1.stix.id) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(1); - - const tactic = tactics[0]; - expect(tactic).toBeDefined(); - expect(tactic.stix).toBeDefined(); - expect(tactic.stix.id).toBe(tactic1.stix.id); - expect(tactic.stix.type).toBe(tactic1.stix.type); - expect(tactic.stix.name).toBe(tactic1.stix.name); - expect(tactic.stix.description).toBe(tactic1.stix.description); - expect(tactic.stix.spec_version).toBe(tactic1.stix.spec_version); - expect(tactic.stix.object_marking_refs).toEqual(expect.arrayContaining(tactic1.stix.object_marking_refs)); - expect(tactic.stix.created_by_ref).toBe(tactic1.stix.created_by_ref); - expect(tactic.stix.x_mitre_modified_by_ref).toBe(tactic1.stix.x_mitre_modified_by_ref); - expect(tactic.stix.x_mitre_attack_spec_version).toBe(tactic1.stix.x_mitre_attack_spec_version); - - expect(tactic.stix.x_mitre_deprecated).not.toBeDefined(); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(1); + + const tactic = tactics[0]; + expect(tactic).toBeDefined(); + expect(tactic.stix).toBeDefined(); + expect(tactic.stix.id).toBe(tactic1.stix.id); + expect(tactic.stix.type).toBe(tactic1.stix.type); + expect(tactic.stix.name).toBe(tactic1.stix.name); + expect(tactic.stix.description).toBe(tactic1.stix.description); + expect(tactic.stix.spec_version).toBe(tactic1.stix.spec_version); + expect(tactic.stix.object_marking_refs).toEqual(expect.arrayContaining(tactic1.stix.object_marking_refs)); + expect(tactic.stix.created_by_ref).toBe(tactic1.stix.created_by_ref); + expect(tactic.stix.x_mitre_modified_by_ref).toBe(tactic1.stix.x_mitre_modified_by_ref); + expect(tactic.stix.x_mitre_attack_spec_version).toBe(tactic1.stix.x_mitre_attack_spec_version); + + expect(tactic.stix.x_mitre_deprecated).not.toBeDefined(); }); - it('PUT /api/tactics updates a tactic', function (done) { + it('PUT /api/tactics updates a tactic', async function () { const originalModified = tactic1.stix.modified; const timestamp = new Date().toISOString(); tactic1.stix.modified = timestamp; tactic1.stix.description = 'This is an updated tactic.' const body = tactic1; - request(app) + const res = await request(app) .put('/api/tactics/' + tactic1.stix.id + '/modified/' + originalModified) .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the updated tactic - const tactic = res.body; - expect(tactic).toBeDefined(); - expect(tactic.stix.id).toBe(tactic1.stix.id); - expect(tactic.stix.modified).toBe(tactic1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the updated tactic + const tactic = res.body; + expect(tactic).toBeDefined(); + expect(tactic.stix.id).toBe(tactic1.stix.id); + expect(tactic.stix.modified).toBe(tactic1.stix.modified); + }); - it('POST /api/tactics does not create a tactic with the same id and modified date', function (done) { + it('POST /api/tactics does not create a tactic with the same id and modified date', async function () { const body = tactic1; - request(app) + // We expect to get the created tactic + const res = await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(409) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(409); }); let tactic2; - it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', async function () { tactic2 = _.cloneDeep(tactic1); tactic2._id = undefined; tactic2.__t = undefined; @@ -256,28 +198,22 @@ describe('Tactics API', function () { tactic2.stix.description = 'Still a tactic. Red.' tactic2.stix.modified = timestamp; const body = tactic2; - request(app) + const res = await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created tactic - const tactic = res.body; - expect(tactic).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created tactic + const tactic = res.body; + expect(tactic).toBeDefined(); + }); let tactic3; - it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/tactics should create a new version of a tactic with a duplicate stix.id but different stix.modified date', async function () { tactic3 = _.cloneDeep(tactic1); tactic3._id = undefined; tactic3.__t = undefined; @@ -286,236 +222,161 @@ describe('Tactics API', function () { tactic3.stix.description = 'Still a tactic. Violet.' tactic3.stix.modified = timestamp; const body = tactic3; - request(app) + const res = await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(201) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get the created tactic - const tactic = res.body; - expect(tactic).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created tactic + const tactic = res.body; + expect(tactic).toBeDefined(); }); - it('GET /api/tactics returns the latest added tactic', function (done) { - request(app) + it('GET /api/tactics returns the latest added tactic', async function () { + const res = await request(app) .get('/api/tactics/' + tactic3.stix.id) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(1); - const tactic = tactics[0]; - expect(tactic.stix.id).toBe(tactic3.stix.id); - expect(tactic.stix.modified).toBe(tactic3.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(1); + const tactic = tactics[0]; + expect(tactic.stix.id).toBe(tactic3.stix.id); + expect(tactic.stix.modified).toBe(tactic3.stix.modified); + }); - it('GET /api/tactics returns all added tactics', function (done) { - request(app) + it('GET /api/tactics returns all added tactics', async function () { + const res = await request(app) .get('/api/tactics/' + tactic1.stix.id + '?versions=all') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get two tactics in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(3); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get two tactics in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(3); + }); - it('GET /api/tactics/:id/modified/:modified returns the first added tactic', function (done) { - request(app) + it('GET /api/tactics/:id/modified/:modified returns the first added tactic', async function () { + const res = await request(app) .get('/api/tactics/' + tactic1.stix.id + '/modified/' + tactic1.stix.modified) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactic = res.body; - expect(tactic).toBeDefined(); - expect(tactic.stix).toBeDefined(); - expect(tactic.stix.id).toBe(tactic1.stix.id); - expect(tactic.stix.modified).toBe(tactic1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactic = res.body; + expect(tactic).toBeDefined(); + expect(tactic.stix).toBeDefined(); + expect(tactic.stix.id).toBe(tactic1.stix.id); + expect(tactic.stix.modified).toBe(tactic1.stix.modified); + }); - it('GET /api/tactics/:id/modified/:modified returns the second added tactic', function (done) { - request(app) + it('GET /api/tactics/:id/modified/:modified returns the second added tactic', async function () { + const res = await request(app) .get('/api/tactics/' + tactic2.stix.id + '/modified/' + tactic2.stix.modified) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactic = res.body; - expect(tactic).toBeDefined(); - expect(tactic.stix).toBeDefined(); - expect(tactic.stix.id).toBe(tactic2.stix.id); - expect(tactic.stix.modified).toBe(tactic2.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactic = res.body; + expect(tactic).toBeDefined(); + expect(tactic.stix).toBeDefined(); + expect(tactic.stix.id).toBe(tactic2.stix.id); + expect(tactic.stix.modified).toBe(tactic2.stix.modified); }); - it('GET /api/tactics uses the search parameter to return the latest version of the tactic', function (done) { - request(app) + it('GET /api/tactics uses the search parameter to return the latest version of the tactic', async function () { + const res = await request(app) .get('/api/tactics?search=violet') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get one tactic in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(1); - - // We expect it to be the latest version of the tactic - const tactic = tactics[0]; - expect(tactic).toBeDefined(); - expect(tactic.stix).toBeDefined(); - expect(tactic.stix.id).toBe(tactic3.stix.id); - expect(tactic.stix.modified).toBe(tactic3.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one tactic in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(1); + + // We expect it to be the latest version of the tactic + const tactic = tactics[0]; + expect(tactic).toBeDefined(); + expect(tactic.stix).toBeDefined(); + expect(tactic.stix.id).toBe(tactic3.stix.id); + expect(tactic.stix.modified).toBe(tactic3.stix.modified); + }); - it('GET /api/tactics should not get the first version of the tactic when using the search parameter', function (done) { - request(app) + it('GET /api/tactics should not get the first version of the tactic when using the search parameter', async function () { + const res = await request(app) .get('/api/tactics?search=yellow') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get zero tactics in an array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(0); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get zero tactics in an array + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(0); + }); - it('DELETE /api/tactics/:id should not delete a tactic when the id cannot be found', function (done) { - request(app) + it('DELETE /api/tactics/:id should not delete a tactic when the id cannot be found', async function () { + const res = await request(app) .delete('/api/tactics/not-an-id') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(404) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(404); }); - it('DELETE /api/tactics/:id/modified/:modified deletes a tactic', function (done) { - request(app) + it('DELETE /api/tactics/:id/modified/:modified deletes a tactic', async function () { + const res = await request(app) .delete('/api/tactics/' + tactic1.stix.id + '/modified/' + tactic1.stix.modified) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(204) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(204); }); - it('DELETE /api/tactics/:id should delete all the tactics with the same stix id', function (done) { - request(app) + it('DELETE /api/tactics/:id should delete all the tactics with the same stix id', async function () { + const res = await request(app) .delete('/api/tactics/' + tactic2.stix.id) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(204) - .end(function(err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(204); }); - it('GET /api/tactics returns an empty array of tactics', function (done) { - request(app) + it('GET /api/tactics returns an empty array of tactics', async function () { + const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - // We expect to get an empty array - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(0); - done(); - } - }); + .expect('Content-Type', /json/); + + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(0); + }); after(async function() { From 03ae65ad7d2dc109e858fee3e0fe0ff393baa176 Mon Sep 17 00:00:00 2001 From: Sun Date: Fri, 29 Sep 2023 12:58:49 -0400 Subject: [PATCH 06/19] updated errors --- app/services/tactics-service.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index fda77c19..86cd7ac0 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -68,15 +68,11 @@ class TacticsService extends BaseService { // Retrieve the techniques associated with the tactic (the tactic identified by stixId and modified date) if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - throw error; + throw new MissingParameterError({ parameterName: 'stixId' }); } if (!modified) { - const error = new Error(errors.missingParameter); - error.parameterName = 'modified'; - throw error; + throw new MissingParameterError({ parameterName: 'modified' }); } try { @@ -109,9 +105,7 @@ class TacticsService extends BaseService { } catch(err) { if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - throw error; + throw new BadlyFormattedParameterError({ parameterName: 'stixId' }); } else { throw err; From 4cc5e9efc9ffcb4736d44429182ca4e6092ad0b0 Mon Sep 17 00:00:00 2001 From: Sun Date: Mon, 2 Oct 2023 14:39:57 -0400 Subject: [PATCH 07/19] making interior function async in tactics service function --- app/services/tactics-service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 86cd7ac0..5fd406bf 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -30,7 +30,7 @@ class TacticsService extends BaseService { } async techniqueMatchesTactic(tactic) { - return function(technique) { + return async function(technique) { // A tactic matches if the technique has a kill chain phase such that: // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) // 2. The phase's phase_name matches the tactic's x_mitre_shortname From 4309cd471dc2f6d0b38ecbf360aac1a8879050ce Mon Sep 17 00:00:00 2001 From: Sun Date: Tue, 3 Oct 2023 16:04:42 -0400 Subject: [PATCH 08/19] refactoring tactics controller --- app/controllers/tactics-controller.js | 101 +++++++++++++------------- 1 file changed, 52 insertions(+), 49 deletions(-) diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index 313f44fa..9f801af6 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -2,8 +2,9 @@ const tacticsService = require('../services/tactics-service'); const logger = require('../lib/logger'); +const { DuplicateIdError, BadlyFormattedParameterError, InvalidQueryStringParameterError } = require('../exceptions'); -exports.retrieveAll = function(req, res) { +exports.retrieveAll = async function(req, res) { const options = { offset: req.query.offset || 0, limit: req.query.limit || 0, @@ -15,13 +16,9 @@ exports.retrieveAll = function(req, res) { lastUpdatedBy: req.query.lastUpdatedBy, includePagination: req.query.includePagination } + try { + const results = await tacticsService.retrieveAll(options) - tacticsService.retrieveAll(options, function(err, results) { - if (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get tactics. Server error.'); - } - else { if (options.includePagination) { logger.debug(`Success: Retrieved ${ results.data.length } of ${ results.pagination.total } total tactic(s)`); } @@ -29,31 +26,21 @@ exports.retrieveAll = function(req, res) { logger.debug(`Success: Retrieved ${ results.length } tactic(s)`); } return res.status(200).send(results); + + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get tactics. Server error.'); } - }); }; -exports.retrieveById = function(req, res) { +exports.retrieveById = async function(req, res) { const options = { versions: req.query.versions || 'latest' } - tacticsService.retrieveById(req.params.stixId, options, function (err, tactics) { - if (err) { - if (err.message === tacticsService.errors.badlyFormattedParameter) { - logger.warn('Badly formatted stix id: ' + req.params.stixId); - return res.status(400).send('Stix id is badly formatted.'); - } - else if (err.message === tacticsService.errors.invalidQueryStringParameter) { - logger.warn('Invalid query string: versions=' + req.query.versions); - return res.status(400).send('Query string parameter versions is invalid.'); - } - else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get tactics. Server error.'); - } - } - else { + try { + const results = await tacticsService.retrieveById(req.params.stixId, options); + if (tactics.length === 0) { return res.status(404).send('Tactic not found.'); } @@ -61,31 +48,47 @@ exports.retrieveById = function(req, res) { logger.debug(`Success: Retrieved ${ tactics.length } tactic(s) with id ${ req.params.stixId }`); return res.status(200).send(tactics); } + + } catch (err) { + if (err.message === tacticsService.errors.badlyFormattedParameter) { + logger.warn('Badly formatted stix id: ' + req.params.stixId); + return res.status(400).send('Stix id is badly formatted.'); } - }); + else if (err.message === tacticsService.errors.invalidQueryStringParameter) { + logger.warn('Invalid query string: versions=' + req.query.versions); + return res.status(400).send('Query string parameter versions is invalid.'); + } + else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get tactics. Server error.'); + } + } }; -exports.retrieveVersionById = function(req, res) { - tacticsService.retrieveVersionById(req.params.stixId, req.params.modified, function (err, tactic) { - if (err) { - if (err.message === tacticsService.errors.badlyFormattedParameter) { - logger.warn('Badly formatted stix id: ' + req.params.stixId); - return res.status(400).send('Stix id is badly formatted.'); - } - else { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get tactic. Server error.'); - } - } else { - if (!tactic) { - return res.status(404).send('tactic not found.'); - } - else { - logger.debug(`Success: Retrieved tactic with id ${tactic.id}`); - return res.status(200).send(tactic); - } +exports.retrieveVersionById = async function(req, res) { + + try { + + const results = await tacticsService.retrieveVersionById(req.params.stixId, req.params.modified); + + if (!tactic) { + return res.status(404).send('tactic not found.'); } - }); + else { + logger.debug(`Success: Retrieved tactic with id ${tactic.id}`); + return res.status(200).send(tactic); + } + + } catch (err) { + if (err.message === tacticsService.errors.badlyFormattedParameter) { + logger.warn('Badly formatted stix id: ' + req.params.stixId); + return res.status(400).send('Stix id is badly formatted.'); + } + else { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get tactic. Server error.'); + } + } }; exports.create = async function(req, res) { @@ -115,7 +118,7 @@ exports.create = async function(req, res) { } }; -exports.updateFull = function(req, res) { +exports.updateFull = async function(req, res) { // Get the data from the request const tacticData = req.body; @@ -136,7 +139,7 @@ exports.updateFull = function(req, res) { }); }; -exports.deleteVersionById = function(req, res) { +exports.deleteVersionById = async function(req, res) { tacticsService.deleteVersionById(req.params.stixId, req.params.modified, function (err, tactic) { if (err) { logger.error('Delete tactic failed. ' + err); @@ -153,7 +156,7 @@ exports.deleteVersionById = function(req, res) { }); }; -exports.deleteById = function(req, res) { +exports.deleteById = async function(req, res) { tacticsService.deleteById(req.params.stixId, function (err, tactics) { if (err) { logger.error('Delete tactic failed. ' + err); From 74d157bbd8aa9668d7492144c9ca1f01c6a0cd71 Mon Sep 17 00:00:00 2001 From: Sun Date: Tue, 3 Oct 2023 16:30:50 -0400 Subject: [PATCH 09/19] finished refactoring tactics controller --- app/controllers/tactics-controller.js | 105 ++++++++++++++------------ 1 file changed, 55 insertions(+), 50 deletions(-) diff --git a/app/controllers/tactics-controller.js b/app/controllers/tactics-controller.js index 9f801af6..fbf694f1 100644 --- a/app/controllers/tactics-controller.js +++ b/app/controllers/tactics-controller.js @@ -39,7 +39,7 @@ exports.retrieveById = async function(req, res) { } try { - const results = await tacticsService.retrieveById(req.params.stixId, options); + const tactics = await tacticsService.retrieveById(req.params.stixId, options); if (tactics.length === 0) { return res.status(404).send('Tactic not found.'); @@ -50,11 +50,11 @@ exports.retrieveById = async function(req, res) { } } catch (err) { - if (err.message === tacticsService.errors.badlyFormattedParameter) { + if (err instanceof BadlyFormattedParameterError) { logger.warn('Badly formatted stix id: ' + req.params.stixId); return res.status(400).send('Stix id is badly formatted.'); } - else if (err.message === tacticsService.errors.invalidQueryStringParameter) { + else if (err instanceof InvalidQueryStringParameterError) { logger.warn('Invalid query string: versions=' + req.query.versions); return res.status(400).send('Query string parameter versions is invalid.'); } @@ -69,7 +69,7 @@ exports.retrieveVersionById = async function(req, res) { try { - const results = await tacticsService.retrieveVersionById(req.params.stixId, req.params.modified); + const tactic = await tacticsService.retrieveVersionById(req.params.stixId, req.params.modified); if (!tactic) { return res.status(404).send('tactic not found.'); @@ -78,9 +78,9 @@ exports.retrieveVersionById = async function(req, res) { logger.debug(`Success: Retrieved tactic with id ${tactic.id}`); return res.status(200).send(tactic); } - + } catch (err) { - if (err.message === tacticsService.errors.badlyFormattedParameter) { + if (err instanceof BadlyFormattedParameterError) { logger.warn('Badly formatted stix id: ' + req.params.stixId); return res.status(400).send('Stix id is badly formatted.'); } @@ -95,19 +95,20 @@ exports.create = async function(req, res) { // Get the data from the request const tacticData = req.body; + const options = { + import: false, + userAccountId: req.user?.userAccountId + }; + // Create the tactic try { - const options = { - import: false, - userAccountId: req.user?.userAccountId - }; const tactic = await tacticsService.create(tacticData, options); logger.debug("Success: Created tactic with id " + tactic.stix.id); return res.status(201).send(tactic); } catch(err) { - if (err.message === tacticsService.errors.duplicateId) { + if (err instanceof DuplicateIdError) { logger.warn("Duplicate stix.id and stix.modified"); return res.status(409).send('Unable to create tactic. Duplicate stix.id and stix.modified properties.'); } @@ -122,54 +123,58 @@ exports.updateFull = async function(req, res) { // Get the data from the request const tacticData = req.body; - // Create the tactic - tacticsService.updateFull(req.params.stixId, req.params.modified, tacticData, function(err, tactic) { - if (err) { - logger.error("Failed with error: " + err); - return res.status(500).send("Unable to update tactic. Server error."); - } - else { - if (!tactic) { - return res.status(404).send('tactic not found.'); - } else { - logger.debug("Success: Updated tactic with id " + tactic.stix.id); - return res.status(200).send(tactic); - } + try { + const tactic = await tacticsService.updateFull(req.params.stixId, req.params.modified, tacticData); + + if (!tactic) { + return res.status(404).send('tactic not found.'); + } else { + logger.debug("Success: Updated tactic with id " + tactic.stix.id); + return res.status(200).send(tactic); } - }); + } catch (err) { + logger.error("Failed with error: " + err); + return res.status(500).send("Unable to update tactic. Server error."); + } }; exports.deleteVersionById = async function(req, res) { - tacticsService.deleteVersionById(req.params.stixId, req.params.modified, function (err, tactic) { - if (err) { - logger.error('Delete tactic failed. ' + err); - return res.status(500).send('Unable to delete tactic. Server error.'); - } - else { - if (!tactic) { - return res.status(404).send('tactic not found.'); - } else { - logger.debug("Success: Deleted tactic with id " + tactic.stix.id); - return res.status(204).end(); - } + + try { + + const tactic = await tacticsService.deleteVersionById(req.params.stixId, req.params.modified); + + if (!tactic) { + return res.status(404).send('tactic not found.'); + } else { + logger.debug("Success: Deleted tactic with id " + tactic.stix.id); + return res.status(204).end(); } - }); + + } catch (err) { + logger.error('Delete tactic failed. ' + err); + return res.status(500).send('Unable to delete tactic. Server error.'); + } + }; exports.deleteById = async function(req, res) { - tacticsService.deleteById(req.params.stixId, function (err, tactics) { - if (err) { - logger.error('Delete tactic failed. ' + err); - return res.status(500).send('Unable to delete tactic. Server error.'); + + try { + + const tactics = await tacticsService.deleteById(req.params.stixId); + + if (tactics.deletedCount === 0) { + return res.status(404).send('Tactic not found.'); } else { - if (tactics.deletedCount === 0) { - return res.status(404).send('Tactic not found.'); - } else { - logger.debug(`Success: Deleted tactic with id ${req.params.stixId}`); - return res.status(204).end(); - } + logger.debug(`Success: Deleted tactic with id ${req.params.stixId}`); + return res.status(204).end(); } - }); + + } catch (err) { + logger.error('Delete tactic failed. ' + err); + return res.status(500).send('Unable to delete tactic. Server error.'); + } }; exports.retrieveTechniquesForTactic = async function(req, res) { @@ -190,7 +195,7 @@ exports.retrieveTechniquesForTactic = async function(req, res) { } } catch(err) { - if (err.message === tacticsService.errors.badlyFormattedParameter) { + if (err instanceof BadlyFormattedParameterError) { logger.warn('Badly formatted stix id: ' + req.params.stixId); return res.status(400).send('Stix id is badly formatted.'); } else { From 5b5d4ecf3756c2b2e95cbed3f320b216881142e0 Mon Sep 17 00:00:00 2001 From: Sun Date: Wed, 4 Oct 2023 17:03:59 -0400 Subject: [PATCH 10/19] imported needed errors --- app/repository/groups-repository.js | 14 ++++++++++++++ app/services/tactics-service.js | 9 +-------- 2 files changed, 15 insertions(+), 8 deletions(-) create mode 100644 app/repository/groups-repository.js diff --git a/app/repository/groups-repository.js b/app/repository/groups-repository.js new file mode 100644 index 00000000..616fa592 --- /dev/null +++ b/app/repository/groups-repository.js @@ -0,0 +1,14 @@ +'use strict'; + +const BaseRepository = require('./_base.repository'); +const Group = require('../models/group-model'); + +class GroupsRepository extends BaseRepository { + + constructor() { + super(Group); + + } +} + +module.exports = new GroupsRepository(); \ No newline at end of file diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 5fd406bf..d2176a35 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -11,14 +11,7 @@ const config = require('../config/config'); const regexValidator = require('../lib/regex'); const {lastUpdatedByQueryHelper} = require('../lib/request-parameter-helper'); -const errors = { - missingParameter: 'Missing required parameter', - badlyFormattedParameter: 'Badly formatted parameter', - duplicateId: 'Duplicate id', - notFound: 'Document not found', - invalidQueryStringParameter: 'Invalid query string parameter' -}; -exports.errors = errors; +const { BadlyFormattedParameterError, DuplicateIdError, NotFoundError, InvalidQueryStringParameterError, MissingParameterError } = require('../exceptions'); const BaseService = require('./_base.service'); const TacticsRepository = require('../repository/tactics-repository'); From 097969aa88ecfc91e5141c931175115b8811fc69 Mon Sep 17 00:00:00 2001 From: Sun Date: Wed, 11 Oct 2023 14:54:44 -0400 Subject: [PATCH 11/19] changed separate test file to be async --- .../api/tactics/tactics.techniques.spec.js | 113 ++++++------------ 1 file changed, 38 insertions(+), 75 deletions(-) diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index e345b084..3583e6c4 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -48,109 +48,72 @@ describe('Tactics with Techniques API', function () { let tactic1; let tactic2; - it('GET /api/tactics should return the preloaded tactics', function (done) { - request(app) + it('GET /api/tactics should return the preloaded tactics', async function () { + const res = await request(app) .get('/api/tactics') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - const tactics = res.body; - expect(tactics).toBeDefined(); - 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'); - - done(); - } - }); + .expect('Content-Type', /json/); + + const tactics = res.body; + expect(tactics).toBeDefined(); + 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'); }); - it('GET /api/techniques should return the preloaded techniques', function (done) { - request(app) + it('GET /api/techniques should return the preloaded techniques', async function () { + const res = await request(app) .get('/api/techniques') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - const techniques = res.body; - expect(techniques).toBeDefined(); - expect(Array.isArray(techniques)).toBe(true); - expect(techniques.length).toBe(5); - done(); - } - }); + .expect('Content-Type', /json/); + + const techniques = res.body; + expect(techniques).toBeDefined(); + expect(Array.isArray(techniques)).toBe(true); + expect(techniques.length).toBe(5); }); - it('GET /api/tactics/:id/modified/:modified/techniques should not return the techniques when the tactic cannot be found', function (done) { + it('GET /api/tactics/:id/modified/:modified/techniques should not return the techniques when the tactic cannot be found', async function () { request(app) .get(`/api/tactics/not-an-id/modified/2022-01-01T00:00:00.000Z/techniques`) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) - .expect(404) - .end(function (err, res) { - if (err) { - done(err); - } - else { - done(); - } - }); + .expect(404); }); - it('GET /api/tactics/:id/modified/:modified/techniques should return the techniques for tactic 1', function (done) { - request(app) + it('GET /api/tactics/:id/modified/:modified/techniques should return the techniques for tactic 1', async function () { + const res = await request(app) .get(`/api/tactics/${ tactic1.stix.id }/modified/${ tactic1.stix.modified }/techniques`) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - const tactics = res.body; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(2); - done(); - } - }); + .expect('Content-Type', /json/); + + const tactics = res.body; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(2); }); - it('GET /api/tactics/:id/modified/:modified/techniques should return the first page of techniques for tactic 2', function (done) { - request(app) + it('GET /api/tactics/:id/modified/:modified/techniques should return the first page of techniques for tactic 2', async function () { + const res = await request(app) .get(`/api/tactics/${ tactic2.stix.id }/modified/${ tactic2.stix.modified }/techniques?offset=0&limit=2&includePagination=true`) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(200) - .expect('Content-Type', /json/) - .end(function(err, res) { - if (err) { - done(err); - } - else { - const results = res.body; - const tactics = results.data; - expect(tactics).toBeDefined(); - expect(Array.isArray(tactics)).toBe(true); - expect(tactics.length).toBe(2); - done(); - } - }); + .expect('Content-Type', /json/); + + const results = res.body; + const tactics = results.data; + expect(tactics).toBeDefined(); + expect(Array.isArray(tactics)).toBe(true); + expect(tactics.length).toBe(2); }); after(async function() { From 5957c38c94320a63a02753d9dbfc21ac584ad1a4 Mon Sep 17 00:00:00 2001 From: Sun Date: Wed, 11 Oct 2023 18:49:26 -0400 Subject: [PATCH 12/19] fixing npm lint problems --- app/services/tactics-service.js | 6 +----- app/tests/api/tactics/tactics.spec.js | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index d2176a35..47c69523 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -1,14 +1,10 @@ 'use strict'; -const uuid = require('uuid'); + const util = require('util'); const Tactic = require('../models/tactic-model'); -const systemConfigurationService = require('./system-configuration-service'); -const identitiesService = require('./identities-service'); -const attackObjectsService = require('./attack-objects-service'); const config = require('../config/config'); -const regexValidator = require('../lib/regex'); const {lastUpdatedByQueryHelper} = require('../lib/request-parameter-helper'); const { BadlyFormattedParameterError, DuplicateIdError, NotFoundError, InvalidQueryStringParameterError, MissingParameterError } = require('../exceptions'); diff --git a/app/tests/api/tactics/tactics.spec.js b/app/tests/api/tactics/tactics.spec.js index f8d77bbe..1673e540 100644 --- a/app/tests/api/tactics/tactics.spec.js +++ b/app/tests/api/tactics/tactics.spec.js @@ -67,7 +67,7 @@ describe('Tactics API', function () { it('POST /api/tactics does not create an empty tactic', async function () { const body = { }; - const res = await request(app) + await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') @@ -118,7 +118,7 @@ describe('Tactics API', function () { }); it('GET /api/tactics/:id should not return a tactic when the id cannot be found', async function () { - const res = await request(app) + await request(app) .get('/api/tactics/not-an-id') .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) @@ -180,7 +180,7 @@ describe('Tactics API', function () { it('POST /api/tactics does not create a tactic with the same id and modified date', async function () { const body = tactic1; // We expect to get the created tactic - const res = await request(app) + await request(app) .post('/api/tactics') .send(body) .set('Accept', 'application/json') @@ -344,21 +344,21 @@ describe('Tactics API', function () { }); it('DELETE /api/tactics/:id should not delete a tactic when the id cannot be found', async function () { - const res = await request(app) + await request(app) .delete('/api/tactics/not-an-id') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(404); }); it('DELETE /api/tactics/:id/modified/:modified deletes a tactic', async function () { - const res = await request(app) + await request(app) .delete('/api/tactics/' + tactic1.stix.id + '/modified/' + tactic1.stix.modified) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(204); }); it('DELETE /api/tactics/:id should delete all the tactics with the same stix id', async function () { - const res = await request(app) + await request(app) .delete('/api/tactics/' + tactic2.stix.id) .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) .expect(204); From 851f5a03b1546fbb48ba63307350847e978bda0d Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 12 Oct 2023 09:56:19 -0400 Subject: [PATCH 13/19] fixing npm lint problems part 2 --- app/services/tactics-service.js | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 47c69523..f6983f1f 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -5,9 +5,8 @@ const util = require('util'); const Tactic = require('../models/tactic-model'); const config = require('../config/config'); -const {lastUpdatedByQueryHelper} = require('../lib/request-parameter-helper'); -const { BadlyFormattedParameterError, DuplicateIdError, NotFoundError, InvalidQueryStringParameterError, MissingParameterError } = require('../exceptions'); +const { BadlyFormattedParameterError, MissingParameterError } = require('../exceptions'); const BaseService = require('./_base.service'); const TacticsRepository = require('../repository/tactics-repository'); @@ -23,18 +22,6 @@ class TacticsService extends BaseService { // A tactic matches if the technique has a kill chain phase such that: // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) // 2. The phase's phase_name matches the tactic's x_mitre_shortname - // Build the aggregation - // - Group the documents by stix.id, sorted by stix.modified - // - Use the first document in each group (according to the value of stix.modified) - // - Then apply query, skip and limit options - const aggregation = [ - { $sort: { 'stix.id': 1, 'stix.modified': -1 } }, - { $group: { _id: '$stix.id', document: { $first: '$$ROOT' }}}, - { $replaceRoot: { newRoot: '$document' }}, - { $sort: { 'stix.id': 1 }}, - { $match: query } - ]; - // Convert the tactic's domain names to kill chain names const tacticKillChainNames = tactic.stix.x_mitre_domains.map(domain => config.domainToKillChainMap[domain]); return technique.stix.kill_chain_phases.some(phase => phase.phase_name === tactic.stix.x_mitre_shortname && tacticKillChainNames.includes(phase.kill_chain_name)); From bdbb85a0a5ca331fab45f56b464f5ff22848ae56 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 12 Oct 2023 15:09:23 -0400 Subject: [PATCH 14/19] fixing last linter problems --- app/services/tactics-service.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index f6983f1f..500d8f6b 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -39,7 +39,7 @@ class TacticsService extends BaseService { // Late binding to avoid circular dependency between modules if (!this.retrieveAllTechniques) { const techniquesService = require('./techniques-service'); - retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); + this.retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); } // Retrieve the techniques associated with the tactic (the tactic identified by stixId and modified date) @@ -59,9 +59,10 @@ class TacticsService extends BaseService { return null; } else { - const allTechniques = await retrieveAllTechniques({}); - const filteredTechniques = allTechniques.filter(techniqueMatchesTactic(tactic)); - const pagedResults = getPageOfData(filteredTechniques, options); + const allTechniques = await this.retrieveAllTechniques({}); + const result = await this.techniqueMatchesTactic(tactic) + const filteredTechniques = allTechniques.filter(result); + const pagedResults = this.getPageOfData(filteredTechniques, options); if (options.includePagination) { const returnValue = { From 25d1a503dc83ec55f4007c6945724ec169155c5c Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 12 Oct 2023 15:13:44 -0400 Subject: [PATCH 15/19] removed async from function because it was unnecessary --- app/services/tactics-service.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 500d8f6b..174eb124 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -17,8 +17,8 @@ class TacticsService extends BaseService { this.retrieveAllTechniques = null; } - async techniqueMatchesTactic(tactic) { - return async function(technique) { + techniqueMatchesTactic(tactic) { + return function(technique) { // A tactic matches if the technique has a kill chain phase such that: // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) // 2. The phase's phase_name matches the tactic's x_mitre_shortname @@ -60,8 +60,7 @@ class TacticsService extends BaseService { } else { const allTechniques = await this.retrieveAllTechniques({}); - const result = await this.techniqueMatchesTactic(tactic) - const filteredTechniques = allTechniques.filter(result); + const filteredTechniques = allTechniques.filter(this.techniqueMatchesTactic(tactic)); const pagedResults = this.getPageOfData(filteredTechniques, options); if (options.includePagination) { From 478ca8dee7760be9f94a703f118f073d26fdc9a9 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 12 Oct 2023 15:32:09 -0400 Subject: [PATCH 16/19] fixing lint --- app/services/tactics-service.js | 2 +- app/tests/api/tactics/tactics.techniques.spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 174eb124..7a3fe3c9 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -28,7 +28,7 @@ class TacticsService extends BaseService { } } - async getPageOfData(data, options) { + getPageOfData(data, options) { const startPos = options.offset; const endPos = (options.limit === 0) ? data.length : Math.min(options.offset + options.limit, data.length); diff --git a/app/tests/api/tactics/tactics.techniques.spec.js b/app/tests/api/tactics/tactics.techniques.spec.js index 3583e6c4..6cd4598d 100644 --- a/app/tests/api/tactics/tactics.techniques.spec.js +++ b/app/tests/api/tactics/tactics.techniques.spec.js @@ -80,7 +80,7 @@ describe('Tactics with Techniques API', function () { }); it('GET /api/tactics/:id/modified/:modified/techniques should not return the techniques when the tactic cannot be found', async function () { - request(app) + await request(app) .get(`/api/tactics/not-an-id/modified/2022-01-01T00:00:00.000Z/techniques`) .set('Accept', 'application/json') .set('Cookie', `${ login.passportCookieName }=${ passportCookie.value }`) From 02499d9ea2b0942b8d13f5fbfbd90fbd6ce05d41 Mon Sep 17 00:00:00 2001 From: Sun Date: Thu, 12 Oct 2023 15:56:59 -0400 Subject: [PATCH 17/19] made tactics service static --- app/services/tactics-service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 7a3fe3c9..0956bdf4 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -17,7 +17,7 @@ class TacticsService extends BaseService { this.retrieveAllTechniques = null; } - techniqueMatchesTactic(tactic) { + static techniqueMatchesTactic(tactic) { return function(technique) { // A tactic matches if the technique has a kill chain phase such that: // 1. The phase's kill_chain_name matches one of the tactic's kill chain names (which are derived from the tactic's x_mitre_domains) @@ -28,7 +28,7 @@ class TacticsService extends BaseService { } } - getPageOfData(data, options) { + static getPageOfData(data, options) { const startPos = options.offset; const endPos = (options.limit === 0) ? data.length : Math.min(options.offset + options.limit, data.length); From dc85630b569624018d81a9278e70f810faf4e466 Mon Sep 17 00:00:00 2001 From: Jack Sheriff <> Date: Wed, 8 Nov 2023 13:16:21 -0500 Subject: [PATCH 18/19] Modify tactics service to use new base service constructor design. Modify techniques service to work with async tactics service function. --- app/services/tactics-service.js | 20 ++++++++------------ app/services/techniques-service.js | 10 ++-------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/app/services/tactics-service.js b/app/services/tactics-service.js index 0956bdf4..6542f20e 100644 --- a/app/services/tactics-service.js +++ b/app/services/tactics-service.js @@ -3,7 +3,6 @@ const util = require('util'); -const Tactic = require('../models/tactic-model'); const config = require('../config/config'); const { BadlyFormattedParameterError, MissingParameterError } = require('../exceptions'); @@ -12,10 +11,7 @@ const BaseService = require('./_base.service'); const TacticsRepository = require('../repository/tactics-repository'); class TacticsService extends BaseService { - constructor () { - super(TacticsRepository, Tactic); - this.retrieveAllTechniques = null; - } + static retrieveAllTechniques = null; static techniqueMatchesTactic(tactic) { return function(technique) { @@ -37,9 +33,9 @@ class TacticsService extends BaseService { async retrieveTechniquesForTactic (stixId, modified, options) { // Late binding to avoid circular dependency between modules - if (!this.retrieveAllTechniques) { + if (!TacticsService.retrieveAllTechniques) { const techniquesService = require('./techniques-service'); - this.retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); + TacticsService.retrieveAllTechniques = util.promisify(techniquesService.retrieveAll); } // Retrieve the techniques associated with the tactic (the tactic identified by stixId and modified date) @@ -52,16 +48,16 @@ class TacticsService extends BaseService { } try { - const tactic = await Tactic.findOne({ 'stix.id': stixId, 'stix.modified': modified }); + const tactic = await this.repository.model.findOne({ 'stix.id': stixId, 'stix.modified': modified }); // Note: document is null if not found if (!tactic) { return null; } else { - const allTechniques = await this.retrieveAllTechniques({}); - const filteredTechniques = allTechniques.filter(this.techniqueMatchesTactic(tactic)); - const pagedResults = this.getPageOfData(filteredTechniques, options); + const allTechniques = await TacticsService.retrieveAllTechniques({}); + const filteredTechniques = allTechniques.filter(TacticsService.techniqueMatchesTactic(tactic)); + const pagedResults = TacticsService.getPageOfData(filteredTechniques, options); if (options.includePagination) { const returnValue = { @@ -91,4 +87,4 @@ class TacticsService extends BaseService { } -module.exports = new TacticsService(); \ No newline at end of file +module.exports = new TacticsService('x-mitre-tactic', TacticsRepository); diff --git a/app/services/techniques-service.js b/app/services/techniques-service.js index 40ffde28..013c51eb 100644 --- a/app/services/techniques-service.js +++ b/app/services/techniques-service.js @@ -7,6 +7,7 @@ const Technique = require('../models/technique-model'); const systemConfigurationService = require('./system-configuration-service'); const identitiesService = require('./identities-service'); const attackObjectsService = require('./attack-objects-service'); +const tacticsService = require('./tactics-service'); const config = require('../config/config'); const regexValidator = require('../lib/regex'); const {lastUpdatedByQueryHelper} = require('../lib/request-parameter-helper'); @@ -406,14 +407,7 @@ function getPageOfData(data, options) { return data.slice(startPos, endPos); } -let retrieveAllTactics; exports.retrieveTacticsForTechnique = async function(stixId, modified, options) { - // Late binding to avoid circular dependency between modules - if (!retrieveAllTactics) { - const tacticsService = require('./tactics-service'); - retrieveAllTactics = util.promisify(tacticsService.retrieveAll); - } - // Retrieve the tactics associated with the technique (the technique identified by stixId and modified date) if (!stixId) { const error = new Error(errors.missingParameter); @@ -434,7 +428,7 @@ exports.retrieveTacticsForTechnique = async function(stixId, modified, options) return null; } else { - const allTactics = await retrieveAllTactics({}); + const allTactics = await tacticsService.retrieveAll({}); const filteredTactics = allTactics.filter(tacticMatchesTechnique(technique)); const pagedResults = getPageOfData(filteredTactics, options); From 9769f0b0e757a0bdac6cdcce513e2cd897f63772 Mon Sep 17 00:00:00 2001 From: Jack Sheriff <> Date: Wed, 8 Nov 2023 13:18:11 -0500 Subject: [PATCH 19/19] Fix lint error. --- app/services/techniques-service.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/services/techniques-service.js b/app/services/techniques-service.js index 013c51eb..2c5a9358 100644 --- a/app/services/techniques-service.js +++ b/app/services/techniques-service.js @@ -1,7 +1,6 @@ 'use strict'; const uuid = require('uuid'); -const util = require('util'); const Technique = require('../models/technique-model'); const systemConfigurationService = require('./system-configuration-service');