diff --git a/apps/client/src/components/board/board.tsx b/apps/client/src/components/board/board.tsx index d98790e..3259ad0 100644 --- a/apps/client/src/components/board/board.tsx +++ b/apps/client/src/components/board/board.tsx @@ -149,6 +149,15 @@ const Board: FC = ({ onSelectDiagram={moves.selectDiagram} onSelectComponent={moves.selectComponent} /> + )} + {G.modelType === ModelType.THREAT_DRAGON_V2 && model !== undefined && ( + )} {G.modelType === ModelType.PRIVACY_ENHANCED && ( diff --git a/apps/client/src/components/sidebar/sidebar.tsx b/apps/client/src/components/sidebar/sidebar.tsx index d129197..c2763e9 100644 --- a/apps/client/src/components/sidebar/sidebar.tsx +++ b/apps/client/src/components/sidebar/sidebar.tsx @@ -60,6 +60,19 @@ const Sidebar: FC = ({ Download Model )} + {G.modelType === ModelType.THREAT_DRAGON_V2 && ( + + Download Model + + )} { } } if ( - (this.state.modelType === ModelType.THREAT_DRAGON && !this.state.model) || + (this.state.modelType === ModelType.THREAT_DRAGON && !this.state.model) || + (this.state.modelType === ModelType.THREAT_DRAGON_V2 && !this.state.model) || (this.state.modelType === ModelType.IMAGE && !this.state.image) ) { return false; @@ -511,6 +512,47 @@ class Create extends React.Component { to try it out. + + + + + Select the JSON model produced by{' '} + + Threat Dragon V2 + + . + + + Or download a{' '} + + sample model + {' '} + to try it out. + +
diff --git a/apps/server/src/endpoints.ts b/apps/server/src/endpoints.ts index 53cc68a..7b3a882 100644 --- a/apps/server/src/endpoints.ts +++ b/apps/server/src/endpoints.ts @@ -10,8 +10,10 @@ import { INTERNAL_API_PORT, isSuit, logEvent, + mapModel2toOldModel, ModelType, ThreatDragonModel, + ThreatDragonModel2, ThreatDragonThreat, gameName, } from '@eop/shared'; @@ -86,10 +88,20 @@ export const createGame = switch (body.modelType) { case ModelType.THREAT_DRAGON: { // TODO: validation - await gameServer.db.setModel( - matchID, - JSON.parse(body.model as string) as ThreatDragonModel, - ); + logEvent('vishal printing body'); + logEvent(`printing body: ${body.model}`); + var model = JSON.parse(body.model as string) as ThreatDragonModel; + await gameServer.db.setModel(matchID,model); + break; + } + + case ModelType.THREAT_DRAGON_V2: { + // TODO: validation + logEvent('vishal printing body'); + logEvent(`printing body: ${body.model}`); + var model2 = JSON.parse(body.model as string) as ThreatDragonModel2; + var model = mapModel2toOldModel(model2); + await gameServer.db.setModel(matchID,model); break; } @@ -222,7 +234,8 @@ export const downloadThreatDragonModel = const isJsonModel = state.G.modelType == ModelType.PRIVACY_ENHANCED || - state.G.modelType == ModelType.THREAT_DRAGON; + state.G.modelType == ModelType.THREAT_DRAGON || + state.G.modelType == ModelType.THREAT_DRAGON_V2; const model = game.model; if (!model || 'extension' in model || !isJsonModel) { @@ -295,7 +308,8 @@ export const downloadThreatsMarkdownFile = const isJsonModel = state.G.modelType == ModelType.PRIVACY_ENHANCED || - state.G.modelType == ModelType.THREAT_DRAGON; + state.G.modelType == ModelType.THREAT_DRAGON || + state.G.modelType == ModelType.THREAT_DRAGON_V2; const model = game.model; const threats = getThreats( diff --git a/packages/shared/src/game/ThreatDragonModel2.ts b/packages/shared/src/game/ThreatDragonModel2.ts new file mode 100644 index 0000000..1e2e314 --- /dev/null +++ b/packages/shared/src/game/ThreatDragonModel2.ts @@ -0,0 +1,232 @@ +/** + * The threat models used by OWASP Threat Dragon + * + * This type is based on the schema at https://owasp.org/www-project-threat-dragon/assets/schemas/owasp.threat-dragon.schema.V1.json + */ + +export interface ThreatDragonModel2 { + summary: Summary + detail: Detail + version: string + } + +export interface Summary { + title: string + owner: string + description: string + id: number + } + +export interface Detail { + contributors: Contributor[] + diagrams: Diagram[] + reviewer: string + diagramTop: number + threatTop: number + } + +export interface Contributor { + name: string + } + +export interface Diagram { + cells: Cell[] + version: string + title: string + thumbnail: string + id: number + } + +export interface Cell { + position?: { + /** + * The component horizontal position + */ + x: number; + /** + * The component vertical position + */ + y: number; + [k: string]: unknown; + }; + size?: { + /** + * The component height + */ + height: number; + /** + * The component width + */ + width: number; + }; + attrs?: Attrs + visible?: boolean + shape: string + zIndex: number + id: string + data: Data + width?: number + height?: number + connector?: string + labels?: Label[] + source?: Source + target?: Target + vertices?: { + /** + * The horizontal value of the curve point + */ + x: number; + /** + * The vertical value of the curve point + */ + y: number; + }; + } + +export interface Position { + x: number + y: number + } + +export interface Size { + width: number + height: number + } + +export interface Attrs { + line?: Line + text?: Text + topLine?: TopLine + bottomLine?: BottomLine + body?: Body + } + +export interface Line { + stroke: string + strokeWidth?: number + targetMarker: TargetMarker + sourceMarker: SourceMarker + strokeDasharray?: string + } + +export interface TargetMarker { + name: string + } + +export interface SourceMarker { + name: string + } + +export interface Text { + text: string + } + +export interface TopLine { + stroke: string + strokeWidth: number + strokeDasharray: any + } + +export interface BottomLine { + stroke: string + strokeWidth: number + strokeDasharray: any + } + +export interface Body { + stroke: string + strokeWidth: number + strokeDasharray: any + } + +export interface Data { + name: string + description: string + type: CellType + isTrustBoundary: boolean + outOfScope?: boolean + reasonOutOfScope?: string + threats?: Threat[] + hasOpenThreats: boolean + isALog?: boolean + storesCredentials?: boolean + isEncrypted?: boolean + isSigned?: boolean + providesAuthentication?: boolean + isBidirectional?: boolean + isPublicNetwork?: boolean + protocol?: string + } + +export interface Threat { + status: Status + severity: string + title: string + type: string + description: string + mitigation: string + modelType: string + id: string + } + +export interface Label { + /** + * The label position + */ + position: number; + /** + * The label text attributes + */ + attrs: { + /** + * The text attributes + */ + label: { + /** + * The text size + */ + // 'font-size': string; + /** + * The text weight + */ + // 'font-weight': string; + /** + * The text content + */ + text: string; + }; + }; + } + +export interface Attrs2 { + label: Label2 + } + +export interface Label2 { + text: string + } + +export interface Source { + x?: number + y?: number + cell?: string + } + +export interface Target { + x?: number + y?: number + cell?: string + } + +export interface Vertex { + x: number + y: number + } + export type CellType = + | 'tm.Process' + | 'tm.Store' + | 'tm.Actor' + | 'tm.Flow' + | 'tm.Boundary'; + +type Status = 'NA' | 'Open' | 'Mitigated'; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 6bb277a..a53d88d 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -2,6 +2,7 @@ export * from './game/eop'; export * from './game/gameState'; export * from './game/setupData'; export * from './game/ThreatDragonModel'; +export * from './game/ThreatDragonModel2'; export * from './utils/serverConfig'; export * from './utils/constants'; diff --git a/packages/shared/src/utils/constants.ts b/packages/shared/src/utils/constants.ts index e240b83..0b39069 100644 --- a/packages/shared/src/utils/constants.ts +++ b/packages/shared/src/utils/constants.ts @@ -1,6 +1,8 @@ import type { ThreatDragonModel } from '../game/ThreatDragonModel'; +import type { ThreatDragonModel2 } from '../game/ThreatDragonModel2'; export enum ModelType { + THREAT_DRAGON_V2 = 'Threat DragonV2', THREAT_DRAGON = 'Threat Dragon', PRIVACY_ENHANCED = 'Default', IMAGE = 'Image', @@ -13,6 +15,50 @@ export const MIN_NUMBER_PLAYERS = 2; export const MAX_NUMBER_PLAYERS = 9; export const DEFAULT_TURN_DURATION = 300; +export const TM2 : ThreatDragonModel2 = { + summary: { + title: 'Threat Modelling', + owner: 'Owner', + description: 'Description', + id: 0, + }, + detail: { + contributors: [], + diagrams: [ + { + title: 'Threat Modelling', + thumbnail: '', + + id: 0, + cells: [ + { + shape: 'Actor', + zIndex: 1, + id: '90cdcc2d-21ab-443d-ae95-f97a798429e7', + data: { + name: 'Application', + description: '', + type: 'tm.Actor', + isTrustBoundary: false, + hasOpenThreats: false, + }, + position: { + x: 50, + y: 50, + }, + }, + ], + version: '1', + }, + ], + reviewer: 'Reviewer', + diagramTop: 0, + threatTop: 0, + }, + version: '1', +}; + + export const DEFAULT_MODEL: ThreatDragonModel = { summary: { title: 'Threat Modelling', diff --git a/packages/shared/src/utils/utils.ts b/packages/shared/src/utils/utils.ts index 9df1b04..b4583c4 100644 --- a/packages/shared/src/utils/utils.ts +++ b/packages/shared/src/utils/utils.ts @@ -2,7 +2,9 @@ import type { PlayerID } from 'boardgame.io'; import type { GameState } from '../game/gameState'; import type { Card, Suit } from './cardDefinitions'; import { ModelType } from './constants'; -import type { ThreatDragonComponent } from '../game/ThreatDragonModel'; +import type { ThreatDragonComponent, ThreatDragonModel } from '../game/ThreatDragonModel'; +import type { ThreatDragonModel2 ,CellType} from '../game/ThreatDragonModel2'; + export function getDealtCard(G: GameState): string { if (G.dealt.length > 0 && G.dealtBy) { @@ -127,3 +129,185 @@ export function logEvent(message: string): void { export function isModelType(value: string): value is ModelType { return Object.values(ModelType).includes(value); } + +export function mapModel2toOldModel(model2: ThreatDragonModel2): ThreatDragonModel { + return { + version: model2.version, + summary: { + description: model2.summary.description, + id: model2.summary.id, + owner: model2.summary.owner, + title: model2.summary.title, + }, + detail: { + contributors: model2.detail.contributors.map(contributor => ({ + name: contributor.name, + })), + diagrams: model2.detail.diagrams.map(diagram => ({ + diagramType: 'Data Flow', + id: diagram.id, + size: { + height: 600, + width: 800, + }, + thumbnail: diagram.thumbnail, + title: diagram.title, + version: diagram.version, + diagramJson: { + cells: diagram.cells.map(cell => { + + let cellAttributes: ThreatDragonComponent = { + attrs: { + text: { + text: cell.data.name, + }, + }, + hasOpenThreats: cell.data.hasOpenThreats, + id: cell.id, + labels: cell.labels?.map(mlable => ({ + attrs: { + text: { + text: "", + 'font-size': "small", + 'font-weight': "400", + }, + }, + position: mlable.position, + })), + outOfScope: cell.data.outOfScope, + size: { + height: 10, + width: 10, + }, + type: cell.data.type, + z: cell.zIndex || 1, + }; + + if (cell.data.type === "tm.Actor") { + cellAttributes.angle = 0; + + cellAttributes.attrs = cellAttributes.attrs || {}; + const elementShape = '.element-shape'; + const elementText = '.element-text'; + const elementUndefined = '.undefined'; + cellAttributes.attrs[elementShape] = cellAttributes.attrs[elementShape] || {}; + cellAttributes.attrs[elementShape].class = "element-shape hasNoOpenThreats isInScope"; + cellAttributes.attrs[elementText] = cellAttributes.attrs[elementText] || {}; + cellAttributes.attrs[elementText].class = "element-text hasNoOpenThreats isInScope"; + + cellAttributes.position = cell.position; + + if (cell.size) { cellAttributes.size = cell.size; } + + cellAttributes.threats = cell.data.threats?.map(threat => ({ + description: threat.description, + mitigation: threat.mitigation, + severity: threat.severity, + status: threat.status, + title: threat.title, + type: threat.type, + })); + + } + + if (cell.data.type === "tm.Boundary") { + cellAttributes.attrs = {}; + cellAttributes.smooth = true; + cellAttributes.source = cell.source; + cellAttributes.target = cell.target; + cellAttributes.vertices = cell.vertices; + // if(cell.height){ + // cellAttributes.size.height = cell.height; + // } + // if(cell.width){ + // cellAttributes.size.width = cell.width; + // } + } + + if (cell.data.type === "tm.Process") { + cellAttributes.angle = 0; + + cellAttributes.attrs = cellAttributes.attrs || {}; + const elementShape = '.element-shape'; + const elementText = '.element-text'; + cellAttributes.attrs[elementShape] = cellAttributes.attrs[elementShape] || {}; + cellAttributes.attrs[elementShape].class = "element-shape hasOpenThreats isInScope"; + cellAttributes.attrs[elementText] = cellAttributes.attrs[elementText] || {}; + cellAttributes.attrs[elementText].class = "element-text hasOpenThreats isInScope"; + + cellAttributes.position = cell.position; + + cellAttributes.privilegeLevel = "executionContext =Limited"; + if (cell.size) { cellAttributes.size = cell.size; } + + cellAttributes.threats = cell.data.threats?.map(threat => ({ + description: threat.description, + mitigation: threat.mitigation, + severity: threat.severity, + status: threat.status, + title: threat.title, + type: threat.type, + })); + } + + if (cell.data.type === "tm.Store") { + cellAttributes.angle = 0; + + cellAttributes.attrs = cellAttributes.attrs || {}; + const elementShape = '.element-shape'; + const elementText = '.element-text'; + cellAttributes.attrs[elementShape] = cellAttributes.attrs[elementShape] || {}; + cellAttributes.attrs[elementShape].class = "element-shape hasOpenThreats isInScope"; + cellAttributes.attrs[elementText] = cellAttributes.attrs[elementText] || {}; + cellAttributes.attrs[elementText].class = "element-text hasOpenThreats isInScope"; + + cellAttributes.position = cell.position; + + if (cell.size) { cellAttributes.size = cell.size; } + + cellAttributes.storesCredentials = cell.data.storesCredentials; + cellAttributes.threats = cell.data.threats?.map(threat => ({ + description: threat.description, + mitigation: threat.mitigation, + severity: threat.severity, + status: threat.status, + title: threat.title, + type: threat.type, + })); + cellAttributes.isEncrypted = cell.data.isEncrypted; + } + + if (cell.data.type === "tm.Flow") { + + cellAttributes.smooth = true; + cellAttributes.size.height = cell.height || 10; + cellAttributes.size.width = cell.width || 10; + + cellAttributes.source = { id: cell.source?.cell }; + + cellAttributes.target = { id: cell.target?.cell }; + + cellAttributes.threats = cell.data.threats?.map(threat => ({ + description: threat.description, + mitigation: threat.mitigation, + severity: threat.severity, + status: threat.status, + title: threat.title, + type: threat.type, + })); + + cellAttributes.vertices = cell.vertices; + cellAttributes.z = cell.zIndex || 1; + return cellAttributes; + } + + return cellAttributes; + }), + }, + diagramTop: model2.detail.diagramTop, + reviewer: model2.detail.reviewer, + threatTop: model2.detail.threatTop, + })), + }, + }; +}