├── .dockerignore
├── .github
└── workflows
│ ├── create-release.yml
│ ├── github-pages.yml
│ └── run-jasmine-tests.yml
├── .gitignore
├── .markdownlint.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE.txt
├── NOTICE.txt
├── README.md
├── USAGE.md
├── layers
├── README.md
├── samples
│ ├── ATTACKcon 2018
│ │ ├── Black_Pins.json
│ │ ├── Blue_Pins.json
│ │ ├── Gold_Pins.json
│ │ ├── Red_Pins.json
│ │ └── Submitter_Responses.json
│ ├── Bear_APT.json
│ └── heatmap_layer.json
└── spec
│ ├── v1.0
│ └── layerformat.md
│ ├── v1.1
│ └── layerformat.md
│ ├── v1.2
│ └── layerformat.md
│ ├── v1.3
│ └── layerformat.md
│ ├── v2.0
│ └── layerformat.md
│ ├── v2.1
│ └── layerformat.md
│ ├── v2.2
│ └── layerformat.md
│ ├── v3.0
│ └── layerformat.md
│ ├── v4.0
│ └── layerformat.md
│ ├── v4.1
│ └── layerformat.md
│ ├── v4.2
│ └── layerformat.md
│ ├── v4.3
│ └── layerformat.md
│ ├── v4.4
│ └── layerformat.md
│ └── v4.5
│ └── layerformat.md
└── nav-app
├── .editorconfig
├── .gitignore
├── .prettierignore
├── .prettierrc
├── angular.json
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── package-lock.json
├── package.json
├── protractor.conf.js
├── redirects
├── enterprise
│ └── index.html
└── mobile
│ └── index.html
├── src
├── app
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.spec.ts
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── changelog
│ │ ├── changelog.component.html
│ │ ├── changelog.component.spec.ts
│ │ └── changelog.component.ts
│ ├── classes
│ │ ├── context-menu-item.ts
│ │ ├── domain.ts
│ │ ├── filter.ts
│ │ ├── gradient.ts
│ │ ├── index.ts
│ │ ├── layout-options.ts
│ │ ├── link.ts
│ │ ├── metadata.ts
│ │ ├── stix
│ │ │ ├── asset.ts
│ │ │ ├── campaign.ts
│ │ │ ├── data-component.ts
│ │ │ ├── group.ts
│ │ │ ├── index.ts
│ │ │ ├── matrix.ts
│ │ │ ├── mitigation.ts
│ │ │ ├── note.ts
│ │ │ ├── software.ts
│ │ │ ├── stix-object.ts
│ │ │ ├── tactic.ts
│ │ │ └── technique.ts
│ │ ├── tab.ts
│ │ ├── technique-vm.ts
│ │ ├── version-changelog.ts
│ │ ├── version.ts
│ │ └── view-model.ts
│ ├── datatable
│ │ ├── data-table.component.html
│ │ ├── data-table.component.scss
│ │ └── data-table.component.ts
│ ├── help
│ │ ├── help.component.html
│ │ ├── help.component.scss
│ │ ├── help.component.spec.ts
│ │ └── help.component.ts
│ ├── layer-information
│ │ ├── layer-information.component.html
│ │ ├── layer-information.component.scss
│ │ ├── layer-information.component.spec.ts
│ │ └── layer-information.component.ts
│ ├── layer-settings
│ │ ├── layer-settings.component.html
│ │ ├── layer-settings.component.scss
│ │ ├── layer-settings.component.spec.ts
│ │ └── layer-settings.component.ts
│ ├── layer-upgrade
│ │ ├── changelog-cell
│ │ │ ├── changelog-cell.component.html
│ │ │ ├── changelog-cell.component.scss
│ │ │ ├── changelog-cell.component.spec.ts
│ │ │ └── changelog-cell.component.ts
│ │ ├── layer-upgrade.component.html
│ │ ├── layer-upgrade.component.scss
│ │ └── layer-upgrade.component.ts
│ ├── list-input
│ │ ├── list-input.component.html
│ │ ├── list-input.component.scss
│ │ ├── list-input.component.spec.ts
│ │ └── list-input.component.ts
│ ├── matrix
│ │ ├── cell.ts
│ │ ├── matrix-common.scss
│ │ ├── matrix-common.spec.ts
│ │ ├── matrix-common.ts
│ │ ├── matrix-flat
│ │ │ ├── matrix-flat.component.html
│ │ │ ├── matrix-flat.component.scss
│ │ │ ├── matrix-flat.component.spec.ts
│ │ │ └── matrix-flat.component.ts
│ │ ├── matrix-mini
│ │ │ ├── matrix-mini.component.html
│ │ │ ├── matrix-mini.component.scss
│ │ │ ├── matrix-mini.component.spec.ts
│ │ │ └── matrix-mini.component.ts
│ │ ├── matrix-side
│ │ │ ├── matrix-side.component.html
│ │ │ ├── matrix-side.component.scss
│ │ │ ├── matrix-side.component.spec.ts
│ │ │ └── matrix-side.component.ts
│ │ ├── tactic-cell
│ │ │ ├── tactic-cell.component.html
│ │ │ ├── tactic-cell.component.scss
│ │ │ ├── tactic-cell.component.spec.ts
│ │ │ └── tactic-cell.component.ts
│ │ └── technique-cell
│ │ │ ├── cell-popover.scss
│ │ │ ├── cell-popover.ts
│ │ │ ├── contextmenu
│ │ │ ├── contextmenu.component.html
│ │ │ ├── contextmenu.component.scss
│ │ │ ├── contextmenu.component.spec.ts
│ │ │ └── contextmenu.component.ts
│ │ │ ├── technique-cell.component.html
│ │ │ ├── technique-cell.component.scss
│ │ │ ├── technique-cell.component.spec.ts
│ │ │ ├── technique-cell.component.ts
│ │ │ └── tooltip
│ │ │ ├── tooltip.component.html
│ │ │ ├── tooltip.component.scss
│ │ │ ├── tooltip.component.spec.ts
│ │ │ └── tooltip.component.ts
│ ├── search-and-multiselect
│ │ ├── search-and-multiselect.component.html
│ │ ├── search-and-multiselect.component.scss
│ │ └── search-and-multiselect.component.ts
│ ├── services
│ │ ├── config.service.spec.ts
│ │ ├── config.service.ts
│ │ ├── data.service.spec.ts
│ │ ├── data.service.ts
│ │ ├── icons.service.spec.ts
│ │ ├── icons.service.ts
│ │ ├── viewmodels.service.spec.ts
│ │ └── viewmodels.service.ts
│ ├── sidebar
│ │ ├── sidebar.component.html
│ │ ├── sidebar.component.scss
│ │ ├── sidebar.component.spec.ts
│ │ └── sidebar.component.ts
│ ├── svg-export
│ │ ├── renderable-objects
│ │ │ ├── index.ts
│ │ │ ├── renderable-matrix.ts
│ │ │ ├── renderable-tactic.ts
│ │ │ └── renderable-technique.ts
│ │ ├── svg-export.component.html
│ │ ├── svg-export.component.scss
│ │ ├── svg-export.component.spec.ts
│ │ └── svg-export.component.ts
│ ├── tabs
│ │ ├── tabs.component.html
│ │ ├── tabs.component.scss
│ │ ├── tabs.component.spec.ts
│ │ └── tabs.component.ts
│ ├── utils
│ │ ├── cookies.ts
│ │ ├── globals.ts
│ │ ├── navigator.d.ts
│ │ ├── taxii2lib.ts
│ │ └── utils.ts
│ └── version-upgrade
│ │ ├── version-upgrade.component.html
│ │ ├── version-upgrade.component.scss
│ │ ├── version-upgrade.component.spec.ts
│ │ └── version-upgrade.component.ts
├── assets
│ ├── config.json
│ ├── icons
│ │ ├── MaterialIcons-Regular.ttf
│ │ ├── baseline-grid_on-24px.svg
│ │ ├── ic_camera_alt_black_24px.svg
│ │ ├── ic_check_box_black_24px.svg
│ │ ├── ic_check_box_outline_blank_black_24px.svg
│ │ ├── ic_clear_black_24px.svg
│ │ ├── ic_clear_gray_24px.svg
│ │ ├── ic_close_black_24px.svg
│ │ ├── ic_color_lens_black_24px.svg
│ │ ├── ic_content_copy_black_24px.svg
│ │ ├── ic_description_black_24px.svg
│ │ ├── ic_done_black_24px.svg
│ │ ├── ic_done_gray_24px.svg
│ │ ├── ic_exportAllExcel_black_24px.svg
│ │ ├── ic_exportAllJson_black_24px.svg
│ │ ├── ic_exportJson_black_24px.svg
│ │ ├── ic_file_download_black_24px.svg
│ │ ├── ic_file_upload_black_24px.svg
│ │ ├── ic_file_upload_gray_24px.svg
│ │ ├── ic_filter_list_black_24px.svg
│ │ ├── ic_format_color_fill_black_24px.svg
│ │ ├── ic_format_color_fill_black_nobottom_24px.svg
│ │ ├── ic_format_color_fill_gray_nobottom_24px.svg
│ │ ├── ic_format_size_black_24px.svg
│ │ ├── ic_insert_chart_black_24px.svg
│ │ ├── ic_insert_chart_gray_24px.svg
│ │ ├── ic_insert_comment_black_24px.svg
│ │ ├── ic_insert_comment_gray_24px.svg
│ │ ├── ic_keyboard_arrow_down_black_24px.svg
│ │ ├── ic_keyboard_arrow_right_black_24px.svg
│ │ ├── ic_keyboard_arrow_up_black_24px.svg
│ │ ├── ic_layers_clear_black_24px.svg
│ │ ├── ic_layers_clear_gray_24px.svg
│ │ ├── ic_link_black_24px.svg
│ │ ├── ic_link_gray_24px.svg
│ │ ├── ic_lock_black_24px.svg
│ │ ├── ic_lock_open_black_24px.svg
│ │ ├── ic_metadata_black_24px.svg
│ │ ├── ic_metadata_gray_24px.svg
│ │ ├── ic_palette_black_24px.svg
│ │ ├── ic_photo_size_select_large_black_24px.svg
│ │ ├── ic_playlist_add_black_24px.svg
│ │ ├── ic_push_pin_black_24px.svg
│ │ ├── ic_push_pin_gray.svg
│ │ ├── ic_remove_circle_black_24px.svg
│ │ ├── ic_save_black_24px.svg
│ │ ├── ic_save_gray_24px.svg
│ │ ├── ic_search_black_24px.svg
│ │ ├── ic_search_gray_24px.svg
│ │ ├── ic_sort_alphabetically_ascending.svg
│ │ ├── ic_sort_alphabetically_ascending_black_24px.svg
│ │ ├── ic_sort_alphabetically_black_24px.svg
│ │ ├── ic_sort_alphabetically_descending.svg
│ │ ├── ic_sort_alphabetically_descending_black_24px.svg
│ │ ├── ic_sort_numerically_ascending.svg
│ │ ├── ic_sort_numerically_ascending_black_24px.svg
│ │ ├── ic_sort_numerically_black_24px.svg
│ │ ├── ic_sort_numerically_descending.svg
│ │ ├── ic_sort_numerically_descending_black_24px.svg
│ │ ├── ic_texture_black_24px.svg
│ │ ├── ic_texture_gray_24px.svg
│ │ ├── ic_unfold_more_alt.svg
│ │ ├── ic_view_large_black_24px.svg
│ │ ├── ic_view_list_black_24px.svg
│ │ ├── ic_view_list_grey_24px.svg
│ │ ├── ic_view_medium_black_24px.svg
│ │ ├── ic_view_small_black_24px.svg
│ │ ├── ic_visibility_black_24px.svg
│ │ ├── ic_visibility_gray_24px.svg
│ │ ├── ic_visibility_off_black_24px.svg
│ │ ├── table_view_FILL0_wght400_GRAD0_opsz24.svg
│ │ ├── unfold_less_black_24px.svg
│ │ ├── unfold_less_gray_24px.svg
│ │ ├── unfold_more_alt_black_24px.svg
│ │ ├── unfold_more_alt_gray_24px.svg
│ │ ├── unfold_more_black_24px.svg
│ │ └── unfold_more_gray_24px.svg
│ └── image_scoreVariableExample.png
├── bootstrap.min.css
├── colors.scss
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.ico
├── favicon.png
├── index.google-analytics.html
├── index.html
├── main.ts
├── polyfills.ts
├── styles.scss
├── test.ts
├── tests
│ └── utils
│ │ ├── mock-data.ts
│ │ └── mock-layers.ts
└── typings.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.spec.json
└── tslint.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | tests/
3 | *.spec.ts
--------------------------------------------------------------------------------
/.github/workflows/create-release.yml:
--------------------------------------------------------------------------------
1 | name: Create Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 | create_github_release:
10 | runs-on: ubuntu-latest
11 | permissions:
12 | contents: write
13 | steps:
14 | - uses: actions/checkout@v4
15 | - uses: ncipollo/release-action@v1
16 | with:
17 | makeLatest: true
18 | name: "attack-navigator ${{github.ref_name}}"
19 | body: "See [the changelog](./CHANGELOG.md) for details about what changed in this release."
--------------------------------------------------------------------------------
/.github/workflows/github-pages.yml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 |
3 | on:
4 | push:
5 | branches: ["master"]
6 |
7 | jobs:
8 | deploy-to-gh-pages:
9 | runs-on: ubuntu-latest
10 | permissions:
11 | contents: write
12 | concurrency:
13 | group: ${{ github.workflow }}-${{ github.ref }}
14 | defaults:
15 | run:
16 | working-directory: ./nav-app/
17 | strategy:
18 | matrix:
19 | node-version: [18.x]
20 |
21 | steps:
22 | - uses: actions/checkout@v4
23 |
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v4
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | cache-dependency-path: nav-app/package-lock.json
30 |
31 | - name: Install Node dependencies
32 | run: npm ci
33 |
34 | - name: Build Navigator
35 | run: npm run build -- --configuration production,googleAnalytics --aot=false --build-optimizer=false --base-href=https://mitre-attack.github.io/attack-navigator/
36 |
37 | - name: Download and extract Navigator v2
38 | uses: robinraju/release-downloader@v1.10
39 | with:
40 | repository: "mitre-attack/attack-navigator"
41 | tag: "v2.3.2"
42 | fileName: "attack-navigator-v2.zip"
43 | extract: true
44 |
45 | - name: Move v2 to output directory
46 | run: mv ../v2/ dist/
47 |
48 | - name: Download and extract Navigator v3
49 | uses: robinraju/release-downloader@v1.10
50 | with:
51 | repository: "mitre-attack/attack-navigator"
52 | tag: "v3.1"
53 | fileName: "attack-navigator-v3.zip"
54 | extract: true
55 |
56 | - name: Move v3 to output directory
57 | run: mv ../v3/ dist/
58 |
59 | - name: Copy redirects
60 | run: cp -r ./redirects/* ./dist/
61 |
62 | - name: Deploy
63 | uses: peaceiris/actions-gh-pages@v3
64 | if: ${{ github.ref == 'refs/heads/master' }}
65 | with:
66 | deploy_key: ${{ secrets.DEPLOY_KEY }}
67 | publish_dir: ./nav-app/dist
68 |
--------------------------------------------------------------------------------
/.github/workflows/run-jasmine-tests.yml:
--------------------------------------------------------------------------------
1 | name: Jasmine Tests
2 |
3 | on:
4 | push:
5 | branches: ["develop"]
6 | pull_request:
7 | branches: ["master", "develop"]
8 | types: [synchronize]
9 |
10 | jobs:
11 | run-tests:
12 | runs-on: ubuntu-latest
13 | defaults:
14 | run:
15 | working-directory: ./nav-app/
16 | strategy:
17 | matrix:
18 | node-version: [18.x]
19 | steps:
20 | - uses: actions/checkout@v4
21 |
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | cache: 'npm'
27 | cache-dependency-path: nav-app/package-lock.json
28 |
29 | - name: Install
30 | run: npm ci
31 |
32 | - name: Run Jasmine tests
33 | run: npm run test -- --code-coverage --no-watch --browsers ChromeHeadlessCI
34 |
35 | - name: Archive code coverage results
36 | uses: actions/upload-artifact@v4
37 | if: always()
38 | with:
39 | name: code-coverage-report
40 | path: nav-app/coverage/chrome/
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
--------------------------------------------------------------------------------
/.markdownlint.yml:
--------------------------------------------------------------------------------
1 | ---
2 | MD013: false
3 | MD024:
4 | siblings_only: true
5 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | Thanks for contributing to `attack-navigator`!
4 |
5 | You are welcome to comment on issues, open new issues, and open pull requests.
6 |
7 | Pull requests should target the **develop** branch of the repository.
8 |
9 | Also, if you contribute any source code, we need you to agree to the following Developer's Certificate of Origin below.
10 |
11 | ## Developer's Certificate of Origin v1.1
12 |
13 | ```text
14 | By making a contribution to this project, I certify that:
15 |
16 | (a) The contribution was created in whole or in part by me and I
17 | have the right to submit it under the open source license
18 | indicated in the file; or
19 |
20 | (b) The contribution is based upon previous work that, to the best
21 | of my knowledge, is covered under an appropriate open source
22 | license and I have the right under that license to submit that
23 | work with modifications, whether created in whole or in part
24 | by me, under the same open source license (unless I am
25 | permitted to submit under a different license), as indicated
26 | in the file; or
27 |
28 | (c) The contribution was provided directly to me by some other
29 | person who certified (a), (b) or (c) and I have not modified
30 | it.
31 |
32 | (d) I understand and agree that this project and the contribution
33 | are public and that a record of the contribution (including all
34 | personal information I submit with it, including my sign-off) is
35 | maintained indefinitely and may be redistributed consistent with
36 | this project or the open source license(s) involved.
37 | ```
38 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Build stage
2 |
3 | FROM node:18
4 |
5 | ENV NODE_OPTIONS=--openssl-legacy-provider
6 |
7 | # install node packages - cache for faster future builds
8 | WORKDIR /src/nav-app
9 | COPY ./nav-app/package*.json ./
10 |
11 | # install packages and build
12 | RUN npm install
13 |
14 | # copy over needed files
15 | COPY ./nav-app/ ./
16 |
17 | # copy layers directory
18 | WORKDIR /src
19 | COPY layers/ ./layers/
20 |
21 | # copy markdown files from root
22 | COPY *.md ./
23 |
24 | WORKDIR /src/nav-app
25 | EXPOSE 4200
26 |
27 | CMD npm start
28 |
--------------------------------------------------------------------------------
/NOTICE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2024 The MITRE Corporation
2 |
3 | Approved for Public Release; Distribution Unlimited. Case Number 18-0128.
4 |
5 | Licensed under the Apache License, Version 2.0 (the "License");
6 | you may not use this file except in compliance with the License.
7 | You may obtain a copy of the License at
8 |
9 | http://www.apache.org/licenses/LICENSE-2.0
10 |
11 | Unless required by applicable law or agreed to in writing, software
12 | distributed under the License is distributed on an "AS IS" BASIS,
13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | See the License for the specific language governing permissions and
15 | limitations under the License.
16 |
17 | This project makes use of ATT&CK®
18 | ATT&CK® Terms of Use - https://attack.mitre.org/resources/terms-of-use/
19 |
--------------------------------------------------------------------------------
/layers/README.md:
--------------------------------------------------------------------------------
1 | # ATT&CK Navigator Layers
2 |
3 | A layer constitutes a set of annotations on the ATT&CK matrix for a specific technology domain. Layers can also store a default configuration of the view such as sorting, visible platforms, and more. The ATT&CK Navigator includes functionalities for exporting annotations into layer files, as well as the ability to import layer files for viewing.
4 |
5 | See the latest [layer format specification](spec/v4.5/layerformat.md) for more information about Layer files.
6 |
7 | ## Sample Layers
8 |
9 | This repository includes a couple of [sample layers](samples/) demonstrating example use cases of layers and the ATT&CK Navigator. The scripts used to generate these layer files can be found in the [mitreattack-python repository](https://github.com/mitre-attack/attack-scripts/tree/master/scripts/layers/samples). These scripts may serve as examples on how to access and work with [ATT&CK data](https://github.com/mitre/cti).
10 |
--------------------------------------------------------------------------------
/nav-app/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | max_line_length = off
13 | trim_trailing_whitespace = false
14 |
--------------------------------------------------------------------------------
/nav-app/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 |
8 | # dependencies
9 | /node_modules
10 |
11 | # IDEs and editors
12 | /.idea
13 | .project
14 | .classpath
15 | .c9/
16 | *.launch
17 | .settings/
18 | *.sublime-workspace
19 |
20 | # IDE - VSCode
21 | .vscode/*
22 | !.vscode/settings.json
23 | !.vscode/tasks.json
24 | !.vscode/launch.json
25 | !.vscode/extensions.json
26 |
27 | # misc
28 | /.sass-cache
29 | /connect.lock
30 | /coverage
31 | /libpeerconnection.log
32 | npm-debug.log
33 | testem.log
34 | /typings
35 | /.angular/cache
36 |
37 | # e2e
38 | /e2e/*.js
39 | /e2e/*.map
40 |
41 | # System Files
42 | .DS_Store
43 | Thumbs.db
44 |
--------------------------------------------------------------------------------
/nav-app/.prettierignore:
--------------------------------------------------------------------------------
1 | # Ignore artifacts:
2 | build
3 | coverage
4 | dist
5 | node_modules
6 | assets
7 | .angular
8 | e2e
--------------------------------------------------------------------------------
/nav-app/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true,
6 | "printWidth": 150,
7 | "bracketSpacing": true,
8 | "bracketSameLine": true,
9 | "htmlWhitespaceSensitivity": "ignore"
10 | }
11 |
--------------------------------------------------------------------------------
/nav-app/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('nav-app App', () => {
4 | let page: AppPage;
5 |
6 | beforeEach(() => {
7 | page = new AppPage();
8 | });
9 |
10 | it('should display welcome message', () => {
11 | page.navigateTo();
12 | expect(page.getParagraphText()).toEqual('Welcome to app!');
13 | });
14 | });
15 |
--------------------------------------------------------------------------------
/nav-app/e2e/app.po.ts:
--------------------------------------------------------------------------------
1 | import { browser, by, element } from 'protractor';
2 |
3 | export class AppPage {
4 | navigateTo() {
5 | return browser.get('/');
6 | }
7 |
8 | getParagraphText() {
9 | return element(by.css('app-root h1')).getText();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/nav-app/e2e/tsconfig.e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/e2e",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "jasminewd2",
11 | "node"
12 | ]
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/nav-app/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration file, see link for more information
2 | // https://karma-runner.github.io/1.0/config/configuration-file.html
3 |
4 | module.exports = function (config) {
5 | config.set({
6 | basePath: '',
7 | frameworks: ['jasmine', '@angular-devkit/build-angular'],
8 | plugins: [
9 | require('@angular-devkit/build-angular/plugins/karma'),
10 | require('karma-chrome-launcher'),
11 | require('karma-coverage'),
12 | // require('karma-coverage-istanbul-reporter'),
13 | require('karma-jasmine'),
14 | require('karma-jasmine-html-reporter'),
15 | ],
16 | client: {
17 | clearContext: false, // leave Jasmine Spec Runner output visible in browser
18 | },
19 | coverageIstanbulReporter: {
20 | dir: require('path').join(__dirname, 'coverage'),
21 | reports: ['html', 'lcovonly'],
22 | fixWebpackSourcePaths: true,
23 | },
24 |
25 | reporters: ['progress', 'kjhtml', 'coverage'],
26 | port: 9876,
27 | colors: true,
28 | logLevel: config.LOG_INFO,
29 | autoWatch: true,
30 | browsers: ['Chrome', 'ChromeHeadless', 'ChromeHeadlessCI'],
31 | customLaunchers: {
32 | ChromeHeadlessCI: {
33 | base: 'ChromeHeadless',
34 | flags: ['--no-sandbox'],
35 | },
36 | },
37 | singleRun: false,
38 | webpack: { node: { fs: 'empty' } }, // https://github.com/angular/angular-cli/issues/8357
39 |
40 | // For code coverage
41 | files: ['src/**/*.ts'],
42 | preprocessors: {
43 | 'src/**/*.js': ['coverage'],
44 | },
45 | coverageReporter: {
46 | type: 'html',
47 | dir: 'coverage/',
48 | subdir: 'chrome',
49 | file: 'index.html',
50 | },
51 | });
52 | };
53 |
--------------------------------------------------------------------------------
/nav-app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "attack-navigator",
3 | "description": "Web app that provides basic navigation and annotation of ATT&CK matrices",
4 | "repository": {
5 | "type": "git",
6 | "url": "https://github.com/mitre-attack/attack-navigator.git"
7 | },
8 | "version": "5.1.0",
9 | "license": "Apache-2.0",
10 | "scripts": {
11 | "ng": "ng",
12 | "start": "ng serve --host 0.0.0.0",
13 | "build": "ng build",
14 | "test": "ng test",
15 | "lint": "ng lint",
16 | "e2e": "ng e2e"
17 | },
18 | "dependencies": {
19 | "@angular/animations": "^17.3.10",
20 | "@angular/cdk": "^17.3.10",
21 | "@angular/common": "^17.3.10",
22 | "@angular/compiler": "^17.3.10",
23 | "@angular/core": "^17.3.10",
24 | "@angular/forms": "^17.3.10",
25 | "@angular/material": "^17.3.10",
26 | "@angular/platform-browser": "^17.3.10",
27 | "@angular/platform-browser-dynamic": "^17.3.10",
28 | "@angular/router": "^17.3.10",
29 | "@babel/runtime": "^7.22.6",
30 | "@fontsource/roboto": "^5.0.7",
31 | "@fontsource/roboto-mono": "^5.0.7",
32 | "@types/file-saver": "^2.0.1",
33 | "classlist.js": "^1.1.20150312",
34 | "core-js": "^3.31.1",
35 | "d3": "^7.8.5",
36 | "d3-svg-legend": "^2.25.6",
37 | "detect-browser": "^5.3.0",
38 | "file-saver": "^2.0.5",
39 | "load-json-file": "^7.0.1",
40 | "mathjs": "^12.4.2",
41 | "ngx-color-picker": "^16.0.0",
42 | "ngx-drag-drop": "^17.0.0",
43 | "ngx-markdown": "^17.2.1",
44 | "rxjs": "^7.8.1",
45 | "rxjs-compat": "^6.6.7",
46 | "tinycolor2": "^1.6.0",
47 | "tinygradient": "^1.1.5",
48 | "tslib": "^2.5.0",
49 | "zone.js": "~0.14.6"
50 | },
51 | "devDependencies": {
52 | "@angular-builders/custom-webpack": "^17.0.2",
53 | "@angular-devkit/build-angular": "^17.3.8",
54 | "@angular/cli": "^17.3.8",
55 | "@angular/compiler-cli": "^17.3.10",
56 | "@angular/language-service": "^17.3.10",
57 | "@types/jasmine": "^5.1.4",
58 | "@types/jasminewd2": "^2.0.8",
59 | "@types/node": "^18.15.12",
60 | "codelyzer": "^6.0.0",
61 | "exceljs": "^4.3.0",
62 | "jasmine-core": "^5.1.0",
63 | "jasmine-spec-reporter": "~7.0.0",
64 | "karma": "~6.4.1",
65 | "karma-chrome-launcher": "^3.2.0",
66 | "karma-cli": "~2.0.0",
67 | "karma-coverage": "^2.2.0",
68 | "karma-coverage-istanbul-reporter": "~3.0.2",
69 | "karma-jasmine": "^5.1.0",
70 | "karma-jasmine-html-reporter": "^2.1.0",
71 | "marked": "^12.0.2",
72 | "prettier": "^3.2.5",
73 | "ts-node": "^10.9.1",
74 | "tslint": "~6.1.0",
75 | "typescript": "^5.4.5"
76 | },
77 | "optionalDependencies": {
78 | "fsevents": "^2.3.2"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/nav-app/protractor.conf.js:
--------------------------------------------------------------------------------
1 | // Protractor configuration file, see link for more information
2 | // https://github.com/angular/protractor/blob/master/lib/config.ts
3 |
4 | const { SpecReporter } = require('jasmine-spec-reporter');
5 |
6 | exports.config = {
7 | allScriptsTimeout: 11000,
8 | specs: ['./e2e/**/*.e2e-spec.ts'],
9 | capabilities: {
10 | browserName: 'chrome',
11 | },
12 | directConnect: true,
13 | baseUrl: 'http://localhost:4200/',
14 | framework: 'jasmine',
15 | jasmineNodeOpts: {
16 | showColors: true,
17 | defaultTimeoutInterval: 30000,
18 | print: function () {},
19 | },
20 | onPrepare() {
21 | require('ts-node').register({
22 | project: 'e2e/tsconfig.e2e.json',
23 | });
24 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/nav-app/redirects/enterprise/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 | ATT&CK® Navigator
16 |
17 |
18 |
23 |
24 |
36 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/nav-app/redirects/mobile/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 | ATT&CK® Navigator
16 |
17 |
18 |
23 |
24 |
36 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/nav-app/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/nav-app/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 | @import '../colors.scss';
2 |
3 | .app-container {
4 | @include adaptive-color('background-color', color(body), color(dark-1));
5 | height: 100vh;
6 | }
7 |
8 | .nav-app {
9 | @include adaptive-color('background-color', color(light), darken(color(dark), 8%));
10 |
11 | tabs {
12 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
13 | font-size: 9pt;
14 | }
15 |
16 | .version-footer {
17 | @include adaptive-color('color', on-color(light), on-color(dark-2));
18 | font-size: 7pt;
19 | border: none;
20 | background-color: transparent;
21 | text-align: right;
22 | margin-left: 16px;
23 | padding: 0;
24 | display: block;
25 | position: fixed;
26 | bottom: 16px;
27 | &:hover {
28 | text-decoration: underline;
29 | cursor: pointer;
30 | }
31 | @media print {
32 | display: none;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/nav-app/src/app/app.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { AppComponent } from './app.component';
3 | import { HttpClientTestingModule } from '@angular/common/http/testing';
4 | import { deleteCookie, getCookie, hasCookie, setCookie } from './utils/cookies';
5 | import { TabsComponent } from './tabs/tabs.component';
6 | import { MatDialogModule } from '@angular/material/dialog';
7 | import { MatSnackBarModule } from '@angular/material/snack-bar';
8 | import { ConfigService } from './services/config.service';
9 | import { MatTabsModule } from '@angular/material/tabs';
10 |
11 | describe('AppComponent', () => {
12 | let fixture: ComponentFixture;
13 | let app: any;
14 |
15 | beforeEach(waitForAsync(() => {
16 | TestBed.configureTestingModule({
17 | imports: [HttpClientTestingModule, MatDialogModule, MatSnackBarModule, MatTabsModule],
18 | declarations: [AppComponent, TabsComponent],
19 | }).compileComponents();
20 |
21 | // set up config service
22 | let configService = TestBed.inject(ConfigService);
23 | configService.defaultLayers = { enabled: false };
24 |
25 | fixture = TestBed.createComponent(AppComponent);
26 | app = fixture.debugElement.componentInstance;
27 | }));
28 |
29 | it('should create the app', waitForAsync(() => {
30 | expect(app).toBeTruthy();
31 | }));
32 |
33 | it('should intialize title', waitForAsync(() => {
34 | app.ngOnInit();
35 | let result = app.titleService.getTitle();
36 | expect(result).toEqual(app.title);
37 | }));
38 |
39 | it(`should have title 'ATT&CK® Navigator'`, waitForAsync(() => {
40 | expect(app.title).toEqual('ATT&CK® Navigator');
41 | }));
42 |
43 | it('should set user theme to theme-override-dark', waitForAsync(() => {
44 | setCookie('is_user_theme_dark', 'true', 1);
45 | // recreate component
46 | fixture = TestBed.createComponent(AppComponent);
47 | app = fixture.componentInstance;
48 | expect(app.user_theme).toEqual('theme-override-dark');
49 | }));
50 |
51 | it('should set user theme to theme-override-light', waitForAsync(() => {
52 | setCookie('is_user_theme_dark', 'false', 1);
53 | // recreate component
54 | fixture = TestBed.createComponent(AppComponent);
55 | app = fixture.componentInstance;
56 | expect(app.user_theme).toEqual('theme-override-light');
57 | }));
58 |
59 | it('should set user theme to theme-use-system', waitForAsync(() => {
60 | deleteCookie('is_user_theme_dark');
61 | // recreate component
62 | fixture = TestBed.createComponent(AppComponent);
63 | app = fixture.componentInstance;
64 | expect(app.user_theme).toEqual('theme-use-system');
65 | }));
66 |
67 | it('should handle dark theme change', waitForAsync(() => {
68 | app.themeChangeHandler('dark');
69 | expect(app.user_theme).toEqual('theme-override-dark');
70 | expect(hasCookie('is_user_theme_dark')).toBeTrue();
71 | expect(getCookie('is_user_theme_dark')).toEqual('true');
72 | }));
73 |
74 | it('should handle light theme change', waitForAsync(() => {
75 | app.themeChangeHandler('light');
76 | expect(app.user_theme).toEqual('theme-override-light');
77 | expect(hasCookie('is_user_theme_dark')).toBeTrue();
78 | expect(getCookie('is_user_theme_dark')).toEqual('false');
79 | }));
80 |
81 | it('should handle system theme change', waitForAsync(() => {
82 | setCookie('is_user_theme_dark', 'true', 1);
83 |
84 | app.themeChangeHandler('system');
85 | expect(app.user_theme).toEqual('theme-use-system');
86 | expect(hasCookie('is_user_theme_dark')).toBeFalse();
87 | }));
88 |
89 | it('should prompt to navigate away', waitForAsync(() => {
90 | app.configService.setFeature('leave_site_dialog', true);
91 | let prompt = 'Are you sure you want to navigate away? Your data may be lost!';
92 | let event$ = { returnValue: null };
93 | app.promptNavAway(event$);
94 | expect(event$.returnValue).toEqual(prompt);
95 | }));
96 |
97 | it('should not prompt to navigate away', waitForAsync(() => {
98 | app.configService.setFeature('leave_site_dialog', false);
99 | let event$ = { returnValue: null };
100 | app.promptNavAway(event$);
101 | expect(event$.returnValue).toEqual(null);
102 | }));
103 | });
104 |
--------------------------------------------------------------------------------
/nav-app/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild, HostListener, OnInit } from '@angular/core';
2 | import { Title } from '@angular/platform-browser';
3 | import { TabsComponent } from './tabs/tabs.component';
4 | import { ConfigService } from './services/config.service';
5 | import * as globals from './utils/globals';
6 | import { IconsService } from './services/icons.service';
7 | import { deleteCookie, getCookie, hasCookie, setCookie } from './utils/cookies';
8 |
9 | @Component({
10 | selector: 'app-root',
11 | templateUrl: './app.component.html',
12 | styleUrls: ['./app.component.scss'],
13 | })
14 | export class AppComponent implements OnInit {
15 | @ViewChild(TabsComponent) tabsComponent;
16 |
17 | navVersion: string = globals.navVersion;
18 | public user_theme: string;
19 | title = 'ATT&CK® Navigator';
20 |
21 | @HostListener('window:beforeunload', ['$event'])
22 | promptNavAway($event) {
23 | if (!this.configService.getFeature('leave_site_dialog')) return;
24 | //this text only shows in the data, not visible to user as far as I can tell
25 | //however, if it's not included the window doesn't open.
26 | $event.returnValue = 'Are you sure you want to navigate away? Your data may be lost!';
27 | }
28 |
29 | constructor(
30 | public configService: ConfigService,
31 | private iconsService: IconsService,
32 | public titleService: Title
33 | ) {
34 | Array.prototype.includes = function (value): boolean {
35 | for (let i = 0; i < this.length; i++) {
36 | if (this[i] === value) return true;
37 | }
38 | return false;
39 | };
40 | if (hasCookie('is_user_theme_dark') && getCookie('is_user_theme_dark') === 'true') {
41 | this.user_theme = 'theme-override-dark';
42 | } else if (getCookie('is_user_theme_dark') === 'false') {
43 | this.user_theme = 'theme-override-light';
44 | } else {
45 | this.user_theme = 'theme-use-system';
46 | }
47 | }
48 |
49 | ngOnInit() {
50 | this.iconsService.registerIcons();
51 | this.titleService.setTitle(this.title);
52 | }
53 |
54 | themeChangeHandler(theme: string) {
55 | if (theme === 'system') {
56 | if (hasCookie('is_user_theme_dark')) deleteCookie('is_user_theme_dark');
57 | this.user_theme = 'theme-use-system';
58 | } else {
59 | this.user_theme = theme === 'dark' ? 'theme-override-dark' : 'theme-override-light';
60 | setCookie('is_user_theme_dark', theme === 'dark' ? 'true' : 'false', 180);
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/nav-app/src/app/changelog/changelog.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Changelog
3 |
4 |
5 |
6 |
7 | close
8 |
9 |
--------------------------------------------------------------------------------
/nav-app/src/app/changelog/changelog.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from "@angular/core/testing";
2 | import { ChangelogComponent } from "./changelog.component"
3 | import { MarkdownModule, MarkdownService } from "ngx-markdown";
4 | import { MAT_DIALOG_DATA, MatDialogModule, MatDialogRef } from "@angular/material/dialog";
5 | import { NO_ERRORS_SCHEMA } from "@angular/core";
6 | import { HttpClient, HttpClientModule } from "@angular/common/http";
7 |
8 | describe('ChangelogComponent', () => {
9 | let component: ChangelogComponent;
10 | let fixture: ComponentFixture;
11 | let markdownService: MarkdownService;
12 |
13 | beforeEach(async () => {
14 | await TestBed.configureTestingModule({
15 | declarations: [ChangelogComponent],
16 | imports: [
17 | MatDialogModule,
18 | MarkdownModule.forRoot({ loader: HttpClient }),
19 | HttpClientModule
20 | ],
21 | providers: [
22 | {provide: MAT_DIALOG_DATA, useValue: {someData: 'test data'}},
23 | {provide: MatDialogRef, useValue: {}},
24 | MarkdownService
25 | ],
26 | schemas: [NO_ERRORS_SCHEMA]
27 | }).compileComponents();
28 |
29 | fixture = TestBed.createComponent(ChangelogComponent);
30 | component = fixture.componentInstance;
31 | markdownService = TestBed.inject(MarkdownService);
32 | fixture.detectChanges();
33 | });
34 |
35 | it('should create', () => {
36 | expect(component).toBeTruthy();
37 | });
38 |
39 | it('should inject MAT_DIALOG_DATA', () => {
40 | expect(component.data).toEqual({someData: 'test data'});
41 | });
42 |
43 | it('should inject MarkdownService', () => {
44 | expect(markdownService).toBeTruthy();
45 | });
46 |
47 | it('should inject MatDialog', () => {
48 | expect(component['dialog']).toBeTruthy();
49 | });
50 | })
--------------------------------------------------------------------------------
/nav-app/src/app/changelog/changelog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, ViewChild } from '@angular/core';
2 | import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
3 | import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
4 |
5 | @Component({
6 | selector: 'app-changelog',
7 | templateUrl: './changelog.component.html',
8 | })
9 | export class ChangelogComponent {
10 | @ViewChild('markdownElement', { static: false }) public markdownElement: any;
11 |
12 | constructor(
13 | private dialog: MatDialog,
14 | private markdownService: MarkdownService,
15 | @Inject(MAT_DIALOG_DATA) public data
16 | ) {
17 | // intentionally left blank
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/context-menu-item.ts:
--------------------------------------------------------------------------------
1 | import { Tactic } from './stix/tactic';
2 | import { Technique } from './stix/technique';
3 |
4 | export class ContextMenuItem {
5 | public readonly label: string;
6 | private readonly url: string;
7 | private readonly subtechnique_url: string;
8 |
9 | constructor(label, url, subtechnique_url = null) {
10 | this.label = label;
11 | this.url = url;
12 | this.subtechnique_url = subtechnique_url;
13 | }
14 |
15 | public getReplacedURL(technique: Technique, tactic: Tactic): string {
16 | if (this.subtechnique_url && technique.isSubtechnique) {
17 | return this.subtechnique_url
18 | .replace(/{{parent_technique_attackID}}/g, technique.parent.attackID)
19 | .replace(/{{parent_technique_stixID}}/g, technique.parent.id)
20 | .replace(/{{parent_technique_name}}/g, technique.parent.name.replace(/ /g, '-').toLowerCase())
21 |
22 | .replace(/{{subtechnique_attackID}}/g, technique.attackID)
23 | .replace(/{{subtechnique_attackID_suffix}}/g, technique.attackID.split('.')[1])
24 | .replace(/{{subtechnique_stixID}}/g, technique.id)
25 | .replace(/{{subtechnique_name}}/g, technique.name.replace(/ /g, '-').toLowerCase())
26 |
27 | .replace(/{{tactic_attackID}}/g, tactic.attackID)
28 | .replace(/{{tactic_stixID}}/g, tactic.id)
29 | .replace(/{{tactic_name}}/g, tactic.shortname);
30 | } else {
31 | return this.url
32 | .replace(/{{technique_attackID}}/g, technique.attackID)
33 | .replace(/{{technique_stixID}}/g, technique.id)
34 | .replace(/{{technique_name}}/g, technique.name.replace(/ /g, '-').toLowerCase())
35 |
36 | .replace(/{{tactic_attackID}}/g, tactic.attackID)
37 | .replace(/{{tactic_stixID}}/g, tactic.id)
38 | .replace(/{{tactic_name}}/g, tactic.shortname);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/domain.ts:
--------------------------------------------------------------------------------
1 | import { ServiceAuth } from '../services/data.service';
2 | import { Campaign } from './stix/campaign';
3 | import { DataComponent } from './stix/data-component';
4 | import { Group } from './stix/group';
5 | import { Matrix } from './stix/matrix';
6 | import { Mitigation } from './stix/mitigation';
7 | import { Note } from './stix/note';
8 | import { Software } from './stix/software';
9 | import { Tactic } from './stix/tactic';
10 | import { Technique } from './stix/technique';
11 | import { Version } from './version';
12 | import { Asset } from './stix/asset';
13 |
14 | export class Domain {
15 | public readonly id: string; // domain ID
16 | public readonly domain_identifier: string; //domain ID without the version suffix
17 | public readonly name: string; // domain display name
18 | public readonly version: Version; // ATT&CK version
19 |
20 | public urls: string[] = [];
21 | public taxii_url: string = '';
22 | public taxii_collection: string = '';
23 | public authentication: ServiceAuth;
24 | public dataLoaded: boolean = false;
25 | public dataLoadedCallbacks: any[] = [];
26 |
27 | // this should only be enabled if the user loads custom data via URL
28 | public isCustom: boolean = false;
29 |
30 | public matrices: Matrix[] = [];
31 | public get tactics(): Tactic[] {
32 | let tactics = [];
33 | for (let matrix of this.matrices) {
34 | tactics = tactics.concat(matrix.tactics);
35 | }
36 | return tactics;
37 | }
38 | public techniques: Technique[] = [];
39 | public platforms: string[] = []; // platforms defined on techniques and software of the domain
40 | public subtechniques: Technique[] = [];
41 | public software: Software[] = [];
42 | public campaigns: Campaign[] = [];
43 | public assets: Asset[] = [];
44 | public dataComponents: DataComponent[] = [];
45 | public dataSources = new Map(); // Map data source ID to name and urls to be used by data components
46 | public groups: Group[] = [];
47 | public mitigations: Mitigation[] = [];
48 | public notes: Note[] = [];
49 | public relationships: any = {
50 | // subtechnique subtechnique-of technique
51 | // ID of technique to [] of subtechnique IDs
52 | subtechniques_of: new Map(),
53 | // data component related to technique
54 | // ID of data component to [] of technique IDs
55 | component_rel: new Map(),
56 | // group uses technique
57 | // ID of group to [] of technique IDs
58 | group_uses: new Map(),
59 | // software uses technique
60 | // ID of software to [] of technique IDs
61 | software_uses: new Map(),
62 | // campaign uses technique
63 | // ID of campaign to [] of technique IDs
64 | campaign_uses: new Map(),
65 | // campaigns attributed to group
66 | // ID of group to [] of campaign IDs
67 | campaigns_attributed_to: new Map(),
68 | // mitigation mitigates technique
69 | // ID of mitigation to [] of technique IDs
70 | mitigates: new Map(),
71 | // object is revoked-by object
72 | // ID of object to ID of revoking object
73 | revoked_by: new Map(),
74 | // technique targets asset
75 | // ID of asset to [] of technique IDs
76 | targeted_assets: new Map(),
77 | };
78 |
79 | constructor(domain_identifier: string, name: string, version: Version, urls?: string[]) {
80 | this.id = `${domain_identifier}-${version.number}`;
81 | this.domain_identifier = domain_identifier;
82 | this.name = name;
83 | this.version = version;
84 | if (urls) this.urls = urls;
85 | }
86 |
87 | /**
88 | * Get the version number for this domain
89 | */
90 | public getVersion(): string {
91 | return this.version.number;
92 | }
93 |
94 | public executeCallbacks(): void {
95 | for (let callback of this.dataLoadedCallbacks) {
96 | callback();
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/filter.ts:
--------------------------------------------------------------------------------
1 | import { Domain } from './domain';
2 |
3 | export class Filter {
4 | // the data for a specific filter
5 | private readonly domain: string;
6 | public platforms: {
7 | options: string[];
8 | selection: string[];
9 | };
10 |
11 | constructor() {
12 | this.platforms = {
13 | selection: [],
14 | options: [],
15 | };
16 | }
17 |
18 | /**
19 | * Initialize the platform options according to the data in the domain
20 | * @param {Domain} domain the domain to parse for platform options
21 | */
22 | public initPlatformOptions(domain: Domain): void {
23 | this.platforms.options = JSON.parse(JSON.stringify(domain.platforms));
24 | if (!this.platforms.selection.length) {
25 | // prevent overwriting current selection
26 | this.platforms.selection = JSON.parse(JSON.stringify(domain.platforms));
27 | }
28 | }
29 |
30 | /**
31 | * toggle the given value in the given filter
32 | * @param {*} filterName the name of the filter
33 | * @param {*} value the value to toggle
34 | */
35 | public toggleInFilter(filterName: string, value: string): void {
36 | if (!this[filterName].options.includes(value)) {
37 | console.error('not a valid option to toggle', value, this[filterName]);
38 | return;
39 | }
40 | if (this[filterName].selection.includes(value)) {
41 | let index = this[filterName].selection.indexOf(value);
42 | this[filterName].selection.splice(index, 1);
43 | } else {
44 | this[filterName].selection.push(value);
45 | }
46 | }
47 |
48 | /**
49 | * determine if the given value is active in the filter
50 | * @param {*} filterName the name of the filter
51 | * @param {*} value the value to determine
52 | * @returns {boolean} true if value is currently enabled in the filter
53 | */
54 | public inFilter(filterName, value): boolean {
55 | return this[filterName].selection.includes(value);
56 | }
57 |
58 | /**
59 | * Return the string representation of this filter
60 | * @return stringified filter
61 | */
62 | public serialize(): string {
63 | return JSON.stringify({ platforms: this.platforms.selection });
64 | }
65 |
66 | /**
67 | * Replace the properties of this object with those of the given serialized filter
68 | * @param rep filter object
69 | */
70 | public deserialize(rep: any): void {
71 | let isStringArray = function (arr): boolean {
72 | for (let item of arr) {
73 | if (typeof item !== 'string') {
74 | console.error('TypeError:', item, '(', typeof item, ')', 'is not a string');
75 | return false;
76 | }
77 | }
78 | return true;
79 | };
80 |
81 | if (rep.platforms) {
82 | if (isStringArray(rep.platforms)) {
83 | let backwards_compatibility_mappings = {
84 | //backwards compatibility with older layers
85 | android: 'Android',
86 | ios: 'iOS',
87 |
88 | windows: 'Windows',
89 | linux: 'Linux',
90 | mac: 'macOS',
91 |
92 | AWS: 'IaaS',
93 | GCP: 'IaaS',
94 | Azure: 'IaaS',
95 | };
96 | const selection = new Set();
97 | rep.platforms.forEach(function (platform) {
98 | if (platform in backwards_compatibility_mappings) selection.add(backwards_compatibility_mappings[platform]);
99 | else selection.add(platform);
100 | });
101 | this.platforms.selection = Array.from(selection);
102 | } else console.error('TypeError: filter platforms field is not a string[]');
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/index.ts:
--------------------------------------------------------------------------------
1 | export { VersionChangelog } from './version-changelog';
2 | export { Version } from './version';
3 | export { Domain } from './domain';
4 | export { ContextMenuItem } from './context-menu-item';
5 | export { Gradient, Gcolor } from './gradient';
6 | export { Filter } from './filter';
7 | export { LayoutOptions } from './layout-options';
8 | export { Link } from './link';
9 | export { Metadata } from './metadata';
10 | export { TechniqueVM } from './technique-vm';
11 | export { ViewModel } from './view-model';
12 | export { Tab } from './tab';
13 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/link.ts:
--------------------------------------------------------------------------------
1 | export class Link {
2 | public label: string;
3 | public url: string;
4 | public divider: boolean;
5 |
6 | constructor() {
7 | // intentionally left blank
8 | }
9 |
10 | public serialize(): object {
11 | return this.label && this.url ? { label: this.label, url: this.url } : { divider: this.divider };
12 | }
13 |
14 | public deserialize(rep: any): void {
15 | let obj = typeof rep == 'string' ? JSON.parse(rep) : rep;
16 | if ('url' in obj) {
17 | // label & url object
18 | if (typeof obj.url === 'string') this.url = obj.url;
19 | else console.error("TypeError: Link field 'url' is not a string");
20 |
21 | if ('label' in obj) {
22 | if (typeof obj.label === 'string') this.label = obj.label;
23 | else console.error("TypeError: Link field 'label' is not a string");
24 | } else console.error("Error: Link required field 'label' not present");
25 | } else if ('divider' in obj) {
26 | // divider object
27 | if (typeof obj.divider === 'boolean') this.divider = obj.divider;
28 | else console.error("TypeError: Link field 'divider' is not a boolean");
29 | } else console.error("Error: Link required field 'url' or 'divider' not present");
30 | }
31 |
32 | public valid(): boolean {
33 | return (this.label && this.label.length > 0 && this.url && this.url.length > 0) || this.divider !== undefined;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/metadata.ts:
--------------------------------------------------------------------------------
1 | export class Metadata {
2 | public name: string;
3 | public value: string;
4 | public divider: boolean;
5 |
6 | public serialize(): object {
7 | return this.name && this.value ? { name: this.name, value: this.value } : { divider: this.divider };
8 | }
9 |
10 | public deserialize(rep: any): void {
11 | let obj = typeof rep == 'string' ? JSON.parse(rep) : rep;
12 |
13 | if ('name' in obj) {
14 | // name & value object
15 | if (typeof obj.name === 'string') this.name = obj.name;
16 | else console.error("TypeError: Metadata field 'name' is not a string");
17 |
18 | if ('value' in obj) {
19 | if (typeof obj.value === 'string') this.value = obj.value;
20 | else console.error("TypeError: Metadata field 'value' is not a string");
21 | } else console.error("Error: Metadata required field 'value' not present");
22 | } else if ('divider' in obj) {
23 | // divider object
24 | if (typeof obj.divider === 'boolean') this.divider = obj.divider;
25 | else console.error("TypeError: Metadata field 'divider' is not a boolean");
26 | } else console.error("Error: Metadata required field 'name' or 'divider' not present");
27 | }
28 |
29 | public valid(): boolean {
30 | return (this.name && this.name.length > 0 && this.value && this.value.length > 0) || this.divider !== undefined;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/asset.ts:
--------------------------------------------------------------------------------
1 | import { StixObject } from './stix-object';
2 |
3 | export class Asset extends StixObject {
4 | /**
5 | * Get techniques targeting this asset
6 | * @returns {string[]} technique IDs targeting this asset
7 | */
8 | public targeted(domainVersionID): string[] {
9 | let rels = this.dataService.getDomain(domainVersionID).relationships.targeted_assets;
10 | if (rels.has(this.id)) {
11 | return rels.get(this.id);
12 | } else {
13 | return [];
14 | }
15 | }
16 |
17 | /**
18 | * Get all techniques related to the asset
19 | */
20 | public relatedTechniques(domainVersionID): string[] {
21 | return this.targeted(domainVersionID);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/campaign.ts:
--------------------------------------------------------------------------------
1 | import { StixObject } from './stix-object';
2 |
3 | export class Campaign extends StixObject {
4 | /**
5 | * Get techniques used by the campaign
6 | * @param domainVersionID the ID of the domain and version
7 | * @returns {string[]} technique IDs used by the campaign
8 | */
9 | public used(domainVersionID): string[] {
10 | let relationships = this.dataService.getDomain(domainVersionID).relationships.campaign_uses;
11 | if (relationships.has(this.id)) {
12 | return relationships.get(this.id);
13 | } else {
14 | return [];
15 | }
16 | }
17 |
18 | /**
19 | * Get all techniques related to the campaign
20 | * @param domainVersionID the ID of the domain and version
21 | * @returns {string[]} technique IDs used by the campaign
22 | */
23 | public relatedTechniques(domainVersionID): string[] {
24 | return this.used(domainVersionID);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/data-component.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 | import { StixObject } from './stix-object';
3 | import { Technique } from './technique';
4 |
5 | export class DataComponent extends StixObject {
6 | public readonly url: string;
7 | public readonly dataSource: string;
8 |
9 | constructor(stixSDO: any, dataService: DataService) {
10 | super(stixSDO, dataService, false);
11 | this.dataSource = stixSDO.x_mitre_data_source_ref;
12 | }
13 |
14 | /**
15 | * Get techniques related to the data component
16 | * @param domainVersionID the ID of the domain and version
17 | * @returns {Technique[]} list of techniques used by the data component
18 | */
19 | public techniques(domainVersionID): Technique[] {
20 | const techniques = [];
21 | const domain = this.dataService.getDomain(domainVersionID);
22 |
23 | let relationships = domain.relationships.component_rel;
24 | if (relationships.has(this.id)) {
25 | relationships.get(this.id).forEach((targetID) => {
26 | const technique = domain.techniques.find((t) => t.id === targetID);
27 | if (technique) techniques.push(technique);
28 | });
29 | }
30 | return techniques;
31 | }
32 | /**
33 | * Get the data source related to this data component
34 | * @param domainVersionID the ID of the domain and version
35 | * @returns { name: string, url: string } the name and first url of the data source referenced by this data component
36 | */
37 | public source(domainVersionID) {
38 | const dataSources = this.dataService.getDomain(domainVersionID).dataSources;
39 | if (dataSources.has(this.dataSource)) {
40 | const source = dataSources.get(this.dataSource);
41 | let url = '';
42 | if (source.external_references && source.external_references[0] && source.external_references[0].url)
43 | url = source.external_references[0].url;
44 | return { name: source.name, url: url };
45 | } else return { name: '', url: '' };
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/group.ts:
--------------------------------------------------------------------------------
1 | import { StixObject } from './stix-object';
2 |
3 | export class Group extends StixObject {
4 | /**
5 | * Get the techniques used by this group
6 | * @param domainVersionID the ID of the domain and version
7 | * @returns {string[]} technique IDs used by this group
8 | */
9 | public used(domainVersionID): string[] {
10 | let rels = this.dataService.getDomain(domainVersionID).relationships.group_uses;
11 | if (rels.has(this.id)) {
12 | return rels.get(this.id);
13 | } else {
14 | return [];
15 | }
16 | }
17 |
18 | /**
19 | * Get techniques used by campaigns attributed to this group
20 | * @param domainVersionID the ID of the domain and version
21 | * @returns {string[]} technique IDs used by campaigns attributed to this group
22 | */
23 | public campaignsUsed(domainVersionID): string[] {
24 | // get campaigns attributed to groups
25 | let attributedCampaigns = this.dataService.getDomain(domainVersionID).relationships.campaigns_attributed_to;
26 | // get techniques used by campaigns
27 | let rels = this.dataService.getDomain(domainVersionID).relationships.campaign_uses;
28 | if (attributedCampaigns.has(this.id)) {
29 | // get set of techniques used by attributed campaigns
30 | let techniques = [];
31 | attributedCampaigns.get(this.id).forEach((campaign_id) => {
32 | if (rels.has(campaign_id)) techniques = techniques.concat(rels.get(campaign_id));
33 | });
34 | return techniques;
35 | } else return []; // no attributed campaigns
36 | }
37 |
38 | /**
39 | * Get all techniques related to the group
40 | */
41 | public relatedTechniques(domainVersionID): string[] {
42 | let usedSet = new Set(this.used(domainVersionID).concat(this.campaignsUsed(domainVersionID)));
43 | return Array.from(usedSet);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/index.ts:
--------------------------------------------------------------------------------
1 | export { StixObject } from './stix-object';
2 | export { Campaign } from './campaign';
3 | export { DataComponent } from './data-component';
4 | export { Group } from './group';
5 | export { Matrix } from './matrix';
6 | export { Mitigation } from './mitigation';
7 | export { Note } from './note';
8 | export { Software } from './software';
9 | export { Tactic } from './tactic';
10 | export { Technique } from './technique';
11 | export { Asset } from './asset';
12 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/matrix.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 | import { StixObject } from './stix-object';
3 | import { Tactic } from './tactic';
4 | import { Technique } from './technique';
5 |
6 | export class Matrix extends StixObject {
7 | public readonly tactics: Tactic[]; //tactics found under this Matrix
8 |
9 | /**
10 | * Creates an instance of Matrix.
11 | * @param {*} stixSDO for the matrix
12 | * @param {Map} idToTacticSDO map of tactic ID to tactic SDO
13 | * @param {Technique[]} techniques all techniques defined in the domain
14 | */
15 | constructor(stixSDO: any, idToTacticSDO: Map, techniques: Technique[], dataService: DataService) {
16 | super(stixSDO, dataService);
17 | this.tactics = stixSDO.tactic_refs
18 | .map((tacticID) => idToTacticSDO.get(tacticID)) // Get tacticSDOs
19 | .filter((tacticSDO) => tacticSDO) // Filter out nulls (tacticSDO not found)
20 | .map((tacticSDO) => new Tactic(tacticSDO, techniques, this.dataService)); // Create Tactic objects
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/mitigation.ts:
--------------------------------------------------------------------------------
1 | import { StixObject } from './stix-object';
2 |
3 | export class Mitigation extends StixObject {
4 | /**
5 | * Get techniques mitigated by this mitigation
6 | * @returns {string[]} technique IDs mitigated by this mitigation
7 | */
8 | public mitigated(domainVersionID): string[] {
9 | let rels = this.dataService.getDomain(domainVersionID).relationships.mitigates;
10 | if (rels.has(this.id)) {
11 | return rels.get(this.id);
12 | } else {
13 | return [];
14 | }
15 | }
16 |
17 | /**
18 | * Get all techniques related to the mitigation
19 | */
20 | public relatedTechniques(domainVersionID): string[] {
21 | return this.mitigated(domainVersionID);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/note.ts:
--------------------------------------------------------------------------------
1 | export class Note {
2 | public readonly abstract?: string; // brief summary of note content
3 | public readonly content: string; // content of the note
4 | public readonly object_refs: string[]; // list of STIX objects the note is applied to
5 |
6 | /**
7 | * Creates an instance of Note.
8 | * @param {any} stixSDO for the note
9 | */
10 | constructor(stixSDO: any) {
11 | if (stixSDO.abstract) this.abstract = stixSDO.abstract;
12 | this.content = stixSDO.content;
13 | this.object_refs = stixSDO.object_refs;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/software.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 | import { StixObject } from './stix-object';
3 |
4 | export class Software extends StixObject {
5 | public readonly platforms: string[] = []; //platforms for this software
6 |
7 | /**
8 | * Creates an instance of Software.
9 | * @param {any} stixSDO for the software
10 | * @param {DataService} DataService the software occurs within
11 | */
12 | constructor(stixSDO: any, dataService: DataService) {
13 | super(stixSDO, dataService);
14 | this.platforms = stixSDO.x_mitre_platforms ? stixSDO.x_mitre_platforms.map((platform) => platform.trim()) : undefined;
15 | }
16 |
17 | /**
18 | * Get techniques used by this software
19 | * @param domainVersionID the ID of the domain and version
20 | * @returns {string[]} technique IDs used by this software
21 | */
22 | public used(domainVersionID): string[] {
23 | let rels = this.dataService.getDomain(domainVersionID).relationships.software_uses;
24 | if (rels.has(this.id)) {
25 | return rels.get(this.id);
26 | } else {
27 | return [];
28 | }
29 | }
30 | /**
31 | * Get all techniques related to the software
32 | */
33 | public relatedTechniques(domainVersionID): string[] {
34 | return this.used(domainVersionID);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/stix-object.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 |
3 | export abstract class StixObject {
4 | public readonly id: string; // STIX ID
5 | public readonly attackID: string; // ATT&CK ID
6 | public readonly name: string; // name of object
7 | public readonly description: string; // description of object
8 | public readonly url: string; // URL of object on the ATT&CK website
9 | public readonly created: string; // date object was created
10 | public readonly modified: string; // date object was last modified
11 | public readonly revoked: boolean; // is the object revoked?
12 | public readonly deprecated: boolean; // is the object deprecated?
13 | public readonly version: string; // object version
14 | protected readonly dataService: DataService;
15 |
16 | constructor(stixSDO: any, dataService: DataService, supportsAttackID = true) {
17 | // Properties
18 | this.id = stixSDO.id;
19 | this.name = stixSDO.name;
20 | this.description = stixSDO.description;
21 | this.created = stixSDO.created;
22 | this.modified = stixSDO.modified;
23 | this.revoked = stixSDO.revoked ? stixSDO.revoked : false;
24 | this.deprecated = stixSDO.x_mitre_deprecated ? stixSDO.x_mitre_deprecated : false;
25 | this.version = stixSDO.x_mitre_version ? stixSDO.x_mitre_version : '';
26 | this.dataService = dataService;
27 |
28 | // ATT&CK ID
29 | if (supportsAttackID) {
30 | if (stixSDO.external_references && stixSDO.external_references[0] && stixSDO.external_references[0].external_id) {
31 | this.attackID = stixSDO.external_references[0].external_id;
32 | } else {
33 | alert('Error: external_references has invalid format in imported StixObject (ID: ' + stixSDO.id + ')');
34 | throw new Error(
35 | 'Error: external_references has invalid format in imported StixObject. Read more here: https://docs.oasis-open.org/cti/stix/v2.1/os/stix-v2.1-os.html#_72bcfr3t79jx'
36 | );
37 | }
38 | }
39 |
40 | // URL
41 | if ('external_references' in stixSDO && stixSDO.external_references.length > 0) {
42 | this.url = stixSDO.external_references[0].url;
43 | } else {
44 | this.url = '';
45 | }
46 | }
47 |
48 | /**
49 | * Compare this object's version number to another object's version number
50 | * @param that the object to compare to
51 | * @returns 0 if the objects have the same version,
52 | * > 0 if this object's version is greater,
53 | * < 0 if that object's version is greater
54 | */
55 | public compareVersion(that: StixObject): number {
56 | if (!this.version || !that.version) return 0; // one or both of the objects have no version
57 |
58 | let thisVersion = this.version.split('.');
59 | let thatVersion = that.version.split('.');
60 |
61 | for (let i = 0; i < Math.max(thisVersion.length, thatVersion.length); i++) {
62 | if (thisVersion.length == thatVersion.length && thisVersion.length < i) return 0;
63 | if (thisVersion.length < i) return -1;
64 | if (thatVersion.length < i) return 1;
65 | if (+thisVersion[i] == +thatVersion[i]) continue;
66 | return +thisVersion[i] - +thatVersion[i];
67 | }
68 | return 0;
69 | }
70 |
71 | /**
72 | * get the stix object that this object is revoked by
73 | * @param {string} domainVersionID the ID of the domain & version this object is found in
74 | * @returns {string} object ID this object is revoked by
75 | */
76 | public revoked_by(domainVersionID): string {
77 | let rels = this.dataService.getDomain(domainVersionID).relationships.revoked_by;
78 | if (rels.has(this.id)) return rels.get(this.id);
79 | else return undefined;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/tactic.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 | import { StixObject } from './stix-object';
3 | import { Technique } from './technique';
4 |
5 | export class Tactic extends StixObject {
6 | public readonly techniques: Technique[]; // techniques found under this tactic
7 | public readonly shortname: string; // shortname property, AKA phase-name for techniques' kill-chain phases
8 |
9 | /**
10 | * Creates an instance of Tactic.
11 | * @param {any} stixSDO for the tactic
12 | * @param {Technique[]} techniques all techniques in the domain
13 | */
14 | constructor(stixSDO: any, techniques: Technique[], dataService: DataService) {
15 | super(stixSDO, dataService);
16 | this.shortname = stixSDO.x_mitre_shortname;
17 | this.techniques = techniques.filter((technique: Technique) => {
18 | if (!technique.revoked && !technique.deprecated) return technique.tactics.includes(this.shortname);
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/stix/technique.ts:
--------------------------------------------------------------------------------
1 | import { DataService } from '../../services/data.service';
2 | import { StixObject } from './stix-object';
3 | import { Tactic } from './tactic';
4 |
5 | export class Technique extends StixObject {
6 | public readonly platforms: string[]; // platforms for this technique.
7 | public readonly tactics: string[]; // tactics this technique is found under in phase-name format
8 | public readonly subtechniques: Technique[]; // subtechniques under this technique
9 | public readonly datasources: string; // data sources of the technique
10 | public parent: Technique = null; // parent technique. Only present if it's a sub-technique
11 | public get isSubtechnique() {
12 | return this.parent != null;
13 | }
14 |
15 | /**
16 | * Creates an instance of Technique.
17 | * @param {any} stixSDO for the technique
18 | * @param {Technique[]} subtechniques occuring under the technique
19 | */
20 | constructor(stixSDO: any, subtechniques: Technique[], dataService: DataService) {
21 | super(stixSDO, dataService);
22 | this.platforms = stixSDO.x_mitre_platforms ? stixSDO.x_mitre_platforms.map((platform) => platform.trim()) : undefined;
23 | this.datasources = stixSDO.x_mitre_data_sources ? stixSDO.x_mitre_data_sources.toString() : '';
24 | if (!this.revoked && !this.deprecated) {
25 | this.tactics = stixSDO.kill_chain_phases.map((phase) => phase.phase_name);
26 | }
27 |
28 | this.subtechniques = subtechniques.filter((sub) => !(sub.deprecated || sub.revoked));
29 | for (let subtechnique of this.subtechniques) {
30 | subtechnique.parent = this;
31 | }
32 | }
33 |
34 | /**
35 | * Get an ID identifying this technique under a specific tactic
36 | * @param {string|Tactic} tactic tactic name in phase-name/shortname format, or a Tactic object itself
37 | * @returns {string} ID for this technique under that tactic
38 | */
39 | public get_technique_tactic_id(tactic: string | Tactic): string {
40 | let tactic_shortname = tactic instanceof Tactic ? tactic.shortname : tactic;
41 |
42 | if (!this.tactics.includes(tactic_shortname)) {
43 | throw new Error(tactic_shortname + ' is not a tactic of ' + this.attackID);
44 | }
45 | return this.attackID + '^' + tactic_shortname;
46 | }
47 |
48 | /**
49 | * Get all possible IDs identifying this technique under tactics
50 | * Basically the same as calling get_technique_tactic_id with all valid tactic values
51 | */
52 | public get_all_technique_tactic_ids(): string[] {
53 | if (this.revoked || this.deprecated) return [];
54 | return this.tactics.map((shortname: string) => this.get_technique_tactic_id(shortname));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/tab.ts:
--------------------------------------------------------------------------------
1 | import { ViewModel } from './view-model';
2 |
3 | export class Tab {
4 | public title: string;
5 | public viewModel: ViewModel;
6 | public domain: string = '';
7 | public isDataTable: boolean;
8 | public isCloseable: boolean = false;
9 | public showScoreVariables: boolean = false;
10 |
11 | constructor(title: string, isCloseable: boolean, showScoreVariables: boolean, domain: string, isDataTable: boolean) {
12 | this.title = title;
13 | this.isCloseable = isCloseable;
14 | this.showScoreVariables = showScoreVariables;
15 | this.domain = domain;
16 | this.isDataTable = isDataTable;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/version-changelog.ts:
--------------------------------------------------------------------------------
1 | export class VersionChangelog {
2 | public oldDomainVersionID: string;
3 | public newDomainVersionID: string;
4 | public additions: string[] = []; // new objects added to newest version
5 | public changes: string[] = []; // object changes between versions
6 | public minor_changes: string[] = []; // changes to objects without version increments
7 | public deprecations: string[] = []; // objects deprecated since older version
8 | public revocations: string[] = []; // objects revoked since older version
9 | public unchanged: string[] = []; // objects which have not changed between versions
10 |
11 | public reviewed = new Set();
12 | public copied = new Set();
13 |
14 | constructor(oldDomainVersionID: string, newDomainVersionID: string) {
15 | this.oldDomainVersionID = oldDomainVersionID;
16 | this.newDomainVersionID = newDomainVersionID;
17 | }
18 |
19 | /** Get the length of the version changelog */
20 | public length(): number {
21 | return (
22 | this.additions.length +
23 | this.changes.length +
24 | this.minor_changes.length +
25 | this.deprecations.length +
26 | this.revocations.length +
27 | this.unchanged.length
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/nav-app/src/app/classes/version.ts:
--------------------------------------------------------------------------------
1 | export class Version {
2 | public readonly name: string;
3 | public readonly number: string;
4 |
5 | /**
6 | * Creates an instance of Version
7 | * @param name version name
8 | * @param number version number
9 | */
10 | constructor(name: string, number: string) {
11 | this.name = name;
12 | this.number = number;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/nav-app/src/app/help/help.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Documentation
4 |
5 | Top ^
6 |
7 |
8 |
9 |
17 |
18 |
19 |
20 | close
21 |
22 |
--------------------------------------------------------------------------------
/nav-app/src/app/help/help.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .help-dialog {
4 | .table-of-contents {
5 | list-style: none;
6 | margin: 0;
7 | padding: 0 25px 0 0;
8 | li.toc-heading {
9 | margin-bottom: 0;
10 | a {
11 | cursor: pointer;
12 | color: blue;
13 | text-decoration: underline;
14 | }
15 | @for $level from 1 through 6 {
16 | &.level-#{$level} {
17 | padding-inline-start: #{40 * ($level)}px;
18 | }
19 | }
20 | }
21 | }
22 | .content {
23 | code {
24 | border: 1px solid color(panel-dark);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/nav-app/src/app/help/help.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject, ViewEncapsulation, ViewChild, Renderer2 } from '@angular/core';
2 | import { MAT_DIALOG_DATA, MatDialog } from '@angular/material/dialog';
3 | import { MarkdownComponent, MarkdownService } from 'ngx-markdown';
4 | import { LayerInformationComponent } from '../layer-information/layer-information.component';
5 |
6 | @Component({
7 | selector: 'help',
8 | templateUrl: './help.component.html',
9 | styleUrls: ['./help.component.scss'],
10 | encapsulation: ViewEncapsulation.None,
11 | })
12 | export class HelpComponent implements OnInit {
13 | private listenObj: any;
14 | @ViewChild('markdownElement', { static: false }) public markdownElement: any;
15 | public headingAnchors: MarkdownHeadingAnchor[] = [];
16 |
17 | constructor(
18 | private dialog: MatDialog,
19 | private markdownService: MarkdownService,
20 | private renderer: Renderer2,
21 | @Inject(MAT_DIALOG_DATA) public data
22 | ) {
23 | // intentionally left blank
24 | }
25 |
26 | ngOnInit(): void {
27 | setTimeout(() => {
28 | this.scrollTo('toc');
29 | }, 175);
30 |
31 | let self = this;
32 | this.markdownService.renderer.heading = (text: string, level: number) => {
33 | let img = text.match(/( )/g) ? text.match(/( )/g)[0].replace(/(nav-app\/src\/)/g, '') : '';
34 | text = text.replace(/( )/g, '');
35 | const escapedText = text
36 | .toLowerCase()
37 | .trim()
38 | .replace(/[^\w]+/g, '-');
39 | self.headingAnchors.push({
40 | level: level,
41 | anchor: escapedText,
42 | label: text.replace('&', '&'),
43 | });
44 | return `${img}${text} `;
45 | };
46 |
47 | this.markdownService.renderer.html = (html: string) => {
48 | if (!html.match(/(nav-app\/src\/)/g)) return html;
49 | return html.replace(/(nav-app\/src\/)/g, '');
50 | };
51 | }
52 |
53 | ngOnDestroy(): void {
54 | if (this.listenObj) {
55 | this.listenObj();
56 | }
57 | }
58 |
59 | // from https://github.com/jfcere/ngx-markdown/issues/125#issuecomment-518025821
60 | public onMarkdownLoad(e) {
61 | // hijack clicks on links
62 | if (this.markdownElement) {
63 | this.listenObj = this.renderer.listen(this.markdownElement.element.nativeElement, 'click', (e: Event) => {
64 | if (e.target && (e.target as any).tagName === 'A') {
65 | const el = e.target as HTMLElement;
66 | const linkURL = el.getAttribute && el.getAttribute('href');
67 | if (linkURL) {
68 | e.preventDefault();
69 | if (linkURL.charAt(0) === '#') this.scrollTo(linkURL.replace('#', ''));
70 | else if (linkURL.includes('layers/')) this.openLayerDialog();
71 | else if (linkURL.match(/(nav-app\/src\/)/g)) window.open(linkURL.replace(/(nav-app\/src\/)/g, ''));
72 | else window.open(linkURL);
73 | }
74 | }
75 | });
76 | }
77 | }
78 |
79 | public scrollTo(anchor) {
80 | let element = document.querySelector('.' + anchor);
81 | if (element) element.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' });
82 | }
83 |
84 | /**
85 | * open the layer information dialog
86 | */
87 | public openLayerDialog(): void {
88 | this.dialog.open(LayerInformationComponent, {
89 | autoFocus: false,
90 | panelClass: this.data.theme,
91 | });
92 | }
93 | }
94 |
95 | interface MarkdownHeadingAnchor {
96 | level: number;
97 | anchor: string;
98 | label: string;
99 | }
100 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-information/layer-information.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | close
9 |
10 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-information/layer-information.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .layer-dialog {
4 | .content {
5 | max-height: 75vh;
6 | font-size: 10pt;
7 | }
8 |
9 | table {
10 | border-collapse: collapse;
11 | width: 100%;
12 |
13 | th,
14 | td {
15 | width: calc(100% / 5);
16 | padding: 5px;
17 | word-break: break-word;
18 | line-height: 16pt;
19 | }
20 |
21 | th:nth-child(4) {
22 | padding: 0 30px;
23 | }
24 |
25 | th:nth-child(3),
26 | th:nth-child(4),
27 | th:last-child,
28 | td:nth-child(3),
29 | td:nth-child(4) {
30 | text-align: center;
31 | }
32 |
33 | td {
34 | vertical-align: middle;
35 | border-top: 1px solid color(panel-dark);
36 | }
37 |
38 | td:not(td:last-child) {
39 | hyphens: auto;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-information/layer-information.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { LayerInformationComponent } from './layer-information.component';
3 | import { MatDialogModule } from '@angular/material/dialog';
4 | import { MarkdownModule, MarkdownService } from 'ngx-markdown';
5 | import { HttpClient, HttpClientModule } from '@angular/common/http';
6 | import * as globals from '../utils/globals';
7 |
8 | describe('LayerInformationComponent', () => {
9 | let component: LayerInformationComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(async () => {
13 | await TestBed.configureTestingModule({
14 | declarations: [LayerInformationComponent],
15 | imports: [
16 | HttpClientModule,
17 | MatDialogModule,
18 | MarkdownModule.forRoot({ loader: HttpClient })],
19 | providers: [MarkdownService]
20 | }).compileComponents();
21 |
22 | fixture = TestBed.createComponent(LayerInformationComponent);
23 | component = fixture.componentInstance;
24 | fixture.detectChanges();
25 | });
26 |
27 | it('should create', () => {
28 | expect(component).toBeTruthy();
29 | });
30 |
31 | it('should return correct layerFormatLink based on global layer version', () => {
32 | let filePath = `./layers/spec/v${globals.layerVersion}/layerformat.md`;
33 | expect(component.layerFormatLink).toBe(filePath);
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-information/layer-information.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewEncapsulation } from '@angular/core';
2 | import * as globals from '../utils/globals';
3 |
4 | @Component({
5 | selector: 'app-layer-information',
6 | templateUrl: './layer-information.component.html',
7 | styleUrls: ['./layer-information.component.scss'],
8 | encapsulation: ViewEncapsulation.None,
9 | })
10 | export class LayerInformationComponent {
11 | public get layerFormatLink(): string {
12 | return `./layers/spec/v${globals.layerVersion}/layerformat.md`;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-settings/layer-settings.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 | .layer-settings {
3 | .sidebar-content {
4 | margin: 1rem 0;
5 | }
6 | .info-card {
7 | display: flex;
8 | justify-content: space-between;
9 | }
10 | .info-field {
11 | margin: 1em 0;
12 | .mat-mdc-form-field {
13 | width: 100%;
14 | }
15 | }
16 | .settings .title {
17 | font-size: 14px;
18 | margin: 0;
19 | }
20 | .layer-data {
21 | .mat-divider.layer-div {
22 | margin-bottom: 10px;
23 | @include adaptive-color-dark-only('border-top-color', color(dark-4));
24 | }
25 | }
26 | .sub-section {
27 | padding-top: 1.5em;
28 | }
29 | .padding-top {
30 | padding-top: 1em;
31 | }
32 | .data-input {
33 | overflow-y: auto;
34 | max-height: 30vh;
35 | }
36 | .button-container {
37 | display: flex;
38 | justify-content: flex-end;
39 | }
40 | .mat-mdc-form-field, .mat-mdc-form-field:hover {
41 | &:not(.mat-form-field-disabled) {
42 | .mat-mdc-floating-label,
43 | .mat-mdc-input-element {
44 | @include adaptive-color-dark-only('color', on-color(dark));
45 | }
46 | .mdc-line-ripple::before {
47 | @include adaptive-color-dark-only('border-bottom-color', on-color(dark-3));
48 | }
49 | }
50 |
51 | &.mat-form-field-disabled {
52 | .mat-mdc-floating-label,
53 | .mdc-text-field__input {
54 | @include adaptive-color-dark-only('color', darken(on-color(dark-1), 25%));
55 | }
56 | .mdc-line-ripple::before {
57 | @include adaptive-color-dark-only('border-bottom-color', darken(on-color(dark-1), 25%));
58 | }
59 | }
60 | .mat-mdc-form-field-hint {
61 | @include adaptive-color-dark-only('color', on-color(dark));
62 | }
63 | }
64 | .mat-mdc-card {
65 | width: 100%;
66 | }
67 | .mat-mdc-card-title {
68 | padding: 16px 16px 0 16px;
69 | }
70 | .mat-mdc-card + .mat-mdc-card {
71 | margin-left: 1em;
72 | }
73 | }
--------------------------------------------------------------------------------
/nav-app/src/app/layer-settings/layer-settings.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { MarkdownModule, MarkdownService } from 'ngx-markdown';
3 | import { HttpClient, HttpClientModule } from '@angular/common/http';
4 | import { ViewModel } from '../classes';
5 | import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; // Import CUSTOM_ELEMENTS_SCHEMA
6 | import { LayerSettingsComponent } from './layer-settings.component';
7 | import { FormsModule } from '@angular/forms';
8 | import { ConfigService } from '../services/config.service';
9 | import * as MockData from '../../tests/utils/mock-data';
10 |
11 | describe('LayerSettingsComponent', () => {
12 | let component: LayerSettingsComponent;
13 | let fixture: ComponentFixture;
14 | let configService: ConfigService;
15 |
16 | beforeEach(waitForAsync(() => {
17 | TestBed.configureTestingModule({
18 | declarations: [LayerSettingsComponent],
19 | imports: [
20 | HttpClientModule,
21 | FormsModule,
22 | MarkdownModule.forRoot({ loader: HttpClient })
23 | ],
24 | providers: [MarkdownService],
25 | schemas: [CUSTOM_ELEMENTS_SCHEMA]
26 | })
27 | .compileComponents();
28 | configService = TestBed.inject(ConfigService);
29 | configService.versions = MockData.configData;
30 | fixture = TestBed.createComponent(LayerSettingsComponent);
31 | component = fixture.componentInstance;
32 | component.viewModel = new ViewModel('layer', '35', 'enterprise-attack-13', null);
33 | fixture.detectChanges();
34 | }));
35 |
36 | it('should create', () => {
37 | expect(component).toBeTruthy();
38 | });
39 | });
40 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-settings/layer-settings.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ViewEncapsulation } from '@angular/core';
2 | import { ViewModel } from '../classes';
3 | import { DataService } from '../services/data.service';
4 |
5 | @Component({
6 | selector: 'app-layer-settings',
7 | templateUrl: './layer-settings.component.html',
8 | styleUrls: ['./layer-settings.component.scss'],
9 | encapsulation: ViewEncapsulation.None
10 | })
11 | export class LayerSettingsComponent {
12 | @Input() viewModel: ViewModel;
13 |
14 | constructor(
15 | public dataService: DataService,
16 | ) {
17 | // intentionally left blank
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-upgrade/changelog-cell/changelog-cell.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ tactic.name }}
6 |
7 |
8 |
9 | {{ technique.attackID }}
10 |
11 |
12 | {{ technique.name }}
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-upgrade/changelog-cell/changelog-cell.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../matrix/technique-cell/technique-cell.component.scss';
2 |
3 | .nopointer {
4 | cursor: unset;
5 | }
6 | .section {
7 | margin: 4px 0;
8 | }
9 | .section + .section {
10 | border-top: 1px solid rgba(border-color(body), 0.5);
11 | }
12 | .technique-cell {
13 | margin: auto;
14 | }
15 | .setwidth {
16 | max-width: 50%;
17 | }
18 | .mat-drawer-inner-container .technique-cell {
19 | @include adaptive-color-dark-only('background-color', color(dark-4));
20 | }
21 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-upgrade/changelog-cell/changelog-cell.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, ViewEncapsulation } from '@angular/core';
2 | import { ViewModelsService } from '../../services/viewmodels.service';
3 | import { ConfigService } from '../../services/config.service';
4 | import { DataService } from '../../services/data.service';
5 | import { Cell } from '../../matrix/cell';
6 |
7 | @Component({
8 | selector: 'changelog-cell',
9 | templateUrl: './changelog-cell.component.html',
10 | styleUrls: ['./changelog-cell.component.scss'],
11 | encapsulation: ViewEncapsulation.None,
12 | })
13 | export class ChangelogCellComponent extends Cell {
14 | @Input() isCurrentVersion?: boolean = true;
15 | @Input() isDraggable?: boolean = false;
16 | @Input() section: string;
17 |
18 | constructor(
19 | public configService: ConfigService,
20 | public dataService: DataService,
21 | public viewModelsService: ViewModelsService
22 | ) {
23 | super(dataService, configService);
24 | }
25 |
26 | /**
27 | * Highlight the moused over technique
28 | */
29 | public highlight(): void {
30 | if (this.isCurrentVersion) {
31 | this.viewModel.highlightTechnique(this.technique, this.tactic);
32 | }
33 | }
34 |
35 | /**
36 | * Clear the technique highlight
37 | */
38 | public unhighlight(): void {
39 | if (this.isCurrentVersion) this.viewModel.clearHighlight();
40 | }
41 |
42 | /**
43 | * Select or unselect this technique
44 | */
45 | public onClick(): void {
46 | if (this.isCurrentVersion) {
47 | // unselect technique
48 | if (this.viewModel.isTechniqueSelected(this.technique, this.tactic)) {
49 | this.viewModel.unselectTechnique(this.technique, this.tactic);
50 | }
51 | // select technique
52 | else {
53 | this.viewModel.clearSelectedTechniques();
54 | this.viewModel.selectTechnique(this.technique, this.tactic);
55 | }
56 | this.viewModelsService.selectionChanged(); // emit selection change
57 | }
58 | }
59 |
60 | /**
61 | * Retrieve css classes for this technique
62 | */
63 | public getClass(): string {
64 | let theclass = super.getClass();
65 | if (!this.isCurrentVersion && !this.isDraggable) {
66 | theclass += ' nopointer';
67 | }
68 | if (this.section == 'additions' || this.section == 'deprecations') {
69 | theclass += ' setwidth';
70 | }
71 | return theclass;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/nav-app/src/app/layer-upgrade/layer-upgrade.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .title {
4 | h2 {
5 | display: inline-block;
6 | margin: 12px 0;
7 | }
8 | .mat-icon {
9 | vertical-align: middle;
10 | }
11 | }
12 | h4 {
13 | margin: 12px 0;
14 | }
15 | .mat-divider {
16 | margin: 1rem 0 !important;
17 | }
18 | .changelog-cells {
19 | padding-bottom: 1rem;
20 | position: relative;
21 | }
22 | .clear-annotations {
23 | position: absolute;
24 | top: -10px;
25 | right: -5px;
26 | padding: 6px 6px;
27 | background: color(panel-dark);
28 | border-radius: 10px;
29 | line-height: 10px;
30 | cursor: pointer;
31 | box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.5);
32 | &:hover {
33 | background-color: darken(color(panel-dark), 5%);
34 | }
35 | }
36 |
37 | .mat-step .mat-mdc-button-base {
38 | @include adaptive-color-dark-only('border-color', color(dark-4));
39 | @include adaptive-color-dark-only('background-color', rgba(0, 0, 0, 0));
40 | &:disabled {
41 | @include adaptive-color-dark-only('color', rgba(255, 255, 255, 0.5));
42 | }
43 | }
44 |
45 | .mat-step-header .mat-step-label {
46 | @include adaptive-color-dark-only('color', on-color(dark));
47 | }
48 |
49 | .mat-stepper-vertical-line::before {
50 | @include adaptive-color-dark-only('border-left-color', on-color(dark));
51 | }
52 |
53 | .mat-mdc-paginator-container {
54 | min-height: unset;
55 | }
56 |
57 | .mat-stepper-vertical,
58 | .mat-mdc-paginator {
59 | @include adaptive-color-dark-only('background-color', color(dark-3));
60 | @include adaptive-color-dark-only('color', on-color(dark));
61 | }
62 |
63 | .stepper-content {
64 | padding: 6px;
65 | }
66 |
67 | .mat-expansion-panel:first-child .mat-expansion-panel-header,
68 | .mat-expansion-panel-header.mat-expanded {
69 | @include adaptive-color-dark-only('border-top', 1px solid #ffffff21);
70 | }
71 |
72 | .mat-expansion-panel-header-title span {
73 | padding-left: 1rem;
74 | display: inline-flex;
75 | align-items: center;
76 | }
77 |
78 | tr td {
79 | padding-bottom: 1rem;
80 | }
81 | .changelog-table {
82 | table-layout: fixed;
83 | width: 100%;
84 | }
85 | .narrow {
86 | width: 44px;
87 | }
88 | .button-container .button {
89 | display: flex;
90 | align-items: center;
91 | margin: 4px;
92 | .mat-icon {
93 | font-size: 16px;
94 | }
95 | &:disabled {
96 | @include adaptive-color('background-color', lighten(color(panel-dark), 5%), color(dark-2));
97 | cursor: unset;
98 | }
99 | }
100 | .cols {
101 | display: flex;
102 | .float {
103 | float: left;
104 | padding: 6px;
105 | }
106 | }
107 | .version {
108 | text-align: center;
109 | margin-bottom: -1rem;
110 | a {
111 | text-decoration: none;
112 | }
113 | }
114 | .arrow {
115 | text-align: center;
116 | vertical-align: top;
117 | }
118 | .info {
119 | margin-top: 1.5rem;
120 | height: 100%;
121 | display: flex;
122 | .mat-icon {
123 | height: 100%;
124 | }
125 | }
126 | .wide {
127 | width: 100%;
128 | }
129 | .checkbox {
130 | padding: 6px;
131 | &.right {
132 | float: right;
133 | }
134 | }
135 | .checkbox-custom-label {
136 | display: block;
137 | }
138 | .summary {
139 | text-indent: 24px;
140 | }
141 | .stepper-button {
142 | float: right;
143 | padding-bottom: 24px;
144 | .mat-mdc-outlined-button {
145 | margin-left: 12px;
146 | }
147 | }
148 | .reviewed {
149 | color: #4bb543;
150 | }
151 | .disabled {
152 | @include adaptive-color('color', rgba(0, 0, 0, 0.15), color(dark-link));
153 | }
154 | .dndDragover .technique-cell {
155 | box-shadow: 0 0 0 1px color(cell-highlight-color) inset !important;
156 | }
157 | .mat-vertical-stepper-header {
158 | pointer-events: none;
159 | }
160 | .description {
161 | white-space: pre-line;
162 | padding: 6px;
163 | text-align: justify;
164 | span {
165 | font-size: 14px;
166 | }
167 | &.center {
168 | text-align: center;
169 | }
170 | }
171 | .spinner {
172 | display: flex;
173 | justify-content: center;
174 | align-items: center;
175 | height: 50vh;
176 |
177 | mat-progress-spinner circle {
178 | stroke: on-color-deemphasis(body);
179 | }
180 | }
181 | .mat-icon {
182 | overflow: visible;
183 | }
184 |
--------------------------------------------------------------------------------
/nav-app/src/app/list-input/list-input.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | remove
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | add
16 |
17 |
18 |
19 |
20 |
21 |
49 |
50 |
51 |
52 | add {{ config.type }}
53 |
54 |
--------------------------------------------------------------------------------
/nav-app/src/app/list-input/list-input.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .list-container,
4 | .add-more {
5 | margin: 10px 0;
6 | }
7 | .item-container {
8 | padding: 10px;
9 | border: 1px solid color(panel-dark);
10 | margin: 4px 0;
11 |
12 | &.hide-subscript {
13 | .mat-mdc-form-field + .mat-mdc-form-field {
14 | margin-top: 8px;
15 | }
16 | }
17 | .mat-mdc-form-field {
18 | width: 100%;
19 | }
20 | }
21 | .remove-button {
22 | margin-top: 8px;
23 | display: flex;
24 | justify-content: flex-end;
25 | }
26 | .assigned-link {
27 | float: left;
28 | @include adaptive-color-dark-only('color', color(dark-link) !important);
29 | }
30 | .assigned-link:active,
31 | .assigned-link:visited {
32 | @include adaptive-color-dark-only('color', color(dark-link-active));
33 | }
34 |
35 | .divider-button-container {
36 | display: flex;
37 | justify-content: center;
38 | align-items: center;
39 | }
40 |
41 | .divider-button {
42 | display: flex;
43 | width: 100%;
44 | align-items: center;
45 | background: none;
46 | border: none;
47 | cursor: pointer;
48 | padding: 5px 0;
49 | .icon {
50 | font-size: 16px;
51 | height: 16px;
52 | width: 16px;
53 | border-radius: 3px;
54 | padding: 1px;
55 | }
56 | .line {
57 | flex: 1;
58 | height: 2px;
59 | }
60 | &.divider-option {
61 | .line, .icon {
62 | @include adaptive-color('background-color', color(panel-dark), color(dark-4));
63 | }
64 | }
65 | &.divider {
66 | .line, .icon {
67 | @include adaptive-color('background-color', darken(color(panel-dark), 40%), lighten(color(dark-4), 40%));
68 | }
69 | }
70 | &:hover .line, &:hover .icon {
71 | @include adaptive-color('background-color', color(button-dark), color(dark-link));
72 | }
73 | }
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-common.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | $sizeunit: 14px;
4 | $sizeunit_print: 12px;
5 |
6 | .matrix {
7 | border-collapse: collapse;
8 | line-height: $sizeunit;
9 | }
10 |
11 | @media print {
12 | .matrix {
13 | border-collapse: collapse;
14 | line-height: $sizeunit_print;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-flat/matrix-flat.component.scss:
--------------------------------------------------------------------------------
1 | @import '../matrix-common.scss';
2 | .matrix.flat {
3 | .tactic {
4 | margin-right: 5px;
5 | width: 1%;
6 | vertical-align: top;
7 |
8 | &.name,
9 | &.count {
10 | text-align: center;
11 | font-size: $sizeunit + 2px;
12 | }
13 | &.name {
14 | cursor: pointer;
15 | vertical-align: bottom;
16 | font-weight: bold;
17 | @media print {
18 | font-size: $sizeunit_print - 4px;
19 | }
20 | position: sticky;
21 | top: 0.05px;
22 | z-index: 9999;
23 | @include adaptive-color-dark-only('background-color', lighten(color(dark-1), 3%));
24 | @include adaptive-color-light-only('background-color', white);
25 | }
26 |
27 | &.count {
28 | @include adaptive-color('border-bottom', 1px solid black, 1px solid white);
29 | font-size: $sizeunit - 1px;
30 | padding-bottom: 5px;
31 | margin-bottom: 5px;
32 | @media print {
33 | font-size: $sizeunit_print - 4px;
34 | }
35 | position: sticky;
36 | top: 32px;
37 | z-index: 9999;
38 | @include adaptive-color-dark-only('background-color', lighten(color(dark-1), 3%));
39 | @include adaptive-color-light-only('background-color', white);
40 | }
41 |
42 | .subtechniques-row.hidden {
43 | display: none;
44 | }
45 | .supertechnique {
46 | border-collapse: collapse;
47 | width: 100%;
48 | padding: 0;
49 | margin: 0;
50 | td {
51 | padding: 0;
52 | vertical-align: top;
53 | &.sidebar.technique {
54 | min-width: 8px;
55 | width: 12px;
56 | padding: 0;
57 | background: on-color-deemphasis(body);
58 | cursor: pointer;
59 | vertical-align: middle;
60 | .handle {
61 | text-align: center;
62 | vertical-align: middle;
63 | transform: rotate(-90deg);
64 | color: color-alternate(body);
65 | width: 12px;
66 | height: 12px;
67 | font-size: 16px;
68 | line-height: 12px;
69 | }
70 | &.disabled {
71 | background: #aaaaaa;
72 | border-color: #aaaaaa;
73 | box-shadow: none;
74 | pointer-events: none;
75 | }
76 | }
77 | &.sidebar.subtechniques {
78 | svg {
79 | fill: on-color-deemphasis(body);
80 | }
81 | }
82 | &.sidebar {
83 | border-right: 2px solid on-color-deemphasis(body);
84 | @media print {
85 | display: none;
86 | }
87 | }
88 | &.technique {
89 | box-shadow: 0 0 0 1px on-color-deemphasis(body) inset;
90 | }
91 | }
92 | }
93 |
94 | .more-icon {
95 | transition: all ease 0.125s;
96 | vertical-align: top;
97 | transform: scale(0.5) rotate(-90deg);
98 | width: 12px;
99 | height: 12px;
100 | &.expanded {
101 | transform: scale(0.5);
102 | }
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-flat/matrix-flat.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { MatrixFlatComponent } from './matrix-flat.component';
4 |
5 | describe('MatrixFlatComponent', () => {
6 | let component: MatrixFlatComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(waitForAsync(() => {
10 | TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule],
12 | declarations: [MatrixFlatComponent],
13 | }).compileComponents();
14 |
15 | fixture = TestBed.createComponent(MatrixFlatComponent);
16 | component = fixture.componentInstance;
17 | }));
18 |
19 | it('should create', () => {
20 | expect(component).toBeTruthy();
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-flat/matrix-flat.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2 | import { MatrixCommon } from '../matrix-common';
3 | import { ConfigService } from '../../services/config.service';
4 | import { ViewModelsService } from '../../services/viewmodels.service';
5 |
6 | @Component({
7 | selector: 'matrix-flat',
8 | templateUrl: './matrix-flat.component.html',
9 | styleUrls: ['./matrix-flat.component.scss'],
10 | encapsulation: ViewEncapsulation.None,
11 | })
12 | export class MatrixFlatComponent extends MatrixCommon implements OnInit {
13 | constructor(configService: ConfigService, viewModelsService: ViewModelsService) {
14 | super(configService, viewModelsService);
15 | }
16 |
17 | ngOnInit(): void {
18 | // intentionally left blank
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-mini/matrix-mini.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | 0; else elseblock">
14 |
15 |
25 |
26 |
27 |
36 |
37 |
38 |
39 |
40 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-mini/matrix-mini.component.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:math' as math;
2 | @import '../matrix-common.scss';
3 | .matrix.mini {
4 | .header {
5 | text-align: left;
6 | }
7 | .tactic {
8 | padding: 0 5px;
9 | }
10 | .tactic.header {
11 | padding: 5px;
12 | }
13 | .tactic.body {
14 | padding-top: 5px;
15 | vertical-align: top;
16 | // display: flex;
17 | // flex-wrap: wrap;
18 | // justify-content: space-between;
19 |
20 | .cell-container {
21 | margin-right: math.div($sizeunit, 6);
22 | margin-bottom: math.div($sizeunit, 6);
23 | &:not(.supertechnique-group) {
24 | display: inline-block;
25 | }
26 | // margin: 0 2px 0 2px;
27 | &.supertechnique-group {
28 | // background: color(panel-light);
29 | // border-top: 1px solid darken(color(panel-light), 5%);
30 | // border-bottom: 1px solid darken(color(panel-light), 5%);
31 | // padding-top: 3px;
32 | // transform: translateX(-3px);
33 | box-shadow: 0 0 0 1px on-color-deemphasis(body) inset;
34 | // padding: 3px 0 0 3px;
35 | padding-top: 1px + math.div($sizeunit, 2);
36 | padding-left: 1px + math.div($sizeunit, 2);
37 | padding-bottom: math.div($sizeunit, 3);
38 | padding-right: math.div($sizeunit, 3);
39 | }
40 | &.supertechnique {
41 | box-shadow: 0 0 0 1px on-color-deemphasis(body);
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-mini/matrix-mini.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { MatrixMiniComponent } from './matrix-mini.component';
4 |
5 | describe('MatrixMiniComponent', () => {
6 | let component: MatrixMiniComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(waitForAsync(() => {
10 | TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule],
12 | declarations: [MatrixMiniComponent],
13 | }).compileComponents();
14 | }));
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(MatrixMiniComponent);
18 | component = fixture.componentInstance;
19 | });
20 |
21 | it('should create', () => {
22 | expect(component).toBeTruthy();
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-mini/matrix-mini.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { MatrixCommon } from '../matrix-common';
3 | import { ConfigService } from '../../services/config.service';
4 | import { ViewModelsService } from '../../services/viewmodels.service';
5 |
6 | @Component({
7 | selector: 'matrix-mini',
8 | templateUrl: './matrix-mini.component.html',
9 | styleUrls: ['./matrix-mini.component.scss'],
10 | })
11 | export class MatrixMiniComponent extends MatrixCommon implements OnInit {
12 | constructor(configService: ConfigService, viewModelsService: ViewModelsService) {
13 | super(configService, viewModelsService);
14 | }
15 |
16 | ngOnInit(): void {
17 | // intentionally left blank
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-side/matrix-side.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { MatrixSideComponent } from './matrix-side.component';
4 | import { MatDialogModule } from '@angular/material/dialog';
5 |
6 | describe('MatrixSideComponent', () => {
7 | let component: MatrixSideComponent;
8 | let fixture: ComponentFixture;
9 |
10 | beforeEach(waitForAsync(() => {
11 | TestBed.configureTestingModule({
12 | imports: [HttpClientTestingModule, MatDialogModule],
13 | declarations: [MatrixSideComponent],
14 | }).compileComponents();
15 | }));
16 |
17 | beforeEach(() => {
18 | fixture = TestBed.createComponent(MatrixSideComponent);
19 | component = fixture.componentInstance;
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 | });
26 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/matrix-side/matrix-side.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core';
2 | import { MatrixCommon } from '../matrix-common';
3 | import { ConfigService } from '../../services/config.service';
4 | import { ViewModelsService } from '../../services/viewmodels.service';
5 |
6 | @Component({
7 | selector: 'matrix-side',
8 | templateUrl: './matrix-side.component.html',
9 | styleUrls: ['./matrix-side.component.scss'],
10 | encapsulation: ViewEncapsulation.None,
11 | })
12 | export class MatrixSideComponent extends MatrixCommon implements OnInit {
13 | constructor(configService: ConfigService, viewModelsService: ViewModelsService) {
14 | super(configService, viewModelsService);
15 | }
16 |
17 | ngOnInit(): void {
18 | // intentionally left blank
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/tactic-cell/tactic-cell.component.html:
--------------------------------------------------------------------------------
1 |
8 |
{{ tactic.attackID }}
9 |
10 |
{{ tactic.name }}
11 |
12 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/tactic-cell/tactic-cell.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../../colors.scss';
2 | @import '../matrix-common.scss';
3 | .tactic-cell {
4 | padding-top: 3px;
5 | box-sizing: border-box;
6 | &:not(.mini) {
7 | min-width: $sizeunit * 3;
8 | }
9 | &.mini {
10 | @include adaptive-color('background', black, white);
11 | width: $sizeunit;
12 | }
13 | min-height: $sizeunit;
14 | &.bordered {
15 | @include adaptive-color('border', 1px solid border-color(body), 1px solid white);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/tactic-cell/tactic-cell.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { TechniqueVM, ViewModel } from '../../classes';
3 | import { Tactic, Technique } from '../../classes/stix';
4 | import { TacticCellComponent } from './tactic-cell.component';
5 | import * as MockData from '../../../tests/utils/mock-data';
6 | import { MatTooltipModule } from '@angular/material/tooltip';
7 |
8 | describe('TacticCellComponent', () => {
9 | let component: TacticCellComponent;
10 | let fixture: ComponentFixture;
11 |
12 | beforeEach(waitForAsync(() => {
13 | TestBed.configureTestingModule({
14 | imports: [MatTooltipModule],
15 | declarations: [TacticCellComponent],
16 | }).compileComponents();
17 | }));
18 |
19 | beforeEach(() => {
20 | let technique_list: Technique[] = [];
21 | fixture = TestBed.createComponent(TacticCellComponent);
22 | component = fixture.componentInstance;
23 | component.viewModel = new ViewModel('layer', '33', 'enterprise-attack-13', null);
24 | component.viewModel.domainVersionID = 'enterprise-attack-13';
25 | let t1 = new Technique(MockData.T0000, [], null);
26 | technique_list.push(t1);
27 | let tvm_1 = new TechniqueVM('T1595^reconnaissance');
28 | component.viewModel.setTechniqueVM(tvm_1);
29 | component.tactic = new Tactic(MockData.TA0000, technique_list, null);
30 | fixture.detectChanges();
31 | });
32 |
33 | it('should create', () => {
34 | expect(component).toBeTruthy();
35 | });
36 | });
37 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/tactic-cell/tactic-cell.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, ViewEncapsulation } from '@angular/core';
2 | import { Tactic } from '../../classes/stix';
3 | import { ViewModel } from '../../classes';
4 |
5 | @Component({
6 | selector: 'tactic-cell',
7 | templateUrl: './tactic-cell.component.html',
8 | styleUrls: ['./tactic-cell.component.scss'],
9 | encapsulation: ViewEncapsulation.None,
10 | })
11 | export class TacticCellComponent implements OnInit {
12 | @Input() tactic: Tactic;
13 | @Input() viewModel: ViewModel;
14 |
15 | constructor() {
16 | // intentionally left blank
17 | }
18 |
19 | ngOnInit() {
20 | // intentionally left blank
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/cell-popover.scss:
--------------------------------------------------------------------------------
1 | @mixin cell-popover($opacity) {
2 | position: absolute;
3 | padding: 6px;
4 | &:not(.top) {
5 | top: 0;
6 | }
7 | z-index: 100;
8 | min-width: 150px;
9 | max-width: 300px;
10 | text-align: left;
11 | white-space: normal;
12 |
13 | border-radius: 3px;
14 | font-size: 8pt;
15 | color: white;
16 | &.bottom.right {
17 | /* ------------------
18 | * | |
19 | * | X XXXX |
20 | * | XXXX |
21 | * ------------------
22 | */
23 | transform: translateX(10px);
24 | &:before {
25 | left: -9.5px;
26 | transform: rotate(90deg);
27 | }
28 | }
29 | &.bottom.left {
30 | /* ------------------
31 | * | |
32 | * | XXXX X |
33 | * | XXXX |
34 | * ------------------
35 | */
36 | transform: translateX(-10px);
37 | right: 100%;
38 | &:before {
39 | right: -9px;
40 | transform: rotate(-90deg);
41 | }
42 | }
43 | &.top.right {
44 | /* ------------------
45 | * | XXXX |
46 | * | XXXX |
47 | * | X |
48 | * | |
49 | * ------------------
50 | */
51 | transform: translateY(-10px);
52 | bottom: 100%;
53 | left: 0;
54 | &:before {
55 | bottom: -9px;
56 | // transform: rotate(180deg);
57 | }
58 | }
59 | &.top.left {
60 | /* ------------------
61 | * | XXXX |
62 | * | XXXX |
63 | * | X |
64 | * | |
65 | * ------------------
66 | */
67 | transform: translateY(-10px);
68 | bottom: 100%;
69 | right: 0;
70 | &:before {
71 | bottom: -9px;
72 | right: 6px;
73 | // transform: rotate(180deg);
74 | }
75 | }
76 | &:before {
77 | position: absolute;
78 | font-size: 12px;
79 | content: '\25BC';
80 | color: rgba(80, 80, 80, $opacity);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/cell-popover.ts:
--------------------------------------------------------------------------------
1 | import { ElementRef } from '@angular/core';
2 |
3 | export abstract class CellPopover {
4 | private theElement: any;
5 | constructor(element: ElementRef) {
6 | this.theElement = element.nativeElement;
7 | }
8 |
9 | /**
10 | * Get the location of the tooltip according to the location on the screen.
11 | * Returns one of the following:
12 | * "top left":
13 | * ------------------
14 | * | XXXX |
15 | * | XXXX |
16 | * | X |
17 | * | |
18 | * ------------------
19 | * "top right":
20 | * ------------------
21 | * | XXXX |
22 | * | XXXX |
23 | * | X |
24 | * | |
25 | * ------------------
26 | * "bottom left":
27 | * ------------------
28 | * | |
29 | * | XXXX X |
30 | * | XXXX |
31 | * ------------------
32 | * "bottom right":
33 | * ------------------
34 | * | |
35 | * | X XXXX |
36 | * | XXXX |
37 | * ------------------
38 | * @returns {string} direction
39 | */
40 | public getPosition(): string {
41 | let boundingRect = this.theElement.getBoundingClientRect();
42 | let halfWidth = window.innerWidth / 2;
43 | let halfHeight = window.innerHeight / 2;
44 | let position = [];
45 | if (boundingRect.right > halfWidth) position.push('left');
46 | else position.push('right');
47 | if (boundingRect.bottom > halfHeight) position.push('top');
48 | else position.push('bottom');
49 | return position.join(' ');
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/contextmenu/contextmenu.component.html:
--------------------------------------------------------------------------------
1 |
54 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/contextmenu/contextmenu.component.scss:
--------------------------------------------------------------------------------
1 | @import '../cell-popover.scss';
2 | @import '../../../../colors.scss';
3 | .contextmenu {
4 | .cover {
5 | z-index: 100;
6 | // background: red;
7 | // opacity: 0.5;
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | width: 100vw;
12 | height: 100vh;
13 | cursor: default;
14 | }
15 | .menu {
16 | cursor: default;
17 | @include cell-popover(0.85);
18 | @include adaptive-color('background', rgba(80, 80, 80, 0.85), rgba(color(cell-highlight-dark-color), 0.85));
19 | .contextMenu-section {
20 | &:not(:first-child) {
21 | border-top: 1px solid white;
22 | margin-top: 2px;
23 | padding-top: 2px;
24 | }
25 | div:not(.link-container) {
26 | padding: 3px;
27 | }
28 | .divider {
29 | border-top: 1px solid white;
30 | margin-top: 2px;
31 | padding: 2px 0 0 0 !important;
32 | }
33 | .contextMenu-button {
34 | cursor: pointer;
35 | &:hover {
36 | @include adaptive-color('background', color(cell-highlight-color), darken(color(cell-highlight-dark-color), 10%));
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/contextmenu/contextmenu.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, Output, EventEmitter, ElementRef, ViewEncapsulation } from '@angular/core';
2 | import { ContextMenuItem, Link, TechniqueVM, ViewModel } from '../../../classes';
3 | import { Technique, Tactic } from '../../../classes/stix';
4 | import { ViewModelsService } from '../../../services/viewmodels.service';
5 | import { ConfigService } from '../../../services/config.service';
6 | import { CellPopover } from '../cell-popover';
7 |
8 | @Component({
9 | selector: 'app-contextmenu',
10 | templateUrl: './contextmenu.component.html',
11 | styleUrls: ['./contextmenu.component.scss'],
12 | encapsulation: ViewEncapsulation.None,
13 | })
14 | export class ContextmenuComponent extends CellPopover implements OnInit {
15 | @Input() technique: Technique;
16 | @Input() tactic: Tactic;
17 | @Input() viewModel: ViewModel;
18 | public placement: string;
19 | @Output() close = new EventEmitter();
20 |
21 | public get techniqueVM(): TechniqueVM {
22 | return this.viewModel.getTechniqueVM(this.technique, this.tactic);
23 | }
24 |
25 | public get links(): Link[] {
26 | return this.techniqueVM.links;
27 | }
28 |
29 | constructor(
30 | private element: ElementRef,
31 | public configService: ConfigService,
32 | public viewModelsService: ViewModelsService
33 | ) {
34 | super(element);
35 | }
36 |
37 | ngOnInit() {
38 | this.placement = this.getPosition();
39 | }
40 |
41 | public closeContextmenu() {
42 | this.close.emit();
43 | }
44 |
45 | public select() {
46 | this.viewModel.clearSelectedTechniques();
47 | this.viewModel.selectTechnique(this.technique, this.tactic);
48 | this.closeContextmenu();
49 | }
50 |
51 | public addSelection() {
52 | this.viewModel.selectTechnique(this.technique, this.tactic);
53 | this.closeContextmenu();
54 | }
55 |
56 | public removeSelection() {
57 | this.viewModel.unselectTechnique(this.technique, this.tactic);
58 | this.closeContextmenu();
59 | }
60 |
61 | public selectAll() {
62 | this.viewModel.selectAllTechniques();
63 | this.closeContextmenu();
64 | }
65 |
66 | public deselectAll() {
67 | this.viewModel.clearSelectedTechniques();
68 | this.closeContextmenu();
69 | }
70 |
71 | public invertSelection() {
72 | this.viewModel.invertSelection();
73 | this.closeContextmenu();
74 | }
75 |
76 | public selectAnnotated() {
77 | this.viewModel.selectAnnotated();
78 | this.closeContextmenu();
79 | }
80 |
81 | public selectUnannotated() {
82 | this.viewModel.selectUnannotated();
83 | this.closeContextmenu();
84 | }
85 |
86 | public selectAllInTactic() {
87 | this.viewModel.selectAllTechniquesInTactic(this.tactic);
88 | this.closeContextmenu();
89 | }
90 |
91 | public deselectAllInTactic() {
92 | this.viewModel.unselectAllTechniquesInTactic(this.tactic);
93 | this.closeContextmenu();
94 | }
95 |
96 | public viewTechnique() {
97 | window.open(this.technique.url, '_blank');
98 | this.closeContextmenu();
99 | }
100 |
101 | public viewTactic() {
102 | window.open(this.tactic.url, '_blank');
103 | this.closeContextmenu();
104 | }
105 |
106 | public pinCell() {
107 | this.viewModelsService.pinnedCell =
108 | this.viewModelsService.pinnedCell === this.techniqueVM.technique_tactic_union_id ? '' : this.techniqueVM.technique_tactic_union_id;
109 | this.closeContextmenu();
110 | }
111 |
112 | public openCustomContextMenuItem(customItem: ContextMenuItem) {
113 | window.open(customItem.getReplacedURL(this.technique, this.tactic), '_blank');
114 | this.closeContextmenu();
115 | }
116 |
117 | public openLink(link: Link) {
118 | window.open(link.url);
119 | this.closeContextmenu();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/technique-cell.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ technique.attackID }}
5 |
6 | {{ technique.name }}
7 |
8 |
0">
9 |
10 | ({{ annotatedSubtechniques() }}/{{ applyControls(technique.subtechniques, tactic).length }})
11 |
12 |
13 |
14 |
15 |
21 |
22 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/technique-cell.component.scss:
--------------------------------------------------------------------------------
1 | @use 'sass:math' as math;
2 | @import '../matrix-common.scss';
3 | .technique-cell {
4 | @include adaptive-color('background-color', white, color(dark-2));
5 | @include adaptive-color('color', black, on-color(dark-3));
6 | cursor: pointer;
7 | height: 100%;
8 | display: flex;
9 | align-items: center;
10 |
11 | position: relative;
12 |
13 | font-size: $sizeunit - 1px;
14 | line-height: $sizeunit;
15 |
16 | min-width: $sizeunit;
17 | min-height: $sizeunit;
18 | @media print {
19 | font-size: $sizeunit_print - 5px;
20 | }
21 |
22 | > div {
23 | padding: math.div($sizeunit, 2) 3px;
24 | box-sizing: border-box;
25 | display: block;
26 | width: 100%;
27 | height: 100%;
28 | }
29 |
30 | &.showID:not(.showName) {
31 | text-align: center;
32 | }
33 |
34 | .id-name-break {
35 | outline-offset: -0.5px;
36 | outline: 1px solid rgba(border-color(body), 0.5);
37 | }
38 |
39 | &:not(.editing):not(.colored):not(.supertechniquecell) {
40 | @include adaptive-color('box-shadow', 0 0 0 1px border-color(body) inset, 0 0 0 1px border-color(dark-2) inset);
41 | }
42 |
43 | &.editing {
44 | @include adaptive-color('box-shadow', 0 0 0 1px black inset, 0 0 0 1px color(cell-highlight-dark-color) inset);
45 | }
46 |
47 | &.underlined {
48 | &.mini div:before {
49 | position: absolute;
50 | content: 'i';
51 | font-size: $sizeunit - 1px;
52 | text-align: center;
53 | width: 100%;
54 | top: 0;
55 | left: 0;
56 | }
57 | div span {
58 | border-bottom: 2px solid transparent;
59 | }
60 | }
61 |
62 | &.mini.disabled div:before {
63 | position: absolute;
64 | content: '\2715';
65 | font-size: $sizeunit -1px;
66 | text-align: center;
67 | width: 100%;
68 | top: 0;
69 | left: 0;
70 | }
71 |
72 | &.highlight {
73 | @include adaptive-color('background', color(cell-highlight-color), color(cell-highlight-dark-color));
74 | .id-name-break {
75 | outline: 1px solid rgba(black, 0.5);
76 | }
77 | }
78 |
79 | &.unannotated {
80 | .sub {
81 | color: gray;
82 | }
83 | }
84 | }
85 |
86 | .sub {
87 | position: relative;
88 | font-size: 75%;
89 | line-height: 0;
90 | vertical-align: baseline;
91 | bottom: -0.5em;
92 | @media print {
93 | display: none;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, EventEmitter, Output, ViewEncapsulation } from '@angular/core';
2 | import { DataService } from '../../services/data.service';
3 | import { Technique, Tactic, Matrix } from '../../classes/stix';
4 | import { ConfigService } from '../../services/config.service';
5 | import { Cell } from '../cell';
6 | import { ViewModelsService } from '../../services/viewmodels.service';
7 |
8 | @Component({
9 | selector: 'technique-cell',
10 | templateUrl: './technique-cell.component.html',
11 | styleUrls: ['./technique-cell.component.scss'],
12 | encapsulation: ViewEncapsulation.None,
13 | })
14 | export class TechniqueCellComponent extends Cell implements OnInit {
15 | @Input() matrix: Matrix;
16 | @Output() highlight = new EventEmitter(); // emit with the highlighted technique, or null to unhighlight
17 | @Output() unhighlight = new EventEmitter();
18 | @Output() leftclick = new EventEmitter(); // emit with the selected technique and the modifier keys
19 |
20 | public get isCellPinned(): boolean {
21 | return this.viewModelsService.pinnedCell === this.viewModel.getTechniqueVM(this.technique, this.tactic).technique_tactic_union_id;
22 | }
23 |
24 | public get showTooltip(): boolean {
25 | if (this.isCellPinned) return true;
26 | if (this.showContextmenu) return false;
27 | if (this.viewModel.highlightedTechniques.size === 0) return false;
28 |
29 | return (
30 | this.viewModel.highlightedTechnique === this.technique &&
31 | this.viewModel.highlightedTactic &&
32 | this.viewModel.highlightedTactic.id === this.tactic.id
33 | );
34 | }
35 |
36 | constructor(
37 | public dataService: DataService,
38 | public configService: ConfigService,
39 | public viewModelsService: ViewModelsService
40 | ) {
41 | super(dataService, configService);
42 | }
43 |
44 | ngOnInit(): void {
45 | // intentionally left blank
46 | }
47 |
48 | // count number of annotated sub-techniques on this technique
49 | public annotatedSubtechniques(): number {
50 | let annotatedSubs: Technique[] = [];
51 | for (let s of this.technique.subtechniques) {
52 | let subVM = this.viewModel.getTechniqueVM(s, this.tactic);
53 | if (subVM.annotated()) annotatedSubs.push(s);
54 | }
55 | return this.applyControls(annotatedSubs, this.tactic).length;
56 | }
57 |
58 | // sort and filter techniques
59 | public applyControls(techniques: Technique[], tactic: Tactic): Technique[] {
60 | return this.viewModel.applyControls(techniques, tactic, this.matrix);
61 | }
62 |
63 | // events to pass to parent component
64 | public onMouseEnter() {
65 | this.highlight.emit();
66 | }
67 | public onMouseLeave() {
68 | this.unhighlight.emit();
69 | }
70 | public onLeftClick(event) {
71 | if (!this.isCellPinned) this.viewModelsService.pinnedCell = '';
72 | if (this.configService.getFeature('selecting_techniques'))
73 | this.leftclick.emit({
74 | technique: this.technique,
75 | // modifier keys
76 | shift: event.shiftKey,
77 | ctrl: event.ctrlKey,
78 | meta: event.metaKey,
79 | // position of event on page
80 | x: event.pageX,
81 | y: event.pageY,
82 | });
83 | else this.onRightClick(event);
84 | }
85 | public onRightClick(event) {
86 | if (!this.isCellPinned) this.viewModelsService.pinnedCell = '';
87 | this.showContextmenu = true;
88 | }
89 |
90 | // return css classes for a technique
91 | public getClass(): string {
92 | let theclass = super.getClass();
93 |
94 | // classes by annotated sub-techniques
95 | if (!this.annotatedSubtechniques()) theclass += ' unannotated';
96 |
97 | if (this.isCellPinned) theclass += ' editing';
98 |
99 | return theclass;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/tooltip/tooltip.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ technique.name }} ({{ technique.attackID }})
6 |
7 | push_pin
8 |
9 |
10 |
11 |
12 | Disabled
13 |
14 |
15 | Score:
16 | {{ techniqueVM.score }}
17 |
18 |
19 | Aggregate Score ({{ viewModel.layout.aggregateFunction }}):
20 | {{ techniqueVM.aggregateScore }}
21 |
22 |
23 | Comment:
24 | {{ techniqueVM.comment }}
25 |
26 |
27 | {{ note.abstract }}:
28 | {{ note.content }}
29 |
30 |
31 |
32 | {{ metadata.name }}:
33 | {{ metadata.value }}
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/tooltip/tooltip.component.scss:
--------------------------------------------------------------------------------
1 | @import '../cell-popover.scss';
2 | @import '../../../../colors';
3 |
4 | .tooltip {
5 | @include cell-popover(0.75);
6 | @include adaptive-color('background', rgba(80, 80, 80, 0.75), rgba(#464dff, 0.75));
7 | max-height: 45vh;
8 | overflow: scroll;
9 | padding-bottom: 1em;
10 | z-index: 1000;
11 | cursor: default;
12 | table {
13 | width: 100%;
14 | border-collapse: collapse;
15 | td {
16 | padding: 3px;
17 | vertical-align: top;
18 | white-space: pre-line !important;
19 | }
20 | td:first-child {
21 | white-space: nowrap;
22 | padding-bottom: 3px !important;
23 | }
24 | td.wrap {
25 | white-space: normal;
26 | }
27 | tr:nth-child(2) td {
28 | border-top: 1px solid white;
29 | }
30 | tr .divider {
31 | margin: 0;
32 | padding: 0;
33 | hr {
34 | margin: 0;
35 | padding: 0;
36 | }
37 | }
38 | .technique-name span {
39 | vertical-align: middle;
40 | }
41 | .pin {
42 | @include adaptive-color('background-color', rgba(160, 160, 160, 0.75), rgba(#8f92fe, 0.75));
43 | border-radius: 4px;
44 | float: right;
45 | cursor: pointer;
46 |
47 | .mat-icon {
48 | font-size: 16px;
49 | height: 20px;
50 | width: 20px;
51 | display: flex;
52 | align-items: center;
53 | justify-content: center;
54 | }
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/nav-app/src/app/matrix/technique-cell/tooltip/tooltip.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Input, ElementRef, ViewEncapsulation } from '@angular/core';
2 | import { DataService } from '../../../services/data.service';
3 | import { Technique, Tactic, Note } from '../../../classes/stix';
4 | import { ViewModel, TechniqueVM } from '../../../classes';
5 | import { ViewModelsService } from '../../../services/viewmodels.service';
6 | import { CellPopover } from '../cell-popover';
7 |
8 | @Component({
9 | selector: 'app-tooltip',
10 | templateUrl: './tooltip.component.html',
11 | styleUrls: ['./tooltip.component.scss'],
12 | encapsulation: ViewEncapsulation.None,
13 | })
14 | export class TooltipComponent extends CellPopover implements OnInit {
15 | @Input() technique: Technique;
16 | @Input() tactic: Tactic;
17 | @Input() viewModel: ViewModel;
18 | public placement: string;
19 | public notes: Note[];
20 |
21 | public get isCellPinned(): boolean {
22 | return this.viewModelsService.pinnedCell === this.techniqueVM.technique_tactic_union_id;
23 | }
24 |
25 | public get techniqueVM(): TechniqueVM {
26 | return this.viewModel.getTechniqueVM(this.technique, this.tactic);
27 | }
28 |
29 | constructor(
30 | public element: ElementRef,
31 | public dataService: DataService,
32 | public viewModelsService: ViewModelsService
33 | ) {
34 | super(element);
35 | }
36 |
37 | ngOnInit() {
38 | this.placement = this.getPlacement();
39 | let domain = this.dataService.getDomain(this.viewModel.domainVersionID);
40 | this.notes = domain.notes.filter((note) => {
41 | return note.object_refs.includes(this.technique.id);
42 | });
43 | }
44 |
45 | public getPlacement(): string {
46 | return this.getPosition();
47 | }
48 |
49 | public unpin(): void {
50 | this.viewModelsService.pinnedCell = '';
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .search-and-multiselect {
4 | mat-label {
5 | padding-left: 5px;
6 | }
7 |
8 | .mat-mdc-form-field {
9 | @include adaptive-color-dark-only('color', on-color(dark));
10 | }
11 |
12 | .mat-mdc-input-element,
13 | .mat-mdc-input-element::placeholder {
14 | @include adaptive-color-dark-only('color', on-color(dark));
15 | }
16 |
17 | .mat-form-field-appearance-outline {
18 | width: 100%;
19 | }
20 |
21 | .sidebar-content {
22 | margin: 1rem 0;
23 | }
24 |
25 | .mat-content {
26 | display: block;
27 | }
28 |
29 | .settings .title {
30 | font-size: 14px;
31 | margin: 0 0 0 5px;
32 | }
33 |
34 | .fields {
35 | text-align: left;
36 | margin: 8px 0;
37 | .field {
38 | display: inline-block;
39 | }
40 | }
41 |
42 | h1 {
43 | font-size: 14px;
44 | background: color(panel-light);
45 | padding: 5px;
46 | margin: 0;
47 | text-align: center;
48 | }
49 |
50 | .allresults-buttons {
51 | display: flex;
52 | justify-content: space-around;
53 |
54 | button {
55 | width: 48.5%;
56 | }
57 | }
58 |
59 | button {
60 | @include adaptive-color-dark-only('background-color', color(dark-4));
61 | @include adaptive-color-dark-only('color', on-color(dark));
62 | }
63 |
64 | button:hover {
65 | @include adaptive-color('background', color(cell-highlight-color), color(dark-link));
66 | }
67 |
68 | .results {
69 | max-height: 150px;
70 | overflow-y: auto;
71 |
72 | table {
73 | border-collapse: collapse;
74 | width: 100%;
75 |
76 | tr + tr {
77 | @include adaptive-color('border-top', 1px solid color(panel-light), 1px solid color(dark-4));
78 | }
79 |
80 | tr:hover {
81 | @include adaptive-color('background', color(cell-highlight-color), color(cell-highlight-dark-color));
82 | }
83 |
84 | td:first-of-type {
85 | width: 200px;
86 | }
87 |
88 | td {
89 | text-align: left;
90 |
91 | & + td {
92 | width: 1px;
93 | }
94 | }
95 | }
96 |
97 | .no-results {
98 | padding: 5px;
99 | text-align: center;
100 | }
101 | }
102 |
103 | mat-panel-description {
104 | align-items: center;
105 | }
106 |
107 | .button-container {
108 | display: flex;
109 | justify-content: flex-end;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/nav-app/src/app/services/config.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { ConfigService } from './config.service';
4 | import * as MockData from '../../tests/utils/mock-data';
5 |
6 | describe('ConfigService', () => {
7 | let service: ConfigService;
8 |
9 | beforeEach(() => {
10 | TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule],
12 | providers: [ConfigService],
13 | });
14 | service = TestBed.inject(ConfigService);
15 | });
16 |
17 | it('should be created', () => {
18 | expect(service).toBeTruthy();
19 | });
20 |
21 | it('should set feature object', () => {
22 | expect(service.setFeature_object(MockData.configTechniqueControls)).toEqual(['disable_techniques', 'manual_color', 'background_color']);
23 | });
24 |
25 | it('should set single feature to given value', () => {
26 | expect(service.setFeature('sticky_toolbar', true)).toEqual(['sticky_toolbar']);
27 | });
28 |
29 | it('should get feature', () => {
30 | service.setFeature('sticky_toolbar', true);
31 | expect(service.getFeature('sticky_toolbar')).toBeTruthy();
32 | });
33 |
34 | it('should get feature group', () => {
35 | expect(service.getFeatureGroup('technique_controls')).toBeTruthy();
36 | service.setFeature_object(MockData.configTechniqueControls);
37 | expect(service.getFeatureGroup('technique_controls')).toBeTruthy();
38 | });
39 |
40 | it('should get feature group count', () => {
41 | expect(service.getFeatureGroupCount('technique_controls')).toEqual(-1);
42 | service.setFeature_object(MockData.configTechniqueControls);
43 | expect(service.getFeatureGroupCount('technique_controls')).toEqual(3);
44 | });
45 |
46 | it('should check if feature exists', () => {
47 | service.setFeature_object(MockData.configTechniqueControls);
48 | expect(service.isFeature('disable_techniques')).toBeTruthy();
49 | });
50 |
51 | it('should check if feature group exists', () => {
52 | service.setFeature_object(MockData.configTechniqueControls);
53 | expect(service.isFeatureGroup('technique_controls')).toBeTruthy();
54 | });
55 |
56 | it('should set features of the given group to provided value', () => {
57 | service.setFeature_object(MockData.configTechniqueControls);
58 | expect(service.setFeature('technique_controls', true)).toEqual(['technique_controls']);
59 | });
60 |
61 | it('should set features of the given group to the value object', () => {
62 | let value_object = { scoring: true, comments: false };
63 | expect(service.setFeature('technique_controls', value_object)).toEqual(['scoring', 'comments']);
64 | });
65 |
66 | it('should get all url fragments', () => {
67 | let fragments = new Map();
68 | expect(service.getAllFragments()).toEqual(fragments);
69 | fragments.set('comments', 'false');
70 | expect(service.getAllFragments('https://mitre-attack.github.io/attack-navigator/#comments=false')).toEqual(fragments);
71 | });
72 |
73 | it('should set up data in constructor', () => {
74 | expect(service).toBeTruthy();
75 | });
76 |
77 | it('should pass validation if only versions is configured', () => {
78 | expect(() => service.validateConfig(MockData.versionsConfig)).not.toThrow();
79 | });
80 |
81 | it('should pass validation if only collection index is configured', () => {
82 | expect(() => service.validateConfig(MockData.collectionIndexConfig)).not.toThrow();
83 | });
84 |
85 | it('should fail validation if collection_index_url is not a string', () => {
86 | expect(() => service.validateConfig(MockData.invalidTypeConfig)).toThrowError();
87 | });
88 |
89 | it('should fail validation if neither versions or collection index are configured', () => {
90 | expect(() => service.validateConfig(MockData.invalidConfig)).toThrowError();
91 | });
92 |
93 | it('should return config if validation passes', () => {
94 | expect(() => service.validateConfig(MockData.customConfig)).not.toThrow();
95 | expect(service.validateConfig(MockData.customConfig)).toEqual(MockData.customConfig);
96 | });
97 | });
98 |
--------------------------------------------------------------------------------
/nav-app/src/app/services/icons.service.spec.ts:
--------------------------------------------------------------------------------
1 | import { TestBed } from '@angular/core/testing';
2 |
3 | import { Icons, IconsService } from './icons.service';
4 |
5 | describe('IconsService', () => {
6 | let service: IconsService;
7 |
8 | beforeEach(() => {
9 | TestBed.configureTestingModule({});
10 | service = TestBed.inject(IconsService);
11 | });
12 |
13 | it('should be created', () => {
14 | expect(service).toBeTruthy();
15 | });
16 |
17 | it('should register', () => {
18 | spyOn(service.matIconRegistry, 'addSvgIcon');
19 | service.registerIcons();
20 | expect(service.matIconRegistry.addSvgIcon).toHaveBeenCalledTimes(Object.values(Icons).length);
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/nav-app/src/app/services/icons.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { MatIconRegistry } from '@angular/material/icon';
3 | import { DomSanitizer } from '@angular/platform-browser';
4 |
5 | @Injectable({
6 | providedIn: 'root',
7 | })
8 | export class IconsService {
9 | constructor(
10 | public matIconRegistry: MatIconRegistry,
11 | private domSanitizer: DomSanitizer
12 | ) {
13 | // intentionally left blank
14 | }
15 |
16 | public registerIcons(): void {
17 | this.loadIcons(Object.values(Icons), 'assets/icons');
18 | }
19 |
20 | private loadIcons(iconKeys: string[], iconUrl: string): void {
21 | iconKeys.forEach((key) => {
22 | this.matIconRegistry.addSvgIcon(key, this.domSanitizer.bypassSecurityTrustResourceUrl(`${iconUrl}/${key}.svg`));
23 | });
24 | }
25 | }
26 |
27 | export enum Icons {
28 | SORT_ALPHABETICAL_ASC = 'ic_sort_alphabetically_ascending',
29 | SORT_ALPHABETICAL_DESC = 'ic_sort_alphabetically_descending',
30 | SORT_NUMERICAL_ASC = 'ic_sort_numerically_ascending',
31 | SORT_NUMERICAL_DESC = 'ic_sort_numerically_descending',
32 | UNFOLD_MORE_ALT = 'ic_unfold_more_alt',
33 | NON_STICKY_TOOLBAR = 'ic_push_pin_gray',
34 | }
35 |
--------------------------------------------------------------------------------
/nav-app/src/app/sidebar/sidebar.component.html:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/nav-app/src/app/sidebar/sidebar.component.scss:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | width: 38em;
3 | padding: 16px;
4 | }
--------------------------------------------------------------------------------
/nav-app/src/app/sidebar/sidebar.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { SidebarComponent } from './sidebar.component';
4 |
5 | describe('SidebarComponent', () => {
6 | let component: SidebarComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(async () => {
10 | await TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule],
12 | declarations: [SidebarComponent],
13 | }).compileComponents();
14 | });
15 |
16 | beforeEach(() => {
17 | fixture = TestBed.createComponent(SidebarComponent);
18 | component = fixture.componentInstance;
19 | fixture.detectChanges();
20 | });
21 |
22 | it('should create', () => {
23 | expect(component).toBeTruthy();
24 | });
25 |
26 | it('should set reloadToggle to false', () => {
27 | component.ngOnChanges();
28 | expect(component.reloadToggle).toEqual(false);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/nav-app/src/app/sidebar/sidebar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Input, OnChanges } from '@angular/core';
2 | import { DataService } from '../services/data.service';
3 | import { ViewModel } from '../classes';
4 |
5 | @Component({
6 | selector: 'sidebar',
7 | templateUrl: './sidebar.component.html',
8 | styleUrls: ['./sidebar.component.scss'],
9 | })
10 | export class SidebarComponent implements OnChanges {
11 | @Input() viewModel: ViewModel;
12 | public reloadToggle: boolean = true;
13 |
14 | constructor(public dataService: DataService) {}
15 |
16 | ngOnChanges(): void {
17 | this.reloadToggle = false;
18 | setTimeout(() => (this.reloadToggle = true));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/nav-app/src/app/svg-export/renderable-objects/index.ts:
--------------------------------------------------------------------------------
1 | export { RenderableMatrix } from './renderable-matrix';
2 | export { RenderableTactic } from './renderable-tactic';
3 | export { RenderableTechnique } from './renderable-technique';
4 |
--------------------------------------------------------------------------------
/nav-app/src/app/svg-export/renderable-objects/renderable-matrix.ts:
--------------------------------------------------------------------------------
1 | import { Matrix } from '../../classes/stix';
2 | import { RenderableTactic } from './renderable-tactic';
3 | import { ViewModel } from '../../classes';
4 |
5 | export class RenderableMatrix {
6 | public matrix: Matrix;
7 | public tactics: RenderableTactic[] = [];
8 |
9 | public get height() {
10 | let heights = this.tactics.map(function (tactic: RenderableTactic) {
11 | return tactic.height;
12 | });
13 | return Math.max(...heights);
14 | }
15 |
16 | constructor(matrix: Matrix, viewModel: ViewModel, renderConfig: any) {
17 | this.matrix = matrix;
18 | let filteredTactics = viewModel.filterTactics(matrix.tactics, matrix);
19 | for (let tactic of filteredTactics) {
20 | this.tactics.push(new RenderableTactic(tactic, matrix, viewModel, renderConfig));
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nav-app/src/app/svg-export/renderable-objects/renderable-tactic.ts:
--------------------------------------------------------------------------------
1 | import { Matrix, Tactic } from '../../classes/stix';
2 | import { RenderableTechnique } from './renderable-technique';
3 | import { ViewModel } from '../../classes';
4 |
5 | export class RenderableTactic {
6 | public readonly tactic: Tactic;
7 | public readonly techniques: RenderableTechnique[] = [];
8 | public readonly subtechniques: RenderableTechnique[] = [];
9 | public height: number;
10 |
11 | constructor(tactic: Tactic, matrix: Matrix, viewModel: ViewModel, renderConfig: any) {
12 | this.tactic = tactic;
13 | let filteredTechniques = viewModel.sortTechniques(viewModel.filterTechniques(tactic.techniques, tactic, matrix), tactic);
14 | let yPosition = 1; // start at 1 to make space for tactic label
15 | for (let technique of filteredTechniques) {
16 | let techniqueVM = viewModel.getTechniqueVM(technique, tactic);
17 | let filteredSubtechniques = viewModel.filterTechniques(technique.subtechniques, tactic, matrix);
18 |
19 | let showSubtechniques =
20 | renderConfig.showSubtechniques == 'all' || (renderConfig.showSubtechniques == 'expanded' && techniqueVM.showSubtechniques);
21 |
22 | this.techniques.push(new RenderableTechnique(yPosition++, technique, tactic, matrix, viewModel, showSubtechniques));
23 |
24 | if (filteredSubtechniques.length > 0 && showSubtechniques) {
25 | for (let subtechnique of filteredSubtechniques) {
26 | this.subtechniques.push(new RenderableTechnique(yPosition++, subtechnique, tactic, matrix, viewModel, renderConfig));
27 | }
28 | }
29 | }
30 | this.height = yPosition;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/nav-app/src/app/svg-export/renderable-objects/renderable-technique.ts:
--------------------------------------------------------------------------------
1 | import { Matrix, Tactic, Technique } from '../../classes/stix';
2 | import { TechniqueVM, ViewModel } from '../../classes';
3 | import tinycolor from 'tinycolor2';
4 |
5 | export class RenderableTechnique {
6 | public readonly yPosition: number;
7 | public readonly technique: Technique;
8 | public readonly tactic: Tactic;
9 | public readonly matrix: Matrix;
10 | public readonly showSubtechniques;
11 | public readonly viewModel: ViewModel;
12 |
13 | constructor(yPosition, technique: Technique, tactic: Tactic, matrix: Matrix, viewModel: ViewModel, showSubtechniques = false) {
14 | this.yPosition = yPosition;
15 | this.technique = technique;
16 | this.tactic = tactic;
17 | this.matrix = matrix;
18 | this.viewModel = viewModel;
19 | this.showSubtechniques = showSubtechniques;
20 | }
21 |
22 | public get fill() {
23 | if (this.viewModel.hasTechniqueVM(this.technique, this.tactic)) {
24 | let techniqueVM: TechniqueVM = this.viewModel.getTechniqueVM(this.technique, this.tactic);
25 | if (!techniqueVM.enabled) return 'white';
26 | if (techniqueVM.color) return techniqueVM.color;
27 | if (this.viewModel.layout.showAggregateScores && techniqueVM.aggregateScoreColor) return techniqueVM.aggregateScoreColor;
28 | if (techniqueVM.score) return techniqueVM.scoreColor;
29 | }
30 | return null; //default
31 | }
32 |
33 | public get textColor() {
34 | if (this.viewModel.hasTechniqueVM(this.technique, this.tactic)) {
35 | let techniqueVM: TechniqueVM = this.viewModel.getTechniqueVM(this.technique, this.tactic);
36 | if (!techniqueVM.enabled) return '#aaaaaa';
37 | }
38 | if (this.fill) {
39 | return tinycolor.mostReadable(this.fill, ['white', 'black']); //default;
40 | }
41 | return null;
42 | }
43 |
44 | public get text() {
45 | let text = [];
46 | if (this.viewModel.layout.showID) text.push(this.technique.attackID);
47 | if (this.viewModel.layout.showName) text.push(this.technique.name);
48 | return text.join(': ');
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/nav-app/src/app/svg-export/svg-export.component.scss:
--------------------------------------------------------------------------------
1 | @import '../../colors.scss';
2 |
3 | .svgcontainer {
4 | overflow-x: auto;
5 | text-align: center;
6 | min-height: 60vh;
7 | max-height: 60vh;
8 | min-width: 75vw;
9 | max-width: 75vw;
10 | padding: 25px;
11 |
12 | &.dark-mode {
13 | background-color: color(dark-1) !important;
14 | }
15 | }
16 |
17 | .dropdown-container {
18 | padding: 10px;
19 | top: 34px;
20 | ul {
21 | padding-left: 0;
22 | li {
23 | list-style: none;
24 | text-align: left;
25 | }
26 | }
27 | }
28 |
29 | .iewarning {
30 | color: red;
31 | }
32 |
--------------------------------------------------------------------------------
/nav-app/src/app/utils/cookies.ts:
--------------------------------------------------------------------------------
1 | // utilities for working with browser cookies
2 |
3 | /**
4 | * Set a cookie
5 | * @param {string} key key to set under
6 | * @param {string} value value to set under key
7 | * @param {number} expirationDays when cookie expires in days
8 | */
9 | const setCookie = function (key: string, value: string, expirationDays: number) {
10 | let d = new Date();
11 | d.setTime(d.getTime() + expirationDays * 24 * 60 * 60 * 1000);
12 | let expires = 'expires=' + d.toUTCString();
13 | document.cookie = key + '=' + value + ';' + expires + ';path=/;SameSite=Strict';
14 | };
15 |
16 | /**
17 | * Get the value of the cookie under the given key
18 | * @param {string} key to retrieve from
19 | * @return {string} cookie value
20 | */
21 | const getCookie = function (key: string): string {
22 | let name = key + '=';
23 | let decodedCookie = decodeURIComponent(document.cookie);
24 | let ca = decodedCookie.split(';');
25 |
26 | for (let c of ca) {
27 | while (c.startsWith(' ')) {
28 | c = c.substring(1);
29 | }
30 | if (c.indexOf(name) == 0) {
31 | return c.substring(name.length, c.length);
32 | }
33 | }
34 | return '';
35 | };
36 | /**
37 | * Does a cookie exist under the given key?
38 | * @param {string} key to check
39 | * @return {boolean} true if cookie is stored under key, false otherwise
40 | */
41 | const hasCookie = function (key: string): boolean {
42 | return getCookie(key) !== '';
43 | };
44 |
45 | /**
46 | * Delete the given cookie
47 | * @param {string} key to delete
48 | */
49 | const deleteCookie = function (key: string) {
50 | document.cookie = key + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/;SameSite=Strict';
51 | };
52 |
53 | export { setCookie, getCookie, hasCookie, deleteCookie };
54 |
--------------------------------------------------------------------------------
/nav-app/src/app/utils/globals.ts:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | import appPackageInfo from '../../../package.json';
3 |
4 | export const navVersion: string = appPackageInfo.version;
5 | export const layerVersion: string = '4.5';
6 | export const minimumSupportedVersion: string = '4.0';
7 |
--------------------------------------------------------------------------------
/nav-app/src/app/utils/navigator.d.ts:
--------------------------------------------------------------------------------
1 | // declaration merging workaround for msSaveOrOpenBlob function that was
2 | // marked for removal as a non-standard feature due to the removal of IE support
3 | // msSaveorOpenBlob removal issued: https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1029
4 | // workaround pulled from: https://github.com/microsoft/TypeScript/issues/45612
5 | interface Navigator {
6 | msSaveOrOpenBlob: (blobOrBase64: Blob | string, filename: string) => boolean;
7 | }
8 |
--------------------------------------------------------------------------------
/nav-app/src/app/utils/utils.ts:
--------------------------------------------------------------------------------
1 | // utils.ts
2 | import { detect } from "detect-browser";
3 |
4 | let comparatorFn = {
5 | '<': function(a, b) { return a < b; },
6 | '<=': function(a, b) { return a <= b; },
7 | '>': function(a, b) { return a > b; },
8 | '>=': function(a, b) { return a >= b; }
9 | };
10 |
11 | export function isBoolean(value: any): boolean {
12 | return typeof value === 'boolean';
13 | }
14 |
15 | export function isNumber(value: any): boolean {
16 | return typeof value === 'number';
17 | }
18 |
19 | export function isIE(): boolean {
20 | const browser = detect();
21 | return browser.name == 'ie';
22 | }
23 |
24 | export function isSafari(compRange): boolean {
25 | function compare(version, comp) {
26 | let str = (comp + '');
27 | let n = +(/\d+/.exec(str) || NaN);
28 | let op = /^[<>]=?/.exec(str)[0];
29 | return comparatorFn[op] ? comparatorFn[op](version, n) : (version == n || Number.isNaN(n));
30 | }
31 |
32 | const browser = detect();
33 | return browser.name == 'safari' && compare(browser.version.split('.')[0], compRange);
34 | }
35 |
--------------------------------------------------------------------------------
/nav-app/src/app/version-upgrade/version-upgrade.component.html:
--------------------------------------------------------------------------------
1 |
2 |
Upgrade layer version?
3 |
4 |
5 | The layer "{{ layerName }}" uses an outdated version of ATT&CK (v{{ vmVersion }}). Do you want to open the workflow to upgrade this
6 | layer to ATT&CK v{{ currVersion }}?
7 |
8 | This version is not supported by Navigator v{{ navVersion }} and must be upgraded for use.
9 |
10 |
11 |
12 |
13 | Yes
14 | No
15 |
16 |
17 |
--------------------------------------------------------------------------------
/nav-app/src/app/version-upgrade/version-upgrade.component.scss:
--------------------------------------------------------------------------------
1 | .mat-dialog {
2 | text-align: center;
3 | .mat-mdc-dialog-actions {
4 | display: inline-block;
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/nav-app/src/app/version-upgrade/version-upgrade.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2 | import { HttpClientTestingModule } from '@angular/common/http/testing';
3 | import { VersionUpgradeComponent } from './version-upgrade.component';
4 | import { MatDialogModule, MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
5 | describe('VersionUpgradeComponent', () => {
6 | let component: VersionUpgradeComponent;
7 | let fixture: ComponentFixture;
8 |
9 | beforeEach(waitForAsync(() => {
10 | TestBed.configureTestingModule({
11 | imports: [HttpClientTestingModule, MatDialogModule],
12 | declarations: [VersionUpgradeComponent],
13 | providers: [
14 | {
15 | provide: MatDialogRef,
16 | useValue: {
17 | close() {
18 | return {};
19 | },
20 | },
21 | },
22 | {
23 | provide: MAT_DIALOG_DATA,
24 | useValue: {},
25 | },
26 | ],
27 | }).compileComponents();
28 | }));
29 |
30 | beforeEach(() => {
31 | fixture = TestBed.createComponent(VersionUpgradeComponent);
32 | component = fixture.componentInstance;
33 | component.data.currVersion = '4.9';
34 | component.data.vmVersion = '4.8';
35 | component.data.layerName = 'test1';
36 | fixture.detectChanges();
37 | });
38 |
39 | it('should create', () => {
40 | expect(component).toBeTruthy();
41 | });
42 |
43 | it('should upgrade version', () => {
44 | const openDialogSpy = spyOn(component.dialogRef, 'close');
45 | component.upgradeVersion(true);
46 | expect(openDialogSpy).toHaveBeenCalledWith({
47 | upgrade: true,
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/nav-app/src/app/version-upgrade/version-upgrade.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject } from '@angular/core';
2 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
3 | import { ViewModelsService } from '../services/viewmodels.service';
4 | import { DataService } from '../services/data.service';
5 | import * as globals from '../utils/globals';
6 |
7 | @Component({
8 | selector: 'app-version-upgrade',
9 | templateUrl: './version-upgrade.component.html',
10 | styleUrls: ['./version-upgrade.component.scss'],
11 | providers: [ViewModelsService],
12 | })
13 | export class VersionUpgradeComponent implements OnInit {
14 | navVersion = globals.navVersion;
15 | currVersion: string;
16 | vmVersion: string;
17 | layerName: string;
18 |
19 | constructor(
20 | public dialogRef: MatDialogRef,
21 | public dataService: DataService,
22 | private viewModelsService: ViewModelsService,
23 | @Inject(MAT_DIALOG_DATA) public data
24 | ) {}
25 |
26 | ngOnInit() {
27 | this.currVersion = this.data.currVersion;
28 | this.vmVersion = this.data.vmVersion;
29 | this.layerName = this.data.layerName;
30 | }
31 |
32 | upgradeVersion(upgrade: boolean) {
33 | this.dialogRef.close({ upgrade: upgrade });
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/MaterialIcons-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitre-attack/attack-navigator/5f3c6ad136927b43cb15af330aa2858ababafebb/nav-app/src/assets/icons/MaterialIcons-Regular.ttf
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/baseline-grid_on-24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_camera_alt_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_check_box_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_check_box_outline_blank_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_clear_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_clear_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_close_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_color_lens_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_content_copy_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_description_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_done_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_done_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_exportAllExcel_black_24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_exportAllJson_black_24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_exportJson_black_24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_file_download_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_file_upload_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_file_upload_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_filter_list_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_format_color_fill_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_format_color_fill_black_nobottom_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_format_color_fill_gray_nobottom_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_format_size_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_insert_chart_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_insert_chart_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_insert_comment_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_insert_comment_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_keyboard_arrow_down_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_keyboard_arrow_right_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_keyboard_arrow_up_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_layers_clear_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_layers_clear_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_link_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_link_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_lock_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_lock_open_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_metadata_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_metadata_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_palette_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_photo_size_select_large_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_playlist_add_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_push_pin_black_24px.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_push_pin_gray.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_remove_circle_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_save_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_save_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_search_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_search_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_alphabetically_ascending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | A
7 | Z
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_alphabetically_ascending_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | A
7 | Z
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_alphabetically_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | A
7 |
8 |
9 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_alphabetically_descending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Z
7 | A
8 |
9 |
10 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_alphabetically_descending_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | Z
7 | A
8 |
9 |
10 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_numerically_ascending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | 1
7 | 2
8 |
9 |
10 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_numerically_ascending_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | 1
7 | 2
8 |
9 |
10 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_numerically_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | #
7 |
8 |
9 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_numerically_descending.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | 2
7 | 1
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_sort_numerically_descending_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | 2
7 | 1
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_texture_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_texture_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_unfold_more_alt.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_view_large_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_view_list_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_view_list_grey_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_view_medium_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_view_small_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_visibility_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_visibility_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/ic_visibility_off_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/table_view_FILL0_wght400_GRAD0_opsz24.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_less_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_less_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_more_alt_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_more_alt_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_more_black_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/icons/unfold_more_gray_24px.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/nav-app/src/assets/image_scoreVariableExample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitre-attack/attack-navigator/5f3c6ad136927b43cb15af330aa2858ababafebb/nav-app/src/assets/image_scoreVariableExample.png
--------------------------------------------------------------------------------
/nav-app/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/nav-app/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // The file contents for the current environment will overwrite these during build.
2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do
3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead.
4 | // The list of which env maps to which file can be found in `.angular-cli.json`.
5 |
6 | export const environment = {
7 | production: false,
8 | };
9 |
--------------------------------------------------------------------------------
/nav-app/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitre-attack/attack-navigator/5f3c6ad136927b43cb15af330aa2858ababafebb/nav-app/src/favicon.ico
--------------------------------------------------------------------------------
/nav-app/src/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mitre-attack/attack-navigator/5f3c6ad136927b43cb15af330aa2858ababafebb/nav-app/src/favicon.png
--------------------------------------------------------------------------------
/nav-app/src/index.google-analytics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/nav-app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/nav-app/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 |
4 | import { AppModule } from './app/app.module';
5 | import { environment } from './environments/environment';
6 |
7 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic()
12 | .bootstrapModule(AppModule)
13 | .catch((err) => console.log(err));
14 |
--------------------------------------------------------------------------------
/nav-app/src/polyfills.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * This file includes polyfills needed by Angular and is loaded before the app.
3 | * You can add your own extra polyfills to this file.
4 | *
5 | * This file is divided into 2 sections:
6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main
8 | * file.
9 | *
10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13 | *
14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
15 | */
16 |
17 | /***************************************************************************************************
18 | * BROWSER POLYFILLS
19 | */
20 |
21 | /** IE9, IE10 and IE11 requires all of the following polyfills. **/
22 | import 'core-js/es/symbol';
23 | import 'core-js/es/object';
24 | import 'core-js/es/function';
25 | import 'core-js/es/parse-int';
26 | import 'core-js/es/parse-float';
27 | import 'core-js/es/number';
28 | import 'core-js/es/math';
29 | import 'core-js/es/string';
30 | import 'core-js/es/date';
31 | import 'core-js/es/array';
32 | import 'core-js/es/regexp';
33 | import 'core-js/es/map';
34 | import 'core-js/es/weak-map';
35 | import 'core-js/es/set';
36 |
37 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */
38 | import 'classlist.js'; // Run `npm install --save classlist.js`.
39 |
40 | /** IE10 and IE11 requires the following for the Reflect API. */
41 | import 'core-js/es/reflect';
42 |
43 | /** Evergreen browsers require these. **/
44 | // Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
45 |
46 | /**
47 | * Required to support Web Animations `@angular/platform-browser/animations`.
48 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
49 | **/
50 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`.
51 |
52 | /***************************************************************************************************
53 | * Zone JS is required by Angular itself.
54 | */
55 | import 'zone.js'; // Included with Angular CLI.
56 |
57 | /***************************************************************************************************
58 | * APPLICATION IMPORTS
59 | */
60 |
61 | /**
62 | * Date, currency, decimal and percent pipes.
63 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
64 | */
65 | // import 'intl'; // Run `npm install --save intl`.
66 | /**
67 | * Need to import at least one locale-data with intl.
68 | */
69 | // import 'intl/locale-data/jsonp/en';
70 | if (!Element.prototype.matches) {
71 | Element.prototype.matches = (Element.prototype).msMatchesSelector || Element.prototype.webkitMatchesSelector;
72 | }
73 |
--------------------------------------------------------------------------------
/nav-app/src/test.ts:
--------------------------------------------------------------------------------
1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files
2 |
3 | import 'zone.js';
4 | import 'zone.js/testing';
5 | import { getTestBed } from '@angular/core/testing';
6 | import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing';
7 |
8 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
9 | declare const __karma__: any;
10 |
11 | // Prevent Karma from running prematurely.
12 | __karma__.loaded = function () {};
13 |
14 | // First, initialize the Angular testing environment.
15 | getTestBed().initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
16 | // Finally, start Karma to run the tests.
17 | __karma__.start();
18 |
--------------------------------------------------------------------------------
/nav-app/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/nav-app/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/app",
5 | "types": []
6 | },
7 | "files": [
8 | "src/main.ts",
9 | "src/polyfills.ts",
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/nav-app/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compileOnSave": false,
3 | "compilerOptions": {
4 | "outDir": "./dist/out-tsc",
5 | "sourceMap": true,
6 | "declaration": false,
7 | "moduleResolution": "node",
8 | "experimentalDecorators": true,
9 | "target": "ES2022",
10 | "typeRoots": [
11 | "node_modules/@types"
12 | ],
13 | "lib": [
14 | "es2017",
15 | "dom"
16 | ],
17 | "module": "es2020",
18 | "baseUrl": "./",
19 | "resolveJsonModule": true,
20 | "allowSyntheticDefaultImports": true,
21 | "useDefineForClassFields": false
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nav-app/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "./out-tsc/spec",
5 | "types": [
6 | "jasmine",
7 | "node"
8 | ]
9 | },
10 | "files": ["src/test.ts", "src/polyfills.ts"],
11 | "include": [
12 | "src/**/*.spec.ts",
13 | "src/**/*.d.ts"
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/nav-app/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "rulesDirectory": ["node_modules/codelyzer"],
3 | "rules": {
4 | "arrow-return-shorthand": true,
5 | "callable-types": true,
6 | "class-name": true,
7 | "comment-format": [true, "check-space"],
8 | "curly": true,
9 | "eofline": true,
10 | "forin": true,
11 | "deprecation": {
12 | "severity": "warning"
13 | },
14 | "import-blacklist": [true, "rxjs/Rx"],
15 | "import-spacing": true,
16 | "indent": [true, "spaces"],
17 | "interface-over-type-literal": true,
18 | "label-position": true,
19 | "max-line-length": [true, 140],
20 | "member-access": false,
21 | "member-ordering": [
22 | true,
23 | {
24 | "order": ["static-field", "instance-field", "static-method", "instance-method"]
25 | }
26 | ],
27 | "no-arg": true,
28 | "no-bitwise": true,
29 | "no-console": [true, "debug", "info", "time", "timeEnd", "trace"],
30 | "no-construct": true,
31 | "no-debugger": true,
32 | "no-duplicate-super": true,
33 | "no-empty": false,
34 | "no-empty-interface": true,
35 | "no-eval": true,
36 | "no-inferrable-types": [true, "ignore-params"],
37 | "no-misused-new": true,
38 | "no-non-null-assertion": true,
39 | "no-shadowed-variable": true,
40 | "no-string-literal": false,
41 | "no-string-throw": true,
42 | "no-switch-case-fall-through": true,
43 | "no-trailing-whitespace": true,
44 | "no-unnecessary-initializer": true,
45 | "no-unused-expression": true,
46 | "no-var-keyword": true,
47 | "object-literal-sort-keys": false,
48 | "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"],
49 | "prefer-const": true,
50 | "quotemark": [true, "single"],
51 | "radix": true,
52 | "semicolon": [true, "always"],
53 | "triple-equals": [true, "allow-null-check"],
54 | "typedef-whitespace": [
55 | true,
56 | {
57 | "call-signature": "nospace",
58 | "index-signature": "nospace",
59 | "parameter": "nospace",
60 | "property-declaration": "nospace",
61 | "variable-declaration": "nospace"
62 | }
63 | ],
64 | "typeof-compare": true,
65 | "unified-signatures": true,
66 | "variable-name": false,
67 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"],
68 | "directive-selector": [true, "attribute", "app", "camelCase"],
69 | "component-selector": [true, "element", "app", "kebab-case"],
70 | "use-input-property-decorator": true,
71 | "use-output-property-decorator": true,
72 | "use-host-property-decorator": true,
73 | "no-input-rename": true,
74 | "no-output-rename": true,
75 | "use-life-cycle-interface": true,
76 | "use-pipe-transform-interface": true,
77 | "component-class-suffix": true,
78 | "directive-class-suffix": true,
79 | "invoke-injectable": true
80 | }
81 | }
82 |
--------------------------------------------------------------------------------