From 38b7cf9dc5b8a87b6bf68eba422236e0387827b6 Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Wed, 26 Jan 2022 15:33:05 +0100 Subject: [PATCH 1/9] Adding uniqueId --- .gitignore | 3 +++ pytm/pytm.py | 26 +++++++++++++++++++++++--- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f67268c8..4f28cae8 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ tm/ /tests/1.txt /tests/0.txt /tests/.config.pytm + +# Snyk cache file +.dccache diff --git a/pytm/pytm.py b/pytm/pytm.py index b18a197c..d47319e5 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -751,6 +751,7 @@ class TM: required=False, doc="A list of assumptions about the design/model.", ) + uniqueFindingIdFormat = varString("{0}-{1}", doc="Default formatting of the uniqueId of findings. Argument 0 is the uniqueId of the element the finding is related to and argument 1 is the id of the finding. E.g., if you elements is called E1 and the finding DE01, the uniqueId of the finding becomes E1-DE01. If you prefer say DE01:E1, specify the format as {1}:{0}") def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -809,7 +810,8 @@ def resolve(self): continue finding_count += 1 - f = Finding(e, id=str(finding_count), threat=t) + f = Finding(e, id=str(finding_count), threat=t, + uniqueId=self.uniqueFindingIdFormat.format(e.uniqueId, t.id)) logger.debug(f"new finding: {f}") findings.append(f) elements[e].append(f) @@ -830,6 +832,8 @@ def check(self): _apply_defaults(TM._flows, TM._data) + self._check_duplicate_uniqueId(TM._elements) + for e in TM._elements: top = Counter(f.threat_id for f in e.overrides).most_common(1) if not top: @@ -896,6 +900,21 @@ def _check_duplicates(self, flows): ) ) + def _check_duplicate_uniqueId(self, elements): + # using Counter.itervalues() + # to check all unique list elements + ids = set() + for e in elements: + if e.uniqueId != "": + if e.uniqueId in ids: + raise ValueError( + "The uniqueId '{}' is used more than once. As the uniqueId must be unique, please provide unique values".format( + e.uniqueId + ) + ) + else: + ids.add(e.uniqueId) + def _dfd_template(self): return """digraph tm {{ graph [ @@ -1295,6 +1314,7 @@ class Element: doc="Location of the source code that describes this element relative to the directory of the model script.", ) controls = varControls(None) + uniqueId = varString("", doc="A unique ID combined with thread ID that gives a unique, stable finding key which in turn can be used for synchronizing the findings with external systems.") def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -1890,8 +1910,8 @@ def encode_element_threat_data(obj): v = getattr(o, a) if (type(v) is not list or (type(v) is list and len(v) != 0)): c._safeset(a, v) - - encoded_elements.append(c) + + encoded_elements.append(c) return encoded_elements From 0db6c67b5c77b400e9e02fbe782e83bd6637eba1 Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Wed, 26 Jan 2022 16:49:44 +0100 Subject: [PATCH 2/9] saving addUniqueIdToName snapshot --- pytm/pytm.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index d47319e5..f71d2f3f 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -752,6 +752,10 @@ class TM: doc="A list of assumptions about the design/model.", ) uniqueFindingIdFormat = varString("{0}-{1}", doc="Default formatting of the uniqueId of findings. Argument 0 is the uniqueId of the element the finding is related to and argument 1 is the id of the finding. E.g., if you elements is called E1 and the finding DE01, the uniqueId of the finding becomes E1-DE01. If you prefer say DE01:E1, specify the format as {1}:{0}") + nameUniqueIdFormat = varString("{0}-{1}",doc="When addUniqueIdToName is true, this format is used to format the name. Argument 0 is the name property, argument 1 is the uniqueId. The default format is '{0} ({1})' e.g., if the name is MyServer and the uniqueId is S1, the name will become 'MyServer (S1)'. If you want another format, provide your own format string" ) + + addUniqueIdToName = varBool(False,doc="If true, the uniqueId will be added to the name of the element as defined by nameUniqueIdFormat") + _test1=nameUniqueIdFormat.data[nameUniqueIdFormat.super().instance] def __init__(self, name, **kwargs): for key, value in kwargs.items(): @@ -762,7 +766,16 @@ def __init__(self, name, **kwargs): # make sure generated diagrams do not change, makes sense if they're commited random.seed(0) + @staticmethod + def GetAddUniqueIdToName(): + return addUniqueIdToName + + @staticmethod + def GetNameUniqueIdFormat(): + return nameUniqueIdFormat + @classmethod + def reset(cls): cls._flows = [] cls._elements = [] @@ -1286,7 +1299,6 @@ def _safeset(self, attr, value): class Element: """A generic element""" - name = varString("", required=True) description = varString("") inBoundary = varBoundary(None, doc="Trust boundary this element exists in") @@ -1319,7 +1331,10 @@ class Element: def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - self.name = name + if self.uniqueId and TM.GetAddUniqueIdToName(): + self.name = TM.GetNameUniqueIdFormat().format(name, self.uniqueId) + else: + self.name = "the name" + TM._test1 self.controls = Controls() self.uuid = uuid.UUID(int=random.getrandbits(128)) self._is_drawn = False From 83f47577c79c859806395b247e40985419a5a566 Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Thu, 27 Jan 2022 17:20:28 +0100 Subject: [PATCH 3/9] Fixing nameUniqueIdFormat --- pytm/pytm.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index f71d2f3f..74f73055 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -36,6 +36,8 @@ logger = logging.getLogger(__name__) +# todo: Sorry, but I could not manage to get the value of a VarString from the Element class so I had to store it globally +nameUniqueIdFormat_shadow="shadow" class var(object): """A descriptor that allows setting a value only once""" @@ -752,15 +754,14 @@ class TM: doc="A list of assumptions about the design/model.", ) uniqueFindingIdFormat = varString("{0}-{1}", doc="Default formatting of the uniqueId of findings. Argument 0 is the uniqueId of the element the finding is related to and argument 1 is the id of the finding. E.g., if you elements is called E1 and the finding DE01, the uniqueId of the finding becomes E1-DE01. If you prefer say DE01:E1, specify the format as {1}:{0}") - nameUniqueIdFormat = varString("{0}-{1}",doc="When addUniqueIdToName is true, this format is used to format the name. Argument 0 is the name property, argument 1 is the uniqueId. The default format is '{0} ({1})' e.g., if the name is MyServer and the uniqueId is S1, the name will become 'MyServer (S1)'. If you want another format, provide your own format string" ) - - addUniqueIdToName = varBool(False,doc="If true, the uniqueId will be added to the name of the element as defined by nameUniqueIdFormat") - _test1=nameUniqueIdFormat.data[nameUniqueIdFormat.super().instance] + nameUniqueIdFormat = varString("{0}",doc="This format is used to format the name. Argument 0 is the name property, argument 1 is the uniqueId. The default format is '{0}'. If you specify the uniqueId property on your objects, you can use this to add it to the name. E.g., if the name is MyServer and the uniqueId is S1, the name will become 'MyServer (S1)' if you specify '{0} ({1})' here." ) def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) self.name = name + global nameUniqueIdFormat_shadow + nameUniqueIdFormat_shadow=self.nameUniqueIdFormat self._sf = SuperFormatter() self._add_threats() # make sure generated diagrams do not change, makes sense if they're commited @@ -1331,10 +1332,7 @@ class Element: def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - if self.uniqueId and TM.GetAddUniqueIdToName(): - self.name = TM.GetNameUniqueIdFormat().format(name, self.uniqueId) - else: - self.name = "the name" + TM._test1 + self.name = nameUniqueIdFormat_shadow.format(name, self.uniqueId) self.controls = Controls() self.uuid = uuid.UUID(int=random.getrandbits(128)) self._is_drawn = False From 31426526d34a5f61761c5097ab3cbf9a44e94088 Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Thu, 27 Jan 2022 17:31:53 +0100 Subject: [PATCH 4/9] Removing @staticmethods as they were left-overs from a test --- pytm/pytm.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index 74f73055..6359e59d 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -767,14 +767,6 @@ def __init__(self, name, **kwargs): # make sure generated diagrams do not change, makes sense if they're commited random.seed(0) - @staticmethod - def GetAddUniqueIdToName(): - return addUniqueIdToName - - @staticmethod - def GetNameUniqueIdFormat(): - return nameUniqueIdFormat - @classmethod def reset(cls): From 08e6c2e1764760fb04fd97497b7fa523023c12d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20=C3=98stergaard?= Date: Thu, 27 Jan 2022 17:37:37 +0100 Subject: [PATCH 5/9] Update pytm/pytm.py adding back spaces autoformat removed --- pytm/pytm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index 6359e59d..0aa181f3 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -1915,8 +1915,8 @@ def encode_element_threat_data(obj): v = getattr(o, a) if (type(v) is not list or (type(v) is list and len(v) != 0)): c._safeset(a, v) - - encoded_elements.append(c) + + encoded_elements.append(c) return encoded_elements From 50041281c95fabac18cf0dd441da80066fda1af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20=C3=98stergaard?= Date: Thu, 27 Jan 2022 17:38:27 +0100 Subject: [PATCH 6/9] Update pytm/pytm.py - readding empty line --- pytm/pytm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pytm/pytm.py b/pytm/pytm.py index 0aa181f3..e94e8772 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -1292,6 +1292,7 @@ def _safeset(self, attr, value): class Element: """A generic element""" + name = varString("", required=True) description = varString("") inBoundary = varBoundary(None, doc="Trust boundary this element exists in") From 0585924a1f37411cc11d7d28d19be11838638b52 Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Mon, 31 Jan 2022 13:30:33 +0100 Subject: [PATCH 7/9] Adding test test_uniqueid_two_runs --- tests/test_pytmfunc.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_pytmfunc.py b/tests/test_pytmfunc.py index 029aefbd..c1364b90 100644 --- a/tests/test_pytmfunc.py +++ b/tests/test_pytmfunc.py @@ -292,6 +292,38 @@ def test_overrides(self): ["accepted since inside the trust boundary"], ) + def test_uniqueid_two_runs(self): + # Ensure unique IDs are returned as specified and that it can be done twice in the same run + tm = TM("my test tm" ) + internet = Boundary("Internet",uniqueId="B1") + athome = Boundary("athome",uniqueId="B2") + user = Actor("user",uniqueId="U1") + badactor = Actor("badactor",uniqueId="U2") + tm.resolve() + self.assertEqual(tm._elements[0].uniqueId, "B1") + self.assertEqual(tm._elements[1].uniqueId, "B2") + self.assertEqual(tm._elements[2].uniqueId, "U1") + self.assertEqual(tm._elements[3].uniqueId, "U2") + self.assertEqual(tm._actors[0].uniqueId, "U1") + self.assertEqual(tm._actors[1].uniqueId, "U2") + self.assertEqual(tm._boundaries[0].uniqueId, "B1") + self.assertEqual(tm._boundaries[1].uniqueId, "B2") + tm.reset() + tm = TM("my test tm" ) + internet = Boundary("Internet", uniqueId="B1") + athome = Boundary("athome", uniqueId="B2") + user = Actor("user", uniqueId="U1") + badactor = Actor("badactor", uniqueId="U2") + self.assertEqual(tm._elements[0].uniqueId, "B1") + self.assertEqual(tm._elements[1].uniqueId, "B2") + self.assertEqual(tm._elements[2].uniqueId, "U1") + self.assertEqual(tm._elements[3].uniqueId, "U2") + self.assertEqual(tm._actors[0].uniqueId, "U1") + self.assertEqual(tm._actors[1].uniqueId, "U2") + self.assertEqual(tm._boundaries[0].uniqueId, "B1") + self.assertEqual(tm._boundaries[1].uniqueId, "B2") + + def test_json_dumps(self): random.seed(0) dir_path = os.path.dirname(os.path.realpath(__file__)) From d23209f8f8b5dadae3659b879149c35c20434c8e Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Wed, 2 Feb 2022 11:09:46 +0100 Subject: [PATCH 8/9] Removing _shadow field. Making TM a superclass of Element to read values. --- pytm/pytm.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index e94e8772..12ed5791 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -36,9 +36,6 @@ logger = logging.getLogger(__name__) -# todo: Sorry, but I could not manage to get the value of a VarString from the Element class so I had to store it globally -nameUniqueIdFormat_shadow="shadow" - class var(object): """A descriptor that allows setting a value only once""" @@ -760,8 +757,6 @@ def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) self.name = name - global nameUniqueIdFormat_shadow - nameUniqueIdFormat_shadow=self.nameUniqueIdFormat self._sf = SuperFormatter() self._add_threats() # make sure generated diagrams do not change, makes sense if they're commited @@ -1290,7 +1285,7 @@ def _safeset(self, attr, value): -class Element: +class Element(TM): """A generic element""" name = varString("", required=True) @@ -1325,7 +1320,7 @@ class Element: def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - self.name = nameUniqueIdFormat_shadow.format(name, self.uniqueId) + self.name = nameUniqueIdFormat.format(name, self.uniqueId) self.controls = Controls() self.uuid = uuid.UUID(int=random.getrandbits(128)) self._is_drawn = False @@ -1916,8 +1911,8 @@ def encode_element_threat_data(obj): v = getattr(o, a) if (type(v) is not list or (type(v) is list and len(v) != 0)): c._safeset(a, v) - - encoded_elements.append(c) + + encoded_elements.append(c) return encoded_elements From b99d4cc3ce003d502d57d27ddea157956dc0ad7a Mon Sep 17 00:00:00 2001 From: Per Oestergaard Date: Wed, 2 Feb 2022 11:11:18 +0100 Subject: [PATCH 9/9] Adding forgotten self. --- pytm/pytm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pytm/pytm.py b/pytm/pytm.py index 12ed5791..6967d065 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -1320,7 +1320,7 @@ class Element(TM): def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - self.name = nameUniqueIdFormat.format(name, self.uniqueId) + self.name = self.nameUniqueIdFormat.format(name, self.uniqueId) self.controls = Controls() self.uuid = uuid.UUID(int=random.getrandbits(128)) self._is_drawn = False