diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3f66abe..2e8e2719 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,10 @@ curl -X POST 'http://localhost:8000/unmodified'\
-d @path_to/scanreport.xml
```
- add stack bar chart possibility [#33](https://github.com/greenbone/pheme/pull/33/)
+- create a markdown table description of scanreport model [#37](https://github.com/greenbone/pheme/pull/37/)
+```
+curl -H 'accept: text/markdown+table' localhost:8000/scanreport/data/description
+```
### Changed
### Deprecated
### Removed
diff --git a/pheme/renderer.py b/pheme/renderer.py
index 940a3a45..1b8e1997 100644
--- a/pheme/renderer.py
+++ b/pheme/renderer.py
@@ -20,6 +20,59 @@
import xmltodict
+class MarkDownTableRenderer(BaseRenderer):
+ media_type = 'text/markdown+table'
+ format = 'text'
+ charset = 'utf-8'
+
+ def __as_md(self, previous_key, data):
+ def append(value):
+ return isinstance(value, str) or isinstance(value, int)
+
+ if not (isinstance(data, dict) or isinstance(data, list)):
+ return []
+ items = None
+ result = []
+
+ def to_new_key(key: str):
+ if not previous_key:
+ return key
+ if not key:
+ return previous_key
+ return "{}.{}".format(previous_key, key)
+
+ if isinstance(data, list):
+ result.append(
+ "|{}|{}| a list of items|".format(
+ previous_key,
+ "{{% for item in {} %}} ... {{% endfor %}}".format(
+ previous_key
+ ),
+ )
+ )
+ previous_key = 'item'
+ items = [("", v) for v in data]
+ else:
+ items = data.items()
+ for key, value in items:
+ new_key = to_new_key(key)
+ if append(value):
+ result.append(
+ "|{}|{}|{}|".format(new_key, "{{ %s }}" % new_key, value)
+ )
+ else:
+ result = result + self.__as_md(new_key, value)
+ return result
+
+ def render(self, data, accepted_media_type=None, renderer_context=None):
+ if data is None:
+ return ''
+ first_line = "|key|template_example|description|\n"
+ table_indicator = "| :- | :--: | -: |\n"
+ rest = "\n".join(self.__as_md("", data))
+ return first_line + table_indicator + rest
+
+
class XMLRenderer(BaseRenderer):
media_type = 'application/xml'
format = 'xml'
diff --git a/pheme/transformation/scanreport/model.py b/pheme/transformation/scanreport/model.py
index debb1f55..1748cd01 100644
--- a/pheme/transformation/scanreport/model.py
+++ b/pheme/transformation/scanreport/model.py
@@ -17,8 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
# pylint: disable=W0614,W0511,W0401,C0103
-from typing import Dict, List, Union
from dataclasses import dataclass
+from typing import Dict, List, Union
@dataclass
@@ -135,3 +135,163 @@ class Report:
vulnerability_overview: VulnerabilityOverview
host_overviews: List[HostOverview]
results: Results
+
+
+def descripe():
+ return Report(
+ id="str; identifier of a report",
+ summary=Summary(
+ scan=Scan(
+ name="str; name of the scan",
+ start="str; start date",
+ duration="str; end date (needs to be renamend)",
+ hosts_scanned="str; number of scanned hosts",
+ comment="str; comment of a report",
+ ),
+ report=SummaryReport(
+ applied_filter="str; applied filter of reports",
+ severities=[
+ "str; High",
+ "str; Medium",
+ "str; Low",
+ ],
+ timezone="str; the timezone of the report",
+ ),
+ results=None,
+ ),
+ common_vulnerabilities=[
+ CountGraph(
+ name="High",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ NVTCount(
+ oid="str; oid of nvt",
+ amount="int; amount of nvts with severity high",
+ name="str; name of nvt",
+ )
+ ],
+ ),
+ CountGraph(
+ name="Medium",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ NVTCount(
+ oid="str; oid of nvt",
+ amount="int; amount of nvts within severityh",
+ name="str; name of nvt",
+ )
+ ],
+ ),
+ CountGraph(
+ name="Low",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ NVTCount(
+ oid="str; oid of nvt",
+ amount="int; amount of nvts within severityh",
+ name="str; name of nvt",
+ )
+ ],
+ ),
+ ],
+ vulnerability_overview=VulnerabilityOverview(
+ hosts=CountGraph(
+ name="str; hosts",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ HostCount(
+ ip="str; IP Address of the host",
+ amount="int; amount of found security breaches",
+ name="str; hostname",
+ )
+ ],
+ ),
+ network_topology=None,
+ ports=CountGraph(
+ name="str; ports",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ PortCount(
+ port="str; port (e.g. tcp/20)",
+ amount="int; amount of found security breaches",
+ )
+ ],
+ ),
+ cvss_distribution_ports=CountGraph(
+ name="str; cvss distribution ports",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ CVSSDistributionCount(
+ identifier="ports",
+ amount="int; amount of cvss across ports in report",
+ cvss="str; cvss",
+ ),
+ ],
+ ),
+ cvss_distribution_hosts=CountGraph(
+ name="str; cvss distribution hosts",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ CVSSDistributionCount(
+ identifier="hosts",
+ amount="int; amount of cvss across hosts in report",
+ cvss="str; cvss",
+ ),
+ ],
+ ),
+ cvss_distribution_vulnerabilities=CountGraph(
+ name="str; cvss distribution hosts",
+ chart="str; link to chart image (base64 encoded datalink)",
+ counts=[
+ CVSSDistributionCount(
+ identifier="ports",
+ amount="int; amount of cvss across hosts in report",
+ cvss="str; cvss",
+ ),
+ ],
+ ),
+ ),
+ host_overviews=[
+ HostOverview(
+ host="str; ip address of the host",
+ highest_severity="str; highest severity of the host",
+ counts=[
+ SeverityCount(
+ severity="str; High", amount="int; amount of severity"
+ ),
+ SeverityCount(
+ severity="str; Medium", amount="int; amount of severity"
+ ),
+ SeverityCount(
+ severity="str; Low", amount="int; amount of severity"
+ ),
+ ],
+ )
+ ],
+ results=Results(
+ max="str; max results",
+ start="str; start of results",
+ scans=[
+ HostResults(
+ host="str; ip address of host",
+ results={
+ 'nvt.oid': 'str; nvt.oid; optional',
+ 'nvt.type': 'str; nvt.type; optional',
+ 'nvt.name': 'str; nvt.name; optional',
+ 'nvt.family': 'str; nvt.family; optional',
+ 'nvt.cvss_base': 'str; nvt.cvss_base; optional',
+ 'nvt.tags': 'str; nvt.tags; optional',
+ 'nvt.refs.ref': 'str; nvt.refs.ref; optional',
+ 'nvt.solution.type': 'str; nvt.solution.type; optional',
+ 'nvt.solution.text': 'str; nvt.solution.text; optional',
+ 'port': 'str; port; optional',
+ 'threat': 'str; threat; optional',
+ 'severity': 'str; severity; optional',
+ 'qod.value': 'str; qod.value; optional',
+ 'qod.type': 'str; qod.type; optional',
+ 'description': 'str; description; optional',
+ },
+ )
+ ],
+ ),
+ )
diff --git a/pheme/urls.py b/pheme/urls.py
index a4a692cb..60862ed8 100644
--- a/pheme/urls.py
+++ b/pheme/urls.py
@@ -42,6 +42,11 @@
path('unmodified', pheme.views.unmodified, name='unmodified'),
path('transform', pheme.views.transform, name='transform'),
path('transform/', pheme.views.transform),
+ path(
+ 'scanreport/data/description',
+ pheme.views.scanreport_data_description,
+ name='scanreport_data_description',
+ ),
path('report/', pheme.views.report, name='report'),
path('report//', pheme.views.report),
re_path(
diff --git a/pheme/views.py b/pheme/views.py
index 7e6ea37c..046bd8db 100644
--- a/pheme/views.py
+++ b/pheme/views.py
@@ -23,6 +23,8 @@
from pheme.parser.xml import XMLParser
from pheme.transformation import scanreport
from pheme.storage import store, load
+from pheme.renderer import MarkDownTableRenderer
+from pheme.transformation.scanreport import model
@api_view(['POST'])
@@ -61,3 +63,14 @@ def unmodified(request):
)
def report(request, name: str):
return Response(load(name))
+
+
+@api_view(['GET'])
+@renderer_classes(
+ [
+ rest_framework.renderers.JSONRenderer,
+ MarkDownTableRenderer,
+ ]
+)
+def scanreport_data_description(request):
+ return Response(dataclasses.asdict(model.descripe()))
diff --git a/poetry.lock b/poetry.lock
index 6bbc135a..3c6d41c9 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -746,7 +746,7 @@ security = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)"]
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7)", "win-inet-pton"]
[[package]]
-category = "dev"
+category = "main"
description = "a python refactoring library..."
name = "rope"
optional = false
@@ -903,7 +903,7 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
testing = ["jaraco.itertools", "func-timeout"]
[metadata]
-content-hash = "a9d3ae5be2264b86cf8780e5a3918e2085f03b60222717bf7bdbe3ffdfd4821a"
+content-hash = "9c77ea731b7d0a5afe8623aee75a9b1730e1b202c70fc507aba13c1492de62ea"
lock-version = "1.0"
python-versions = "^3.7"
diff --git a/pyproject.toml b/pyproject.toml
index 23916478..69aed74d 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,6 +34,7 @@ pytest-django = "^3.9.0"
weasyprint = "^51"
matplotlib = "^3.3.1"
pandas = "^1.1.2"
+rope = "^0.17.0"
[tool.poetry.dev-dependencies]
pylint = "^2.6.0"
diff --git a/tests/test_report_data_description.py b/tests/test_report_data_description.py
new file mode 100644
index 00000000..2d636d7e
--- /dev/null
+++ b/tests/test_report_data_description.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+# tests/test_report_generation.py
+# Copyright (C) 2020 Greenbone Networks GmbH
+#
+# SPDX-License-Identifier: AGPL-3.0-or-later
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+
+from rest_framework.test import APIClient
+from django.urls import reverse
+
+
+def test_markdown_description_without_error():
+ client = APIClient()
+ url = reverse('scanreport_data_description')
+ response = client.get(url, format='text/markdown+table')
+ assert response.status_code == 200