Skip to content

Commit

Permalink
v0.4.20
Browse files Browse the repository at this point in the history
  • Loading branch information
SecSimon committed Oct 10, 2023
1 parent 4470b78 commit df65dd1
Show file tree
Hide file tree
Showing 38 changed files with 679 additions and 197 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
## 0.4.20

* Diagram: change direction and anchor of data flow
* Diagram: optional and higher radius of grid sticking
* Support of arrow keys in tables and dialogs
* Added search bar in various selects
* UI improvements

## 0.4.19

* Refactoring file handling with password protection dialog
Expand Down
2 changes: 1 addition & 1 deletion app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ttmodeler",
"version": "0.4.19",
"version": "0.4.20",
"description": "Thing Threat Modeler for Internet of Things Devices",
"homepage": "https://www.simon-liebl.de/TTM",
"author": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ttmodeler",
"version": "0.4.19",
"version": "0.4.20",
"description": "Thing Threat Modeler for Internet of Things Devices",
"homepage": "https://www.simon-liebl.de/TTM",
"author": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ <h2>
</ng-container>
<ng-container matColumnDef="elements">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{'general.Elements' | translate}} </th>
<td mat-cell *matCellDef="let entry"> {{GetTargets(entry)}} </td>
<td mat-cell *matCellDef="let entry" [class.selected-cell]="IsElementSelected(entry)"> {{GetTargets(entry)}} </td>
</ng-container>
<ng-container matColumnDef="view">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{'general.View' | translate}} </th>
Expand Down Expand Up @@ -77,7 +77,7 @@ <h2>
</ng-container>

<tr mat-header-row style="height: 30px;" *matHeaderRowDef="displayedThreatColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedThreatColumns;" [class.selected-entry]="IsThreatSelected(row)"
<tr mat-row *matRowDef="let row; columns: displayedThreatColumns;" [id]="row.ID" [class.selected-entry]="IsThreatSelected(row)"
(click)="SelectThreat(row)" (dblclick)="OnMappingDblClick(row, $event)" (contextmenu)="OpenContextMenu($event, row)">
</tr>
<tr class="mat-row" *matNoDataRow>
Expand Down Expand Up @@ -105,7 +105,7 @@ <h2>
</ng-container>
<ng-container matColumnDef="targets">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{'general.Targets' | translate}} </th>
<td mat-cell *matCellDef="let entry"> {{GetTargets(entry)}} </td>
<td mat-cell *matCellDef="let entry" [class.selected-cell]="IsElementSelected(entry)"> {{GetTargets(entry)}} </td>
</ng-container>
<ng-container matColumnDef="view">
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{'general.View' | translate}} </th>
Expand All @@ -131,7 +131,7 @@ <h2>
</ng-container>

