From 8f989a5e17b424035f30a77c62733717683aecde Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Fri, 20 Aug 2021 13:26:49 -0400 Subject: [PATCH 01/89] fix header-hiding bug --- app/src/app/app.component.ts | 10 ++++++---- docs/changelog.md | 4 ++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/app/app.component.ts b/app/src/app/app.component.ts index 5b0023a8..e6bb9387 100644 --- a/app/src/app/app.component.ts +++ b/app/src/app/app.component.ts @@ -51,15 +51,17 @@ export class AppComponent implements AfterViewInit { // header hiding with scroll ngAfterViewInit() { - this.scrollRef.nativeElement.addEventListener('scroll', (e) => this.scrollEvent(), true); + this.scrollRef.nativeElement.addEventListener('scroll', (e) => this.adjustHeaderPlacement(), true); + //to fix rare cases that the page has resized without scroll events triggering, recompute the offset every 5 seconds + setInterval(() => this.adjustHeaderPlacement(), 5000); } ngOnDestroy() { - this.scrollRef.nativeElement.removeEventListener('scroll', (e) => this.scrollEvent(), true); + this.scrollRef.nativeElement.removeEventListener('scroll', (e) => this.adjustHeaderPlacement(), true); } public hiddenHeaderPX: number = 0; //number of px of the header which is hidden - // when a scroll happens - private scrollEvent(): void { + // adjust the header placement + private adjustHeaderPlacement(): void { let headerHeight = this.header.nativeElement.offsetHeight; // constrain amount of hidden to bounds, round up because decimal scroll causes flicker this.hiddenHeaderPX = Math.floor(Math.min(Math.max(0, this.scrollRef.nativeElement.scrollTop/2), headerHeight)); diff --git a/docs/changelog.md b/docs/changelog.md index e0b74161..4528ce18 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -31,6 +31,10 @@ # Changelog +## Changes staged on develop +### ATT&CK Workbench version 1.1.0 +#### Fixes in 1.1.0 +- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. ## 20 August 2021 ### ATT&CK Workbench version 1.0.2 #### Fixes in 1.0.2 From 009cc5c126bc7de7c1039496ac12a544f5970d54 Mon Sep 17 00:00:00 2001 From: idavila Date: Tue, 14 Sep 2021 14:51:41 -0400 Subject: [PATCH 02/89] Adding object documentation on object list page --- .../group-list/group-list.component.html | 19 ++++++++++++- .../group/group-list/group-list.component.ts | 1 - .../matrix-list/matrix-list.component.html | 17 ++++++++++- .../mitigation-list.component.html | 18 +++++++++++- .../software-list.component.html | 28 ++++++++++++++++++- .../tactic-list/tactic-list.component.html | 18 +++++++++++- .../technique-list.component.html | 18 +++++++++++- app/src/style/layouts/list-page.scss | 10 +++++++ 8 files changed, 122 insertions(+), 7 deletions(-) diff --git a/app/src/app/views/stix/group/group-list/group-list.component.html b/app/src/app/views/stix/group/group-list/group-list.component.html index d84fb1c9..47eb6a14 100644 --- a/app/src/app/views/stix/group/group-list/group-list.component.html +++ b/app/src/app/views/stix/group/group-list/group-list.component.html @@ -1,6 +1,23 @@
-

groups

+

+ groups help_outline +

+ + +
+

+ Groups are sets of related intrusion activity that are tracked by a common name in the security community. Overlaps between names based on publicly reported associations are tracked using "Associated Groups" (also known as "Aliases"). +

+ Groups support the standard set of fields as well as the "Associated Groups" field. Each associated group is tracked using a name and description. The alias description is typically used to hold a set of citations, though plain-text can also be entered alongside citations if additional context is necessary. Alias names cannot be changed after they are added, but the description can be changed by clicking on the entry in the associated groups list. +

+
+
+
- @@ -101,8 +100,8 @@ arrow_forward
- -
+ +
Procedure Examples }">
+ +
+
+

Data Sources

