Implemented true Unicode (UTF-8) support
Added first Pentext classes: Finding and NonFinding
This commit is contained in:
parent
feb154554a
commit
acc6dc4b50
@ -24,6 +24,7 @@ from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import argparse
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import textwrap
|
||||
@ -34,7 +35,7 @@ try:
|
||||
# Path of this script. The validate_report module is on the same path.
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
import validate_report
|
||||
except ImportError as exception:
|
||||
except (NameError, ImportError) as exception:
|
||||
print('[-] This script needs python-gitlab, pypandoc and validate_report library',
|
||||
file=sys.stderr)
|
||||
print("validate_report is part of the pentext framework", file=sys.stderr)
|
||||
@ -43,51 +44,135 @@ except ImportError as exception:
|
||||
print("Currently missing: " + exception.message, file=sys.stderr)
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
class BaseItem(object):
|
||||
"""
|
||||
Base class for Pentext items
|
||||
"""
|
||||
|
||||
DECLARATION = '<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
|
||||
def add_finding(issue, options):
|
||||
def __init__(self, item_type):
|
||||
if item_type not in ('finding', 'non-finding'):
|
||||
raise ValueError('Only finding and non-finding are currently supported')
|
||||
self.item_type = item_type
|
||||
self.__path = '{0}s'.format(self.item_type)
|
||||
self.root_open = '<{0}>\n'.format(self.item_type)
|
||||
self.root_close = '</{0}>\n'.format(self.item_type)
|
||||
self.title = ''
|
||||
self.content = ''
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
"""
|
||||
Writes issue as XML finding to file.
|
||||
Filename.
|
||||
"""
|
||||
title = validate_report.capitalize(issue.title.strip())
|
||||
print_status('{0} - {1} - {2}'.format(issue.state, issue.labels,
|
||||
title), options)
|
||||
threat_level = 'Moderate'
|
||||
finding_type = 'TODO'
|
||||
finding_id = 'f{0}-{1}'.format(issue.iid, valid_filename(title))
|
||||
filename = 'findings/{0}.xml'.format(finding_id)
|
||||
finding = u'<title>{0}</title>\n'.format(title)
|
||||
finding += '<description>{0}\n</description>\n'.format(convert_text(issue.description))
|
||||
impact = 'TODO'
|
||||
recommendation = '<ul>\n<li>\nTODO\n</li>\n</ul>\n';
|
||||
technical_description = ''
|
||||
return '{0}/{1}.xml'.format(self.__path, valid_filename(self.identifier))
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a XML version of the class
|
||||
"""
|
||||
return self.DECLARATION + self.root_open + self.element('title') + \
|
||||
self.content + self.root_close
|
||||
|
||||
def element(self, attribute):
|
||||
"""
|
||||
Return opening and closing attribute tags, including attribute value.
|
||||
"""
|
||||
return '<{0}>{1}</{0}>\n'.format(attribute, getattr(self, attribute))
|
||||
|
||||
def write_file(self):
|
||||
"""
|
||||
Write item as XML to file.
|
||||
"""
|
||||
try:
|
||||
with io.open(self.filename, 'w') as xmlfile:
|
||||
xmlfile.write(unicode(self))
|
||||
print_line('[+] Wrote {0}'.format(self.filename))
|
||||
except IOError:
|
||||
print_error('Could not write to %s', self.filename)
|
||||
|
||||
|
||||
class Finding(BaseItem):
|
||||
"""
|
||||
Encapsulates finding.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
BaseItem.__init__(self, 'finding')
|
||||
self.threat_level = 'Moderate'
|
||||
self.finding_type = 'TODO'
|
||||
self.description = '<p>TODO</p>'
|
||||
self.technicaldescription = '<p>TODO</p>'
|
||||
self.impact = '<p>TODO</p>'
|
||||
self.recommendation = '<ul><li>TODO</li></ul>'
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Return a XML version of the class
|
||||
"""
|
||||
self.root_open = '<finding id="{0}" threatLevel="{1}" type="{2}">\n'.format(self.identifier,
|
||||
self.threat_level,
|
||||
self.finding_type)
|
||||
self.content = self.element('description') + \
|
||||
self.element('technicaldescription') + \
|
||||
self.element('impact') + \
|
||||
self.element('recommendation')
|
||||
return BaseItem.__str__(self)
|
||||
|
||||
|
||||
class NonFinding(BaseItem):
|
||||
"""
|
||||
Encapsulates non-finding.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
BaseItem.__init__(self, 'non-finding')
|
||||
|
||||
|
||||
def from_issue(issue):
|
||||
"""
|
||||
Parse gitlab issue and return Finding, NonFinding or None
|
||||
"""
|
||||
if 'finding' in [x.lower() for x in issue.labels]:
|
||||
item = Finding()
|
||||
item.description = convert_text(issue.description)
|
||||
for note in [x for x in reversed(issue.notes.list()) if not x.system]:
|
||||
if len(note.body.splitlines()):
|
||||
if 'impact' in note.body.split()[0].lower():
|
||||
impact = convert_text(''.join(note.body.splitlines(True)[1:]))
|
||||
item.impact = convert_text(''.join(note.body.splitlines(True)[1:]))
|
||||
elif 'recommendation' in note.body.split()[0].lower():
|
||||
recommendation = convert_text(''.join(note.body.splitlines(True)[1:]))
|
||||
item.recommendation = convert_text(''.join(note.body.splitlines(True)[1:]))
|
||||
else:
|
||||
technical_description += u'{0}\n'.format(convert_text(note.body))
|
||||
finding += '<technicaldescription>\n{0}\n</technicaldescription>\n\n'.format(technical_description)
|
||||
finding += '<impact>\n{0}\n</impact>\n\n'.format(impact)
|
||||
finding += '<recommendation>\n{0}\n</recommendation>\n\n'.format(recommendation)
|
||||
finding = u'{0}<finding id="{1}" threatLevel="{2}" type="{3}">\n{4}</finding>'.format(DECLARATION,
|
||||
finding_id,
|
||||
threat_level,
|
||||
finding_type,
|
||||
finding)
|
||||
item.technicaldescription += u'{0}\n'.format(convert_text(note.body))
|
||||
elif 'non-finding' in [x.lower() for x in issue.labels]:
|
||||
item = NonFinding()
|
||||
for note in [x for x in reversed(issue.notes.list()) if not x.system]:
|
||||
item.content += convert_text(note.body) + '\n'
|
||||
else:
|
||||
return None
|
||||
item.title = validate_report.capitalize(issue.title.strip())
|
||||
item.identifier = 'f{0}-{1}'.format(issue.iid, valid_filename(item.title))
|
||||
return item
|
||||
|
||||
|
||||
def add_item(issue, options):
|
||||
"""
|
||||
Convert issue into XML finding and create file.
|
||||
"""
|
||||
item = from_issue(issue)
|
||||
if not item:
|
||||
return
|
||||
if os.path.isfile(item.filename) and not options['overwrite']:
|
||||
print_line('{0} {1} already exists (use --overwrite to overwrite)'.
|
||||
format(item.item_type, item.filename))
|
||||
return
|
||||
if options['dry_run']:
|
||||
print_line('[+] {0}\n{1}'.format(filename, finding))
|
||||
print_line('[+] {0}\n{1}'.format(item.filename, item))
|
||||
else:
|
||||
if os.path.isfile(filename) and not options['overwrite']:
|
||||
print_line('Finding {0} already exists (use --overwrite to overwrite)'.
|
||||
format(filename))
|
||||
else:
|
||||
if options['y'] or ask_permission('Create file ' + filename):
|
||||
with open(filename, 'w') as xmlfile:
|
||||
xmlfile.write(finding)
|
||||
print_line('[+] Created {0}'.format(filename))
|
||||
if options['y'] or ask_permission('Create file ' + item.filename):
|
||||
item.write_file()
|
||||
|
||||
|
||||
def convert_text(text):
|
||||
@ -97,35 +182,6 @@ def convert_text(text):
|
||||
return unicode.replace(pypandoc.convert_text(text, 'html5', format='markdown_github'), '\r\n', '\n')
|
||||
|
||||
|
||||
def add_non_finding(issue, options):
|
||||
"""
|
||||
Adds a non-finding.
|
||||
"""
|
||||
title = validate_report.capitalize(issue.title.strip())
|
||||
print_status('{0} - {1} - {2}'.format(issue.state, issue.labels,
|
||||
title), options)
|
||||
non_finding_id = 'nf{0}-{1}'.format(issue.iid, valid_filename(title))
|
||||
filename = 'non-findings/{0}.xml'.format(non_finding_id)
|
||||
non_finding = u'<title>{0}</title>\n{1}\n'.format(title,
|
||||
convert_text(issue.description))
|
||||
for note in [x for x in reversed(issue.notes.list()) if not x.system]:
|
||||
non_finding += u'<p>{0}</p>\n'.format(convert_text(note.body))
|
||||
non_finding = u'{0}<non-finding id="{1}">\n{2}\n</non-finding>\n'.format(DECLARATION,
|
||||
non_finding_id,
|
||||
non_finding)
|
||||
if options['dry_run']:
|
||||
print_line('[+] {0}\n{1}'.format(filename, non_finding))
|
||||
else:
|
||||
if os.path.isfile(filename) and not options['overwrite']:
|
||||
print_line('Non-finding {0} already exists (use --overwrite to overwrite)'.
|
||||
format(filename))
|
||||
else:
|
||||
if options['y'] or ask_permission('Create file ' + filename):
|
||||
with open(filename, 'w') as xmlfile:
|
||||
xmlfile.write(non_finding)
|
||||
print_line('[+] Created {0}'.format(filename))
|
||||
|
||||
|
||||
def ask_permission(question):
|
||||
"""
|
||||
Ask question and return True if user answered with y.
|
||||
@ -138,17 +194,11 @@ def list_issues(gitserver, options):
|
||||
"""
|
||||
Lists all issues for options['issues']
|
||||
"""
|
||||
try:
|
||||
for issue in gitserver.project_issues.list(project_id=options['issues'],
|
||||
per_page=999):
|
||||
if issue.state == 'closed' and not options['closed']:
|
||||
continue
|
||||
if 'finding' in [x.lower() for x in issue.labels]:
|
||||
add_finding(issue, options)
|
||||
if 'non-finding' in [x.lower() for x in issue.labels]:
|
||||
add_non_finding(issue, options)
|
||||
except Exception as exception:
|
||||
print_error('could not find any issues ({0})'.format(exception), -1)
|
||||
add_item(issue, options)
|
||||
|
||||
|
||||
def list_projects(gitserver):
|
||||
@ -205,6 +255,9 @@ def preflight_checks():
|
||||
gitserver.auth()
|
||||
except gitlab.config.GitlabDataError as exception:
|
||||
print_error('could not connect {0}'.format(exception), -1)
|
||||
for path in ('findings', 'non-findings'):
|
||||
if not os.path.isdir(path):
|
||||
print_error('Path {0} does not exist: Is this a Pentext repository ?'.format(path), -1)
|
||||
return gitserver
|
||||
|
||||
|
||||
@ -246,7 +299,7 @@ def valid_filename(filename):
|
||||
"""
|
||||
result = ''
|
||||
for char in filename.strip():
|
||||
if char in ['*', ':', '/', '.', '\\', ' ', '[', ']', '(', ')', '\'']:
|
||||
if char in ['*', ':', '/', '.', '\\', ' ', '[', ']', '(', ')', '\'', '\"']:
|
||||
if len(char) and not result.endswith('-'):
|
||||
result += '-'
|
||||
else:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user