From 756d02c7283cea4d2920dd26c1b0a17dc01043ee Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 22 Apr 2021 15:46:36 -0400 Subject: [PATCH 01/65] compare stix bundle versions when user upgrades layer version on import --- nav-app/src/app/tabs/tabs.component.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index e4ac4d7ed..c8385ed5f 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -3,6 +3,7 @@ import { Component, AfterContentInit, ViewChild, TemplateRef, AfterViewInit } fr import { DataService, Technique } from '../data.service'; //import the DataService component so we can use it import { ConfigService } from '../config.service'; import * as is from 'is_js'; +import { forkJoin } from 'rxjs'; import { VersionUpgradeComponent } from '../version-upgrade/version-upgrade.component'; import { HelpComponent } from '../help/help.component'; import { ExporterComponent } from '../exporter/exporter.component'; @@ -487,8 +488,10 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { this.alwaysUpgradeVersion = result.upgrade; } if (result.upgrade) { + let prevDomainID = viewModel.domainID; viewModel.version = currVersion viewModel.domainID = this.dataService.getDomainID(viewModel.domain, viewModel.version); + resolve({previous: prevDomainID, latest: viewModel.domainID}); } resolve(null); }, @@ -530,9 +533,26 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { if (!this.dataService.getDomain(viewModel.domainID)) { throw {message: "Error: '" + viewModel.domain + "' (" + viewModel.version + ") is an invalid domain."}; } - this.versionUpgradeDialog(viewModel).then( () => { + this.versionUpgradeDialog(viewModel).then( (versions) => { + //TODO: don't automatically transfer over the annotations this.openTab("new layer", viewModel, true, true, true, true); - if (!this.dataService.getDomain(viewModel.domainID).dataLoaded) { + if (versions) { // user upgraded to latest version + let loads: any = {}; + if (!this.dataService.getDomain(versions.previous).dataLoaded) loads.previous = this.dataService.loadDomainData(versions.previous, true, true); + if (!this.dataService.getDomain(versions.latest).dataLoaded) loads.latest = this.dataService.loadDomainData(versions.latest, true, true); + + // load layer version & latest ATT&CK version + let dataSubscription = forkJoin(loads).subscribe({ + next: (res) => { + // TODO: diff_stix stuff + this.dataService.compareVersions(versions.previous, versions.latest); + viewModel.deSerialize(string); + viewModel.loadVMData(); + }, + complete: () => { dataSubscription.unsubscribe(); } + }); + } + else if (!this.dataService.getDomain(viewModel.domainID).dataLoaded) { this.dataService.loadDomainData(viewModel.domainID, true).then( () => { viewModel.deSerialize(string); viewModel.loadVMData(); From c4d9b506c583ae28afc6b2e226b5987e89a40b27 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 22 Apr 2021 15:49:33 -0400 Subject: [PATCH 02/65] option to include revoked/deprecated objects when parsing a bundle; created object in which to store stix bundle changes --- nav-app/src/app/data.service.ts | 86 +++++++++++++++++++++++--- nav-app/src/app/tabs/tabs.component.ts | 2 - 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index 56bc3e26d..2f6e5fdb2 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -41,7 +41,7 @@ export class DataService { * Parse the given stix bundle into the relevant data holders * @param {any[]} stixBundle: the STIX bundle to parse */ - parseBundle(domain: Domain, stixBundles: any[]): void { + parseBundle(domain: Domain, stixBundles: any[], includeAll: boolean = false): void { let platforms = new Set(); for (let bundle of stixBundles) { let techniqueSDOs = []; @@ -49,8 +49,9 @@ export class DataService { let idToTechniqueSDO = new Map(); let idToTacticSDO = new Map(); for (let sdo of bundle.objects) { //iterate through stix domain objects in the bundle - // ignore deprecated and revoked objects in the bundle - if (sdo.x_mitre_deprecated || sdo.revoked) continue; + // ignore deprecated and revoked objects in the bundle? + if (!includeAll && (sdo.x_mitre_deprecated || sdo.revoked)) continue; + // parse according to type switch(sdo.type) { case "intrusion-set": @@ -141,8 +142,10 @@ export class DataService { // parse platforms for (let technique of domain.techniques) { - for (let platform of technique.platforms) { - platforms.add(platform) + if (technique.platforms) { + for (let platform of technique.platforms) { + platforms.add(platform) + } } } for (let subtechnique of domain.subtechniques) { @@ -252,13 +255,13 @@ export class DataService { /** * Load and parse domain data */ - loadDomainData(domainID: string, refresh: boolean = false): Promise { + loadDomainData(domainID: string, includeAll: boolean = false, refresh: boolean = false): Promise { let dataPromise: Promise = new Promise((resolve, reject) => { let domain = this.getDomain(domainID); if (domain) { let subscription = this.getDomainData(domain, refresh).subscribe({ next: (data: Object[]) => { - this.parseBundle(domain, data); + this.parseBundle(domain, data, includeAll); resolve(null); }, complete: () => { if (subscription) subscription.unsubscribe(); } //prevent memory leaks @@ -300,6 +303,37 @@ export class DataService { isSupported(version: string) { return version.match(/[0-9]/g)[0] < this.versions[this.versions.length - 1].match(/[0-9]/g)[0]? false : true; } + + /** + * Compare two ATT&CK versions and return a set of object changes + * @param previous imported layer version to upgrade from + * @param latest latest ATT&CK version to upgrade to + */ + public compareVersions(previous: string, latest: string): VersionChangelog { + console.log("generating version changelog"); + let changelog = new VersionChangelog(previous, latest); + let previousTechniques = this.getDomain(previous).techniques; + let latestTechniques = this.getDomain(latest).techniques; + // console.log("previous techniques: ", previousTechniques); + // console.log("latest techniques: ", latestTechniques); + + for (let latestTechnique of latestTechniques) { + if (!latestTechnique) continue; + + let prevTechnique = previousTechniques.find(p => p.id == latestTechnique.id); + if (!prevTechnique) { + // object doesn't exist in previous version - added + changelog.additions.push(latestTechnique); + } + else if (latestTechnique.modified == prevTechnique.modified) { + // no changes made to the object + changelog.unchanged.push(latestTechnique); + } else { + // changes were made to the object + } + } + return changelog; + } } /** @@ -311,6 +345,10 @@ export abstract class BaseStix { public readonly name: string; // name of object public readonly description: string; // description of object public readonly url: string; // URL of object on the ATT&CK website + public readonly created: string; // date object was created + public readonly modified: string; // date object was last modified + public readonly revoked: boolean; // is the object revoked? + public readonly deprecated: boolean; // is the object deprecated? protected readonly dataService: DataService; constructor(stixSDO: any, dataService: DataService) { this.id = stixSDO.id; @@ -318,6 +356,10 @@ export abstract class BaseStix { this.description = stixSDO.description; this.attackID = stixSDO.external_references[0].external_id; this.url = stixSDO.external_references[0].url; + this.created = stixSDO.created; + this.modified = stixSDO.modified; + this.revoked = stixSDO.revoked ? stixSDO.revoked : false; + this.deprecated = stixSDO.x_mitre_deprecated ? stixSDO.x_mitre_deprecated : false; this.dataService = dataService; } } @@ -353,7 +395,10 @@ export class Tactic extends BaseStix { constructor(stixSDO: any, techniques: Technique[], dataService: DataService) { super(stixSDO, dataService); this.shortname = stixSDO.x_mitre_shortname; - this.techniques = techniques.filter((technique: Technique) => technique.tactics.includes(this.shortname)); + this.techniques = techniques.filter((technique: Technique) => { + if (!technique.revoked && !technique.deprecated) + return technique.tactics.includes(this.shortname) + }); } } /** @@ -376,9 +421,11 @@ export class Technique extends BaseStix { this.platforms = stixSDO.x_mitre_platforms; if (stixSDO.x_mitre_data_sources !== undefined) this.datasources = stixSDO.x_mitre_data_sources.toString(); - else + else this.datasources = ""; - this.tactics = stixSDO.kill_chain_phases.map((phase) => phase.phase_name); + + if (!this.revoked && !this.deprecated) + this.tactics = stixSDO.kill_chain_phases.map((phase) => phase.phase_name); this.subtechniques = subtechniques; for (let subtechnique of this.subtechniques) { @@ -403,10 +450,27 @@ export class Technique extends BaseStix { * Basically the same as calling get_technique_tactic_id with all valid tactic values */ public get_all_technique_tactic_ids(): string[] { + if (this.revoked || this.deprecated) return []; return this.tactics.map((shortname: string) => this.get_technique_tactic_id(shortname)); } } +export class VersionChangelog { + public previousVersion: string; // TODO keep this? + public latestVersion: string; // TODO keep this? + public additions: T[] = []; // new objects added to newest version + public changes: T[] = []; // object changes between versions + public minor_changes: T[] = []; // changes to objects without version increments + public deprecations: T[] = []; // objects deprecated since older version + public revocations: T[] = []; // objects revoked since older version + public unchanged: T[] = []; // objects which have not changed between versions + + constructor(prev: string, latest: string) { + this.previousVersion = prev; + this.latestVersion = latest; + } +} + /** * Object representing a Software (tool, malware) in the ATT&CK catalogue */ @@ -534,6 +598,8 @@ export class Domain { mitigates: new Map() } + //TODO: add VersionChangelog class variable? + constructor(id: string, name: string, version: string) { this.id = id; this.name = name; diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index c8385ed5f..edb4f39b7 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -534,7 +534,6 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { throw {message: "Error: '" + viewModel.domain + "' (" + viewModel.version + ") is an invalid domain."}; } this.versionUpgradeDialog(viewModel).then( (versions) => { - //TODO: don't automatically transfer over the annotations this.openTab("new layer", viewModel, true, true, true, true); if (versions) { // user upgraded to latest version let loads: any = {}; @@ -544,7 +543,6 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { // load layer version & latest ATT&CK version let dataSubscription = forkJoin(loads).subscribe({ next: (res) => { - // TODO: diff_stix stuff this.dataService.compareVersions(versions.previous, versions.latest); viewModel.deSerialize(string); viewModel.loadVMData(); From 01ab3161c45c0dc608109146c299c1ffca193f87 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 23 Apr 2021 13:31:08 -0400 Subject: [PATCH 03/65] build changelog between two versions --- nav-app/src/app/data.service.ts | 41 ++++++++++++++++++++------ nav-app/src/app/tabs/tabs.component.ts | 21 ++++++++----- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index 2f6e5fdb2..30fef9487 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -310,19 +310,16 @@ export class DataService { * @param latest latest ATT&CK version to upgrade to */ public compareVersions(previous: string, latest: string): VersionChangelog { - console.log("generating version changelog"); let changelog = new VersionChangelog(previous, latest); let previousTechniques = this.getDomain(previous).techniques; let latestTechniques = this.getDomain(latest).techniques; - // console.log("previous techniques: ", previousTechniques); - // console.log("latest techniques: ", latestTechniques); for (let latestTechnique of latestTechniques) { if (!latestTechnique) continue; let prevTechnique = previousTechniques.find(p => p.id == latestTechnique.id); if (!prevTechnique) { - // object doesn't exist in previous version - added + // object doesn't exist in previous version, added to latest version changelog.additions.push(latestTechnique); } else if (latestTechnique.modified == prevTechnique.modified) { @@ -330,6 +327,18 @@ export class DataService { changelog.unchanged.push(latestTechnique); } else { // changes were made to the object + if (latestTechnique.revoked && !prevTechnique.revoked) { + // object was revoked since the previous version + changelog.revocations.push(latestTechnique); + } else if (latestTechnique.deprecated && !prevTechnique.deprecated) { + // object was deprecated since the previous version + changelog.deprecations.push(latestTechnique); + } else if (latestTechnique.compareVersion(prevTechnique.version) != 0) { + // version number changed + changelog.changes.push(latestTechnique); + } else { // minor change + changelog.minor_changes.push(latestTechnique); + } } } return changelog; @@ -349,6 +358,7 @@ export abstract class BaseStix { public readonly modified: string; // date object was last modified public readonly revoked: boolean; // is the object revoked? public readonly deprecated: boolean; // is the object deprecated? + public readonly version: string; // object version protected readonly dataService: DataService; constructor(stixSDO: any, dataService: DataService) { this.id = stixSDO.id; @@ -360,8 +370,24 @@ export abstract class BaseStix { this.modified = stixSDO.modified; this.revoked = stixSDO.revoked ? stixSDO.revoked : false; this.deprecated = stixSDO.x_mitre_deprecated ? stixSDO.x_mitre_deprecated : false; + this.version = stixSDO.x_mitre_version ? stixSDO.x_mitre_version : ''; this.dataService = dataService; } + + public compareVersion(v: string): number { + if (!this.version || !v) return 0; // one or both of the objects have no version + + let thisVersion = this.version.split('.'); + let prevVersion = v.split('.'); + for (let i = 0; i < Math.max(thisVersion.length, prevVersion.length); i++) { + if (thisVersion.length == prevVersion.length && thisVersion.length < i) return 0; + if (thisVersion.length < i) return -1; + if (prevVersion.length < i) return 1; + if (+thisVersion[i] == +prevVersion[i]) continue; + return +thisVersion[i] - +prevVersion[i]; + } + return 0 + } } /** @@ -431,7 +457,6 @@ export class Technique extends BaseStix { for (let subtechnique of this.subtechniques) { subtechnique.parent = this; } - } /** @@ -456,8 +481,8 @@ export class Technique extends BaseStix { } export class VersionChangelog { - public previousVersion: string; // TODO keep this? - public latestVersion: string; // TODO keep this? + public previousVersion: string; + public latestVersion: string; public additions: T[] = []; // new objects added to newest version public changes: T[] = []; // object changes between versions public minor_changes: T[] = []; // changes to objects without version increments @@ -598,8 +623,6 @@ export class Domain { mitigates: new Map() } - //TODO: add VersionChangelog class variable? - constructor(id: string, name: string, version: string) { this.id = id; this.name = name; diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index edb4f39b7..84f866b71 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -541,14 +541,19 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { if (!this.dataService.getDomain(versions.latest).dataLoaded) loads.latest = this.dataService.loadDomainData(versions.latest, true, true); // load layer version & latest ATT&CK version - let dataSubscription = forkJoin(loads).subscribe({ - next: (res) => { - this.dataService.compareVersions(versions.previous, versions.latest); - viewModel.deSerialize(string); - viewModel.loadVMData(); - }, - complete: () => { dataSubscription.unsubscribe(); } - }); + if (Object.keys(loads).length) { + let dataSubscription = forkJoin(loads).subscribe({ + next: (res) => { + this.dataService.compareVersions(versions.previous, versions.latest); + viewModel.deSerialize(string); + viewModel.loadVMData(); + }, + complete: () => { dataSubscription.unsubscribe(); } + }); + } else { + viewModel.deSerialize(string); + viewModel.loadVMData(); + } } else if (!this.dataService.getDomain(viewModel.domainID).dataLoaded) { this.dataService.loadDomainData(viewModel.domainID, true).then( () => { From f95e593f4770da5fdeba5804ec4f26dde3d3ec8b Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 26 Apr 2021 09:10:09 -0400 Subject: [PATCH 04/65] include sub-techniques in stix bundle comparison --- nav-app/src/app/data.service.ts | 6 ++++-- nav-app/src/app/tabs/tabs.component.ts | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index 30fef9487..08f97df47 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -311,9 +311,11 @@ export class DataService { */ public compareVersions(previous: string, latest: string): VersionChangelog { let changelog = new VersionChangelog(previous, latest); - let previousTechniques = this.getDomain(previous).techniques; - let latestTechniques = this.getDomain(latest).techniques; + let previousDomain = this.getDomain(previous); + let latestDomain = this.getDomain(latest); + let previousTechniques = previousDomain.techniques.concat(previousDomain.subtechniques); + let latestTechniques = latestDomain.techniques.concat(latestDomain.subtechniques); for (let latestTechnique of latestTechniques) { if (!latestTechnique) continue; diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index 84f866b71..48ec53348 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -551,6 +551,7 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { complete: () => { dataSubscription.unsubscribe(); } }); } else { + this.dataService.compareVersions(versions.previous, versions.latest); viewModel.deSerialize(string); viewModel.loadVMData(); } From e9f84c4f43ea0f0737544ac5002fe790507bc5d6 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 28 Apr 2021 08:31:44 -0400 Subject: [PATCH 05/65] added sidebar adjacent to matrices --- nav-app/src/app/app.component.html | 22 +++++++--------- nav-app/src/app/app.component.ts | 1 + nav-app/src/app/app.module.ts | 5 ++++ .../src/app/sidebar/sidebar.component.html | 1 + .../src/app/sidebar/sidebar.component.scss | 0 .../src/app/sidebar/sidebar.component.spec.ts | 25 +++++++++++++++++++ nav-app/src/app/sidebar/sidebar.component.ts | 15 +++++++++++ 7 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 nav-app/src/app/sidebar/sidebar.component.html create mode 100644 nav-app/src/app/sidebar/sidebar.component.scss create mode 100644 nav-app/src/app/sidebar/sidebar.component.spec.ts create mode 100644 nav-app/src/app/sidebar/sidebar.component.ts diff --git a/nav-app/src/app/app.component.html b/nav-app/src/app/app.component.html index 2e2dc5d15..d0b9f59d3 100755 --- a/nav-app/src/app/app.component.html +++ b/nav-app/src/app/app.component.html @@ -1,13 +1,9 @@ - - - - - -MITRE ATT&CK® Navigator v{{nav_version}} - - - + + + + MITRE ATT&CK® Navigator v{{nav_version}} + + + + + diff --git a/nav-app/src/app/app.component.ts b/nav-app/src/app/app.component.ts index f94d84c29..c1fa5e6b2 100755 --- a/nav-app/src/app/app.component.ts +++ b/nav-app/src/app/app.component.ts @@ -12,6 +12,7 @@ export class AppComponent { @ViewChild(TabsComponent) tabsComponent; nav_version: string = globals.nav_version; + public sidebarOpened: boolean = true; @HostListener('window:beforeunload', ['$event']) promptNavAway($event) { diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts index 807631e4d..670a4ef32 100755 --- a/nav-app/src/app/app.module.ts +++ b/nav-app/src/app/app.module.ts @@ -14,6 +14,8 @@ import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatDialogModule } from '@angular/material/dialog'; import { MatMenuModule } from '@angular/material/menu'; import { MatExpansionModule } from '@angular/material/expansion'; +import { MatSidenavModule } from '@angular/material/sidenav'; + import { ColorPickerModule } from 'ngx-color-picker'; import { HttpClientModule } from '@angular/common/http'; @@ -32,6 +34,7 @@ import { TechniquesSearchComponent } from './techniques-search/techniques-search import { ContextmenuComponent } from './matrix/technique-cell/contextmenu/contextmenu.component'; import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component'; import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.component'; +import { SidebarComponent } from './sidebar/sidebar.component'; @NgModule({ @@ -51,6 +54,7 @@ import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.compo ContextmenuComponent, TacticCellComponent, VersionUpgradeComponent, + SidebarComponent, ], imports: [ BrowserModule, @@ -68,6 +72,7 @@ import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.compo MatExpansionModule, MatDialogModule, ColorPickerModule, + MatSidenavModule ], exports: [ MatSelectModule, diff --git a/nav-app/src/app/sidebar/sidebar.component.html b/nav-app/src/app/sidebar/sidebar.component.html new file mode 100644 index 000000000..52a8ffc15 --- /dev/null +++ b/nav-app/src/app/sidebar/sidebar.component.html @@ -0,0 +1 @@ +