<tr mat-header-row style="height: 30px;" *matHeaderRowDef="displayedCountermeasureColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedCountermeasureColumns;" [class.selected-entry]="IsCountermeasureSelected(row)"
<tr mat-row *matRowDef="let row; columns: displayedCountermeasureColumns;" [id]="row.ID" [class.selected-entry]="IsCountermeasureSelected(row)"
(click)="SelectCountermeasure(row)" (dblclick)="OnMappingDblClick(row, $event)" (contextmenu)="OpenContextMenu($event, row)">
</tr>
<tr class="mat-row" *matNoDataRow>
Expand Down
87 changes: 54 additions & 33 deletions src/app/dashboard/results-analysis/results-analysis.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, HostListener, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { MatMenuTrigger } from '@angular/material/menu';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { MatRow, MatTableDataSource } from '@angular/material/table';
import { TranslateService } from '@ngx-translate/core';
import { Diagram, DiagramTypes } from '../../model/diagram';
import { Diagram } from '../../model/diagram';
import { Countermeasure, MitigationStates, MitigationStateUtil } from '../../model/mitigations';
import { AttackScenario, ThreatStates, MappingStates, ThreatSeverityUtil, LifeCycleUtil, ThreatStateUtil, ThreatSeverities, ImpactCategoryUtil } from '../../model/threat-model';
import { DataService } from '../../util/data.service';
Expand Down Expand Up @@ -52,15 +52,16 @@ export class ResultsAnalysisComponent implements AfterViewInit {
private updateDiagramsDelayCounter = 0;
public get isUpdatingDiagrams(): boolean { return this.updateDiagramsDelayCounter > 0; }
private _attackScenarios: AttackScenario[] = [];
private _selectedThreats: AttackScenario[] = [];
private _countermeasures: Countermeasure[] = [];
private _selectedCountermeasures: Countermeasure[] = [];
private _selectedObject: AttackScenario|Countermeasure;

public menuTopLeftPosition = {x: '0', y: '0'};
@ViewChild(MatMenuTrigger) public matMenuTrigger: MatMenuTrigger;
@ViewChild('threattable') sortThreats: MatSort;
@ViewChild('countermeasuretable') sortCountermeasures: MatSort;

@ViewChildren(MatRow, {read: ElementRef}) rows!: QueryList<ElementRef<HTMLTableRowElement>>;

public diagrams: IDiagramData[] = [];

public displayedThreatColumns = ['number', 'name', 'elements', 'view', 'severity', 'risk', 'status', 'more'];
Expand Down Expand Up @@ -96,17 +97,6 @@ export class ResultsAnalysisComponent implements AfterViewInit {

if (this.sortThreats) this.sortThreats.sortChange.emit(this.sortThreats);
}
public get selectedThreats(): AttackScenario[] { return this._selectedThreats; }
public set selectedThreats(val: AttackScenario[]) {
if (val.length == this._selectedThreats.length) {
if (val.every(x => this._selectedThreats.some(y => y.ID == x.ID))) return;
}
this._selectedThreats = val;

if (val.length == 1) {
this.selectedCountermeasures = this.Countermeasures.filter(x => x.AttackScenarios.includes(val[0]));
}
}

public get Countermeasures(): Countermeasure[] { return this._countermeasures; }
public set Countermeasures(val: Countermeasure[]) {
Expand Down Expand Up @@ -134,17 +124,9 @@ export class ResultsAnalysisComponent implements AfterViewInit {

if (this.sortCountermeasures) this.sortCountermeasures.sortChange.emit(this.sortCountermeasures);
}
public get selectedCountermeasures(): Countermeasure[] { return this._selectedCountermeasures; }
public set selectedCountermeasures(val: Countermeasure[]) {
if (val.length == this._selectedCountermeasures.length) {
if (val.every(x => this._selectedCountermeasures.some(y => y.ID == x.ID))) return;
}
this._selectedCountermeasures = val;

if (val.length == 1) {
this.selectedThreats = this.AttackScenarios.filter(x => val[0].AttackScenarios.includes(x));
}
}
public get selectedObject(): AttackScenario|Countermeasure { return this._selectedObject; }
public set selectedObject(val: AttackScenario|Countermeasure) { this._selectedObject = val; }

constructor(public theme: ThemeService, public dataService: DataService, public dialog: DialogService, private translate: TranslateService, private locStorage: LocalStorageService, public elRef: ElementRef) { }

Expand All @@ -167,6 +149,41 @@ export class ResultsAnalysisComponent implements AfterViewInit {
});
}

