diff --git a/app/controllers/identities-controller.js b/app/controllers/identities-controller.js index 1b099cc2..d87f6353 100644 --- a/app/controllers/identities-controller.js +++ b/app/controllers/identities-controller.js @@ -2,8 +2,9 @@ const identitiesService = require('../services/identities-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, @@ -13,76 +14,75 @@ exports.retrieveAll = function(req, res) { includePagination: req.query.includePagination } - identitiesService.retrieveAll(options, function(err, results) { - if (err) { - logger.error('Failed with error: ' + err); - return res.status(500).send('Unable to get identities. Server error.'); + try { + const results = await identitiesService.retrieveAll(options); + if (options.includePagination) { + logger.debug(`Success: Retrieved ${ results.data.length } of ${ results.pagination.total } total identities`); } else { - if (options.includePagination) { - logger.debug(`Success: Retrieved ${ results.data.length } of ${ results.pagination.total } total identities`); - } - else { - logger.debug(`Success: Retrieved ${ results.length } identities`); - } - return res.status(200).send(results); - } - }); + logger.debug(`Success: Retrieved ${ results.length } identities`); + } + return res.status(200).send(results); + } catch (err) { + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get identities. Server error.'); + } + }; -exports.retrieveById = function(req, res) { +exports.retrieveById = async function(req, res) { const options = { versions: req.query.versions || 'latest' } - identitiesService.retrieveById(req.params.stixId, options, function (err, identities) { - if (err) { - if (err.message === identitiesService.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 === identitiesService.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 identities. Server error.'); - } + try { + const identities = await identitiesService.retrieveById(req.params.stixId, options); + if (identities.length === 0) { + return res.status(404).send('Identity not found.'); + } + else { + logger.debug(`Success: Retrieved ${ identities.length } identities with id ${ req.params.stixId }`); + return res.status(200).send(identities); + } + } catch (err) { + 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 instanceof InvalidQueryStringParameterError) { + logger.warn('Invalid query string: versions=' + req.query.versions); + return res.status(400).send('Query string parameter versions is invalid.'); } else { - if (identities.length === 0) { - return res.status(404).send('Identity not found.'); - } - else { - logger.debug(`Success: Retrieved ${ identities.length } identities with id ${ req.params.stixId }`); - return res.status(200).send(identities); - } - } - }); + logger.error('Failed with error: ' + err); + return res.status(500).send('Unable to get identities. Server error.'); + } + } + }; -exports.retrieveVersionById = function(req, res) { - identitiesService.retrieveVersionById(req.params.stixId, req.params.modified, function (err, identity) { - if (err) { - if (err.message === identitiesService.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 identity. Server error.'); - } - } else { - if (!identity) { - return res.status(404).send('Identity not found.'); - } - else { - logger.debug(`Success: Retrieved identity with id ${identity.id}`); - return res.status(200).send(identity); - } - } - }); +exports.retrieveVersionById = async function(req, res) { + + try { + const identity = await identitiesService.retrieveVersionById(req.params.stixId, req.params.modified); + if (!identity) { + return res.status(404).send('Identity not found.'); + } + else { + logger.debug(`Success: Retrieved identity with id ${identity.id}`); + return res.status(200).send(identity); + } + } catch (err) { + if (err instanceof BadlyFormattedParameterError) { + 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 identity. Server error.'); + } + } + }; exports.create = async function(req, res) { @@ -100,7 +100,7 @@ exports.create = async function(req, res) { return res.status(201).send(identity); } catch(err) { - if (err.message === identitiesService.errors.duplicateId) { + if (err instanceof DuplicateIdError) { logger.warn("Duplicate stix.id and stix.modified"); return res.status(409).send('Unable to create identity. Duplicate stix.id and stix.modified properties.'); } @@ -111,58 +111,54 @@ exports.create = async function(req, res) { } }; -exports.updateFull = function(req, res) { +exports.updateFull = async function(req, res) { // Get the data from the request const identityData = req.body; // Create the identity - identitiesService.updateFull(req.params.stixId, req.params.modified, identityData, function(err, identity) { - if (err) { - logger.error("Failed with error: " + err); - return res.status(500).send("Unable to update identity. Server error."); + try { + const identity = await identitiesService.updateFull(req.params.stixId, req.params.modified, identityData); + if (!identity) { + return res.status(404).send('Identity not found.'); + } else { + logger.debug("Success: Updated identity with id " + identity.stix.id); + return res.status(200).send(identity); } - else { - if (!identity) { - return res.status(404).send('Identity not found.'); - } else { - logger.debug("Success: Updated identity with id " + identity.stix.id); - return res.status(200).send(identity); - } - } - }); + } catch (err) { + logger.error("Failed with error: " + err); + return res.status(500).send("Unable to update identity. Server error."); + } }; -exports.deleteVersionById = function(req, res) { - identitiesService.deleteVersionById(req.params.stixId, req.params.modified, function (err, identity) { - if (err) { - logger.error('Delete identity failed. ' + err); - return res.status(500).send('Unable to delete identity. Server error.'); +exports.deleteVersionById = async function(req, res) { + + try { + const identity = await identitiesService.deleteVersionById(req.params.stixId, req.params.modified); + if (!identity) { + return res.status(404).send('Identity not found.'); + } else { + logger.debug("Success: Deleted identity with id " + identity.stix.id); + return res.status(204).end(); } - else { - if (!identity) { - return res.status(404).send('Identity not found.'); - } else { - logger.debug("Success: Deleted identity with id " + identity.stix.id); - return res.status(204).end(); - } - } - }); + } catch (err) { + logger.error('Delete identity failed. ' + err); + return res.status(500).send('Unable to delete identity. Server error.'); + } }; -exports.deleteById = function(req, res) { - identitiesService.deleteById(req.params.stixId, function (err, identities) { - if (err) { - logger.error('Delete identity failed. ' + err); - return res.status(500).send('Unable to identity identity. Server error.'); +exports.deleteById = async function(req, res) { + + try { + const identities = await identitiesService.deleteById(req.params.stixId); + if (identities.deletedCount === 0) { + return res.status(404).send('Identity not found.'); } else { - if (identities.deletedCount === 0) { - return res.status(404).send('Identity not found.'); - } - else { - logger.debug(`Success: Deleted identity with id ${ req.params.stixId }`); - return res.status(204).end(); - } - } - }); + logger.debug(`Success: Deleted identity with id ${ req.params.stixId }`); + return res.status(204).end(); + } + } catch (err) { + logger.error('Delete identity failed. ' + err); + return res.status(500).send('Unable to identity identity. Server error.'); + } }; diff --git a/app/repository/identities-repository.js b/app/repository/identities-repository.js new file mode 100644 index 00000000..5690e513 --- /dev/null +++ b/app/repository/identities-repository.js @@ -0,0 +1,8 @@ +'use strict'; + +const BaseRepository = require('./_base.repository'); +const Identity = require('../models/identity-model'); + +class IdentitiesRepository extends BaseRepository { } + +module.exports = new IdentitiesRepository(Identity); \ No newline at end of file diff --git a/app/services/_base.service.js b/app/services/_base.service.js index 0b3e1069..55481e8b 100644 --- a/app/services/_base.service.js +++ b/app/services/_base.service.js @@ -84,7 +84,6 @@ class BaseService extends AbstractService { return callback(identityError); } throw identityError; - } const paginatedResults = BaseService.paginate(options, results); @@ -92,7 +91,6 @@ class BaseService extends AbstractService { return callback(null, paginatedResults); } return paginatedResults; - } async retrieveById(stixId, options, callback) { @@ -200,7 +198,6 @@ class BaseService extends AbstractService { const document = await this.repository.retrieveOneByVersion(stixId, modified); if (!document) { - console.log('** NOT FOUND'); if (callback) { return callback(null, null); } diff --git a/app/services/identities-service.js b/app/services/identities-service.js index f3e27c3f..f54f09ca 100644 --- a/app/services/identities-service.js +++ b/app/services/identities-service.js @@ -1,10 +1,12 @@ 'use strict'; const uuid = require('uuid'); -const Identity = require('../models/identity-model'); const attackObjectsService = require('./attack-objects-service'); const config = require('../config/config'); const userAccountsService = require('./user-accounts-service'); +const identitiesRepository = require('../repository/identities-repository'); +const BaseService = require('./_base.service'); +const { InvalidTypeError } = require('../exceptions'); const errors = { missingParameter: 'Missing required parameter', @@ -15,421 +17,137 @@ 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; - } - } +const identityType = 'identity'; - // 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 } - ]; +class IdentitiesService extends BaseService { - const facet = { - $facet: { - totalCount: [ { $count: 'totalCount' }], - documents: [ ] + async addCreatedByAndModifiedByIdentitiesToAll(attackObjects) { + const identityCache = new Map(); + const userAccountCache = new Map(); + for (const attackObject of attackObjects) { + // eslint-disable-next-line no-await-in-loop + await this.addCreatedByAndModifiedByIdentities(attackObject, identityCache, userAccountCache); } - }; - 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); + + async create(data, options) { + // This function overrides the base class create() because + // 1. It does not set the created_by_ref or x_mitre_modified_by_ref properties + // 2. It does not check for an existing identity object - // Retrieve the documents - Identity.aggregate(aggregation, function(err, results) { - if (err) { - return callback(err); + if (data?.stix?.type !== identityType) { + throw new InvalidTypeError(); } - else { - 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 identities with the stixId - // versions=latest Retrieve the identity with the latest modified date for this stixId - - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); - } - - if (options.versions === 'all') { - Identity.find({'stix.id': stixId}) - .sort('-stix.modified') - .lean() - .exec(function (err, identities) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); - } - else { - return callback(err); - } - } - else { - return callback(null, identities); - } - }); - } - else if (options.versions === 'latest') { - Identity.findOne({ 'stix.id': stixId }) - .sort('-stix.modified') - .lean() - .exec(function(err, identity) { - 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 (identity) { - return callback(null, [ identity ]); - } - 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 identity 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); - } + options = options || {}; + if (!options.import) { + // Set the ATT&CK Spec Version + data.stix.x_mitre_attack_spec_version = data.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; - Identity.findOne({ 'stix.id': stixId, 'stix.modified': modified }, function(err, identity) { - if (err) { - if (err.name === 'CastError') { - const error = new Error(errors.badlyFormattedParameter); - error.parameterName = 'stixId'; - return callback(error); + // Record the user account that created the object + if (options.userAccountId) { + data.workspace.workflow.created_by_user_account = options.userAccountId; } - else { - return callback(err); - } - } - else { - // Note: document is null if not found - if (identity) { - return callback(null, identity); - } - 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. - // 2. This is a new version of an existing object. Create a new object with the specified id. - // Do not set the created_by_ref or x_mitre_modified_by_ref properties. - - // Create the document - const identity = new Identity(data); - options = options || {}; - if (!options.import) { - // Set the ATT&CK Spec Version - identity.stix.x_mitre_attack_spec_version = identity.stix.x_mitre_attack_spec_version ?? config.app.attackSpecVersion; + // Set the default marking definitions + await attackObjectsService.setDefaultMarkingDefinitions(data); - // Record the user account that created the object - if (options.userAccountId) { - identity.workspace.workflow.created_by_user_account = options.userAccountId; + // Assign a new STIX id if not already provided + data.stix.id = data.stix.id || `identity--${uuid.v4()}`; } - // Set the default marking definitions - await attackObjectsService.setDefaultMarkingDefinitions(identity); - - // Assign a new STIX id if not already provided - identity.stix.id = identity.stix.id || `identity--${uuid.v4()}`; - } - - // Save the document in the database - try { - const savedIdentity = await identity.save(); + // Save the document in the database + const savedIdentity = await this.repository.save(data); return savedIdentity; } - 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); - } - - Identity.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); + async addCreatedByIdentity(attackObject, cache) { + // Use the cache if the caller provides one + if (cache) { + const identityObject = cache.get(attackObject.stix.created_by_ref); + if (identityObject) { + attackObject.created_by_identity = identityObject; } } - 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); - } + if (!attackObject.created_by_identity) { + // No cache or not found in cache + try { + // eslint-disable-next-line require-atomic-updates + const identityObject = await this.repository.retrieveLatestByStixId(attackObject.stix.created_by_ref); + attackObject.created_by_identity = identityObject; - Identity.findOneAndRemove({ 'stix.id': stixId, 'stix.modified': stixModified }, function (err, identity) { - if (err) { - return callback(err); - } else { - //Note: identity is null if not found - return callback(null, identity); + if (cache) { + cache.set(attackObject.stix.created_by_ref, identityObject); + } + } + catch (err) { + // Ignore lookup errors + } } - }); -}; - -exports.deleteById = function (stixId, callback) { - if (!stixId) { - const error = new Error(errors.missingParameter); - error.parameterName = 'stixId'; - return callback(error); } - Identity.deleteMany({ 'stix.id': stixId }, function (err, identity) { - if (err) { - return callback(err); - } else { - //Note: identity is null if not found - return callback(null, identity); + async addModifiedByIdentity(attackObject, cache) { + // Use the cache if the caller provides one + if (cache) { + const identityObject = cache.get(attackObject.stix.x_mitre_modified_by_ref); + if (identityObject) { + attackObject.modified_by_identity = identityObject; + } } - }); -}; - -async function getLatest(stixId) { - const identity = await Identity - .findOne({ 'stix.id': stixId }) - .sort('-stix.modified') - .lean() - .exec(); - return identity; -} + if (!attackObject.modified_by_identity) { + // No cache or not found in cache + try { + // eslint-disable-next-line require-atomic-updates + const identityObject = await this.repository.retrieveLatestByStixId(attackObject.stix.x_mitre_modified_by_ref); + attackObject.modified_by_identity = identityObject; -async function addCreatedByIdentity(attackObject, cache) { - // Use the cache if the caller provides one - if (cache) { - const identityObject = cache.get(attackObject.stix.created_by_ref); - if (identityObject) { - attackObject.created_by_identity = identityObject; + if (cache) { + cache.set(attackObject.stix.x_mitre_modified_by_ref, identityObject); + } + } + catch (err) { + // Ignore lookup errors + } } } - if (!attackObject.created_by_identity) { - // No cache or not found in cache - try { - // eslint-disable-next-line require-atomic-updates - const identityObject = await getLatest(attackObject.stix.created_by_ref); - attackObject.created_by_identity = identityObject; - + static async addCreatedByUserAccountWithCache(attackObject, cache) { + const userAccountRef = attackObject?.workspace?.workflow?.created_by_user_account; + if (userAccountRef) { + // Use the cache if the caller provides one if (cache) { - cache.set(attackObject.stix.created_by_ref, identityObject); + const userAccountObject = cache.get(userAccountRef); + if (userAccountObject) { + attackObject.created_by_user_account = userAccountObject; + } } - } - catch (err) { - // Ignore lookup errors - } - } -} -async function addModifiedByIdentity(attackObject, cache) { - // Use the cache if the caller provides one - if (cache) { - const identityObject = cache.get(attackObject.stix.x_mitre_modified_by_ref); - if (identityObject) { - attackObject.modified_by_identity = identityObject; - } - } - - if (!attackObject.modified_by_identity) { - // No cache or not found in cache - try { - // eslint-disable-next-line require-atomic-updates - const identityObject = await getLatest(attackObject.stix.x_mitre_modified_by_ref); - attackObject.modified_by_identity = identityObject; - - if (cache) { - cache.set(attackObject.stix.x_mitre_modified_by_ref, identityObject); + if (!attackObject.created_by_user_account) { + // No cache or not found in cache + await userAccountsService.addCreatedByUserAccount(attackObject); + if (cache) { + cache.set(userAccountRef, attackObject.created_by_user_account); + } } } - catch (err) { - // Ignore lookup errors - } } -} -async function addCreatedByUserAccountWithCache(attackObject, cache) { - const userAccountRef = attackObject?.workspace?.workflow?.created_by_user_account; - if (userAccountRef) { - // Use the cache if the caller provides one - if (cache) { - const userAccountObject = cache.get(userAccountRef); - if (userAccountObject) { - attackObject.created_by_user_account = userAccountObject; - } + async addCreatedByAndModifiedByIdentities(attackObject, identityCache, userAccountCache) { + if (attackObject && attackObject.stix && attackObject.stix.created_by_ref) { + await this.addCreatedByIdentity(attackObject, identityCache); } - if (!attackObject.created_by_user_account) { - // No cache or not found in cache - await userAccountsService.addCreatedByUserAccount(attackObject); - if (cache) { - cache.set(userAccountRef, attackObject.created_by_user_account); - } + if (attackObject && attackObject.stix && attackObject.stix.x_mitre_modified_by_ref) { + await this.addModifiedByIdentity(attackObject, identityCache); } - } -} - -async function addCreatedByAndModifiedByIdentities(attackObject, identityCache, userAccountCache) { - if (attackObject && attackObject.stix && attackObject.stix.created_by_ref) { - await addCreatedByIdentity(attackObject, identityCache); - } - - if (attackObject && attackObject.stix && attackObject.stix.x_mitre_modified_by_ref) { - await addModifiedByIdentity(attackObject, identityCache); - } - // Add user account data - if (attackObject?.workspace?.workflow?.created_by_user_account) { - await addCreatedByUserAccountWithCache(attackObject, userAccountCache); + // Add user account data + if (attackObject?.workspace?.workflow?.created_by_user_account) { + await IdentitiesService.addCreatedByUserAccountWithCache(attackObject, userAccountCache); + } } } -exports.addCreatedByAndModifiedByIdentities = addCreatedByAndModifiedByIdentities; -exports.addCreatedByAndModifiedByIdentitiesToAll = async function(attackObjects) { - const identityCache = new Map(); - const userAccountCache = new Map(); - for (const attackObject of attackObjects) { - // eslint-disable-next-line no-await-in-loop - await addCreatedByAndModifiedByIdentities(attackObject, identityCache, userAccountCache); - } -} +module.exports = new IdentitiesService(identityType, identitiesRepository); \ No newline at end of file diff --git a/app/tests/api/identities/identities.spec.js b/app/tests/api/identities/identities.spec.js index 72023787..e3a2dcd9 100644 --- a/app/tests/api/identities/identities.spec.js +++ b/app/tests/api/identities/identities.spec.js @@ -48,198 +48,141 @@ describe('Identity API', function () { passportCookie = await login.loginAnonymous(app); }); - it('GET /api/identities returns the placeholder identity', function (done) { - request(app) + it('GET /api/identities returns the placeholder identity', async function () { + const res = await request(app) .get('/api/identities') .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 identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(1); - done(); - } - }); + + // We expect to get an empty array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(1); }); - it('POST /api/identities does not create an empty identity', function (done) { + it('POST /api/identities does not create an empty identity', async function () { const body = { }; - request(app) + await request(app) .post('/api/identities') .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 identity1; - it('POST /api/identities creates an identity', function (done) { + it('POST /api/identities creates an identity', 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/identities') .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 identity - identity1 = res.body; - expect(identity1).toBeDefined(); - expect(identity1.stix).toBeDefined(); - expect(identity1.stix.id).toBeDefined(); - expect(identity1.stix.created).toBeDefined(); - expect(identity1.stix.modified).toBeDefined(); - expect(identity1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created identity + identity1 = res.body; + expect(identity1).toBeDefined(); + expect(identity1.stix).toBeDefined(); + expect(identity1.stix.id).toBeDefined(); + expect(identity1.stix.created).toBeDefined(); + expect(identity1.stix.modified).toBeDefined(); + expect(identity1.stix.x_mitre_attack_spec_version).toBe(config.app.attackSpecVersion); }); - it('GET /api/identities returns the added identity', function (done) { - request(app) + it('GET /api/identities returns the added identity', async function () { + const res = await request(app) .get('/api/identities') .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 identity in an array - const identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(2); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one identity in an array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(2); }); - it('GET /api/identities/:id should not return an identity when the id cannot be found', function (done) { - request(app) + it('GET /api/identities/:id should not return an identity when the id cannot be found', async function () { + await request(app) .get('/api/identities/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/identities/:id returns the added identity', function (done) { - request(app) + it('GET /api/identities/:id returns the added identity', async function () { + const res = await request(app) .get('/api/identities/' + identity1.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 identity in an array - const identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(1); - - const identity = identities[0]; - expect(identity).toBeDefined(); - expect(identity.stix).toBeDefined(); - expect(identity.stix.id).toBe(identity1.stix.id); - expect(identity.stix.type).toBe(identity1.stix.type); - expect(identity.stix.name).toBe(identity1.stix.name); - expect(identity.stix.description).toBe(identity1.stix.description); - expect(identity.stix.spec_version).toBe(identity1.stix.spec_version); - expect(identity.stix.object_marking_refs).toEqual(expect.arrayContaining(identity1.stix.object_marking_refs)); - expect(identity.stix.created_by_ref).toBe(identity1.stix.created_by_ref); - expect(identity.stix.x_mitre_attack_spec_version).toBe(identity1.stix.x_mitre_attack_spec_version); - - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one identity in an array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(1); + + const identity = identities[0]; + expect(identity).toBeDefined(); + expect(identity.stix).toBeDefined(); + expect(identity.stix.id).toBe(identity1.stix.id); + expect(identity.stix.type).toBe(identity1.stix.type); + expect(identity.stix.name).toBe(identity1.stix.name); + expect(identity.stix.description).toBe(identity1.stix.description); + expect(identity.stix.spec_version).toBe(identity1.stix.spec_version); + expect(identity.stix.object_marking_refs).toEqual(expect.arrayContaining(identity1.stix.object_marking_refs)); + expect(identity.stix.created_by_ref).toBe(identity1.stix.created_by_ref); + expect(identity.stix.x_mitre_attack_spec_version).toBe(identity1.stix.x_mitre_attack_spec_version); + + }); - it('PUT /api/identities updates an identity', function (done) { + it('PUT /api/identities updates an identity', async function () { const originalModified = identity1.stix.modified; const timestamp = new Date().toISOString(); identity1.stix.modified = timestamp; identity1.stix.description = 'This is an updated identity.' const body = identity1; - request(app) + const res = await request(app) .put('/api/identities/' + identity1.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 identity - const identity = res.body; - expect(identity).toBeDefined(); - expect(identity.stix.id).toBe(identity1.stix.id); - expect(identity.stix.modified).toBe(identity1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the updated identity + const identity = res.body; + expect(identity).toBeDefined(); + expect(identity.stix.id).toBe(identity1.stix.id); + expect(identity.stix.modified).toBe(identity1.stix.modified); + }); - it('POST /api/identities does not create an identity with the same id and modified date', function (done) { + it('POST /api/identities does not create an identity with the same id and modified date', async function () { const body = identity1; - request(app) + await request(app) .post('/api/identities') .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 identity2; - it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', async function () { identity2 = _.cloneDeep(identity1); identity2._id = undefined; identity2.__t = undefined; @@ -247,121 +190,87 @@ describe('Identity API', function () { const timestamp = new Date().toISOString(); identity2.stix.modified = timestamp; const body = identity2; - request(app) + const res = await request(app) .post('/api/identities') .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 identity - const identity = res.body; - expect(identity).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created identity + const identity = res.body; + expect(identity).toBeDefined(); }); - it('GET /api/identities returns the latest added identity', function (done) { - request(app) + it('GET /api/identities returns the latest added identity', async function () { + const res = await request(app) .get('/api/identities/' + identity2.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 identity in an array - const identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(1); - const identity = identities[0]; - expect(identity.stix.id).toBe(identity2.stix.id); - expect(identity.stix.modified).toBe(identity2.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one identity in an array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(1); + const identity = identities[0]; + expect(identity.stix.id).toBe(identity2.stix.id); + expect(identity.stix.modified).toBe(identity2.stix.modified); }); - it('GET /api/identities returns all added identities', function (done) { - request(app) + it('GET /api/identities returns all added identities', async function () { + const res = await request(app) .get('/api/identities/' + identity1.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 identities in an array - const identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(2); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get two identities in an array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(2); }); - it('GET /api/identities/:id/modified/:modified returns the first added identity', function (done) { - request(app) + it('GET /api/identities/:id/modified/:modified returns the first added identity', async function () { + const res = await request(app) .get('/api/identities/' + identity1.stix.id + '/modified/' + identity1.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 identity in an array - const identity = res.body; - expect(identity).toBeDefined(); - expect(identity.stix).toBeDefined(); - expect(identity.stix.id).toBe(identity1.stix.id); - expect(identity.stix.modified).toBe(identity1.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one identity in an array + const identity = res.body; + expect(identity).toBeDefined(); + expect(identity.stix).toBeDefined(); + expect(identity.stix.id).toBe(identity1.stix.id); + expect(identity.stix.modified).toBe(identity1.stix.modified); }); - it('GET /api/identities/:id/modified/:modified returns the second added identity', function (done) { - request(app) + it('GET /api/identities/:id/modified/:modified returns the second added identity', async function () { + const res = await request(app) .get('/api/identities/' + identity2.stix.id + '/modified/' + identity2.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 identity in an array - const identity = res.body; - expect(identity).toBeDefined(); - expect(identity.stix).toBeDefined(); - expect(identity.stix.id).toBe(identity2.stix.id); - expect(identity.stix.modified).toBe(identity2.stix.modified); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get one identity in an array + const identity = res.body; + expect(identity).toBeDefined(); + expect(identity.stix).toBeDefined(); + expect(identity.stix.id).toBe(identity2.stix.id); + expect(identity.stix.modified).toBe(identity2.stix.modified); + }); let identity3; - it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', function (done) { + it('POST /api/identities should create a new version of an identity with a duplicate stix.id but different stix.modified date', async function () { identity3 = _.cloneDeep(identity1); identity3._id = undefined; identity3.__t = undefined; @@ -369,91 +278,54 @@ describe('Identity API', function () { const timestamp = new Date().toISOString(); identity3.stix.modified = timestamp; const body = identity3; - request(app) + const res = await request(app) .post('/api/identities') .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 identity - const identity = res.body; - expect(identity).toBeDefined(); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get the created identity + const identity = res.body; + expect(identity).toBeDefined(); }); - it('DELETE /api/identities/:id should not delete a identity when the id cannot be found', function (done) { - request(app) + it('DELETE /api/identities/:id should not delete a identity when the id cannot be found', async function () { + await request(app) .delete('/api/identities/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/identities/:id/modified/:modified deletes an identity', function (done) { - request(app) + it('DELETE /api/identities/:id/modified/:modified deletes an identity', async function () { + await request(app) .delete('/api/identities/' + identity1.stix.id + '/modified/' + identity1.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/identities/:id should delete all the identities with the same stix id', function (done) { - request(app) + it('DELETE /api/identities/:id should delete all the identities with the same stix id', async function () { + await request(app) .delete('/api/identities/' + identity2.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/identities returns only the placeholder identity', function (done) { - request(app) + it('GET /api/identities returns only the placeholder identity', async function () { + const res = await request(app) .get('/api/identities') .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 identities = res.body; - expect(identities).toBeDefined(); - expect(Array.isArray(identities)).toBe(true); - expect(identities.length).toBe(1); - done(); - } - }); + .expect('Content-Type', /json/); + + // We expect to get an empty array + const identities = res.body; + expect(identities).toBeDefined(); + expect(Array.isArray(identities)).toBe(true); + expect(identities.length).toBe(1); + }); after(async function() {