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

change barchart to distribution chart #45

Merged
merged 4 commits into from
Sep 30, 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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ curl -X POST 'http://localhost:8000/unmodified'\
-H 'Accept: application/json'\
-d @path_to/scanreport.xml
```
- add stack bar chart possibility [#33](https://github.com/greenbone/pheme/pull/33/)
- add distribution chart possibility [#33](https://github.com/greenbone/pheme/pull/45/)
- 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
Expand Down
2 changes: 1 addition & 1 deletion pheme/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
TEMPLATE_DIR = BASE_DIR / 'template'
TEMPLATE_LOGO_ADDRESS = os.environ.get(
"TEMPLATE_LOGO_ADDRESS"
) or 'file://{}/logo.jpg'.format(STATIC_DIR)
) or '//{}/logo.jpg'.format(STATIC_DIR)
TEMPLATE_COVER_IMAGE_ADDRESS = os.environ.get(
"TEMPLATE_COVER_IMAGE_ADDRESS"
) or '//{}/cover_image.png'.format(STATIC_DIR)
Expand Down
103 changes: 85 additions & 18 deletions pheme/transformation/scanreport/gvmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,18 @@
import io
import logging
import urllib
from typing import Any, Union, Callable, Dict, List, Optional
from typing import Any, Callable, Dict, List, Optional, Union

import numpy as np
import squarify
import pandas as pd
import squarify
from matplotlib.figure import Figure
from pandas import DataFrame
from pandas.core.series import Series

from pheme.transformation.scanreport.model import (
CountGraph,
Equipment,
HostCount,
HostResults,
Overview,
Report,
Expand Down Expand Up @@ -95,6 +94,56 @@ def set_plot(ax):
return __create_chart(set_plot)


def __create_distribution_chart(results: DataFrame):
def set_plot(ax):
labels = results.index.values.tolist()
data = results.values
data_cum = data.cumsum(axis=1)
ax.invert_yaxis()
ax.xaxis.set_visible(False)
ax.set_xlim(0, np.sum(data, axis=1).max())
# guardian when results does not contain all severity classes
col_colors = [
(k, __severity_class_colors.get(k, 'tab:white'))
for k in results.columns
]

for i, (colname, color) in enumerate(col_colors):
widths = data[:, i]
starts = data_cum[:, i] - widths
ax.barh(
labels,
widths,
left=starts,
height=0.5,
label=colname,
color=color,
)
xcenters = starts + widths / 2
text_color = 'white'
for y_scalar, (x_scalar, count) in enumerate(zip(xcenters, widths)):
ax.text(
x_scalar,
y_scalar,
str(int(count)),
ha='center',
va='center',
color=text_color,
)
ax.legend(
ncol=len(__severity_class_colors.items()),
bbox_to_anchor=(0, 1),
loc='lower left',
fontsize='small',
)

def create_fig():
fig = Figure(figsize=(9.2, 5))
return fig

return __create_chart(set_plot, fig=create_fig)


def __create_tree_chart(values, labels, *, colors=None) -> Optional[str]:
def set_plot(ax):
squarify.plot(sizes=values, label=labels, color=colors, pad=True, ax=ax)
Expand Down Expand Up @@ -138,22 +187,24 @@ def __severity_class_to_color(severity_classes: List[str]):


def __create_host_top_ten(result_series_df: DataFrame) -> CountGraph:
threat = result_series_df.get(['host.text', 'threat'])
if threat is None:

host_threat = result_series_df.get(['host.text', 'threat'])
if host_threat is None:
return None
host_threat = host_threat.value_counts().unstack('threat').fillna(0)
host_threat['sum'] = host_threat.sum(axis=1)
host_threat = (
host_threat.sort_values(by='sum', ascending=False)
.head(10)
.loc[:, host_threat.columns != 'sum']
)

counted = threat.value_counts().head(10)
return CountGraph(
name="host_top_ten",
chart=__create_bar_h_chart(
counted.unstack('threat'),
stacked=True,
colors=__severity_class_colors,
chart=__create_distribution_chart(
host_threat,
),
counts=[
HostCount(ip=k[0], amount=v, name=None)
for k, v in counted.to_dict().items()
],
counts=[],
)


Expand All @@ -174,7 +225,9 @@ def __create_nvt(result_series_df: DataFrame) -> CountGraph:
)


def __create_results(report: DataFrame, os_lookup: DataFrame) -> List[Dict]:
def __create_results(
report: DataFrame, os_lookup: DataFrame, ports_lookup: DataFrame
) -> List[Dict]:
try:
grouped_host = report.groupby('host.text')
wanted_columns = [
Expand Down Expand Up @@ -218,6 +271,12 @@ def normalize_ref_value(ref_val) -> List[str]:
os_lookup['ip'] == host_text, 'detail'
].values
os = may_os[0].value.all() if len(may_os) > 0 else None
ports = []
if ports_lookup is not None:
ports = ports_lookup.query(
'host == "{}"'.format(host_text)
).get('text')
ports = ports.values.tolist() if ports is not None else []
for key, series in flat_results.items():
for i, value in enumerate(series):
if not (isinstance(value, float) and np.isnan(value)):
Expand All @@ -235,7 +294,7 @@ def normalize_ref_value(ref_val) -> List[str]:
results.append(
HostResults(
host=host_text,
equipment=Equipment(os=os, ports=[]),
equipment=Equipment(os=os, ports=ports),
results=result,
)
)
Expand Down Expand Up @@ -313,12 +372,20 @@ def transform(data: Dict[str, str]) -> Report:
host_df = host_df.get(['ip', 'detail'])
if host_df is not None:
host_df = host_df.applymap(__filter_os_based_on_name)
results = __create_results(result_series_df, host_df)
ports = n_df.get('ports.port')
if ports is not None:
ports = ports.map(pd.json_normalize).all()

results = __create_results(result_series_df, host_df, ports)
task = report.get('task') or {}
gmp = report.get('gmp') or {}
logger.info("data transformation")
return Report(
report.get('id'),
None,
task.get('name'),
task.get('comment'),
gmp.get('version'),
report.get('scan_start'),
Overview(
hosts=__create_host_top_ten(result_series_df),
nvts=__create_nvt(result_series_df),
Expand Down
20 changes: 17 additions & 3 deletions pheme/transformation/scanreport/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,21 @@ class Overview:
@dataclass
class Report:
id: str
name: str
comment: str
version: str
start: str
overview: Overview
results: List[HostResults]


def descripe():
return Report(
id="str; identifier of a report",
name="str; name of the scan",
comment="str; comment of the scan",
version="str; version of gvmd",
start="str; datetime of a scan start",
overview=Overview(
hosts=CountGraph(
name="host_top_ten",
Expand Down Expand Up @@ -115,12 +121,20 @@ def descripe():
'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.tags_interpreted': {
'name_of_tag': 'str; value of a tag ()',
'may_contain_multiple_tags': 'str; depends on xml',
'solution': 'str; solution the fix the problem',
'solution_type': 'str; solution type',
'insight': 'str; insight into the problem',
'vuldetect': 'str; ',
'cvss_base_vector': 'str, cvss base vector',
},
'nvt.refs.ref': [{'name_of_ref': ["str; ref_vallue"]}],
'nvt.solution.type': 'str; nvt.solution.type; optional',
'nvt.solution.text': 'str; nvt.solution.text; optional',
'port': 'str; port; optional',
'threat': 'str; threat; optional',
'threat': 'str; severity class of nvt',
'severity': 'str; severity; optional',
'qod.value': 'str; qod.value; optional',
'qod.type': 'str; qod.type; optional',
Expand Down
38 changes: 19 additions & 19 deletions pheme/transformation/scanreport/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,30 @@


class DetailScanReport(renderers.BaseRenderer):
def _get_css(self, name: str) -> CSS:

background_image = urllib.parse.quote(
base64.b64encode(
Path(settings.TEMPLATE_COVER_IMAGE_ADDRESS).read_bytes()
def _load_design_elemets(self):
def as_datalink(location: str, *, file_format: str = None) -> str:
if not file_format:
file_format = location.split('.')[-1]
if file_format == 'svg':
file_format += '+xml'
img = urllib.parse.quote(
base64.b64encode(Path(location).read_bytes())
)
)
return 'data:image/{};base64,{}'.format(file_format, img)

indicator_image = urllib.parse.quote(
base64.b64encode(
Path('/{}/heading.svg'.format(settings.STATIC_DIR)).read_bytes()
)
)
return {
'logo': as_datalink(settings.TEMPLATE_LOGO_ADDRESS),
'cover_image': as_datalink(settings.TEMPLATE_COVER_IMAGE_ADDRESS),
'indicator': as_datalink(
'/{}/heading.svg'.format(settings.STATIC_DIR)
),
}

return loader.get_template(name).render(
{
'background_image': 'data:image/png;base64,' + background_image,
'indicator': 'data:image/svg+xml;base64,' + indicator_image,
}
)
def _get_css(self, name: str) -> CSS:

return loader.get_template(name).render(self._load_design_elemets())

def _enrich(self, name: str, data: Dict) -> Dict:
data['logo'] = settings.TEMPLATE_LOGO_ADDRESS
data['grouping'] = 'host'
data['internal_name'] = name
return data

Expand Down
2 changes: 1 addition & 1 deletion template/html_report.css
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
content: none; } }

@page :first {
background: url({{ background_image }}) no-repeat;
background: url({{ cover_image}}) no-repeat;
background-color: #e6e6e6;
background-size: cover;
margin: 0; }
Expand Down
2 changes: 1 addition & 1 deletion template/pdf_report.css
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ html body h4 {
}

html body article#cover {
background: url({{ background_image }}) no-repeat;
background: url({{ cover_image}}) no-repeat;
background-size: 100%;
align-content: space-between;
height: 297mm;
Expand Down
9 changes: 3 additions & 6 deletions template/scan_report.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<h1>Security Report</h1>
<p>Scan Name: {{ name }}</p>
<p>Scan Comment: {{ comment }}</p>
<p>Scan Date: {{ date }}</p>
<p>Scan Date: {{ start }}</p>
<p>Hosts scanned: {{ hosts_amount }}</p>

</article>
Expand All @@ -28,11 +28,8 @@ <h3>Top 10 Hosts</h3>
<article id="finding">
{% for scan in results%}
<h3 class="host-title">{{ scan.host }}</h3>
<p>score: {{ severity }}</p>
<p>cpu:</p>
<p>o/s:</p>
<p>Memory:</p>
<p>Ports open:</p>
<p>o/s: {{ scan.equipment.os }}</p>
<p>Ports open:{{ scan.equipment.ports }}</p>
{% for detail in scan.results %}
<section class="{{ detail.threat }}">
<h4>{{ detail.nvt_name }}</h4>
Expand Down