@HostListener('document:keydown', ['$event'])
public onKeyDown(event: KeyboardEvent) {
if (this.selectedObject) {
const selectObj = (objects, index: number) => {
this.selectedObject = objects[index];
const r = this.rows.find(row => row.nativeElement.id === objects[index].ID);
r?.nativeElement.scrollIntoView({block: 'center', behavior: 'smooth'});
};

if (event.key == 'ArrowDown') {
let objects = [];
if (this.selectedObject instanceof AttackScenario) { objects = this.dataSourceThreats.sortData(this.dataSourceThreats.filteredData, this.sortThreats); }
else { objects = this.dataSourceCountermeasures.sortData(this.dataSourceCountermeasures.filteredData, this.sortThreats); }
const currIdx = objects.indexOf(this.selectedObject);
if (currIdx < objects.length-1) {
selectObj(objects, currIdx+1);
}
}
else if (event.key == 'ArrowUp') {
let objects = [];
if (this.selectedObject instanceof AttackScenario) { objects = this.dataSourceThreats.sortData(this.dataSourceThreats.filteredData, this.sortThreats); }
else { objects = this.dataSourceCountermeasures.sortData(this.dataSourceCountermeasures.filteredData, this.sortThreats); }
const currIdx = objects.indexOf(this.selectedObject);
if (currIdx > 0) {
selectObj(objects, currIdx-1);
}
}
}

if (['ArrowDown', 'ArrowUp'].includes(event.key)) {
event.preventDefault();
event.stopImmediatePropagation();
}
}

