Skip to content

Commit

Permalink
add assets to website service (#527)
Browse files Browse the repository at this point in the history
  • Loading branch information
clemiller authored Feb 19, 2024
1 parent 7e825a5 commit f46c712
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 101 deletions.
214 changes: 113 additions & 101 deletions app/src/app/services/website-integration/website-integration.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,113 +3,125 @@ import { EditorService } from '../editor/editor.service';
import { RestApiConnectorService } from '../connectors/rest-api/rest-api-connector.service';
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
providedIn: 'root'
})
export class WebsiteIntegrationService {
// returns the actual integration url
private get websiteIntegrationUrl(): string { return environment.integrations.attack_website.url; }
// reads if the integration is enabled
private get websiteIntegrationEnabled(): boolean { return environment.integrations.attack_website.enabled; }
// returns the actual integration url
private get websiteIntegrationUrl(): string {
return environment.integrations.attack_website.url;
}

// route mapping based on type
private typeToRouteMap = {
'software': 'software',
'matrix': 'matrices',
'group': 'groups',
'mitigation': 'mitigations',
'tactic': 'tactics',
'campaign': 'campaigns',
'technique': 'techniques',
'data-source': 'datasources',
}
// reads if the integration is enabled
private get websiteIntegrationEnabled(): boolean {
return environment.integrations.attack_website.enabled;
}

// tracks the current URL, stixID, and validity of the URL
currentWebIntegrationStatus: ExternalWebIntegrationStatus = {url:null,stixId:null,valid:false};

constructor(private editorService:EditorService, private restAPIService: RestApiConnectorService) { }
// route mapping based on type
private typeToRouteMap = {
'software': 'software',
'matrix': 'matrices',
'group': 'groups',
'mitigation': 'mitigations',
'tactic': 'tactics',
'campaign': 'campaigns',
'technique': 'techniques',
'data-source': 'datasources',
'asset': 'assets'
}

/**
* Takes in a stixID and returns whether the external URL for that object would be valid
* @param stixIdToCheck The stixID of the object we wish to check
* @return {boolean}
*/
public checkExternalUrlValidity(stixIdToCheck) {
// if the integration is disabled, if the stixId passed in is not truthy, or if the stixId is 'new' we can auto-return false
if (this.websiteIntegrationEnabled == false || !stixIdToCheck || stixIdToCheck == 'new') {
this.currentWebIntegrationStatus = {url:null, valid: false, stixId: stixIdToCheck};
return false;
}
// if the stixId matches the currently held stixId, no need to do the HTTP reqs
if (this.currentWebIntegrationStatus.stixId == stixIdToCheck) {
return this.currentWebIntegrationStatus.valid;
}
// set the stixId to the passed in stixId
this.currentWebIntegrationStatus = {url:null, valid: false, stixId: stixIdToCheck};
// retrieve object based on type
let data$: any;
if (this.editorService.type == "software") data$ = this.restAPIService.getSoftware(stixIdToCheck);
else if (this.editorService.type == "group") data$ = this.restAPIService.getGroup(stixIdToCheck);
else if (this.editorService.type == "matrix") data$ = this.restAPIService.getMatrix(stixIdToCheck);
else if (this.editorService.type == "mitigation") data$ = this.restAPIService.getMitigation(stixIdToCheck);
else if (this.editorService.type == "tactic") data$ = this.restAPIService.getTactic(stixIdToCheck);
else if (this.editorService.type == "campaign") data$ = this.restAPIService.getCampaign(stixIdToCheck);
else if (this.editorService.type == "technique") data$ = this.restAPIService.getTechnique(stixIdToCheck);
else if (this.editorService.type == "data-source") data$ = this.restAPIService.getDataSource(stixIdToCheck);
else {
return false;
}
// fetch the object from the REST API based on stixId
let objSubscription = data$.subscribe({
next: (data) => {
// if data is not there, we can return false immediately
if (data.length !== 1) {
return false;
}
// grab attackObject and the attackID for the external lookup
const attackObject = data[0];
const attackID = attackObject.attackID.split('.');
let additionalPath = '';
// matrix type is special
if (this.editorService.type == "matrix") {
if (attackID == 'enterprise-attack') {
additionalPath = '/enterprise/';
} else if (attackID == 'mobile-attack') {
additionalPath = '/mobile/';
} else if (attackID == 'ics-attack') {
additionalPath = '/ics/';
} else {
return false;
}
} else {
// for sub-objects we need to split based on the '.' delimiter in the url
for (let i = 0; i < attackID.length; i++) {
additionalPath = additionalPath.concat('/',attackID[i]);
}
}
const url = `${this.websiteIntegrationUrl}/${this.typeToRouteMap[this.editorService.type]}${additionalPath}`;
this.currentWebIntegrationStatus.url = url;
// with no-cors we will get no data but we can validate that the URL is valid
fetch(url, {mode: 'no-cors'}).then(
(response) => {
this.currentWebIntegrationStatus.valid = true;
return this.currentWebIntegrationStatus.valid;
},
(error) => {
this.currentWebIntegrationStatus.valid = false;
return this.currentWebIntegrationStatus.valid;
}
);
},
complete: () => { objSubscription.unsubscribe() }
});
}
// tracks the current URL, stixID, and validity of the URL
public currentWebIntegrationStatus: ExternalWebIntegrationStatus = {
url:null,stixId:null,valid:false
};

constructor(private editorService:EditorService, private restAPIService: RestApiConnectorService) {
// intentionally left blank
}

/**
* Takes in a stixID and returns whether the external URL for that object would be valid
* @param stixIdToCheck The stixID of the object we wish to check
* @return {boolean}
*/
public checkExternalUrlValidity(stixIdToCheck) {
// if the integration is disabled, if the stixId passed in is not truthy, or if the stixId is 'new' we can auto-return false
if (this.websiteIntegrationEnabled == false || !stixIdToCheck || stixIdToCheck == 'new') {
this.currentWebIntegrationStatus = { url:null, valid: false, stixId: stixIdToCheck };
return false;
}
// if the stixId matches the currently held stixId, no need to do the HTTP reqs
if (this.currentWebIntegrationStatus.stixId == stixIdToCheck) {
return this.currentWebIntegrationStatus.valid;
}
// set the stixId to the passed in stixId
this.currentWebIntegrationStatus = {url:null, valid: false, stixId: stixIdToCheck};
// retrieve object based on type
let data$: any;
if (this.editorService.type == "software") data$ = this.restAPIService.getSoftware(stixIdToCheck);
else if (this.editorService.type == "group") data$ = this.restAPIService.getGroup(stixIdToCheck);
else if (this.editorService.type == "matrix") data$ = this.restAPIService.getMatrix(stixIdToCheck);
else if (this.editorService.type == "mitigation") data$ = this.restAPIService.getMitigation(stixIdToCheck);
else if (this.editorService.type == "tactic") data$ = this.restAPIService.getTactic(stixIdToCheck);
else if (this.editorService.type == "campaign") data$ = this.restAPIService.getCampaign(stixIdToCheck);
else if (this.editorService.type == "technique") data$ = this.restAPIService.getTechnique(stixIdToCheck);
else if (this.editorService.type == "data-source") data$ = this.restAPIService.getDataSource(stixIdToCheck);
else if (this.editorService.type == "asset") data$ = this.restAPIService.getAsset(stixIdToCheck);
else {
return false;
}
// fetch the object from the REST API based on stixId
let objSubscription = data$.subscribe({
next: (data) => {
// if data is not there, we can return false immediately
if (data.length !== 1) {
return false;
}
// grab attackObject and the attackID for the external lookup
const attackObject = data[0];
const attackID = attackObject.attackID.split('.');
let additionalPath = '';
// matrix type is special
if (this.editorService.type == "matrix") {
if (attackID == 'enterprise-attack') {
additionalPath = '/enterprise/';
} else if (attackID == 'mobile-attack') {
additionalPath = '/mobile/';
} else if (attackID == 'ics-attack') {
additionalPath = '/ics/';
} else {
return false;
}
} else {
// for sub-objects we need to split based on the '.' delimiter in the url
for (let i = 0; i < attackID.length; i++) {
additionalPath = additionalPath.concat('/',attackID[i]);
}
}
const url = `${this.websiteIntegrationUrl}/${this.typeToRouteMap[this.editorService.type]}${additionalPath}`;
this.currentWebIntegrationStatus.url = url;

// with no-cors we will get no data but we can validate that the URL is valid
fetch(url, {mode: 'no-cors'}).then(
(response) => {
this.currentWebIntegrationStatus.valid = true;
return this.currentWebIntegrationStatus.valid;
},
(error) => {
this.currentWebIntegrationStatus.valid = false;
return this.currentWebIntegrationStatus.valid;
}
);
},
complete: () => { objSubscription.unsubscribe() }
});
}
}

interface ExternalWebIntegrationStatus {
// the stixID that the validity and url are assocciated with
stixId:string,
// the external URL for the object with the given stixID
url: string,
// whether the externalURL is a valid URL
valid:boolean,
// the stixID that the validity and url are assocciated with
stixId:string,
// the external URL for the object with the given stixID
url: string,
// whether the externalURL is a valid URL
valid:boolean,
};
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@

#### Improvements
- Added the ability to view mapped techniques for a given data source. See [frontend#255](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/255).
- Added support for viewing Assets on a configured external ATT&CK Website. See [frontend#526](https://github.com/center-for-threat-informed-defense/attack-workbench-frontend/issues/526).

## 31 October 2023

Expand Down

0 comments on commit f46c712

Please sign in to comment.