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

Add endpoint for scanreport model description #37

Merged
merged 3 commits into from
Sep 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
53 changes: 53 additions & 0 deletions pheme/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could have been

return isinstance(value, (str, 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'
Expand Down
162 changes: 161 additions & 1 deletion pheme/transformation/scanreport/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# pylint: disable=W0614,W0511,W0401,C0103
from typing import Dict, List, Union
from dataclasses import dataclass
from typing import Dict, List, Union


@dataclass
Expand Down Expand Up @@ -135,3 +135,163 @@ class Report:
vulnerability_overview: VulnerabilityOverview
host_overviews: List[HostOverview]
results: Results


def descripe():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this is a typo and the function should be named describe?

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',
},
)
],
),
)
5 changes: 5 additions & 0 deletions pheme/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<str:name>', pheme.views.report, name='report'),
path('report/<str:name>/', pheme.views.report),
re_path(
Expand Down
13 changes: 13 additions & 0 deletions pheme/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Expand Down Expand Up @@ -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()))
4 changes: 2 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pytest-django = "^3.9.0"
weasyprint = "^51"
matplotlib = "^3.3.1"
pandas = "^1.1.2"
rope = "^0.17.0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is rope a direct dependency now or should it still be a dev dependency?


[tool.poetry.dev-dependencies]
pylint = "^2.6.0"
Expand Down
28 changes: 28 additions & 0 deletions tests/test_report_data_description.py
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.

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