public UpdateDiagrams() {
if (!this.dataService.Project) return;
let hei = (window.innerHeight - 24) * this.GetSplitSize(1, 0, 50) / 100;
Expand Down Expand Up @@ -591,21 +608,25 @@ export class ResultsAnalysisComponent implements AfterViewInit {
}

public IsThreatSelected(threat) {
return this.selectedThreats.includes(threat);
return this.selectedObject == threat;
}

public SelectThreat(threat) {
this.selectedThreats = [threat];
public SelectThreat(as) {
this.selectedObject = as;

//if (threat.Target) this.selectedObjectChanged.emit(threat.Target);
}

public IsCountermeasureSelected(mit) {
return this.selectedCountermeasures.includes(mit);
public IsCountermeasureSelected(cm) {
return this.selectedObject == cm;
}

public SelectCountermeasure(cm) {
this.selectedObject = cm;
}

public SelectCountermeasure(mit) {
this.selectedCountermeasures = [mit];
public IsElementSelected(obj: AttackScenario|Countermeasure) {
return obj && this.selectedObject && this.selectedObject.Targets.some(x => obj.Targets.includes(x));
}

public GetViewName(entry) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/model/project-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ export class ProjectFile extends DatabaseBase {
if (!this.Data['Tasks']) this.Data['Tasks'] = [];
if (!this.Data['Notes']) this.Data['Notes'] = [];
if (!this.Data['Settings']) {
const settings: IProjectSettings = { ThreatActorToAttackScenario: false };
const settings: IProjectSettings = { ThreatActorToAttackScenario: true };
this.Data['Settings'] = settings;
}
this.config = cf;
Expand Down
20 changes: 12 additions & 8 deletions src/app/modeling/attack-scenario/attack-scenario.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@
<br/>
<mat-form-field appearance="fill" class="property-form-field">
<mat-label>{{'general.AttackVector' | translate}} ({{'general.Informative' | translate}})</mat-label>
<mat-select [(value)]="attackScenario.AttackVector" matTooltip="{{attackScenario.AttackVector?.Name}}" matTooltipShowDelay="1000">
<mat-select no-space [(value)]="attackScenario.AttackVector" matTooltip="{{attackScenario.AttackVector?.Name}}" matTooltipShowDelay="1000">
<input matInput class="searchBox" (keyup)="OnSearchAttackVectors($event)" placeholder="{{ 'general.Search' | translate }}"/>
<mat-option>{{'properties.selectNone' | translate}}</mat-option>
<mat-optgroup *ngFor="let group of GetAttackVectorGroups()" [label]="group.Name">
<mat-optgroup *ngFor="let group of GetAttackVectorGroups()" [label]="group.name">
<mat-option *ngFor="let threat of group.AttackVectors" [value]="threat">
{{threat.Name}}
</mat-option>
Expand All @@ -51,8 +52,9 @@
</mat-form-field>
<mat-form-field appearance="fill" style="margin-left: 10px; width: calc(100% - 300px - 15px);">
<mat-label>{{'general.ThreatCategories' | translate}}*</mat-label>
<mat-select [(value)]="attackScenario.ThreatCategories" multiple (selectionChange)="sysThreatGroups = null">
<mat-optgroup *ngFor="let group of GetThreatCategoryGroups()" [label]="group.Name">
<mat-select no-space [(value)]="attackScenario.ThreatCategories" multiple (selectionChange)="sysThreatGroups = null">
<input matInput class="searchBox" (keyup)="OnSearchThreatCategories($event)" placeholder="{{ 'general.Search' | translate }}"/>
<mat-optgroup *ngFor="let group of GetThreatCategoryGroups()" [label]="group.name">
<mat-option *ngFor="let cat of group.ThreatCategories" [value]="cat" matTooltip="{{cat.Description}}" matTooltipShowDelay="1000">
{{cat.Name}}
</mat-option>
Expand All @@ -61,7 +63,8 @@
</mat-form-field>
<mat-form-field appearance="fill" style="width: calc(100% - {{GetSystemThreatsWidth()}});" >
<mat-label>{{'general.SystemThreats' | translate}}</mat-label>
<mat-select [(value)]="attackScenario.SystemThreats" multiple>
<mat-select no-space [(value)]="attackScenario.SystemThreats" multiple>
<input matInput class="searchBox" (keyup)="OnSearchSystemThreat($event)" placeholder="{{ 'general.Search' | translate }}"/>
<mat-optgroup *ngFor="let group of GetSystemThreatGroups()" label="{{group.name | translate}}">
<mat-option *ngFor="let threat of group.SystemThreats" [value]="threat" matTooltip="{{threat.Description}}" matTooltipShowDelay="1000">
{{threat.Name}}
Expand All @@ -71,9 +74,10 @@
</mat-form-field>
<mat-form-field *ngIf="dataService.Project.Settings.ThreatActorToAttackScenario" appearance="fill" style="margin-left: 10px; width: 300px;">
<mat-label>{{'general.ThreatSources' | translate}}</mat-label>
<mat-select [(value)]="attackScenario.ThreatSources" multiple>
<mat-checkbox class="mat-option" color="primary" [checked]="ThreatSourcesAll()" [indeterminate]="ThreatSourcesSome()" (change)="ThreatSourcesUpdate($event.checked)">{{ThreatSourcesLabel() | translate}}</mat-checkbox>
<mat-option *ngFor="let actor of dataService.Project.GetThreatSources().Sources" [value]="actor" matTooltip="{{'general.Likelihood' | translate}}: {{GetLMHName(actor.Likelihood) | translate}}" matTooltipShowDelay="1000">
<mat-select no-space [(value)]="attackScenario.ThreatSources" multiple>
<input matInput #srcSearch class="searchBox" (keyup)="OnSearchThreatSources($event)" placeholder="{{ 'general.Search' | translate }}"/>
<mat-checkbox *ngIf="srcSearch.value.length == 0" class="mat-option" color="primary" [checked]="ThreatSourcesAll()" [indeterminate]="ThreatSourcesSome()" (change)="ThreatSourcesUpdate($event.checked)">{{ThreatSourcesLabel() | translate}}</mat-checkbox>
<mat-option *ngFor="let actor of GetThreatSources()" [value]="actor" matTooltip="{{'general.Likelihood' | translate}}: {{GetLMHName(actor.Likelihood) | translate}}" matTooltipShowDelay="1000">
{{actor.Name}}
</mat-option>
</mat-select>
Expand Down
13 changes: 9 additions & 4 deletions src/app/modeling/attack-scenario/attack-scenario.component.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
@import 'stylesPropList.scss';
@import 'stylesReorderList.scss';

.property-form-field {
width: 300px;
}

.disable {
pointer-events: none;
}

.searchBox {
padding: 0 16px;
width: calc(100% - 32px);
height: 35px !important;
line-height: 35px !important;
font-size: 14px;
font-weight: 400;
}
Loading

0 comments on commit df65dd1

Please sign in to comment.