diff --git a/.gitignore b/.gitignore index f67268c8..997e21cb 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ tm/ /tests/1.txt /tests/0.txt /tests/.config.pytm + +# Snyk cache +.dccache diff --git a/pytm/pytm.py b/pytm/pytm.py index b18a197c..9d0e5875 100644 --- a/pytm/pytm.py +++ b/pytm/pytm.py @@ -652,6 +652,8 @@ class Finding: """, ) cvss = varString("", required=False, doc="The CVSS score and/or vector") + uniqueId = varString( + "", doc="When order is present and includeOrder is true on the object, this will be formatted as findingId:order. E.g. if finding is INP01 and order is 123, the value becomes INP01:123.") def __init__( self, @@ -809,7 +811,12 @@ def resolve(self): continue finding_count += 1 - f = Finding(e, id=str(finding_count), threat=t) + if e.includeOrder is True and e.order != -1: + uniqueId="{}:{}".format(t.id,e.order) + else: + uniqueId=str(finding_count) + + f = Finding(e, id=str(finding_count), threat=t, uniqueId=uniqueId) logger.debug(f"new finding: {f}") findings.append(f) elements[e].append(f) @@ -1295,11 +1302,15 @@ class Element: doc="Location of the source code that describes this element relative to the directory of the model script.", ) controls = varControls(None) + includeOrder = varBool( + False, doc="If True and Order is set (not -1), the displayed name will be formatted as 'order:name'. If you make Order unique, this will give you a stable reference you can use for synchronization etc.") + order = varInt(-1, doc="Number of this element in the threat model") def __init__(self, name, **kwargs): for key, value in kwargs.items(): setattr(self, key, value) - self.name = name + if self.includeOrder is True: + self.name = "{}:{}".format(self.order, name) self.controls = Controls() self.uuid = uuid.UUID(int=random.getrandbits(128)) self._is_drawn = False @@ -1717,7 +1728,10 @@ def __init__(self, source, sink, name, **kwargs): def display_name(self): if self.order == -1: return self.name - return "({}) {}".format(self.order, self.name) + elif self.includeOrder is True: # order is already included in name + return self.name + else: + return "({}) {}".format(self.order, self.name) def _dfd_template(self): return """{source} -> {sink} [ @@ -1890,8 +1904,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 @@ -1909,6 +1923,7 @@ def encode_threat_data(obj): "threat_id", "references", "condition", + "uniqueId" ] if type(obj) is Finding or (len(obj) != 0 and type(obj[0]) is Finding): diff --git a/tests/test_pytmfunc.py b/tests/test_pytmfunc.py index 029aefbd..5ee10fde 100644 --- a/tests/test_pytmfunc.py +++ b/tests/test_pytmfunc.py @@ -186,6 +186,41 @@ def test_dfd_duplicates_raise(self): with self.assertRaisesRegex(ValueError, e): tm.check() + def test_order_in_finding(self): + random.seed(0) + + TM.reset() + order=1234 + threat_name = "INP03" + formatted_name="{0}:{1}".format(threat_name,order) + + tm = TM("my test tm", description="aaa") + web = Server("Web") + web.includeOrder= True + web.order=order + + tm.resolve() + + self.assertIn(formatted_name, [t.uniqueId for t in tm.findings]) + + def test_order_not_in_finding_no_includeorder(self): + random.seed(0) + + TM.reset() + order=1234 + threat_name = "INP03" + formatted_name="{0}:{1}".format(threat_name,order) + + tm = TM("my test tm", description="aaa") + web = Server("Web") + web.includeOrder= False + web.order=order + + tm.resolve() + + self.assertIn(("1","INP03"), [(t.uniqueId,t.threat_id) for t in tm.findings]) + self.assertNotIn((formatted_name,"INP03"), [(t.uniqueId,t.threat_id) for t in tm.findings]) + def test_exclude_threats_ignore(self): random.seed(0)