Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
clemiller committed Oct 31, 2023
2 parents d4b0db2 + 0fc32e6 commit 1162b9d
Show file tree
Hide file tree
Showing 106 changed files with 2,786 additions and 1,892 deletions.
2,419 changes: 1,166 additions & 1,253 deletions app/package-lock.json

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "attack-workbench-frontend",
"version": "2.0.1",
"version": "2.1.0",
"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.",
"repository": {
"type": "git",
Expand Down Expand Up @@ -44,6 +44,7 @@
"@angular/router": "^14.3.0",
"jdenticon": "^3.2.0",
"moment": "^2.29.4",
"ngx-autosize": "^2.0.4",
"ngx-jdenticon": "^2.0.0",
"ngx-logger": "^5.0.12",
"ngx-markdown": "^14.0.1",
Expand All @@ -56,7 +57,7 @@
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.12",
"@angular-devkit/build-angular": "^14.2.13",
"@angular/cli": "^14.2.12",
"@angular/compiler-cli": "^14.3.0",
"@types/jasmine": "^4.3.5",
Expand Down
7 changes: 7 additions & 0 deletions app/src/app/app-routing-stix.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { MarkingDefinitionListComponent } from './views/stix/marking-definition/
import { DataSourceListComponent } from './views/stix/data-source/data-source-list/data-source-list.component';
import { ReferenceManagerComponent } from './views/reference-manager/reference-manager.component';
import { CampaignListComponent } from './views/stix/campaign/campaign-list/campaign-list.component';
import { AssetListComponent } from './views/stix/asset/asset-list/asset-list.component';
import { NotesPageComponent } from './views/notes-page/notes-page.component';
import { StixPageComponent } from './views/stix/stix-page/stix-page.component';

Expand All @@ -31,6 +32,7 @@ const attackTypeToPlural = {
'mitigation': 'mitigations',
'matrix': 'matrices',
'data-source': 'data-sources',
'asset': 'assets',
}
const stixRouteData = [
{
Expand Down Expand Up @@ -73,6 +75,11 @@ const stixRouteData = [
editable: true,
component: CampaignListComponent
},
{
attackType: 'asset',
editable: true,
component: AssetListComponent
},
]

const stixRoutes: Routes = [];
Expand Down
24 changes: 17 additions & 7 deletions app/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { environment } from 'src/environments/environment';
import { LoggerModule } from 'ngx-logger';

//angular imports
// angular imports
import { BrowserModule } from '@angular/platform-browser';
import { APP_INITIALIZER, NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
Expand Down Expand Up @@ -52,6 +52,7 @@ import { MaterialFileInputModule } from 'ngx-material-file-input';
import { MarkdownModule } from "ngx-markdown";
import { PopoverModule } from "ngx-smart-popover";
import { NgxJdenticonModule, JDENTICON_CONFIG } from 'ngx-jdenticon';
import { AutosizeModule } from 'ngx-autosize';

// custom components
import { HeaderComponent } from './components/header/header.component';
Expand Down Expand Up @@ -82,16 +83,13 @@ import { UsersListComponent } from './components/users-list/users-list.component

import { ExternalReferencesPropertyComponent } from "./components/stix/external-references-property/external-references-property.component";
import { ExternalReferencesViewComponent } from './components/stix/external-references-property/external-references-view/external-references-view.component';
import { ExternalReferencesDiffComponent } from './components/stix/external-references-property/external-references-diff/external-references-diff.component';

import { DescriptivePropertyComponent } from './components/stix/descriptive-property/descriptive-property.component';
import { DescriptiveViewComponent } from './components/stix/descriptive-property/descriptive-view/descriptive-view.component';
import { DescriptiveEditComponent } from './components/stix/descriptive-property/descriptive-edit/descriptive-edit.component';
import { DescriptiveDiffComponent } from './components/stix/descriptive-property/descriptive-diff/descriptive-diff.component';

import { TimestampPropertyComponent } from "./components/stix/timestamp-property/timestamp-property.component";
import { TimestampViewComponent } from "./components/stix/timestamp-property/timestamp-view/timestamp-view.component";
import { TimestampDiffComponent } from "./components/stix/timestamp-property/timestamp-diff/timestamp-diff.component";

import { StatementPropertyComponent } from "./components/stix/statement-property/statement-property.component";
import { StatementViewComponent } from './components/stix/statement-property/statement-view/statement-view.component';
Expand Down Expand Up @@ -124,6 +122,11 @@ import { AliasViewComponent } from './components/stix/alias-property/alias-view/
import { AliasEditComponent } from './components/stix/alias-property/alias-edit/alias-edit.component';
import { AliasEditDialogComponent } from './components/stix/alias-property/alias-edit/alias-edit-dialog/alias-edit-dialog.component';

import { SubtypePropertyComponent } from './components/stix/subtype-property/subtype-property.component';
import { SubtypeViewComponent } from './components/stix/subtype-property/subtype-view/subtype-view.component';
import { SubtypeEditComponent } from './components/stix/subtype-property/subtype-edit/subtype-edit.component';
import { SubtypeDialogComponent } from './components/stix/subtype-property/subtype-dialog/subtype-dialog.component';

import { OrderedListPropertyComponent } from './components/stix/ordered-list-property/ordered-list-property.component';
import { OrderedListViewComponent } from './components/stix/ordered-list-property/ordered-list-view/ordered-list-view.component';
import { OrderedListEditComponent } from './components/stix/ordered-list-property/ordered-list-edit/ordered-list-edit.component';
Expand Down Expand Up @@ -199,6 +202,9 @@ import { TacticCellComponent } from './components/matrix/tactic-cell/tactic-cell
import { TechniqueCellComponent } from './components/matrix/technique-cell/technique-cell.component';
import { MatrixFlatComponent } from './views/stix/matrix/matrix-flat/matrix-flat.component';

import { AssetListComponent } from './views/stix/asset/asset-list/asset-list.component';
import { AssetViewComponent } from './views/stix/asset/asset-view/asset-view.component';

import { TeamsListPageComponent } from './views/admin-page/teams/teams-list-page/teams-list-page.component';
import { TeamsViewPageComponent } from './views/admin-page/teams/teams-view-page/teams-view-page.component';
import { CreateNewDialogComponent } from './components/create-new-dialog/create-new-dialog.component';
Expand Down Expand Up @@ -239,13 +245,10 @@ export function initConfig(appConfigService: AppConfigService) {
DescriptivePropertyComponent,
DescriptiveViewComponent,
DescriptiveEditComponent,
DescriptiveDiffComponent,
ExternalReferencesPropertyComponent,
ExternalReferencesViewComponent,
ExternalReferencesDiffComponent,
TimestampPropertyComponent,
TimestampViewComponent,
TimestampDiffComponent,
StatementPropertyComponent,
StatementViewComponent,
StatementEditComponent,
Expand Down Expand Up @@ -339,6 +342,12 @@ export function initConfig(appConfigService: AppConfigService) {
TeamsViewPageComponent,
CreateNewDialogComponent,
UsersListComponent,
AssetListComponent,
AssetViewComponent,
SubtypePropertyComponent,
SubtypeViewComponent,
SubtypeEditComponent,
SubtypeDialogComponent,
BreadcrumbComponent
],
imports: [
Expand All @@ -359,6 +368,7 @@ export function initConfig(appConfigService: AppConfigService) {
}),
PopoverModule,
NgxJdenticonModule,
AutosizeModule,

BrowserModule,

Expand Down
34 changes: 33 additions & 1 deletion app/src/app/classes/external-references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { RestApiConnectorService } from "../services/connectors/rest-api/rest-ap
import { Serializable, ValidationData } from "./serializable";
import { StixObject } from "./stix/stix-object";
import { logger } from "../util/logger";
import { RelatedAsset } from "./stix/asset";

export class ExternalReferences extends Serializable {
private _externalReferences : Map<string, ExternalReference> = new Map();
Expand Down Expand Up @@ -146,12 +147,15 @@ export class ExternalReferences extends Serializable {
// get list of descriptive fields that support citations
let refs_fields = ['description'];
if (['software', 'group', 'campaign'].includes(object.attackType)) refs_fields.push('aliases');
if (object.attackType == 'asset') refs_fields.push('relatedAssets');
if (object.attackType == 'technique') refs_fields.push('detection');
if (object.attackType == 'campaign') refs_fields.push('first_seen_citation', 'last_seen_citation');

// parse citations for each descriptive field on the object
let parse_apis = [];
for (let field of refs_fields) {
if (field == 'aliases') parse_apis.push(this.parseCitationsFromAliases(object[field], restAPIConnector));
else if (field == 'relatedAssets') parse_apis.push(this.parseCitationsFromRelatedAssets(object[field], restAPIConnector));
else parse_apis.push(this.parseCitations(object[field], restAPIConnector));
}

Expand Down Expand Up @@ -252,6 +256,33 @@ export class ExternalReferences extends Serializable {
)
}

/**
* Parse citations from related assets which stores descriptions in the related asset object
* Add missing references to object if found in global external reference list
* @param relatedAssets list of related asset objects
* @param restApiConnector to connect to the REST API
*/
public parseCitationsFromRelatedAssets(relatedAssets : RelatedAsset[], restApiConnector : RestApiConnectorService): Observable<CitationParseResult> {
// Parse citations from the related asset descriptions
let api_calls = [];
let result = new CitationParseResult();
for (let relatedAsset of relatedAssets) {
if ('description' in relatedAsset && relatedAsset.description) {
api_calls.push(this.parseCitations(relatedAsset.description, restApiConnector));
}
}
if (api_calls.length == 0) return of(result);
else return forkJoin(api_calls).pipe( // get citation errors
map((api_results) => {
let citation_results = api_results as any;
for (let citation_result of citation_results) { // merge into master list
result.merge(citation_result);
}
return result; // return master list
})
)
}

/**
* Update external references map with given reference list
* @param references list of references
Expand Down Expand Up @@ -354,7 +385,8 @@ export class ExternalReferences extends Serializable {
let parse_apis = [];
for (let field of options.fields) {
if (!Object.keys(options.object)) continue; //object does not implement the field
if (field == "aliases") parse_apis.push(this.parseCitationsFromAliases(options.object[field], restAPIService))
if (field == "aliases") parse_apis.push(this.parseCitationsFromAliases(options.object[field], restAPIService));
else if (field == 'relatedAssets') parse_apis.push(this.parseCitationsFromRelatedAssets(options.object[field], restAPIService));
else parse_apis.push(this.parseCitations(options.object[field], restAPIService));
}
return forkJoin(parse_apis).pipe(
Expand Down
140 changes: 140 additions & 0 deletions app/src/app/classes/stix/asset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { StixObject } from "./stix-object";
import { logger } from "../../util/logger";
import { Observable } from "rxjs";
import { RestApiConnectorService } from "src/app/services/connectors/rest-api/rest-api-connector.service";
import { ValidationData } from "../serializable";

export class Asset extends StixObject {
public name: string = "";
public contributors: string[] = [];
public sectors: string[] = [];
public relatedAssets: RelatedAsset[] = [];
public platforms: string[] = [];

// assets are ICS-only
public domains: string[] = ['ics-attack'];

public readonly supportsAttackID = true;
public readonly supportsNamespace = true;
protected get attackIDValidator() {
return {
regex: "A\\d{4}",
format: "A####"
}
}

constructor(sdo?: any) {
super(sdo, "x-mitre-asset");
if (sdo) {
this.deserialize(sdo);
}
}

/**
* Transform the current object into a raw object for sending to the back-end, stripping any unnecessary fields
* @abstract
* @returns {*} the raw object to send
*/
public serialize(): any {
let rep = super.base_serialize();

rep.stix.name = this.name.trim();
rep.stix.x_mitre_domains = this.domains;
rep.stix.x_mitre_sectors = this.sectors;
rep.stix.x_mitre_related_assets = this.relatedAssets.map((asset: RelatedAsset) => {
return {
name: asset.name.trim(),
related_asset_sectors: asset.related_asset_sectors ? asset.related_asset_sectors : [],
description: asset.description ? asset.description : ""
}
});
rep.stix.x_mitre_platforms = this.platforms;
rep.stix.x_mitre_contributors = this.contributors.map(x => x.trim());

return rep;
}

public isRelatedAssetArray(arr: any[]): boolean {
return arr.every(a => this.instanceOfRelatedAsset(a));
}

public instanceOfRelatedAsset(object: any): boolean {
return 'name' in object && 'related_asset_sectors' in object;
}

/**
* 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)) return;

let sdo = raw.stix;

if (!("name" in sdo)) this.name = "";
else if (typeof(sdo.name) === "string") this.name = sdo.name;
else logger.error(`TypeError: name field is not a string: ${sdo.name} (${typeof(sdo.name)})`);

if (!("x_mitre_sectors" in sdo)) this.sectors = [];
else if (this.isStringArray(sdo.x_mitre_sectors)) this.sectors = sdo.x_mitre_sectors;
else logger.error(`TypeError: x_mitre_sectors field is not a string array.`);

if (!("x_mitre_related_assets" in sdo)) this.relatedAssets = [];
else if (this.isRelatedAssetArray(sdo.x_mitre_related_assets)) this.relatedAssets = sdo.x_mitre_related_assets;
else logger.error(`TypeError: x_mitre_related_assets field is not an array of related assets.`);

if (!("x_mitre_platforms" in sdo)) this.platforms = [];
else if (this.isStringArray(sdo.x_mitre_platforms)) this.platforms = sdo.x_mitre_platforms;
else logger.error(`TypeError: platforms field is not a string array.`);

if (!("x_mitre_domains" in sdo)) this.domains = ["ics-attack"];
else if(this.isStringArray(sdo.x_mitre_domains)) this.domains = sdo.x_mitre_domains;
else logger.error(`TypeError: domains field is not a string array.`);

if (!("x_mitre_contributors" in sdo)) this.contributors = [];
else 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.`);
}

/**
* 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<ValidationData>} the validation warnings and errors once validation is complete.
*/
public validate(restAPIService: RestApiConnectorService): Observable<ValidationData> {
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<Asset> {
let postObservable = restAPIService.postAsset(this);
let subscription = postObservable.subscribe({
next: (result) => { this.deserialize(result.serialize()); },
complete: () => { subscription.unsubscribe(); }
});
return postObservable;
}

/**
* Delete this STIX object from the database.
* @param restAPIService [RestApiConnectorService] the service to perform the DELETE through
*/
public delete(restAPIService: RestApiConnectorService) : Observable<{}> {
let deleteObservable = restAPIService.deleteAsset(this.stixID);
let subscription = deleteObservable.subscribe({
complete: () => { subscription.unsubscribe(); }
});
return deleteObservable;
}
}

export interface RelatedAsset {
name: string;
related_asset_sectors: string;
description: string;
}
Loading

0 comments on commit 1162b9d

Please sign in to comment.