diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..3c5026850 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Jasmine Testing + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "master", "develop" ] + +jobs: + run-jasmine-tests: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./nav-app/ + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + cache: 'npm' + cache-dependency-path: nav-app/package-lock.json + - name: Install + run: npm ci + - name: Run Jasmine tests + run: npm run test -- --code-coverage --no-watch --browsers ChromeHeadlessCI + - name: Archive code coverage results + uses: actions/upload-artifact@v4 + if: always() + with: + name: code-coverage-report + path: nav-app/coverage/chrome/index.html diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e25d8aa9..d04b488cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,22 @@ This will patch the version number appropriately and create the correct tag on the current commit. The creation of the tag can be disabled with the --no-git-tag-version if desired. --> +# 4.9.1 - 14 November 2023 -# Changes Staged on Develop +Adds support for ATT&CK v14.1. + +## Fixes +- Fixed an issue with the Dockerfile which was preventing the docker image from building. See issue [#598](https://github.com/mitre-attack/attack-navigator/pull/598). + +# 4.9.0 - 31 October 2023 + +Adds support for ATT&CK v14.0. ## New Features - Consolidated the JSON, Excel, and SVG export options into a single dropdown. Added an option to the export interface to only download annotations on visible techniques. See issue [#215](https://github.com/mitre-attack/attack-navigator/issues/215). - Extended search interface to support searching for techniques by asset. - Added the ability to configure how sub-techniques are displayed in the layer file through the `expandedSubtechniques` property - annotated, all, or none. See issue [#560](https://github.com/mitre-attack/attack-navigator/issues/560) and the `Layer File Format Changes` section. -- Added functionality to download all open layers in JSON or MS Excel format. Also added the functionality to upload file with multiple layers. See issue [#128](https://github.com/mitre-attack/attack-navigator/issues/128). +- Added functionality to download all open layers in JSON or MS Excel format. Also added the ability to upload a file with multiple layers. See issue [#128](https://github.com/mitre-attack/attack-navigator/issues/128). - Added a new toolbar option to enable or disable the sticky toolbar. ## Improvements @@ -39,8 +47,8 @@ Layer file format updated to version 4.5. See [layers/LAYERFORMATv4_5.md](layers/LAYERFORMATv4_5.md) for the full specification. - Added support for selecting only visible techniques. The `selectVisibleTechniques` field specifies whether or not hidden techniques will be included in the different select behaviors. -- Added support for configuring how to display sub-techniques in the layer file with the help of the `expandedSubtechniques` field. This property can be set to `all`, `none`, or `annotated` to display the sub-techniques. -- Added support for downloading all open layers in JSON or MS Excel (.xlsx) format. The user can now upload a file with multiple layers. +- Added support for configuring how sub-techniques are displayed in the layer with the `expandedSubtechniques` field. This property can be set to `all`, `annotated`, or `none` to expand all sub-techniques, expand only annotated sub-techniques, or collapse all sub-techniques, respectively. +- Added support for a list of layers. Users can now upload a layer file that contains multiple layers. # 4.8.2 - 9 May 2023 diff --git a/Dockerfile b/Dockerfile index a3c3a9cc1..173243fc1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,22 +6,13 @@ ENV NODE_OPTIONS=--openssl-legacy-provider # install node packages - cache for faster future builds WORKDIR /src/nav-app -COPY nav-app/package*.json nav-app/patch-webpack.js . -# install packages and build -RUN npm install --unsafe-perm --legacy-peer-deps - -# NOTE on legacy-peer-deps: -# The --legacy-peer-deps flags is included to bypass the dependency peer resolution conflict that arises between Angular -# and @angular-devkit/build-angular@0.1100.7, the latter of which has peerDependency: karma: '~5.1.0'. However, -# upgrading karma to 5.1.0 cascades into a litany of other dependency conflicts, which would ultimately require us to -# upgrade from Angular v11 to v12. Therefore, legacy-peer-deps will be allowed until a major framework upgrade can occur +COPY ./nav-app/package*.json ./ -# give user permissions -RUN chown -R node:node ./ +# install packages and build +RUN npm install # copy over needed files -USER node -COPY nav-app/ ./ +COPY ./nav-app/ ./ WORKDIR /src COPY layers/*.md ./layers/ diff --git a/NOTICE.txt b/NOTICE.txt index 428b4a119..84f26facb 100755 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -1,4 +1,4 @@ -Copyright 2023 The MITRE Corporation +Copyright 2024 The MITRE Corporation Approved for Public Release; Distribution Unlimited. Case Number 18-0128. diff --git a/README.md b/README.md index 7ccfccb3e..bc2421c08 100755 --- a/README.md +++ b/README.md @@ -256,7 +256,7 @@ STIX is designed to improve many different capabilities, such as collaborative t ## Notice -Copyright 2023 The MITRE Corporation +Copyright 2024 The MITRE Corporation Approved for Public Release; Distribution Unlimited. Case Number 18-0128. diff --git a/USAGE.md b/USAGE.md index 10158247a..10a2828d1 100644 --- a/USAGE.md +++ b/USAGE.md @@ -635,7 +635,7 @@ To get the full view of the matrix on a single page, be sure to disable the stic # Notice -Copyright 2023 The MITRE Corporation +Copyright 2024 The MITRE Corporation Approved for Public Release; Distribution Unlimited. Case Number 18-0128. diff --git a/layers/LAYERFORMATv4_5.md b/layers/LAYERFORMATv4_5.md index b373f6c18..83e16a71d 100644 --- a/layers/LAYERFORMATv4_5.md +++ b/layers/LAYERFORMATv4_5.md @@ -37,7 +37,7 @@ This document describes **Version 4.5** of the MITRE ATT&CK Navigator Layer file | Name | Type | Required? | Default Value (if not present) | Description | | :------------- | :------------- | :------------- | :------------- | :------------- | | attack | String | No | Current version of ATT&CK | ATT&CK version of this layer | -| navigator | String | Yes | | Must be at least "4.8.0" | +| navigator | String | Yes | | Must be at least "4.9.0" | | layer | String | Yes | | Must be "4.5" | ## Technique Object properties @@ -109,7 +109,7 @@ The following example illustrates the layer file format of a single layer: "name": "example layer", "versions": { "attack": "13", - "navigator": "4.8.2", + "navigator": "4.9.1", "layer": "4.5" }, "domain": "enterprise-attack", @@ -232,8 +232,8 @@ The following example illustrates the layer file format of multiple layers. The { "name": "example layer", "versions": { - "attack": "13", - "navigator": "4.8.2", + "attack": "14", + "navigator": "4.9.1", "layer": "4.5" }, "domain": "enterprise-attack", @@ -350,8 +350,8 @@ The following example illustrates the layer file format of multiple layers. The { "name": "example layer", "versions": { - "attack": "13", - "navigator": "4.8.2", + "attack": "14", + "navigator": "4.9.1", "layer": "4.5" }, "domain": "enterprise-attack", diff --git a/nav-app/angular.json b/nav-app/angular.json index 6d64d6111..e2d130db1 100644 --- a/nav-app/angular.json +++ b/nav-app/angular.json @@ -99,7 +99,8 @@ "node_modules/tinygradient/browser.js" ], "styles": ["src/styles.scss"], - "assets": ["src/assets", "src/favicon.ico"] + "assets": ["src/assets", "src/favicon.ico"], + "codeCoverage": true } }, "lint": { diff --git a/nav-app/karma.conf.js b/nav-app/karma.conf.js index 0dbe551e4..da90bc863 100755 --- a/nav-app/karma.conf.js +++ b/nav-app/karma.conf.js @@ -45,6 +45,8 @@ module.exports = function (config) { coverageReporter: { type: 'html', dir: 'coverage/', + subdir: 'chrome', + file: 'index.html' }, }); }; diff --git a/nav-app/package-lock.json b/nav-app/package-lock.json index 04b44d0d6..e80c2d60b 100644 --- a/nav-app/package-lock.json +++ b/nav-app/package-lock.json @@ -1,12 +1,12 @@ { "name": "attack-navigator", - "version": "4.8.2", + "version": "4.9.1", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "attack-navigator", - "version": "4.8.2", + "version": "4.9.1", "license": "Apache-2.0", "dependencies": { "@angular/animations": "^14.3.0", @@ -72,9 +72,9 @@ } }, "node_modules/@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "node_modules/@ampproject/remapping": { @@ -7476,9 +7476,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true, "funding": [ { @@ -15177,9 +15177,9 @@ }, "dependencies": { "@adobe/css-tools": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz", - "integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.2.tgz", + "integrity": "sha512-DA5a1C0gD/pLOvhv33YMrbf2FK3oUzwNl9oOJqE4XVjuEtt6XIakRcsd7eLiOSPkp1kTRQGICTA8cKra/vFbjw==", "dev": true }, "@ampproject/remapping": { @@ -20624,9 +20624,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", - "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", + "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", "dev": true }, "foreground-child": { diff --git a/nav-app/package.json b/nav-app/package.json index 7ae006ba3..9a0e72f18 100644 --- a/nav-app/package.json +++ b/nav-app/package.json @@ -5,7 +5,7 @@ "type": "git", "url": "https://github.com/mitre-attack/attack-navigator.git" }, - "version": "4.8.2", + "version": "4.9.1", "license": "Apache-2.0", "scripts": { "ng": "ng", diff --git a/nav-app/src/app/classes/view-model.ts b/nav-app/src/app/classes/view-model.ts index 882841873..b42f3bf32 100644 --- a/nav-app/src/app/classes/view-model.ts +++ b/nav-app/src/app/classes/view-model.ts @@ -918,9 +918,6 @@ export class ViewModel { */ private sortingAlgorithm(technique1: Technique, technique2: Technique, score1: number, score2: number) { switch (this.sorting) { - default: - case 0: // A-Z - return technique1.name.localeCompare(technique2.name); case 1: // Z-A return technique2.name.localeCompare(technique1.name); case 2: // 1-2 @@ -935,6 +932,9 @@ export class ViewModel { } else { return score2 - score1; } + case 0: // A-Z + default: + return technique1.name.localeCompare(technique2.name); } } @@ -958,13 +958,6 @@ export class ViewModel { let aggScore: any = 0; switch (this.layout.aggregateFunction) { - default: - case 'average': - // Divide by count of all subtechniques + 1 (for parent technique) if counting unscored is enabled - // Otherwise, divide by count of all scored only - score = scores.reduce((a, b) => a + b); - aggScore = score / (this.layout.countUnscored ? technique.subtechniques.length + 1 : validTechniquesCount); - break; case 'min': if (scores.length > 0) aggScore = Math.min(...scores); break; @@ -974,6 +967,13 @@ export class ViewModel { case 'sum': aggScore = scores.reduce((a, b) => a + b); break; + case 'average': + default: + // Divide by count of all subtechniques + 1 (for parent technique) if counting unscored is enabled + // Otherwise, divide by count of all scored only + score = scores.reduce((a, b) => a + b); + aggScore = score / (this.layout.countUnscored ? technique.subtechniques.length + 1 : validTechniquesCount); + break; } aggScore = aggScore.toFixed(2); @@ -1259,9 +1259,6 @@ export class ViewModel { */ if (typeof obj.viewMode === 'number') { switch (obj.viewMode) { - default: - case 0: - break; //default matrix layout already initialized case 1: this.layout.layout = 'side'; this.layout.showName = false; @@ -1271,6 +1268,10 @@ export class ViewModel { this.layout.layout = 'mini'; this.layout.showName = false; this.layout.showID = false; + break; + case 0: + default: + break; //default matrix layout already initialized } } else console.error('TypeError: viewMode field is not a number'); } diff --git a/nav-app/src/app/list-input/list-input.component.ts b/nav-app/src/app/list-input/list-input.component.ts index 2eeb5113c..aecd4fef1 100644 --- a/nav-app/src/app/list-input/list-input.component.ts +++ b/nav-app/src/app/list-input/list-input.component.ts @@ -18,10 +18,6 @@ export class ListInputComponent implements OnInit { return this.config.type == 'links'; } - constructor() { - // intentionally left blank - } - ngOnInit(): void { if (this.config.level == 'technique') { this.list = this.config.list.map((item) => { @@ -52,7 +48,7 @@ export class ListInputComponent implements OnInit { this.list.splice(i, 1); } - if (this.list[0] && this.list[0].divider) this.removeDivider(0); + if (this.list[0]?.divider) this.removeDivider(0); if (this.list[this.list.length - 1] && this.list[this.list.length - 1].divider) this.removeDivider(this.list.length - 1); this.updateList(); @@ -82,12 +78,10 @@ export class ListInputComponent implements OnInit { public canAddDivider(i: number): boolean { if (i < 1) return false; // cannot add divider before the first item if ( - this.list[i] && - this.list[i].valid() && - !this.list[i].divider && - this.list[i - 1] && - this.list[i - 1].valid() && - !this.list[i - 1].divider + this.list[i]?.valid() && + !this.list[i]?.divider && + this.list[i - 1]?.valid() && + !this.list[i - 1]?.divider ) { return true; } diff --git a/nav-app/src/app/services/data.service.ts b/nav-app/src/app/services/data.service.ts index ed417c0bc..fedd10a10 100755 --- a/nav-app/src/app/services/data.service.ts +++ b/nav-app/src/app/services/data.service.ts @@ -237,11 +237,11 @@ export class DataService { private domainData$: Observable; // URLs in case config file doesn't load properly - public readonly latestVersion: Version = { name: 'ATT&CK v13', number: '13' }; - private lowestSupportedVersion: Version; - private enterpriseAttackURL: string = 'https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json'; - private mobileAttackURL: string = 'https://raw.githubusercontent.com/mitre/cti/master/mobile-attack/mobile-attack.json'; - private icsAttackURL: string = 'https://raw.githubusercontent.com/mitre/cti/master/ics-attack/ics-attack.json'; + private latestVersion: Version = { name: "ATT&CK v14", number: "14" }; + private lowestSupportedVersion: Version; // used by tabs component + private enterpriseAttackURL: string = "https://raw.githubusercontent.com/mitre/cti/master/enterprise-attack/enterprise-attack.json"; + private mobileAttackURL: string = "https://raw.githubusercontent.com/mitre/cti/master/mobile-attack/mobile-attack.json"; + private icsAttackURL: string = "https://raw.githubusercontent.com/mitre/cti/master/ics-attack/ics-attack.json"; /** * Set up the URLs for data diff --git a/nav-app/src/assets/config.json b/nav-app/src/assets/config.json index 810b59045..44271d80e 100755 --- a/nav-app/src/assets/config.json +++ b/nav-app/src/assets/config.json @@ -1,5 +1,26 @@ { "versions": [ + { + "name": "ATT&CK v14", + "version": "14", + "domains": [ + { + "name": "Enterprise", + "identifier": "enterprise-attack", + "data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v14.1/enterprise-attack/enterprise-attack.json"] + }, + { + "name": "Mobile", + "identifier": "mobile-attack", + "data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v14.1/mobile-attack/mobile-attack.json"] + }, + { + "name": "ICS", + "identifier": "ics-attack", + "data": ["https://raw.githubusercontent.com/mitre/cti/ATT%26CK-v14.1/ics-attack/ics-attack.json"] + } + ] + }, { "name": "ATT&CK v13", "version": "13",