Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/app-routing-stix.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ const stixRouteData = [
headerSection: 'defenses',
deprecated: true,
},
// more
{
attackType: 'identity',
editable: true,
headerSection: 'more',
},
];

const stixRoutes: Routes = [];
Expand Down
5 changes: 5 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatRadioModule } from '@angular/material/radio';
import { MatSelectModule } from '@angular/material/select';
import { MatSidenavModule } from '@angular/material/sidenav';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatStepperModule } from '@angular/material/stepper';
Expand Down Expand Up @@ -185,6 +186,7 @@ import { CampaignViewComponent } from './views/stix/campaign-view/campaign-view.
import { DataComponentViewComponent } from './views/stix/data-component-view/data-component-view.component';
import { DataSourceViewComponent } from './views/stix/data-source-view/data-source-view.component';
import { GroupViewComponent } from './views/stix/group-view/group-view.component';
import { IdentityViewComponent } from './views/stix/identity-view/identity-view.component';
import { MarkingDefinitionViewComponent } from './views/stix/marking-definition-view/marking-definition-view.component';
import { MatrixFlatComponent } from './views/stix/matrix/matrix-flat/matrix-flat.component';
import { MatrixSideComponent } from './views/stix/matrix/matrix-side/matrix-side.component';
Expand Down Expand Up @@ -328,6 +330,7 @@ export function initConfig(appConfigService: AppConfigService) {
IdentityPropertyComponent,
DataSourceViewComponent,
DataComponentViewComponent,
IdentityViewComponent,
MarkingDefinitionViewComponent,
CampaignViewComponent,
CitationPropertyComponent,
Expand Down Expand Up @@ -394,6 +397,7 @@ export function initConfig(appConfigService: AppConfigService) {
MatSelectModule,
MatExpansionModule,
MatCheckboxModule,
MatSlideToggleModule,
MatRadioModule,
MatProgressSpinnerModule,
MatMenuModule,
Expand Down Expand Up @@ -441,6 +445,7 @@ export function initConfig(appConfigService: AppConfigService) {
MatSelectModule,
MatExpansionModule,
MatCheckboxModule,
MatSlideToggleModule,
MatRadioModule,
MatProgressSpinnerModule,
MatMenuModule,
Expand Down
31 changes: 24 additions & 7 deletions src/app/classes/stix/identity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Observable, of } from 'rxjs';
import { Observable } from 'rxjs';
import { RestApiConnectorService } from 'src/app/services/connectors/rest-api/rest-api-connector.service';
import { logger } from '../../utils/logger';
import { ValidationData } from '../serializable';
Expand All @@ -8,7 +8,8 @@ import { WorkflowState } from 'src/app/utils/types';
export class Identity extends StixObject {
public name: string; // identity name
public identity_class: string; // type of entity this identity describes
public roles?: string[]; // list of roles this identity performs
public roles: string[] = []; // list of roles this identity performs
public sectors: string[] = []; // list of sectors this identity belongs to
public contact?: string; // contact information for this identity

public readonly supportsAttackID = false; // Identity does not support ATT&CK IDs
Expand All @@ -24,6 +25,7 @@ export class Identity extends StixObject {
if (sdo) {
this.deserialize(sdo);
}
this.workflow = undefined;
}

/**
Expand All @@ -37,6 +39,7 @@ export class Identity extends StixObject {
rep.stix.name = this.name;
rep.stix.identity_class = this.identity_class;
if (this.roles) rep.stix.roles = this.roles;
if (this.sectors?.length) rep.stix.sectors = this.sectors;
if (this.contact) rep.stix.contact_information = this.contact;

// Strip properties that are empty strs + lists
Expand Down Expand Up @@ -82,6 +85,15 @@ export class Identity extends StixObject {
if ('roles' in sdo) {
if (this.isStringArray(sdo.roles)) this.roles = sdo.roles;
else logger.error('TypeError: roles field is not a string array.');
} else {
this.roles = [];
}

if ('sectors' in sdo) {
if (this.isStringArray(sdo.sectors)) this.sectors = sdo.sectors;
else logger.error('TypeError: sectors field is not a string array.');
} else {
this.sectors = [];
}

if ('contact_information' in sdo) {
Expand All @@ -106,9 +118,9 @@ export class Identity extends StixObject {
*/
public validate(
restAPIService: RestApiConnectorService,
tempWorkflowState?: WorkflowState
_tempWorkflowState?: WorkflowState
): Observable<ValidationData> {
return this.base_validate(restAPIService, tempWorkflowState);
return this.base_validate(restAPIService);
}

/**
Expand All @@ -130,9 +142,14 @@ export class Identity extends StixObject {
return postObservable;
}

public delete(_restAPIService: RestApiConnectorService): Observable<object> {
// deletion is not supported on Identity objects
return of({});
public delete(restAPIService: RestApiConnectorService): Observable<object> {
const deleteObservable = restAPIService.deleteIdentity(this.stixID);
const subscription = deleteObservable.subscribe({
complete: () => {
subscription.unsubscribe();
},
});
return deleteObservable;
}

/**
Expand Down
7 changes: 7 additions & 0 deletions src/app/classes/stix/stix-object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { v4 as uuid } from 'uuid';
import { logger } from '../../utils/logger';
import { ExternalReferences } from '../external-references';
import { Serializable, ValidationData } from '../serializable';
import { UserAccount } from '../authn/user-account';
import { VersionNumber } from '../version-number';

export type workflowStates =
Expand All @@ -35,6 +36,7 @@ export abstract class StixObject extends Serializable {
public created_by?: any;
public modified_by_ref: string; //embedded relationship
public modified_by?: any;
public created_by_user_account?: UserAccount;
public firstInitialized: boolean; // boolean to track if it is a newly created object

public object_marking_refs: string[] = []; //list of embedded relationships to marking_defs
Expand Down Expand Up @@ -357,6 +359,11 @@ export abstract class StixObject extends Serializable {
"ObjectError: 'stix' field does not exist in modified_by_identity object"
);
}
if ('created_by_user_account' in raw && raw.created_by_user_account) {
this.created_by_user_account = new UserAccount(
raw.created_by_user_account
);
}

if ('workspace' in raw) {
// parse workspace fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
<span>no</span
><span *ngIf="config.no_suffix">, {{ config.no_suffix }}</span>
</button>
<button
*ngIf="config.alternate_label"
mat-button
[mat-dialog-close]="config.alternate_value || 'alternate'"
class="text-label">
<span>{{ config.alternate_label }}</span>
</button>
<button
mat-stroked-button
color="warn"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ export interface ConfirmationDialogConfig {
message: string; //prompt text
yes_suffix?: string; //optional suffix to add to the yes button
no_suffix?: string; //optional suffix to add to the no button
alternate_label?: string; //optional label for a third button
alternate_value?: string; //optional value returned by the third button
}
5 changes: 4 additions & 1 deletion src/app/components/save-dialog/save-dialog.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ <h3>Validation</h3>
</div>
<div class="column narrow version-buttons">
<mat-action-list>
<mat-form-field class="workflow-status" appearance="outline">
<mat-form-field
*ngIf="workflowEnabled"
class="workflow-status"
appearance="outline">
<mat-label>mark as...</mat-label>
<mat-select
[(ngModel)]="newState"
Expand Down
37 changes: 25 additions & 12 deletions src/app/components/save-dialog/save-dialog.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ export class SaveDialogComponent implements OnInit {
return this.validation && this.validation.errors.length == 0;
}

public get workflowEnabled(): boolean {
return this.config.showWorkflow !== false;
}

constructor(
public dialogRef: MatDialogRef<SaveDialogComponent>,
@Inject(MAT_DIALOG_DATA) public config: SaveDialogConfig,
Expand Down Expand Up @@ -64,7 +68,9 @@ export class SaveDialogComponent implements OnInit {
}

ngOnInit(): void {
this.newState = this.config.initialWorkflowState || 'work-in-progress';
this.newState = this.workflowEnabled
? this.config.initialWorkflowState || 'work-in-progress'
: undefined;
if (this.config.object.attackType === 'relationship') {
this.newState = undefined;
}
Expand All @@ -91,7 +97,10 @@ export class SaveDialogComponent implements OnInit {
}
// Run validation for the initial state
const subscription = this.config.object
.validate(this.restApiService, this.newState)
.validate(
this.restApiService,
this.workflowEnabled ? this.newState : undefined
)
.subscribe({
next: result => {
this.validation = result;
Expand All @@ -104,6 +113,8 @@ export class SaveDialogComponent implements OnInit {
}

onStatusChanged(event) {
if (!this.workflowEnabled) return;

const subscription = this.config.object
.validate(this.restApiService, event.value)
.subscribe({
Expand Down Expand Up @@ -225,9 +236,7 @@ export class SaveDialogComponent implements OnInit {
* Save the object with the current version and check for patches
*/
public saveCurrentVersion() {
this.config.object.workflow = this.newState
? { state: this.newState }
: undefined;
this.applyWorkflowState();
if (this.config.patchId || this.config.patchAnalytics) this.parse_patches();
else this.save();
}
Expand All @@ -237,9 +246,7 @@ export class SaveDialogComponent implements OnInit {
*/
public saveNextMinorVersion() {
this.config.object.version = new VersionNumber(this.nextMinorVersion);
this.config.object.workflow = this.newState
? { state: this.newState }
: undefined;
this.applyWorkflowState();
if (this.config.patchId || this.config.patchAnalytics) this.parse_patches();
else this.save();
}
Expand All @@ -249,13 +256,18 @@ export class SaveDialogComponent implements OnInit {
*/
public saveNextMajorVersion() {
this.config.object.version = new VersionNumber(this.nextMajorVersion);
this.config.object.workflow = this.newState
? { state: this.newState }
: undefined;
this.applyWorkflowState();
if (this.config.patchId || this.config.patchAnalytics) this.parse_patches();
else this.save();
}

private applyWorkflowState(): void {
this.config.object.workflow =
this.workflowEnabled && this.newState
? { state: this.newState }
: undefined;
}

private saveObject() {
return this.config.object.save(this.restApiService); // save this object
}
Expand All @@ -267,7 +279,7 @@ export class SaveDialogComponent implements OnInit {
if (!this.saveEnabled) {
return;
}
this.config.object.workflow = { state: this.newState };
this.applyWorkflowState();
const sub = this.saveObject().subscribe({
next: () => {
this.dialogRef.close(true);
Expand All @@ -283,4 +295,5 @@ export interface SaveDialogConfig {
patchAnalytics?: Set<string>; // previous list of analytics related to a detection strategy
versionAlreadyIncremented: boolean;
initialWorkflowState?: WorkflowState;
showWorkflow?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
import { Identity } from 'src/app/classes/stix/identity';
import { StixObject } from 'src/app/classes/stix/stix-object';
import { AuthenticationService } from '../../../services/connectors/authentication/authentication.service';
import { RestApiConnectorService } from '../../../services/connectors/rest-api/rest-api-connector.service';
import { Subscription } from 'rxjs';
import { UserAccount } from 'src/app/classes/authn/user-account';

@Component({
Expand All @@ -17,12 +15,8 @@ export class IdentityPropertyComponent implements OnInit {
@Input() public config: IdentityPropertyConfig;

public identity: Identity;
private userSubscription$: Subscription;

constructor(
private authenticationService: AuthenticationService,
private restAPIConnector: RestApiConnectorService
) {}
constructor(private authenticationService: AuthenticationService) {}

ngOnInit(): void {
const object = Array.isArray(this.config.object)
Expand All @@ -36,19 +30,11 @@ export class IdentityPropertyComponent implements OnInit {
if (
this.authenticationService.isLoggedIn &&
this.config.field.includes('modified') &&
object?.['workflow']?.['created_by_user_account']
object?.['created_by_user_account']
) {
const userID = object.workflow.created_by_user_account;
this.userSubscription$ = this.restAPIConnector
.getUserAccount(userID)
.subscribe({
next: account => {
const user = new UserAccount(account);
if (!this.identity) this.identity = new Identity();
this.identity.name = user.displayName;
},
complete: () => this.userSubscription$.unsubscribe(),
});
const user = new UserAccount(object.created_by_user_account);
if (!this.identity) this.identity = new Identity();
this.identity.name = user.displayName;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked {
permissions_required: 'x_mitre_permissions_required',
collection_layers: 'x_mitre_collection_layers',
data_sources: 'x_mitre_data_sources',
sectors: 'x_mitre_sectors',
sectors: 'sectors',
};
public domains = ['enterprise-attack', 'mobile-attack', 'ics-attack'];

Expand Down Expand Up @@ -299,7 +299,7 @@ export class ListEditComponent implements OnInit, AfterContentChecked {
// filter values
const values = new Set<string>();
const property = this.allAllowedValues.properties.find(p => {
return p.propertyName == this.fieldToStix[this.config.field];
return p.propertyName == this.allowedValuesPropertyName();
});
if (!property) {
// property not found
Expand Down Expand Up @@ -337,6 +337,14 @@ export class ListEditComponent implements OnInit, AfterContentChecked {
return values;
}

private allowedValuesPropertyName(): string {
const object = this.config.object as StixObject;
if (object.attackType === 'asset' && this.config.field === 'sectors') {
return 'x_mitre_sectors';
}
return this.fieldToStix[this.config.field];
}

/** Add value to object property list */
public add(event: MatChipInputEvent): void {
if (event.value?.trim()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export class NamePropertyComponent implements OnInit {
return (
this.config.mode === 'view' &&
this.object instanceof StixObject &&
this.object.attackType !== 'collection'
!['collection', 'identity'].includes(this.object.attackType)
);
}

Expand Down Expand Up @@ -100,6 +100,8 @@ export class NamePropertyComponent implements OnInit {
}

public workflowChange(event): void {
if (!this.showWorkflowControl) return;

const previousWorkflowState =
this.object?.workflow?.state || 'work-in-progress';
if (event.isUserInput) {
Expand All @@ -110,6 +112,7 @@ export class NamePropertyComponent implements OnInit {
object: this.object,
versionAlreadyIncremented: false,
initialWorkflowState: event.source.value,
showWorkflow: true,
},
autoFocus: false,
});
Expand Down
Loading
Loading