diff --git a/lws10-core/diagrams/.likec4/threat-model.likec4.snap b/lws10-core/diagrams/.likec4/threat-model.likec4.snap new file mode 100644 index 0000000..c353077 --- /dev/null +++ b/lws10-core/diagrams/.likec4/threat-model.likec4.snap @@ -0,0 +1,1339 @@ +{ + _type: 'element', + tags: null, + links: null, + _stage: 'layouted', + sourcePath: 'lws10-core.c4', + description: null, + title: 'Threat Model', + id: 'threat-model', + autoLayout: { + direction: 'TB', + }, + hash: 'BI40UWUepFHdTcEPR_kXjp3cvHlXa4YyzdYjM51fWgc', + bounds: { + x: 522, + y: -86, + width: 1993, + height: 1639, + }, + nodes: [ + { + id: 'cid-context', + parent: null, + level: 0, + children: [ + 'cid-context.controlled-identifier', + ], + inEdges: [ + 'ggg1kt', + 'pl84ws', + ], + outEdges: [ + '1knbh47', + '2cv4th', + ], + title: 'C1 End User CID Provider', + modelRef: 'cid-context', + shape: 'rectangle', + color: 'sky', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + metadata: { + _shape: 'threat-container', + }, + kind: 'component', + depth: 1, + description: { + txt: '', + }, + isCustomized: true, + x: 542, + y: 524, + width: 384, + height: 287, + labelBBox: { + x: 6, + y: 0, + width: 160, + height: 15, + }, + navigateTo: null, + links: null, + }, + { + id: 'app-provider', + parent: null, + level: 0, + children: [], + inEdges: [], + outEdges: [ + '1lfl6us', + ], + title: 'E2 Application Provider', + modelRef: 'app-provider', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + kind: 'actor', + description: { + txt: '', + }, + isCustomized: true, + x: 1478, + y: -66, + width: 320, + height: 180, + labelBBox: { + x: 54, + y: 76, + width: 212, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'resource-owner', + parent: null, + level: 0, + children: [], + inEdges: [], + outEdges: [ + 'bqwu9r', + ], + title: 'E3 Resource Owner', + modelRef: 'resource-owner', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + kind: 'actor', + description: { + txt: '', + }, + isCustomized: true, + x: 2142, + y: 370, + width: 320, + height: 180, + labelBBox: { + x: 68, + y: 76, + width: 184, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'cid-context.controlled-identifier', + parent: 'cid-context', + level: 1, + children: [], + inEdges: [ + 'ggg1kt', + 'pl84ws', + ], + outEdges: [ + '1knbh47', + '2cv4th', + ], + title: 'P1 Controlled Identifier', + modelRef: 'cid-context.controlled-identifier', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + metadata: { + _shape: 'process', + }, + kind: 'component', + isCustomized: true, + x: 574, + y: 599, + width: 320, + height: 180, + labelBBox: { + x: 56, + y: 76, + width: 207, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'agent', + parent: null, + level: 0, + children: [], + inEdges: [ + '2cv4th', + ], + outEdges: [ + '13mr6gs', + 'drbxma', + ], + title: 'E1 Agent', + modelRef: 'agent', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + kind: 'actor', + isCustomized: true, + x: 888, + y: 219, + width: 320, + height: 180, + labelBBox: { + x: 117, + y: 76, + width: 86, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'app-context', + parent: null, + level: 0, + children: [ + 'app-context.lws-client', + ], + inEdges: [ + 'drbxma', + '1lfl6us', + ], + outEdges: [ + 'ggg1kt', + '1lfprtc', + '1ql4uuj', + 'ojnu3e', + ], + title: 'C3 Application Provider', + modelRef: 'app-context', + shape: 'rectangle', + color: 'sky', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + metadata: { + _shape: 'threat-container', + }, + kind: 'component', + depth: 1, + description: { + txt: '', + }, + isCustomized: true, + x: 1448, + y: 249, + width: 384, + height: 286, + labelBBox: { + x: 6, + y: 0, + width: 156, + height: 15, + }, + navigateTo: null, + links: null, + }, + { + id: 'app-context.lws-client', + parent: 'app-context', + level: 1, + children: [], + inEdges: [ + 'drbxma', + ], + outEdges: [ + 'ggg1kt', + '1lfprtc', + '1ql4uuj', + 'ojnu3e', + ], + title: 'P3 LWS Client', + modelRef: 'app-context.lws-client', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + metadata: { + _shape: 'process', + }, + kind: 'component', + isCustomized: true, + x: 1480, + y: 323, + width: 320, + height: 180, + labelBBox: { + x: 93, + y: 76, + width: 134, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'owner-context', + parent: null, + level: 0, + children: [ + 'owner-context.lws-server.authorization-server', + 'owner-context.lws-server.resource-server', + ], + inEdges: [ + '1ql4uuj', + 'ojnu3e', + 'bqwu9r', + ], + outEdges: [ + 'pl84ws', + 'chcn5d', + ], + title: 'C4 Resource Owner', + modelRef: 'owner-context', + shape: 'rectangle', + color: 'sky', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + metadata: { + _shape: 'threat-container', + }, + kind: 'component', + depth: 1, + description: { + txt: '', + }, + isCustomized: true, + x: 2088, + y: 793, + width: 407, + height: 604, + labelBBox: { + x: 6, + y: 0, + width: 129, + height: 15, + }, + navigateTo: null, + links: null, + }, + { + id: 'owner-context.lws-server.authorization-server', + parent: 'owner-context', + level: 1, + children: [], + inEdges: [ + '1ql4uuj', + ], + outEdges: [ + 'pl84ws', + 'chcn5d', + '129v7rv', + ], + title: 'P5 Authorization Server', + modelRef: 'owner-context.lws-server.authorization-server', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + metadata: { + _shape: 'process', + }, + kind: 'component', + isCustomized: true, + x: 2130, + y: 853, + width: 320, + height: 180, + labelBBox: { + x: 52, + y: 76, + width: 215, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'idp-context', + parent: null, + level: 0, + children: [ + 'idp-context.identity-provider', + 'idp-context.key', + ], + inEdges: [ + '1knbh47', + '13mr6gs', + '1lfprtc', + 'chcn5d', + ], + outEdges: [], + title: 'C2 End User Auth / IdP', + modelRef: 'idp-context', + shape: 'rectangle', + color: 'sky', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + metadata: { + _shape: 'threat-container', + }, + kind: 'component', + depth: 1, + description: { + txt: '', + }, + isCustomized: true, + x: 886, + y: 932, + width: 407, + height: 601, + labelBBox: { + x: 6, + y: 0, + width: 139, + height: 15, + }, + navigateTo: null, + links: null, + }, + { + id: 'owner-context.lws-server.resource-server', + parent: 'owner-context', + level: 1, + children: [], + inEdges: [ + 'ojnu3e', + '129v7rv', + ], + outEdges: [], + title: 'P4 Resource Server', + modelRef: 'owner-context.lws-server.resource-server', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + metadata: { + _shape: 'process', + }, + kind: 'component', + isCustomized: true, + x: 2133, + y: 1175, + width: 320, + height: 180, + labelBBox: { + x: 68, + y: 76, + width: 184, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'idp-context.identity-provider', + parent: 'idp-context', + level: 1, + children: [], + inEdges: [ + '1knbh47', + '1lfprtc', + 'chcn5d', + ], + outEdges: [ + 'u0vw7b', + ], + title: 'P2 Identity Provider', + modelRef: 'idp-context.identity-provider', + shape: 'rectangle', + color: 'primary', + style: { + opacity: 15, + size: 'md', + }, + description: { + txt: '', + }, + tags: [], + metadata: { + _shape: 'process', + }, + kind: 'component', + isCustomized: true, + x: 928, + y: 992, + width: 320, + height: 180, + labelBBox: { + x: 71, + y: 76, + width: 179, + height: 24, + }, + navigateTo: null, + links: null, + }, + { + id: 'idp-context.key', + parent: 'idp-context', + level: 1, + children: [], + inEdges: [ + 'u0vw7b', + ], + outEdges: [], + title: 'O1 Private Key', + modelRef: 'idp-context.key', + shape: 'rectangle', + color: 'pink', + style: { + opacity: 15, + size: 'md', + }, + tags: [], + metadata: { + _shape: 'data-object', + }, + kind: 'component', + description: { + txt: '', + }, + isCustomized: true, + x: 931, + y: 1311, + width: 320, + height: 180, + labelBBox: { + x: 91, + y: 76, + width: 139, + height: 24, + }, + navigateTo: null, + links: null, + }, + ], + edges: [ + { + id: '1knbh47', + source: 'cid-context.controlled-identifier', + target: 'idp-context.identity-provider', + label: 'F1 designates authn service', + points: [ + [ + 1298, + 856, + ], + [ + 1316, + 885, + ], + [ + 1339, + 915, + ], + [ + 1363, + 939, + ], + [ + 1394, + 970, + ], + [ + 1430, + 999, + ], + [ + 1466, + 1024, + ], + ], + labelBBox: { + x: 741, + y: 858, + width: 179, + height: 18, + }, + parent: null, + relations: [ + '1q2n0s2', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 869, + y: 833, + }, + { + x: 955, + y: 926, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'u0vw7b', + source: 'idp-context.identity-provider', + target: 'idp-context.key', + label: 'F2 signs AuthN Credentials with', + points: [ + [ + 1123, + 1101, + ], + [ + 1216, + 1101, + ], + [ + 1333, + 1101, + ], + [ + 1428, + 1101, + ], + ], + labelBBox: { + x: 987, + y: 1225, + width: 203, + height: 18, + }, + parent: 'idp-context', + relations: [ + '1gd7fwx', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 1089, + y: 1236, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '2cv4th', + source: 'cid-context.controlled-identifier', + target: 'agent', + label: 'F9 identifies', + points: [ + [ + 1351, + 677, + ], + [ + 1391, + 647, + ], + [ + 1440, + 618, + ], + [ + 1490, + 602, + ], + [ + 1598, + 570, + ], + [ + 1635, + 570, + ], + [ + 1743, + 602, + ], + [ + 1790, + 617, + ], + [ + 1835, + 643, + ], + [ + 1874, + 671, + ], + ], + labelBBox: { + x: 749, + y: 438, + width: 59, + height: 18, + }, + parent: null, + relations: [ + '7ipqa4', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 811, + y: 471, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'drbxma', + source: 'agent', + target: 'app-context.lws-client', + label: 'F13 agent requests', + points: [ + [ + 1898, + 677, + ], + [ + 1848, + 626, + ], + [ + 1786, + 562, + ], + [ + 1734, + 510, + ], + ], + labelBBox: { + x: 1269, + y: 323, + width: 97, + height: 18, + }, + parent: null, + relations: [ + '113u95g', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1277, + y: 349, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'ggg1kt', + source: 'app-context.lws-client', + target: 'cid-context.controlled-identifier', + label: 'F3 discovers AuthN Service', + points: [ + [ + 1480, + 456, + ], + [ + 1420, + 479, + ], + [ + 1356, + 513, + ], + [ + 1311, + 563, + ], + [ + 1286, + 592, + ], + [ + 1270, + 631, + ], + [ + 1261, + 667, + ], + ], + labelBBox: { + x: 1139, + y: 514, + width: 177, + height: 18, + }, + parent: null, + relations: [ + '75anbe', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 1205, + y: 524, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '1lfprtc', + source: 'app-context.lws-client', + target: 'idp-context.identity-provider', + label: 'F4 authenticates', + points: [ + [ + 1638, + 503, + ], + [ + 1636, + 635, + ], + [ + 1631, + 882, + ], + [ + 1629, + 1020, + ], + ], + labelBBox: { + x: 1262, + y: 803, + width: 87, + height: 18, + }, + parent: null, + relations: [ + '1amt79c', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1298, + y: 842, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '1ql4uuj', + source: 'app-context.lws-client', + target: 'owner-context.lws-server.authorization-server', + label: 'F8 requests access token', + points: [ + [ + 1480, + 427, + ], + [ + 1214, + 449, + ], + [ + 692, + 499, + ], + [ + 519, + 563, + ], + [ + 450, + 589, + ], + [ + 382, + 631, + ], + [ + 326, + 671, + ], + ], + labelBBox: { + x: 1917, + y: 654, + width: 143, + height: 18, + }, + parent: null, + relations: [ + 'yw59y2', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1927, + y: 627, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'pl84ws', + source: 'owner-context.lws-server.authorization-server', + target: 'cid-context.controlled-identifier', + label: 'F5 verifies identity provider', + points: [ + [ + 368, + 683, + ], + [ + 438, + 651, + ], + [ + 522, + 618, + ], + [ + 602, + 602, + ], + [ + 698, + 583, + ], + [ + 951, + 570, + ], + [ + 1044, + 602, + ], + [ + 1085, + 617, + ], + [ + 1123, + 643, + ], + [ + 1155, + 670, + ], + ], + labelBBox: { + x: 1488, + y: 692, + width: 151, + height: 18, + }, + parent: null, + relations: [ + 'g6czpr', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1614, + y: 729, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'chcn5d', + source: 'owner-context.lws-server.authorization-server', + target: 'idp-context.identity-provider', + label: 'F6 validates credentials', + points: [ + [ + 368, + 824, + ], + [ + 439, + 848, + ], + [ + 524, + 875, + ], + [ + 602, + 897, + ], + [ + 900, + 977, + ], + [ + 1253, + 1049, + ], + [ + 1457, + 1088, + ], + ], + labelBBox: { + x: 1572, + y: 1008, + width: 131, + height: 18, + }, + parent: null, + relations: [ + 'n6e90l', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1678, + y: 1018, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'ojnu3e', + source: 'app-context.lws-client', + target: 'owner-context.lws-server.resource-server', + label: 'F14 sends requests', + points: [ + [ + 1480, + 416, + ], + [ + 1239, + 422, + ], + [ + 800, + 448, + ], + [ + 702, + 563, + ], + [ + 675, + 595, + ], + [ + 688, + 633, + ], + [ + 713, + 668, + ], + ], + labelBBox: { + x: 1736, + y: 869, + width: 127, + height: 18, + }, + parent: null, + relations: [ + 'ubw0eg', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + kind: 'manages', + isLabelCustomized: true, + controlPoints: [ + { + x: 1816, + y: 883, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '129v7rv', + source: 'owner-context.lws-server.authorization-server', + target: 'owner-context.lws-server.resource-server', + label: 'F7 issues access tokens for', + points: [ + [ + 2353, + 920, + ], + [ + 2439, + 920, + ], + [ + 2544, + 920, + ], + [ + 2632, + 920, + ], + ], + labelBBox: { + x: 2266, + y: 1081, + width: 177, + height: 18, + }, + parent: 'owner-context', + relations: [ + '1sd9jib', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 2290, + y: 1099, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '13mr6gs', + source: 'agent', + target: 'idp-context', + label: 'F10 controlls', + points: [ + [ + 1895, + 856, + ], + [ + 1869, + 882, + ], + [ + 1839, + 911, + ], + [ + 1809, + 940, + ], + ], + labelBBox: { + x: 1058, + y: 646, + width: 84, + height: 18, + }, + parent: null, + relations: [ + '127n9hr', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 1110, + y: 672, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: '1lfl6us', + source: 'app-provider', + target: 'app-context', + label: 'F11 controlls', + points: [ + [ + 1640, + 180, + ], + [ + 1640, + 198, + ], + [ + 1640, + 218, + ], + [ + 1640, + 239, + ], + ], + labelBBox: { + x: 1642, + y: 170, + width: 84, + height: 18, + }, + parent: null, + relations: [ + '1vtvtom', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 1638, + y: 167, + }, + ], + navigateTo: null, + tags: null, + }, + { + id: 'bqwu9r', + source: 'resource-owner', + target: 'owner-context', + label: 'F12 controlls', + points: [ + [ + 208, + 503, + ], + [ + 208, + 528, + ], + [ + 208, + 556, + ], + [ + 208, + 584, + ], + ], + labelBBox: { + x: 2285, + y: 651, + width: 84, + height: 18, + }, + parent: null, + relations: [ + 'ct53zi', + ], + color: 'gray', + line: 'dashed', + head: 'normal', + isLabelCustomized: true, + controlPoints: [ + { + x: 2296, + y: 666, + }, + ], + navigateTo: null, + tags: null, + }, + ], + _layout: 'manual', +} diff --git a/lws10-core/diagrams/lws10-core.c4 b/lws10-core/diagrams/lws10-core.c4 index 5288ea4..039faa2 100644 --- a/lws10-core/diagrams/lws10-core.c4 +++ b/lws10-core/diagrams/lws10-core.c4 @@ -1,51 +1,77 @@ specification { - - element actor { - style { - shape person - opacity 50% - color gray - } - } - element component { - style { - border solid - color secondary - } - } + color pink #f3d9f2 - relationship manages { - color gray - line solid - } + element actor + element component + + relationship manages } model { - agent = actor 'Agent' { + actor agent 'Agent' { description 'An agent of the LWS system' } + actor app-provider 'E2 Application Provider' + actor resource-owner 'E3 Resource Owner' - component lws-client 'LWS Client' { - description 'An HTTP client that complies with the LWS Protocol' + component cid-context 'C1 End User CID Provider' { + metadata { + _shape 'threat-container' + } + component controlled-identifier 'Controlled Identifier' { + description 'An identifier that is controlled by an agent and used to identify the agent to the LWS system' + metadata { + _shape 'process' + } + } } - component controlled-identifier 'Controlled Identifier' { - description 'An identifier that is controlled by an agent and used to identify the agent to the LWS system' + component idp-context 'C2 End User Auth / IdP' { + metadata { + _shape 'threat-container' + } + component identity-provider 'Identity Provider' { + description 'Confirms user identity and issues signed credentials. MAY be an external system' + metadata { + _shape 'process' + } + } + component key 'O1 Private Key' { + metadata { + _shape 'data-object' + } + } } - component identity-provider 'Identity Provider' { - description 'Confirms user identity and issues signed credentials. MAY be an external system' + component app-context 'C3 Application Provider' { + metadata { + _shape 'threat-container' + } + component lws-client 'LWS Client' { + description 'An HTTP client that complies with the LWS Protocol' + metadata { + _shape 'process' + } + } } - component lws-server 'LWS Server' { - description 'An HTTP server that complies with the LWS Protocol' - - component authorization-server "Authorization Server" { - description 'An OAuth 2.0 authorization server that issues access tokens. MAY be an external system' + component owner-context 'C4 Resource Owner' { + metadata { + _shape 'threat-container' } - component resource-server 'Resource Server' { - description 'Manages data resources, containers, containment and linksets' - component storage 'Storage' { - description 'A set of hierarchically organized HTTP resources managed per LWS conventions' - style { - multiple true + component lws-server 'LWS Server' { + description 'An HTTP server that complies with the LWS Protocol' + + component authorization-server 'Authorization Server' { + description 'An OAuth 2.0 authorization server that issues access tokens. MAY be an external system' + metadata { + _shape 'process' + } + } + component resource-server 'Resource Server' { + description 'Manages data resources, containers, containment and linksets' + metadata { + _shape 'process' + } + component storage 'Storage' { + description 'A set of hierarchically organized HTTP resources managed per LWS conventions' } } } @@ -60,12 +86,132 @@ model { lws-client .manages resource-server 'sends request' authorization-server .manages controlled-identifier 'verifies identity provider' authorization-server .manages identity-provider 'validates credentials' + + // Copied from threat model (F-relationships without a .manages counterpart) + controlled-identifier -> identity-provider 'F1 designates authn service' + identity-provider -> key 'F2 signs AuthN Credentials with' + lws-client -> controlled-identifier 'F3 discovers AuthN Service' + authorization-server -> resource-server 'F7 issues access tokens for' + agent -> idp-context 'F10 controlls' + app-provider -> app-context 'F11 controlls' + resource-owner -> owner-context 'F12 controlls' } views { view fig-container-diagram of lws-server { title 'Container Diagram' autoLayout TopBottom - include *, identity-provider, agent, storage + include *, identity-provider, agent, storage, lws-client, controlled-identifier + exclude cid-context, idp-context, app-context, owner-context, key + exclude controlled-identifier -> identity-provider, lws-client -> controlled-identifier, authorization-server -> resource-server + + include + * -> * where kind is manages with { + color gray + line solid + } + + style element.kind = actor { + shape person + opacity 50% + color gray + } + style element.kind = component { + border solid + color secondary + } + style storage { + multiple true + } + } + + view threat-model { + title 'Threat Model' + include + cid-context with { + description '' + }, + cid-context.* with { + description '' + }, + controlled-identifier with { + title 'P1 Controlled Identifier' + description '' + }, + idp-context with { + description '' + }, + idp-context.* with { + description '' + }, + identity-provider with { + title 'P2 Identity Provider' + description '' + }, + agent with { + title 'E1 Agent' + description '' + }, + app-context with { + description '' + }, + app-context.* with { + description '' + }, + lws-client with { + title 'P3 LWS Client' + description '' + }, + app-provider with { + description '' + }, + owner-context with { + description '' + }, + owner-context.* with { + description '' + }, + authorization-server with { + title 'P5 Authorization Server' + description '' + }, + resource-server with { + title 'P4 Resource Server' + description '' + }, + resource-owner with { + description '' + } + + include + controlled-identifier -> agent with { + title 'F9 identifies' + }, + agent -> lws-client with { + title 'F13 agent requests' + }, + lws-client -> identity-provider with { + title 'F4 authenticates' + }, + lws-client -> authorization-server with { + title 'F8 requests access token' + }, + lws-client -> resource-server with { + title 'F14 sends requests' + }, + authorization-server -> controlled-identifier with { + title 'F5 verifies identity provider' + }, + authorization-server -> identity-provider with { + title 'F6 validates credentials' + } + exclude lws-server + + style key { + color pink + } + style cid-context, idp-context, app-context, owner-context { + color sky + } } } diff --git a/threat-model/README.md b/threat-model/README.md new file mode 100644 index 0000000..c090fe1 --- /dev/null +++ b/threat-model/README.md @@ -0,0 +1,7 @@ +### Diagrams + +Currently there are issues with LikeC4 CLI and custom styles. +Diagrams can be modified using setup in https://github.com/hackers4peace/likec4-example-customization/ + +>[!NOTE] +@elf-pavlik will keep them in sync until CLI is updated diff --git a/threat-model/index.html b/threat-model/index.html new file mode 100644 index 0000000..6eeda8e --- /dev/null +++ b/threat-model/index.html @@ -0,0 +1,384 @@ + + + + + LWS Threat Model + + + + + + + + + + + + +
+

+ This document describes the Threat Modelling of LWS. +

+
+
+

Status of this Document

+

This is a work-in-progress at the moment. + It is not yet complete, but each presented threat is carefully considered. +

+
+
+

Introduction

+

+ Threat modeling is a vital part of specification development. +

+

+ This document intends to create a comprehensive view of threats in LWS systems. + It is built on top of https://github.com/LegReq/respec-threats/tree/main +

+
+

Description

+

Linked Web Storage (LWS) enables agents to securely store + and share data through a network of interoperable storage + providers. Identity is rooted in controlled identifier + documents (CIDs), and access is mediated through + authorization servers using authentication + credentials.

+

This threat model considers the core LWS architecture: + three external entities (User, Application Admin, and + Storage Controller), four containers (the CID Provider, + the User Authorization Server, the Application + Provider, and the LWS Storage), and the data flows + connecting them.

+
+
+

+ Data Flow Diagram for LWS Threat Model +

+
+ +
Data Flow Diagram for LWS Threat Model + (source: lws.c4) +
+
+
+

Data Flow Description

+

This diagram models the core interactions in an LWS system where + authentication uses CID Documents.

+

An agent (E1) controls both their + CID Document (P1) and their + User Authorization Server (P2). The + CID Document designates (F1) which + User Authorization Server(s) handle authentication. The + User Authorization Server uses the User's private key + (O1) to sign authentication + credentials.

+

An LWS Client (P3), deployed by an Application + Admin (E2), first discovers (F3) the + User Authorization Server by reading the + CID Document. It then obtains + (F4) an authentication credential from that + User Authorization Server. The User interacts (F13) + with the LWS Client to initiate these flows.

+

To access resources, the LWS Client exchanges + (F8) its authentication credential for an access + token at the Resource Authorization Server + (P5). That Resource Authorization Server + independently discovers (F5) the + User Authorization Server via the + CID Document and fetches (F6) the public + verification method to validate the credential. The + LWS Client then uses the access token to access + (F14) LWS resources hosted by the + LWS Server (P4).

+

The Storage Controller (E3) specifies the + Resource Authorization Server and may determine access + control policies for the LWS Storage.

+

note:We + did not yet model potential attackers apart from the actors already existed, + as over-characterizing attackers can lead to over complication or analysis bias.

+

The CID Provider (C1) and User Authorization Server + (C2) are modeled separately, though in practice they may be + provided by the same service. The Application Provider + (C3) may deliver the LWS Client as a web + application, a native app, or a server-side process. The + LWS Storage (C4) encompasses both the + LWS Server hosting LWS resources and its + Resource Authorization Server — these are modeled as + separate components because they may be operated independently.

+

This threat model focuses on the LWS protocol layer. Threats + to underlying transport security (TLS), DNS, and the physical + or operating-system layers are out of scope except where they + directly enable attacks on LWS-specific flows.

+ +
+

Dictionary

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
E1 Agent + An agent who the LWS Client represents; the User is identified by their CID Document, which specifies basic User information, and the User Authorization Server(s) that proves the User's identity.
E2 + Application ProviderAn agent responsible for developing, deploying or configuring an + LWS Client that accesses resources on behalf of Users.
E3 + Resource OwnerAn agent that controls all resources in an LWS Storage. The Storage Controller specifies the Resource Authorization Server, and may determine the Access Control policies for Resources in the LWS Storage.
C1 + End User CID ProviderThe service where the User stores their + CID Document, which designates the + User Authorization Server(s) that prove the User's + identity.
C2 + End User Auth / IdPThe service that proves the User's identity by issuing + authentication credentials signed with the User's private + key.
C3 + Application ProviderThe service provider that develops and operates an + LWS Client, which accesses LWS resources on behalf of + the User.
C4 + Resource OwnerThe storage system where the User's LWS resources are + hosted, managed by a Resource Authorization Server that + validates authentication credentials and issues access + tokens.
P1 + Controlled IdentifierProcess managing the User's CID Document, + which points to the User's chosen + User Authorization Server(s).
P2 + Identity ProviderProcess acting as the User's authorization server and + issuer, which issues authentication credentials signed + with the User's private key, proving the User's identity.
P3 LWS ClientAn LWS Client process that discovers the + User Authorization Server via the + CID Document, obtains an + authentication credential, and accesses protected + LWS resources on behalf of the User.
P4 Resource + ServerAn LWS Server process hosting an LWS Storage that + serves the User's LWS resources, accepting access tokens + issued by the Resource Authorization Server.
P5 + Authorization ServerThe authorization server for an LWS Storage. + Validates authentication credentials by fetching the User's + public verification method from their + User Authorization Server, and issues access tokens for + Resources in the LWS Storage.
O1 Private KeyThe User's private key, held by the + User Authorization Server and used to sign + authentication credentials.
F1 + designates authn serviceThe CID Document designates which + User Authorization Server(s) handle authentication for the + User.
F3 + discovers AuthN ServiceThe LWS Client reads the User's CID + Document to discover the User Authorization Server + endpoint.
F4 + authenticatesThe LWS Client requests and receives an + authentication credential from the + User Authorization Server.
F5 + verifies identity providerThe Resource Authorization Server reads the User's + CID Document to discover the + User Authorization Server for verifying + authentication credentials.
F6 + validates credentialsThe Resource Authorization Server fetches the User's + public verification method from their + User Authorization Server to verify an + authentication credential.
F8 + requests access tokenThe LWS Client presents its authentication credential + to the Resource Authorization Server in exchange for + an access token.
F13 + agent requestsThe User interacts with an LWS Client to access and manage + their LWS resources.
F14 + sends requestsThe LWS Client uses an access token to read and write + LWS resources in the LWS Storage on behalf of the + User.
+
+
+
+

Stakeholders

+

E1 User

+

The agent who the LWS Client represents. The User is + identified by their CID Document, which specifies basic User + information and the User Authorization Server(s) that prove + the User’s identity. LWS separates identity, authentication, and + storage, so the User independently chooses their CID Provider, + User Authorization Server, and LWS Storage + provider(s).

+

The User’s security interest is ensuring that their identity is only + operationalized by the LWS Clients they authorize, for the + period and purpose of their own interests. They may access their own + LWS Storage, or other Users’ LWS Storages, meaning the + Storage Controller can be the same as or different from the + User, leading to different security interests between them. Users may + also have widely varying levels of technical expertise.

+ +

E2 Application Admin

+

The agent responsible for developing, deploying, or + configuring an LWS Client that accesses + LWS resources on behalf of Users. The LWS Client + relies on LWS resources in LWS Storages, while it may + perform additional actions on them, such as caching or further + processing.

+

Ideally, the Application Admin would ensure the LWS Client + complies with all LWS protocol specifications and does not perform + malicious or stealthy actions on the retrieved data. In practice, + the Application Admin does not inherently have a positive security + interest — their trustworthiness depends on whether Users trust and + utilize the LWS Client they manage. From a threat modeling + perspective, the LWS Client must be treated as potentially + untrusted, even when acting on behalf of the User.

+ +

E3 Storage Controller

+

The agent that controls all resources in an + LWS Storage. The Storage Controller specifies the + Resource Authorization Server and may determine access control + policies for LWS resources in that LWS Storage. + A typical example of the Storage Controller is the User + themselves; however, Users may also access LWS resources + managed by other Storage Controllers.

+

The Storage Controller’s security interest is in ensuring the + security of the LWS resources — that only trusted or + authorized Users and LWS Clients can access them. That is + typically accomplished by the Resource Authorization Server + correctly validating authentication credentials and issuing + access tokens only for authorized access, access control policies + being correctly enforced, and the LWS resources under their + control being protected from unauthorized access or modification.

+
+
+

Threats

+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/threat-model/lws.png b/threat-model/lws.png new file mode 100644 index 0000000..7d85b4c Binary files /dev/null and b/threat-model/lws.png differ diff --git a/threat-model/threats/outline.js b/threat-model/threats/outline.js new file mode 100644 index 0000000..9ddc17f --- /dev/null +++ b/threat-model/threats/outline.js @@ -0,0 +1,11 @@ +(function () { + var threatCategories = [ + { + name: "Client Threats", + id: "threat-model-client-threats", + threats: ["T1", "T2", "T3", "T4"], + }, + ]; + + window.ThreatModel.registerCategories(threatCategories); +})(); diff --git a/threat-model/threats/t1-injected-identity-providers.js b/threat-model/threats/t1-injected-identity-providers.js new file mode 100644 index 0000000..44b339f --- /dev/null +++ b/threat-model/threats/t1-injected-identity-providers.js @@ -0,0 +1,26 @@ +(function () { + var threat = { + id: "T1", + name: "Injected Identity Providers", + desc: "The Client may tamper the User's Web-CID Document, given it has right permissions. It may inject a malicious *issuer* itself controls, directly leading to user impersonation.", + response: [ + { + id: "R1", + name: "Access Control Protection", + type: "Reduce", + desc: 'The User or Agent sets appropriate Access Control rules, permitting only trustworthy Clients in modifying the content of the Web-CID Document. Care should be taken not to disallow *any* modifications from any Client. The power of this response depends on the Access Control model the Authorization Server supports.', + }, + { + id: "R2", + name: "External Web-CID Document", + type: "Reduce", + desc: "Instead of having the Web-CID Document as an LWS Resource, serve it as an external resource. This prevents the LWS Client from modifying Web-CID Document through LWS protocol; the external service can use custom mechanisms to protect the Web-CID Document. But this does not prevent the same LWS Client supporting other protocols to modify the Web-CID Document.", + }, + ], + elements: ["P1", "C2", "C3", "C4"], + taxonomyName: "STRIDE", + taxonomyClass: "Spoofing", + }; + + window.ThreatModel.register(threat); +})(); diff --git a/threat-model/threats/t2-impersonation-through-credentials.js b/threat-model/threats/t2-impersonation-through-credentials.js new file mode 100644 index 0000000..973e065 --- /dev/null +++ b/threat-model/threats/t2-impersonation-through-credentials.js @@ -0,0 +1,26 @@ +(function () { + var threat = { + id: "T2", + name: "Impersonation through Credentials", + desc: "The LWS Client may impersonate the User by using the user' access Credentials to access LWS Resources without the user's knowledge.", + response: [ + { + id: "R2", + name: "Frontend-only Ephemeral Apps", + type: "Reduce", + desc: "Requiring the Client to be an ephemeral app, whose whole lifecycle is in the browser, and does not store anything externally (apart from the LWS Resources). This circumvents the impersonation from the current Client.", + }, + { + id: "R3", + name: "Securely Manages Access Credentials", + type: "Transfer", + desc: "The User's access Credentials are securely stored and managed in a location where only the corresponding Client can use (e.g. browser's Cookie with `HttpOnly` and `Strict`). This prohibits Client B from using the Credentials issues to Client A. This transfers the threat from the Client or Client Admin to the underlying technology (e.g. browser).", + }, + ], + elements: ["P3", "P4", "P5"], + taxonomyName: "STRIDE", + taxonomyClass: "Spoofing", + }; + + window.ThreatModel.register(threat); +})(); diff --git a/threat-model/threats/t3-client-id-impersonation.js b/threat-model/threats/t3-client-id-impersonation.js new file mode 100644 index 0000000..08cafb8 --- /dev/null +++ b/threat-model/threats/t3-client-id-impersonation.js @@ -0,0 +1,20 @@ +(function () { + var threat = { + id: "T3", + name: "Client ID Impersonation (to User Authorization Server)", + desc: "The Client (Client A) may pretend to be another Client (Client B), to gain authorization it should not have.", + response: [ + { + id: "R4", + name: "CID and Environment Verification (from User Authorization Server)", + type: "Reduce", + desc: "The User Authorization Server verifies the identity of the Client, by checking the environmental information (e.g., redirect URL) of the Client matches those specified in the Client CID Document. This ensures that the response sent by the User Authorization Server is to a location trusted by the claimed Client CID Document. As long as those specified locations are not compromised, there will be no impersonations.", + }, + ], + elements: ["P3", "P2"], + taxonomyName: "STRIDE", + taxonomyClass: "Spoofing", + }; + + window.ThreatModel.register(threat); +})(); diff --git a/threat-model/threats/t4-client-id-impersonation-resource-server.js b/threat-model/threats/t4-client-id-impersonation-resource-server.js new file mode 100644 index 0000000..172e616 --- /dev/null +++ b/threat-model/threats/t4-client-id-impersonation-resource-server.js @@ -0,0 +1,26 @@ +(function () { + var threat = { + id: "T4", + name: "Client ID Impersonation (to Resource Authorization Server)", + desc: "The Client (Client A) may pretend to be another Client (Client B), to gain authorization to Resources it should not have.", + response: [ + { + id: "R5", + name: "Verification and Proof from User Authorization Server", + type: "Transfer", + desc: "The User Authorization Server verifies the identity of the Client, issuing a proof accompanying the Client's request to Resource Authorization Server, which in turn verifies that the proof is authentic with the User Authorization Server. This transfers the verification to User Authorization Server, thus requiring trusting User Authorization Server.", + }, + { + id: "R6", + name: "Verification from Resource Authorization Server", + type: "Reduce", + desc: "The Resource Authorization Server verifies the identity of the Client. Because there is no OIDC redirect during this interaction, this verification requires a different mechanism from the CID Document verification in R4. An appropriate different mechanism is therefore needed.", + }, + ], + elements: ["P2", "P3", "P5"], + taxonomyName: "STRIDE", + taxonomyClass: "Spoofing", + }; + + window.ThreatModel.register(threat); +})(); diff --git a/threat-model/threats/threat-model.css b/threat-model/threats/threat-model.css new file mode 100644 index 0000000..d242ccc --- /dev/null +++ b/threat-model/threats/threat-model.css @@ -0,0 +1,168 @@ +/* + * Copyright 2026 Legendary Requirements + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +table.visual-terminology, +table.dfd-dictionary { + padding: 1em; + border: 1px solid black; +} + +table.visual-terminology th, +table.visual-terminology td, +table.dfd-dictionary th, +table.dfd-dictionary td { + border-bottom: 1px solid black; + border-collapse: collapse; + padding: 0.5em; +} + +table.visual-terminology tr { + border-collapse: collapse; +} + +table.visual-terminology th { + width: 11em; + font-weight: bold; +} + +table.visual-terminology td.term-def { +} + +table.visual-terminology td.term-img { + width: 11em; +} + +table.dfd-dictionary { +} + +table.dfd-dictionary td.dfd-id { + width: 3em; + font-weight: bold; +} +table.dfd-dictionary td.dfd-name { + width: 10em; +} +table.dfd-dictionary td.dfd-type { + width: 6em; +} + +#threat-toc { + counter-reset: threat; +} + +ol.threat-toc { + list-style: none; +} + +ol.threat-toc li::before { + content: "T" attr(value) ". "; + font-weight: bold; +} + +ol.threat-toc li.section-title::before { + font-weight: bold; + counter-increment: none; + content: ""; +} + +ol.threat-toc li.section-title { + font-weight: bold; + font-style: italic; +} + +table.threat td.threat-name span.index-link { + position: absolute; + right: 10px; + height: 1em; + font-size: small; + margin-top: -0.7em; + top: 50%; +} +ol.threat-toc h5 { + margin: 0; +} + +table.threat { + border: thin solid black; + border-collapse: collapse; + margin: 1em 0 1em 0; + width: 100%; + padding-left: 30px; +} +table.threat a.self-link { + left: -35px !important; +} +table.threat h3, +table.threat h4, +table.threat h5 { + color: black; + margin: 0 0 0 -5px; + font-weight: bold; + font-style: normal; +} +table.threat td { + padding: 1em; + position: relative; + border-bottom: thin solid black; +} + +table.threat td.threat-name { + font-weight: bold; + background-color: #c9daf7; +} + +table.threat td.response-name { + font-weight: bold; + background-color: #d9ead4; +} + +table.threat td.affected-component { + background-color: #fff2ce; +} +table.threat span.component-entry { + font-weight: bold; +} +table.threat td.analysis-taxonomy { + background-color: #fce5ce; +} + +p.threatCategory { + font-size: larger; + font-weight: bolder; + margin: 0; +} + +section.threatDetail { + visibility: hidden; + height: 1px; +} + +span.threat-tag { + border-radius: 4px; + padding: 2px 5px; + color: white; + margin: 0px 2px; + font-weight: 100; + background-color: black; +} + +span.threat-tag.threat-tag-security { + background-color: red; +} + +span.threat-tag.threat-tag-privacy { + background-color: blue; +} diff --git a/threat-model/threats/threat-model.js b/threat-model/threats/threat-model.js new file mode 100644 index 0000000..de305a0 --- /dev/null +++ b/threat-model/threats/threat-model.js @@ -0,0 +1,366 @@ +/* + * Copyright 2026 Legendary Requirements + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function () { + // set up debugger output + // you can toggle different debugging namespaces by adjusting the + // value passed via a debug query parameter. + // e.g., ?debug=* will turn on all debugging output + // while ?debug=render will turn on just the render debugging output + // the value is a regex against debugging namespaces + /////////////////////////////////////////////////////////////////////// + if (typeof debug === "undefined") { + var debug = function () {}; + } else { + const params = new URLSearchParams(window.location.search); + const debug_param = params.get("debug"); + if (debug_param) { + localStorage.debug = debug_param; + console.log(`Debugging ${debug_param} enabled`); + } + } + + // Set up globals + /////////////////////////////////////////////////////////////////////// + var threats = []; // Populated by calling RegisterThreats + var threatCategories = []; // Populated by calling RegisterCategories + + // render(config, document) + // Renders all the threats, creating both a Table of Contents and + // Threat Details sections + /////////////////////////////////////////////////////////////////////// + function render(config, document) { + var renderLog = debug("render"); + console.log("Starting render"); + + tocElement = document.querySelector(config.threatModelTocSelector); + if (!tocElement) { + console.warn("No Threat Toc found. Selector: ${config.threatModelTocSelector}"); + } else { + renderToc(threats, tocElement); + } + + detailsElement = document.querySelector(config.threatModelDetailsSelector); + if (!detailsElement) { + console.warn("No Threat Section found. Selector: ${config.threatModelDetailsSelector}"); + } else { + renderThreats(threats, detailsElement, tocElement); + } + } + + function areRenderTocInputsGood(threats, tocElement) { + console.log("areRenderTocInputsGood", threats, tocElement); + if (!threats || !threats.length) { + console.warn("No threats to render in renderToc"); + return false; + } + + if (!threatCategories || !threatCategories.length) { + console.warn("No threat categories to render in renderToc"); + return false; + } + + if (!tocElement) { + console.error(`Target table of contents element must be passed to renderToc, but tocElement is ${tocElement}.`); + return false; + } + + return true; + } + + function renderToc(threats, tocElement) { + console.log("renderToc", threats); + + if (!areRenderTocInputsGood(threats, tocElement)) { + return; + } + + var threatCount = 1; + let tocHtml = threatCategories + .map((category) => { + return ` +

${category.name}

+
    + ${category.threats + .map((threatId) => { + threatCount++; + return renderTocEntry(threatId); + }) + .join("")} +
+ `; + }) + .join(""); + + tocElement.innerHTML = ` +

Threat List

+ ${tocHtml} + `; + return; + + function renderTocEntry(threatId) { + console.log("renderTocEntry", threatId); + let threatValue = threatId.substring(1); + + let threat = getThreat(threatId); + if (!threat) { + return `
  • Threat ${threatId} not found
  • `; + } + + let id = makeId(threat); + + return `
  • ${threat.name} ${renderTags(threat)} +
  • `; + } + + return; + } + + function getThreat(threatId) { + console.log("getThreat", threatId); + let threat = threats.find((threat) => threat.id === threatId); + return threat; + } + + function makeId(threat) { + var id = `${threat.id}-${threat.name}`; + id = id.toLowerCase(); + id = id.replace(/ /g, "-"); + return id; + } + + function areRenderThreatInputsGood(threats, detailsElement) { + if (!threats || !threats.length) { + console.warn("No threats to render in renderThreats"); + return false; + } + + if (!detailsElement) { + console.error(`Target Threat Details element must be passed to renderThreats but detailsElement is ${detailsElement}.`); + return false; + } + return true; + } + + function renderThreats(threats, detailsElement, tocElement) { + console.log("renderThreats", threats); + + // note that tocElement is optional + // if not present, the threat will not have a link to the TOC + if (!areRenderThreatInputsGood(threats, detailsElement)) { + return; + } + + let threatsHtml = threatCategories + .map((category) => { + return ` +

    ${category.name}

    + ${category.threats + .map((threatId) => { + return renderThreat(threatId, tocElement); + }) + .join("")}`; + }) + .join(""); + + detailsElement.innerHTML = ` +

    Threat Details

    + ${threatsHtml} + `; + return; + + function renderThreat(threatId, tocElement) { + console.log("renderThreat", threatId); + let threat = getThreat(threatId); + + if (!threat) return `

    Threat ${threatId} not found.

    `; + + let id = makeId(threat); + return ` +
    +
    ${threat.id}. ${threat.name}
    +
    + + ${renderName(threat, tocElement)} + ${renderDescription(threat)} + ${renderImage(threat)} + ${renderResponses(threat)} + ${renderComponents(threat)} + ${renderTaxonomy(threat)} +
    `; + + // now define support functions + + function renderName(threat, tocElement) { + // tocElement to render link + console.log("renderName", threat); + if (!threat.name || threat.name == "") return ""; + + return ` + + +
    +
    ${threat.id}. ${threat.name} + ${renderTags(threat)} +
    + ${renderTocLink(tocElement)} +
    + + `; + } + + function renderDescription(threat) { + if (!threat.desc || threat.desc == "") return ""; + + return ` + + + ${threat.desc} + + + `; + } + + function renderComponents(threat) { + if (!threat.elements || !threat.elements.length) return ""; + + return ` + Affected Components: ${threat.elements.map((element) => `${element}`).join(", ")} + `; + } + + function renderTocLink(tocElement) { + if (!tocElement) return ""; + + var selector = `#${tocElement.id}`; // the toc element MUST have an id + return `[Threat List]`; + } + + function renderImage(threat) { + let image = threat.image; + + console.log("renderImage", threat); + if (!threat || !image || !image.src) return ""; + + let id = makeId(threat); + return ` + + +
    + ${image.alt} +
    ${image.caption}
    +
    + + `; + } + + function renderTaxonomy(threat) { + let taxonomyName = threat?.taxonomyName; + let taxonomyClass = threat?.taxonomyClass; + + console.log("renderTaxonomy", taxonomyName); + if (!taxonomyName) return ""; + + return ` + + Threat Taxonomy: ${taxonomyName} (${taxonomyClass}) + `; + } + function renderResponses(threat) { + let responses = threat.response; + console.log("renderResponses", responses); + if (!responses || !responses.length) return "No responses."; + + let responseHtml = responses + .map( + (response) => ` + + ${response.id}. ${response.name} + + + + ${response.desc} + + + `, + ) + .join(""); + + return responseHtml; + } + } + + detailsElement.innerHTML = ` +

    Threat Details

    + ${threatHtml} + `; + console.log(detailsElement.innerHTML); + return; + } + + function renderTags(threat) { + console.log("renderTags", threat); + let tags = threat?.tags; + if (!tags || !tags.length) return ""; + + let tagsHtml = tags + .map( + (tag) => ` + ${tag} + `, + ) + .join(""); + + return tagsHtml; + } + + function register(threat) { + console.log("register", threat); + + if (!validate(threat)) { + console.log("Registration failed. Invalid criteria.", threat); + return; + } + + threats.push(threat); + } + + function registerCategories(categories) { + console.log("registerCategories", categories); + threatCategories = categories; + } + + function validate(threat) { + console.log("validate", threat); + return true; + } + + function validateThreat(threat) { + console.log("validateThreat", threat); + return true; + } + + var ThreatModel = { + render, + renderToc, + renderThreats, + register, + registerCategories, + validate, + }; + + window.ThreatModel = ThreatModel; +})();