Skip to content

Commit

Permalink
Da/cleanup export sspcsv (#1674)
Browse files Browse the repository at this point in the history
* created a de-oscalizing function and applied to selected controls on import.

* some super route way of making sure the catalogs produce 3.1.

* add test of de_oscalize_control
  • Loading branch information
davidpofo authored Aug 5, 2021
1 parent 3b6f237 commit 7ffbca1
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 12 deletions.
9 changes: 8 additions & 1 deletion controls/oscal.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ def check_and_extend(values, external_values, extendtype, splitter):
values.extend(files)
return values

def de_oscalize_control(control_id):
"""
Returns the regular control formatting from an oscalized version of the control number.
de_oscalize_control("ac-2.3") --> AC-2 (3)
"""
return re.sub(r'^([A-Za-z][A-Za-z]-)([0-9]*)\.([0-9]*)$', r'\1\2 (\3)', control_id).upper()

class Catalog(object):
"""Represent a catalog"""

Expand Down Expand Up @@ -431,7 +438,7 @@ def get_flattened_control_as_dict(self, control):
description_print = description.replace("\n", "<br/>")
cl_dict = {
"id": control['id'],
"id_display": re.sub(r'^([A-Za-z][A-Za-z]-)([0-9]*)\.([0-9]*)$', r'\1\2 (\3)', control['id']),
"id_display": de_oscalize_control(control['id']),
"title": control['title'],
"family_id": family_id,
"family_title": self.get_group_title_by_id(family_id),
Expand Down
11 changes: 10 additions & 1 deletion controls/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from system_settings.models import SystemSettings
from controls.models import *
from controls.enums.statements import StatementTypeEnum
from controls.oscal import Catalogs, Catalog, EXTERNAL_CATALOG_PATH
from controls.oscal import Catalogs, Catalog, EXTERNAL_CATALOG_PATH, de_oscalize_control
from siteapp.models import User, Project, Portfolio
from system_settings.models import SystemSettings

Expand Down Expand Up @@ -1225,3 +1225,12 @@ def test_export_oscal_system_security_plan(self):
response.get('Content-Disposition')
)

def test_deoscalization_control_id(self):
"""
Tests de_oscalize_control function on expected formats from sid (oscal) format to regular.
"""
controls = ["ac-2.4", "ac-2.5", "ac-2.11","ac-2.13", "ac-3", "ac-4", "si-3.2", "si-4.2", "si-4.5"]
regular_sid_controls = [de_oscalize_control(control) for control in controls]
self.assertEqual(['AC-2 (4)', 'AC-2 (5)', 'AC-2 (11)', 'AC-2 (13)', 'AC-3', 'AC-4', 'SI-3 (2)', 'SI-4 (2)', 'SI-4 (5)'], regular_sid_controls)


7 changes: 5 additions & 2 deletions guidedmodules/forms.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from django import forms


class ExportCSVTemplateSSPForm(forms.Form):

info_system = forms.CharField(label='"Project Name" exported to column named: ', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Information System'}))
control_id = forms.CharField(label='"Control ID" exported to column named:', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Control ID'}))
catalog = forms.CharField(label='"Control Catalog" exported to column named:', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Control Set Version Number'}))
shared_imps = forms.CharField(label='"Implementation Statement (Library)" exported to column named:', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Shared Implementation Details'}))
private_imps = forms.CharField(label='"Implementation Statement (Local)" exported to column named:', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Private Implementation Details'}))
private_imps = forms.CharField(label='"Implementation Statement (Local)" exported to column named:', widget=forms.TextInput(attrs={'class': 'form-control', 'style':'resize:none;width:500px;', 'placeholder': 'Private Implementation Details'}))
oscal_format = forms.BooleanField(
label='Would you like to have the Control ID data in OSCAL format?',
required=False
)
26 changes: 19 additions & 7 deletions guidedmodules/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
from datetime import datetime
from zipfile import ZipFile
from zipfile import BadZipFile
from django.shortcuts import render, redirect, get_object_or_404
from django.shortcuts import render, get_object_or_404
from django.http import Http404, HttpResponse, HttpResponseRedirect, HttpResponseForbidden, JsonResponse, HttpResponseNotAllowed
from django.urls import reverse
from controls.oscal import de_oscalize_control
from django.contrib import messages
from django.contrib.auth.decorators import login_required, permission_required
from django.conf import settings
Expand Down Expand Up @@ -2021,14 +2021,14 @@ def start_a_discussion(request):

# Get the TaskAnswer for this task. It may not exist yet.
tq, isnew = TaskAnswer.objects.get_or_create(**tq_filter)

# Filter for discussion and return the first entry (if it doesn't exist it returns None)
discussion = Discussion.get_for(task.project.organization, tq)
if not discussion:
# Validate user can create discussion.
if not task.has_read_priv(request.user):
return JsonResponse({ "status": "error", "message": "You do not have permission!" })

# Get the Discussion.
# Create a Discussion.
discussion = Discussion.get_for(task.project.organization, tq, create=True)

return JsonResponse(discussion.render_context_dict(request.user))
Expand Down Expand Up @@ -2131,7 +2131,7 @@ def compute_table(opt):
]
})

def export_ssp_csv(export_csv_data, system):
def export_ssp_csv(form_data, system):
"""
Export an SSP's control implementations with the submitted headers
"""
Expand All @@ -2140,10 +2140,22 @@ def export_ssp_csv(export_csv_data, system):
statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).order_by('pid')

selected_controls = list(smts.values_list('sid', flat=True))
catalog_keys = list(smts.values_list('sid_class', flat=True))
# If the user selected to format the control id in OSCAL this will be skipped
if not form_data.get('oscal_format'):
# De-oscalize every control id (sid)
selected_controls = [de_oscalize_control(control) for control in selected_controls]
db_catalog_keys = list(smts.values_list('sid_class', flat=True))
catalog_keys = []
# XYZ_3_0 --> XYZ 3.0
for catalog in db_catalog_keys:
if catalog.count("_") == 3:
catalog_keys.append(" ".join(catalog.split("_")[:2]) + " " + ".".join(catalog.split("_")[-2:]))
else:
catalog_keys.append(catalog)
imps = list(smts.values_list('body', flat=True))
headers = [export_csv_data.get('info_system'), export_csv_data.get('control_id'), export_csv_data.get('catalog'), export_csv_data.get('shared_imps'), export_csv_data.get('private_imps')]
headers = [form_data.get('info_system'), form_data.get('control_id'), form_data.get('catalog'), form_data.get('shared_imps'), form_data.get('private_imps')]
system_name = system.root_element.name # TODO: Should this come from questionnaire answer or project name as we have it?

data = [
[system_name] * len(selected_controls),
selected_controls,
Expand Down
2 changes: 1 addition & 1 deletion templates/controls/detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
<div style="float: left;">
{% if control.title is not None %}
<h2 id="control-heading" class="">
{{ control.id_display|upper }} {{ control.title }}
{{ control.id_display }} {{ control.title }}
</h2>
{% else %}
Control was not found in the catalog.
Expand Down

0 comments on commit 7ffbca1

Please sign in to comment.