From fd9c950c19f750986ba3aab03db1ca0a881f184e Mon Sep 17 00:00:00 2001 From: Nick Ozmore Date: Mon, 4 Oct 2021 12:31:37 -0400 Subject: [PATCH 1/3] Add custom_properties dict for custom data to be inserted into the model which does not currently exist in pytm's data model. --- docs/template.md | 5 +++++ pytm/pytm.py | 25 +++++++++++++++++++++++++ tm.py | 2 ++ 3 files changed, 32 insertions(+) diff --git a/docs/template.md b/docs/template.md index 55a7a03..9e3128f 100644 --- a/docs/template.md +++ b/docs/template.md @@ -7,6 +7,10 @@   +Repo: {tm.props[repo_url]}
+ +  + ## Dataflow Diagram - Level 0 DFD ![](sample.png) @@ -54,3 +58,4 @@ Name|Description|Classification   }| + diff --git a/pytm/pytm.py b/pytm/pytm.py index 9791b7a..ca0b554 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -210,6 +210,13 @@ def __set__(self, instance, value): super().__set__(instance, DataSet(value)) +class varDict(var): + def __set__(self, instance, value): + if not isinstance(value, dict): + raise ValueError("expecting a dict, got a {}".format(type(value))) + super().__set__(instance, value) + + class DataSet(set): def __contains__(self, item): if isinstance(item, str): @@ -720,6 +727,7 @@ class TM: doc="""How to handle duplicate Dataflow with same properties, except name and notes""", ) + props = varDict(dict([]), doc="Custom name/value pairs containing data about the model.") def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -1142,6 +1150,7 @@ class Element: required=False, doc="Location of the source code that describes this element relative to the directory of the model script.", ) + props = varDict(dict([]), doc="Custom name/value pairs containing data about this element.") def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -1265,6 +1274,22 @@ def inside(self, *boundaries): return True return False + + def getProperty(self, prop): + """getter method to extract data from props dict and avoid KeyError exceptions""" + + if (self.props): + try: + value = self.props[prop] + print("getProperty:value = " + value) + except KeyError: + value = None + else: + value = None + + return value + + def _attr_values(self): klass = self.__class__ result = {} diff --git a/tm.py b/tm.py index 8c59864..554b160 100755 --- a/tm.py +++ b/tm.py @@ -16,6 +16,7 @@ tm.description = "This is a sample threat model of a very simple system - a web-based comment system. The user enters comments and these are added to a database and displayed back to the user. The thought is that it is, though simple, a complete enough example to express meaningful threats." tm.isOrdered = True tm.mergeResponses = True +tm.props = { "repo_url" : "https://github.com/izar/pytm" } internet = Boundary("Internet") server_db = Boundary("Server/DB") @@ -120,3 +121,4 @@ if __name__ == "__main__": tm.process() + From 891c3944cdd0e4568c2963bd3c4fa67ac9d9a559 Mon Sep 17 00:00:00 2001 From: Nick Ozmore Date: Tue, 5 Oct 2021 13:41:04 -0400 Subject: [PATCH 2/3] Updated json serialization for dict objects, updated json test case. --- pytm/pytm.py | 1 + tests/output.json | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index 2bc2a3d..915997c 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -1873,6 +1873,7 @@ def serialize(obj, nested=False): elif ( not nested and not isinstance(value, str) + and not isinstance(value, dict) and isinstance(value, Iterable) ): value = [v.id if isinstance(v, Finding) else v.name for v in value] diff --git a/tests/output.json b/tests/output.json index 36798c1..32222a6 100644 --- a/tests/output.json +++ b/tests/output.json @@ -24,7 +24,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesIntegrity": false, "sourceFiles": [] @@ -80,7 +80,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -136,7 +136,7 @@ "outputs": [], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesIntegrity": false, "sanitizesInput": false, @@ -193,7 +193,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -255,7 +255,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -283,7 +283,7 @@ "minTLSVersion": "TLSVersion.NONE", "name": "Internet", "overrides": [], - "props": [], + "props": {}, "sourceFiles": [] }, { @@ -298,7 +298,7 @@ "minTLSVersion": "TLSVersion.NONE", "name": "Server/DB", "overrides": [], - "props": [], + "props": {}, "sourceFiles": [] } ], @@ -349,7 +349,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesIntegrity": false, "sourceFiles": [] @@ -403,7 +403,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -459,7 +459,7 @@ "outputs": [], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesIntegrity": false, "sanitizesInput": false, @@ -516,7 +516,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -578,7 +578,7 @@ ], "overrides": [], "port": -1, - "props": [], + "props": {}, "protocol": "", "providesConfidentiality": false, "providesIntegrity": false, @@ -620,7 +620,7 @@ "note": "bbb", "order": 1, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, @@ -656,7 +656,7 @@ "note": "ccc", "order": 2, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, @@ -692,7 +692,7 @@ "note": "", "order": 3, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, @@ -728,7 +728,7 @@ "note": "", "order": 4, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, @@ -764,7 +764,7 @@ "note": "", "order": 5, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, @@ -800,7 +800,7 @@ "note": "", "order": 6, "overrides": [], - "props": [], + "props": {}, "protocol": "", "response": null, "responseTo": null, From a53b843a8876ed5edcb1f67c2cb6d9373e381703 Mon Sep 17 00:00:00 2001 From: Nick Ozmore Date: Sun, 31 Oct 2021 11:57:58 -0400 Subject: [PATCH 3/3] Updated branch using latest logging improvements, added dynamic property output to advanced report, added element usage in sample tm. --- docs/advanced_template.md | 15 +++++++++++++++ pytm/pytm.py | 1 + pytm/report_util.py | 18 ++++++++++++++++++ tests/output.json | 1 + tm.py | 1 + 5 files changed, 36 insertions(+) diff --git a/docs/advanced_template.md b/docs/advanced_template.md index 5a8892d..491d8a7 100644 --- a/docs/advanced_template.md +++ b/docs/advanced_template.md @@ -4,6 +4,17 @@ {tm.description} +{tm.props:if: + +## TM Properties + +Key|Value| +|:----:|:----:| +{tm.props:call:getPair:|{{item.key}}|{{item.value}}| +} + +} + ## Dataflow Diagram - Level 0 DFD ![](sample.png) @@ -100,6 +111,10 @@ Description|{{item.description}}| In Scope|{{item.inScope}}| Type|{{item:call:getElementType}}| Finding Count|{{item:call:getFindingCount}}| +{{item.props:if:{{item.props:call:getPair:|{{{{item.key}}}}|{{{{item.value}}}}| +}} +}} + {{item.findings:if: diff --git a/pytm/pytm.py b/pytm/pytm.py index 4739b6a..a474440 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -759,6 +759,7 @@ class TM: required=False, doc="A list of assumptions about the design/model.", ) + custom_properties = dict([]) def __init__(self, name, **kwargs): for key, value in kwargs.items(): diff --git a/pytm/report_util.py b/pytm/report_util.py index 90df7de..c8a17c0 100644 --- a/pytm/report_util.py +++ b/pytm/report_util.py @@ -1,5 +1,23 @@ +class Pair: + key = ""; + value = ""; class ReportUtils: + + @staticmethod + def getPair(obj): + returnValue = [] + if (isinstance(obj, dict)): + for k, v in obj.items(): + p = Pair() + p.key = str(k) + p.value = str(v) + returnValue.append(p) + else: + return "ERROR: getPair method is not valid for " + element.__class__.__name__ + + return returnValue + @staticmethod def getParentName(element): from pytm import Boundary diff --git a/tests/output.json b/tests/output.json index b2bf39e..304c3c9 100644 --- a/tests/output.json +++ b/tests/output.json @@ -513,6 +513,7 @@ "sourceFiles": [] } ], + "custom_properties": {}, "data": [ { "carriedBy": [ diff --git a/tm.py b/tm.py index 1e4909a..ca0b23a 100755 --- a/tm.py +++ b/tm.py @@ -40,6 +40,7 @@ web.controls.encodesOutput = True web.controls.authorizesSource = False web.sourceFiles = ["pytm/json.py", "docs/template.md"] +web.props = { "Security Champion" : "John Smith" } db = Datastore("SQL Database") db.OS = "CentOS"