+
+
+
+
+ +
+
+
+
+ +
+
From cff762d0b198952221da4c9986f21d70966109d3 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:09:15 -0400 Subject: [PATCH 08/89] define data source and data component class objects --- app/src/app/classes/stix/data-component.ts | 93 ++++++++++++++++ app/src/app/classes/stix/data-source.ts | 123 +++++++++++++++++++++ app/src/app/classes/stix/stix-object.ts | 8 +- 3 files changed, 222 insertions(+), 2 deletions(-) create mode 100644 app/src/app/classes/stix/data-component.ts create mode 100644 app/src/app/classes/stix/data-source.ts diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts new file mode 100644 index 00000000..a0ca6975 --- /dev/null +++ b/app/src/app/classes/stix/data-component.ts @@ -0,0 +1,93 @@ +import { Observable } from "rxjs"; +import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; +import { ValidationData } from "../serializable"; +import { StixObject } from "./stix-object"; +import { logger } from "../../util/logger"; + +export class DataComponent extends StixObject { + public name: string = ""; + public description: string = ""; + public domains: string[] = []; + public data_source_ref: string; // stix ID of the data source + + protected get attackIDValidator() { return null; } // data components have no ATT&CK ID + + constructor(sdo?: any) { + super(sdo, "x-mitre-data-component"); + if (sdo) { + this.deserialize(sdo); + } + } + + /** + * Transform the current object into a raw object for sending to the back-end + * @abstract + * @returns {*} the raw object to send + */ + public serialize(): any { + let rep = super.base_serialize(); + + rep.stix.name = this.name; + rep.stix.description = this.description; + rep.stix.x_mitre_data_source_ref = this.data_source_ref; + rep.stix.x_mitre_domains = this.domains; + + return rep; + } + + /** + * Parse the object from the record returned from the back-end + * @abstract + * @param {*} raw the raw object to parse + */ + public deserialize(raw: any) { + if ("stix" in raw) { + let sdo = raw.stix; + + if ("name" in sdo) { + if (typeof(sdo.name) === "string") this.name = sdo.name; + else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") + } else this.name = ""; + + if ("description" in sdo) { + if (typeof(sdo.description) === "string") this.description = sdo.description; + else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") + } else this.description = ""; + + if ("x_mitre_data_source_ref" in sdo) { + if (typeof(sdo.x_mitre_data_source_ref) === "string") this.data_source_ref = sdo.x_mitre_data_source_ref; + else logger.error("TypeError: data source ref field is not a string:", sdo.x_mitre_data_source_ref, "(",typeof(sdo.x_mitre_data_source_ref),")") + } else this.data_source_ref = ""; + + if ("x_mitre_domains" in sdo) { + if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; + else logger.error("TypeError: domains field is not a string array."); + } else this.domains = []; + } + } + + /** + * Validate the current object state and return information on the result of the validation + * @param {RestApiConnectorService} restAPIService: the REST API connector through which asynchronous validation can be completed + * @returns {Observable} the validation warnings and errors once validation is complete. + */ + public validate(restAPIService: RestApiConnectorService): Observable { + return this.base_validate(restAPIService); + } + + /** + * Save the current state of the STIX object in the database. Update the current object from the response + * @param restAPIService [RestApiConnectorService] the service to perform the POST/PUT through + * @returns {Observable} of the post + */ + public save(restAPIService: RestApiConnectorService): Observable { + // TODO POST if the object was just created (doesn't exist in db yet) + + let postObservable = restAPIService.postDataComponent(this); + let subscription = postObservable.subscribe({ + next: (result) => { this.deserialize(result.serialize()); }, + complete: () => { subscription.unsubscribe(); } + }); + return postObservable; + } +} \ No newline at end of file diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts new file mode 100644 index 00000000..add37fc2 --- /dev/null +++ b/app/src/app/classes/stix/data-source.ts @@ -0,0 +1,123 @@ +import { StixObject } from "./stix-object"; +import { logger } from "../../util/logger"; +import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; +import { Observable } from "rxjs"; +import { ValidationData } from "../serializable"; +import { DataComponent } from "./data-component"; + +export class DataSource extends StixObject { + public name: string = ""; + public description: string = ""; + public platforms: string[] = []; + public collection_layers: string[] = []; + public contributors: string[] = []; + public domains: string[] = []; + public data_components: DataComponent[] = []; + + /** + * TODO this assumes data sources are returned with a list of their associated + * data component STIX objects + */ + + protected get attackIDValidator() { + return { + regex: "DS\\d{4}", + format: "DS####" + } + } + + constructor(sdo?: any) { + super(sdo, "x-mitre-data-source"); + if (sdo) { + this.deserialize(sdo); + } + } + + /** + * Transform the current object into a raw object for sending to the back-end + * @abstract + * @returns {*} the raw object to send + */ + public serialize(): any { + let rep = super.base_serialize(); + + rep.stix.name = this.name; + rep.stix.description = this.description; + rep.stix.x_mitre_platforms = this.platforms; + rep.stix.x_mitre_collection_layers = this.collection_layers; + rep.stix.x_mitre_contributors = this.contributors; + rep.stix.x_mitre_domains = this.domains; + + return rep; + } + + /** + * Parse the object from the record returned from the back-end + * @abstract + * @param {*} raw the raw object to parse + */ + public deserialize(raw: any) { + if ("stix" in raw) { + let sdo = raw.stix; + if ("name" in sdo) { + if (typeof(sdo.name) === "string") this.name = sdo.name; + else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") + } else this.name = ""; + + if ("description" in sdo) { + if (typeof(sdo.description) === "string") this.description = sdo.description; + else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") + } else this.description = ""; + + if ("x_mitre_platforms" in sdo) { + if (this.isStringArray(sdo.x_mitre_platforms)) this.platforms = sdo.x_mitre_platforms; + else logger.error("TypeError: platforms field is not a string array.") + } else this.platforms = []; + + if ("x_mitre_collection_layers" in sdo) { + if (this.isStringArray(sdo.x_mitre_collection_layers)) this.collection_layers = sdo.x_mitre_collection_layers; + else logger.error("TypeError: collection layers field is not a string array."); + } else this.collection_layers = []; + + if ("x_mitre_contributors" in sdo) { + if (this.isStringArray(sdo.x_mitre_contributors)) this.contributors = sdo.x_mitre_contributors; + else logger.error("TypeError: x_mitre_contributors is not a string array:", sdo.x_mitre_contributors, "(",typeof(sdo.x_mitre_contributors),")") + } else this.contributors = []; + + if ("x_mitre_domains" in sdo) { + if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; + else logger.error("TypeError: domains field is not a string array."); + } else this.domains = ['enterprise-attack']; + + if ("data_components" in sdo) { + if (typeof(sdo.data_components) == "object") this.data_components = sdo.data_components; // TODO: parse data components + else logger.error("TypeError: data components field is not an object:", sdo.data_components, "(", typeof(sdo.data_components), ")"); + } else this.data_components = []; + } + } + + /** + * Validate the current object state and return information on the result of the validation + * @param {RestApiConnectorService} restAPIService: the REST API connector through which asynchronous validation can be completed + * @returns {Observable} the validation warnings and errors once validation is complete. + */ + public validate(restAPIService: RestApiConnectorService): Observable { + return this.base_validate(restAPIService); + } + + /** + * Save the current state of the STIX object in the database. Update the current object from the response + * @param restAPIService [RestApiConnectorService] the service to perform the POST/PUT through + * @returns {Observable} of the post + */ + public save(restAPIService: RestApiConnectorService): Observable { + // TODO POST if the object was just created (doesn't exist in db yet) + + let postObservable = restAPIService.postDataSource(this); + let subscription = postObservable.subscribe({ + next: (result) => { this.deserialize(result.serialize()); }, + complete: () => { subscription.unsubscribe(); } + }); + return postObservable; + } +} diff --git a/app/src/app/classes/stix/stix-object.ts b/app/src/app/classes/stix/stix-object.ts index d289752f..c707c7b5 100644 --- a/app/src/app/classes/stix/stix-object.ts +++ b/app/src/app/classes/stix/stix-object.ts @@ -19,7 +19,9 @@ let stixTypeToAttackType = { "x-mitre-matrix": "matrix", "x-mitre-tactic": "tactic", "relationship": "relationship", - "marking-definition": "marking-definition" + "marking-definition": "marking-definition", + "x-mitre-data-source": "data-source", + "x-mitre-data-component": "data-component" } export {stixTypeToAttackType}; @@ -50,7 +52,9 @@ export abstract class StixObject extends Serializable { "matrix": "matrices", "tactic": "tactics", "note": "notes", - "marking-definition": "marking-definitions" + "marking-definition": "marking-definitions", + "data-source": "data-sources", + "data-component": "data-components" } public get routes(): any[] { // route to view the object From 5e1d2f29ac024cc9eb42f923d1fe0dff7a3e3471 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:12:28 -0400 Subject: [PATCH 09/89] define columns for data sources/components --- .../stix/stix-list/stix-list.component.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index 4630c1ae..74bd45b9 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -275,6 +275,30 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (!(this.config.relationshipType && this.config.relationshipType == "subtechnique-of")) this.addColumn("description", "description", "descriptive", false); // controls_after.push("open-link") break; + case "data-source": + this.addColumn("ID", "attackID", "plain", false); + this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); + this.addColumn("platforms", "platforms", "list"); + this.addColumn("domain", "domains", "list"); + this.addColumn("version", "version", "version"); + this.addColumn("modified","modified", "timestamp"); + this.addColumn("created", "created", "timestamp"); + this.tableDetail = [{ + "field": "description", + "display": "descriptive" + }] + break; + case "data-component": + this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); + this.addColumn("domain", "domains", "list"); + this.addColumn("version", "version", "version"); + this.addColumn("modified","modified", "timestamp"); + this.addColumn("created", "created", "timestamp"); + this.tableDetail = [{ + "field": "description", + "display": "descriptive" + }] + break; default: this.addColumn("type", "attackType", "plain"); this.addColumn("modified","modified", "timestamp"); @@ -462,6 +486,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { else if (this.config.type == "technique") this.data$ = this.restAPIConnectorService.getAllTechniques(options); else if (this.config.type.includes("collection")) this.data$ = this.restAPIConnectorService.getAllCollections({search: this.searchQuery, versions: "all"}); else if (this.config.type == "relationship") this.data$ = this.restAPIConnectorService.getRelatedTo({sourceRef: this.config.sourceRef, targetRef: this.config.targetRef, sourceType: this.config.sourceType, targetType: this.config.targetType, relationshipType: this.config.relationshipType, excludeSourceRefs: this.config.excludeSourceRefs, excludeTargetRefs: this.config.excludeTargetRefs, limit: limit, offset: offset, includeDeprecated: deprecated}); + else if (this.config.type == "data-source") this.data$ = this.restAPIConnectorService.getAllDataSources(options); let subscription = this.data$.subscribe({ next: (data) => { this.totalObjectCount = data.pagination.total; }, complete: () => { subscription.unsubscribe() } @@ -487,7 +512,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { } //allowed types for StixListConfig -type type_attacktype = "collection" | "group" | "matrix" | "mitigation" | "software" | "tactic" | "technique" | "relationship"; +type type_attacktype = "collection" | "group" | "matrix" | "mitigation" | "software" | "tactic" | "technique" | "relationship" | "data-source" | "data-component"; type selection_types = "one" | "many" | "disabled" export interface StixListConfig { /* if specified, shows the given STIX objects in the table instead of loading from the back-end based on other configurations. */ From f77e217fa6bede85a62047ad40aedd118c1d02bb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:17:21 -0400 Subject: [PATCH 10/89] data source list/view pages --- app/src/app/app.module.ts | 6 +- .../data-source-list.component.html | 11 ++ .../data-source-list.component.scss | 0 .../data-source-list.component.spec.ts | 25 ++++ .../data-source-list.component.ts | 15 +++ .../data-source-view.component.html | 126 ++++++++++++++++++ .../data-source-view.component.scss | 0 .../data-source-view.component.spec.ts | 25 ++++ .../data-source-view.component.ts | 17 +++ .../stix/stix-page/stix-page.component.html | 1 + .../stix/stix-page/stix-page.component.ts | 3 + 11 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/src/app/views/stix/data-source/data-source-list/data-source-list.component.html create mode 100644 app/src/app/views/stix/data-source/data-source-list/data-source-list.component.scss create mode 100644 app/src/app/views/stix/data-source/data-source-list/data-source-list.component.spec.ts create mode 100644 app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts create mode 100644 app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html create mode 100644 app/src/app/views/stix/data-source/data-source-view/data-source-view.component.scss create mode 100644 app/src/app/views/stix/data-source/data-source-view/data-source-view.component.spec.ts create mode 100644 app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts index 1f7b44e9..b0684d80 100644 --- a/app/src/app/app.module.ts +++ b/app/src/app/app.module.ts @@ -153,6 +153,8 @@ import { ObjectStatusComponent } from './components/object-status/object-status. import { IconViewComponent } from './components/stix/icon-view/icon-view.component'; import { IdentityPropertyComponent } from './components/stix/identity-property/identity-property.component'; import { NgxJdenticonModule, JDENTICON_CONFIG } from 'ngx-jdenticon'; +import { DataSourceViewComponent } from './views/stix/data-source/data-source-view/data-source-view.component'; +import { DataSourceListComponent } from './views/stix/data-source/data-source-list/data-source-list.component'; @NgModule({ @@ -249,7 +251,9 @@ import { NgxJdenticonModule, JDENTICON_CONFIG } from 'ngx-jdenticon'; NotesEditorComponent, ObjectStatusComponent, - IdentityPropertyComponent + IdentityPropertyComponent, + DataSourceViewComponent, + DataSourceListComponent ], imports: [ BreadcrumbModule, diff --git a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.html b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.html new file mode 100644 index 00000000..7f623b58 --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.html @@ -0,0 +1,11 @@ +
+
+

data sources

+
+ +
+
+ +
\ No newline at end of file diff --git a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.scss b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.spec.ts b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.spec.ts new file mode 100644 index 00000000..b1635cd4 --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataSourceListComponent } from './data-source-list.component'; + +describe('DataSourceListComponent', () => { + let component: DataSourceListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DataSourceListComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DataSourceListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts new file mode 100644 index 00000000..779a3708 --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts @@ -0,0 +1,15 @@ +import { Component, OnInit } from '@angular/core'; + +@Component({ + selector: 'app-data-source-list', + templateUrl: './data-source-list.component.html', + styleUrls: ['./data-source-list.component.scss'] +}) +export class DataSourceListComponent implements OnInit { + + constructor() { } + + ngOnInit(): void { + } + +} diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html new file mode 100644 index 00000000..7d22a8fa --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html @@ -0,0 +1,126 @@ +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+

Data Components

+
+
+
+
+ + +
+
+
+
+ +
+
+
+ +
+
+
+

References

+
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.scss b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.spec.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.spec.ts new file mode 100644 index 00000000..d8e99a62 --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataSourceViewComponent } from './data-source-view.component'; + +describe('DataSourceViewComponent', () => { + let component: DataSourceViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DataSourceViewComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DataSourceViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts new file mode 100644 index 00000000..02374832 --- /dev/null +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit } from '@angular/core'; +import { DataSource } from 'src/app/classes/stix/data-source'; +import { StixViewPage } from '../../stix-view-page'; +import { ActivatedRoute } from '@angular/router'; + +@Component({ + selector: 'app-data-source-view', + templateUrl: './data-source-view.component.html', + styleUrls: ['./data-source-view.component.scss'] +}) +export class DataSourceViewComponent extends StixViewPage implements OnInit { + public get data_source(): DataSource { return this.config.object as DataSource; } + + constructor(private route: ActivatedRoute) { super(); } + + ngOnInit(): void { } +} diff --git a/app/src/app/views/stix/stix-page/stix-page.component.html b/app/src/app/views/stix/stix-page/stix-page.component.html index b061a4a8..cb59a9e5 100644 --- a/app/src/app/views/stix/stix-page/stix-page.component.html +++ b/app/src/app/views/stix/stix-page/stix-page.component.html @@ -11,6 +11,7 @@

¯\_(ツ)_/¯ Nothing her + diff --git a/app/src/app/views/stix/stix-page/stix-page.component.ts b/app/src/app/views/stix/stix-page/stix-page.component.ts index 0cd6a3a3..bbb4bb13 100644 --- a/app/src/app/views/stix/stix-page/stix-page.component.ts +++ b/app/src/app/views/stix/stix-page/stix-page.component.ts @@ -4,6 +4,7 @@ import { ActivatedRoute, Router, NavigationEnd } from '@angular/router'; import { BreadcrumbService } from 'angular-crumbs'; import { Observable } from 'rxjs'; import { Collection } from 'src/app/classes/stix/collection'; +import { DataSource } from 'src/app/classes/stix/data-source'; import { Group } from 'src/app/classes/stix/group'; import { Matrix } from 'src/app/classes/stix/matrix'; import { Mitigation } from 'src/app/classes/stix/mitigation'; @@ -125,6 +126,7 @@ export class StixPageComponent implements OnInit, OnDestroy { else if (this.objectType == "tactic") objects$ = this.restAPIConnectorService.getTactic(objectStixID); else if (this.objectType == "technique") objects$ = this.restAPIConnectorService.getTechnique(objectStixID, null, "latest", true); else if (this.objectType == "collection") objects$ = this.restAPIConnectorService.getCollection(objectStixID, objectModified, "latest", false, true); + else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID); let subscription = objects$.subscribe({ next: result => { this.updateBreadcrumbs(result, this.objectType ); @@ -168,6 +170,7 @@ export class StixPageComponent implements OnInit, OnDestroy { this.objectType == "mitigation" ? new Mitigation() : this.objectType == "group" ? new Group(): this.objectType == "collection" ? new Collection() : + this.objectType == "data-source" ? new DataSource() : null // if not any of the above types ); this.initialVersion = new VersionNumber(this.objects[0].version.toString()); From e77e00f400ffa878a8cad6ee2c45ad9d2c9630b2 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:29:17 -0400 Subject: [PATCH 11/89] added data source routing --- app/src/app/app-routing-stix.module.ts | 50 +++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/app/src/app/app-routing-stix.module.ts b/app/src/app/app-routing-stix.module.ts index 5ac630a7..a1e00ee6 100644 --- a/app/src/app/app-routing-stix.module.ts +++ b/app/src/app/app-routing-stix.module.ts @@ -13,6 +13,7 @@ import { NgModule } from '@angular/core'; import { environment } from "../environments/environment" import { CollectionImportComponent } from "./views/stix/collection/collection-import/collection-import-workflow/collection-import.component"; import { CollectionIndexImportComponent } from "./views/stix/collection/collection-index/collection-index-import/collection-index-import.component"; +import { DataSourceListComponent } from "./views/stix/data-source/data-source-list/data-source-list.component"; const stixRoutes: Routes = [{ path: 'matrix', @@ -294,7 +295,54 @@ const stixRoutes: Routes = [{ } ] }, - + { + path: 'data-source', + data: { + breadcrumb: 'data sources' + }, + children: [{ + path: '', + data: { + breadcrumb: 'list', + title: "data sources" + }, + component: DataSourceListComponent + }, + { + path: ':id', + data: { + breadcrumb: 'loading...' + }, + children: [{ + path: '', + data: { + breadcrumb: 'view', + editable: true, + title: "view data source" + }, + component: StixPageComponent + } + ] + }, + { + path: ":new", + data: { + breadcrumb: "new data source" + }, + children: [{ + path: '', + data: { + breadcrumb: 'view', + editable: true, + title: "new data source" + }, + component: StixPageComponent + } + ] + } + ] + }, + ] if (environment.integrations.collection_manager.enabled) { From ce206836f569aec23dbe4b3c691d75b7da34c12f Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:30:21 -0400 Subject: [PATCH 12/89] add collection layers field to edit list property --- .../stix/list-property/list-edit/list-edit.component.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index 527975f5..9a6a4d55 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -35,7 +35,8 @@ export class ListEditComponent implements OnInit, AfterContentChecked { "tactic_type": "x_mitre_tactic_type", "impact_type": "x_mitre_impact_type", "effective_permissions": "x_mitre_effective_permissions", - "permissions_required": "x_mitre_permissions_required" + "permissions_required": "x_mitre_permissions_required", + "collection_layers": "x_mitre_collection_layers" } public domains = [ "enterprise-attack", @@ -60,7 +61,8 @@ export class ListEditComponent implements OnInit, AfterContentChecked { || this.config.field == 'permissions_required' || this.config.field == 'effective_permissions' || this.config.field == 'impact_type' - || this.config.field == 'domains') { + || this.config.field == 'domains' + || this.config.field == 'collection_layers') { if (!this.dataLoaded) { let data$ = this.restAPIConnectorService.getAllAllowedValues(); this.sub = data$.subscribe({ From 2be1c015aab8c768f8cc38a72801795dbe6c4743 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 16 Sep 2021 12:32:34 -0400 Subject: [PATCH 13/89] rest api functions for data sources --- .../rest-api/rest-api-connector.service.ts | 61 +++++++++++++++++-- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 831f1afa..9c03746e 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -20,9 +20,11 @@ import { Technique } from 'src/app/classes/stix/technique'; import { environment } from "../../../../environments/environment"; import { ApiConnector } from '../api-connector'; import { logger } from "../../../util/logger"; +import { DataSource } from 'src/app/classes/stix/data-source'; +import { DataComponent } from 'src/app/classes/stix/data-component'; //attack types -type AttackType = "collection" | "group" | "matrix" | "mitigation" | "software" | "tactic" | "technique" | "relationship" | "note" | "identity" | "marking-definition"; +type AttackType = "collection" | "group" | "matrix" | "mitigation" | "software" | "tactic" | "technique" | "relationship" | "note" | "identity" | "marking-definition" | "data-source" | "data-component"; // pluralize AttackType const attackTypeToPlural = { "technique": "techniques", @@ -35,7 +37,9 @@ const attackTypeToPlural = { "relationship": "relationships", "note": "notes", "identity": "identities", - "marking-definition": "marking-definitions" + "marking-definition": "marking-definitions", + "data-source": "data-sources", + "data-component": "data-components" } // transform AttackType to the relevant class const attackTypeToClass = { @@ -49,7 +53,9 @@ const attackTypeToClass = { "relationship": Relationship, "note": Note, "identity": Identity, - "marking-definition": MarkingDefinition + "marking-definition": MarkingDefinition, + "data-source": DataSource, + "data-component": DataComponent } // transform AttackType to the relevant class @@ -64,7 +70,9 @@ const stixTypeToClass = { "x-mitre-collection": Collection, "relationship": Relationship, "identity": Identity, - "marking-definition": MarkingDefinition + "marking-definition": MarkingDefinition, + "x-mitre-data-source": DataSource, + "x-mitre-data-component": DataComponent } export interface Paginated { @@ -228,6 +236,17 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} observable of retrieved objects */ public get getAllMitigations() { return this.getStixObjectsFactory("mitigation"); } + /** + * Get all data sources + * @param {number} [limit] the number of data sources to retrieve + * @param {number} [offset] the number of data sources to skip + * @param {string} [state] if specified, only get objects with this state + * @param {boolean} [revoked] if true, get revoked objects + * @param {boolean} [deprecated] if true, get deprecated objects + * @param {string[]} [excludeIDs] if specified, excludes these STIX IDs from the result + * @returns {Observable} observable of retrieved objects + */ + public get getAllDataSources() { return this.getStixObjectsFactory("data-source"); } /** * Get all matrices * @param {number} [limit] the number of matrices to retrieve @@ -452,6 +471,14 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} the object with the given ID and modified date */ public get getMitigation() { return this.getStixObjectFactory("mitigation"); } + /** + * Get a single data source by STIX ID + * @param {string} id the object STIX ID + * @param {Date} [modified] if specified, get the version modified at the given date + * @param {versions} [string] default "latest", if "all" returns all versions of the object instead of just the latest version. + * @returns {Observable} the object with the given ID and modified date + */ + public get getDataSource() { return this.getStixObjectFactory("data-source"); } /** * Get a single matrix by STIX ID * @param {string} id the object STIX ID @@ -540,6 +567,18 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} the created object */ public get postMitigation() { return this.postStixObjectFactory("mitigation"); } + /** + * POST (create) a new data source + * @param {DataSource} object the object to create + * @returns {Observable} the created object + */ + public get postDataSource() { return this.postStixObjectFactory("data-source"); } + /** + * POST (create) a new data component + * @param {DataComponent} object the object to create + * @returns {Observable} the created object + */ + public get postDataComponent() { return this.postStixObjectFactory("data-component"); } /** * POST (create) a new matrix * @param {Matrix} object the object to create @@ -631,6 +670,13 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} the updated object */ public get putMitigation() { return this.putStixObjectFactory("mitigation"); } + /** + * PUT (update) a data source + * @param {DataSource} object the object to update + * @param {Date} [modified] optional, the modified date to overwrite. If omitted, uses the modified field of the object + * @returns {Observable} the updated object + */ + public get putDataSource() { return this.putStixObjectFactory("data-source"); } /** * PUT (update) a matrix * @param {Matrix} object the object to update @@ -709,6 +755,13 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable<{}>} observable of the response body */ public get deleteMitigation() { return this.deleteStixObjectFactory("mitigation"); } + /** + * DELETE a data source + * @param {string} id the STIX ID of the object to delete + * @param {Date} modified the modified date of the version to delete + * @returns {Observable<{}>} observable of the response body + */ + public get deleteDataSource() { return this.deleteStixObjectFactory("data-source"); } /** * DELETE a matrix * @param {string} id the STIX ID of the object to delete From 60abea31b94f45af977560b0f249217dc601db96 Mon Sep 17 00:00:00 2001 From: idavila Date: Fri, 17 Sep 2021 09:41:21 -0400 Subject: [PATCH 14/89] Updating with feedback --- .../group/group-list/group-list.component.html | 4 +--- .../matrix/matrix-list/matrix-list.component.html | 3 +-- .../mitigation-list/mitigation-list.component.html | 4 +--- .../software-list/software-list.component.html | 14 +------------- .../tactic/tactic-list/tactic-list.component.html | 4 +--- .../technique-list/technique-list.component.html | 4 +--- 6 files changed, 6 insertions(+), 27 deletions(-) diff --git a/app/src/app/views/stix/group/group-list/group-list.component.html b/app/src/app/views/stix/group/group-list/group-list.component.html index 47eb6a14..c53056fd 100644 --- a/app/src/app/views/stix/group/group-list/group-list.component.html +++ b/app/src/app/views/stix/group/group-list/group-list.component.html @@ -5,15 +5,13 @@

Groups are sets of related intrusion activity that are tracked by a common name in the security community. Overlaps between names based on publicly reported associations are tracked using "Associated Groups" (also known as "Aliases"). -

- Groups support the standard set of fields as well as the "Associated Groups" field. Each associated group is tracked using a name and description. The alias description is typically used to hold a set of citations, though plain-text can also be entered alongside citations if additional context is necessary. Alias names cannot be changed after they are added, but the description can be changed by clicking on the entry in the associated groups list.

diff --git a/app/src/app/views/stix/matrix/matrix-list/matrix-list.component.html b/app/src/app/views/stix/matrix/matrix-list/matrix-list.component.html index 372ede1c..f00b46bd 100644 --- a/app/src/app/views/stix/matrix/matrix-list/matrix-list.component.html +++ b/app/src/app/views/stix/matrix/matrix-list/matrix-list.component.html @@ -5,14 +5,13 @@

- The relationship between tactics, techniques, and sub-techniques can be visualized in matrices + Matrices are an organized visualization depicting the relationships between tactics, techniques, and sub-techniques. ATT&CK Workbench does not support matrix visualizations at present, but you can still create and edit them for visualization in other tools.

diff --git a/app/src/app/views/stix/mitigation/mitigation-list/mitigation-list.component.html b/app/src/app/views/stix/mitigation/mitigation-list/mitigation-list.component.html index 86fa9655..70e1ee1a 100644 --- a/app/src/app/views/stix/mitigation/mitigation-list/mitigation-list.component.html +++ b/app/src/app/views/stix/mitigation/mitigation-list/mitigation-list.component.html @@ -5,15 +5,13 @@

Mitigations represent security concepts and classes of technologies that can be used to prevent a technique or sub-technique from being successfully executed. They support the standard set of fields and must be assigned to a domain. -

- A special mitigation published within the Enterprise domain, "Do Not Mitigate," should be used to mark any techniques which should not be mitigated.

diff --git a/app/src/app/views/stix/software/software-list/software-list.component.html b/app/src/app/views/stix/software/software-list/software-list.component.html index 69351ee7..2613c730 100644 --- a/app/src/app/views/stix/software/software-list/software-list.component.html +++ b/app/src/app/views/stix/software/software-list/software-list.component.html @@ -5,26 +5,14 @@

Software is a generic term for custom or commercial code, operating system utilities, open-source software, or other tools used to conduct behavior modeled in ATT&CK. Some instances of software have multiple names associated with the same instance due to various organizations tracking the same set of software by different names. The team makes a best effort to track overlaps between names based on publicly reported associations, which are designated as “Associated Software” on each page (also known as "Aliases"). -

- Software support the standard set of fields as well as the "Associated Software" field. Each associated software is tracked using a name and description. The alias description is typically used to hold a set of citations, though plain-text can also be entered alongside citations if additional context is necessary. Alias names cannot be changed after they are added, but the description can be changed by clicking on the entry in the associated groups list. -

- Types of Software:

-
    -
  • - malware: commercial, custom closed source, or open source software intended to be used for malicious purposes by adversaries -
  • -
  • - tool: commercial, open-source, built-in, or publicly available software that could be used by a defender, pen tester, red teamer, or an adversary -
  • -
diff --git a/app/src/app/views/stix/tactic/tactic-list/tactic-list.component.html b/app/src/app/views/stix/tactic/tactic-list/tactic-list.component.html index fc725588..9176608c 100644 --- a/app/src/app/views/stix/tactic/tactic-list/tactic-list.component.html +++ b/app/src/app/views/stix/tactic/tactic-list/tactic-list.component.html @@ -5,15 +5,13 @@

Tactics represent the "why" of an ATT&CK technique or sub-technique. It is the adversary's tactical goal: the reason for performing an action. For example, an adversary may want to achieve credential access. -

- Tactics support the standard set of fields, including a description supporting citations and markdown formatting. Tactics must be assigned to a domain before techniques can be assigned to them. The assignment of techniques to tactics can only be done on the techniques page.

diff --git a/app/src/app/views/stix/technique/technique-list/technique-list.component.html b/app/src/app/views/stix/technique/technique-list/technique-list.component.html index 7293e406..144d9d54 100644 --- a/app/src/app/views/stix/technique/technique-list/technique-list.component.html +++ b/app/src/app/views/stix/technique/technique-list/technique-list.component.html @@ -5,15 +5,13 @@

Techniques represent "how" an adversary achieves a tactical goal by performing an action. For example, an adversary may dump credentials to achieve credential access. -

- Like all objects, techniques support descriptions with markdown and citation support. The detection field also supports markdown and citations. Additionally, techniques list relevant platforms according to their domain. After a domain has been selected, the user may select the relevant platforms from the dropdown menu.

From 1d69e27689466a1ca6b51b81f7e7c0effa90b67a Mon Sep 17 00:00:00 2001 From: idavila Date: Fri, 17 Sep 2021 10:23:30 -0400 Subject: [PATCH 15/89] Updating changelog --- docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index fba316bb..210618fe 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -31,6 +31,10 @@ # Changelog +### ATT&CK Workbench version 1.1.0 +#### Improvements in 1.1.0 +- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). + ## 21 June 2021 ### ATT&CK Workbench version 1.0.0 #### Improvements in 1.0.0 From 4340064edc62206f3aa86de6b54c758ab77c17ba Mon Sep 17 00:00:00 2001 From: idavila Date: Fri, 17 Sep 2021 10:39:21 -0400 Subject: [PATCH 16/89] Updating changelog --- docs/changelog.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index 4528ce18..dd759f55 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,6 +33,9 @@ # Changelog ## Changes staged on develop ### ATT&CK Workbench version 1.1.0 +#### Improvements in 1.1.0 +- Added validation for missing ATT&CK IDs on objects that support them. Validation for missing ATT&CK IDs was also added when creating a collection. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). + #### Fixes in 1.1.0 - Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. ## 20 August 2021 From 1699ae11e0c1dea15b2f57d01195a606a42be4ee Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Sat, 18 Sep 2021 15:14:55 -0400 Subject: [PATCH 17/89] add parent object to name property config --- .../stix/name-property/name-property.component.html | 4 ++-- .../components/stix/name-property/name-property.component.ts | 4 ++++ .../technique/technique-view/technique-view.component.html | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/app/components/stix/name-property/name-property.component.html b/app/src/app/components/stix/name-property/name-property.component.html index 052df9f6..95337cab 100644 --- a/app/src/app/components/stix/name-property/name-property.component.html +++ b/app/src/app/components/stix/name-property/name-property.component.html @@ -1,12 +1,12 @@

- {{config.object.parentTechnique.name}}: {{config.object[config.field ? config.field : "name"]}} + {{config.parent.name}}: {{config.object[config.field ? config.field : "name"]}} name - {{config.object.parentTechnique.name}}: + {{config.parent.name}}: diff --git a/app/src/app/components/stix/name-property/name-property.component.ts b/app/src/app/components/stix/name-property/name-property.component.ts index 8b594e85..972ff846 100644 --- a/app/src/app/components/stix/name-property/name-property.component.ts +++ b/app/src/app/components/stix/name-property/name-property.component.ts @@ -46,6 +46,10 @@ export interface NamePropertyConfig { * Note: if mode is diff, pass an array of two objects to diff */ object: StixObject | [StixObject, StixObject]; + /* The parent object. If specified, the object name will be + * prefixed with the name of the parent + */ + parent?: StixObject; /* the field of the object(s) to visualize as a name * If unspecified, uses 'name' field as defined on StixObject */ diff --git a/app/src/app/views/stix/technique/technique-view/technique-view.component.html b/app/src/app/views/stix/technique/technique-view/technique-view.component.html index 1ec85ef4..46b051bd 100644 --- a/app/src/app/views/stix/technique/technique-view/technique-view.component.html +++ b/app/src/app/views/stix/technique/technique-view/technique-view.component.html @@ -4,7 +4,8 @@
From 14aaf27b3f87c31a15172ff3bb08ddd8adcd4ef5 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Sat, 18 Sep 2021 15:20:44 -0400 Subject: [PATCH 18/89] data component dialog page --- app/src/app/app.module.ts | 4 +- .../data-component-view.component.html | 69 +++++++++++++++++++ .../data-component-view.component.scss | 0 .../data-component-view.component.spec.ts | 25 +++++++ .../data-component-view.component.ts | 21 ++++++ .../stix-dialog/stix-dialog.component.html | 5 ++ 6 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html create mode 100644 app/src/app/views/stix/data-component/data-component-view/data-component-view.component.scss create mode 100644 app/src/app/views/stix/data-component/data-component-view/data-component-view.component.spec.ts create mode 100644 app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts index b0684d80..8fc1c85c 100644 --- a/app/src/app/app.module.ts +++ b/app/src/app/app.module.ts @@ -155,6 +155,7 @@ import { IdentityPropertyComponent } from './components/stix/identity-property/i import { NgxJdenticonModule, JDENTICON_CONFIG } from 'ngx-jdenticon'; import { DataSourceViewComponent } from './views/stix/data-source/data-source-view/data-source-view.component'; import { DataSourceListComponent } from './views/stix/data-source/data-source-list/data-source-list.component'; +import { DataComponentViewComponent } from './views/stix/data-component/data-component-view/data-component-view.component'; @NgModule({ @@ -253,7 +254,8 @@ import { DataSourceListComponent } from './views/stix/data-source/data-source-li ObjectStatusComponent, IdentityPropertyComponent, DataSourceViewComponent, - DataSourceListComponent + DataSourceListComponent, + DataComponentViewComponent ], imports: [ BreadcrumbModule, diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html new file mode 100644 index 00000000..9b25f01a --- /dev/null +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html @@ -0,0 +1,69 @@ +
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ +
+
+
+

Techniques Detected

+
+
+
+ +
+
+
+

References

+
+
+
+
+ +
+
+
+
\ No newline at end of file diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.scss b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.spec.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.spec.ts new file mode 100644 index 00000000..c6867efb --- /dev/null +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DataComponentViewComponent } from './data-component-view.component'; + +describe('DataComponentViewComponent', () => { + let component: DataComponentViewComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [DataComponentViewComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DataComponentViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts new file mode 100644 index 00000000..3077b867 --- /dev/null +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts @@ -0,0 +1,21 @@ +import { Component, OnInit } from '@angular/core'; +import { DataComponent } from 'src/app/classes/stix/data-component'; +import { DataSource } from 'src/app/classes/stix/data-source'; +import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; +import { StixViewPage } from '../../stix-view-page'; + +@Component({ + selector: 'app-data-component-view', + templateUrl: './data-component-view.component.html', + styleUrls: ['./data-component-view.component.scss'] +}) +export class DataComponentViewComponent extends StixViewPage implements OnInit { + public get data_component() { return this.config.object as DataComponent; } + + constructor(private restAPIConnectorService: RestApiConnectorService) { super(); } + + ngOnInit(): void { + // TODO retrieve parent data source + } + +} diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html index b18f592b..6331bd88 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html @@ -48,6 +48,11 @@ (onOpenHistory)="openHistory()" (onOpenNotes)="openNotes()"> + + Date: Tue, 21 Sep 2021 10:00:05 -0400 Subject: [PATCH 19/89] data source label for cross-domain techniques --- .../technique-view/technique-view.component.html | 2 +- .../technique-view/technique-view.component.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/app/views/stix/technique/technique-view/technique-view.component.html b/app/src/app/views/stix/technique/technique-view/technique-view.component.html index 46b051bd..fa13e6fd 100644 --- a/app/src/app/views/stix/technique/technique-view/technique-view.component.html +++ b/app/src/app/views/stix/technique/technique-view/technique-view.component.html @@ -107,7 +107,7 @@ mode: editing? 'edit': 'view', object: technique, field: 'data_sources', - label: 'data sources', + label: dataSourcesLabel(), editType: 'any' }">

diff --git a/app/src/app/views/stix/technique/technique-view/technique-view.component.ts b/app/src/app/views/stix/technique/technique-view/technique-view.component.ts index 1e34dee9..2c2df64b 100644 --- a/app/src/app/views/stix/technique/technique-view/technique-view.component.ts +++ b/app/src/app/views/stix/technique/technique-view/technique-view.component.ts @@ -29,6 +29,18 @@ export class TechniqueViewComponent extends StixViewPage implements OnInit { this.ref.detectChanges(); } + /** + * Get label for the data sources field. + * Appends 'ics' for clarification if the object is cross-domain + */ + public dataSourcesLabel(): string { + let label = 'data sources'; + if (this.technique.domains.includes('ics-attack') && this.technique.domains.length > 1) { + label = 'ics ' + label; + } + return label; + } + public showDomainField(domain: string, field: string): boolean { return this.technique.domains.includes(domain) && (this.technique[field].length > 0 || this.editing); } From 0e042d53e23c0c88c25c7f1a2f166b28eee190fa Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:04:15 -0400 Subject: [PATCH 20/89] show relationships on data component dialog --- app/src/app/views/stix/stix-dialog/stix-dialog.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts index 7f7af02e..cd0e6091 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts @@ -19,10 +19,11 @@ export class StixDialogComponent implements OnInit { if (this._config.mode && this._config.mode == "edit") this.startEditing(); } public get config(): StixViewConfig { + let object = Array.isArray(this._config.object)? this._config.object[0] : this._config.object; return { mode: this.editing? "edit" : "view", - object: this._config.object, - showRelationships: false, + object: object, + showRelationships: object.attackType == "data-component" ? true : false, editable: this._config.editable, sidebarControl: this._config.sidebarControl == "disable"? "disable" : "events" } From f7f8bc554a5129561d2eebb782d30ec33f55dae9 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:07:03 -0400 Subject: [PATCH 21/89] update data source & component models --- app/src/app/classes/stix/data-component.ts | 2 +- app/src/app/classes/stix/data-source.ts | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts index a0ca6975..d8c0c50c 100644 --- a/app/src/app/classes/stix/data-component.ts +++ b/app/src/app/classes/stix/data-component.ts @@ -62,7 +62,7 @@ export class DataComponent extends StixObject { if ("x_mitre_domains" in sdo) { if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; else logger.error("TypeError: domains field is not a string array."); - } else this.domains = []; + } else this.domains = ['enterprise-attack']; // default to enterprise } } diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index add37fc2..b0a9f662 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -14,11 +14,6 @@ export class DataSource extends StixObject { public domains: string[] = []; public data_components: DataComponent[] = []; - /** - * TODO this assumes data sources are returned with a list of their associated - * data component STIX objects - */ - protected get attackIDValidator() { return { regex: "DS\\d{4}", @@ -87,10 +82,10 @@ export class DataSource extends StixObject { if ("x_mitre_domains" in sdo) { if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; else logger.error("TypeError: domains field is not a string array."); - } else this.domains = ['enterprise-attack']; + } else this.domains = ['enterprise-attack']; // default to enterprise if ("data_components" in sdo) { - if (typeof(sdo.data_components) == "object") this.data_components = sdo.data_components; // TODO: parse data components + if (typeof(sdo.data_components) == "object") this.data_components = sdo.data_components.map(dc => new DataComponent(dc)); else logger.error("TypeError: data components field is not an object:", sdo.data_components, "(", typeof(sdo.data_components), ")"); } else this.data_components = []; } From 992d7780c5e62e3ebb364ceb4a1e7500806ceb6b Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 21 Sep 2021 10:11:25 -0400 Subject: [PATCH 22/89] add option to retrieve data components with a given data source --- .../connectors/rest-api/rest-api-connector.service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 9c03746e..2e7f8d45 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -368,7 +368,7 @@ export class RestApiConnectorService extends ApiConnector { private getStixObjectFactory(attackType: AttackType) { let attackClass = attackTypeToClass[attackType]; let plural = attackTypeToPlural[attackType] - return function

(id: string, modified?: Date | string, versions="latest", includeSubs?: boolean, retrieveContents?: boolean): Observable { + return function

(id: string, modified?: Date | string, versions="latest", includeSubs?: boolean, retrieveContents?: boolean, retrieveDataComponents?: boolean): Observable { let url = `${this.baseUrl}/${plural}/${id}`; if (modified) { let modifiedString = typeof(modified) == "string"? modified : modified.toISOString(); @@ -377,6 +377,7 @@ export class RestApiConnectorService extends ApiConnector { let query = new HttpParams(); if (versions != "latest") query = query.set("versions", versions); if (attackType == "collection" && retrieveContents) query = query.set("retrieveContents", "true"); + if (attackType == "data-source" && retrieveDataComponents) query = query.set("retrieveDataComponents", "true"); return this.http.get(url, {headers: this.headers, params: query}).pipe( tap(result => logger.log(`retrieved ${attackType}`, result)), // on success, trigger the success notification map(result => { @@ -476,6 +477,7 @@ export class RestApiConnectorService extends ApiConnector { * @param {string} id the object STIX ID * @param {Date} [modified] if specified, get the version modified at the given date * @param {versions} [string] default "latest", if "all" returns all versions of the object instead of just the latest version. + * @param {retrieveDataComponents} [boolean] if true, include data components with a reference to the given object. Incompatible with versions="all" * @returns {Observable} the object with the given ID and modified date */ public get getDataSource() { return this.getStixObjectFactory("data-source"); } From b25b4ad782c966c368869b7e3ae747daecd2ec04 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 21 Sep 2021 11:19:37 -0400 Subject: [PATCH 23/89] valid source & target types for detects relationship --- app/src/app/classes/stix/relationship.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 8ba44bbf..177db617 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -34,6 +34,7 @@ export class Relationship extends StixObject { } if (this.relationship_type == "mitigates") return ["mitigation"]; if (this.relationship_type == "subtechnique-of") return ["technique"]; + if (this.relationship_type == "detects") return ["data-component"]; else return null; } /** @@ -47,6 +48,7 @@ export class Relationship extends StixObject { } if (this.relationship_type == "mitigates") return ["technique"]; if (this.relationship_type == "subtechnique-of") return ["technique"]; + if (this.relationship_type == "detects") return ["technique"]; else return null; } From 279639b3e3046ad07295afbfe25a06e8a818b829 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:23:36 -0400 Subject: [PATCH 24/89] validation for data source & data component objects --- app/src/app/classes/stix/stix-object.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/app/classes/stix/stix-object.ts b/app/src/app/classes/stix/stix-object.ts index c707c7b5..4b79def3 100644 --- a/app/src/app/classes/stix/stix-object.ts +++ b/app/src/app/classes/stix/stix-object.ts @@ -300,6 +300,8 @@ export abstract class StixObject extends Serializable { this.attackType == "matrix"? restAPIService.getAllMatrices() : this.attackType == "mitigation"? restAPIService.getAllMitigations() : this.attackType == "technique"? restAPIService.getAllTechniques() : + this.attackType == "data-source" ? restAPIService.getAllDataSources() : + this.attackType == "data-component" ? restAPIService.getAllDataComponents() : restAPIService.getAllTactics(); return accessor.pipe( map(objects => { From ae682ff9116fa3f746de22e5e256d84dcdc8a9d8 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:24:27 -0400 Subject: [PATCH 25/89] rest api functions for data components --- .../rest-api/rest-api-connector.service.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 2e7f8d45..9eb1f731 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -247,6 +247,17 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} observable of retrieved objects */ public get getAllDataSources() { return this.getStixObjectsFactory("data-source"); } + /** + * Get all data components + * @param {number} [limit] the number of data components to retrieve + * @param {number} [offset] the number of data components to skip + * @param {string} [state] if specified, only get objects with this state + * @param {boolean} [revoked] if true, get revoked objects + * @param {boolean} [deprecated] if true, get deprecated objects + * @param {string[]} [excludeIDs] if specified, excludes these STIX IDs from the result + * @returns {Observable} observable of retrieved objects + */ + public get getAllDataComponents() { return this.getStixObjectsFactory("data-component"); } /** * Get all matrices * @param {number} [limit] the number of matrices to retrieve @@ -480,7 +491,15 @@ export class RestApiConnectorService extends ApiConnector { * @param {retrieveDataComponents} [boolean] if true, include data components with a reference to the given object. Incompatible with versions="all" * @returns {Observable} the object with the given ID and modified date */ - public get getDataSource() { return this.getStixObjectFactory("data-source"); } + public get getDataSource() { return this.getStixObjectFactory("data-source"); } + /** + * Get a single data component by STIX ID + * @param {string} id the object STIX ID + * @param {Date} [modified] if specified, get the version modified at the given date + * @param {versions} [string] default "latest", if "all" returns all versions of the object instead of just the latest version. + * @returns {Observable} the object with the given ID and modified date + */ + public get getDataComponent() { return this.getStixObjectFactory("data-component"); } /** * Get a single matrix by STIX ID * @param {string} id the object STIX ID @@ -679,6 +698,13 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable} the updated object */ public get putDataSource() { return this.putStixObjectFactory("data-source"); } + /** + * PUT (update) a data component + * @param {DataComponent} object the object to update + * @param {Date} [modified] optional, the modified date to overwrite. If omitted, uses the modified field of the object + * @returns {Observable} the updated object + */ + public get putDataComponent() { return this.putStixObjectFactory("data-component"); } /** * PUT (update) a matrix * @param {Matrix} object the object to update @@ -764,6 +790,13 @@ export class RestApiConnectorService extends ApiConnector { * @returns {Observable<{}>} observable of the response body */ public get deleteDataSource() { return this.deleteStixObjectFactory("data-source"); } + /** + * DELETE a data component + * @param {string} id the STIX ID of the object to delete + * @param {Date} modified the modified date of the version to delete + * @returns {Observable<{}>} observable of the response body + */ + public get deleteDataComponent() { return this.deleteStixObjectFactory("data-component"); } /** * DELETE a matrix * @param {string} id the STIX ID of the object to delete From 9f54711d73420a0c006b0d981a05dfbb22cb2a74 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 22 Sep 2021 10:31:12 -0400 Subject: [PATCH 26/89] view/edit relationship in dialog --- .../data-component-view.component.html | 25 +++++++++++++++++++ .../data-component-view.component.ts | 9 +++++-- .../stix-dialog/stix-dialog.component.html | 4 ++- .../stix/stix-dialog/stix-dialog.component.ts | 10 ++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html index 9b25f01a..7efac968 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html @@ -48,6 +48,31 @@

Techniques Detected

+
+
+ +
+
+
+
+ +
+
diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts index 3077b867..36ef0995 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts @@ -1,6 +1,6 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { DataComponent } from 'src/app/classes/stix/data-component'; -import { DataSource } from 'src/app/classes/stix/data-source'; +import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { StixViewPage } from '../../stix-view-page'; @@ -10,6 +10,8 @@ import { StixViewPage } from '../../stix-view-page'; styleUrls: ['./data-component-view.component.scss'] }) export class DataComponentViewComponent extends StixViewPage implements OnInit { + @Output() onClickRelationship = new EventEmitter(); + public get data_component() { return this.config.object as DataComponent; } constructor(private restAPIConnectorService: RestApiConnectorService) { super(); } @@ -18,4 +20,7 @@ export class DataComponentViewComponent extends StixViewPage implements OnInit { // TODO retrieve parent data source } + public viewRelationship(object: StixObject): void { + this.onClickRelationship.emit(object); + } } diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html index 6331bd88..c83935b5 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html @@ -4,6 +4,7 @@
+
@@ -51,7 +52,8 @@ + (onOpenNotes)="openNotes()" + (onClickRelationship)="openObject($event)"> Date: Thu, 23 Sep 2021 13:50:57 -0400 Subject: [PATCH 27/89] remove dead code --- .../collection-export.component.html | 36 --------- .../collection-export.component.scss | 14 ---- .../collection-export.component.spec.ts | 25 ------ .../collection-export.component.ts | 78 ------------------- .../collection-exported-list.component.html | 1 - .../collection-exported-list.component.scss | 0 ...collection-exported-list.component.spec.ts | 25 ------ .../collection-exported-list.component.ts | 15 ---- 8 files changed, 194 deletions(-) delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.html delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.scss delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.spec.ts delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.ts delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.html delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.scss delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.spec.ts delete mode 100644 app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.ts diff --git a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.html b/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.html deleted file mode 100644 index d84670ea..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.html +++ /dev/null @@ -1,36 +0,0 @@ -
- - -
-
- - name - - name is required - - - version - - invalid version number - version must be incremented - -
-
- - description - - -
-
- -
-
-
- - TODO - - - TODO export button - -
-
diff --git a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.scss b/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.scss deleted file mode 100644 index 85cdf8f2..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.scss +++ /dev/null @@ -1,14 +0,0 @@ -@import "../.../../../../../../style/globals"; -.collectionInfo { - mat-form-field {box-sizing: content-box;} - .name { - flex-grow: 1 - } - .version { - width: 25%; - } - .description { - width: 100%; - } - -} \ No newline at end of file diff --git a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.spec.ts b/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.spec.ts deleted file mode 100644 index 35963e15..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CollectionExportComponent } from './collection-export.component'; - -describe('CollectionExportComponent', () => { - let component: CollectionExportComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ CollectionExportComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(CollectionExportComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.ts b/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.ts deleted file mode 100644 index 07aa5d52..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-export-workflow/collection-export.component.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Component, OnInit } from '@angular/core'; -import { Collection } from 'src/app/classes/stix/collection'; -import { ActivatedRoute } from '@angular/router'; -import { CollectionService } from 'src/app/services/stix/collection/collection.service'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { versionNumberIncrementValidator, versionNumberFormatValidator } from 'src/app/classes/version-number'; -import { GroupService } from 'src/app/services/stix/group/group.service'; -import { MatrixService } from 'src/app/services/stix/matrix/matrix.service'; -import { MitigationService } from 'src/app/services/stix/mitigation/mitigation.service'; -import { SoftwareService } from 'src/app/services/stix/software/software.service'; -import { TacticService } from 'src/app/services/stix/tactic/tactic.service'; -import { TechniqueService } from 'src/app/services/stix/technique/technique.service'; -import { Group } from 'src/app/classes/stix/group'; -import { Software } from 'src/app/classes/stix/software'; -import { Matrix } from 'src/app/classes/stix/matrix'; -import { Mitigation } from 'src/app/classes/stix/mitigation'; -import { Tactic } from 'src/app/classes/stix/tactic'; -import { Technique } from 'src/app/classes/stix/technique'; -import { StixObject } from 'src/app/classes/stix/stix-object'; - -@Component({ - selector: 'app-collection-export', - templateUrl: './collection-export.component.html', - styleUrls: ['./collection-export.component.scss'] -}) -export class CollectionExportComponent implements OnInit { - - public collection: Collection; - - public collectionInformation: FormGroup; - - // this won't actually be here, it's just for the mockups - public contents: StixObject[] = []; - - constructor(private route: ActivatedRoute, - private collectionService: CollectionService, - private formBuilder: FormBuilder, - - private groupService: GroupService, - private matrixService: MatrixService, - private mitigationService: MitigationService, - private softwareService: SoftwareService, - private tacticService: TacticService, - private techniqueService: TechniqueService) { } - - ngOnInit() { - let id = this.route.snapshot.paramMap.get("id"); - if (id) this.collection = this.collectionService.get(id, true); - else this.collection = new Collection(); - - let versionValidators = id ? [ //if existing collection, require increment - Validators.required, //field is required - versionNumberFormatValidator(), //must be formatted correctly - versionNumberIncrementValidator(this.collection.version) //must be incremented compared to previous collection version - ] : [ //if new collection, do not require increment - Validators.required, //field is required - versionNumberFormatValidator(), //must be formatted correctly - ] - - this.collectionInformation = this.formBuilder.group({ - name: [this.collection.name, Validators.required], - description: [this.collection.description], - version: [this.collection.version.toString(), versionValidators], - }) - this.collectionInformation.markAllAsTouched(); // force field validation on page load - for (let service of [this.groupService, - this.matrixService, - this.mitigationService, - this.softwareService, - this.tacticService, - this.techniqueService]) { - this.contents = this.contents.concat(service.getAll()); - } - } - - - -} diff --git a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.html b/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.html deleted file mode 100644 index f7dbe07e..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.html +++ /dev/null @@ -1 +0,0 @@ -

collection-exported-list works!

diff --git a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.scss b/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.spec.ts b/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.spec.ts deleted file mode 100644 index 27cb8aaa..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.spec.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { CollectionExportedListComponent } from './collection-exported-list.component'; - -describe('CollectionExportedListComponent', () => { - let component: CollectionExportedListComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ CollectionExportedListComponent ] - }) - .compileComponents(); - }); - - beforeEach(() => { - fixture = TestBed.createComponent(CollectionExportedListComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.ts b/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.ts deleted file mode 100644 index ccee708c..00000000 --- a/app/src/app/views/stix/collection/collection-export/collection-exported-list/collection-exported-list.component.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-collection-exported-list', - templateUrl: './collection-exported-list.component.html', - styleUrls: ['./collection-exported-list.component.scss'] -}) -export class CollectionExportedListComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} From b74ffe70fd7822d8e4590f99244124056b631107 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 23 Sep 2021 15:06:52 -0400 Subject: [PATCH 28/89] improve collection import error visibility --- app/package-lock.json | 2 +- app/package.json | 2 +- .../rest-api/rest-api-connector.service.ts | 4 ++-- .../collection-import.component.html | 22 ++++++++++++++++--- .../collection-import.component.scss | 5 +++++ .../collection-import.component.ts | 21 +++++++++++++++++- app/src/style/layouts/expansion-panel.scss | 3 +-- docs/changelog.md | 13 +++++++---- 8 files changed, 58 insertions(+), 14 deletions(-) diff --git a/app/package-lock.json b/app/package-lock.json index 3fbd5b5d..11f387ad 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -1,6 +1,6 @@ { "name": "attack-workbench-frontend", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/app/package.json b/app/package.json index 0b0e0f39..e551f5f0 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "attack-workbench-frontend", - "version": "1.0.2", + "version": "1.0.3", "description": "An application allowing users to explore, create, annotate, and share extensions of the MITRE ATT&CK® knowledge base. This repository contains an Angular-based web application providing the user interface for the ATT&CK Workbench application.", "keywords": [ "cti", diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 831f1afa..4db30718 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -1076,8 +1076,8 @@ export class RestApiConnectorService extends ApiConnector { * @param data: the data to download. Must be a JSON * @param filename: the name of the file to download */ - private triggerBrowserDownload(data: any, filename: string) { - let url = URL.createObjectURL(new Blob([JSON.stringify(data)], {type: "text/json"})); + public triggerBrowserDownload(data: any, filename: string) { + let url = URL.createObjectURL(new Blob([JSON.stringify(data, null, 4)], {type: "text/json"})); let downloadLink = document.createElement("a"); downloadLink.href = url; downloadLink.download = filename diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html index 1c2aa47c..eb12f715 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html @@ -56,11 +56,27 @@
-

Success!

+

Success! The collection has been imported into your workbench.

- The collection has been imported into your workbench. {{select.selected.length}} new objects were added. You can review the results of this import at any time on the imported collections section of the collection page. + {{successfully_saved.size}} new objects were added. You can review the imported objects at any time on the imported collections section of the collection page. +

+ +

+ {{save_errors.length}} objects could not be imported. +

+ + + + Import errors + + + +
{{save_errors | json}}
+
+
+

+

-
diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.scss b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.scss index f68dbd6e..76f16f77 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.scss +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.scss @@ -20,6 +20,11 @@ flex-grow: 1; } } + .error-log { + overflow-x: scroll; + overflow-y: auto; + max-height: 30em; + } } // .collection-import { // width: 45em; diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index efd3ae04..063a8aa2 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -36,6 +36,8 @@ export class CollectionImportComponent implements OnInit { public changed_ids: string[] = []; // ids of objects which have nto changed (object-version not already in knowledge base) public unchanged_ids: string[] = []; + public save_errors: string[] = []; + public successfully_saved: Set = new Set(); public collectionBundle: any; public object_import_categories = { @@ -215,7 +217,17 @@ export class CollectionImportComponent implements OnInit { } newBundle.objects = objects; let subscription = this.restAPIConnectorService.postCollectionBundle(newBundle, false).subscribe({ - next: () => { + next: (results) => { + if (results.import_categories.errors.length > 0) { + logger.warn("Collection import completed with errors:", results.import_categories.errors); + } + this.save_errors = results.import_categories.errors.filter(err => err["error_type"] == "Save error"); + let save_error_ids = new Set(this.save_errors.map(err => err['object_ref'])); + for (let category in results.import_categories) { + if (category == "errors") continue; + for (let id of results.import_categories[category]) if (!save_error_ids.has(id)) this.successfully_saved.add(id); + } + logger.log("Successfully imported the following objects:", Array.from(this.successfully_saved)); this.stepper.next(); }, complete: () => { subscription.unsubscribe(); } //prevent memory leaks @@ -227,4 +239,11 @@ export class CollectionImportComponent implements OnInit { }) } + /** + * Download a log of errors from the import + */ + public downloadErrorLog() { + this.restAPIConnectorService.triggerBrowserDownload(this.save_errors, "import-errors.json") + } + } \ No newline at end of file diff --git a/app/src/style/layouts/expansion-panel.scss b/app/src/style/layouts/expansion-panel.scss index 3e056f89..86d191fe 100644 --- a/app/src/style/layouts/expansion-panel.scss +++ b/app/src/style/layouts/expansion-panel.scss @@ -5,7 +5,7 @@ .dark & { border: 1px solid border-color(dark); } .light & { border: 1px solid border-color(light); } } - &:not(:first-child):not(.mat-expanded) { border-top-width: 0px } + &:not(:first-of-type):not(.mat-expanded) { border-top-width: 0px } &.mat-expanded + .mat-expansion-panel { border-top-width: 1px; } @@ -13,7 +13,6 @@ flex-basis: 0; align-items: center; } - .mat-expansion-panel-header-title { } .mat-expansion-panel-header-description { justify-content: flex-start; } diff --git a/docs/changelog.md b/docs/changelog.md index 8c65468e..d316976f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,13 +33,18 @@ # Changelog ## Changes staged on develop -### ATT&CK Workbench version 1.1.0 -#### Improvements in 1.1.0 +### ATT&CK Workbench version 1.0.3 +#### Improvements in 1.0.3 - Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Improved the visibility of errors in collection imports: + - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. + - REST API will now log import errors to the console. + - Frontend will now log import errors to the console when the application environment is not set to production. -#### Fixes in 1.1.0 +#### Fixes in 1.0.3 - Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. - +- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. +- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). ## 20 August 2021 ### ATT&CK Workbench version 1.0.2 #### Fixes in 1.0.2 From 00f83d9412ea2cc8def0a611b222de16a652f531 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 23 Sep 2021 15:10:51 -0400 Subject: [PATCH 29/89] fix String to string --- .../collection-import-workflow/collection-import.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index 063a8aa2..7fd4725a 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -37,7 +37,7 @@ export class CollectionImportComponent implements OnInit { // ids of objects which have nto changed (object-version not already in knowledge base) public unchanged_ids: string[] = []; public save_errors: string[] = []; - public successfully_saved: Set = new Set(); + public successfully_saved: Set = new Set(); public collectionBundle: any; public object_import_categories = { From b5d01157391df39b6643f67e82c912a41bb6e7ea Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 23 Sep 2021 15:14:36 -0400 Subject: [PATCH 30/89] include data source & data component objects in collection import workflow --- app/src/app/classes/stix/collection.ts | 40 ++++++++++++------- .../collection-import-summary.component.html | 4 +- .../collection-import-summary.component.ts | 9 ++++- .../collection-import-review.component.ts | 24 +++++++---- .../collection-import.component.ts | 24 +++++++---- .../collection-view.component.html | 4 +- .../collection-view.component.ts | 38 +++++++++++------- 7 files changed, 96 insertions(+), 47 deletions(-) diff --git a/app/src/app/classes/stix/collection.ts b/app/src/app/classes/stix/collection.ts index 1e70a70c..0bfbebe9 100644 --- a/app/src/app/classes/stix/collection.ts +++ b/app/src/app/classes/stix/collection.ts @@ -9,6 +9,8 @@ import { Software } from './software'; import { StixObject } from './stix-object'; import { Tactic } from './tactic'; import { Technique } from './technique'; +import { DataSource } from './data-source'; +import { DataComponent } from './data-component'; import { logger } from "../../util/logger"; /** @@ -279,6 +281,12 @@ export class Collection extends StixObject { case "intrusion-set": //group this.stix_contents.push(new Group(obj)) break; + case "x-mitre-data-source": // data source + this.stix_contents.push(new DataSource(obj)) + break; + case "x-mitre-data-component": // data component + this.stix_contents.push(new DataComponent(obj)) + break; } } } @@ -289,22 +297,26 @@ export class Collection extends StixObject { * @memberof Collection */ public compareTo(that: Collection): { - technique: CollectionDiffCategories, - tactic: CollectionDiffCategories, - software: CollectionDiffCategories, - relationship: CollectionDiffCategories, - mitigation: CollectionDiffCategories, - matrix: CollectionDiffCategories, - group: CollectionDiffCategories + technique: CollectionDiffCategories, + tactic: CollectionDiffCategories, + software: CollectionDiffCategories, + relationship: CollectionDiffCategories, + mitigation: CollectionDiffCategories, + matrix: CollectionDiffCategories, + group: CollectionDiffCategories, + data_source: CollectionDiffCategories, + data_component: CollectionDiffCategories } { let results = { - technique: new CollectionDiffCategories(), - tactic: new CollectionDiffCategories(), - software: new CollectionDiffCategories(), - relationship: new CollectionDiffCategories(), - mitigation: new CollectionDiffCategories(), - matrix: new CollectionDiffCategories(), - group: new CollectionDiffCategories() + technique: new CollectionDiffCategories(), + tactic: new CollectionDiffCategories(), + software: new CollectionDiffCategories(), + relationship: new CollectionDiffCategories(), + mitigation: new CollectionDiffCategories(), + matrix: new CollectionDiffCategories(), + group: new CollectionDiffCategories(), + data_source: new CollectionDiffCategories(), + data_component: new CollectionDiffCategories() } // build helper lookups to reduce complexity from n^2 to n. let thisStixLookup = new Map(this.stix_contents.map(sdo => [sdo.stixID, sdo])) diff --git a/app/src/app/components/collection-import-summary/collection-import-summary.component.html b/app/src/app/components/collection-import-summary/collection-import-summary.component.html index d11f0e03..c9f2f39e 100644 --- a/app/src/app/components/collection-import-summary/collection-import-summary.component.html +++ b/app/src/app/components/collection-import-summary/collection-import-summary.component.html @@ -1,9 +1,9 @@
- + - {{attackType}} ({{config.object_import_categories[attackType].object_count}}) + {{format(attackType)}} ({{config.object_import_categories[attackType].object_count}}) diff --git a/app/src/app/components/collection-import-summary/collection-import-summary.component.ts b/app/src/app/components/collection-import-summary/collection-import-summary.component.ts index ff823737..2b6bed6c 100644 --- a/app/src/app/components/collection-import-summary/collection-import-summary.component.ts +++ b/app/src/app/components/collection-import-summary/collection-import-summary.component.ts @@ -1,6 +1,8 @@ import { SelectionModel } from '@angular/cdk/collections'; import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { CollectionDiffCategories } from 'src/app/classes/stix/collection'; +import { DataComponent } from 'src/app/classes/stix/data-component'; +import { DataSource } from 'src/app/classes/stix/data-source'; import { Group } from 'src/app/classes/stix/group'; import { Matrix } from 'src/app/classes/stix/matrix'; import { Mitigation } from 'src/app/classes/stix/mitigation'; @@ -23,6 +25,9 @@ export class CollectionImportSummaryComponent implements OnInit { ngOnInit(): void { } + public format(attackType: string): string { + return attackType.replace(/_/g, ' '); + } } export interface CollectionImportSummaryConfig { @@ -33,7 +38,9 @@ export interface CollectionImportSummaryConfig { relationship: CollectionDiffCategories, mitigation: CollectionDiffCategories, matrix: CollectionDiffCategories, - group: CollectionDiffCategories + group: CollectionDiffCategories, + data_source: CollectionDiffCategories, + data_component: CollectionDiffCategories }; select?: SelectionModel; } diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.ts index ff6f2ed3..2a1b3f09 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-review/collection-import-review.component.ts @@ -8,6 +8,8 @@ import { Relationship } from 'src/app/classes/stix/relationship'; import { Software } from 'src/app/classes/stix/software'; import { Tactic } from 'src/app/classes/stix/tactic'; import { Technique } from 'src/app/classes/stix/technique'; +import { DataSource } from 'src/app/classes/stix/data-source'; +import { DataComponent } from 'src/app/classes/stix/data-component'; import { EditorService } from 'src/app/services/editor/editor.service'; import { StixViewPage } from '../../../stix-view-page'; import { logger } from "../../../../../util/logger"; @@ -24,13 +26,15 @@ export class CollectionImportReviewComponent extends StixViewPage implements OnI public get collection(): Collection { return this.config.object as Collection; } public collection_import_categories = { - technique: new CollectionDiffCategories(), - tactic: new CollectionDiffCategories(), - software: new CollectionDiffCategories(), - relationship: new CollectionDiffCategories(), - mitigation: new CollectionDiffCategories(), - matrix: new CollectionDiffCategories(), - group: new CollectionDiffCategories() + technique: new CollectionDiffCategories(), + tactic: new CollectionDiffCategories(), + software: new CollectionDiffCategories(), + relationship: new CollectionDiffCategories(), + mitigation: new CollectionDiffCategories(), + matrix: new CollectionDiffCategories(), + group: new CollectionDiffCategories(), + data_source: new CollectionDiffCategories(), + data_component: new CollectionDiffCategories() } constructor(private route: ActivatedRoute, public editor: EditorService) { super() } @@ -88,6 +92,12 @@ export class CollectionImportReviewComponent extends StixViewPage implements OnI case "intrusion-set": //group this.collection_import_categories.group[category].push(object); break; + case "x-mitre-data-source": // data source + this.collection_import_categories.data_source[category].push(object); + break; + case "x-mitre-data-component": // data component + this.collection_import_categories.data_component[category].push(object); + break; } } logger.log(this.collection_import_categories); diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index efd3ae04..f772398d 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -7,6 +7,8 @@ import { MatStepper } from '@angular/material/stepper'; import { ActivatedRoute } from '@angular/router'; import { FileInputComponent } from 'ngx-material-file-input'; import { Collection, CollectionDiffCategories } from 'src/app/classes/stix/collection'; +import { DataComponent } from 'src/app/classes/stix/data-component'; +import { DataSource } from 'src/app/classes/stix/data-source'; import { Group } from 'src/app/classes/stix/group'; import { Matrix } from 'src/app/classes/stix/matrix'; import { Mitigation } from 'src/app/classes/stix/mitigation'; @@ -39,13 +41,15 @@ export class CollectionImportComponent implements OnInit { public collectionBundle: any; public object_import_categories = { - technique: new CollectionDiffCategories(), - tactic: new CollectionDiffCategories(), - software: new CollectionDiffCategories(), - relationship: new CollectionDiffCategories(), - mitigation: new CollectionDiffCategories(), - matrix: new CollectionDiffCategories(), - group: new CollectionDiffCategories() + technique: new CollectionDiffCategories(), + tactic: new CollectionDiffCategories(), + software: new CollectionDiffCategories(), + relationship: new CollectionDiffCategories(), + mitigation: new CollectionDiffCategories(), + matrix: new CollectionDiffCategories(), + group: new CollectionDiffCategories(), + data_source: new CollectionDiffCategories(), + data_component: new CollectionDiffCategories() } constructor(public route: ActivatedRoute, public http: HttpClient, public snackbar: MatSnackBar, public restAPIConnectorService: RestApiConnectorService, private dialog: MatDialog) { } @@ -165,6 +169,12 @@ export class CollectionImportComponent implements OnInit { case "intrusion-set": //group this.object_import_categories.group[category].push(new Group(raw)) break; + case "x-mitre-data-source": // data source + this.object_import_categories.data_source[category].push(new DataSource(raw)) + break; + case "x-mitre-data-component": // data component + this.object_import_categories.data_component[category].push(new DataComponent(raw)) + break; } } // set up selection diff --git a/app/src/app/views/stix/collection/collection-view/collection-view.component.html b/app/src/app/views/stix/collection/collection-view/collection-view.component.html index dcf43221..ae2cc50c 100644 --- a/app/src/app/views/stix/collection/collection-view/collection-view.component.html +++ b/app/src/app/views/stix/collection/collection-view/collection-view.component.html @@ -83,10 +83,10 @@

Objects in Collection

- + -

{{attackType}}

+

{{format(attackType)}}

diff --git a/app/src/app/views/stix/collection/collection-view/collection-view.component.ts b/app/src/app/views/stix/collection/collection-view/collection-view.component.ts index b9ff8182..e8c78db4 100644 --- a/app/src/app/views/stix/collection/collection-view/collection-view.component.ts +++ b/app/src/app/views/stix/collection/collection-view/collection-view.component.ts @@ -12,6 +12,8 @@ import { Software } from 'src/app/classes/stix/software'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { Tactic } from 'src/app/classes/stix/tactic'; import { Technique } from 'src/app/classes/stix/technique'; +import { DataSource } from 'src/app/classes/stix/data-source'; +import { DataComponent } from 'src/app/classes/stix/data-component'; import { VersionNumber } from 'src/app/classes/version-number'; import { StixListComponent } from 'src/app/components/stix/stix-list/stix-list.component'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; @@ -50,23 +52,27 @@ export class CollectionViewComponent extends StixViewPage implements OnInit { public stagedData: VersionReference[] = []; public potentialChanges = { - technique: new CollectionDiffCategories(), - tactic: new CollectionDiffCategories(), - software: new CollectionDiffCategories(), - relationship: new CollectionDiffCategories(), - mitigation: new CollectionDiffCategories(), - matrix: new CollectionDiffCategories(), - group: new CollectionDiffCategories() + technique: new CollectionDiffCategories(), + tactic: new CollectionDiffCategories(), + software: new CollectionDiffCategories(), + relationship: new CollectionDiffCategories(), + mitigation: new CollectionDiffCategories(), + matrix: new CollectionDiffCategories(), + group: new CollectionDiffCategories(), + data_source: new CollectionDiffCategories(), + data_component: new CollectionDiffCategories() } public collectionChanges = { - technique: new CollectionDiffCategories(), - tactic: new CollectionDiffCategories(), - software: new CollectionDiffCategories(), - relationship: new CollectionDiffCategories(), - mitigation: new CollectionDiffCategories(), - matrix: new CollectionDiffCategories(), - group: new CollectionDiffCategories() + technique: new CollectionDiffCategories(), + tactic: new CollectionDiffCategories(), + software: new CollectionDiffCategories(), + relationship: new CollectionDiffCategories(), + mitigation: new CollectionDiffCategories(), + matrix: new CollectionDiffCategories(), + group: new CollectionDiffCategories(), + data_source: new CollectionDiffCategories(), + data_component: new CollectionDiffCategories() } public collection_import_categories = []; @@ -498,6 +504,10 @@ export class CollectionViewComponent extends StixViewPage implements OnInit { }) } + public format(attackType: string): string { + return attackType.replace(/_/g, ' '); + } + ngOnInit() { //set up subscription to route query params to reinitialize stix lists this.route.queryParams.subscribe(params => { From ede3039ab3fcc00fe315002fbe0fc60d853330ba Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 23 Sep 2021 15:19:32 -0400 Subject: [PATCH 31/89] data components do not have attackIDs --- app/src/app/classes/stix/relationship.ts | 2 +- .../app/components/stix/stix-list/stix-list.component.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 177db617..26648a17 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -172,7 +172,7 @@ export class Relationship extends StixObject { // this.source_name = raw.source_object.stix.name; let src_sdo = raw.source_object.stix; - if ("external_references" in src_sdo) { + if ("external_references" in src_sdo && src_sdo["type"] !== "x-mitre-data-component") { if (src_sdo.external_references.length > 0 && src_sdo.external_references[0].hasOwnProperty("external_id")) { if (typeof(src_sdo.external_references[0].external_id) === "string") this.source_ID = src_sdo.external_references[0].external_id; else logger.error("TypeError: attackID field is not a string:", src_sdo.external_references[0].external_id, "(", typeof(src_sdo.external_references[0].external_id), ")"); diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index 74bd45b9..c8650e67 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -267,8 +267,10 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { break; case "relationship": this.addColumn("", "state", "icon"); - this.addColumn("source", "source_ID", "plain"); - this.addColumn("", "source_name", "plain", this.config.targetRef? sticky_allowed: false, ["relationship-name"]);// ["name", "relationship-left"]); + if (this.config.relationshipType && this.config.relationshipType !== "detects") { + this.addColumn("source", "source_ID", "plain"); + this.addColumn("", "source_name", "plain", this.config.targetRef? sticky_allowed: false, ["relationship-name"]);// ["name", "relationship-left"]); + } else this.addColumn("source", "source_name", "plain", this.config.targetRef? sticky_allowed: false, ["relationship-name"]); this.addColumn("type", "relationship_type", "plain", false, ["text-deemphasis", "relationship-joiner"]); this.addColumn("target", "target_ID", "plain"); this.addColumn("", "target_name", "plain", this.config.sourceRef? sticky_allowed: false, ["relationship-name"]);// ["name", "relationship-right"]); From a17b310725a52030c6b3e72a7b130493042fdc9a Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 23 Sep 2021 15:34:28 -0400 Subject: [PATCH 32/89] retrieve 'parent' data source --- .../data-component-view.component.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts index 36ef0995..edd3c104 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts @@ -1,5 +1,6 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { DataComponent } from 'src/app/classes/stix/data-component'; +import { DataSource } from 'src/app/classes/stix/data-source'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { StixViewPage } from '../../stix-view-page'; @@ -11,13 +12,22 @@ import { StixViewPage } from '../../stix-view-page'; }) export class DataComponentViewComponent extends StixViewPage implements OnInit { @Output() onClickRelationship = new EventEmitter(); - + public get data_component() { return this.config.object as DataComponent; } + public data_source: DataSource = null; constructor(private restAPIConnectorService: RestApiConnectorService) { super(); } ngOnInit(): void { - // TODO retrieve parent data source + // retrieve parent data source + let objects$ = this.restAPIConnectorService.getDataSource(this.data_component.data_source_ref); + let subscription = objects$.subscribe({ + next: (result) => { + let objects = result as DataSource[]; + this.data_source = objects[0]; + }, + complete: () => { subscription.unsubscribe() } + }); } public viewRelationship(object: StixObject): void { From 87ea3922ea3dc762393964c099e371b09c024cda Mon Sep 17 00:00:00 2001 From: idavila Date: Thu, 23 Sep 2021 16:04:19 -0400 Subject: [PATCH 33/89] Refactoring popover to enable view/edit buttons. --- .../group-list/group-list.component.html | 20 +++++++----------- .../matrix-list/matrix-list.component.html | 19 +++++++---------- .../mitigation-list.component.html | 21 +++++++------------ .../software-list.component.html | 21 +++++++------------ .../tactic-list/tactic-list.component.html | 19 +++++++---------- .../technique-list.component.html | 19 +++++++---------- app/src/style/layouts/list-page.scss | 13 +++++++++++- 7 files changed, 56 insertions(+), 76 deletions(-) diff --git a/app/src/app/views/stix/group/group-list/group-list.component.html b/app/src/app/views/stix/group/group-list/group-list.component.html index c53056fd..bc6926be 100644 --- a/app/src/app/views/stix/group/group-list/group-list.component.html +++ b/app/src/app/views/stix/group/group-list/group-list.component.html @@ -1,21 +1,15 @@

- groups help_outline + groups + + help_outline +

- -
-

- Groups are sets of related intrusion activity that are tracked by a common name in the security community. Overlaps between names based on publicly reported associations are tracked using "Associated Groups" (also known as "Aliases"). -

-
-
-
+
{ + if (result) { + if (prompt.componentInstance.dirty) this.getDataComponents(); //re-fetch values since an edit occurred + } + }, + complete: () => { subscription.unsubscribe(); } + }); + } } From ed332f9fb979ed7e765540522b36e84fabdde5d2 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 08:55:47 -0400 Subject: [PATCH 38/89] required/disabled config features for list property --- .../stix/list-property/list-edit/list-edit.component.html | 8 ++++---- .../stix/list-property/list-edit/list-edit.component.ts | 2 +- .../stix/list-property/list-property.component.ts | 4 ++++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html index f5efa816..61115490 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html @@ -2,7 +2,7 @@ {{config.label? config.label : config.field}} - + {{value}} cancel @@ -10,19 +10,19 @@ [matChipInputFor]="anyChipList" [matChipInputSeparatorKeyCodes]="separatorKeysCodes" [matChipInputAddOnBlur]="true" - (matChipInputTokenEnd)="add($event)"> + (matChipInputTokenEnd)="add($event)"/> + [matTooltip]="selectControl.disabled && !config.disabled ? 'a valid domain must be selected first' : null"> Loading {{config.label? config.label : config.field}}... {{config.label? config.label : config.field}} - + diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index 9a6a4d55..0879b5c1 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -55,7 +55,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked { constructor(public dialog: MatDialog, private restAPIConnectorService: RestApiConnectorService, private ref: ChangeDetectorRef) { } ngOnInit(): void { - this.selectControl = new FormControl(this.config.object[this.config.field]); + this.selectControl = new FormControl({value: this.config.object[this.config.field], disabled: this.config.disabled ? this.config.disabled : false}); if (this.config.field == 'platforms' || this.config.field == 'tactic_type' || this.config.field == 'permissions_required' diff --git a/app/src/app/components/stix/list-property/list-property.component.ts b/app/src/app/components/stix/list-property/list-property.component.ts index 5b5c8a05..098051d0 100644 --- a/app/src/app/components/stix/list-property/list-property.component.ts +++ b/app/src/app/components/stix/list-property/list-property.component.ts @@ -29,6 +29,10 @@ export interface ListPropertyConfig { object: StixObject | [StixObject, StixObject]; /* Edit mode. Default: 'any' */ editType?: "select" | "stixList" | "any"; + /* If true, the field will be disabled. Default false if omitted. */ + disabled?: boolean; + /* If true, the field will be required. Default false if omitted. */ + required?: boolean; /* the field of the object(s) to visualize as a list */ field: string; /* if specified, label with this string instead of field */ From 39241ad36953d06a5a5fde1ce3b3bd3a4a0eba4f Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 10:02:52 -0400 Subject: [PATCH 39/89] validate data source fields --- app/src/app/classes/stix/data-component.ts | 2 +- app/src/app/classes/stix/data-source.ts | 30 ++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts index d8c0c50c..27ebf070 100644 --- a/app/src/app/classes/stix/data-component.ts +++ b/app/src/app/classes/stix/data-component.ts @@ -7,7 +7,7 @@ import { logger } from "../../util/logger"; export class DataComponent extends StixObject { public name: string = ""; public description: string = ""; - public domains: string[] = []; + public domains: string[] = ['enterprise-attack']; // default to enterprise public data_source_ref: string; // stix ID of the data source protected get attackIDValidator() { return null; } // data components have no ATT&CK ID diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index b0a9f662..269b93ea 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -2,6 +2,7 @@ import { StixObject } from "./stix-object"; import { logger } from "../../util/logger"; import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; import { Observable } from "rxjs"; +import { map } from "rxjs/operators"; import { ValidationData } from "../serializable"; import { DataComponent } from "./data-component"; @@ -11,7 +12,7 @@ export class DataSource extends StixObject { public platforms: string[] = []; public collection_layers: string[] = []; public contributors: string[] = []; - public domains: string[] = []; + public domains: string[] = ['enterprise-attack']; // default to enterprise public data_components: DataComponent[] = []; protected get attackIDValidator() { @@ -97,7 +98,32 @@ export class DataSource extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate(restAPIService: RestApiConnectorService): Observable { - return this.base_validate(restAPIService); + return this.base_validate(restAPIService).pipe( + map(result => { + // check contributors + if (!this.contributors.length) result.errors.push({ + "field": "contributors", + "result": "error", + "message": "object has no contributors" + }); + // check platforms + if (!this.platforms.length) result.errors.push({ + "field": "platforms", + "result": "error", + "message": "object has no platforms" + }); + + // check collection layers + if (!this.collection_layers.length) result.errors.push({ + "field": "collection_layers", + "result": "error", + "message": "object has no collection layers" + }); + + return result; + }), + + ); } /** From f35fde7a50dc00fcf91d194ecf101442b40b8ccb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 12:57:46 -0400 Subject: [PATCH 40/89] only show relationship source ID if it exists --- .../relationship-view/relationship-view.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html index cbb91e99..6477d321 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html @@ -11,7 +11,7 @@

{{relationship.source
-
+
{{relationship.source_ID}} From b2bca16cfb1ef8cd42e9075d10da3950b0784e38 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:05:35 -0400 Subject: [PATCH 41/89] fetch parent data source with data component --- app/src/app/classes/stix/data-component.ts | 4 ++++ .../rest-api/rest-api-connector.service.ts | 13 +++++++++++++ .../data-component-view.component.html | 9 ++++++--- .../data-component-view.component.ts | 18 +++++++++--------- 4 files changed, 32 insertions(+), 12 deletions(-) diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts index 27ebf070..602039c8 100644 --- a/app/src/app/classes/stix/data-component.ts +++ b/app/src/app/classes/stix/data-component.ts @@ -3,6 +3,7 @@ import { RestApiConnectorService } from "src/app/services/connectors/rest-api/re import { ValidationData } from "../serializable"; import { StixObject } from "./stix-object"; import { logger } from "../../util/logger"; +import { DataSource } from "./data-source"; export class DataComponent extends StixObject { public name: string = ""; @@ -10,6 +11,9 @@ export class DataComponent extends StixObject { public domains: string[] = ['enterprise-attack']; // default to enterprise public data_source_ref: string; // stix ID of the data source + // NOTE: the following field will only be populated when this object is fetched using getTechnique() + public data_source: DataSource = null; + protected get attackIDValidator() { return null; } // data components have no ATT&CK ID constructor(sdo?: any) { diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 9eb1f731..1c968d40 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -437,6 +437,19 @@ export class RestApiConnectorService extends ApiConnector { ); } }), + switchMap(result => { + let x = result as any[]; + if (x[0].attackType != "data-component") return of(result); + let d = x[0] as DataComponent; + return this.getDataSource(d.data_source_ref).pipe( // fetch data source from REST API + map(data_source => { + let ds = data_source as DataSource[]; + d.data_source = ds[0]; + return [d]; + }), + tap(result => logger.log("fetched data source of", result)) + ); + }), catchError(this.handleError_continue([])), // on error, trigger the error notification and continue operation without crashing (returns empty item) share() // multicast so that multiple subscribers don't trigger the call twice. THIS MUST BE THE LAST LINE OF THE PIPE ) diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html index 7efac968..6eca54b2 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html @@ -1,10 +1,10 @@ -
+
@@ -91,4 +91,7 @@

References

-
\ No newline at end of file +
+ + + \ No newline at end of file diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts index edd3c104..5faa78a5 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { DataComponent } from 'src/app/classes/stix/data-component'; -import { DataSource } from 'src/app/classes/stix/data-source'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { StixViewPage } from '../../stix-view-page'; @@ -12,21 +11,22 @@ import { StixViewPage } from '../../stix-view-page'; }) export class DataComponentViewComponent extends StixViewPage implements OnInit { @Output() onClickRelationship = new EventEmitter(); - - public get data_component() { return this.config.object as DataComponent; } - public data_source: DataSource = null; + public loading = false; + public data_component: DataComponent; constructor(private restAPIConnectorService: RestApiConnectorService) { super(); } ngOnInit(): void { - // retrieve parent data source - let objects$ = this.restAPIConnectorService.getDataSource(this.data_component.data_source_ref); + this.loading = true; + let object = Array.isArray(this.config.object)? this.config.object[0] : this.config.object; + let objects$ = this.restAPIConnectorService.getDataComponent(object.stixID); let subscription = objects$.subscribe({ next: (result) => { - let objects = result as DataSource[]; - this.data_source = objects[0]; + let objects = result as DataComponent[]; + this.data_component = objects[0]; + this.loading = false; }, - complete: () => { subscription.unsubscribe() } + complete: () => { subscription.unsubscribe(); } }); } From c873025201a6ab4b8d5489c5e4107bf4202a64e6 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:17:30 -0400 Subject: [PATCH 42/89] replace dialog content when creating a relationship between data components and techniques --- .../add-relationship-button.component.ts | 40 ++++++++++++------- .../data-component-view.component.html | 3 +- .../stix/stix-dialog/stix-dialog.component.ts | 3 +- app/src/app/views/stix/stix-view-page.ts | 3 ++ 4 files changed, 32 insertions(+), 17 deletions(-) diff --git a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts index 0ddf34c7..8ccf3d5f 100644 --- a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts +++ b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts @@ -1,10 +1,11 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; import { EMPTY, empty, zip } from 'rxjs'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; import { StixDialogComponent } from 'src/app/views/stix/stix-dialog/stix-dialog.component'; +import { StixViewConfig } from 'src/app/views/stix/stix-view-page'; @Component({ selector: 'app-add-relationship-button', @@ -33,24 +34,32 @@ export class AddRelationshipButtonComponent implements OnInit { var zip_subscription = initializer.subscribe({ next: () => { this.loading = false; - let prompt = this.dialog.open(StixDialogComponent, { - data: { - object: relationship, - editable: true, - mode: "edit", - sidebarControl: "events" - }, - maxHeight: "75vh" - }) - let subscription = prompt.afterClosed().subscribe({ - next: result => { if (prompt.componentInstance.dirty) this.created.emit(); }, //re-fetch values since an edit occurred - complete: () => { subscription.unsubscribe(); } - }); + let config: StixViewConfig = { + object: relationship, + editable: true, + mode: "edit", + sidebarControl: "events" + } + + if (this.config.dialog && this.config.dialog.componentInstance) { + // replace current dialog content + this.config.dialog.componentInstance._config = config; + this.config.dialog.componentInstance.startEditing(); + } else { + // open a new dialog + let prompt = this.dialog.open(StixDialogComponent, { + data: config, + maxHeight: "75vh" + }) + let subscription = prompt.afterClosed().subscribe({ + next: result => { if (prompt.componentInstance.dirty) this.created.emit(); }, //re-fetch values since an edit occurred + complete: () => { subscription.unsubscribe(); } + }); + } }, complete: () => { if (zip_subscription) zip_subscription.unsubscribe(); } //for some reason zip_subscription doesn't exist if using set_source_object or set_target_object }) } - } export interface AddRelationshipButtonConfig { @@ -61,4 +70,5 @@ export interface AddRelationshipButtonConfig { source_object?: StixObject; //initial relationship source object. Takes precedence over source_ref if both are specified, and is much faster to execute target_ref?: string; //initial relationship target ref target_object?: StixObject; //initial relationship target object. Takes precedence over target_ref if both are specified, and is much faster to execute + dialog?: MatDialogRef //dialog ref; if provided the 'create relationship interface' will replace the current dialog content } \ No newline at end of file diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html index 6eca54b2..a99c877a 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html @@ -53,7 +53,8 @@

Techniques Detected

diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts index 5c0af87e..e439ed60 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts @@ -25,7 +25,8 @@ export class StixDialogComponent implements OnInit { object: object, showRelationships: object.attackType == "data-component" ? true : false, editable: this._config.editable, - sidebarControl: this._config.sidebarControl == "disable"? "disable" : "events" + sidebarControl: this._config.sidebarControl == "disable"? "disable" : "events", + dialog: this.dialogRef } } diff --git a/app/src/app/views/stix/stix-view-page.ts b/app/src/app/views/stix/stix-view-page.ts index 7119b624..9631d4dc 100644 --- a/app/src/app/views/stix/stix-view-page.ts +++ b/app/src/app/views/stix/stix-view-page.ts @@ -1,5 +1,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { MatDialogRef } from '@angular/material/dialog'; import { StixObject } from 'src/app/classes/stix/stix-object'; +import { StixDialogComponent } from './stix-dialog/stix-dialog.component'; @Component({template: ''}) export abstract class StixViewPage { @@ -38,4 +40,5 @@ export interface StixViewConfig { * if "service", use sidebar.service to control the sidebar */ sidebarControl?: "disable" | "events" | "service" + dialog?: MatDialogRef // reference to the current dialog } \ No newline at end of file From 00cdbe6c37f34f9fd8a82fe46c998b06bfe7e835 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 28 Sep 2021 13:29:08 -0400 Subject: [PATCH 43/89] minor changes to code documentation --- app/src/app/classes/stix/data-component.ts | 2 +- .../services/connectors/rest-api/rest-api-connector.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts index 602039c8..b3225334 100644 --- a/app/src/app/classes/stix/data-component.ts +++ b/app/src/app/classes/stix/data-component.ts @@ -11,7 +11,7 @@ export class DataComponent extends StixObject { public domains: string[] = ['enterprise-attack']; // default to enterprise public data_source_ref: string; // stix ID of the data source - // NOTE: the following field will only be populated when this object is fetched using getTechnique() + // NOTE: the following field will only be populated when this object is fetched using getDataComponent() public data_source: DataSource = null; protected get attackIDValidator() { return null; } // data components have no ATT&CK ID diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 1c968d40..911afbad 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -437,7 +437,7 @@ export class RestApiConnectorService extends ApiConnector { ); } }), - switchMap(result => { + switchMap(result => { // fetch parent data source of data component let x = result as any[]; if (x[0].attackType != "data-component") return of(result); let d = x[0] as DataComponent; From 5ee57d9af08688e032368fbebad61edc949a3797 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 09:06:29 -0400 Subject: [PATCH 44/89] prepend data component & sub-technique names with parent name in relationship view --- app/src/app/classes/stix/relationship.ts | 84 +++++++++++++++---- .../add-relationship-button.component.ts | 6 +- .../relationship-view.component.ts | 23 ++++- .../stix/stix-page/stix-page.component.ts | 1 + 4 files changed, 92 insertions(+), 22 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 26648a17..9ba81bfb 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -1,22 +1,27 @@ -import { Observable, of } from "rxjs"; +import { Observable, of, EMPTY } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; import { ValidationData } from "../serializable"; -import {StixObject} from "./stix-object"; +import { StixObject } from "./stix-object"; import { logger } from "../../util/logger"; export class Relationship extends StixObject { public source_ref: string = ""; - public get source_name(): string { return this.source_object? this.source_object.stix.name : "[unknown object]"; } + public get source_name(): string { + return `${this.source_parent ? this.source_parent.stix.name + ': ' : ''}${this.source_object ? this.source_object.stix.name : '[unknown object]'}`; + } public source_ID: string = ""; public source_object: any; - + public source_parent: any; public target_ref: string = ""; - public get target_name(): string { return this.target_object? this.target_object.stix.name : "[unknown object]"; } + public get target_name(): string { + return `${this.target_parent ? this.target_parent.stix.name + ': ' : ''}${this.target_object ? this.target_object.stix.name : '[unknown object]'}`; + } public target_ID: string = ""; public target_object: any; + public target_parent: any; public updating_refs: boolean = false; //becomes true while source and target refs are being asynchronously updated. @@ -76,6 +81,10 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.source_object = x.find(result => result.stix.id == new_source_ref); this.deserialize(serialized); + var src_sub = this.get_parent_object(serialized.source_object, restAPIService).subscribe({ + next: (res) => { this.source_parent = res; }, + complete: () => { if (src_sub) src_sub.unsubscribe(); } + }); this.updating_refs = false; return this; }) @@ -85,14 +94,19 @@ export class Relationship extends StixObject { /** * Set the source object * @param {StixObject} new_source_object the object to set + * @param {RestApiConnectorService} restAPIService: the REST API connector through which the parent of the source can be fetched * @returns {Observable} of this object after the data has been updated */ - public set_source_object(new_source_object: StixObject): Observable { + public set_source_object(new_source_object: StixObject, restAPIService: RestApiConnectorService): Observable { this.updating_refs = true; this.source_ref = new_source_object.stixID; let serialized = this.serialize(); serialized.source_object = new_source_object.serialize(); this.deserialize(serialized); + var src_sub = this.get_parent_object(serialized.source_object, restAPIService).subscribe({ + next: (res) => { this.source_parent = res; }, + complete: () => { if (src_sub) src_sub.unsubscribe(); } + }); this.updating_refs = false; return of(this); } @@ -112,26 +126,66 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.target_object = x.find(result => result.stix.id == new_target_ref); this.deserialize(serialized); + let tgt_sub = this.get_parent_object(serialized.target_object, restAPIService).subscribe({ + next: (res) => { this.target_parent = res; }, + complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } + }); this.updating_refs = false; return this; }) ) } - /** + /** * Set the target object * @param {StixObject} new_target_object the object to set + * @param {RestApiConnectorService} restAPIService: the REST API connector through which the parent of the target can be fetched * @returns {Observable} of this object after the data has been updated */ - public set_target_object(new_target_object: StixObject): Observable { - this.updating_refs = true; - this.target_ref = new_target_object.stixID; - let serialized = this.serialize(); - serialized.target_object = new_target_object.serialize(); - this.deserialize(serialized); - this.updating_refs = false; - return of(this); + public set_target_object(new_target_object: StixObject, restAPIService: RestApiConnectorService): Observable { + this.updating_refs = true; + this.target_ref = new_target_object.stixID; + let serialized = this.serialize(); + serialized.target_object = new_target_object.serialize(); + this.deserialize(serialized); + var tgt_sub = this.get_parent_object(serialized.target_object, restAPIService).subscribe({ + next: (res) => { this.target_parent = res; }, + complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } + }); + this.updating_refs = false; + return of(this); + } + + /** + * Retrieve parent object from the REST API, if applicable. + * Fetches the parent technique of a sub-technique and the parent data source of + * a data component. + * @param {any} object the raw source or target object + * @param {RestApiConnectorService} restAPIService the REST API connector through which the parent can be fetched + * @returns {Observable} of the parent object + */ + public get_parent_object(object: any, restAPIService: RestApiConnectorService): Observable { + this.updating_refs = true; + if (object.stix.type == 'attack-pattern' && object.stix.x_mitre_is_subtechnique) { + return restAPIService.getRelatedTo({sourceRef: object.stix.id, relationshipType: "subtechnique-of"}).pipe( // fetch parent from REST API + map(relationshp => { + let p = relationshp as any; + if (!p || p.data.length == 0) return null; // no parent technique + this.updating_refs = false; + return p.data[0].target_object; + }) + ); + } else if (object.stix.type == 'x-mitre-data-component') { + return restAPIService.getDataSource(object.stix.x_mitre_data_source_ref).pipe( // fetch data source from REST API + map(data_sources => { + this.updating_refs = false; + return data_sources[0].serialize(); + }) + ); } + this.updating_refs = false + return EMPTY; + } /** * Transform the current object into a raw object for sending to the back-end, stripping any unnecessary fields diff --git a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts index 8ccf3d5f..96496a94 100644 --- a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts +++ b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts @@ -1,6 +1,6 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { MatDialog, MatDialogRef } from '@angular/material/dialog'; -import { EMPTY, empty, zip } from 'rxjs'; +import { EMPTY } from 'rxjs'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; @@ -25,9 +25,9 @@ export class AddRelationshipButtonComponent implements OnInit { let relationship = new Relationship(); relationship.relationship_type = this.config.relationship_type; let initializer = null; - if (this.config.source_object) initializer = relationship.set_source_object(this.config.source_object); + if (this.config.source_object) initializer = relationship.set_source_object(this.config.source_object, this.restApiService); else if (this.config.source_ref) initializer = relationship.set_source_ref(this.config.source_ref, this.restApiService); - else if (this.config.target_object) initializer = relationship.set_target_object(this.config.target_object); + else if (this.config.target_object) initializer = relationship.set_target_object(this.config.target_object, this.restApiService); else if (this.config.target_ref) initializer = relationship.set_target_ref(this.config.target_ref, this.restApiService); else initializer = EMPTY; this.loading = true; diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts index 237b76ca..a3e950e1 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts @@ -34,20 +34,35 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { ngOnInit(): void { // initialize source/target types if there is a source/target object, or if there is only one possible value - if (this.relationship.source_object) this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] + if (this.relationship.source_object) { + this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] + // retrieve parent of source object + var src_sub = this.relationship.get_parent_object(this.relationship.source_object, this.restApiService).subscribe({ + next: (res) => { this.relationship.source_parent = res; }, + complete: () => { if (src_sub) src_sub.unsubscribe(); } + }); + } else if (this.relationship.valid_source_types.length == 1) this.source_type = this.relationship.valid_source_types[0]; - if (this.relationship.target_object) this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] + + if (this.relationship.target_object) { + this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] + // retrieve parent of target object + var tgt_sub = this.relationship.get_parent_object(this.relationship.target_object, this.restApiService).subscribe({ + next: (res) => { this.relationship.target_parent = res; }, + complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } + }); + } else if (this.relationship.valid_target_types.length == 1) this.target_type = this.relationship.valid_target_types[0]; } public setSourceObject(object: StixObject) { - var subscription = this.relationship.set_source_object(object).subscribe({ + var subscription = this.relationship.set_source_object(object, this.restApiService).subscribe({ complete: () => { if (subscription) subscription.unsubscribe() } //subscription doesn't exist for some reason in this case }) } public setTargetObject(object: StixObject) { - var subscription = this.relationship.set_target_object(object).subscribe({ + var subscription = this.relationship.set_target_object(object, this.restApiService).subscribe({ complete: () => { if (subscription) subscription.unsubscribe() } //subscription doesn't exist for some reason in this case }) } diff --git a/app/src/app/views/stix/stix-page/stix-page.component.ts b/app/src/app/views/stix/stix-page/stix-page.component.ts index bbb4bb13..39bad473 100644 --- a/app/src/app/views/stix/stix-page/stix-page.component.ts +++ b/app/src/app/views/stix/stix-page/stix-page.component.ts @@ -127,6 +127,7 @@ export class StixPageComponent implements OnInit, OnDestroy { else if (this.objectType == "technique") objects$ = this.restAPIConnectorService.getTechnique(objectStixID, null, "latest", true); else if (this.objectType == "collection") objects$ = this.restAPIConnectorService.getCollection(objectStixID, objectModified, "latest", false, true); else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID); + else if (this.objectType == "data-component") objects$ = this.restAPIConnectorService.getDataComponent(objectStixID); let subscription = objects$.subscribe({ next: result => { this.updateBreadcrumbs(result, this.objectType ); From 9cce3820f9efee358869ceff8a396087c8007885 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 09:13:15 -0400 Subject: [PATCH 45/89] docs for rest api integration --- app/src/app/classes/stix/data-source.ts | 2 +- .../stix/list-property/list-edit/list-edit.component.ts | 3 ++- app/src/app/views/stix/stix-page/stix-page.component.ts | 2 ++ .../technique/technique-view/technique-view.component.html | 3 ++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index 269b93ea..c0760825 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -85,7 +85,7 @@ export class DataSource extends StixObject { else logger.error("TypeError: domains field is not a string array."); } else this.domains = ['enterprise-attack']; // default to enterprise - if ("data_components" in sdo) { + if ("data_components" in sdo) { // TODO parse objects returned from REST API if (typeof(sdo.data_components) == "object") this.data_components = sdo.data_components.map(dc => new DataComponent(dc)); else logger.error("TypeError: data components field is not an object:", sdo.data_components, "(", typeof(sdo.data_components), ")"); } else this.data_components = []; diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index 0879b5c1..0079e5fc 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -62,7 +62,8 @@ export class ListEditComponent implements OnInit, AfterContentChecked { || this.config.field == 'effective_permissions' || this.config.field == 'impact_type' || this.config.field == 'domains' - || this.config.field == 'collection_layers') { + || this.config.field == 'collection_layers' + || this.config.field == 'data_sources') { // TODO retrieve ics data sources allowed values from REST API if (!this.dataLoaded) { let data$ = this.restAPIConnectorService.getAllAllowedValues(); this.sub = data$.subscribe({ diff --git a/app/src/app/views/stix/stix-page/stix-page.component.ts b/app/src/app/views/stix/stix-page/stix-page.component.ts index 39bad473..5f8a8f60 100644 --- a/app/src/app/views/stix/stix-page/stix-page.component.ts +++ b/app/src/app/views/stix/stix-page/stix-page.component.ts @@ -126,6 +126,8 @@ export class StixPageComponent implements OnInit, OnDestroy { else if (this.objectType == "tactic") objects$ = this.restAPIConnectorService.getTactic(objectStixID); else if (this.objectType == "technique") objects$ = this.restAPIConnectorService.getTechnique(objectStixID, null, "latest", true); else if (this.objectType == "collection") objects$ = this.restAPIConnectorService.getCollection(objectStixID, objectModified, "latest", false, true); + // TODO retrieve data components along with data source object from from REST API + // else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID, null, "latest", false, false, true); else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID); else if (this.objectType == "data-component") objects$ = this.restAPIConnectorService.getDataComponent(objectStixID); let subscription = objects$.subscribe({ diff --git a/app/src/app/views/stix/technique/technique-view/technique-view.component.html b/app/src/app/views/stix/technique/technique-view/technique-view.component.html index fa13e6fd..e76f1d62 100644 --- a/app/src/app/views/stix/technique/technique-view/technique-view.component.html +++ b/app/src/app/views/stix/technique/technique-view/technique-view.component.html @@ -102,13 +102,14 @@
+
From 0af2d32e02379d100bd0fc59724553d164cfb2ad Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 10:16:19 -0400 Subject: [PATCH 46/89] revoke/deprecate data sources --- .../object-status/object-status.component.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/app/src/app/components/object-status/object-status.component.ts b/app/src/app/components/object-status/object-status.component.ts index a3d7e8f1..642bce83 100644 --- a/app/src/app/components/object-status/object-status.component.ts +++ b/app/src/app/components/object-status/object-status.component.ts @@ -5,6 +5,7 @@ import { MatDialog } from '@angular/material/dialog'; import { PopoverContentComponent } from 'ngx-smart-popover'; import { forkJoin } from 'rxjs'; import { Collection } from 'src/app/classes/stix/collection'; +import { DataComponent } from 'src/app/classes/stix/data-component'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; @@ -57,6 +58,8 @@ export class ObjectStatusComponent implements OnInit { else if (this.editorService.type == "tactic") data$ = this.restAPIService.getAllTactics(options); else if (this.editorService.type == "technique") data$ = this.restAPIService.getAllTechniques(options); else if (this.editorService.type == "collection") data$ = this.restAPIService.getAllCollections(options); + else if (this.editorService.type == "data-source") data$ = this.restAPIService.getAllDataSources(options); + else if (this.editorService.type == "data-component") data$ = this.restAPIService.getAllDataComponents(options); let objSubscription = data$.subscribe({ next: (data) => { this.objects = data.data; @@ -212,6 +215,35 @@ export class ObjectStatusComponent implements OnInit { } } + if (this.editorService.type == 'data-source') { + // deprecate related data components + let dataComponents$ = this.restAPIService.getAllDataComponents(); + let dcSubscription = dataComponents$.subscribe({ + next: (dataComponents) => { + let allDataComponents = dataComponents.data as DataComponent[]; + let relDataComponents = allDataComponents.filter(dc => dc.data_source_ref == this.object.stixID); + for (let dataComponent of relDataComponents) { + dataComponent.deprecated = true; + saves.push(dataComponent.save(this.restAPIService)); + + // deprecate relationships with the data component + let dataComponentRels$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: dataComponent.stixID}); + let relSubscription = dataComponentRels$.subscribe({ + next: (rels) => { + let relationships = rels.data as Relationship[]; + for (let rel of relationships) { + rel.deprecated = true; + saves.push(rel.save(this.restAPIService)); + } + }, + complete: () => { relSubscription.unsubscribe(); } + }); + } + }, + complete: () => { dcSubscription.unsubscribe(); } + }); + } + if (revoked_by_id) { // create a new 'revoked-by' relationship let revokedRelationship = new Relationship(); From ea3d43482c84bc6cc32316d36e25ac7ca5698698 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 10:26:35 -0400 Subject: [PATCH 47/89] data source history --- .../history-timeline/history-timeline.component.ts | 2 ++ .../app/views/stix/stix-dialog/stix-dialog.component.html | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/app/src/app/components/resources-drawer/history-timeline/history-timeline.component.ts b/app/src/app/components/resources-drawer/history-timeline/history-timeline.component.ts index 42817263..65a41ab9 100644 --- a/app/src/app/components/resources-drawer/history-timeline/history-timeline.component.ts +++ b/app/src/app/components/resources-drawer/history-timeline/history-timeline.component.ts @@ -152,6 +152,8 @@ export class HistoryTimelineComponent implements OnInit, OnDestroy { else if (objectType == "tactic") objects$ = this.restAPIConnectorService.getTactic(objectStixID, null, "all"); else if (objectType == "technique") objects$ = this.restAPIConnectorService.getTechnique(objectStixID, null, "all"); else if (objectType == "collection") objects$ = this.restAPIConnectorService.getCollection(objectStixID, null, "all"); + else if (objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID, null, "all"); + else if (objectType == "data-component") objects$ = this.restAPIConnectorService.getDataComponent(objectStixID, null, "all"); // set up subscribers to get relationships let relationships$ = this.restAPIConnectorService.getRelatedTo({sourceOrTargetRef: objectStixID, versions: "all"}); // join subscribers diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html index c83935b5..f2c07c4a 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html @@ -49,6 +49,11 @@ (onOpenHistory)="openHistory()" (onOpenNotes)="openNotes()"> + + Date: Fri, 1 Oct 2021 10:47:14 -0400 Subject: [PATCH 48/89] bug fix for creating data components --- .../list-edit/list-edit.component.html | 2 +- .../data-component-view.component.html | 3 ++- .../data-component-view.component.ts | 26 ++++++++++--------- .../data-source-view.component.ts | 1 + 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html index 61115490..a9c3dae1 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html @@ -26,7 +26,7 @@ - {{value}} cancel + {{value}} cancel diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html index a99c877a..9b1ba79b 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.html @@ -17,7 +17,8 @@ mode: editing? 'edit': 'view', object: data_component, field: 'domains', - editType: 'select' + editType: 'select', + disabled: true }">
diff --git a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts index 5faa78a5..92163147 100644 --- a/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts +++ b/app/src/app/views/stix/data-component/data-component-view/data-component-view.component.ts @@ -12,22 +12,24 @@ import { StixViewPage } from '../../stix-view-page'; export class DataComponentViewComponent extends StixViewPage implements OnInit { @Output() onClickRelationship = new EventEmitter(); public loading = false; - public data_component: DataComponent; + public get data_component(): DataComponent { return this.config.object as DataComponent; } constructor(private restAPIConnectorService: RestApiConnectorService) { super(); } ngOnInit(): void { - this.loading = true; - let object = Array.isArray(this.config.object)? this.config.object[0] : this.config.object; - let objects$ = this.restAPIConnectorService.getDataComponent(object.stixID); - let subscription = objects$.subscribe({ - next: (result) => { - let objects = result as DataComponent[]; - this.data_component = objects[0]; - this.loading = false; - }, - complete: () => { subscription.unsubscribe(); } - }); + if (!this.data_component.data_source) { + // fetch parent data source + this.loading = true; + let objects$ = this.restAPIConnectorService.getDataComponent(this.data_component.stixID); + let subscription = objects$.subscribe({ + next: (result) => { + let objects = result as DataComponent[]; + this.data_component.data_source = objects[0].data_source; + this.loading = false; + }, + complete: () => { subscription.unsubscribe(); } + }); + } } public viewRelationship(object: StixObject): void { diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts index 023fdb7c..f468a57c 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -38,6 +38,7 @@ export class DataSourceViewComponent extends StixViewPage implements OnInit { public createDataComponent(): void { let data_component = new DataComponent(); data_component.data_source_ref = this.data_source.stixID; + data_component.data_source = this.data_source; let prompt = this.dialog.open(StixDialogComponent, { data: { object: data_component, From cf4cc01abd0ae9576aa0f2d966892fc74ebeaa13 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 11:35:02 -0400 Subject: [PATCH 49/89] bug fix for viewing collection import summary --- app/src/app/components/stix/stix-list/stix-list.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index 22ee528d..a7d9418c 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -171,7 +171,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if ("type" in this.config) { // this.filter.push("type." + this.config.type); // set columns according to type - switch(this.config.type) { + switch(this.config.type.replace(/_/g, '-')) { case "collection": case "collection-created": this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); From 405c4bab1cf579221a20e92b00b35200b9f6a5e8 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 12:15:47 -0400 Subject: [PATCH 50/89] show deprecated/revoked status in relationship view --- .../relationship-view/relationship-view.component.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html index 6477d321..ee84b68a 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html @@ -2,6 +2,10 @@

{{relationship.source_name}} {{relationship.relationship_type}} {{relationship.target_name}}

+

+ deprecated + revoked by: {{target_obj.stix.name}} +

From 42adbbefbb9928c2e63bbfb2273410dc05b34694 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 1 Oct 2021 12:27:01 -0400 Subject: [PATCH 51/89] refresh list of data components after changes --- .../app/components/stix/stix-list/stix-list.component.ts | 8 +++++++- .../data-source-view/data-source-view.component.html | 3 ++- .../data-source-view/data-source-view.component.ts | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index a7d9418c..7af4479c 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -43,6 +43,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { @Input() public config: StixListConfig = {}; @Output() public onRowAction = new EventEmitter(); @Output() public onSelect = new EventEmitter(); + @Output() public refresh = new EventEmitter(); @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild('search') search: ElementRef; public searchQuery: string = ""; @@ -114,7 +115,12 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { maxHeight: "75vh" }) let subscription = prompt.afterClosed().subscribe({ - next: result => { if (prompt.componentInstance.dirty) this.applyControls(); }, //re-fetch values since an edit occurred + next: result => { + if (prompt.componentInstance.dirty) { //re-fetch values since an edit occurred + this.applyControls(); + this.refresh.emit(); + } + }, complete: () => { subscription.unsubscribe(); } }); } diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html index b8e5546a..0be678c7 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html @@ -105,7 +105,8 @@

Data Components

'type': 'data-component', 'clickBehavior': 'dialog', 'allowEdits': true - }"> + }" + (refresh)="getDataComponents()">
diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts index f468a57c..49be72db 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -39,6 +39,7 @@ export class DataSourceViewComponent extends StixViewPage implements OnInit { let data_component = new DataComponent(); data_component.data_source_ref = this.data_source.stixID; data_component.data_source = this.data_source; + data_component.workflow = undefined; let prompt = this.dialog.open(StixDialogComponent, { data: { object: data_component, From f2c388b48bfe5f2850ab5e23c137110750337df1 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 5 Oct 2021 08:31:33 -0400 Subject: [PATCH 52/89] minor review updates --- app/src/app/classes/stix/data-source.ts | 27 +------------------ app/src/app/classes/stix/relationship.ts | 4 +-- .../rest-api/rest-api-connector.service.ts | 2 +- .../data-source-view.component.html | 9 +++---- 4 files changed, 7 insertions(+), 35 deletions(-) diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index c0760825..507e3657 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -98,32 +98,7 @@ export class DataSource extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate(restAPIService: RestApiConnectorService): Observable { - return this.base_validate(restAPIService).pipe( - map(result => { - // check contributors - if (!this.contributors.length) result.errors.push({ - "field": "contributors", - "result": "error", - "message": "object has no contributors" - }); - // check platforms - if (!this.platforms.length) result.errors.push({ - "field": "platforms", - "result": "error", - "message": "object has no platforms" - }); - - // check collection layers - if (!this.collection_layers.length) result.errors.push({ - "field": "collection_layers", - "result": "error", - "message": "object has no collection layers" - }); - - return result; - }), - - ); + return this.base_validate(restAPIService); } /** diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 9ba81bfb..f91bd47f 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -168,8 +168,8 @@ export class Relationship extends StixObject { this.updating_refs = true; if (object.stix.type == 'attack-pattern' && object.stix.x_mitre_is_subtechnique) { return restAPIService.getRelatedTo({sourceRef: object.stix.id, relationshipType: "subtechnique-of"}).pipe( // fetch parent from REST API - map(relationshp => { - let p = relationshp as any; + map(relationship => { + let p = relationship as any; if (!p || p.data.length == 0) return null; // no parent technique this.updating_refs = false; return p.data[0].target_object; diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 08aa9ae9..1ff0399c 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -447,7 +447,7 @@ export class RestApiConnectorService extends ApiConnector { d.data_source = ds[0]; return [d]; }), - tap(result => logger.log("fetched data source of", result)) + tap(data_component => logger.log("fetched data source of", data_component)) ); }), catchError(this.handleError_continue([])), // on error, trigger the error notification and continue operation without crashing (returns empty item) diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html index 0be678c7..0bfb1d7d 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.html @@ -41,8 +41,7 @@ mode: editing? 'edit' : 'view', object: data_source, field: 'contributors', - editType: 'any', - required: true + editType: 'any' }">

@@ -53,8 +52,7 @@ mode: editing? 'edit' : 'view', object: data_source, field: 'platforms', - editType: 'select', - required: true + editType: 'select' }">
@@ -64,8 +62,7 @@ object: data_source, field: 'collection_layers', label: 'collection layers', - editType: 'select', - required: true + editType: 'select' }">
From eea737a8c2b0ab5dbdbe1025889416ca5746e420 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 5 Oct 2021 09:25:04 -0400 Subject: [PATCH 53/89] move logic to update parent to relationship class --- app/src/app/classes/stix/relationship.ts | 24 +++++++++++++++---- .../relationship-view.component.ts | 8 ++----- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index f91bd47f..8c9c682a 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -165,28 +165,44 @@ export class Relationship extends StixObject { * @returns {Observable} of the parent object */ public get_parent_object(object: any, restAPIService: RestApiConnectorService): Observable { - this.updating_refs = true; if (object.stix.type == 'attack-pattern' && object.stix.x_mitre_is_subtechnique) { return restAPIService.getRelatedTo({sourceRef: object.stix.id, relationshipType: "subtechnique-of"}).pipe( // fetch parent from REST API map(relationship => { let p = relationship as any; if (!p || p.data.length == 0) return null; // no parent technique - this.updating_refs = false; return p.data[0].target_object; }) ); } else if (object.stix.type == 'x-mitre-data-component') { return restAPIService.getDataSource(object.stix.x_mitre_data_source_ref).pipe( // fetch data source from REST API map(data_sources => { - this.updating_refs = false; return data_sources[0].serialize(); }) ); } - this.updating_refs = false return EMPTY; } + public update_source_parent(restAPIService: RestApiConnectorService): Observable { + this.updating_refs = true; + var subscription = this.get_parent_object(this.source_object, restAPIService).subscribe({ + next: (res) => { this.source_parent = res; }, + complete: () => { if (subscription) subscription.unsubscribe(); } + }); + this.updating_refs = false; + return of(this); + } + + public update_target_parent(restAPIService: RestApiConnectorService): Observable { + this.updating_refs = true; + var subscription = this.get_parent_object(this.target_object, restAPIService).subscribe({ + next: (res) => { this.target_parent = res; }, + complete: () => { if (subscription) subscription.unsubscribe(); } + }); + this.updating_refs = false; + return of(this); + } + /** * Transform the current object into a raw object for sending to the back-end, stripping any unnecessary fields * @abstract diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts index a3e950e1..6ceb89ec 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts @@ -36,9 +36,7 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { // initialize source/target types if there is a source/target object, or if there is only one possible value if (this.relationship.source_object) { this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] - // retrieve parent of source object - var src_sub = this.relationship.get_parent_object(this.relationship.source_object, this.restApiService).subscribe({ - next: (res) => { this.relationship.source_parent = res; }, + var src_sub = this.relationship.update_source_parent(this.restApiService).subscribe({ complete: () => { if (src_sub) src_sub.unsubscribe(); } }); } @@ -46,9 +44,7 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { if (this.relationship.target_object) { this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] - // retrieve parent of target object - var tgt_sub = this.relationship.get_parent_object(this.relationship.target_object, this.restApiService).subscribe({ - next: (res) => { this.relationship.target_parent = res; }, + var tgt_sub = this.relationship.update_target_parent(this.restApiService).subscribe({ complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } }); } From fcd19113adead096f4b625c8dde83f2e19650543 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:47:00 -0400 Subject: [PATCH 54/89] clean up and document new dialog features --- .../stix-dialog/stix-dialog.component.html | 6 ++-- .../stix/stix-dialog/stix-dialog.component.ts | 30 ++++++++++++++++--- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html index f2c07c4a..f2f1e47f 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html @@ -3,13 +3,13 @@
+ -
- + @@ -58,7 +58,7 @@ [config]="config" (onOpenHistory)="openHistory()" (onOpenNotes)="openNotes()" - (onClickRelationship)="openObject($event)"> + (onClickRelationship)="changeDialogObject($event)"> { - this.dialogRef.close(this.dirty); + next: (result) => { this.editorService.onEditingStopped.emit(); + if (this.prevObject) this.revertToPreviousObject(); + else this.dialogRef.close(this.dirty); }, complete: () => { subscription.unsubscribe(); } }) @@ -78,7 +93,14 @@ export class StixDialogComponent implements OnInit { this.validating = false; } + public discardChanges() { + this.editorService.onEditingStopped.emit(); + if (this.prevObject) this.revertToPreviousObject(); + else this.close(); + } + public close() { + if (this.prevObject) this.prevObject = undefined; // unset previous object this.dialogRef.close(this.dirty); } From 5db5a63eebf7b7df669e7a1b1e6c447b6491ee97 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Tue, 5 Oct 2021 14:04:59 -0400 Subject: [PATCH 55/89] update changelog for 66 and 67 --- docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index d316976f..a671b2db 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -34,6 +34,10 @@ ## Changes staged on develop ### ATT&CK Workbench version 1.0.3 +#### New Features in 1.0.3 +- Created view pages data source and data component objects. See [frontend#66](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/66). +- Added ability to create and edit data sources and data components. See [frontend#67](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/67). + #### Improvements in 1.0.3 - Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). - Improved the visibility of errors in collection imports: From 7ada684d891b8e8c0e77540301909ad91b75aab4 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 6 Oct 2021 11:41:33 -0400 Subject: [PATCH 56/89] update usage file for data sources --- docs/usage.md | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 5928c3eb..696cb2ce 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -173,10 +173,11 @@ ATT&CK IDs must follow a prescribed format: | Matrix | (domain identifier)* | | Tactic | `TAxxxx` | | Technique | `Txxxx` | -| Sub-Technique | `Txxxx.yyy` | +| Sub-Technique | `Txxxx.yyy` | | Mitigation | `Mxxxx` | | Group | `Gxxxx` | | Software | `Sxxxx` | +| Data Source | `DSxxxx` | _\* Domain identifiers for Matrices are described in the section for editing matrices._ @@ -232,7 +233,7 @@ The set of fields available to edit on a technique differs according to the doma | Field | Domains | Tactics? | Description | |:------|:--------|:---------|:-------------| -| Data Sources | Enterprise, ICS | (All Tactics) | Sources of information that may be used to identify the action or result of the action being performed. | +| Data Sources | ICS | (All Tactics) | Sources of information that may be used to identify the action or result of the action being performed. | | sub-technique? | Enterprise | (All Tactics) | Is this object a sub-technique? This cannot be changed for sub-techniques with assigned parents, or for parent-techniques with assigned sub-techniques. | | System Requirements | Enterprise | (All Tactics) | Additional information on requirements the adversary needs to meet or about the state of the system (software, patch level, etc.) that may be required for the technique to work. | | Permissions Required | Enterprise | Privilege Escalation | The lowest level of permissions the adversary is required to be operating within to perform the technique on a system. | @@ -252,7 +253,7 @@ The set of fields available to edit on a technique differs according to the doma | Sub-techniques / Other Sub-techniques | Sub-techniques of the technique if it is a parent technique, or other sub-techniques of the parent | is a sub-technique. | Mitigations | Mitigations that apply to this technique | | Procedure Examples | Groups and software that use this technique | - +| Data Sources | Data components that detect this technique | #### Editing Tactics @@ -306,6 +307,21 @@ The software type must be selected when creating it and due to limitations of th | Techniques Used | Techniques used by the group | | Associated Groups | Groups that use this software | +#### Editing Data Sources + + + +##### Data Source Relationships + +| Relationship Section | Description | +|:-----|:----| +| Data Components | Data components related to this data source | + +##### Data Component Relationships + +| Relationship Section | Description | +|:-----|:----| +| Techniques Detected | Techniques detected by the data component | #### Editing Relationships Relationships map objects to other objects. Relationships have types, sources, and targets. The source and targets define the objects connected by the relationship, and the type is a verb describing the nature of their relationship. @@ -316,6 +332,7 @@ Relationships map objects to other objects. Relationships have types, sources, a | uses | Group, Software* | Software*, Technique | | mitigates | Mitigation | Technique | | subtechnique-of | Technique | Technique | +| detects | Data Component | Technique | _\* Relationships cannot be created between two software._ From 17984b367164f55c8f39efa1bacfffb6909b24bf Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 7 Oct 2021 09:17:52 -0400 Subject: [PATCH 57/89] update version --- docs/changelog.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index a671b2db..7691aee3 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -32,20 +32,20 @@ # Changelog -## Changes staged on develop -### ATT&CK Workbench version 1.0.3 -#### New Features in 1.0.3 +## 21 October 2021 - Changes staged on develop +### ATT&CK Workbench version 1.1.0 +#### New Features in 1.1.0 - Created view pages data source and data component objects. See [frontend#66](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/66). - Added ability to create and edit data sources and data components. See [frontend#67](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/67). -#### Improvements in 1.0.3 +#### Improvements in 1.1.0 - Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). - Improved the visibility of errors in collection imports: - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - REST API will now log import errors to the console. - Frontend will now log import errors to the console when the application environment is not set to production. -#### Fixes in 1.0.3 +#### Fixes in 1.1.0 - Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. - Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. - Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). From 2258e45d7675753a25793a0ed1a6c6d4b4448368 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 7 Oct 2021 10:00:25 -0400 Subject: [PATCH 58/89] expand dialog documentation --- app/src/app/classes/stix/relationship.ts | 3 +-- .../add-relationship-button.component.ts | 10 ++++++++-- .../views/stix/stix-dialog/stix-dialog.component.ts | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 8c9c682a..358d24cc 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -242,7 +242,7 @@ export class Relationship extends StixObject { // this.source_name = raw.source_object.stix.name; let src_sdo = raw.source_object.stix; - if ("external_references" in src_sdo && src_sdo["type"] !== "x-mitre-data-component") { + if ("external_references" in src_sdo) { if (src_sdo.external_references.length > 0 && src_sdo.external_references[0].hasOwnProperty("external_id")) { if (typeof(src_sdo.external_references[0].external_id) === "string") this.source_ID = src_sdo.external_references[0].external_id; else logger.error("TypeError: attackID field is not a string:", src_sdo.external_references[0].external_id, "(", typeof(src_sdo.external_references[0].external_id), ")"); @@ -271,7 +271,6 @@ export class Relationship extends StixObject { * @returns {Observable} the validation warnings and errors once validation is complete. */ public validate(restAPIService: RestApiConnectorService): Observable { - //TODO verify source and target ref exist return this.base_validate(restAPIService).pipe( map(result => { // presence of source-ref diff --git a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts index 96496a94..62256815 100644 --- a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts +++ b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts @@ -41,8 +41,10 @@ export class AddRelationshipButtonComponent implements OnInit { sidebarControl: "events" } + // if a dialog reference is provided, replace the active + // content with the relationship edit interface. This prevents + // a new dialog from opening over the current dialog. if (this.config.dialog && this.config.dialog.componentInstance) { - // replace current dialog content this.config.dialog.componentInstance._config = config; this.config.dialog.componentInstance.startEditing(); } else { @@ -70,5 +72,9 @@ export interface AddRelationshipButtonConfig { source_object?: StixObject; //initial relationship source object. Takes precedence over source_ref if both are specified, and is much faster to execute target_ref?: string; //initial relationship target ref target_object?: StixObject; //initial relationship target object. Takes precedence over target_ref if both are specified, and is much faster to execute - dialog?: MatDialogRef //dialog ref; if provided the 'create relationship interface' will replace the current dialog content + /** + * reference to the current working dialog. This is relevant when adding a new relationship from within the dialog. + * If provided, the 'create relationship' interface will replace the dialog content. + */ + dialog?: MatDialogRef } \ No newline at end of file diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts index 177bbe21..8a3c709a 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts @@ -26,7 +26,7 @@ export class StixDialogComponent implements OnInit { showRelationships: object.attackType == "data-component" ? true : false, editable: this._config.editable, sidebarControl: this._config.sidebarControl == "disable"? "disable" : "events", - dialog: this.dialogRef + dialog: this.dialogRef // relevant when adding a new relationship inside of an existing dialog } } From f616af8b2785488701c5453e4db1e66eb8c70020 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Thu, 7 Oct 2021 15:04:18 -0400 Subject: [PATCH 59/89] fix styling for required input fields --- .../stix/list-property/list-edit/list-edit.component.html | 3 ++- .../stix/list-property/list-edit/list-edit.component.ts | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html index a9c3dae1..eb07aa35 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.html +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.html @@ -2,11 +2,12 @@ {{config.label? config.label : config.field}} - + {{value}} cancel = 0) { this.config.object[this.config.field].splice(i, 1); } + this.inputControl.setValue(this.config.object[this.config.field]) } /** Remove selection from via chip cancel button */ From 02ce288d5e91e8d64061c36a9f937b53610fe0aa Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 8 Oct 2021 14:01:20 -0400 Subject: [PATCH 60/89] fix for async issues fetching parent of source or target objects --- app/src/app/classes/stix/relationship.ts | 78 +++++++++++++------ .../relationship-view.component.ts | 18 +++-- 2 files changed, 68 insertions(+), 28 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 358d24cc..9b3c7431 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -81,12 +81,22 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.source_object = x.find(result => result.stix.id == new_source_ref); this.deserialize(serialized); - var src_sub = this.get_parent_object(serialized.source_object, restAPIService).subscribe({ - next: (res) => { this.source_parent = res; }, - complete: () => { if (src_sub) src_sub.unsubscribe(); } - }); - this.updating_refs = false; return this; + }), + switchMap(relationship => { + if (relationship.source_object.stix.x_mitre_is_subtechnique || relationship.source_object.stix.type == 'x-mitre-data-component') { + return this.get_parent_object(relationship.source_object, restAPIService).pipe( + map(res => { + this.source_parent = res; + this.updating_refs = false; + return this; + }) + ) + } else { + this.source_parent = undefined; // source object has no parent + this.updating_refs = false; + return of(this); + } }) ) } @@ -103,10 +113,17 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.source_object = new_source_object.serialize(); this.deserialize(serialized); - var src_sub = this.get_parent_object(serialized.source_object, restAPIService).subscribe({ - next: (res) => { this.source_parent = res; }, - complete: () => { if (src_sub) src_sub.unsubscribe(); } - }); + + if (this.source_object.stix.x_mitre_is_subtechnique || this.source_object.stix.type == 'x-mitre-data-component') { + return this.get_parent_object(this.source_object, restAPIService).pipe( + map(result => { + this.source_parent = result; + this.updating_refs = false; + return this; + }) + ); + } else this.source_parent = undefined; // source object has no parent + this.updating_refs = false; return of(this); } @@ -126,12 +143,22 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.target_object = x.find(result => result.stix.id == new_target_ref); this.deserialize(serialized); - let tgt_sub = this.get_parent_object(serialized.target_object, restAPIService).subscribe({ - next: (res) => { this.target_parent = res; }, - complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } - }); - this.updating_refs = false; return this; + }), + switchMap(relationship => { + if (relationship.target_object.stix.x_mitre_is_subtechnique || relationship.target_object.stix.type == 'x-mitre-data-component') { + return this.get_parent_object(relationship.target_object, restAPIService).pipe( + map(res => { + this.target_parent = res; + this.updating_refs = false; + return this; + }) + ) + } else { + this.target_parent = undefined; // target object has no parent + this.updating_refs = false; + return of(this); + } }) ) } @@ -148,10 +175,17 @@ export class Relationship extends StixObject { let serialized = this.serialize(); serialized.target_object = new_target_object.serialize(); this.deserialize(serialized); - var tgt_sub = this.get_parent_object(serialized.target_object, restAPIService).subscribe({ - next: (res) => { this.target_parent = res; }, - complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } - }); + + if (this.target_object.stix.x_mitre_is_subtechnique || this.target_object.stix.type == 'x-mitre-data-component') { + return this.get_parent_object(this.target_object, restAPIService).pipe( + map(result => { + this.target_parent = result; + this.updating_refs = false; + return this; + }) + ); + } else this.target_parent = undefined; // target object has no parent + this.updating_refs = false; return of(this); } @@ -165,22 +199,22 @@ export class Relationship extends StixObject { * @returns {Observable} of the parent object */ public get_parent_object(object: any, restAPIService: RestApiConnectorService): Observable { - if (object.stix.type == 'attack-pattern' && object.stix.x_mitre_is_subtechnique) { + if (object.stix.x_mitre_is_subtechnique) { // sub-technique return restAPIService.getRelatedTo({sourceRef: object.stix.id, relationshipType: "subtechnique-of"}).pipe( // fetch parent from REST API map(relationship => { let p = relationship as any; - if (!p || p.data.length == 0) return null; // no parent technique + if (!p || p.data.length == 0) return null; // no parent technique found return p.data[0].target_object; }) ); - } else if (object.stix.type == 'x-mitre-data-component') { + } else { // data component return restAPIService.getDataSource(object.stix.x_mitre_data_source_ref).pipe( // fetch data source from REST API map(data_sources => { + if (!data_sources || data_sources.length == 0) return null; // no data source found return data_sources[0].serialize(); }) ); } - return EMPTY; } public update_source_parent(restAPIService: RestApiConnectorService): Observable { diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts index 6ceb89ec..c1037ce4 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts @@ -36,17 +36,23 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { // initialize source/target types if there is a source/target object, or if there is only one possible value if (this.relationship.source_object) { this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] - var src_sub = this.relationship.update_source_parent(this.restApiService).subscribe({ - complete: () => { if (src_sub) src_sub.unsubscribe(); } - }); + if (this.relationship.source_object.stix.x_mitre_is_subtechnique || this.source_type == 'data-component') { + // fetch parent of source object if it is a sub-technique or data component + var src_sub = this.relationship.update_source_parent(this.restApiService).subscribe({ + complete: () => { if (src_sub) src_sub.unsubscribe(); } + }); + } } else if (this.relationship.valid_source_types.length == 1) this.source_type = this.relationship.valid_source_types[0]; if (this.relationship.target_object) { this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] - var tgt_sub = this.relationship.update_target_parent(this.restApiService).subscribe({ - complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } - }); + if ( (this.relationship.target_object.stix.x_mitre_is_subtechnique || this.target_type == 'data-component') ) { + // fetch parent of target object if it is a sub-technique or data component + var tgt_sub = this.relationship.update_target_parent(this.restApiService).subscribe({ + complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } + }); + } } else if (this.relationship.valid_target_types.length == 1) this.target_type = this.relationship.valid_target_types[0]; } From fc2aa0026f0c127cbec248d95cd7e62ee9e7e6ac Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 11 Oct 2021 10:15:55 -0400 Subject: [PATCH 61/89] view data component on creation --- .../add-relationship-button.component.ts | 1 + .../data-source-view/data-source-view.component.ts | 4 +--- app/src/app/views/stix/stix-dialog/stix-dialog.component.html | 2 +- app/src/app/views/stix/stix-dialog/stix-dialog.component.ts | 4 ++++ 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts index 62256815..c8518f46 100644 --- a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts +++ b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts @@ -46,6 +46,7 @@ export class AddRelationshipButtonComponent implements OnInit { // a new dialog from opening over the current dialog. if (this.config.dialog && this.config.dialog.componentInstance) { this.config.dialog.componentInstance._config = config; + this.config.dialog.componentInstance.prevObject = this.config.source_object ? this.config.source_object : this.config.target_object; this.config.dialog.componentInstance.startEditing(); } else { // open a new dialog diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts index 49be72db..342b89bf 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -50,9 +50,7 @@ export class DataSourceViewComponent extends StixViewPage implements OnInit { }); let subscription = prompt.afterClosed().subscribe({ next: (result) => { - if (result) { - if (prompt.componentInstance.dirty) this.getDataComponents(); //re-fetch values since an edit occurred - } + if (result) { this.getDataComponents(); } //re-fetch values since an edit occurred }, complete: () => { subscription.unsubscribe(); } }); diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html index f2f1e47f..6045c3a1 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.html +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.html @@ -4,7 +4,7 @@
- +
diff --git a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts index 8a3c709a..283875f6 100644 --- a/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts +++ b/app/src/app/views/stix/stix-dialog/stix-dialog.component.ts @@ -84,6 +84,10 @@ export class StixDialogComponent implements OnInit { next: (result) => { this.editorService.onEditingStopped.emit(); if (this.prevObject) this.revertToPreviousObject(); + else if (object.attackType == 'data-component') { // view data component on save + this.validating = false; + this.editing = false; + } else this.dialogRef.close(this.dirty); }, complete: () => { subscription.unsubscribe(); } From 3520b5f6b39d5883efdd2f2063be2f590ab0489b Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 11 Oct 2021 12:44:27 -0400 Subject: [PATCH 62/89] test reduction of the cognitive complexity of deserialization? --- app/src/app/classes/stix/data-source.ts | 66 ++++++++++++------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index 507e3657..1e79c3c7 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -2,7 +2,6 @@ import { StixObject } from "./stix-object"; import { logger } from "../../util/logger"; import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; import { Observable } from "rxjs"; -import { map } from "rxjs/operators"; import { ValidationData } from "../serializable"; import { DataComponent } from "./data-component"; @@ -53,43 +52,44 @@ export class DataSource extends StixObject { * @param {*} raw the raw object to parse */ public deserialize(raw: any) { - if ("stix" in raw) { - let sdo = raw.stix; - if ("name" in sdo) { - if (typeof(sdo.name) === "string") this.name = sdo.name; - else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") - } else this.name = ""; + if ("dataComponents" in raw) { + for (let obj of raw.dataComponents) { + this.data_components.push(new DataComponent(obj)); + } + } + + if (!("stix" in raw)) return; - if ("description" in sdo) { - if (typeof(sdo.description) === "string") this.description = sdo.description; - else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") - } else this.description = ""; + let sdo = raw.stix; + if ("name" in sdo) { + if (typeof(sdo.name) === "string") this.name = sdo.name; + else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") + } else this.name = ""; - if ("x_mitre_platforms" in sdo) { - if (this.isStringArray(sdo.x_mitre_platforms)) this.platforms = sdo.x_mitre_platforms; - else logger.error("TypeError: platforms field is not a string array.") - } else this.platforms = []; - - if ("x_mitre_collection_layers" in sdo) { - if (this.isStringArray(sdo.x_mitre_collection_layers)) this.collection_layers = sdo.x_mitre_collection_layers; - else logger.error("TypeError: collection layers field is not a string array."); - } else this.collection_layers = []; + if ("description" in sdo) { + if (typeof(sdo.description) === "string") this.description = sdo.description; + else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") + } else this.description = ""; - if ("x_mitre_contributors" in sdo) { - if (this.isStringArray(sdo.x_mitre_contributors)) this.contributors = sdo.x_mitre_contributors; - else logger.error("TypeError: x_mitre_contributors is not a string array:", sdo.x_mitre_contributors, "(",typeof(sdo.x_mitre_contributors),")") - } else this.contributors = []; + if ("x_mitre_platforms" in sdo) { + if (this.isStringArray(sdo.x_mitre_platforms)) this.platforms = sdo.x_mitre_platforms; + else logger.error("TypeError: platforms field is not a string array.") + } else this.platforms = []; + + if ("x_mitre_collection_layers" in sdo) { + if (this.isStringArray(sdo.x_mitre_collection_layers)) this.collection_layers = sdo.x_mitre_collection_layers; + else logger.error("TypeError: collection layers field is not a string array."); + } else this.collection_layers = []; - if ("x_mitre_domains" in sdo) { - if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; - else logger.error("TypeError: domains field is not a string array."); - } else this.domains = ['enterprise-attack']; // default to enterprise + if ("x_mitre_contributors" in sdo) { + if (this.isStringArray(sdo.x_mitre_contributors)) this.contributors = sdo.x_mitre_contributors; + else logger.error("TypeError: x_mitre_contributors is not a string array:", sdo.x_mitre_contributors, "(",typeof(sdo.x_mitre_contributors),")") + } else this.contributors = []; - if ("data_components" in sdo) { // TODO parse objects returned from REST API - if (typeof(sdo.data_components) == "object") this.data_components = sdo.data_components.map(dc => new DataComponent(dc)); - else logger.error("TypeError: data components field is not an object:", sdo.data_components, "(", typeof(sdo.data_components), ")"); - } else this.data_components = []; - } + if ("x_mitre_domains" in sdo) { + if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; + else logger.error("TypeError: domains field is not a string array."); + } else this.domains = ['enterprise-attack']; // default to enterprise } /** From 8f50b1eaa3b46ffbaffe6722f1d9587646c0c176 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:05:27 -0400 Subject: [PATCH 63/89] code cleanup --- app/src/app/classes/stix/data-component.ts | 38 +++--- app/src/app/classes/stix/relationship.ts | 8 +- app/src/app/classes/stix/stix-object.ts | 4 +- .../add-relationship-button.component.ts | 21 ++-- .../object-status/object-status.component.ts | 117 +++++++++--------- .../data-source-list.component.ts | 11 +- .../relationship-view.component.ts | 4 +- 7 files changed, 98 insertions(+), 105 deletions(-) diff --git a/app/src/app/classes/stix/data-component.ts b/app/src/app/classes/stix/data-component.ts index b3225334..cc46b6d2 100644 --- a/app/src/app/classes/stix/data-component.ts +++ b/app/src/app/classes/stix/data-component.ts @@ -45,29 +45,29 @@ export class DataComponent extends StixObject { * @param {*} raw the raw object to parse */ public deserialize(raw: any) { - if ("stix" in raw) { - let sdo = raw.stix; + if (!("stix" in raw)) return; - if ("name" in sdo) { - if (typeof(sdo.name) === "string") this.name = sdo.name; - else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") - } else this.name = ""; + let sdo = raw.stix; - if ("description" in sdo) { - if (typeof(sdo.description) === "string") this.description = sdo.description; - else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") - } else this.description = ""; + if ("name" in sdo) { + if (typeof(sdo.name) === "string") this.name = sdo.name; + else logger.error("TypeError: name field is not a string:", sdo.name, "(",typeof(sdo.name),")") + } else this.name = ""; - if ("x_mitre_data_source_ref" in sdo) { - if (typeof(sdo.x_mitre_data_source_ref) === "string") this.data_source_ref = sdo.x_mitre_data_source_ref; - else logger.error("TypeError: data source ref field is not a string:", sdo.x_mitre_data_source_ref, "(",typeof(sdo.x_mitre_data_source_ref),")") - } else this.data_source_ref = ""; + if ("description" in sdo) { + if (typeof(sdo.description) === "string") this.description = sdo.description; + else logger.error("TypeError: description field is not a string:", sdo.description, "(",typeof(sdo.description),")") + } else this.description = ""; - if ("x_mitre_domains" in sdo) { - if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; - else logger.error("TypeError: domains field is not a string array."); - } else this.domains = ['enterprise-attack']; // default to enterprise - } + if ("x_mitre_data_source_ref" in sdo) { + if (typeof(sdo.x_mitre_data_source_ref) === "string") this.data_source_ref = sdo.x_mitre_data_source_ref; + else logger.error("TypeError: data source ref field is not a string:", sdo.x_mitre_data_source_ref, "(",typeof(sdo.x_mitre_data_source_ref),")") + } else this.data_source_ref = ""; + + if ("x_mitre_domains" in sdo) { + if (this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains; + else logger.error("TypeError: domains field is not a string array."); + } else this.domains = ['enterprise-attack']; // default to enterprise } /** diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 9b3c7431..0034c37c 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -1,4 +1,4 @@ -import { Observable, of, EMPTY } from "rxjs"; +import { Observable, of } from "rxjs"; import { map, switchMap } from "rxjs/operators"; import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service"; import { ValidationData } from "../serializable"; @@ -202,9 +202,9 @@ export class Relationship extends StixObject { if (object.stix.x_mitre_is_subtechnique) { // sub-technique return restAPIService.getRelatedTo({sourceRef: object.stix.id, relationshipType: "subtechnique-of"}).pipe( // fetch parent from REST API map(relationship => { - let p = relationship as any; - if (!p || p.data.length == 0) return null; // no parent technique found - return p.data[0].target_object; + if (!relationship || relationship.data.length == 0) return null; // no parent technique found + let p = relationship.data[0] as Relationship; + return p.target_object; }) ); } else { // data component diff --git a/app/src/app/classes/stix/stix-object.ts b/app/src/app/classes/stix/stix-object.ts index 4b79def3..768c0523 100644 --- a/app/src/app/classes/stix/stix-object.ts +++ b/app/src/app/classes/stix/stix-object.ts @@ -300,8 +300,8 @@ export abstract class StixObject extends Serializable { this.attackType == "matrix"? restAPIService.getAllMatrices() : this.attackType == "mitigation"? restAPIService.getAllMitigations() : this.attackType == "technique"? restAPIService.getAllTechniques() : - this.attackType == "data-source" ? restAPIService.getAllDataSources() : - this.attackType == "data-component" ? restAPIService.getAllDataComponents() : + this.attackType == "data-source"? restAPIService.getAllDataSources() : + this.attackType == "data-component"? restAPIService.getAllDataComponents() : restAPIService.getAllTactics(); return accessor.pipe( map(objects => { diff --git a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts index c8518f46..079ef226 100644 --- a/app/src/app/components/add-relationship-button/add-relationship-button.component.ts +++ b/app/src/app/components/add-relationship-button/add-relationship-button.component.ts @@ -48,17 +48,18 @@ export class AddRelationshipButtonComponent implements OnInit { this.config.dialog.componentInstance._config = config; this.config.dialog.componentInstance.prevObject = this.config.source_object ? this.config.source_object : this.config.target_object; this.config.dialog.componentInstance.startEditing(); - } else { - // open a new dialog - let prompt = this.dialog.open(StixDialogComponent, { - data: config, - maxHeight: "75vh" - }) - let subscription = prompt.afterClosed().subscribe({ - next: result => { if (prompt.componentInstance.dirty) this.created.emit(); }, //re-fetch values since an edit occurred - complete: () => { subscription.unsubscribe(); } - }); + return; } + + // open a new dialog + let prompt = this.dialog.open(StixDialogComponent, { + data: config, + maxHeight: "75vh" + }) + let subscription = prompt.afterClosed().subscribe({ + next: result => { if (prompt.componentInstance.dirty) this.created.emit(); }, //re-fetch values since an edit occurred + complete: () => { subscription.unsubscribe(); } + }); }, complete: () => { if (zip_subscription) zip_subscription.unsubscribe(); } //for some reason zip_subscription doesn't exist if using set_source_object or set_target_object }) diff --git a/app/src/app/components/object-status/object-status.component.ts b/app/src/app/components/object-status/object-status.component.ts index 642bce83..1034aa89 100644 --- a/app/src/app/components/object-status/object-status.component.ts +++ b/app/src/app/components/object-status/object-status.component.ts @@ -201,69 +201,70 @@ export class ObjectStatusComponent implements OnInit { let confirmationSub = confirmationPrompt.afterClosed().subscribe({ next: (result) => { - if (result) { - // deprecate object - if (revoked) this.object.revoked = true; - else this.object.deprecated = true; - saves.push(this.object.save(this.restAPIService)); - - // update relationships with the object - for (let relationship of this.relationships) { - if (!relationship.deprecated) { - relationship.deprecated = true; - saves.push(relationship.save(this.restAPIService)); - } - } - - if (this.editorService.type == 'data-source') { - // deprecate related data components - let dataComponents$ = this.restAPIService.getAllDataComponents(); - let dcSubscription = dataComponents$.subscribe({ - next: (dataComponents) => { - let allDataComponents = dataComponents.data as DataComponent[]; - let relDataComponents = allDataComponents.filter(dc => dc.data_source_ref == this.object.stixID); - for (let dataComponent of relDataComponents) { - dataComponent.deprecated = true; - saves.push(dataComponent.save(this.restAPIService)); + if (!result) { // user cancelled + if (revoked) this.revoked = false; + else this.deprecated = false; + return; + } - // deprecate relationships with the data component - let dataComponentRels$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: dataComponent.stixID}); - let relSubscription = dataComponentRels$.subscribe({ - next: (rels) => { - let relationships = rels.data as Relationship[]; - for (let rel of relationships) { - rel.deprecated = true; - saves.push(rel.save(this.restAPIService)); - } - }, - complete: () => { relSubscription.unsubscribe(); } - }); - } - }, - complete: () => { dcSubscription.unsubscribe(); } - }); + // deprecate object + if (revoked) this.object.revoked = true; + else this.object.deprecated = true; + saves.push(this.object.save(this.restAPIService)); + + // update relationships with the object + for (let relationship of this.relationships) { + if (!relationship.deprecated) { + relationship.deprecated = true; + saves.push(relationship.save(this.restAPIService)); } + } - if (revoked_by_id) { - // create a new 'revoked-by' relationship - let revokedRelationship = new Relationship(); - revokedRelationship.relationship_type = 'revoked-by'; - revokedRelationship.source_ref = this.object.stixID; - revokedRelationship.target_ref = revoked_by_id; - saves.push(revokedRelationship.save(this.restAPIService)); - } - - // complete save calls - let saveSubscription = forkJoin(saves).subscribe({ - complete: () => { - this.editorService.onReload.emit(); - saveSubscription.unsubscribe(); - } + if (this.editorService.type == 'data-source') { + // deprecate related data components + let dataComponents$ = this.restAPIService.getAllDataComponents(); + let dcSubscription = dataComponents$.subscribe({ + next: (dataComponents) => { + let allDataComponents = dataComponents.data as DataComponent[]; + let relDataComponents = allDataComponents.filter(dc => dc.data_source_ref == this.object.stixID); + for (let dataComponent of relDataComponents) { + dataComponent.deprecated = true; + saves.push(dataComponent.save(this.restAPIService)); + + // deprecate relationships with the data component + let dataComponentRels$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: dataComponent.stixID}); + let relSubscription = dataComponentRels$.subscribe({ + next: (rels) => { + let relationships = rels.data as Relationship[]; + for (let rel of relationships) { + rel.deprecated = true; + saves.push(rel.save(this.restAPIService)); + } + }, + complete: () => { relSubscription.unsubscribe(); } + }); + } + }, + complete: () => { dcSubscription.unsubscribe(); } }); - } else { // user cancelled - if (revoked) this.revoked = false; - else this.deprecated = false; } + + if (revoked_by_id) { + // create a new 'revoked-by' relationship + let revokedRelationship = new Relationship(); + revokedRelationship.relationship_type = 'revoked-by'; + revokedRelationship.source_ref = this.object.stixID; + revokedRelationship.target_ref = revoked_by_id; + saves.push(revokedRelationship.save(this.restAPIService)); + } + + // complete save calls + let saveSubscription = forkJoin(saves).subscribe({ + complete: () => { + this.editorService.onReload.emit(); + saveSubscription.unsubscribe(); + } + }); }, complete: () => { confirmationSub.unsubscribe(); } }); diff --git a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts index 779a3708..cfec6ca9 100644 --- a/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts +++ b/app/src/app/views/stix/data-source/data-source-list/data-source-list.component.ts @@ -1,15 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; @Component({ selector: 'app-data-source-list', templateUrl: './data-source-list.component.html', styleUrls: ['./data-source-list.component.scss'] }) -export class DataSourceListComponent implements OnInit { - - constructor() { } - - ngOnInit(): void { - } - -} +export class DataSourceListComponent { } diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts index c1037ce4..be30eb2e 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts @@ -1,6 +1,4 @@ -import { SelectionModel } from '@angular/cdk/collections'; -import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; -import { Observer } from 'rxjs'; +import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject, stixTypeToAttackType } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; From 0435b567c94a4b2ef858d4832c050ff10e8f4cbb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 11 Oct 2021 13:06:47 -0400 Subject: [PATCH 64/89] integration with updated rest api endpoints --- .../stix/list-property/list-edit/list-edit.component.ts | 5 +++-- .../data-source-view/data-source-view.component.ts | 2 +- app/src/app/views/stix/stix-page/stix-page.component.ts | 4 +--- .../technique/technique-view/technique-view.component.html | 1 - 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index 6e1429b4..f916ddf9 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -36,7 +36,8 @@ export class ListEditComponent implements OnInit, AfterContentChecked { "impact_type": "x_mitre_impact_type", "effective_permissions": "x_mitre_effective_permissions", "permissions_required": "x_mitre_permissions_required", - "collection_layers": "x_mitre_collection_layers" + "collection_layers": "x_mitre_collection_layers", + "data_sources": "x_mitre_data_sources" } public domains = [ "enterprise-attack", @@ -65,7 +66,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked { || this.config.field == 'impact_type' || this.config.field == 'domains' || this.config.field == 'collection_layers' - || this.config.field == 'data_sources') { // TODO retrieve ics data sources allowed values from REST API + || this.config.field == 'data_sources') { if (!this.dataLoaded) { let data$ = this.restAPIConnectorService.getAllAllowedValues(); this.sub = data$.subscribe({ diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts index 342b89bf..55c1294f 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -19,7 +19,7 @@ export class DataSourceViewComponent extends StixViewPage implements OnInit { constructor(public dialog: MatDialog, private restAPIConnectorService: RestApiConnectorService) { super(); } ngOnInit(): void { - this.getDataComponents(); + this.data_components = this.data_source.data_components; } public getDataComponents(): void { diff --git a/app/src/app/views/stix/stix-page/stix-page.component.ts b/app/src/app/views/stix/stix-page/stix-page.component.ts index 5f8a8f60..3a85acfa 100644 --- a/app/src/app/views/stix/stix-page/stix-page.component.ts +++ b/app/src/app/views/stix/stix-page/stix-page.component.ts @@ -126,9 +126,7 @@ export class StixPageComponent implements OnInit, OnDestroy { else if (this.objectType == "tactic") objects$ = this.restAPIConnectorService.getTactic(objectStixID); else if (this.objectType == "technique") objects$ = this.restAPIConnectorService.getTechnique(objectStixID, null, "latest", true); else if (this.objectType == "collection") objects$ = this.restAPIConnectorService.getCollection(objectStixID, objectModified, "latest", false, true); - // TODO retrieve data components along with data source object from from REST API - // else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID, null, "latest", false, false, true); - else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID); + else if (this.objectType == "data-source") objects$ = this.restAPIConnectorService.getDataSource(objectStixID, null, "latest", false, false, true); else if (this.objectType == "data-component") objects$ = this.restAPIConnectorService.getDataComponent(objectStixID); let subscription = objects$.subscribe({ next: result => { diff --git a/app/src/app/views/stix/technique/technique-view/technique-view.component.html b/app/src/app/views/stix/technique/technique-view/technique-view.component.html index e76f1d62..65604deb 100644 --- a/app/src/app/views/stix/technique/technique-view/technique-view.component.html +++ b/app/src/app/views/stix/technique/technique-view/technique-view.component.html @@ -102,7 +102,6 @@
-
} the validation warnings and errors once validation is complete. */ - public validate(restAPIService: RestApiConnectorService): Observable { + public validate(restAPIService: RestApiConnectorService): Observable { return this.base_validate(restAPIService); } @@ -84,7 +84,7 @@ export class DataComponent extends StixObject { * @param restAPIService [RestApiConnectorService] the service to perform the POST/PUT through * @returns {Observable} of the post */ - public save(restAPIService: RestApiConnectorService): Observable { + public save(restAPIService: RestApiConnectorService): Observable { // TODO POST if the object was just created (doesn't exist in db yet) let postObservable = restAPIService.postDataComponent(this); diff --git a/app/src/app/classes/stix/data-source.ts b/app/src/app/classes/stix/data-source.ts index 1e79c3c7..abcf06f1 100644 --- a/app/src/app/classes/stix/data-source.ts +++ b/app/src/app/classes/stix/data-source.ts @@ -51,7 +51,7 @@ export class DataSource extends StixObject { * @abstract * @param {*} raw the raw object to parse */ - public deserialize(raw: any) { + public deserialize(raw: any) { if ("dataComponents" in raw) { for (let obj of raw.dataComponents) { this.data_components.push(new DataComponent(obj)); @@ -97,7 +97,7 @@ export class DataSource extends StixObject { * @param {RestApiConnectorService} restAPIService: the REST API connector through which asynchronous validation can be completed * @returns {Observable} the validation warnings and errors once validation is complete. */ - public validate(restAPIService: RestApiConnectorService): Observable { + public validate(restAPIService: RestApiConnectorService): Observable { return this.base_validate(restAPIService); } @@ -106,7 +106,7 @@ export class DataSource extends StixObject { * @param restAPIService [RestApiConnectorService] the service to perform the POST/PUT through * @returns {Observable} of the post */ - public save(restAPIService: RestApiConnectorService): Observable { + public save(restAPIService: RestApiConnectorService): Observable { // TODO POST if the object was just created (doesn't exist in db yet) let postObservable = restAPIService.postDataSource(this); diff --git a/app/src/app/components/object-status/object-status.component.ts b/app/src/app/components/object-status/object-status.component.ts index 1034aa89..6a04bb53 100644 --- a/app/src/app/components/object-status/object-status.component.ts +++ b/app/src/app/components/object-status/object-status.component.ts @@ -207,7 +207,7 @@ export class ObjectStatusComponent implements OnInit { return; } - // deprecate object + // deprecate or revoke object if (revoked) this.object.revoked = true; else this.object.deprecated = true; saves.push(this.object.save(this.restAPIService)); diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index f916ddf9..f7f90134 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -58,7 +58,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked { ngOnInit(): void { this.selectControl = new FormControl({value: this.config.object[this.config.field], disabled: this.config.disabled ? this.config.disabled : false}); - this.inputControl = new FormControl(this.config.object[this.config.field], this.config.required ? [Validators.required] : undefined); + this.inputControl = new FormControl(null, this.config.required ? [Validators.required] : undefined); if (this.config.field == 'platforms' || this.config.field == 'tactic_type' || this.config.field == 'permissions_required' @@ -72,7 +72,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked { this.sub = data$.subscribe({ next: (data) => { let stixObject = this.config.object as StixObject; - this.allAllowedValues = data.find(obj => { return obj.objectType == stixObject.attackType; }) + this.allAllowedValues = data.find(obj => { return obj.objectType == stixObject.attackType; }); this.dataLoaded = true; }, complete: () => { this.sub.unsubscribe(); } @@ -200,6 +200,11 @@ export class ListEditComponent implements OnInit, AfterContentChecked { }); } + // check for existing data + for (let value of this.selectControl.value) { + if (!values.includes(value)) values.push(value); + } + if (!values.length) { // disable field and reset selection this.selectControl.disable(); diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index 7af4479c..62b0c609 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -285,6 +285,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { break; case "data-source": this.addColumn("", "workflow", "icon"); + this.addColumn("", "state", "icon"); this.addColumn("ID", "attackID", "plain", false); this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); this.addColumn("platforms", "platforms", "list"); diff --git a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts index 55c1294f..a53ee3c6 100644 --- a/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts +++ b/app/src/app/views/stix/data-source/data-source-view/data-source-view.component.ts @@ -46,7 +46,8 @@ export class DataSourceViewComponent extends StixViewPage implements OnInit { editable: true, mode: "edit", sidebarControl: "events" - } + }, + maxHeight: "75vh" }); let subscription = prompt.afterClosed().subscribe({ next: (result) => { From ce0acca7509ba78d988554590d3238a3bf8e0d5b Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 13 Oct 2021 15:03:12 -0400 Subject: [PATCH 67/89] track loading of source/target parent in relationship view --- .../relationship-view.component.html | 7 ++-- .../relationship-view.component.ts | 32 +++++++++++-------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html index ee84b68a..51d3adde 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.html @@ -1,4 +1,4 @@ -
+

{{relationship.source_name}} {{relationship.relationship_type}} {{relationship.target_name}}

@@ -110,4 +110,7 @@

References

-
\ No newline at end of file +
+ + + \ No newline at end of file diff --git a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts index be30eb2e..037f0b67 100644 --- a/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts +++ b/app/src/app/views/stix/relationship/relationship-view/relationship-view.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; +import { forkJoin } from 'rxjs'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject, stixTypeToAttackType } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; @@ -16,6 +17,7 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { public source_type: string; public target_type: string; public refresh: boolean = true; + public loaded: boolean = false; /** refresh the list of source objects if the type changes * This is bad code and should be done a better way. */ @@ -32,27 +34,31 @@ export class RelationshipViewComponent extends StixViewPage implements OnInit { ngOnInit(): void { // initialize source/target types if there is a source/target object, or if there is only one possible value + if (this.relationship.source_object) this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] + else if (this.relationship.valid_source_types.length == 1) this.source_type = this.relationship.valid_source_types[0]; + if (this.relationship.target_object) this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] + else if (this.relationship.valid_target_types.length == 1) this.target_type = this.relationship.valid_target_types[0]; + + // fetch parent of source and/or target objects + let parent_calls = []; if (this.relationship.source_object) { - this.source_type = stixTypeToAttackType[this.relationship.source_object.stix.type] if (this.relationship.source_object.stix.x_mitre_is_subtechnique || this.source_type == 'data-component') { - // fetch parent of source object if it is a sub-technique or data component - var src_sub = this.relationship.update_source_parent(this.restApiService).subscribe({ - complete: () => { if (src_sub) src_sub.unsubscribe(); } - }); + parent_calls.push(this.relationship.update_source_parent(this.restApiService)); } } - else if (this.relationship.valid_source_types.length == 1) this.source_type = this.relationship.valid_source_types[0]; - if (this.relationship.target_object) { - this.target_type = stixTypeToAttackType[this.relationship.target_object.stix.type] if ( (this.relationship.target_object.stix.x_mitre_is_subtechnique || this.target_type == 'data-component') ) { - // fetch parent of target object if it is a sub-technique or data component - var tgt_sub = this.relationship.update_target_parent(this.restApiService).subscribe({ - complete: () => { if (tgt_sub) tgt_sub.unsubscribe(); } - }); + parent_calls.push(this.relationship.update_target_parent(this.restApiService)); } } - else if (this.relationship.valid_target_types.length == 1) this.target_type = this.relationship.valid_target_types[0]; + if (parent_calls.length) { + var pSubscription = forkJoin(parent_calls).subscribe({ + complete: () => { + this.loaded = true; + if (pSubscription) pSubscription.unsubscribe(); + } + }); + } else this.loaded = true; } public setSourceObject(object: StixObject) { From 74ea8381ee6d3b8068a936509d8d723bef667955 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 13 Oct 2021 15:04:08 -0400 Subject: [PATCH 68/89] fix async issue when deprecating/revoking a data source & related objects --- .../object-status/object-status.component.ts | 71 +++++++++++-------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/app/src/app/components/object-status/object-status.component.ts b/app/src/app/components/object-status/object-status.component.ts index 6a04bb53..ae309863 100644 --- a/app/src/app/components/object-status/object-status.component.ts +++ b/app/src/app/components/object-status/object-status.component.ts @@ -3,8 +3,8 @@ import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { FormControl } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { PopoverContentComponent } from 'ngx-smart-popover'; -import { forkJoin } from 'rxjs'; -import { Collection } from 'src/app/classes/stix/collection'; +import { forkJoin, Observable } from 'rxjs'; +import { map, mergeMap } from 'rxjs/operators'; import { DataComponent } from 'src/app/classes/stix/data-component'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject } from 'src/app/classes/stix/stix-object'; @@ -29,6 +29,7 @@ export class ObjectStatusComponent implements OnInit { public objects: StixObject[]; public object: StixObject; public relationships; + public dataSourceRelationships: StixObject[]; public revoked: boolean = false; public deprecated: boolean = false; @@ -80,14 +81,48 @@ export class ObjectStatusComponent implements OnInit { let relSubscription = data$.subscribe({ next: (data) => { this.relationships = data.data as Relationship[]; - this.loaded = true; + if (this.editorService.type !== 'data-source') this.loaded = true; // mark as complete if object is not a data source setTimeout(() => this.popover.updatePosition()); //after render cycle update popover position since it has new content }, complete: () => { relSubscription.unsubscribe() } }); + + if (this.editorService.type == 'data-source') { + // retrieve related data components & their relationships + data$ = this.getDataSourceRelationships(); + let dataSubscription = data$.subscribe({ + next: (results) => { + this.dataSourceRelationships = results; + this.loaded = true; + }, + complete: () => { dataSubscription.unsubscribe(); } + }); + } } } + public getDataSourceRelationships(): Observable { + let dataComponents$ = this.restAPIService.getAllDataComponents(); + return dataComponents$.pipe( + map(result => { + let dataComponents = result.data as DataComponent[]; + return dataComponents.filter(d => d.data_source_ref == this.editorService.stixId); + }), + mergeMap(dataComponents => { + let relatedTo = dataComponents.map(dc => this.restAPIService.getRelatedTo({sourceOrTargetRef: dc.stixID})); + return forkJoin(relatedTo).pipe( + map(relationships => { + let all_results: StixObject[] = []; + for(let relationship_result of relationships) { + all_results = all_results.concat(relationship_result.data) + } + return all_results.concat(dataComponents);; + }) + ); + }) + ); + } + private save() { let saveSubscription = this.object.save(this.restAPIService).subscribe({ complete: () => { @@ -221,32 +256,10 @@ export class ObjectStatusComponent implements OnInit { } if (this.editorService.type == 'data-source') { - // deprecate related data components - let dataComponents$ = this.restAPIService.getAllDataComponents(); - let dcSubscription = dataComponents$.subscribe({ - next: (dataComponents) => { - let allDataComponents = dataComponents.data as DataComponent[]; - let relDataComponents = allDataComponents.filter(dc => dc.data_source_ref == this.object.stixID); - for (let dataComponent of relDataComponents) { - dataComponent.deprecated = true; - saves.push(dataComponent.save(this.restAPIService)); - - // deprecate relationships with the data component - let dataComponentRels$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: dataComponent.stixID}); - let relSubscription = dataComponentRels$.subscribe({ - next: (rels) => { - let relationships = rels.data as Relationship[]; - for (let rel of relationships) { - rel.deprecated = true; - saves.push(rel.save(this.restAPIService)); - } - }, - complete: () => { relSubscription.unsubscribe(); } - }); - } - }, - complete: () => { dcSubscription.unsubscribe(); } - }); + for (let obj of this.dataSourceRelationships) { + obj.deprecated = true; + saves.push(obj.save(this.restAPIService)); + } } if (revoked_by_id) { From 69808b25d55d34c8560936bc0e93dd248d1ced73 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:11:24 -0400 Subject: [PATCH 69/89] refactor fix for previous async issue --- .../object-status/object-status.component.ts | 64 +++++-------------- .../rest-api/rest-api-connector.service.ts | 34 +++++++++- 2 files changed, 48 insertions(+), 50 deletions(-) diff --git a/app/src/app/components/object-status/object-status.component.ts b/app/src/app/components/object-status/object-status.component.ts index ae309863..97343950 100644 --- a/app/src/app/components/object-status/object-status.component.ts +++ b/app/src/app/components/object-status/object-status.component.ts @@ -3,9 +3,7 @@ import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { FormControl } from '@angular/forms'; import { MatDialog } from '@angular/material/dialog'; import { PopoverContentComponent } from 'ngx-smart-popover'; -import { forkJoin, Observable } from 'rxjs'; -import { map, mergeMap } from 'rxjs/operators'; -import { DataComponent } from 'src/app/classes/stix/data-component'; +import { forkJoin } from 'rxjs'; import { Relationship } from 'src/app/classes/stix/relationship'; import { StixObject } from 'src/app/classes/stix/stix-object'; import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service'; @@ -28,8 +26,7 @@ export class ObjectStatusComponent implements OnInit { @ViewChild("objectStatus", {static: false}) public popover: PopoverContentComponent; public objects: StixObject[]; public object: StixObject; - public relationships; - public dataSourceRelationships: StixObject[]; + public relationships = []; public revoked: boolean = false; public deprecated: boolean = false; @@ -76,51 +73,29 @@ export class ObjectStatusComponent implements OnInit { complete: () => { objSubscription.unsubscribe() } }); - // retrieve relationships with the object - data$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: this.editorService.stixId}); - let relSubscription = data$.subscribe({ - next: (data) => { - this.relationships = data.data as Relationship[]; - if (this.editorService.type !== 'data-source') this.loaded = true; // mark as complete if object is not a data source - setTimeout(() => this.popover.updatePosition()); //after render cycle update popover position since it has new content - }, - complete: () => { relSubscription.unsubscribe() } - }); - if (this.editorService.type == 'data-source') { // retrieve related data components & their relationships - data$ = this.getDataSourceRelationships(); + data$ = this.restAPIService.getAllRelatedToDataSource(this.editorService.stixId); let dataSubscription = data$.subscribe({ next: (results) => { - this.dataSourceRelationships = results; - this.loaded = true; + this.relationships = this.relationships.concat(results); }, complete: () => { dataSubscription.unsubscribe(); } }); } - } - } - public getDataSourceRelationships(): Observable { - let dataComponents$ = this.restAPIService.getAllDataComponents(); - return dataComponents$.pipe( - map(result => { - let dataComponents = result.data as DataComponent[]; - return dataComponents.filter(d => d.data_source_ref == this.editorService.stixId); - }), - mergeMap(dataComponents => { - let relatedTo = dataComponents.map(dc => this.restAPIService.getRelatedTo({sourceOrTargetRef: dc.stixID})); - return forkJoin(relatedTo).pipe( - map(relationships => { - let all_results: StixObject[] = []; - for(let relationship_result of relationships) { - all_results = all_results.concat(relationship_result.data) - } - return all_results.concat(dataComponents);; - }) - ); - }) - ); + // retrieve relationships with the object + data$ = this.restAPIService.getRelatedTo({sourceOrTargetRef: this.editorService.stixId}); + let relSubscription = data$.subscribe({ + next: (data) => { + let relationships = data.data as Relationship[]; + this.relationships = this.relationships.concat(relationships) + this.loaded = true; + setTimeout(() => this.popover.updatePosition()); //after render cycle update popover position since it has new content + }, + complete: () => { relSubscription.unsubscribe() } + }); + } } private save() { @@ -255,13 +230,6 @@ export class ObjectStatusComponent implements OnInit { } } - if (this.editorService.type == 'data-source') { - for (let obj of this.dataSourceRelationships) { - obj.deprecated = true; - saves.push(obj.save(this.restAPIService)); - } - } - if (revoked_by_id) { // create a new 'revoked-by' relationship let revokedRelationship = new Relationship(); diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 1ff0399c..e5940ca8 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -1,8 +1,8 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { forkJoin, Observable, of, ReplaySubject } from 'rxjs'; -import { tap, catchError, map, share, switchMap } from 'rxjs/operators'; +import { forkJoin, Observable, of } from 'rxjs'; +import { tap, catchError, map, share, switchMap, mergeMap } from 'rxjs/operators'; import { CollectionIndex } from 'src/app/classes/collection-index'; import { ExternalReference } from 'src/app/classes/external-references'; import { Collection } from 'src/app/classes/stix/collection'; @@ -924,6 +924,36 @@ export class RestApiConnectorService extends ApiConnector { ) } + /** + * Get all objects related to a data source + * @param id the STIX ID of the data source + * @returns list of data components related to the data source along with the data components' relationships with techniques + */ + public getAllRelatedToDataSource(id: string): Observable { + let dataComponents$ = this.getAllDataComponents(); + return dataComponents$.pipe( + map(result => { // get related data component objects + let dataComponents = result.data as DataComponent[]; + return dataComponents.filter(d => d.data_source_ref == id); + }), + mergeMap(dataComponents => { // get relationships for each data component + let relatedTo = dataComponents.map(dc => this.getRelatedTo({sourceOrTargetRef: dc.stixID})); + if (!relatedTo.length) return of(dataComponents); + return forkJoin(relatedTo).pipe( + map(relationships => { + let all_results: StixObject[] = []; + for(let relationship_result of relationships) { + all_results = all_results.concat(relationship_result.data) + } + return all_results.concat(dataComponents);; + }) + ); + }), + catchError(this.handleError_continue([])), + share() + ); + } + // ___ ___ ___ ___ ___ ___ _ _ ___ ___ ___ // | _ \ __| __| __| _ \ __| \| |/ __| __/ __| // | / _|| _|| _|| / _|| .` | (__| _|\__ \ From bc82cfc327cd4c9077566ce2fcad182039066cab Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:39:16 -0400 Subject: [PATCH 70/89] update data source & data component documentation --- docs/usage.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 696cb2ce..c0147881 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -309,7 +309,13 @@ The software type must be selected when creating it and due to limitations of th #### Editing Data Sources - +Data sources represent relevant information that can be collected by sensors or logs to detect adversary behaviors. Data sources +include data components to provide an additional layer of context and identify the specific properties of a data source +that are relevant to detecting an ATT&CK technique or sub-technique. + +Data sources support the standard set of fields and also define collection layers, which are a description of where the data source may be +physically collected. After a data component has been added to a data source, the user can create relationships with techniques in the +data component dialog window. ##### Data Source Relationships @@ -322,6 +328,7 @@ The software type must be selected when creating it and due to limitations of th | Relationship Section | Description | |:-----|:----| | Techniques Detected | Techniques detected by the data component | + #### Editing Relationships Relationships map objects to other objects. Relationships have types, sources, and targets. The source and targets define the objects connected by the relationship, and the type is a verb describing the nature of their relationship. From 765339a9e402d4151d53f917e23ec68b109f62a7 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 13 Oct 2021 16:52:23 -0400 Subject: [PATCH 71/89] minor changes for sonar cloud quality --- .../stix/stix-list/stix-list.component.ts | 38 ++++++------------- .../rest-api/rest-api-connector.service.ts | 2 +- 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/app/src/app/components/stix/stix-list/stix-list.component.ts b/app/src/app/components/stix/stix-list/stix-list.component.ts index 62b0c609..48433bda 100644 --- a/app/src/app/components/stix/stix-list/stix-list.component.ts +++ b/app/src/app/components/stix/stix-list/stix-list.component.ts @@ -256,6 +256,7 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { "display": "descriptive" }] break; + case "data-source": case "technique": this.addColumn("", "workflow", "icon"); this.addColumn("", "state", "icon"); @@ -271,6 +272,17 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { "display": "descriptive" }] break; + case "data-component": + this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); + this.addColumn("domain", "domains", "list"); + this.addColumn("version", "version", "version"); + this.addColumn("modified","modified", "timestamp"); + this.addColumn("created", "created", "timestamp"); + this.tableDetail = [{ + "field": "description", + "display": "descriptive" + }] + break; case "relationship": this.addColumn("", "state", "icon"); if (this.config.relationshipType && this.config.relationshipType !== "detects") { @@ -283,32 +295,6 @@ export class StixListComponent implements OnInit, AfterViewInit, OnDestroy { if (!(this.config.relationshipType && this.config.relationshipType == "subtechnique-of")) this.addColumn("description", "description", "descriptive", false); // controls_after.push("open-link") break; - case "data-source": - this.addColumn("", "workflow", "icon"); - this.addColumn("", "state", "icon"); - this.addColumn("ID", "attackID", "plain", false); - this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); - this.addColumn("platforms", "platforms", "list"); - this.addColumn("domain", "domains", "list"); - this.addColumn("version", "version", "version"); - this.addColumn("modified","modified", "timestamp"); - this.addColumn("created", "created", "timestamp"); - this.tableDetail = [{ - "field": "description", - "display": "descriptive" - }] - break; - case "data-component": - this.addColumn("name", "name", "plain", sticky_allowed, ["name"]); - this.addColumn("domain", "domains", "list"); - this.addColumn("version", "version", "version"); - this.addColumn("modified","modified", "timestamp"); - this.addColumn("created", "created", "timestamp"); - this.tableDetail = [{ - "field": "description", - "display": "descriptive" - }] - break; default: this.addColumn("type", "attackType", "plain"); this.addColumn("modified","modified", "timestamp"); diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index e5940ca8..db36f999 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -945,7 +945,7 @@ export class RestApiConnectorService extends ApiConnector { for(let relationship_result of relationships) { all_results = all_results.concat(relationship_result.data) } - return all_results.concat(dataComponents);; + return all_results.concat(dataComponents); }) ); }), From a30b54f9d2285d623de1e3d559c0cbd4d488eccb Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 14 Oct 2021 11:37:19 -0400 Subject: [PATCH 72/89] update changelog, collections and integrations --- .gitignore | 1 + docs/changelog.md | 16 ++++++++---- docs/collections.md | 6 +++-- docs/integrations.md | 61 +++++++++++++++++++++++++++++--------------- 4 files changed, 57 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index a097d388..66ddf947 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,4 @@ Temporary Items # End of https://www.toptal.com/developers/gitignore/api/macos *.env +.vscode \ No newline at end of file diff --git a/docs/changelog.md b/docs/changelog.md index d316976f..ea9e56df 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,15 +33,21 @@ # Changelog ## Changes staged on develop -### ATT&CK Workbench version 1.0.3 -#### Improvements in 1.0.3 +### ATT&CK Workbench version 1.1.0 +ATT&CK Workbench v1.1.0 includes support for ATT&CK Spec v2.1.0 and coincides with the ATT&CK v10.0 release. Users who do not upgrade to Workbench v1.1.0 may encounter issues with the new ATT&CK data: +- If the user added the ATT&CK collection index prior to the ATT&CK v10.0 release, it may lose track of imported Enterprise collections. These collections can still be found in the "imported collections" tab of the collection manager, but won't be reflected in the collection manager. Collection subscriptions for Enterprise may also be lost. +- If the user imports ATT&CK v10.0, data sources and data components will not be imported into their local knowlegeb + +#### Improvements in 1.1.0 - Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). -- Improved the visibility of errors in collection imports: +- Improved the flexibility and robustness of collection imports: + - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. + - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. + - Workbench can now import STIX bundles that don't contain a collection object. This is not recommended however since the results of such an import (the list of imported objects) will not be recorded for future reference. - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - REST API will now log import errors to the console. - Frontend will now log import errors to the console when the application environment is not set to production. - -#### Fixes in 1.0.3 +#### Fixes in 1.1.0 - Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. - Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. - Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). diff --git a/docs/collections.md b/docs/collections.md index 167e7b7e..97c3e974 100644 --- a/docs/collections.md +++ b/docs/collections.md @@ -36,6 +36,7 @@ Collections are represented in STIX using the `x-mitre-collection` type, describ | **modified** (required)| `timestamp` | Represents the time at which the collection was most recently modified. | | **x_mitre_version** (required) | `string` | The version of the collection object, which must follow the MAJOR.MINOR.PATCH pattern. | | **spec_version** (required) | `string` | The version of the STIX specification used to represent the object. This value MUST be `2.1`. +| **x_mitre_attack_spec_version** (required) | `string` | The version of the ATT&CK spec used to represent the object. More information on the ATT&CK spec and the current ATT&CK Spec version can be found [on the attack-stix-data GitHub repository](https://github.com/mitre-attack/attack-stix-data/blob/master/USAGE.md). | **created_by_ref** (required) | `string` | identifier | Specifies the **id** property of the `identity` object that describes the entity that created this collection. | | **object_marking_refs** (required) | `list` of type `identifier` | Specifies a list of **id** properties of `marking-definition` objects that apply to this object. Typically used for copyright statements. | | **x_mitre_contents** (required) | `list` of type _object version reference_ | Specifies the objects contained within the collection. See the _object version reference_ type below. | @@ -52,9 +53,10 @@ Object version references are used to refer to a specific version of a STIX obje ### Collection Example ```json { - "id": "x-mitre-collection--23320f4-22ad-8467-3b73-ed0c869a12838", + "id": "x-mitre-collection--402e24b4-436e-4936-b19b-2038648f489", "type": "x-mitre-collection", "spec_version": "2.1", + "x_mitre_attack_spec_version": "2.1.0", "name": "Enterprise ATT&CK", "x_mitre_version": "6.2", "description": "Version 6.2 of the Enterprise ATT&CK dataset", @@ -148,7 +150,7 @@ Collection version objects describe specific versions of collections within a _c "modified": "2019-07-17T20:04:40.297Z", "collections": [ { - "id": "x-mitre-collection--23320f4-22ad-8467-3b73-ed0c869a12838", + "id": "x-mitre-collection--402e24b4-436e-4936-b19b-2038648f489", "name": "Enterprise ATT&CK", "description": "The Enterprise domain of the ATT&CK dataset", "created": "2019-07-31T00:00:00.000Z", diff --git a/docs/integrations.md b/docs/integrations.md index c79697ee..b366eaa7 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -2,36 +2,58 @@ The ATT&CK Workbench is designed to integrate with a variety of tools. This page provides documentation on how to set up such integrations. -## ATT&CK Navigator +## Workbench REST API URLs -The [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) can be configured to display the contents of your local knowledge base. +Depending on whether you are using the manual install or the docker install, and where Workbench has been deployed, the URLs you will be setting in your integrations will be different. -### 1. Install the Navigator +The following API routes are used with integrations to access STIX bundles representing the local knowledge base: + +| Domain | API Route | +| :--------- | :-------------------------------------------- | +| Enterprise | `/api/stix-bundles/?domain=enterprise-attack` | +| Mobile | `/api/stix-bundles/?domain=mobile-attack` | +| ICS | `/api/stix-bundles/?domain=ics-attack` | + +The host the routes are available at depends on how the Workbench has been installed and deployed: + +| Deployment Type | Installation Type | Host (hostname:port) | +| :-------------- | :---------------- | :----------------------- | +| local | manual | `http://localhost:3000` | +| local | docker | `http://localhost` | +| remote | manual | `{remote-hostname}:3000` | +| remote | docker | `{remote-hostname}` | + +For example, the enterprise STIX bundle of a manual installation running locally would be available at `http://localhost:3000/api/stix-bundles/?domain=enterprise-attack`. You can test the URL by opening it in your web browser while the Workbench is running - if it is correct you should see a JSON object as a response. + +## ATT&CK Navigator Integration +The [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) can be configured to display the contents of your local knowledge base. + +### 1. Install the Navigator Clone the [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) repository: `git clone https://github.com/mitre-attack/attack-navigator.git` ### 2. Update the config -Edit the config file `nav-app/src/assets/config.json` by prepending a new object to the versions array connecting to the ATT&CK Workbench REST API. +Edit the config file `nav-app/src/assets/config.json` by prepending a new object to the versions array connecting to the ATT&CK Workbench REST API. Refer to [Workbench REST API URLs, above](#workbench-rest-api-urls), for the values for the enterprise, mobile and ICS URLs. ```json { "versions": [ { - "name": "ATT&CK Workbench Data", + "name": "ATT&CK Workbench Data", "domains": [ - { - "name": "Enterprise", - "data": ["http://localhost:3000/api/stix-bundles/?domain=enterprise-attack"] + { + "name": "Enterprise", + "data": ["(Enterprise URL)"] }, - { - "name": "Mobile", - "data": ["http://localhost:3000/api/stix-bundles/?domain=mobile-attack"] + { + "name": "Mobile", + "data": ["(Mobile URL)"] }, { "name": "ICS", - "data": ["http://localhost:3000/api/stix-bundles/?domain=ics-attack"] + "data": ["(ICS URL)"] } ] } @@ -43,8 +65,9 @@ Edit the config file `nav-app/src/assets/config.json` by prepending a new object Follow the [install and run](https://github.com/mitre-attack/attack-navigator#install-and-run) instructions on the ATT&CK Navigator to deploy the application. The Navigator will update its data from the Workbench every time it loads, so there is no need to periodically rebuild the application to stay synchronized with Workbench data. -## ATT&CK Website -The [ATT&CK Website](https://github.com/mitre-attack/attack-website) can be configured to display the contents of your local knowledge base. +## ATT&CK Website Integration + +The [ATT&CK Website](https://github.com/mitre-attack/attack-website) can be configured to display the contents of your local knowledge base. ### 1. Install the Website @@ -52,19 +75,19 @@ Clone the [ATT&CK Website](https://github.com/mitre-attack/attack-website) repos ### 2. Update Config -Edit the domain URLs in the config file `modules/site_config.py` as shown below to connect to the Workbench REST API. +Edit the domain URLs in the config file `modules/site_config.py` as shown below to connect to the Workbench REST API. Refer to [Workbench REST API URLs, above](#workbench-rest-api-urls), for the values for the enterprise and mobile URLs. ```python domains = [ { "name" : "enterprise-attack", - "location" : "http://localhost:3000/api/stix-bundles/?domain=enterprise-attack", + "location" : "(Enterprise URL)", "alias" : "Enterprise", "deprecated" : False }, { "name" : "mobile-attack", - "location" : "http://localhost:3000/api/stix-bundles/?domain=mobile-attack", + "location" : "(Mobile URL)", "alias" : "Mobile", "deprecated" : False }, @@ -77,11 +100,9 @@ domains = [ ] ``` -Depending on your deployment of the Workbench the REST API URL may be different. See the environment files, `src/environments/`, for the currently configured REST API URL. - ### 3. Build the site -Run `python3 update-attack.py` to build the website using the current Workbench data. +Run `python3 update-attack.py` to build the website using the current Workbench data. ### 4. Serve the site From 20f7626e7ca7f9ef1d05fa3b4dcd76235b98b795 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 14 Oct 2021 11:44:36 -0400 Subject: [PATCH 73/89] update changelog explanation of data sources import issue --- docs/changelog.md | 182 +++++++++++++++++++++++++++------------------- 1 file changed, 109 insertions(+), 73 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index ea9e56df..4a414a53 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,125 +33,161 @@ # Changelog ## Changes staged on develop + ### ATT&CK Workbench version 1.1.0 + ATT&CK Workbench v1.1.0 includes support for ATT&CK Spec v2.1.0 and coincides with the ATT&CK v10.0 release. Users who do not upgrade to Workbench v1.1.0 may encounter issues with the new ATT&CK data: -- If the user added the ATT&CK collection index prior to the ATT&CK v10.0 release, it may lose track of imported Enterprise collections. These collections can still be found in the "imported collections" tab of the collection manager, but won't be reflected in the collection manager. Collection subscriptions for Enterprise may also be lost. -- If the user imports ATT&CK v10.0, data sources and data components will not be imported into their local knowlegeb + +- If the user added the ATT&CK collection index prior to the ATT&CK v10.0 release, it may lose track of imported Enterprise collections. These collections can still be found in the "imported collections" tab of the collection manager, but won't be reflected in the collection manager. Collection subscriptions for Enterprise may also be lost. +- If the user imports ATT&CK v10.0, data sources and data components will not be imported into their local knowledge base. You can re-import the collection after upgrading workbench to v1.1.0 to acquire the data sources and data components even if you had already imported it when running a prior version of Workbench. #### Improvements in 1.1.0 -- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). -- Improved the flexibility and robustness of collection imports: - - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. - - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. - - Workbench can now import STIX bundles that don't contain a collection object. This is not recommended however since the results of such an import (the list of imported objects) will not be recorded for future reference. - - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - - REST API will now log import errors to the console. - - Frontend will now log import errors to the console when the application environment is not set to production. + +- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Improved the flexibility and robustness of collection imports: + - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. + - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. + - Workbench can now import STIX bundles that don't contain a collection object. This is not recommended however since the results of such an import (the list of imported objects) will not be recorded for future reference. + - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. + - REST API will now log import errors to the console. + - Frontend will now log import errors to the console when the application environment is not set to production. + #### Fixes in 1.1.0 -- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. -- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. -- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). + +- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. +- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. +- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). + ## 20 August 2021 + ### ATT&CK Workbench version 1.0.2 + #### Fixes in 1.0.2 -- Error snackbars will now show appropriate messages instead of `[object ProgressEvent]` when communication with the REST API is interrupted or cannot be established. See [frontend#227](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/227). -- Fixed a bug where tactic shortnames were computed incorrectly for tactics with more than one space in the name (E.g `"Command and Control"`). See [frontend#239](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/239). - - If you have edited a technique under a tactic with more than one space in the name, remove and re-add the tactic under the technique edit interface to ensure that the tactic reference is formatted properly. - - If you have created a tactic with more than one space in the name, save a new version of the tactic and the proper shortname should be saved. You do not need to make any edits when saving the tactic page for the shortname to be fixed. + +- Error snackbars will now show appropriate messages instead of `[object ProgressEvent]` when communication with the REST API is interrupted or cannot be established. See [frontend#227](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/227). +- Fixed a bug where tactic shortnames were computed incorrectly for tactics with more than one space in the name (E.g `"Command and Control"`). See [frontend#239](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/239). + - If you have edited a technique under a tactic with more than one space in the name, remove and re-add the tactic under the technique edit interface to ensure that the tactic reference is formatted properly. + - If you have created a tactic with more than one space in the name, save a new version of the tactic and the proper shortname should be saved. You do not need to make any edits when saving the tactic page for the shortname to be fixed. ## 8 July 2021 + ### ATT&CK Workbench version 1.0.1 + #### Improvements in 1.0.1 -- Added a system for configuring the Collection Manager with self-signed certs when using the docker setup. Documentation for this configuration will be improved in a subsequent release. + +- Added a system for configuring the Collection Manager with self-signed certs when using the docker setup. Documentation for this configuration will be improved in a subsequent release. #### Fixes in 1.0.1 -- Fixed an error encountered when using the `attack-objects` API with large datasets. This error was preventing users from loading the "create a collection" page when Enterprise ATT&CK collections were imported. See [rest-api#87](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/87). + +- Fixed an error encountered when using the `attack-objects` API with large datasets. This error was preventing users from loading the "create a collection" page when Enterprise ATT&CK collections were imported. See [rest-api#87](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/87). ## 21 June 2021 + ### ATT&CK Workbench version 1.0.0 + #### Improvements in 1.0.0 -- Performance improvements when adding, editing, and validating relationships. -- Improved error messages when importing collections that are too large or malformed. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198). -- Improved page titles and breadcrumb on "object not found" pages. -- User can now import collections from file. See [frontend#207](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/207). -- Collection index update interval is now set in the REST API configuration instead of hardcoded in the frontend. See [frontend#200](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/200). + +- Performance improvements when adding, editing, and validating relationships. +- Improved error messages when importing collections that are too large or malformed. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198). +- Improved page titles and breadcrumb on "object not found" pages. +- User can now import collections from file. See [frontend#207](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/207). +- Collection index update interval is now set in the REST API configuration instead of hardcoded in the frontend. See [frontend#200](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/200). #### Fixes in 1.0.0 -- Fixed vertically misaligned timestamps across several UIs. -- Fixed missing timestamp on collection version lists within collection indexes. -- Fixed object status popover showing the wrong status if opened too soon after the page loads. Also improved performance of the status popover code. -- Collection import UI no longer gets stuck if it runs into a problem fetching/importing/previewing the collection. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198) -- Object status popover now closes properly when the user starts editing the object. See [frontend#199](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/199). + +- Fixed vertically misaligned timestamps across several UIs. +- Fixed missing timestamp on collection version lists within collection indexes. +- Fixed object status popover showing the wrong status if opened too soon after the page loads. Also improved performance of the status popover code. +- Collection import UI no longer gets stuck if it runs into a problem fetching/importing/previewing the collection. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198) +- Object status popover now closes properly when the user starts editing the object. See [frontend#199](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/199). ## 7 May 2021 + ### ATT&CK Workbench version 0.4.0 + #### Improvements in 0.4.0 -- Added a favicon. See [frontend#137](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/137). -- Added dynamic page title to make it easier to distinguish multiple Workbench tabs in the browser. See [frontend#130](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/130). -- Added a list of recommended indexes available when adding a collection index. See [frontend#194](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/194). -- Added ability to set workflow state when objects are saved. See [frontend#184](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/184). -- Updated occurrences of "aliases" to "associated groups" or "associated software" for consistency across the application. See [frontend#176](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/176). -- Improved logging and added log level to environment configuration to suppress unnecessary logs from production deployments. See [frontend#209](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/209). -- Updated the reference editor to enforce correct formatting when creating a new reference. See [frontend#177](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/177). + +- Added a favicon. See [frontend#137](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/137). +- Added dynamic page title to make it easier to distinguish multiple Workbench tabs in the browser. See [frontend#130](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/130). +- Added a list of recommended indexes available when adding a collection index. See [frontend#194](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/194). +- Added ability to set workflow state when objects are saved. See [frontend#184](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/184). +- Updated occurrences of "aliases" to "associated groups" or "associated software" for consistency across the application. See [frontend#176](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/176). +- Improved logging and added log level to environment configuration to suppress unnecessary logs from production deployments. See [frontend#209](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/209). +- Updated the reference editor to enforce correct formatting when creating a new reference. See [frontend#177](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/177). ## 21 April 2021 + ### ATT&CK Workbench version 0.3.0 + #### New Features in 0.3.0 -- Added attribution of edits and tracking of organization identity. See [frontend#61](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124) and [frontend#182](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/61). -- Added ability to revoke and deprecate objects. See [frontend#164](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/164). -- Added tracking of workflow state. See [frontend#3](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/3). -- Added ability to create and edit collections. See [frontend#4](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/4), [frontend#5](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/5), and [frontend#112](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/112). -- Added support and documentation for [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) integration. See [frontend#153](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/153). -- Added support and documentation for [ATT&CK Website](https://github.com/mitre-attack/attack-website/) integration. See [frontend#152](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/152). + +- Added attribution of edits and tracking of organization identity. See [frontend#61](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124) and [frontend#182](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/61). +- Added ability to revoke and deprecate objects. See [frontend#164](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/164). +- Added tracking of workflow state. See [frontend#3](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/3). +- Added ability to create and edit collections. See [frontend#4](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/4), [frontend#5](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/5), and [frontend#112](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/112). +- Added support and documentation for [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) integration. See [frontend#153](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/153). +- Added support and documentation for [ATT&CK Website](https://github.com/mitre-attack/attack-website/) integration. See [frontend#152](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/152). #### Improvements in 0.3.0 -- Improved display of object domains. See [frontend#166](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/166). + +- Improved display of object domains. See [frontend#166](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/166). ## 19 March 2021 ### ATT&CK Workbench version 0.2.0 + #### New Features in 0.2.0 -- Added support for MTC and CAPEC IDs. See [frontend#124](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124). -- Added ability to create and edit objects. See [frontend#44](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/44) and [frontend#145](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/145). - - Added ability to edit group/software aliases. See [frontend#118](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/118). - - Added ability to edit various list properties such as platforms, tactics, and domains. See [frontend#31](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/31). - - Added rich-text description editor. See [frontend#32](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/32). - - Added ability to convert techniques to sub-techniques, and vice versa. - - Added ability to edit ATT&CK IDs. See [frontend#55](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/55). - - Added validation system to warn user of malformed data. - - Added ability to reorder tactics on matrices. See [frontend#116](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/116). - - Added ability to edit object version numbers, and a UI for incrementing versions when objects are saved. See [frontend#56](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/56). -- Added ability to create and edit notes (annotations) on objects. See [frontend#59](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/59). -- Added citations/references support. - - Added automatic detection of citations on descriptions and aliases. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115). - - Added references manager tool. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115) and [frontend#133](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/133). + +- Added support for MTC and CAPEC IDs. See [frontend#124](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124). +- Added ability to create and edit objects. See [frontend#44](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/44) and [frontend#145](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/145). + - Added ability to edit group/software aliases. See [frontend#118](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/118). + - Added ability to edit various list properties such as platforms, tactics, and domains. See [frontend#31](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/31). + - Added rich-text description editor. See [frontend#32](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/32). + - Added ability to convert techniques to sub-techniques, and vice versa. + - Added ability to edit ATT&CK IDs. See [frontend#55](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/55). + - Added validation system to warn user of malformed data. + - Added ability to reorder tactics on matrices. See [frontend#116](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/116). + - Added ability to edit object version numbers, and a UI for incrementing versions when objects are saved. See [frontend#56](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/56). +- Added ability to create and edit notes (annotations) on objects. See [frontend#59](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/59). +- Added citations/references support. + - Added automatic detection of citations on descriptions and aliases. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115). + - Added references manager tool. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115) and [frontend#133](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/133). #### Improvements in 0.2.0 -- Lists of objects can now be searched and filtered. See [frontend#128](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/128) and [frontend#127](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/127). -- Lists of objects now display ATT&CK IDs when relevant. See [frontend#119](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/119). -- When viewing an object, fields which have no value(s) will now be hidden. See [frontend#120](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/120). -- Improved display of sub-techniques. See [frontend#125](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/125). -- Layout and formatting improvements to [USAGE](/docs/usage.md) document + +- Lists of objects can now be searched and filtered. See [frontend#128](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/128) and [frontend#127](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/127). +- Lists of objects now display ATT&CK IDs when relevant. See [frontend#119](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/119). +- When viewing an object, fields which have no value(s) will now be hidden. See [frontend#120](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/120). +- Improved display of sub-techniques. See [frontend#125](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/125). +- Layout and formatting improvements to [USAGE](/docs/usage.md) document #### Fixes in 0.2.0 -- Fixed broken pagination on relationship tables. See [frontend#126](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/126). + +- Fixed broken pagination on relationship tables. See [frontend#126](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/126). ## 16 February 2021 + ### ATT&CK Workbench version 0.1.1 + #### New Features in 0.1.1 -- Added Dockerfiles, docker-compose, [and documentation on how to use them](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/tree/master/docs/docker-compose.md). See [frontend#108](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/108), [frontend#109](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/109) [rest-api#14](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/14), and [collection-manager#13](https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager/issues/13). + +- Added Dockerfiles, docker-compose, [and documentation on how to use them](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/tree/master/docs/docker-compose.md). See [frontend#108](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/108), [frontend#109](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/109) [rest-api#14](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/14), and [collection-manager#13](https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager/issues/13). #### Fixes in 0.1.1 -- Fixed a crash that could occur with specific queries on the REST API. See [rest-api#28](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/28). + +- Fixed a crash that could occur with specific queries on the REST API. See [rest-api#28](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/28). ## 19 January 2021 + ### ATT&CK Workbench version 0.1.0 + #### New Features in 0.1.0 -- Created object view pages for matrix, technique, tactic, mitigation, group, and software objects. -- Added the ability to browse and import collection indexes. - - Collection indexes can be imported via URL. - - A preview of the collection index is shown before confirming the import. -- Added the ability to import, view, and subscribe to collections. - - Collections listed within an index can be subscribed to, which will pull new versions when they are published. - - Collections can also be manually imported via URL. When importing, a preview of the collection and its contents is shown before confirming the import. At this step, users can preview the objects in the collection and select which ones they want to import. Changes in the import are displayed relative to the state of the knowledge base similar to the update pages on the [ATT&CK Website](https://attack.mitre.org/resources/updates/). - - An interface provides the ability to review prior imports, which provides a list of changes at the time of the import identical to that shown during the import of the collection. \ No newline at end of file + +- Created object view pages for matrix, technique, tactic, mitigation, group, and software objects. +- Added the ability to browse and import collection indexes. + - Collection indexes can be imported via URL. + - A preview of the collection index is shown before confirming the import. +- Added the ability to import, view, and subscribe to collections. + - Collections listed within an index can be subscribed to, which will pull new versions when they are published. + - Collections can also be manually imported via URL. When importing, a preview of the collection and its contents is shown before confirming the import. At this step, users can preview the objects in the collection and select which ones they want to import. Changes in the import are displayed relative to the state of the knowledge base similar to the update pages on the [ATT&CK Website](https://attack.mitre.org/resources/updates/). + - An interface provides the ability to review prior imports, which provides a list of changes at the time of the import identical to that shown during the import of the collection. From 86469a3b3ed7164e7e573112cfcae14b2340a9cb Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 14 Oct 2021 11:53:59 -0400 Subject: [PATCH 74/89] update changelog --- docs/changelog.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 4a414a53..8310140f 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -38,12 +38,17 @@ ATT&CK Workbench v1.1.0 includes support for ATT&CK Spec v2.1.0 and coincides with the ATT&CK v10.0 release. Users who do not upgrade to Workbench v1.1.0 may encounter issues with the new ATT&CK data: -- If the user added the ATT&CK collection index prior to the ATT&CK v10.0 release, it may lose track of imported Enterprise collections. These collections can still be found in the "imported collections" tab of the collection manager, but won't be reflected in the collection manager. Collection subscriptions for Enterprise may also be lost. -- If the user imports ATT&CK v10.0, data sources and data components will not be imported into their local knowledge base. You can re-import the collection after upgrading workbench to v1.1.0 to acquire the data sources and data components even if you had already imported it when running a prior version of Workbench. +- If the user added the ATT&CK collection index prior to the ATT&CK v10.0 release, it may lose track of imported Enterprise collections. These collections can still be found in the "imported collections" tab of the collection manager, but won't be reflected in the collection manager. Collection subscriptions for Enterprise may also be lost. Upgrading to ATT&CK Workbench v1.1.0 will fix this issue and restore prior collection subscriptions. +- If the user imports ATT&CK v10.0 using ATT&CK Workbench 1.0.X, data sources and data components will not be imported into their local knowledge base. You can re-import the collection after upgrading Workbench to v1.1.0 to acquire the data sources and data components even if you had already imported it when running a prior version of Workbench. + +ATT&CK Workbench version 1.1.0 includes improvements to how data is imported which should circumvent the above issues for future releases of ATT&CK. #### Improvements in 1.1.0 - Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Added support for ATT&CK Spec v2.1.0: + - Added support for data sources and data components, and viewing/editing interfaces for these object types and their relationships with techniques. + - Added support for `x_mitre_attack_spec_version` on all object types. - Improved the flexibility and robustness of collection imports: - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. From c9c5d1201f9a48a99a9c633dda687ab0d626c0e2 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Fri, 15 Oct 2021 10:17:24 -0400 Subject: [PATCH 75/89] patch changelog --- docs/changelog.md | 172 +++++++++++++++++++++++++++------------------- 1 file changed, 103 insertions(+), 69 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index c0a41cb6..0513e7c4 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -33,120 +33,154 @@ # Changelog ## Changes staged on develop + ### ATT&CK Workbench version 1.0.3 + #### Improvements in 1.0.3 -- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). -- Improved the visibility of errors in collection imports: - - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - - REST API will now log import errors to the console. - - Frontend will now log import errors to the console when the application environment is not set to production. -- Added validation for missing ATT&CK IDs on objects that support them. `StixObject` class now includes boolean field (`supportsAttackID`) that specifies whether they support ATT&CK IDs or not. Inheriting classes must specify this field. Validation for missing ATT&CK IDs was also added when creating a collection. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). + +- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Improved the visibility of errors in collection imports: + - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. + - REST API will now log import errors to the console. + - Frontend will now log import errors to the console when the application environment is not set to production. +- Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). #### Fixes in 1.0.3 -- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. -- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. -- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). + +- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. +- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. +- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). + ## 20 August 2021 + ### ATT&CK Workbench version 1.0.2 + #### Fixes in 1.0.2 -- Error snackbars will now show appropriate messages instead of `[object ProgressEvent]` when communication with the REST API is interrupted or cannot be established. See [frontend#227](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/227). -- Fixed a bug where tactic shortnames were computed incorrectly for tactics with more than one space in the name (E.g `"Command and Control"`). See [frontend#239](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/239). - - If you have edited a technique under a tactic with more than one space in the name, remove and re-add the tactic under the technique edit interface to ensure that the tactic reference is formatted properly. - - If you have created a tactic with more than one space in the name, save a new version of the tactic and the proper shortname should be saved. You do not need to make any edits when saving the tactic page for the shortname to be fixed. + +- Error snackbars will now show appropriate messages instead of `[object ProgressEvent]` when communication with the REST API is interrupted or cannot be established. See [frontend#227](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/227). +- Fixed a bug where tactic shortnames were computed incorrectly for tactics with more than one space in the name (E.g `"Command and Control"`). See [frontend#239](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/239). + - If you have edited a technique under a tactic with more than one space in the name, remove and re-add the tactic under the technique edit interface to ensure that the tactic reference is formatted properly. + - If you have created a tactic with more than one space in the name, save a new version of the tactic and the proper shortname should be saved. You do not need to make any edits when saving the tactic page for the shortname to be fixed. ## 8 July 2021 + ### ATT&CK Workbench version 1.0.1 + #### Improvements in 1.0.1 -- Added a system for configuring the Collection Manager with self-signed certs when using the docker setup. Documentation for this configuration will be improved in a subsequent release. + +- Added a system for configuring the Collection Manager with self-signed certs when using the docker setup. Documentation for this configuration will be improved in a subsequent release. #### Fixes in 1.0.1 -- Fixed an error encountered when using the `attack-objects` API with large datasets. This error was preventing users from loading the "create a collection" page when Enterprise ATT&CK collections were imported. See [rest-api#87](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/87). + +- Fixed an error encountered when using the `attack-objects` API with large datasets. This error was preventing users from loading the "create a collection" page when Enterprise ATT&CK collections were imported. See [rest-api#87](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/87). ## 21 June 2021 + ### ATT&CK Workbench version 1.0.0 + #### Improvements in 1.0.0 -- Performance improvements when adding, editing, and validating relationships. -- Improved error messages when importing collections that are too large or malformed. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198). -- Improved page titles and breadcrumb on "object not found" pages. -- User can now import collections from file. See [frontend#207](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/207). -- Collection index update interval is now set in the REST API configuration instead of hardcoded in the frontend. See [frontend#200](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/200). + +- Performance improvements when adding, editing, and validating relationships. +- Improved error messages when importing collections that are too large or malformed. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198). +- Improved page titles and breadcrumb on "object not found" pages. +- User can now import collections from file. See [frontend#207](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/207). +- Collection index update interval is now set in the REST API configuration instead of hardcoded in the frontend. See [frontend#200](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/200). #### Fixes in 1.0.0 -- Fixed vertically misaligned timestamps across several UIs. -- Fixed missing timestamp on collection version lists within collection indexes. -- Fixed object status popover showing the wrong status if opened too soon after the page loads. Also improved performance of the status popover code. -- Collection import UI no longer gets stuck if it runs into a problem fetching/importing/previewing the collection. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198) -- Object status popover now closes properly when the user starts editing the object. See [frontend#199](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/199). + +- Fixed vertically misaligned timestamps across several UIs. +- Fixed missing timestamp on collection version lists within collection indexes. +- Fixed object status popover showing the wrong status if opened too soon after the page loads. Also improved performance of the status popover code. +- Collection import UI no longer gets stuck if it runs into a problem fetching/importing/previewing the collection. See [frontend#198](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/198) +- Object status popover now closes properly when the user starts editing the object. See [frontend#199](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/199). ## 7 May 2021 + ### ATT&CK Workbench version 0.4.0 + #### Improvements in 0.4.0 -- Added a favicon. See [frontend#137](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/137). -- Added dynamic page title to make it easier to distinguish multiple Workbench tabs in the browser. See [frontend#130](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/130). -- Added a list of recommended indexes available when adding a collection index. See [frontend#194](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/194). -- Added ability to set workflow state when objects are saved. See [frontend#184](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/184). -- Updated occurrences of "aliases" to "associated groups" or "associated software" for consistency across the application. See [frontend#176](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/176). -- Improved logging and added log level to environment configuration to suppress unnecessary logs from production deployments. See [frontend#209](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/209). -- Updated the reference editor to enforce correct formatting when creating a new reference. See [frontend#177](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/177). + +- Added a favicon. See [frontend#137](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/137). +- Added dynamic page title to make it easier to distinguish multiple Workbench tabs in the browser. See [frontend#130](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/130). +- Added a list of recommended indexes available when adding a collection index. See [frontend#194](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/194). +- Added ability to set workflow state when objects are saved. See [frontend#184](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/184). +- Updated occurrences of "aliases" to "associated groups" or "associated software" for consistency across the application. See [frontend#176](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/176). +- Improved logging and added log level to environment configuration to suppress unnecessary logs from production deployments. See [frontend#209](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/209). +- Updated the reference editor to enforce correct formatting when creating a new reference. See [frontend#177](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/177). ## 21 April 2021 + ### ATT&CK Workbench version 0.3.0 + #### New Features in 0.3.0 -- Added attribution of edits and tracking of organization identity. See [frontend#61](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124) and [frontend#182](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/61). -- Added ability to revoke and deprecate objects. See [frontend#164](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/164). -- Added tracking of workflow state. See [frontend#3](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/3). -- Added ability to create and edit collections. See [frontend#4](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/4), [frontend#5](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/5), and [frontend#112](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/112). -- Added support and documentation for [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) integration. See [frontend#153](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/153). -- Added support and documentation for [ATT&CK Website](https://github.com/mitre-attack/attack-website/) integration. See [frontend#152](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/152). + +- Added attribution of edits and tracking of organization identity. See [frontend#61](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124) and [frontend#182](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/61). +- Added ability to revoke and deprecate objects. See [frontend#164](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/164). +- Added tracking of workflow state. See [frontend#3](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/3). +- Added ability to create and edit collections. See [frontend#4](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/4), [frontend#5](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/5), and [frontend#112](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/112). +- Added support and documentation for [ATT&CK Navigator](https://github.com/mitre-attack/attack-navigator) integration. See [frontend#153](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/153). +- Added support and documentation for [ATT&CK Website](https://github.com/mitre-attack/attack-website/) integration. See [frontend#152](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/152). #### Improvements in 0.3.0 -- Improved display of object domains. See [frontend#166](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/166). + +- Improved display of object domains. See [frontend#166](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/166). ## 19 March 2021 ### ATT&CK Workbench version 0.2.0 + #### New Features in 0.2.0 -- Added support for MTC and CAPEC IDs. See [frontend#124](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124). -- Added ability to create and edit objects. See [frontend#44](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/44) and [frontend#145](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/145). - - Added ability to edit group/software aliases. See [frontend#118](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/118). - - Added ability to edit various list properties such as platforms, tactics, and domains. See [frontend#31](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/31). - - Added rich-text description editor. See [frontend#32](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/32). - - Added ability to convert techniques to sub-techniques, and vice versa. - - Added ability to edit ATT&CK IDs. See [frontend#55](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/55). - - Added validation system to warn user of malformed data. - - Added ability to reorder tactics on matrices. See [frontend#116](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/116). - - Added ability to edit object version numbers, and a UI for incrementing versions when objects are saved. See [frontend#56](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/56). -- Added ability to create and edit notes (annotations) on objects. See [frontend#59](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/59). -- Added citations/references support. - - Added automatic detection of citations on descriptions and aliases. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115). - - Added references manager tool. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115) and [frontend#133](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/133). + +- Added support for MTC and CAPEC IDs. See [frontend#124](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/124). +- Added ability to create and edit objects. See [frontend#44](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/44) and [frontend#145](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/145). + - Added ability to edit group/software aliases. See [frontend#118](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/118). + - Added ability to edit various list properties such as platforms, tactics, and domains. See [frontend#31](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/31). + - Added rich-text description editor. See [frontend#32](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/32). + - Added ability to convert techniques to sub-techniques, and vice versa. + - Added ability to edit ATT&CK IDs. See [frontend#55](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/55). + - Added validation system to warn user of malformed data. + - Added ability to reorder tactics on matrices. See [frontend#116](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/116). + - Added ability to edit object version numbers, and a UI for incrementing versions when objects are saved. See [frontend#56](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/56). +- Added ability to create and edit notes (annotations) on objects. See [frontend#59](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/59). +- Added citations/references support. + - Added automatic detection of citations on descriptions and aliases. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115). + - Added references manager tool. See [frontend#115](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/115) and [frontend#133](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/133). #### Improvements in 0.2.0 -- Lists of objects can now be searched and filtered. See [frontend#128](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/128) and [frontend#127](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/127). -- Lists of objects now display ATT&CK IDs when relevant. See [frontend#119](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/119). -- When viewing an object, fields which have no value(s) will now be hidden. See [frontend#120](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/120). -- Improved display of sub-techniques. See [frontend#125](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/125). -- Layout and formatting improvements to [USAGE](/docs/usage.md) document + +- Lists of objects can now be searched and filtered. See [frontend#128](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/128) and [frontend#127](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/127). +- Lists of objects now display ATT&CK IDs when relevant. See [frontend#119](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/119). +- When viewing an object, fields which have no value(s) will now be hidden. See [frontend#120](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/120). +- Improved display of sub-techniques. See [frontend#125](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/125). +- Layout and formatting improvements to [USAGE](/docs/usage.md) document #### Fixes in 0.2.0 -- Fixed broken pagination on relationship tables. See [frontend#126](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/126). + +- Fixed broken pagination on relationship tables. See [frontend#126](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/126). ## 16 February 2021 + ### ATT&CK Workbench version 0.1.1 + #### New Features in 0.1.1 -- Added Dockerfiles, docker-compose, [and documentation on how to use them](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/tree/master/docs/docker-compose.md). See [frontend#108](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/108), [frontend#109](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/109) [rest-api#14](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/14), and [collection-manager#13](https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager/issues/13). + +- Added Dockerfiles, docker-compose, [and documentation on how to use them](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/tree/master/docs/docker-compose.md). See [frontend#108](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/108), [frontend#109](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/109) [rest-api#14](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/14), and [collection-manager#13](https://github.com/center-for-threat-informed-defense/attack-workbench-collection-manager/issues/13). #### Fixes in 0.1.1 -- Fixed a crash that could occur with specific queries on the REST API. See [rest-api#28](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/28). + +- Fixed a crash that could occur with specific queries on the REST API. See [rest-api#28](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/28). ## 19 January 2021 + ### ATT&CK Workbench version 0.1.0 + #### New Features in 0.1.0 -- Created object view pages for matrix, technique, tactic, mitigation, group, and software objects. -- Added the ability to browse and import collection indexes. - - Collection indexes can be imported via URL. - - A preview of the collection index is shown before confirming the import. -- Added the ability to import, view, and subscribe to collections. - - Collections listed within an index can be subscribed to, which will pull new versions when they are published. - - Collections can also be manually imported via URL. When importing, a preview of the collection and its contents is shown before confirming the import. At this step, users can preview the objects in the collection and select which ones they want to import. Changes in the import are displayed relative to the state of the knowledge base similar to the update pages on the [ATT&CK Website](https://attack.mitre.org/resources/updates/). - - An interface provides the ability to review prior imports, which provides a list of changes at the time of the import identical to that shown during the import of the collection. \ No newline at end of file + +- Created object view pages for matrix, technique, tactic, mitigation, group, and software objects. +- Added the ability to browse and import collection indexes. + - Collection indexes can be imported via URL. + - A preview of the collection index is shown before confirming the import. +- Added the ability to import, view, and subscribe to collections. + - Collections listed within an index can be subscribed to, which will pull new versions when they are published. + - Collections can also be manually imported via URL. When importing, a preview of the collection and its contents is shown before confirming the import. At this step, users can preview the objects in the collection and select which ones they want to import. Changes in the import are displayed relative to the state of the knowledge base similar to the update pages on the [ATT&CK Website](https://attack.mitre.org/resources/updates/). + - An interface provides the ability to review prior imports, which provides a list of changes at the time of the import identical to that shown during the import of the collection. From 733500fea187db45bb9c0d5dd040982b7abb89fb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Fri, 15 Oct 2021 10:44:40 -0400 Subject: [PATCH 76/89] fix for update relationship source/target parent --- app/src/app/classes/stix/relationship.ts | 36 ++++++++++++------- .../list-edit/list-edit.component.ts | 6 ++-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/app/src/app/classes/stix/relationship.ts b/app/src/app/classes/stix/relationship.ts index 857294ca..e46e6b28 100644 --- a/app/src/app/classes/stix/relationship.ts +++ b/app/src/app/classes/stix/relationship.ts @@ -218,24 +218,36 @@ export class Relationship extends StixObject { } } + /** + * Retrieve the parent object of this source object + * @param {RestApiConnectorService} restAPIService the REST API connector through which the parent can be fetched + * @returns {Observable} of this object after the source parent has been updated + */ public update_source_parent(restAPIService: RestApiConnectorService): Observable { this.updating_refs = true; - var subscription = this.get_parent_object(this.source_object, restAPIService).subscribe({ - next: (res) => { this.source_parent = res; }, - complete: () => { if (subscription) subscription.unsubscribe(); } - }); - this.updating_refs = false; - return of(this); + return this.get_parent_object(this.source_object, restAPIService).pipe( + map(result => { + this.source_parent = result; + this.updating_refs = false; + return this; + }) + ); } + /** + * Retrieve the parent object of this target object + * @param {RestApiConnectorService} restAPIService the REST API connector through which the parent can be fetched + * @returns {Observable} of this object after the target parent has been updated + */ public update_target_parent(restAPIService: RestApiConnectorService): Observable { this.updating_refs = true; - var subscription = this.get_parent_object(this.target_object, restAPIService).subscribe({ - next: (res) => { this.target_parent = res; }, - complete: () => { if (subscription) subscription.unsubscribe(); } - }); - this.updating_refs = false; - return of(this); + return this.get_parent_object(this.target_object, restAPIService).pipe( + map(result => { + this.target_parent = result; + this.updating_refs = false; + return this; + }) + ) } /** diff --git a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts index f7f90134..a2edb346 100644 --- a/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts +++ b/app/src/app/components/stix/list-property/list-edit/list-edit.component.ts @@ -201,8 +201,10 @@ export class ListEditComponent implements OnInit, AfterContentChecked { } // check for existing data - for (let value of this.selectControl.value) { - if (!values.includes(value)) values.push(value); + if (this.selectControl.value) { + for (let value of this.selectControl.value) { + if (!values.includes(value)) values.push(value); + } } if (!values.length) { From d50638cd3f54db96906219532bb9bc60805126cd Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:19:46 -0400 Subject: [PATCH 77/89] force import collection --- app/src/app/app.module.ts | 5 +- .../rest-api/rest-api-connector.service.ts | 7 +- .../collection-import-error.component.html | 29 +++++++ .../collection-import-error.component.scss | 7 ++ .../collection-import-error.component.spec.ts | 25 ++++++ .../collection-import-error.component.ts | 15 ++++ .../collection-import.component.html | 5 +- .../collection-import.component.ts | 76 ++++++++++++++++--- 8 files changed, 155 insertions(+), 14 deletions(-) create mode 100644 app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html create mode 100644 app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss create mode 100644 app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts create mode 100644 app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts diff --git a/app/src/app/app.module.ts b/app/src/app/app.module.ts index 1f7b44e9..71d48192 100644 --- a/app/src/app/app.module.ts +++ b/app/src/app/app.module.ts @@ -114,11 +114,13 @@ import { CollectionManagerComponent } from "./views/stix/collection/collection-m import { CollectionIndexListComponent } from "./views/stix/collection/collection-index/collection-index-list/collection-index-list.component"; import { CollectionIndexViewComponent } from "./views/stix/collection/collection-index/collection-index-view/collection-index-view.component"; import { CollectionIndexImportComponent } from "./views/stix/collection/collection-index/collection-index-import/collection-index-import.component"; -import { CollectionImportReviewComponent } from "./views/stix/collection/collection-import/collection-import-review/collection-import-review.component"; import { CollectionListComponent } from './views/stix/collection/collection-list/collection-list.component'; import { CollectionViewComponent } from './views/stix/collection/collection-view/collection-view.component'; import { CollectionImportComponent } from './views/stix/collection/collection-import/collection-import-workflow/collection-import.component'; +import { CollectionImportReviewComponent } from "./views/stix/collection/collection-import/collection-import-review/collection-import-review.component"; +import { CollectionImportErrorComponent } from './views/stix/collection/collection-import/collection-import-error/collection-import-error.component'; + // import { CollectionExportComponent } from './views/stix/collection/collection-export/collection-export.component'; import { GroupViewComponent } from './views/stix/group/group-view/group-view.component'; @@ -218,6 +220,7 @@ import { NgxJdenticonModule, JDENTICON_CONFIG } from 'ngx-jdenticon'; CollectionIndexImportComponent, CollectionImportComponent, CollectionImportReviewComponent, + CollectionImportErrorComponent, // CollectionExportComponent, RelationshipViewComponent, diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index 4db30718..e48f7979 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -906,12 +906,15 @@ export class RestApiConnectorService extends ApiConnector { * POST a collection bundle (including a collection SDO and the objects to which it refers) to the back-end * @param {*} collectionBundle the STIX bundle to write * @param {boolean} [preview] if true, preview the results of the import without actually committing the import + * @param {boolean} [force] if true, force import the collection * @returns {Observable} collection object marking the results of the import */ - public postCollectionBundle(collectionBundle: any, preview: boolean = false): Observable { + public postCollectionBundle(collectionBundle: any, preview: boolean = false, force: boolean = false): Observable { // add query params for preview let query = new HttpParams(); - if (preview) query = query.set("checkOnly", "true"); + if (preview) query = query.set("previewOnly", "true"); + if (force) query = query.set("forceImport", "all"); + console.log(`${this.baseUrl}/collection-bundles`, this.headers, query) // perform the request return this.http.post(`${this.baseUrl}/collection-bundles`, collectionBundle, {headers: this.headers, params: query}).pipe( tap(result => { diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html new file mode 100644 index 00000000..00e66467 --- /dev/null +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html @@ -0,0 +1,29 @@ +
+

+ WARNING: the data you are trying to import contains some problems which may affect your knowledge base if you proceed with the import: +

+ +
+
    +
  • + The bundle has previously been imported. Some objects will not be imported. +
  • +
  • + {{objectSpecVersionViolation}} {{objectSpecVersionViolation > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objectSpecVersionViolation > 1 ? 'these objects' : 'this object'}}. +
  • +
+
+ +
+
+ + +
+
+
\ No newline at end of file diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss new file mode 100644 index 00000000..30214e91 --- /dev/null +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss @@ -0,0 +1,7 @@ +@import "../../../../../../style/colors"; + +.collection-import-error { + .warn { + color: color(warn); + } +} \ No newline at end of file diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts new file mode 100644 index 00000000..7aa1a31d --- /dev/null +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CollectionImportErrorComponent } from './collection-import-error.component'; + +describe('CollectionImportErrorComponent', () => { + let component: CollectionImportErrorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CollectionImportErrorComponent] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(CollectionImportErrorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts new file mode 100644 index 00000000..2edf6b1d --- /dev/null +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts @@ -0,0 +1,15 @@ +import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector: 'app-collection-import-error', + templateUrl: './collection-import-error.component.html', + styleUrls: ['./collection-import-error.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class CollectionImportErrorComponent { + @Input() error: any; + @Output() onForceImport = new EventEmitter(); + + public get duplicateCollection(): boolean { return this.error.bundleErrors.duplicateCollection; } + public get objectSpecVersionViolation(): number { return this.error.objectErrors.summary.invalidAttackSpecVersionCount; } +} diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html index eb12f715..9db89a35 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html @@ -26,7 +26,7 @@ -
+

Below are the objects of the collection as compared to the current contents of your workbench. Select the objects you want to import into your knowledge base.

@@ -50,9 +50,10 @@
- + +
diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index 7fd4725a..493426e7 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -36,6 +36,8 @@ export class CollectionImportComponent implements OnInit { public changed_ids: string[] = []; // ids of objects which have nto changed (object-version not already in knowledge base) public unchanged_ids: string[] = []; + + public import_error: any; public save_errors: string[] = []; public successfully_saved: Set = new Set(); public collectionBundle: any; @@ -107,6 +109,9 @@ export class CollectionImportComponent implements OnInit { }, error: (err) => { this.loadingStep1 = false; + if (err.status == "400" && this.canForceImport(err)) { + this.errorPreview(collectionBundle, err.error) + } }, complete: () => { subscription_preview.unsubscribe() } }) @@ -191,8 +196,9 @@ export class CollectionImportComponent implements OnInit { /** * Perform the import of the collection + * @param force if true, force import the collection */ - public import() { + public import(force: boolean = false) { let prompt = this.dialog.open(ConfirmationDialogComponent, { maxWidth: "25em", data: { @@ -207,16 +213,18 @@ export class CollectionImportComponent implements OnInit { this.loadingStep2 = true; setTimeout(() => { //make sure the loading icon renders before the parsing/writing let newBundle = JSON.parse(JSON.stringify(this.collectionBundle)); //deep copy - let objects = [] - // filter objects to selected or unchanged - for (let object of newBundle.objects) { - if (this.unchanged_ids.includes(object.id) || this.select.selected.includes(object.id)) { - // object is selected or unchanged - objects.push(object); + if (!force) { + let objects = [] + // filter objects to selected or unchanged + for (let object of newBundle.objects) { + if (this.unchanged_ids.includes(object.id) || this.select.selected.includes(object.id)) { + // object is selected or unchanged + objects.push(object); + } } + newBundle.objects = objects; } - newBundle.objects = objects; - let subscription = this.restAPIConnectorService.postCollectionBundle(newBundle, false).subscribe({ + let subscription = this.restAPIConnectorService.postCollectionBundle(newBundle, false, force).subscribe({ next: (results) => { if (results.import_categories.errors.length > 0) { logger.warn("Collection import completed with errors:", results.import_categories.errors); @@ -239,6 +247,56 @@ export class CollectionImportComponent implements OnInit { }) } + /** + * Determine if the user can force import a collection after the post collection + * call fails. Users may force an import when: + * 1. the collection bundle is already in the database (user is re-importing the bundle) + * 2. the collection bundle has one or more objects with an invalid ATT&CK spec version. + * @param error the resulting error from previewing the collection + * @returns true if the user can force import the collection; false otherwise + */ + private canForceImport(err: any): boolean { + if (!err.error) return false; + if (err.error.bundleErrors.duplicateCollection || + err.error.objectErrors.summary.invalidAttackSpecVersionCount) return true; + return false; + } + + /** + * Set up the objects to force import from a collection + * @param collectionBundle the collection bundle to import + * @param error the resulting error from previewing the collection + */ + public errorPreview(collectionBundle: any, error: any): void { + this.import_error = error; + this.collectionBundle = collectionBundle; //save for later + + let invalidObjects: string[] = error.objectErrors.errors.map(obj => obj.id); + let selected: string[] = []; + for (let object of this.collectionBundle.objects) { + if ("type" in object && object.type == 'x-mitre-collection') continue; + if ("id" in object && !invalidObjects.includes(object.id)) { + selected.push(object.id); + } + } + this.select = new SelectionModel(true, selected); + this.stepper.next(); + } + + /** + * Handle user selection on collection import errors + * @param force if true, force import the collection + */ + forceImport(force: boolean) { + if (force) { + // proceed to force import the collection + this.import(true); + } else { + // user cancelled, revert to previous step + this.stepper.previous(); + } + } + /** * Download a log of errors from the import */ From ea2417c77b525d98939aa96402717b6aefe55ea1 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:26:59 -0400 Subject: [PATCH 78/89] update changelog for 252 --- docs/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.md b/docs/changelog.md index d316976f..6186d831 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -40,6 +40,7 @@ - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - REST API will now log import errors to the console. - Frontend will now log import errors to the console when the application environment is not set to production. +- Allow users to force import a collection if the collection's data format is more recent than the Workbench can support or if the collection bundle already exists in the database. See [frontend#252](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/252). #### Fixes in 1.0.3 - Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. From 5dc6a0fc6038a426e1bc724fd3f05788c94c9f69 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 18 Oct 2021 11:32:11 -0400 Subject: [PATCH 79/89] minor updates --- .../services/connectors/rest-api/rest-api-connector.service.ts | 1 - .../collection-import-error.component.scss | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index e48f7979..d7f23d30 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -914,7 +914,6 @@ export class RestApiConnectorService extends ApiConnector { let query = new HttpParams(); if (preview) query = query.set("previewOnly", "true"); if (force) query = query.set("forceImport", "all"); - console.log(`${this.baseUrl}/collection-bundles`, this.headers, query) // perform the request return this.http.post(`${this.baseUrl}/collection-bundles`, collectionBundle, {headers: this.headers, params: query}).pipe( tap(result => { diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss index 30214e91..2af729e3 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss @@ -4,4 +4,4 @@ .warn { color: color(warn); } -} \ No newline at end of file +} From 7126bc2f6f54b1063eda34a555ba1d1cff2b8b9e Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Mon, 18 Oct 2021 15:10:51 -0400 Subject: [PATCH 80/89] increased error reporting & review updates --- .../collection-import-error.component.html | 58 +++++++++++----- .../collection-import-error.component.scss | 3 + .../collection-import-error.component.ts | 20 +++++- .../collection-import.component.html | 6 +- .../collection-import.component.ts | 66 +++++++------------ 5 files changed, 91 insertions(+), 62 deletions(-) diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html index 00e66467..fd15a82e 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html @@ -1,26 +1,54 @@
-

- WARNING: the data you are trying to import contains some problems which may affect your knowledge base if you proceed with the import: -

+ +

+ WARNING: the data you are trying to import contains some problems which may affect your knowledge base if you proceed with the import: +

-
-
    -
  • - The bundle has previously been imported. Some objects will not be imported. -
  • -
  • - {{objectSpecVersionViolation}} {{objectSpecVersionViolation > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objectSpecVersionViolation > 1 ? 'these objects' : 'this object'}}. -
  • -
-
+
+
    +
  • + The collection bundle already exists in the database. Some objects may not be imported. +
  • +
  • + {{duplicateObjects}} {{duplicateObjects > 1 ? 'objects are' : 'object is'}} duplicated in the collection bundle and will not be imported. +
  • +
  • + {{objSpecVersionViolations}} {{objSpecVersionViolations > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objSpecVersionViolations > 1 ? 'these objects' : 'this object'}}. +
  • +
  • + {{objSpecViolations}} {{objSpecViolations > 1 ? 'objects have' : 'object has'}} other spec violations and will not be imported. +
  • +
+
+
+ + +

+ ERROR: the data you are trying to import contains errors and may not be imported: +

+ +
+
    +
  • + The collection bundle does not include an x-mitre-collection object. +
  • +
  • + The collection bundle contains multiple x-mitre-collection objects. +
  • +
  • + The collection bundle is improperly formatted. +
  • +
+
+
- - diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss index 2af729e3..e33760f9 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.scss @@ -4,4 +4,7 @@ .warn { color: color(warn); } + .error { + color: color(error); + } } diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts index 2edf6b1d..2a09a168 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts @@ -8,8 +8,24 @@ import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angu }) export class CollectionImportErrorComponent { @Input() error: any; - @Output() onForceImport = new EventEmitter(); + @Output() onForceImport = new EventEmitter(); + @Output() onCancel = new EventEmitter(); + public get hasWarnings(): boolean { + return this.duplicateCollection || this.duplicateObjects > 0 || this.objSpecVersionViolations > 0 || this.objSpecViolations > 0; + } + public get hasErrors(): boolean { + return this.noCollection || this.multipleCollections || this.badlyFormatted; + } + + // can be overridden public get duplicateCollection(): boolean { return this.error.bundleErrors.duplicateCollection; } - public get objectSpecVersionViolation(): number { return this.error.objectErrors.summary.invalidAttackSpecVersionCount; } + public get duplicateObjects(): number { return this.error.objectErrors.summary.duplicateObjectInBundleCount; } + public get objSpecVersionViolations(): number { return this.error.objectErrors.summary.invalidAttackSpecVersionCount; } + public get objSpecViolations(): number { return this.error.objectErrors.errors.length - this.objSpecVersionViolations - this.duplicateObjects; } + + // cannot be overridden + public get noCollection(): boolean { return this.error.bundleErrors.noCollection; } + public get multipleCollections(): boolean { return this.error.bundleErrors.moreThanOneCollection; } + public get badlyFormatted(): boolean { return this.error.bundleErrors.badlyFormattedCollection; } } diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html index 9db89a35..d4c1f439 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.html @@ -50,10 +50,10 @@
- - + + - +
diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index 84513a14..422af0dd 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -36,7 +36,7 @@ export class CollectionImportComponent implements OnInit { public select: SelectionModel; // ids of objects which have changed (object-version not already in knowledge base) public changed_ids: string[] = []; - // ids of objects which have nto changed (object-version not already in knowledge base) + // ids of objects which have not changed (object-version not already in knowledge base) public unchanged_ids: string[] = []; public import_error: any; @@ -113,7 +113,7 @@ export class CollectionImportComponent implements OnInit { }, error: (err) => { this.loadingStep1 = false; - if (err.status == "400" && this.canForceImport(err)) { + if (err.status == "400") { this.errorPreview(collectionBundle, err.error) } }, @@ -223,23 +223,21 @@ export class CollectionImportComponent implements OnInit { this.loadingStep2 = true; setTimeout(() => { //make sure the loading icon renders before the parsing/writing let newBundle = JSON.parse(JSON.stringify(this.collectionBundle)); //deep copy - if (!force) { - let objects = [] - // filter objects to selected or unchanged - for (let object of newBundle.objects) { - if (this.unchanged_ids.includes(object.id) || this.select.selected.includes(object.id)) { - // object is selected or unchanged - objects.push(object); - } + let objects = [] + // filter objects to selected or unchanged + for (let object of newBundle.objects) { + if (this.unchanged_ids.includes(object.id) || this.select.selected.includes(object.id)) { + // object is selected or unchanged + objects.push(object); } - newBundle.objects = objects; } + newBundle.objects = objects; let subscription = this.restAPIConnectorService.postCollectionBundle(newBundle, false, force).subscribe({ next: (results) => { if (results.import_categories.errors.length > 0) { logger.warn("Collection import completed with errors:", results.import_categories.errors); } - this.save_errors = results.import_categories.errors.filter(err => err["error_type"] == "Save error"); + this.save_errors = results.import_categories.errors; let save_error_ids = new Set(this.save_errors.map(err => err['object_ref'])); for (let category in results.import_categories) { if (category == "errors") continue; @@ -257,21 +255,6 @@ export class CollectionImportComponent implements OnInit { }) } - /** - * Determine if the user can force import a collection after the post collection - * call fails. Users may force an import when: - * 1. the collection bundle is already in the database (user is re-importing the bundle) - * 2. the collection bundle has one or more objects with an invalid ATT&CK spec version. - * @param error the resulting error from previewing the collection - * @returns true if the user can force import the collection; false otherwise - */ - private canForceImport(err: any): boolean { - if (!err.error) return false; - if (err.error.bundleErrors.duplicateCollection || - err.error.objectErrors.summary.invalidAttackSpecVersionCount) return true; - return false; - } - /** * Set up the objects to force import from a collection * @param collectionBundle the collection bundle to import @@ -280,31 +263,30 @@ export class CollectionImportComponent implements OnInit { public errorPreview(collectionBundle: any, error: any): void { this.import_error = error; this.collectionBundle = collectionBundle; //save for later - - let invalidObjects: string[] = error.objectErrors.errors.map(obj => obj.id); let selected: string[] = []; for (let object of this.collectionBundle.objects) { - if ("type" in object && object.type == 'x-mitre-collection') continue; - if ("id" in object && !invalidObjects.includes(object.id)) { - selected.push(object.id); + if ("type" in object && object.type == 'x-mitre-collection') { + this.unchanged_ids.push(object.id); } + else if ("id" in object) selected.push(object.id); } this.select = new SelectionModel(true, selected); this.stepper.next(); } /** - * Handle user selection on collection import errors - * @param force if true, force import the collection + * Proceed to force import the collection */ - forceImport(force: boolean) { - if (force) { - // proceed to force import the collection - this.import(true); - } else { - // user cancelled, revert to previous step - this.stepper.previous(); - } + public forceImport(): void { + this.import(true); + } + + /** + * Cancel the collection import and revert to previous step + */ + public cancelImport(): void { + this.import_error = undefined; + this.stepper.previous(); } /** From 317949090f78670ea013023080872a09debf61b8 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Wed, 20 Oct 2021 11:22:11 -0400 Subject: [PATCH 81/89] remove feature that never existed from CHANGELOG --- docs/changelog.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 519c2de0..bd542c88 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -52,9 +52,8 @@ ATT&CK Workbench version 1.1.0 includes improvements to how data is imported whi - Improved the flexibility and robustness of collection imports: - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. - - Workbench can now import STIX bundles that don't contain a collection object. This is not recommended however since the results of such an import (the list of imported objects) will not be recorded for future reference. - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - - REST API will now log import errors to the console. + - REST API will now log import errors to the console when the application environment is not set to production. - Frontend will now log import errors to the console when the application environment is not set to production. - Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). From df1068cc49ac60edab45de10d772ba9aa72d968e Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 20 Oct 2021 11:39:30 -0400 Subject: [PATCH 82/89] update import warnings/errors to conform to the behavior of the REST API --- .../collection-import-error.component.html | 17 +++++------------ .../collection-import-error.component.ts | 8 +++----- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html index fd15a82e..a25d1319 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html @@ -9,22 +9,16 @@
  • The collection bundle already exists in the database. Some objects may not be imported.
  • -
  • - {{duplicateObjects}} {{duplicateObjects > 1 ? 'objects are' : 'object is'}} duplicated in the collection bundle and will not be imported. -
  • {{objSpecVersionViolations}} {{objSpecVersionViolations > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objSpecVersionViolations > 1 ? 'these objects' : 'this object'}}.
  • -
  • - {{objSpecViolations}} {{objSpecViolations > 1 ? 'objects have' : 'object has'}} other spec violations and will not be imported. -
  • - ERROR: the data you are trying to import contains errors and may not be imported: + ERROR: the data you are trying to import contains errors and cannot be imported:

    @@ -38,20 +32,19 @@
  • The collection bundle is improperly formatted.
  • +
  • + The collection bundle contains {{duplicateObjects}} duplicated {{duplicateObjects > 1 ? 'objects' : 'object'}}. +
  • - -
    \ No newline at end of file diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts index 2a09a168..a548171b 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.ts @@ -8,24 +8,22 @@ import { Component, EventEmitter, Input, Output, ViewEncapsulation } from '@angu }) export class CollectionImportErrorComponent { @Input() error: any; - @Output() onForceImport = new EventEmitter(); @Output() onCancel = new EventEmitter(); public get hasWarnings(): boolean { - return this.duplicateCollection || this.duplicateObjects > 0 || this.objSpecVersionViolations > 0 || this.objSpecViolations > 0; + return this.duplicateCollection || this.objSpecVersionViolations > 0; } public get hasErrors(): boolean { - return this.noCollection || this.multipleCollections || this.badlyFormatted; + return this.noCollection || this.multipleCollections || this.badlyFormatted || this.duplicateObjects > 0; } // can be overridden public get duplicateCollection(): boolean { return this.error.bundleErrors.duplicateCollection; } - public get duplicateObjects(): number { return this.error.objectErrors.summary.duplicateObjectInBundleCount; } public get objSpecVersionViolations(): number { return this.error.objectErrors.summary.invalidAttackSpecVersionCount; } - public get objSpecViolations(): number { return this.error.objectErrors.errors.length - this.objSpecVersionViolations - this.duplicateObjects; } // cannot be overridden public get noCollection(): boolean { return this.error.bundleErrors.noCollection; } + public get duplicateObjects(): number { return this.error.objectErrors.summary.duplicateObjectInBundleCount; } public get multipleCollections(): boolean { return this.error.bundleErrors.moreThanOneCollection; } public get badlyFormatted(): boolean { return this.error.bundleErrors.badlyFormattedCollection; } } From 4439644f219f45b079ca40bcba9723269da2a545 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 20 Oct 2021 11:44:13 -0400 Subject: [PATCH 83/89] errors are displayed first in cases where imports have both errors & warnings --- .../collection-import-error.component.html | 34 +++++++++---------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html index a25d1319..e422d645 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html +++ b/app/src/app/views/stix/collection/collection-import/collection-import-error/collection-import-error.component.html @@ -1,26 +1,8 @@
    - -

    - WARNING: the data you are trying to import contains some problems which may affect your knowledge base if you proceed with the import: -

    - -
    -
      -
    • - The collection bundle already exists in the database. Some objects may not be imported. -
    • -
    • - {{objSpecVersionViolations}} {{objSpecVersionViolations > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objSpecVersionViolations > 1 ? 'these objects' : 'this object'}}. -
    • -
    -
    -
    -

    ERROR: the data you are trying to import contains errors and cannot be imported:

    -
    • @@ -39,6 +21,22 @@
    + +

    + WARNING: the data you are trying to import contains some problems which may affect your knowledge base if you proceed with the import: +

    +
    +
      +
    • + The collection bundle already exists in the database. Some objects may not be imported. +
    • +
    • + {{objSpecVersionViolations}} {{objSpecVersionViolations > 1 ? 'objects use' : 'object uses'}} a newer version of the ATT&CK spec than your Workbench supports and will not be imported. After upgrading Workbench to the latest version you can re-import the data to acquire {{objSpecVersionViolations > 1 ? 'these objects' : 'this object'}}. +
    • +
    +
    +
    +
    @@ -53,7 +54,6 @@ -
    From 497ba28578e565837aa8e46164afb2d0528b70bb Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:11:14 -0400 Subject: [PATCH 85/89] add option to suppress the error snackbar --- app/src/app/services/connectors/api-connector.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/app/services/connectors/api-connector.ts b/app/src/app/services/connectors/api-connector.ts index 3da308b2..361969ec 100644 --- a/app/src/app/services/connectors/api-connector.ts +++ b/app/src/app/services/connectors/api-connector.ts @@ -23,11 +23,12 @@ export abstract class ApiConnector { /** * Log the error and then raise it to the next level + * @param {boolean} showSnack if true, show the error snackbar */ - protected handleError_raise() { + protected handleError_raise(showSnack: boolean = true) { return (error: any): Observable => { logger.error(error); - this.errorSnack(error); + if (showSnack) this.errorSnack(error); return throwError(error); } } From 910565de3e848cff09cafb0c8370106567e344b6 Mon Sep 17 00:00:00 2001 From: Charissa Miller <48832936+clemiller@users.noreply.github.com> Date: Wed, 20 Oct 2021 12:32:58 -0400 Subject: [PATCH 86/89] preview collection bundle with import errors --- .../rest-api/rest-api-connector.service.ts | 58 ++++++++++++++++++- .../collection-import.component.ts | 55 ++++++------------ 2 files changed, 73 insertions(+), 40 deletions(-) diff --git a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts index e826678a..317f0f36 100644 --- a/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts +++ b/app/src/app/services/connectors/rest-api/rest-api-connector.service.ts @@ -1038,9 +1038,10 @@ export class RestApiConnectorService extends ApiConnector { * @param {*} collectionBundle the STIX bundle to write * @param {boolean} [preview] if true, preview the results of the import without actually committing the import * @param {boolean} [force] if true, force import the collection + * @param {boolean} [suppressErrors] if true, suppress the error snackbar * @returns {Observable} collection object marking the results of the import */ - public postCollectionBundle(collectionBundle: any, preview: boolean = false, force: boolean = false): Observable { + public postCollectionBundle(collectionBundle: any, preview: boolean = false, force: boolean = false, suppressErrors: boolean = false): Observable { // add query params for preview let query = new HttpParams(); if (preview) query = query.set("previewOnly", "true"); @@ -1054,11 +1055,64 @@ export class RestApiConnectorService extends ApiConnector { map(result => { return new Collection(result); }), - catchError(this.handleError_raise()), + catchError(this.handleError_raise(!suppressErrors)), share() ) } + /** + * Preview a collection bundle. + * POST the collection bundle to the back end to retrieve a preview of the import results. A second POST + * call will occur (with ?forceImport='all') if the first POST call results in an overridable import error. + * This is done in order to view the import errors alongside a preview of the import results. + * @param collectionBundle the STIX bundle to preview + * @returns {Observable} the collection object and any import errors as result of the preview import + */ + public previewCollectionBundle(collectionBundle: any): Observable { + // perform preview request + return this.postCollectionBundle(collectionBundle, true, false, true).pipe( + map(result => { + return {error: undefined, preview: result}; + }), + catchError(err => { + // check if import can be forced + if (this.cannotForceImport(err)) { + return of({error: err.error, preview: undefined}); + } + // force request + return this.postCollectionBundle(collectionBundle, true, true, true).pipe( + map(force_result => { + return {error: err.error, preview: force_result}; + }), + catchError(this.handleError_raise()) + ) + }), + share() + ); + } + + /** + * Determine if the user cannot force import a collection when the post collection call fails. + * Users cannot force an import when: + * 1. the collection bundle has more than one collection object + * 2. the collection bundle does not have a collection object + * 3. the collection is badly formatted + * 4. the collection contains duplicate objects + * @param err the resulting error from previewing the collection + * @returns true if the user cannot force import the collection; false otherwise + */ + private cannotForceImport(err: any): boolean { + if (err.status == "400") { + let bundleErrors = err.error.bundleErrors; + let objectErrors = err.error.objectErrors.summary; + if (bundleErrors.noCollection || bundleErrors.moreThanOneCollection || bundleErrors.badlyFormattedCollection || objectErrors.duplicateObjectInBundleCount) { + return true; + } + return false; + } + return true; + } + /** * Get a collection bundle * @param {string} id STIX ID of collection diff --git a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts index 422af0dd..199f8f69 100644 --- a/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts +++ b/app/src/app/views/stix/collection/collection-import/collection-import-workflow/collection-import.component.ts @@ -39,7 +39,7 @@ export class CollectionImportComponent implements OnInit { // ids of objects which have not changed (object-version not already in knowledge base) public unchanged_ids: string[] = []; - public import_error: any; + public import_errors: any; public save_errors: string[] = []; public successfully_saved: Set = new Set(); public collectionBundle: any; @@ -103,22 +103,27 @@ export class CollectionImportComponent implements OnInit { public previewCollection(collectionBundle) { // send the collection bundle to the backend - let subscription_preview = this.restAPIConnectorService.postCollectionBundle(collectionBundle, true).subscribe({ + let subscription_preview = this.restAPIConnectorService.previewCollectionBundle(collectionBundle).subscribe({ next: (preview_results) => { - if (!preview_results) { - this.loadingStep1 = false; + if (preview_results.error) { + // errors occurred when fetching collection preview + this.import_errors = preview_results.error; + } + + if (!preview_results.preview) { + // collection bundle cannot be imported, show errors on next step + this.loadingStep1 = false; + this.stepper.next(); } else { - this.parsePreview(collectionBundle, preview_results) + // successfully fetched preview + this.parsePreview(collectionBundle, preview_results.preview); } }, error: (err) => { this.loadingStep1 = false; - if (err.status == "400") { - this.errorPreview(collectionBundle, err.error) - } }, complete: () => { subscription_preview.unsubscribe() } - }) + }); } public parsePreview(collectionBundle: any, preview: Collection) { @@ -206,9 +211,8 @@ export class CollectionImportComponent implements OnInit { /** * Perform the import of the collection - * @param force if true, force import the collection */ - public import(force: boolean = false) { + public import() { let prompt = this.dialog.open(ConfirmationDialogComponent, { maxWidth: "25em", data: { @@ -232,6 +236,7 @@ export class CollectionImportComponent implements OnInit { } } newBundle.objects = objects; + let force = this.import_errors ? true : false; // force import if the collection bundle has errors let subscription = this.restAPIConnectorService.postCollectionBundle(newBundle, false, force).subscribe({ next: (results) => { if (results.import_categories.errors.length > 0) { @@ -255,37 +260,11 @@ export class CollectionImportComponent implements OnInit { }) } - /** - * Set up the objects to force import from a collection - * @param collectionBundle the collection bundle to import - * @param error the resulting error from previewing the collection - */ - public errorPreview(collectionBundle: any, error: any): void { - this.import_error = error; - this.collectionBundle = collectionBundle; //save for later - let selected: string[] = []; - for (let object of this.collectionBundle.objects) { - if ("type" in object && object.type == 'x-mitre-collection') { - this.unchanged_ids.push(object.id); - } - else if ("id" in object) selected.push(object.id); - } - this.select = new SelectionModel(true, selected); - this.stepper.next(); - } - - /** - * Proceed to force import the collection - */ - public forceImport(): void { - this.import(true); - } - /** * Cancel the collection import and revert to previous step */ public cancelImport(): void { - this.import_error = undefined; + this.import_errors = undefined; this.stepper.previous(); } From 3bf0ffd4a985c4ce91dc141d0d0a6cb8290ebc9f Mon Sep 17 00:00:00 2001 From: Jack Sheriff Date: Wed, 20 Oct 2021 15:10:42 -0400 Subject: [PATCH 87/89] Update the change log to include more REST API issues. --- docs/changelog.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index bd542c88..0b5091b9 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -45,23 +45,26 @@ ATT&CK Workbench version 1.1.0 includes improvements to how data is imported whi #### Improvements in 1.1.0 -- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). -- Added support for ATT&CK Spec v2.1.0: +- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Added support for ATT&CK Spec v2.1.0: - Added support for data sources and data components, and viewing/editing interfaces for these object types and their relationships with techniques. See [frontend#67](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/67), [frontend#66](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/66). - Added support for `x_mitre_attack_spec_version` on all object types. -- Improved the flexibility and robustness of collection imports: - - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (e.x if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. +- Improved the flexibility and robustness of collection imports: + - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (ex. if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - - REST API will now log import errors to the console when the application environment is not set to production. + - REST API will now log import errors for individual objects to the console when the log level is set to `verbose`. - Frontend will now log import errors to the console when the application environment is not set to production. -- Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). +- Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). +- REST API now supports setting the log level through an environment variable. See [rest-api#108](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/108). +- REST API no longer sets the `upgrade-insecure-requests` directive of the `Content-Security-Policy` header in responses. This will facilitate the deployment of ATT&CK Workbench in an internal environment without requiring the system to be configured to support HTTPS. See [rest-api#96](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/96). #### Fixes in 1.1.0 -- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. -- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. -- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). +- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. +- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. +- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). +- Fixed an issue where the REST API would save references when importing a collection bundle even though the `previewOnly` flag had been set. See [rest-api#120](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/120). ## 20 August 2021 From 3c8f76f2c838bc850ec6349e69e8e022a6ccda66 Mon Sep 17 00:00:00 2001 From: Jack Sheriff Date: Wed, 20 Oct 2021 15:18:43 -0400 Subject: [PATCH 88/89] Fix inadvertent whitespace changes. --- docs/changelog.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index 0b5091b9..b2cf7abc 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -45,26 +45,26 @@ ATT&CK Workbench version 1.1.0 includes improvements to how data is imported whi #### Improvements in 1.1.0 -- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). -- Added support for ATT&CK Spec v2.1.0: +- Added object type documentation on list pages. See [frontend#221](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/221). +- Added support for ATT&CK Spec v2.1.0: - Added support for data sources and data components, and viewing/editing interfaces for these object types and their relationships with techniques. See [frontend#67](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/67), [frontend#66](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/66). - Added support for `x_mitre_attack_spec_version` on all object types. -- Improved the flexibility and robustness of collection imports: +- Improved the flexibility and robustness of collection imports: - Workbench will now check the ATT&CK Spec version of imported data and warn the user if the ATT&CK Spec version is unsupported (ex. if the Workbench instance is too outdated to support the data it is trying to import). The user can choose to bypass this warning. - Workbench can now import the same collection multiple times in case objects in the initial import could not be imported due to an error. - The user will now be provided with a downloadable list of objects that could not be saved (and the reason why) in the event of import errors. - REST API will now log import errors for individual objects to the console when the log level is set to `verbose`. - Frontend will now log import errors to the console when the application environment is not set to production. -- Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). -- REST API now supports setting the log level through an environment variable. See [rest-api#108](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/108). -- REST API no longer sets the `upgrade-insecure-requests` directive of the `Content-Security-Policy` header in responses. This will facilitate the deployment of ATT&CK Workbench in an internal environment without requiring the system to be configured to support HTTPS. See [rest-api#96](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/96). +- Added validation for missing ATT&CK IDs on objects that support them. The user will now be warned if they neglect to assign an ATT&CK ID to an object which supports it. When exporting a collection, the user will similarly be warned if any contained objects are missing ATT&CK IDs. See [frontend#231](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/231). +- REST API now supports setting the log level through an environment variable. See [rest-api#108](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/108). +- REST API no longer sets the `upgrade-insecure-requests` directive of the `Content-Security-Policy` header in responses. This will facilitate the deployment of ATT&CK Workbench in an internal environment without requiring the system to be configured to support HTTPS. See [rest-api#96](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/96). #### Fixes in 1.1.0 -- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. -- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. -- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). -- Fixed an issue where the REST API would save references when importing a collection bundle even though the `previewOnly` flag had been set. See [rest-api#120](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/120). +- Fixed an issue where the navigation header could be inaccessible when navigating within the application or when the page resized due to user input. +- Frontend will no longer claim objects were imported when they were actually discarded due to import errors such as spec violations. +- Imported STIX bundles will no longer require (but still allow) the `spec_version` field on the bundle itself. This was causing issues importing collections created by the Workbench. Objects within the bundle still require the `spec_version` field per the STIX 2.1 spec. See [rest-api#103](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/103). +- Fixed an issue where the REST API would save references when importing a collection bundle even though the `previewOnly` flag had been set. See [rest-api#120](https://github.com/center-for-threat-informed-defense/attack-workbench-rest-api/issues/120). ## 20 August 2021 From bb7c6ee22ddd039b968dfb5e86a03bcba2c4dde4 Mon Sep 17 00:00:00 2001 From: Isabel Tuson Date: Thu, 21 Oct 2021 13:41:37 -0400 Subject: [PATCH 89/89] update changelog --- docs/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.md b/docs/changelog.md index b2cf7abc..ce929798 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -32,7 +32,7 @@ # Changelog -## 21 October 2021 - Changes staged on develop +## 21 October 2021 ### ATT&CK Workbench version 1.1.0