Skip to content

Commit

Permalink
Merge pull request #1551 from GovReady/ge/display-impact-level
Browse files Browse the repository at this point in the history
Closes #1547 Display impact level on project page.
  • Loading branch information
davidpofo authored Apr 22, 2021
2 parents 30c91a3 + 459277a commit 86327e3
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 7 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,16 @@ v999 (April XX, 2021)

**Developer changes**


* Set system fisma_impact_level as part of question action to set baseline. Also add fisma_impact_level set/get methods to System model.

**Bug fixes**

* Properly filter system POA&M stat to only count POA&Ms for system.

* Provide better error reporting on import component schema validation; report actual validation error to standout.


**Data changes**

* Replace me
Expand All @@ -43,8 +51,13 @@ v0.9.3.4 (April 20, 2021)

**UI changes**

* Display system impact level on project page.
* Link mini-dashboards on project page to sensible related pages.

**Data changes**

* Use statement type `fisma_impact_level` to track impact level of a system.


v0.9.3.3 (April 13, 2021)
-------------------------
Expand Down
Empty file added controls/enums/__init__.py
Empty file.
9 changes: 9 additions & 0 deletions controls/enums/statements.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from siteapp.enums.base import BaseEnum


class StatementTypeEnum(BaseEnum):
CONTROL_IMPLEMENTATION = "control_implementation"
CONTROL_IMPLEMENTATION_PROTOTYPE = "control_implementation_prototype"
ASSESSMENT_RESULT = "assessment_result"
POAM = "POAM"
FISMA_IMPACT_LEVEL = "fisma_impact_level"
23 changes: 23 additions & 0 deletions controls/migrations/0048_auto_20210420_1704.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 3.1.8 on 2021-04-20 17:04

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('controls', '0047_merge_20210408_1515'),
]

operations = [
migrations.AlterField(
model_name='historicalstatement',
name='statement_type',
field=models.CharField(blank=True, choices=[('CONTROL_IMPLEMENTATION', 'control_implementation'), ('CONTROL_IMPLEMENTATION_PROTOTYPE', 'control_implementation_prototype'), ('ASSESSMENT_RESULT', 'assessment_result'), ('POAM', 'POAM'), ('FISMA_IMPACT_LEVEL', 'fisma_impact_level')], help_text='Statement type.', max_length=150, null=True),
),
migrations.AlterField(
model_name='statement',
name='statement_type',
field=models.CharField(blank=True, choices=[('CONTROL_IMPLEMENTATION', 'control_implementation'), ('CONTROL_IMPLEMENTATION_PROTOTYPE', 'control_implementation_prototype'), ('ASSESSMENT_RESULT', 'assessment_result'), ('POAM', 'POAM'), ('FISMA_IMPACT_LEVEL', 'fisma_impact_level')], help_text='Statement type.', max_length=150, null=True),
),
]
26 changes: 23 additions & 3 deletions controls/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from natsort import natsorted

from siteapp.model_mixins.tags import TagModelMixin
from controls.enums.statements import StatementTypeEnum
from controls.oscal import Catalogs, Catalog, check_and_extend
import uuid
import tools.diff_match_patch.python3 as dmp_module
Expand Down Expand Up @@ -51,7 +52,7 @@ class Statement(auto_prefetch.Model):
sid_class = models.CharField(max_length=200, help_text="Statement identifier 'class' such as 'NIST_SP-800-53_rev4' or other OSCAL catalog name Control ID.", unique=False, blank=True, null=True)
pid = models.CharField(max_length=20, help_text="Statement part identifier such as 'h' or 'h.1' or other part key", unique=False, blank=True, null=True)
body = models.TextField(help_text="The statement itself", unique=False, blank=True, null=True)
statement_type = models.CharField(max_length=150, help_text="Statement type.", unique=False, blank=True, null=True)
statement_type = models.CharField(max_length=150, help_text="Statement type.", unique=False, blank=True, null=True, choices=StatementTypeEnum.choices())
remarks = models.TextField(help_text="Remarks about the statement.", unique=False, blank=True, null=True)
status = models.CharField(max_length=100, help_text="The status of the statement.", unique=False, blank=True, null=True)
version = models.CharField(max_length=20, help_text="Optional version number.", unique=False, blank=True, null=True)
Expand Down Expand Up @@ -522,6 +523,25 @@ def remove_control(self, control_id):
control.delete()
return control

@transaction.atomic
def set_fisma_impact_level(self, fisma_impact_level):
"""Assign FISMA impact level to system"""

# Get or create the fisma_impact_level smt for system's root_element; should only have 1 statement
smt, created = Statement.objects.get_or_create(statement_type="fisma_impact_level", producer_element=self.root_element,consumer_element=self.root_element)
smt.body = fisma_impact_level
smt.save()
return fisma_impact_level

@property
def get_fisma_impact_level(self):
"""Assign FISMA impact level to system"""

# Get or create the fisma_impact_level smt for system's root_element; should only have 1 statement
smt, created = Statement.objects.get_or_create(statement_type="fisma_impact_level", producer_element=self.root_element,consumer_element=self.root_element)
fisma_impact_level = smt.body
return fisma_impact_level

