Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TemplateEngine improvements, updated template.md #155

Merged
merged 10 commits into from
Oct 31, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ plantuml.jar
tm/
/sqldump
/tests/output_current.json
/tests/output_current.md
/tests/1.txt
/tests/0.txt
/tests/.config.pytm
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The `tm.py` is an example model. You can run it to generate the report and diagr

```
mkdir -p tm
./tm.py --report docs/template.md | pandoc -f markdown -t html > tm/report.html
./tm.py --report docs/basic_template.md | pandoc -f markdown -t html > tm/report.html
./tm.py --dfd | dot -Tpng -o tm/dfd.png
./tm.py --seq | java -Djava.awt.headless=true -jar $PLANTUML_PATH -tpng -pipe > tm/seq.png
```
Expand Down Expand Up @@ -213,7 +213,7 @@ The diagrams and findings can be included in the template to create a final repo

```bash

tm.py --report docs/template.md | pandoc -f markdown -t html > report.html
tm.py --report docs/basic_template.md | pandoc -f markdown -t html > report.html

```
The templating format used in the report template is very simple:
Expand Down
10 changes: 7 additions & 3 deletions docs/Stylesheet.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ hr {
opacity: .5;
}
table {
margin: .75rem 0;
padding: 0;
margin: .75rem 0 0 1rem;
padding: 0;
width: 50%;
text-align: left;
white-space: nowrap;
Expand Down Expand Up @@ -76,4 +76,8 @@ table tr td {
text-align: left;
border: 1px solid #ccc;
}
/* @end */

details {
margin-left: 2rem
}
/* @end */
160 changes: 160 additions & 0 deletions docs/advanced_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
<link href="docs/Stylesheet.css" rel="stylesheet"></link>

## System Description

{tm.description}

## Dataflow Diagram - Level 0 DFD

![](sample.png)

&nbsp;

## Dataflows

Name|From|To |Data|Protocol|Port
|:----:|:----:|:---:|:----:|:--------:|:----:|
{dataflows:repeat:|{{item.display_name:call:}}|{{item.source.name}}|{{item.sink.name}}|{{item.data}}|{{item.protocol}}|{{item.dstPort}}|
}

## Data Dictionary

Name|Description|Classification|Carried|Processed
|:----:|:--------:|:----:|:----|:----|
{data:repeat:|{{item.name}}|{{item.description}}|{{item.classification.name}}|{{item.carriedBy:repeat:{{{{item.name}}}}<br>}}|{{item.processedBy:repeat:{{{{item.name}}}}<br>}}|
}

## Actors

{actors:repeat:
Name|{{item.name}}
|:----|:----|
Description|{{item.description}}|
Is Admin|{{item.isAdmin}}
Finding Count|{{item:call:getFindingCount}}|

{{item.findings:if:

**Threats**

{{item.findings:repeat:
<details>
<summary> {{{{item.id}}}} -- {{{{item.threat_id}}}} -- {{{{item.description}}}}</summary>
<h6> Targeted Element </h6>
<p> {{{{item.target}}}} </p>
<h6> Severity </h6>
<p>{{{{item.severity}}}}</p>
<h6>Example Instances</h6>
<p>{{{{item.example}}}}</p>
<h6>Mitigations</h6>
<p>{{{{item.mitigations}}}}</p>
<h6>References</h6>
<p>{{{{item.references}}}}</p>
&emsp;
</details>
}}
}}
}

## Boundaries

{boundaries:repeat:
Name|{{item.name}}
|:----|:----|
Description|{{item.description}}|
In Scope|{{item.inScope}}|
Immediate Parent|{{item.parents:if:{{item:call:getParentName}}}}{{item.parents:not:N/A, primary boundary}}|
All Parents|{{item.parents:call:{{{{item.display_name:call:}}}}, }}|
Classification|{{item.maxClassification}}|
Finding Count|{{item:call:getFindingCount}}|

{{item.findings:if:

**Threats**

{{item.findings:repeat:
<details>
<summary> {{{{item.id}}}} -- {{{{item.threat_id}}}} -- {{{{item.description}}}}</summary>
<h6> Targeted Element </h6>
<p> {{{{item.target}}}} </p>
<h6> Severity </h6>
<p>{{{{item.severity}}}}</p>
<h6>Example Instances</h6>
<p>{{{{item.example}}}}</p>
<h6>Mitigations</h6>
<p>{{{{item.mitigations}}}}</p>
<h6>References</h6>
<p>{{{{item.references}}}}</p>
&emsp;
</details>
}}
}}
}

## Assets

{assets:repeat:
Name|{{item.name}}|
|:----|:----|
Description|{{item.description}}|
In Scope|{{item.inScope}}|
Type|{{item:call:getElementType}}|
Finding Count|{{item:call:getFindingCount}}|

{{item.findings:if:

**Threats**

{{item.findings:repeat:
<details>
<summary> {{{{item.id}}}} -- {{{{item.threat_id}}}} -- {{{{item.description}}}}</summary>
<h6> Targeted Element </h6>
<p> {{{{item.target}}}} </p>
<h6> Severity </h6>
<p>{{{{item.severity}}}}</p>
<h6>Example Instances</h6>
<p>{{{{item.example}}}}</p>
<h6>Mitigations</h6>
<p>{{{{item.mitigations}}}}</p>
<h6>References</h6>
<p>{{{{item.references}}}}</p>
&nbsp;
</details>
}}
}}
}

## Data Flows

{dataflows:repeat:
Name|{{item.name}}
|:----|:----|
Description|{{item.description}}|
Sink|{{item.sink}}|
Source|{{item.source}}|
Is Response|{{item.isResponse}}|
In Scope|{{item.inScope}}|
Finding Count|{{item:call:getFindingCount}}|

{{item.findings:if:

**Threats**

{{item.findings:repeat:
<details>
<summary> {{{{item.id}}}} -- {{{{item.threat_id}}}} -- {{{{item.description}}}}</summary>
<h6> Targeted Element </h6>
<p> {{{{item.target}}}} </p>
<h6> Severity </h6>
<p>{{{{item.severity}}}}</p>
<h6>Example Instances</h6>
<p>{{{{item.example}}}}</p>
<h6>Mitigations</h6>
<p>{{{{item.mitigations}}}}</p>
<h6>References</h6>
<p>{{{{item.references}}}}</p>
&emsp;
</details>
}}
}}
}
2 changes: 1 addition & 1 deletion docs/template.md → docs/basic_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Name|Description|Classification

|{findings:repeat:
<details>
<summary> {{item.id}} -- {{item.description}}</summary>
<summary> {{item.threat_id}} -- {{item.description}}</summary>
<h6> Targeted Element </h6>
<p> {{item.target}} </p>
<h6> Severity </h6>
Expand Down
39 changes: 39 additions & 0 deletions pytm/report_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

class ReportUtils:
@staticmethod
def getParentName(element):
nozmore marked this conversation as resolved.
Show resolved Hide resolved
from pytm import Boundary
if (isinstance(element, Boundary)):
parent = element.inBoundary
if (parent is not None):
return parent.name
else:
return str("")
else:
return "ERROR: getParentName method is not valid for " + element.__class__.__name__


@staticmethod
def getNamesOfParents(element):
from pytm import Boundary
if (isinstance(element, Boundary)):
parents = [p.name for p in element.parents()]
return parents
else:
return "ERROR: getNamesOfParents method is not valid for " + element.__class__.__name__

@staticmethod
def getFindingCount(element):
from pytm import Element
if (isinstance(element, Element)):
return str(len(list(element.findings)))
else:
return "ERROR: getFindingCount method is not valid for " + element.__class__.__name__

@staticmethod
def getElementType(element):
from pytm import Element
if (isinstance(element, Element)):
return str(element.__class__.__name__)
else:
return "ERROR: getElementType method is not valid for " + element.__class__.__name__
73 changes: 69 additions & 4 deletions pytm/template_engine.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# shamelessly lifted from https://makina-corpus.com/blog/metier/2016/the-worlds-simplest-python-template-engine
# but modified to include support to call methods which return lists, to call external utility methods, use
# if operator with methods and added a not operator.

import string

Expand All @@ -7,14 +9,77 @@ class SuperFormatter(string.Formatter):
"""World's simplest Template engine."""

def format_field(self, value, spec):

spec_parts = spec.split(":")
if spec.startswith("repeat"):
# Example usage, format, count of spec_parts, exampple format
# object:repeat:template 2 {item.findings:repeat:{{item.id}}, }

template = spec.partition(":")[-1]
if type(value) is dict:
value = value.items()
return "".join([self.format(template, item=item) for item in value])
elif spec == "call":
return value()
elif spec.startswith("if"):
return (value and spec.partition(":")[-1]) or ""

elif spec.startswith("call:") and hasattr(value, "__call__"):
# Example usage, format, exampple format
# methood:call {item.display_name:call:}
# methood:call:template {item.parents:call:{{item.name}}, }
result = value()

if type(result) is list:
template = spec.partition(":")[-1]
return "".join([self.format(template, item=item) for item in result])

return result

elif spec.startswith("call:"):
# Example usage, format, exampple format
# object:call:method_name {item:call:getFindingCount}
# object:call:method_name:template {item:call:getNamesOfParents:
# {{item}}
# }

method_name = spec_parts[1]

result = self.call_util_method(method_name, value)

if type(result) is list:
template = spec.partition(":")[-1]
template = template.partition(":")[-1]
return "".join([self.format(template, item=item) for item in result])

return result

elif (spec.startswith("if") or spec.startswith("not")):
# Example usage, format, exampple format
# object.bool:if:template {item.inScope:if:Is in scope}
# object:if:template {item.findings:if:Has Findings}
# object.method:if:template {item.parents:if:Has Parents}
#
# object.bool:not:template {item.inScope:not:Is not in scope}
# object:not:template {item.findings:not:Has No Findings}
# object.method:not:template {item.parents:not:Has No Parents}

template = spec.partition(":")[-1]
if (hasattr(value, "__call__")):
result = value()
else:
result = value

if (spec.startswith("if")):
return (result and template or "")
else:
return (not result and template or "")

else:
return super(SuperFormatter, self).format_field(value, spec)

def call_util_method(self, method_name, object):
module_name = "pytm.report_util"
klass_name = "ReportUtils"
module = __import__(module_name, fromlist=['ReportUtils'])
klass = getattr(module, klass_name)
method = getattr(klass, method_name)

result = method(object)
return result
Binary file modified sample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion tests/test_pytmfunc.py
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,10 @@ def test_report(self):
Dataflow(worker, db, "Query for tasks")

self.assertTrue(tm.check())
output = tm.report("docs/template.md")
output = tm.report("docs/basic_template.md")

with open(os.path.join(dir_path, "output_current.md"), "w") as x:
x.write(output)

with open(os.path.join(dir_path, "output_current.md"), "w") as x:
x.write(output)
Expand Down
2 changes: 2 additions & 0 deletions tm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
]

internet = Boundary("Internet")

server_db = Boundary("Server/DB")
server_db.levels = [2]

vpc = Boundary("AWS VPC")

user = Actor("User")
Expand Down