sidebar works!

diff --git a/nav-app/src/app/sidebar/sidebar.component.scss b/nav-app/src/app/sidebar/sidebar.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/nav-app/src/app/sidebar/sidebar.component.spec.ts b/nav-app/src/app/sidebar/sidebar.component.spec.ts new file mode 100644 index 000000000..1f932e207 --- /dev/null +++ b/nav-app/src/app/sidebar/sidebar.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SidebarComponent } from './sidebar.component'; + +describe('SidebarComponent', () => { + let component: SidebarComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SidebarComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SidebarComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nav-app/src/app/sidebar/sidebar.component.ts b/nav-app/src/app/sidebar/sidebar.component.ts new file mode 100644 index 000000000..d379606cd --- /dev/null +++ b/nav-app/src/app/sidebar/sidebar.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'] +}) +export class SidebarComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} From 41d423eb639b959951eaacc5e459b8094d898803 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 28 Apr 2021 12:52:49 -0400 Subject: [PATCH 06/65] layer upgrade sidebar component --- nav-app/src/app/app.component.ts | 5 ++-- nav-app/src/app/app.module.ts | 2 ++ nav-app/src/app/data.service.ts | 3 +++ .../layer-upgrade.component.html | 7 ++++++ .../layer-upgrade.component.scss | 0 .../layer-upgrade.component.spec.ts | 25 +++++++++++++++++++ .../layer-upgrade/layer-upgrade.component.ts | 18 +++++++++++++ .../src/app/sidebar/sidebar.component.html | 6 ++++- .../src/app/sidebar/sidebar.component.scss | 1 + nav-app/src/app/sidebar/sidebar.component.ts | 3 ++- nav-app/src/app/tabs/tabs.component.ts | 3 ++- 11 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html create mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss create mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts create mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts diff --git a/nav-app/src/app/app.component.ts b/nav-app/src/app/app.component.ts index c1fa5e6b2..1e1d8709a 100755 --- a/nav-app/src/app/app.component.ts +++ b/nav-app/src/app/app.component.ts @@ -2,6 +2,7 @@ import { Component, ViewChild, DoCheck, HostListener } from '@angular/core'; import { TabsComponent } from './tabs/tabs.component'; import { ConfigService } from './config.service'; import * as globals from "./globals"; +import { DataService } from './data.service'; @Component({ selector: 'app-root', @@ -12,7 +13,7 @@ export class AppComponent { @ViewChild(TabsComponent) tabsComponent; nav_version: string = globals.nav_version; - public sidebarOpened: boolean = true; + public get sidebarOpened() { return this.dataService.sidebarOpened; } @HostListener('window:beforeunload', ['$event']) promptNavAway($event) { @@ -22,7 +23,7 @@ export class AppComponent { $event.returnValue='Are you sure you want to navigate away? Your data may be lost!'; } - constructor(public configService: ConfigService) { + constructor(public configService: ConfigService, public dataService: DataService) { Array.prototype.includes = function(value): boolean { // console.log("checking include") for (let i = 0; i < this.length; i++) { diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts index 670a4ef32..3d5288e01 100755 --- a/nav-app/src/app/app.module.ts +++ b/nav-app/src/app/app.module.ts @@ -35,6 +35,7 @@ import { ContextmenuComponent } from './matrix/technique-cell/contextmenu/contex import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component'; import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.component'; import { SidebarComponent } from './sidebar/sidebar.component'; +import { LayerUpgradeComponent } from './sidebar/layer-upgrade/layer-upgrade.component'; @NgModule({ @@ -55,6 +56,7 @@ import { SidebarComponent } from './sidebar/sidebar.component'; TacticCellComponent, VersionUpgradeComponent, SidebarComponent, + LayerUpgradeComponent, ], imports: [ BrowserModule, diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index 08f97df47..f764c3084 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -28,6 +28,8 @@ export class DataService { public versions: any[] = []; public subtechniquesEnabled: boolean = true; + public sidebarOpened: boolean = false; + public versionChangelog?: VersionChangelog; /** * Callback functions passed to this function will be called after data is loaded @@ -343,6 +345,7 @@ export class DataService { } } } + this.versionChangelog = changelog; return changelog; } } diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html new file mode 100644 index 000000000..c8477aeda --- /dev/null +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html @@ -0,0 +1,7 @@ +
+

Layer Upgrade

+
+ + +
+
diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts new file mode 100644 index 000000000..3ee5e20bb --- /dev/null +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LayerUpgradeComponent } from './layer-upgrade.component'; + +describe('LayerUpgradeComponent', () => { + let component: LayerUpgradeComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LayerUpgradeComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LayerUpgradeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts new file mode 100644 index 000000000..44f363e32 --- /dev/null +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts @@ -0,0 +1,18 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { BaseStix, VersionChangelog } from '../../data.service'; + +@Component({ + selector: 'layer-upgrade', + templateUrl: './layer-upgrade.component.html', + styleUrls: ['./layer-upgrade.component.scss'] +}) +export class LayerUpgradeComponent implements OnInit { + @Input() changelog: VersionChangelog; + public showUnannotated: boolean = false; + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/nav-app/src/app/sidebar/sidebar.component.html b/nav-app/src/app/sidebar/sidebar.component.html index 52a8ffc15..9e8e6cb38 100644 --- a/nav-app/src/app/sidebar/sidebar.component.html +++ b/nav-app/src/app/sidebar/sidebar.component.html @@ -1 +1,5 @@ -

sidebar works!

+ diff --git a/nav-app/src/app/sidebar/sidebar.component.scss b/nav-app/src/app/sidebar/sidebar.component.scss index e69de29bb..c8b8b592e 100644 --- a/nav-app/src/app/sidebar/sidebar.component.scss +++ b/nav-app/src/app/sidebar/sidebar.component.scss @@ -0,0 +1 @@ +.sidebar { padding: 16px; } \ No newline at end of file diff --git a/nav-app/src/app/sidebar/sidebar.component.ts b/nav-app/src/app/sidebar/sidebar.component.ts index d379606cd..bc83c2130 100644 --- a/nav-app/src/app/sidebar/sidebar.component.ts +++ b/nav-app/src/app/sidebar/sidebar.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { DataService } from '../data.service'; @Component({ selector: 'sidebar', @@ -7,7 +8,7 @@ import { Component, OnInit } from '@angular/core'; }) export class SidebarComponent implements OnInit { - constructor() { } + constructor(public dataService: DataService) { } ngOnInit(): void { } diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index 48ec53348..c546e5101 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -543,8 +543,9 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { // load layer version & latest ATT&CK version if (Object.keys(loads).length) { let dataSubscription = forkJoin(loads).subscribe({ - next: (res) => { + next: () => { this.dataService.compareVersions(versions.previous, versions.latest); + this.dataService.sidebarOpened = true; viewModel.deSerialize(string); viewModel.loadVMData(); }, From 20d01a46185746494279cfd29108a8af9e7328eb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 30 Apr 2021 14:39:28 -0400 Subject: [PATCH 07/65] show changelog sections in sidebar --- nav-app/src/app/data.service.ts | 5 ++ .../layer-upgrade.component.html | 57 ++++++++++++++++++- .../layer-upgrade.component.scss | 8 +++ .../layer-upgrade/layer-upgrade.component.ts | 28 ++++++++- 4 files changed, 95 insertions(+), 3 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index f764c3084..83bcf10b6 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -499,6 +499,11 @@ export class VersionChangelog { this.previousVersion = prev; this.latestVersion = latest; } + + public length(): number { + return this.additions.length + this.changes.length + this.minor_changes.length + + this.deprecations.length + this.revocations.length; + } } /** diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html index c8477aeda..164a2062d 100644 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html @@ -1,7 +1,62 @@
-

Layer Upgrade

+

Layer Upgrade

({{version(changelog.previousVersion)}} → {{version(changelog.latestVersion)}})
+ + + + + Techniques + + + ({{changelog.length()}} techniques) + + + + + + + + {{sectionHeader(section)}} + + + ({{changelog[section].length}} techniques) + + + + + + + + + + + + +
+ + + + +
+
+
+
+
+ +
+ Updated {{updated.length}}/{{changelog.length()}} objects + +
diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss index e69de29bb..f973d660a 100644 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss @@ -0,0 +1,8 @@ +.spacer { flex: 1 1 auto; } +.button-container { + padding: 12px 0; + float: right; + + .mat-stroked-button { margin-left: 12px; } +} +h3 { display: inline-block; } \ No newline at end of file diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts index 44f363e32..784df485b 100644 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts @@ -1,5 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; -import { BaseStix, VersionChangelog } from '../../data.service'; +import { BaseStix, DataService, Technique, VersionChangelog } from '../../data.service'; @Component({ selector: 'layer-upgrade', @@ -9,10 +9,34 @@ import { BaseStix, VersionChangelog } from '../../data.service'; export class LayerUpgradeComponent implements OnInit { @Input() changelog: VersionChangelog; public showUnannotated: boolean = false; + public sections: string[] = [ + "additions", "changes", "minor_changes", + "deprecations", "revocations" + ]; + public updated: BaseStix[] = []; + public object_updated: boolean = false; - constructor() { } + constructor(public dataService: DataService) { } ngOnInit(): void { } + public sectionHeader(section: string): string { + return section.split('_').map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' '); + } + + public version(version: string): string { + return version.match(/v[0-9]/g)[0].toLowerCase(); + } + + public upgradeLayer(): void { + // close sidebar + this.dataService.sidebarOpened = false; + } + + public getIDs(object: Technique) { + let ids = object.get_all_technique_tactic_ids() + return ids + // getTechniqueVM_id + } } From 6b2ee5d8eb51b21571f442f701fcab6b3e7afba7 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 5 May 2021 16:29:16 -0700 Subject: [PATCH 08/65] Moved new sidebar from inside app.component to inside matrix (data-table.component) --- nav-app/src/app/app.component.html | 3 --- nav-app/src/app/app.component.ts | 1 - nav-app/src/app/datatable/data-table.component.html | 3 +++ nav-app/src/app/datatable/data-table.component.ts | 2 ++ 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nav-app/src/app/app.component.html b/nav-app/src/app/app.component.html index d0b9f59d3..b49ee6b70 100755 --- a/nav-app/src/app/app.component.html +++ b/nav-app/src/app/app.component.html @@ -3,7 +3,4 @@ MITRE ATT&CK® Navigator v{{nav_version}} - - - diff --git a/nav-app/src/app/app.component.ts b/nav-app/src/app/app.component.ts index 1e1d8709a..47e62a1b4 100755 --- a/nav-app/src/app/app.component.ts +++ b/nav-app/src/app/app.component.ts @@ -13,7 +13,6 @@ export class AppComponent { @ViewChild(TabsComponent) tabsComponent; nav_version: string = globals.nav_version; - public get sidebarOpened() { return this.dataService.sidebarOpened; } @HostListener('window:beforeunload', ['$event']) promptNavAway($event) { diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html index 2d5d6423b..5b7563ed7 100755 --- a/nav-app/src/app/datatable/data-table.component.html +++ b/nav-app/src/app/datatable/data-table.component.html @@ -530,6 +530,9 @@
+ + +
{{matrix.name}}
diff --git a/nav-app/src/app/datatable/data-table.component.ts b/nav-app/src/app/datatable/data-table.component.ts index 34ad0edb3..3d3ea0000 100755 --- a/nav-app/src/app/datatable/data-table.component.ts +++ b/nav-app/src/app/datatable/data-table.component.ts @@ -37,6 +37,8 @@ export class DataTableComponent implements AfterViewInit { currentDropdown: string = ""; //current dropdown menu + public get sidebarOpened() { return this.dataService.sidebarOpened; } + ////////////////////////////////////////////////////////// // Stringifies the current view model into a json string// From e18a8f340b14203c32cc32e3b3b2e707db655f5a Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 5 May 2021 16:29:56 -0700 Subject: [PATCH 09/65] Styled new mat-drawer to show inside matrix tab, rather than overlay entire Navigator site --- nav-app/src/app/datatable/data-table.component.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/nav-app/src/app/datatable/data-table.component.scss b/nav-app/src/app/datatable/data-table.component.scss index b444e39a3..ae3751e4e 100755 --- a/nav-app/src/app/datatable/data-table.component.scss +++ b/nav-app/src/app/datatable/data-table.component.scss @@ -473,3 +473,8 @@ $panel-light: lighten($panel-dark, 8%); } } +.mat-drawer { + top: initial; + bottom: initial; +} + From 85909cde74d2ddf695e71bd66e3a5d8da6795a2e Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Thu, 6 May 2021 12:41:36 -0700 Subject: [PATCH 10/65] Moved mat-drawer-container to wrap matrix in data-table.component, added autosize and mode="side" setting to mat-drawer to push matrix to the side when sidebar is open --- nav-app/src/app/app.component.html | 8 +--- .../app/datatable/data-table.component.html | 44 ++++++++++--------- .../app/datatable/data-table.component.scss | 6 --- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/nav-app/src/app/app.component.html b/nav-app/src/app/app.component.html index b49ee6b70..a53798120 100755 --- a/nav-app/src/app/app.component.html +++ b/nav-app/src/app/app.component.html @@ -1,6 +1,2 @@ - - - - MITRE ATT&CK® Navigator v{{nav_version}} - - + +MITRE ATT&CK® Navigator v{{nav_version}} diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html index 5b7563ed7..03c1978ff 100755 --- a/nav-app/src/app/datatable/data-table.component.html +++ b/nav-app/src/app/datatable/data-table.component.html @@ -525,29 +525,33 @@ 88 888 88 8oooo88 888 888 88o 888 88 888 o88o 8 o88o o88o o888o o888o o888o 88o8 o888o o88o o888o --> - -
-
- -
- - - -
-
-
{{matrix.name}}
-
- -
-
- -
-
- + + +
+
+ +
+
+
+
{{matrix.name}}
+
+ +
+
+ +
+
+ +
-
+ + + + + + - - -
-
- -
-
-
-
{{matrix.name}}
-
- -
-
- -
-
- +
+ + +
+
+ +
+
+
+
{{matrix.name}}
+
+ +
+
+ +
+
+ +
-
- - - - - - + + + + + +
-
- -
-
Date: Tue, 18 May 2021 16:57:02 -0400 Subject: [PATCH 19/65] Added optional ? when looking for field in the search all techniques function, as it was throwing an error trying to convert undefined to lower case --- .../src/app/techniques-search/techniques-search.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.ts b/nav-app/src/app/techniques-search/techniques-search.component.ts index 9bd8722c2..fffb1d3cd 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.ts +++ b/nav-app/src/app/techniques-search/techniques-search.component.ts @@ -54,7 +54,7 @@ export class TechniquesSearchComponent implements OnInit { for (let field of self.fields) { if (field.enabled) { // query in this field - if (technique[field.field].toLowerCase().includes(self._query.trim().toLowerCase())) return true; + if (technique[field.field]?.toLowerCase().includes(self._query.trim().toLowerCase())) return true; } } return false; From 8e35a26c688057dd04aa14fa49fe91faf5f3c8fb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 19 May 2021 13:40:10 -0400 Subject: [PATCH 20/65] moved sidebar control to viewmodel --- nav-app/src/app/data.service.ts | 6 +---- .../app/datatable/data-table.component.html | 24 +++++++++---------- .../layer-upgrade/layer-upgrade.component.ts | 4 +++- .../src/app/sidebar/sidebar.component.html | 2 +- nav-app/src/app/sidebar/sidebar.component.ts | 16 +++++++------ nav-app/src/app/tabs/tabs.component.ts | 3 ++- nav-app/src/app/viewmodels.service.ts | 4 ++++ 7 files changed, 32 insertions(+), 27 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index 4eeba5cca..d3ccd0f65 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -28,11 +28,7 @@ export class DataService { public versions: any[] = []; public subtechniquesEnabled: boolean = true; - public versionChangelog?: VersionChangelog; - - private _sidebarOpened: boolean; - public get sidebarOpened(): boolean { return this._sidebarOpened; }; - public set sidebarOpened(newVal: boolean) { this._sidebarOpened = newVal; }; + public versionChangelog?: VersionChangelog;å /** * Callback functions passed to this function will be called after data is loaded diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html index ac95b51da..68604c277 100755 --- a/nav-app/src/app/datatable/data-table.component.html +++ b/nav-app/src/app/datatable/data-table.component.html @@ -526,12 +526,12 @@ o88o 8 o88o o88o o888o o888o o888o 88o8 o888o o88o o888o -->
- - -
-
- -
+
+
+ +
+ +
{{matrix.name}}
@@ -546,12 +546,12 @@
-
-
- - - -
+ + + + + +
diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts index 0ba4a39d4..e77fa00ea 100644 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts +++ b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts @@ -1,4 +1,5 @@ import { Component, Input, OnInit } from '@angular/core'; +import { ViewModel } from '../../viewmodels.service'; import { BaseStix, DataService, Technique, VersionChangelog } from '../../data.service'; @Component({ @@ -8,6 +9,7 @@ import { BaseStix, DataService, Technique, VersionChangelog } from '../../data.s }) export class LayerUpgradeComponent implements OnInit { @Input() changelog: VersionChangelog; + @Input() viewModel: ViewModel; public showUnannotated: boolean = false; public sections: string[] = [ "additions", "changes", "minor_changes", @@ -31,7 +33,7 @@ export class LayerUpgradeComponent implements OnInit { public upgradeLayer(): void { // close sidebar - this.dataService.sidebarOpened = !this.dataService.sidebarOpened; + this.viewModel.sidebarOpened = !this.viewModel.sidebarOpened; } public getIDs(object: Technique) { diff --git a/nav-app/src/app/sidebar/sidebar.component.html b/nav-app/src/app/sidebar/sidebar.component.html index 9e8e6cb38..56cec860c 100644 --- a/nav-app/src/app/sidebar/sidebar.component.html +++ b/nav-app/src/app/sidebar/sidebar.component.html @@ -1,5 +1,5 @@ diff --git a/nav-app/src/app/sidebar/sidebar.component.ts b/nav-app/src/app/sidebar/sidebar.component.ts index bc83c2130..82ffe3e6b 100644 --- a/nav-app/src/app/sidebar/sidebar.component.ts +++ b/nav-app/src/app/sidebar/sidebar.component.ts @@ -1,16 +1,18 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit } from '@angular/core'; import { DataService } from '../data.service'; +import { ViewModel } from '../viewmodels.service'; @Component({ - selector: 'sidebar', - templateUrl: './sidebar.component.html', - styleUrls: ['./sidebar.component.scss'] + selector: 'sidebar', + templateUrl: './sidebar.component.html', + styleUrls: ['./sidebar.component.scss'] }) export class SidebarComponent implements OnInit { + @Input() viewModel?: ViewModel; - constructor(public dataService: DataService) { } + constructor(public dataService: DataService) { } - ngOnInit(): void { - } + ngOnInit(): void { + } } diff --git a/nav-app/src/app/tabs/tabs.component.ts b/nav-app/src/app/tabs/tabs.component.ts index c546e5101..64ff7bd95 100755 --- a/nav-app/src/app/tabs/tabs.component.ts +++ b/nav-app/src/app/tabs/tabs.component.ts @@ -545,7 +545,7 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { let dataSubscription = forkJoin(loads).subscribe({ next: () => { this.dataService.compareVersions(versions.previous, versions.latest); - this.dataService.sidebarOpened = true; + viewModel.sidebarOpened = true; viewModel.deSerialize(string); viewModel.loadVMData(); }, @@ -553,6 +553,7 @@ export class TabsComponent implements AfterContentInit, AfterViewInit { }); } else { this.dataService.compareVersions(versions.previous, versions.latest); + viewModel.sidebarOpened = true; viewModel.deSerialize(string); viewModel.loadVMData(); } diff --git a/nav-app/src/app/viewmodels.service.ts b/nav-app/src/app/viewmodels.service.ts index 289b9e417..caab74ed9 100755 --- a/nav-app/src/app/viewmodels.service.ts +++ b/nav-app/src/app/viewmodels.service.ts @@ -382,6 +382,10 @@ export class ViewModel { techIDtoUIDMap: Object = {}; techUIDtoIDMap: Object = {}; + private _sidebarOpened: boolean; + public get sidebarOpened(): boolean { return this._sidebarOpened; }; + public set sidebarOpened(newVal: boolean) { this._sidebarOpened = newVal; }; + constructor(name: string, uid: string, domainID: string, private dataService: DataService) { this.domainID = domainID; console.log("initializing ViewModel '" + name + "'"); From f4b727fc043f3411d77b3b9d4e4b0ba903b6de09 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 19 May 2021 14:05:00 -0400 Subject: [PATCH 21/65] Merging moving sidebar options from Data Service to View Model, and updating with my changes to set the sidebar content --- nav-app/src/app/data.service.ts | 8 -------- nav-app/src/app/datatable/data-table.component.html | 2 +- nav-app/src/app/sidebar/sidebar.component.html | 8 ++++---- nav-app/src/app/tabs/tabs.component.ts | 5 +++-- nav-app/src/app/viewmodels.service.ts | 8 ++++++++ 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index d3aa4dfd8..d3ccd0f65 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -30,14 +30,6 @@ export class DataService { public subtechniquesEnabled: boolean = true; public versionChangelog?: VersionChangelog;å - public readonly sidebarContentTypes = ['layerUpgrade', 'search']; - private _sidebarContentType: string; - public get sidebarContentType(): string { return this._sidebarContentType; }; - public set sidebarContentType(newVal: string) { - if (this.sidebarContentTypes.includes(newVal)) this._sidebarContentType = newVal; - else this._sidebarContentType = ''; - }; - /** * Callback functions passed to this function will be called after data is loaded * @param {*} callback callback function to call when data is done loading diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html index abb33bc80..110831d8a 100755 --- a/nav-app/src/app/datatable/data-table.component.html +++ b/nav-app/src/app/datatable/data-table.component.html @@ -41,7 +41,7 @@
@@ -12,7 +11,7 @@ -

Settings

+

Search Settings

@@ -32,12 +31,11 @@

Settings

Techniques ({{techniqueResults.length}})

- // -
+
@@ -60,9 +58,6 @@

Techniques ({{techniqueResults.length}})

no results
- - No results - @@ -73,7 +68,6 @@

- //
diff --git a/nav-app/src/app/techniques-search/techniques-search.component.scss b/nav-app/src/app/techniques-search/techniques-search.component.scss index 18a8aa36d..401b3ba08 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.scss +++ b/nav-app/src/app/techniques-search/techniques-search.component.scss @@ -9,14 +9,6 @@ ::ng-deep .mat-form-field-outline-start, ::ng-deep .mat-form-field-outline-gap, ::ng-deep .mat-form-field-outline-end { background-color: white; } - .button-settings { - border: none; - background-color: initial; - } - .button-settings:hover { - cursor: pointer; - opacity: 0.8; - } .fields { padding: 0 10px; text-align: left; diff --git a/nav-app/src/app/techniques-search/techniques-search.component.ts b/nav-app/src/app/techniques-search/techniques-search.component.ts index d09dd29a9..25b72325c 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.ts +++ b/nav-app/src/app/techniques-search/techniques-search.component.ts @@ -42,6 +42,7 @@ export class TechniquesSearchComponent implements OnInit { private previousQuery: string = ""; private _query: string = ""; public set query(newQuery: string) { + this.previousQuery = this._query; this._query = newQuery; this.getStixResults(this._query); this.getTechniqueResults(this._query); @@ -59,45 +60,61 @@ export class TechniquesSearchComponent implements OnInit { ngOnInit() { this.getStixData(); + this.getTechniques(); } - getTechniqueResults(query = "") { + // filterAndSort() takes an array of items and does the following: + // 1) if the query is empty, then it sorts the array + // 2) if the query is not empty, then it filters the already sorted array until nothing is left, or, + // the query is cleared out and empty again + filterAndSort(items: any[], query = "", sortAllTechniques = false) { let self = this; - if (query.trim() != "") { - //get master list of techniques and sub-techniques - let allTechniques = this.dataService.getDomain(this.viewModel.domainID).techniques; - for (let technique of allTechniques) { - allTechniques = allTechniques.concat(technique.subtechniques); + let results = items; + if (query.trim() === "") { + if (sortAllTechniques) { + results.sort((tA: Technique, tB: Technique) => { + let c1 = tA.isSubtechnique ? tA.parent.name : tA.name; + let c2 = tB.isSubtechnique ? tB.parent.name : tB.name; + return c1.localeCompare(c2) + }); + // console.log(seenIDs, techniqueResults) + } else { + results.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); } - - let techniqueResults = allTechniques.filter(function (technique: Technique) { + } else { + // deconflict IDs for cross-tactic techniques + let seenIDs = new Set(); + results = results.filter(function (technique: Technique) { + if (seenIDs.has(technique.id)) return false; for (let field of self.fields) { if (field.enabled) { // query in this field - if (technique[field.field]?.toLowerCase().includes(self._query.trim().toLowerCase())) return true; + if (technique[field.field]?.toLowerCase().includes(query.trim().toLowerCase())) { + seenIDs.add(technique.id); + return true; + } } } return false; }); - // deconflict IDs for cross-tactic techniques - let seenIDs = new Set(); - techniqueResults = techniqueResults.filter(function (technique: Technique) { - if (seenIDs.has(technique.id)) return false; - else { - seenIDs.add(technique.id); - return true; - } - }) + } + return results; + } - techniqueResults = techniqueResults.sort((tA: Technique, tB: Technique) => { - let c1 = tA.isSubtechnique ? tA.parent.name : tA.name; - let c2 = tB.isSubtechnique ? tB.parent.name : tB.name; - return c1.localeCompare(c2) - }); - // console.log(seenIDs, techniqueResults) - this.techniqueResults = techniqueResults; + getTechniques() { + //get master list of techniques and sub-techniques + let allTechniques = this.dataService.getDomain(this.viewModel.domainID).techniques; + for (let technique of allTechniques) { + allTechniques = allTechniques.concat(technique.subtechniques); + } + this.techniqueResults = this.filterAndSort(allTechniques); + } + + getTechniqueResults(query = "") { + if (query.trim() != "" && query.includes(this.previousQuery)) { + this.techniqueResults = this.filterAndSort(this.techniqueResults, query, true); } else { - this.techniqueResults = []; + this.getTechniques(); } } @@ -106,7 +123,7 @@ export class TechniquesSearchComponent implements OnInit { this.stixTypes = [{ "label": "threat groups", - "objects": this.filterAndSort(domain.groups.filter((group, i, arr) => arr.findIndex(t => t.id === group.id) === i)) + "objects": this.filterAndSort(domain.groups) }, { "label": "software", "objects": this.filterAndSort(domain.software) @@ -116,34 +133,30 @@ export class TechniquesSearchComponent implements OnInit { }] } + // getStixResults() checks if query is: + // 1) valid, and + // 2) part of last query, otherwise call getStixData() to search all objects again getStixResults(query = "") { - // Checks if query is 1) valid, and - // 2) part of last query, otherwise call getStixData() to search all objects again if (query.trim() != "" && query.includes(this.previousQuery)) { this.stixTypes.forEach(item => item['objects'] = this.filterAndSort(item['objects'], query)); } else { this.getStixData(); } - this.previousQuery = query; - } - - public filterAndSort(items: any[], query = "") { - let results = items; - if (query.trim() === "") { - results.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())); - } else { - results = results.filter(item => item.name.toLowerCase().includes(query.trim().toLowerCase())) - } - return results; } public toggleFieldEnabled(field: string) { + let temp = this._query; for (let thefield of this.fields) { if (thefield.field == field) { thefield.enabled = !thefield.enabled; + // this is to trigger getTechniques() and getStixData() in the case that: + // a field was toggled, and + // the query did not change + this.query = ""; break; } } + this._query = temp; this.query = this._query; } @@ -163,6 +176,18 @@ export class TechniquesSearchComponent implements OnInit { for (let result of this.techniqueResults) this.deselect(result); } + public deselectStix(stixObject: BaseStix): void { + for (let technique of this.getRelated(stixObject)) { + this.viewModel.unselectTechniqueAcrossTactics(technique); + } + } + + public selectStix(stixObject: BaseStix): void { + for (let technique of this.getRelated(stixObject)) { + this.viewModel.selectTechniqueAcrossTactics(technique); + } + } + public getRelated(stixObject: BaseStix): Technique[] { // master list of all techniques and sub-techniques let techniques = this.dataService.getDomain(this.viewModel.domainID).techniques; From 0e97ea776ab8d131671ec65bc38cd7f241b57dc5 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Mon, 7 Jun 2021 13:48:42 -0400 Subject: [PATCH 31/65] Added selectAllStix/deselectAllStic methods --- .../techniques-search.component.html | 11 +++-- .../techniques-search.component.ts | 40 +++++++++---------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/techniques-search/techniques-search.component.html index f7a7ee9a4..7b36ce3b1 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/techniques-search/techniques-search.component.html @@ -34,11 +34,10 @@

Techniques ({{techniqueResults.length}})

-
- - + +
@@ -70,7 +69,11 @@

-
+
+ + +
+

diff --git a/nav-app/src/app/techniques-search/techniques-search.component.ts b/nav-app/src/app/techniques-search/techniques-search.component.ts index 25b72325c..1216858ef 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.ts +++ b/nav-app/src/app/techniques-search/techniques-search.component.ts @@ -107,7 +107,7 @@ export class TechniquesSearchComponent implements OnInit { for (let technique of allTechniques) { allTechniques = allTechniques.concat(technique.subtechniques); } - this.techniqueResults = this.filterAndSort(allTechniques); + this.techniqueResults = this.filterAndSort(allTechniques, "", true); } getTechniqueResults(query = "") { @@ -168,12 +168,18 @@ export class TechniquesSearchComponent implements OnInit { this.viewModel.unselectTechniqueAcrossTactics(technique); } - public selectAll(): void { - for (let result of this.techniqueResults) this.select(result); + public selectAll(items: any[]): void { + for (let result of items) this.select(result); } - public deselectAll(): void { - for (let result of this.techniqueResults) this.deselect(result); + public deselectAll(items: any[]): void { + for (let result of items) this.deselect(result); + } + + public selectStix(stixObject: BaseStix): void { + for (let technique of this.getRelated(stixObject)) { + this.viewModel.selectTechniqueAcrossTactics(technique); + } } public deselectStix(stixObject: BaseStix): void { @@ -182,9 +188,15 @@ export class TechniquesSearchComponent implements OnInit { } } - public selectStix(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.selectTechniqueAcrossTactics(technique); + public selectAllStix(items: BaseStix[]): void { + for (let stixObject of items) { + this.selectStix(stixObject); + } + } + + public deselectAllSitx(items: BaseStix[]): void { + for (let stixObject of items) { + this.deselectStix(stixObject); } } @@ -202,16 +214,4 @@ export class TechniquesSearchComponent implements OnInit { return allTechniques.filter((technique: Technique) => (stixObject as Mitigation).relatedTechniques(domainID).includes(technique.id)); } } - - public deselectStix(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.unselectTechniqueAcrossTactics(technique); - } - } - - public selectStix(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.selectTechniqueAcrossTactics(technique); - } - } } From 7adec5939c586ce727589aedfb1670667d5b269a Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Mon, 7 Jun 2021 14:26:21 -0400 Subject: [PATCH 32/65] Adjusted styling of sidebar contents, added MatCard and MatDivider modules --- nav-app/src/app/app.module.ts | 6 ++- .../techniques-search.component.html | 38 ++++++++++--------- .../techniques-search.component.scss | 11 +++++- 3 files changed, 36 insertions(+), 19 deletions(-) diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts index 3d5288e01..261f7b1eb 100755 --- a/nav-app/src/app/app.module.ts +++ b/nav-app/src/app/app.module.ts @@ -36,6 +36,8 @@ import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component' import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.component'; import { SidebarComponent } from './sidebar/sidebar.component'; import { LayerUpgradeComponent } from './sidebar/layer-upgrade/layer-upgrade.component'; +import { MatCardModule } from "@angular/material/card"; +import { MatDividerModule } from "@angular/material/divider"; @NgModule({ @@ -74,7 +76,9 @@ import { LayerUpgradeComponent } from './sidebar/layer-upgrade/layer-upgrade.com MatExpansionModule, MatDialogModule, ColorPickerModule, - MatSidenavModule + MatSidenavModule, + MatCardModule, + MatDividerModule ], exports: [ MatSelectModule, diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/techniques-search/techniques-search.component.html index 7b36ce3b1..723949265 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/techniques-search/techniques-search.component.html @@ -1,19 +1,17 @@ diff --git a/nav-app/src/app/techniques-search/techniques-search.component.scss b/nav-app/src/app/techniques-search/techniques-search.component.scss index 401b3ba08..359cb17ce 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.scss +++ b/nav-app/src/app/techniques-search/techniques-search.component.scss @@ -9,8 +9,17 @@ ::ng-deep .mat-form-field-outline-start, ::ng-deep .mat-form-field-outline-gap, ::ng-deep .mat-form-field-outline-end { background-color: white; } + .sidebar-content { + margin: 1rem 0; + } + mat-card-content { + margin-bottom: 0; + } + .settings .title { + font-size: 14px; + margin: 0; + } .fields { - padding: 0 10px; text-align: left; .field { display: inline-block; From 4e6a26e34ae13cae81863ab673ef834fcfcf8e11 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Mon, 7 Jun 2021 14:31:12 -0400 Subject: [PATCH 33/65] Removed Multiselect component --- nav-app/src/app/app.module.ts | 2 - .../multiselect/multiselect.component.html | 20 ------ .../multiselect/multiselect.component.scss | 32 --------- .../multiselect/multiselect.component.spec.ts | 25 ------- .../app/multiselect/multiselect.component.ts | 65 ------------------- 5 files changed, 144 deletions(-) delete mode 100644 nav-app/src/app/multiselect/multiselect.component.html delete mode 100644 nav-app/src/app/multiselect/multiselect.component.scss delete mode 100644 nav-app/src/app/multiselect/multiselect.component.spec.ts delete mode 100644 nav-app/src/app/multiselect/multiselect.component.ts diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts index 261f7b1eb..615070aef 100755 --- a/nav-app/src/app/app.module.ts +++ b/nav-app/src/app/app.module.ts @@ -29,7 +29,6 @@ import { MatrixSideComponent } from './matrix/matrix-side/matrix-side.component' import { MatrixFlatComponent } from './matrix//matrix-flat/matrix-flat.component'; import { MatrixMiniComponent } from './matrix//matrix-mini/matrix-mini.component'; import { TooltipComponent } from './matrix/technique-cell/tooltip/tooltip.component'; -import { MultiselectComponent } from './multiselect/multiselect.component'; import { TechniquesSearchComponent } from './techniques-search/techniques-search.component'; import { ContextmenuComponent } from './matrix/technique-cell/contextmenu/contextmenu.component'; import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component'; @@ -52,7 +51,6 @@ import { MatDividerModule } from "@angular/material/divider"; MatrixFlatComponent, MatrixMiniComponent, TooltipComponent, - MultiselectComponent, TechniquesSearchComponent, ContextmenuComponent, TacticCellComponent, diff --git a/nav-app/src/app/multiselect/multiselect.component.html b/nav-app/src/app/multiselect/multiselect.component.html deleted file mode 100644 index 5772717e1..000000000 --- a/nav-app/src/app/multiselect/multiselect.component.html +++ /dev/null @@ -1,20 +0,0 @@ -
-
-

- {{stixType.label}} -

-
-
{{stixObject.name}}
- - - - - - -
{{stixObject.name}}view
-
-
- Data does not include {{stixType.label}} -
-
-
diff --git a/nav-app/src/app/multiselect/multiselect.component.scss b/nav-app/src/app/multiselect/multiselect.component.scss deleted file mode 100644 index 29c0f299a..000000000 --- a/nav-app/src/app/multiselect/multiselect.component.scss +++ /dev/null @@ -1,32 +0,0 @@ -@import "../../colors.scss"; -.multiselect { - .stixType { - h1 { - font-size: 14px; - background: $panel-light; - padding: 5px; - margin: 0; - } - .objects { - height: 150px; - overflow-y: scroll; - table { - border-collapse: collapse; - width: 100%; - tr + tr { - border-top: 1px solid $panel-light; - } - tr:hover { - background: $cell-highlight-color - } - td { - width: 100px; - text-align: left; - &+td { - width: 1px; - } - } - } - } - } -} \ No newline at end of file diff --git a/nav-app/src/app/multiselect/multiselect.component.spec.ts b/nav-app/src/app/multiselect/multiselect.component.spec.ts deleted file mode 100644 index 0171a69bd..000000000 --- a/nav-app/src/app/multiselect/multiselect.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { MultiselectComponent } from './multiselect.component'; - -describe('MultiselectComponent', () => { - let component: MultiselectComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ MultiselectComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(MultiselectComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/nav-app/src/app/multiselect/multiselect.component.ts b/nav-app/src/app/multiselect/multiselect.component.ts deleted file mode 100644 index 06ab9d3ed..000000000 --- a/nav-app/src/app/multiselect/multiselect.component.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Component, OnInit, Input } from '@angular/core'; -import { ViewModel } from '../viewmodels.service'; -import { DataService, BaseStix, Group, Technique, Mitigation, Software } from '../data.service'; - -@Component({ - selector: 'app-multiselect', - templateUrl: './multiselect.component.html', - styleUrls: ['./multiselect.component.scss'] -}) -export class MultiselectComponent implements OnInit { - @Input() viewModel: ViewModel; - - public openedPanel: string = ""; - - public stixTypes: any[]; - - constructor(private dataService: DataService) { - this.stixTypes = []; - } - - ngOnInit() { - let domain = this.dataService.getDomain(this.viewModel.domainID) - - this.stixTypes = [{ - "label": "threat groups", - "objects": domain.groups.filter((group, i, arr) => arr.findIndex(t => t.id === group.id) === i) - .sort((a,b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) - }, { - "label": "software", - "objects": domain.software.sort((a,b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) - }, { - "label": "mitigations", - "objects": domain.mitigations.sort((a,b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) - }] - } - - public getRelated(stixObject: BaseStix): Technique[] { - // master list of all techniques and sub-techniques - let techniques = this.dataService.getDomain(this.viewModel.domainID).techniques; - let allTechniques = techniques.concat(this.dataService.getDomain(this.viewModel.domainID).subtechniques); - let domainID = this.viewModel.domainID; - - if (stixObject instanceof Group) { - return allTechniques.filter((technique: Technique) => (stixObject as Group).relatedTechniques(domainID).includes(technique.id)); - } else if (stixObject instanceof Software) { - return allTechniques.filter((technique: Technique) => (stixObject as Software).relatedTechniques(domainID).includes(technique.id)); - } else if (stixObject instanceof Mitigation) { - return allTechniques.filter((technique: Technique) => (stixObject as Mitigation).relatedTechniques(domainID).includes(technique.id)); - } - } - - public deselect(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.unselectTechniqueAcrossTactics(technique); - } - } - - public select(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.selectTechniqueAcrossTactics(technique); - } - } - - -} From 06b9bfe123a153566948f23fa17bb0bed46ba562 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:20:55 -0400 Subject: [PATCH 34/65] Changed highlightedTechnique in ViewModel to a Set of technique IDs that are highlighted; Added highlight on hover over technique search results feature --- .../technique-cell.component.ts | 37 +++++++------------ .../techniques-search.component.html | 7 +++- .../techniques-search.component.ts | 13 +++++++ nav-app/src/app/viewmodels.service.ts | 25 ++++++++----- 4 files changed, 48 insertions(+), 34 deletions(-) diff --git a/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts b/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts index 0c01c9cb5..894d358c2 100644 --- a/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts +++ b/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts @@ -22,35 +22,26 @@ export class TechniqueCellComponent implements OnInit { public get showTooltip(): boolean { if (this.showContextmenu) return false; - if (!this.viewModel.highlightedTechnique) return false; + if (this.viewModel.highlightedTechniques.size === 0) return false; - return (this.viewModel.highlightedTechnique.id == this.technique.id && this.viewModel.highlightedTactic.id == this.tactic.id); + return (this.viewModel.highlightedTechniques.has(this.technique.id) && this.viewModel.highlightedTactic && this.viewModel.highlightedTactic.id == this.tactic.id); } public get isHighlighted(): boolean { - if (this.viewModel.highlightedTactic) { - if (this.viewModel.selectTechniquesAcrossTactics) { - if (this.viewModel.selectSubtechniquesWithParent) { - let compareTo = this.viewModel.highlightedTechnique; - if (compareTo.isSubtechnique) compareTo = compareTo.parent; - let compare = this.technique; - if (compare.isSubtechnique) compare = compare.parent; - if (compare.attackID == compareTo.attackID) return true; - } else if (this.viewModel.highlightedTechnique.id == this.technique.id) { - return true; - } - } else if (this.viewModel.highlightedTactic.id == this.tactic.id) { - if (this.viewModel.selectSubtechniquesWithParent) { - let compareTo = this.viewModel.highlightedTechnique; - if (compareTo.isSubtechnique) compareTo = compareTo.parent; - let compare = this.technique; - if (compare.isSubtechnique) compare = compare.parent; - if (compare.attackID == compareTo.attackID) return true; - } else if (this.viewModel.highlightedTechnique.id == this.technique.id) { - return true; - } + if (this.viewModel.selectTechniquesAcrossTactics) { + if (this.viewModel.selectSubtechniquesWithParent) { + return this.technique.isSubtechnique && this.viewModel.highlightedTechniques.has(this.technique.parent.id); } } + else if (this.viewModel.highlightedTactic && this.viewModel.highlightedTactic.id == this.tactic.id) { + if (this.viewModel.selectSubtechniquesWithParent) { + return this.technique.isSubtechnique && this.viewModel.highlightedTechniques.has(this.technique.parent.id); + } + } + // TODO: when hovering over resulting techniques in the techniques search component, should it also highlight the parent, or is this sufficient? + if (this.viewModel.highlightedTechniques.has(this.technique.id)) { // for highlighting techniques from the techniques search component, where highlightedTactic is null + return true; + } return this.showContextmenu; } diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/techniques-search/techniques-search.component.html index 723949265..f707a8871 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/techniques-search/techniques-search.component.html @@ -45,7 +45,7 @@

Techniques ({{techniqueResults.length}})

- @@ -79,7 +79,10 @@

+ {{result.parent.name}}: {{result.name}} view
- +
{{stixObject.name}} + {{stixObject.name}} + view diff --git a/nav-app/src/app/techniques-search/techniques-search.component.ts b/nav-app/src/app/techniques-search/techniques-search.component.ts index 1216858ef..4e676cb3a 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.ts +++ b/nav-app/src/app/techniques-search/techniques-search.component.ts @@ -160,6 +160,19 @@ export class TechniquesSearchComponent implements OnInit { this.query = this._query; } + public mouseEnter(technique: Technique, isStix?: boolean): void { + if (isStix) { + for (let t of this.getRelated(technique)) { + this.viewModel.selectTechniqueAcrossTactics(t, true, true); + } + } else { + this.viewModel.highlightTechnique(technique); + } + } + public mouseLeave(): void { + this.viewModel.clearHighlight(); + } + public select(technique: Technique): void { this.viewModel.selectTechniqueAcrossTactics(technique); } diff --git a/nav-app/src/app/viewmodels.service.ts b/nav-app/src/app/viewmodels.service.ts index b5649c028..39ed2b505 100755 --- a/nav-app/src/app/viewmodels.service.ts +++ b/nav-app/src/app/viewmodels.service.ts @@ -497,15 +497,16 @@ export class ViewModel { public highlightedTactic: Tactic = null; - public highlightedTechnique: Technique = null; + public highlightedTechniques: Set = new Set(); /** * Highlight the given technique under the given tactic * @param {Technique} technique to highlight * @param {Tactic} tactic wherein the technique occurs */ - public highlightTechnique(technique: Technique, tactic: Tactic) { - this.highlightedTechnique = technique; + public highlightTechnique(technique: Technique, tactic?: Tactic | null) { + if (this.selectSubtechniquesWithParent && technique.isSubtechnique) this.highlightedTechniques.add(technique.parent.id); + this.highlightedTechniques.add(technique.id); this.highlightedTactic = tactic; } /** @@ -513,7 +514,7 @@ export class ViewModel { */ public clearHighlight() { this.highlightedTactic = null; - this.highlightedTechnique = null; + this.highlightedTechniques = new Set(); } /** @@ -575,20 +576,26 @@ export class ViewModel { * select the given technique across all tactics in which it occurs * @param {Technique} technique to select * @param {boolean} walkChildren (recursion helper) if true and selectSubtechniquesWithParent is true, walk selection up to parent technique + * @param highlightTechniques, if true, highlight techniques rather than add to selected techniques group */ - public selectTechniqueAcrossTactics(technique: Technique, walkChildren=true): void { + public selectTechniqueAcrossTactics(technique: Technique, walkChildren= true, highlightTechniques = false): void { if (this.selectSubtechniquesWithParent && walkChildren) { //walk to parent / children / siblings if (technique.isSubtechnique) { //select from parent - this.selectTechniqueAcrossTactics(technique.parent, true); + this.selectTechniqueAcrossTactics(technique.parent, true, highlightTechniques); return; } else { //select subtechniques for (let subtechnique of technique.subtechniques) { - this.selectTechniqueAcrossTactics(subtechnique, false); + this.selectTechniqueAcrossTactics(subtechnique, false, highlightTechniques); } } } - for (let id of technique.get_all_technique_tactic_ids()) { - this.selectedTechniques.add(id); + if (highlightTechniques) { + this.highlightTechnique(technique); + } + else { + for (let id of technique.get_all_technique_tactic_ids()) { + this.selectedTechniques.add(id); + } } } From 05c9e91be87ff5a962fa149f18bd0b5a0da8b49a Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:31:20 -0400 Subject: [PATCH 35/65] Adjusted style in techniques search component results table for consistency across tables --- .../app/techniques-search/techniques-search.component.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.scss b/nav-app/src/app/techniques-search/techniques-search.component.scss index 359cb17ce..2b048e274 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.scss +++ b/nav-app/src/app/techniques-search/techniques-search.component.scss @@ -33,6 +33,9 @@ text-align: center; } .allresults-buttons { + display: flex; + justify-content: space-around; + button { width: 48.5%; } @@ -49,6 +52,9 @@ tr:hover { background: $cell-highlight-color } + td:first-of-type { + width: 200px; + } td { width: 100px; text-align: left; From b8aa97a20cc8fd409b0668a2f1918f0a53075ef1 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:32:09 -0400 Subject: [PATCH 36/65] Adjusted style in techniques search component results table for consistency across tables --- .../src/app/techniques-search/techniques-search.component.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.scss b/nav-app/src/app/techniques-search/techniques-search.component.scss index 2b048e274..a8914a8d2 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.scss +++ b/nav-app/src/app/techniques-search/techniques-search.component.scss @@ -56,7 +56,6 @@ width: 200px; } td { - width: 100px; text-align: left; &+td { width: 1px; From 76b9682564fba501bc3f81a023fe34f01cccb9a4 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:33:46 -0400 Subject: [PATCH 37/65] Added seenIDs set to remove duplicate sdos in dataService, since the filter has been removed from the techniques search (multiselect) component --- nav-app/src/app/data.service.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/nav-app/src/app/data.service.ts b/nav-app/src/app/data.service.ts index d3ccd0f65..56230959e 100755 --- a/nav-app/src/app/data.service.ts +++ b/nav-app/src/app/data.service.ts @@ -44,6 +44,7 @@ export class DataService { */ parseBundle(domain: Domain, stixBundles: any[], includeAll: boolean = false): void { let platforms = new Set(); + let seenIDs = new Set(); for (let bundle of stixBundles) { let techniqueSDOs = []; let matrixSDOs = []; @@ -53,6 +54,10 @@ export class DataService { // ignore deprecated and revoked objects in the bundle? if (!includeAll && (sdo.x_mitre_deprecated || sdo.revoked)) continue; + // filter out duplicates + if (!seenIDs.has(sdo.id)) seenIDs.add(sdo.id) + else break; + // parse according to type switch(sdo.type) { case "intrusion-set": @@ -135,12 +140,12 @@ export class DataService { } domain.techniques.push(new Technique(techniqueSDO, subtechniques, this)); } - + //create matrices, which also creates tactics and filters techniques for (let matrixSDO of matrixSDOs) { domain.matrices.push(new Matrix(matrixSDO, idToTacticSDO, domain.techniques, this)); } - + // parse platforms for (let technique of domain.techniques) { if (technique.platforms) { @@ -349,9 +354,9 @@ export class DataService { } } -/** +/** * Common attributes for STIX objects - */ + */ export abstract class BaseStix { public readonly id: string; // STIX ID public readonly attackID: string; // ATT&CK ID @@ -570,7 +575,7 @@ export class Mitigation extends BaseStix { let rels = this.dataService.getDomain(domainID).relationships.mitigates; if (rels.has(this.id)) { return rels.get(this.id); - } + } else return []; } /** @@ -620,10 +625,10 @@ export class Domain { public relationships: any = { // subtechnique subtechnique-of technique // ID of technique to [] of subtechnique IDs - subtechniques_of: new Map(), + subtechniques_of: new Map(), // group uses technique // ID of group to [] of technique IDs - group_uses: new Map(), + group_uses: new Map(), // group uses technique // ID of group to [] of technique IDs software_uses: new Map(), From 85daf2e5545307186ac09c8d57bbcab6ef13d87a Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:34:44 -0400 Subject: [PATCH 38/65] Updated CHANGELOG.md and Help component --- CHANGELOG.md | 3 +++ nav-app/src/app/help/help.component.html | 30 +++++++----------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab0797131..411a37cf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# v4.4 - 9 June 2021 +- Added combined techniques search and multiselect feature in sidebar. See issue [#204](https://github.com/mitre-attack/attack-navigator/issues/204). + # v4.3 - 26 February 2021 - Added aggregate score that displays the combined score of the technique and all of its sub-techniques. See issue [#269](https://github.com/mitre-attack/attack-navigator/issues/269). diff --git a/nav-app/src/app/help/help.component.html b/nav-app/src/app/help/help.component.html index 6c27cbed8..e15497546 100755 --- a/nav-app/src/app/help/help.component.html +++ b/nav-app/src/app/help/help.component.html @@ -63,8 +63,7 @@

Table of Contents

  • Selecting with the Mouse
  • Selection Behavior
  • -
  • Search Interface
  • -
  • Multiselect Interface
  • +
  • Search & Multiselect Interface
  • @@ -697,27 +696,12 @@

    - Search Interface + Search & Multiselect Interface

    The search interface provides a text input to search through techniques in the matrix. Toggles in the interface allow for searching of specific technique fields (name, ID, and description).

    -

    - The interface provides - buttons to select and deselect techniques. These buttons modify the currently selected techniques rather - than replacing then, allowing for the selection of the multiple techniques by selecting them in sequence. - There are 'view' links that lead to more info on each technique. -

    -

    - Buttons labelled 'select all' and 'deselect all' are provided to quickly select/deselect all techniques in the results - area. -

    - -

    - - Multiselect Interface -

    The multiselect interface provides a way to quickly select and deselect groups of techniques. The interface provides two types of groupings, threat @@ -727,16 +711,20 @@

    The interface provides - buttons to select and deselect techniques associated with each software or - threat group. These buttons modify the currently selected techniques rather - than replacing then, allowing for the selection of the techniques of multiple + buttons to select and deselect techniques. These buttons modify the currently selected techniques rather + than replacing then, allowing for the selection of the multiple techniques or the techniques of multiple threat groups or software by selecting them in sequence. + There are 'view' links that lead to more info on each technique.

    Threat groups and software in the list are given a gray background when all of their techniques are selected, even if the selection was not made using the multiselect interface.

    +

    + Buttons labelled 'select all' and 'deselect all' are provided to quickly select/deselect all techniques in the results + area. +

    Customizing The Navigator

    From 48b64d1e56ec14ac63e67fd9bc686f86f036cd8b Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Wed, 9 Jun 2021 07:53:32 -0400 Subject: [PATCH 39/65] Updated CHANGELOG.md date to TBD, and removed TODO as it's working as intended --- CHANGELOG.md | 3 +-- .../src/app/matrix/technique-cell/technique-cell.component.ts | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa1d24d04..149c5e7bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,4 @@ -# v4.4 - 9 June 2021 -## New Features +# v4.4 - changes staged on develop - Added combined techniques search and multiselect feature in sidebar. See issue [#204](https://github.com/mitre-attack/attack-navigator/issues/204). # v4.3 - 29 April 2021 diff --git a/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts b/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts index 894d358c2..30f9ed269 100644 --- a/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts +++ b/nav-app/src/app/matrix/technique-cell/technique-cell.component.ts @@ -38,7 +38,6 @@ export class TechniqueCellComponent implements OnInit { return this.technique.isSubtechnique && this.viewModel.highlightedTechniques.has(this.technique.parent.id); } } - // TODO: when hovering over resulting techniques in the techniques search component, should it also highlight the parent, or is this sufficient? if (this.viewModel.highlightedTechniques.has(this.technique.id)) { // for highlighting techniques from the techniques search component, where highlightedTactic is null return true; } From c69d74bdd49b935ec099d85168bc3485de3967d5 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Thu, 10 Jun 2021 15:57:04 -0400 Subject: [PATCH 40/65] Remove unused icons --- nav-app/src/assets/icons/ic_playlist_add_black_24px.svg | 4 ---- nav-app/src/assets/icons/ic_playlist_add_gray_24px.svg | 4 ---- nav-app/src/assets/icons/ic_settings_black_24dp.svg | 1 - 3 files changed, 9 deletions(-) delete mode 100644 nav-app/src/assets/icons/ic_playlist_add_black_24px.svg delete mode 100644 nav-app/src/assets/icons/ic_playlist_add_gray_24px.svg delete mode 100644 nav-app/src/assets/icons/ic_settings_black_24dp.svg diff --git a/nav-app/src/assets/icons/ic_playlist_add_black_24px.svg b/nav-app/src/assets/icons/ic_playlist_add_black_24px.svg deleted file mode 100644 index 6ecd376e5..000000000 --- a/nav-app/src/assets/icons/ic_playlist_add_black_24px.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/nav-app/src/assets/icons/ic_playlist_add_gray_24px.svg b/nav-app/src/assets/icons/ic_playlist_add_gray_24px.svg deleted file mode 100644 index 64349c77d..000000000 --- a/nav-app/src/assets/icons/ic_playlist_add_gray_24px.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/nav-app/src/assets/icons/ic_settings_black_24dp.svg b/nav-app/src/assets/icons/ic_settings_black_24dp.svg deleted file mode 100644 index f53024e88..000000000 --- a/nav-app/src/assets/icons/ic_settings_black_24dp.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 5cad3a2aad5211fa8eaa239f20f75e4732ea05f0 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 13:52:21 -0400 Subject: [PATCH 41/65] Fixed typo --- .../src/app/techniques-search/techniques-search.component.html | 2 +- .../src/app/techniques-search/techniques-search.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/techniques-search/techniques-search.component.html index f707a8871..42674937d 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/techniques-search/techniques-search.component.html @@ -74,7 +74,7 @@

    - +
    diff --git a/nav-app/src/app/techniques-search/techniques-search.component.ts b/nav-app/src/app/techniques-search/techniques-search.component.ts index 4e676cb3a..1eec43a76 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.ts +++ b/nav-app/src/app/techniques-search/techniques-search.component.ts @@ -207,7 +207,7 @@ export class TechniquesSearchComponent implements OnInit { } } - public deselectAllSitx(items: BaseStix[]): void { + public deselectAllStix(items: BaseStix[]): void { for (let stixObject of items) { this.deselectStix(stixObject); } From e6ed89031d397d3b94b92bab46c3b90bd68454c9 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 14:51:49 -0400 Subject: [PATCH 42/65] Removed layer-upgrade component from this PR (this is part of issue#181) --- .../layer-upgrade.component.html | 62 ------------------- .../layer-upgrade.component.scss | 8 --- .../layer-upgrade.component.spec.ts | 25 -------- .../layer-upgrade/layer-upgrade.component.ts | 44 ------------- 4 files changed, 139 deletions(-) delete mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html delete mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss delete mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts delete mode 100644 nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html deleted file mode 100644 index 164a2062d..000000000 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.html +++ /dev/null @@ -1,62 +0,0 @@ -
    -

    Layer Upgrade

    ({{version(changelog.previousVersion)}} → {{version(changelog.latestVersion)}})
    -
    - - -
    - - - - - Techniques - - - ({{changelog.length()}} techniques) - - - - - - - - {{sectionHeader(section)}} - - - ({{changelog[section].length}} techniques) - - - -
    - - - - - - - - -
    - - - - -
    - - - - - -
    - Updated {{updated.length}}/{{changelog.length()}} objects - -
    -
    diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss deleted file mode 100644 index f973d660a..000000000 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.spacer { flex: 1 1 auto; } -.button-container { - padding: 12px 0; - float: right; - - .mat-stroked-button { margin-left: 12px; } -} -h3 { display: inline-block; } \ No newline at end of file diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts deleted file mode 100644 index 3ee5e20bb..000000000 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { LayerUpgradeComponent } from './layer-upgrade.component'; - -describe('LayerUpgradeComponent', () => { - let component: LayerUpgradeComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ LayerUpgradeComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(LayerUpgradeComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts b/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts deleted file mode 100644 index e77fa00ea..000000000 --- a/nav-app/src/app/sidebar/layer-upgrade/layer-upgrade.component.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Component, Input, OnInit } from '@angular/core'; -import { ViewModel } from '../../viewmodels.service'; -import { BaseStix, DataService, Technique, VersionChangelog } from '../../data.service'; - -@Component({ - selector: 'layer-upgrade', - templateUrl: './layer-upgrade.component.html', - styleUrls: ['./layer-upgrade.component.scss'] -}) -export class LayerUpgradeComponent implements OnInit { - @Input() changelog: VersionChangelog; - @Input() viewModel: ViewModel; - public showUnannotated: boolean = false; - public sections: string[] = [ - "additions", "changes", "minor_changes", - "deprecations", "revocations" - ]; - public updated: BaseStix[] = []; - public object_updated: boolean = false; - - constructor(public dataService: DataService) { } - - ngOnInit(): void { - } - - public sectionHeader(section: string): string { - return section.split('_').map((s) => s.charAt(0).toUpperCase() + s.substring(1)).join(' '); - } - - public version(version: string): string { - return version.match(/v[0-9]/g)[0].toLowerCase(); - } - - public upgradeLayer(): void { - // close sidebar - this.viewModel.sidebarOpened = !this.viewModel.sidebarOpened; - } - - public getIDs(object: Technique) { - let ids = object.get_all_technique_tactic_ids() - return ids - // getTechniqueVM_id - } -} From 4e29984161edeafb5d39b9eb900f572b6357aa9f Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 14:52:21 -0400 Subject: [PATCH 43/65] Removed layerUpgrade from sidebar (this is part of issue#181) --- nav-app/src/app/sidebar/sidebar.component.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/nav-app/src/app/sidebar/sidebar.component.html b/nav-app/src/app/sidebar/sidebar.component.html index e4d1f1523..db6077c3d 100644 --- a/nav-app/src/app/sidebar/sidebar.component.html +++ b/nav-app/src/app/sidebar/sidebar.component.html @@ -1,8 +1,5 @@ diff --git a/nav-app/src/app/techniques-search/techniques-search.component.scss b/nav-app/src/app/techniques-search/techniques-search.component.scss index a8914a8d2..c23979545 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.scss +++ b/nav-app/src/app/techniques-search/techniques-search.component.scss @@ -70,4 +70,8 @@ ::ng-deep mat-panel-description { align-items: center; } + .button-container { + display: flex; + justify-content: flex-end; + } } From 61fa1d141f7734665e39206057877eb1db0f8a7e Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:07:29 -0400 Subject: [PATCH 49/65] Added close button to techniques search component --- .../src/app/techniques-search/techniques-search.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/techniques-search/techniques-search.component.html index d7ff4c7d3..1f0e9aba8 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/techniques-search/techniques-search.component.html @@ -103,6 +103,6 @@

    From a6d149a1697b7ed35327baaa8ac51deee2799730 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:13:22 -0400 Subject: [PATCH 50/65] Renamed techniques-search component to search-and-multiselect component --- nav-app/src/app/app.module.ts | 4 ++-- .../src/app/datatable/data-table.component.ts | 18 +++++++++--------- .../search-and-multiselect.component.html} | 2 +- .../search-and-multiselect.component.scss} | 2 +- .../search-and-multiselect.component.ts} | 8 ++++---- nav-app/src/app/sidebar/sidebar.component.html | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) rename nav-app/src/app/{techniques-search/techniques-search.component.html => search-and-multiselect/search-and-multiselect.component.html} (99%) rename nav-app/src/app/{techniques-search/techniques-search.component.scss => search-and-multiselect/search-and-multiselect.component.scss} (98%) rename nav-app/src/app/{techniques-search/techniques-search.component.ts => search-and-multiselect/search-and-multiselect.component.ts} (97%) diff --git a/nav-app/src/app/app.module.ts b/nav-app/src/app/app.module.ts index b0dad3645..e83d1240f 100755 --- a/nav-app/src/app/app.module.ts +++ b/nav-app/src/app/app.module.ts @@ -29,7 +29,7 @@ import { MatrixSideComponent } from './matrix/matrix-side/matrix-side.component' import { MatrixFlatComponent } from './matrix//matrix-flat/matrix-flat.component'; import { MatrixMiniComponent } from './matrix//matrix-mini/matrix-mini.component'; import { TooltipComponent } from './matrix/technique-cell/tooltip/tooltip.component'; -import { TechniquesSearchComponent } from './techniques-search/techniques-search.component'; +import { SearchAndMultiselectComponent } from './search-and-multiselect/search-and-multiselect.component'; import { ContextmenuComponent } from './matrix/technique-cell/contextmenu/contextmenu.component'; import { TacticCellComponent } from './matrix/tactic-cell/tactic-cell.component'; import { VersionUpgradeComponent } from './version-upgrade/version-upgrade.component'; @@ -50,7 +50,7 @@ import { MatDividerModule } from "@angular/material/divider"; MatrixFlatComponent, MatrixMiniComponent, TooltipComponent, - TechniquesSearchComponent, + SearchAndMultiselectComponent, ContextmenuComponent, TacticCellComponent, VersionUpgradeComponent, diff --git a/nav-app/src/app/datatable/data-table.component.ts b/nav-app/src/app/datatable/data-table.component.ts index 1fb83a19e..330dcdb6f 100755 --- a/nav-app/src/app/datatable/data-table.component.ts +++ b/nav-app/src/app/datatable/data-table.component.ts @@ -16,7 +16,7 @@ declare var tinycolor: any; //use tinycolor2 import * as FileSaver from 'file-saver'; import { ColorPickerModule } from 'ngx-color-picker'; -import { TechniquesSearchComponent } from '../techniques-search/techniques-search.component'; +import { SearchAndMultiselectComponent } from '../search-and-multiselect/search-and-multiselect.component'; import { TmplAstVariable } from '@angular/compiler'; @Component({ @@ -49,7 +49,7 @@ export class DataTableComponent implements AfterViewInit { let filename = this.viewModel.name.replace(/ /g, "_") + ".json"; // FileSaver.saveAs(blob, this.viewModel.name.replace(/ /g, "_") + ".json"); this.saveBlob(blob, filename); - + } saveBlob(blob, filename){ @@ -74,8 +74,8 @@ export class DataTableComponent implements AfterViewInit { var workbook = new Excel.Workbook(); let domain = this.dataService.getDomain(this.viewModel.domainID); for (let matrix of domain.matrices) { - var worksheet = workbook.addWorksheet(matrix.name + " (v" + domain.getVersion() + ")"); - + var worksheet = workbook.addWorksheet(matrix.name + " (v" + domain.getVersion() + ")"); + // create tactic columns let columns = this.viewModel.filterTactics(matrix.tactics, matrix).map(tactic => { return {header: this.getDisplayName(tactic), key: tactic.name} }); worksheet.columns = columns; @@ -233,10 +233,10 @@ export class DataTableComponent implements AfterViewInit { } } - constructor(public dataService: DataService, - private tabs: TabsComponent, - private sanitizer: DomSanitizer, - private viewModelsService: ViewModelsService, + constructor(public dataService: DataService, + private tabs: TabsComponent, + private sanitizer: DomSanitizer, + private viewModelsService: ViewModelsService, public configService: ConfigService) { } /** @@ -272,7 +272,7 @@ export class DataTableComponent implements AfterViewInit { * @param addToSelection add to the technique selection (shift key) or replace selection? */ onTechniqueSelect(technique, addToSelection, eventX, eventY): void { - + if (!this.viewModel.isCurrentlyEditing()) { if (["comment", "score", "colorpicker"].includes(this.currentDropdown)) this.currentDropdown = ""; //remove technique control dropdowns, because everything was deselected return; diff --git a/nav-app/src/app/techniques-search/techniques-search.component.html b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html similarity index 99% rename from nav-app/src/app/techniques-search/techniques-search.component.html rename to nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html index 1f0e9aba8..f2a658153 100644 --- a/nav-app/src/app/techniques-search/techniques-search.component.html +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html @@ -1,4 +1,4 @@ - @@ -76,8 +76,8 @@

    -
    - +
    +
    @@ -92,9 +92,7 @@

    -
    -
    - Data does not include {{stixType.label}} +
    no results for {{stixType.label}}
    diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss index 145d2c934..cc88ecfb6 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss @@ -1,12 +1,12 @@ @import "../../colors.scss"; .search-and-multiselect { - ::ng-deep .mat-form-field-appearance-outline { + .mat-form-field-appearance-outline { width: 100%; } - ::ng-deep .mat-form-field-outline-start { + .mat-form-field-outline-start { width: 30px !important; } - ::ng-deep .mat-form-field-outline-start, ::ng-deep .mat-form-field-outline-gap, ::ng-deep .mat-form-field-outline-end { + .mat-form-field-outline-start, .mat-form-field-outline-gap, .mat-form-field-outline-end { background-color: white; } .sidebar-content { @@ -67,7 +67,7 @@ text-align: center; } } - ::ng-deep mat-panel-description { + mat-panel-description { align-items: center; } .button-container { diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts index 184dfdf9f..2e863a239 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts @@ -1,12 +1,14 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, ViewEncapsulation } from '@angular/core'; import { ViewModel } from '../viewmodels.service'; import {BaseStix, DataService, Group, Mitigation, Software, Technique} from '../data.service'; @Component({ selector: 'app-search-and-multiselect', templateUrl: './search-and-multiselect.component.html', - styleUrls: ['./search-and-multiselect.component.scss'] + styleUrls: ['./search-and-multiselect.component.scss'], + encapsulation: ViewEncapsulation.None }) + export class SearchAndMultiselectComponent implements OnInit { @Input() viewModel: ViewModel; public stixTypes: any[]; From 9b148fa14383eecf3557eb6221833c54afa131a9 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:50:28 -0400 Subject: [PATCH 52/65] Deleted unused spec file --- .../techniques-search.component.spec.ts | 25 ------------------- 1 file changed, 25 deletions(-) delete mode 100644 nav-app/src/app/techniques-search/techniques-search.component.spec.ts diff --git a/nav-app/src/app/techniques-search/techniques-search.component.spec.ts b/nav-app/src/app/techniques-search/techniques-search.component.spec.ts deleted file mode 100644 index 403bbf3cd..000000000 --- a/nav-app/src/app/techniques-search/techniques-search.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; - -import { TechniquesSearchComponent } from './techniques-search.component'; - -describe('TechniquesSearchComponent', () => { - let component: TechniquesSearchComponent; - let fixture: ComponentFixture; - - beforeEach(waitForAsync(() => { - TestBed.configureTestingModule({ - declarations: [ TechniquesSearchComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(TechniquesSearchComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); From 2f7de8c6ac460d14538829407cbab3fa2ff471d4 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:51:25 -0400 Subject: [PATCH 53/65] Combined select & deselect functions to be repurposed for non-techniques in search --- .../search-and-multiselect.component.html | 8 ++-- .../search-and-multiselect.component.ts | 40 +++++++++++++++---- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html index e7f8f8e9f..4dd1b5803 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html @@ -73,8 +73,8 @@

    - - + +
    @@ -85,10 +85,10 @@

    view - + - +
    diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts index 2e863a239..c0f6a6f80 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts @@ -175,20 +175,44 @@ export class SearchAndMultiselectComponent implements OnInit { this.viewModel.clearHighlight(); } - public select(technique: Technique): void { - this.viewModel.selectTechniqueAcrossTactics(technique); + public select(stixObject: any, isTechnique= true): void { + if (isTechnique) { + this.viewModel.selectTechniqueAcrossTactics(stixObject); + } + else if (!isTechnique) { + for (let technique of this.getRelated(stixObject)) { + this.viewModel.selectTechniqueAcrossTactics(technique); + } + } } - public deselect(technique: Technique): void { - this.viewModel.unselectTechniqueAcrossTactics(technique); + public deselect(stixObject: any, isTechnique = true): void { + if (isTechnique) { + this.viewModel.unselectTechniqueAcrossTactics(stixObject); + } + else if (!isTechnique) { + for (let technique of this.getRelated(stixObject)) { + this.viewModel.unselectTechniqueAcrossTactics(technique); + } + } } - public selectAll(items: any[]): void { - for (let result of items) this.select(result); + public selectAll(items: any[], isTechniqueArray = true): void { + if (isTechniqueArray) { + for (let result of items) this.select(result, isTechniqueArray); + } + else if (!isTechniqueArray) { + for (let stixObject of items) this.select(stixObject, isTechniqueArray); + } } - public deselectAll(items: any[]): void { - for (let result of items) this.deselect(result); + public deselectAll(items: any[], isTechniqueArray = true): void { + if (isTechniqueArray) { + for (let result of items) this.deselect(result, isTechniqueArray); + } + else if (!isTechniqueArray) { + for (let stixObject of items) this.deselect(stixObject, isTechniqueArray); + } } public selectStix(stixObject: BaseStix): void { From 75e863941bae64ad78cd09ee46547743ef298d9f Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:51:48 -0400 Subject: [PATCH 54/65] Combined select & deselect functions to be repurposed for non-techniques in search --- .../search-and-multiselect.component.ts | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts index c0f6a6f80..3173aa0e5 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.ts @@ -215,30 +215,6 @@ export class SearchAndMultiselectComponent implements OnInit { } } - public selectStix(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.selectTechniqueAcrossTactics(technique); - } - } - - public deselectStix(stixObject: BaseStix): void { - for (let technique of this.getRelated(stixObject)) { - this.viewModel.unselectTechniqueAcrossTactics(technique); - } - } - - public selectAllStix(items: BaseStix[]): void { - for (let stixObject of items) { - this.selectStix(stixObject); - } - } - - public deselectAllStix(items: BaseStix[]): void { - for (let stixObject of items) { - this.deselectStix(stixObject); - } - } - public getRelated(stixObject: BaseStix): Technique[] { // master list of all techniques and sub-techniques let techniques = this.dataService.getDomain(this.viewModel.domainID).techniques; From 069d2ce9a26c2f646381bc94b47e66ffff961c3d Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:56:11 -0400 Subject: [PATCH 55/65] Changed Search Techniques to Search in query bar --- .../search-and-multiselect.component.html | 4 ++-- .../search-and-multiselect.component.scss | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html index 4dd1b5803..101a19e75 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.html @@ -1,8 +1,8 @@
    - Search Techniques + Search - + diff --git a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss index cc88ecfb6..f6bc81f17 100644 --- a/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss +++ b/nav-app/src/app/search-and-multiselect/search-and-multiselect.component.scss @@ -1,5 +1,8 @@ @import "../../colors.scss"; .search-and-multiselect { + mat-label { + padding-left: 5px; + } .mat-form-field-appearance-outline { width: 100%; } From e7ded44d783ce0819dcef4d9b39d22998bc60870 Mon Sep 17 00:00:00 2001 From: Anna Lin Date: Fri, 11 Jun 2021 15:59:15 -0400 Subject: [PATCH 56/65] Removed dropdown class from search icon in toolbar --- nav-app/src/app/datatable/data-table.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nav-app/src/app/datatable/data-table.component.html b/nav-app/src/app/datatable/data-table.component.html index 5e91563d2..6d73f7c66 100755 --- a/nav-app/src/app/datatable/data-table.component.html +++ b/nav-app/src/app/datatable/data-table.component.html @@ -40,7 +40,7 @@
    -