@property
def smts_common_controls_as_dict(self):
common_controls = self.root_element.common_controls.all()
Expand Down Expand Up @@ -656,8 +676,8 @@ def poam_status_count(self):
# TODO
# Get a unique filter of status list and gather on that...
status_stats = {status: 0 for status in status_list}
# Fetch all selected controls
counts = Statement.objects.filter(statement_type="POAM", status__in=status_list).values('status').order_by('status').annotate(
# Fetch all system POA&Ms
counts = Statement.objects.filter(statement_type="POAM", consumer_element=self.root_element, status__in=status_list).values('status').order_by('status').annotate(
count=Count('status'))
status_stats.update({r['status']: r['count'] for r in counts})
# TODO add index on statement status
Expand Down
26 changes: 23 additions & 3 deletions guidedmodules/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -467,14 +467,34 @@ def redirect_to():
# Split a_filter into catalog and baseline
catalog, baseline = a_filter.split("=+=")
if catalog is None or baseline is None:
# Problem, we did not get two value
# Problem, we did not get two values
print("Problem - assign_baseline a_filter did not produce catalog, baseline", a_filter)
#element.assign_baseline_controls(user, 'NIST_SP-800-53_rev4', 'moderate')
system.root_element.assign_baseline_controls(request.user, catalog, baseline)
catalog_display = catalog.replace("_", " ")
messages.add_message(request, messages.INFO,
f'I\'ve set the control baseline to "{catalog_display} {baseline}."')
# TODO Log setting baseline
# Log setting baseline
logger.info(
event=f"system assign_baseline {baseline}",
object={"object": "system", "id": system.id, "name": system.root_element.name},
user={"id": request.user.id, "username": request.user.username}
)
# Set fisma_impact_level statement
if baseline.lower() in ["low", "moderate", "high"]:
fisma_impact_level = system.set_fisma_impact_level(baseline)
if fisma_impact_level == baseline.lower():
messages.add_message(request, messages.INFO,
f'I\'ve set the system FISMA impact level to "{fisma_impact_level}.')
# Log setting fisma_impact_level
logger.info(
event=f"system assign_fisma_impact_level {fisma_impact_level}",
object={"object": "system", "id": system.id, "name": system.root_element.name},
user={"id": request.user.id, "username": request.user.username}
)
else:
messages.add_message(request, messages.ERROR,
f'I failed to set the system FISMA impact level to "{baseline}."')


# Update name of system and project
Expand Down Expand Up @@ -570,7 +590,7 @@ def redirect_to():
Statement.objects.filter(producer_element_id = producer_element.id, consumer_element_id = system.root_element.id, statement_type="control_implementation").delete()
messages.add_message(request, messages.INFO,
f'I\'ve deleted "{producer_element.name}" and its {smts_assigned_count} control implementation statements from the system.')


# Form a JSON response to the AJAX request and indicate the
# URL to redirect to, to load the next question.
Expand Down
27 changes: 27 additions & 0 deletions siteapp/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from guidedmodules.tests import TestCaseWithFixtureData
from siteapp.models import (Organization, Portfolio, Project,
ProjectMembership, User)
from controls.models import Statement, Element
from siteapp.settings import HEADLESS, DOS
from siteapp.views import project_edit
from tools.utils.linux_to_dos import convert_w
Expand Down Expand Up @@ -1448,3 +1449,29 @@ def test_mini_dashboard(self):
self.click_element('#status-box-poams')
wait_for_sleep_after( lambda: self.assertInNodeText("POA&Ms", ".systems-selected-items") )

def test_display_impact_level(self):
""" Tests for project page mini compliance dashboard """

# Log in, create a new project.
self._login()
self._new_project()
# On project page?
wait_for_sleep_after( lambda: self.assertInNodeText("I want to answer some questions", "#project-title") )

# Display imact level testing
# New project should not be categorized
self.assertInNodeText("not categorized", "#systems-fisma-impact-level")

# Update impact level
# Get project.system.root_element to attach statement holding fisma impact level
project = self.current_project
fil = "Low"
# Test change and test system fisma_impact_level set/get methods
project.system.set_fisma_impact_level(fil)
# Check value changed worked
self.assertEqual(project.system.get_fisma_impact_level, fil)
# Refresh project page
self.click_element('#btn-project-home')
# See if project page has changed
wait_for_sleep_after( lambda: self.assertInNodeText("low", "#systems-fisma-impact-level") )

8 changes: 8 additions & 0 deletions siteapp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,10 +970,18 @@ def project(request, project):
if approx_compliance_degrees > 358:
approx_compliance_degrees = 358

# Fetch statement defining FISMA impact level if set
impact_level_smts = project.system.root_element.statements_consumed.filter(statement_type="fisma_impact_level")
if len(impact_level_smts) > 0:
impact_level = impact_level_smts[0].body
else:
impact_level = None

# Render.
return render(request, "project.html", {
"is_project_page": True,
"project": project,
"impact_level": impact_level,

"controls_status_count": project.system.controls_status_count,
"poam_status_count": project.system.poam_status_count,
Expand Down
10 changes: 9 additions & 1 deletion templates/project.html
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,15 @@ <h2 class="status">{{ poam_status_count.Open }}</h2> <span class="status-small">
<div id="status-box-compliance-piechart" class="status-box-invisible">
<h2 class="piechart" style="background-image: conic-gradient(#E1F8E2 {{ approx_compliance_degrees }}deg,
#B2E0B2 0 235deg);"> </h2>
<span class="status-small">{{ percent_compliant_100|floatformat:2 }}% compliance (unassessed)<span>
<span class="status-small">{{ percent_compliant_100|floatformat:2 }}% compliance (unassessed)</span>
<br/>
<span id="systems-fisma-impact-level" class="status-small">
{% if impact_level %}
Mission Impact: <b>{{ impact_level|upper }}</b>
{% else %}
Mission Impact: <b>Not Categorized</b>
{% endif %}
</span>
</div>
</div>
</div>
Expand Down

0 comments on commit 86327e3

Please sign in to comment.