Skip to content

Commit

Permalink
Add UI for legacy statement display. Also fix StatementTypeEnum. (#1644)
Browse files Browse the repository at this point in the history
* [WIP] UI to display legacy control impl smts

Create a conditional display of legacy control implementation
statements in control editor page.

Also widen width of display of editor control statements
to 1250px.

* Improve display of legacy statement

* StatementTypeEnum fixes. Closes #1643

Set all `StatementTypeEnum.<LABEL>.value` to `StatementTypeEnum.<LABEL>.name`
in order for relevant label/term to show up in Django database admin interface.

Set component library detail page Systems tab to not be inactive thereby
removing the content from the System tab showing up on the Control Implementation
Statements tab.

Update controls.tests.

Co-authored-by: Greg Elin <greg.elin@govready.com>
  • Loading branch information
gregelin and govreadydeploy committed Jun 30, 2021
1 parent c98f4a1 commit 57688c2
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 67 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,17 @@ GovReady-Q Release Notes
v999 (June XX, 2021)
---------------------

**UI changes**

* Display legacy control implementation statements within system's statements.

**Bug fixes**

* Set component library detail page Systems tab to not be inactive and thus remove the content from the System tab showing up on the Control Implementation Statements tab.

**Data changes**

* Set all `StatementTypeEnum.<LABEL>.value` to `StatementTypeEnum.<LABEL>.name` in order for relevant label/term to show up in Django database admin interface.

v0.9.5 (June 23, 2021)
----------------------
Expand Down
53 changes: 53 additions & 0 deletions controls/migrations/0052_auto_20210629_2328.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 3.1.8 on 2021-06-29 23:28

from django.db import migrations
from controls.enums.statements import StatementTypeEnum


class Migration(migrations.Migration):

dependencies = [
('controls', '0051_auto_20210520_1213'),
]

def update_statement_type_to_enum_name(apps, schema_editor):
"""Update the statement_type values to use enum 'name' instead of enum 'value'"""

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value)\
.update(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_LEGACY.value)\
.update(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_LEGACY.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value)\
.update(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.ASSESSMENT_RESULT.value)\
.update(statement_type=StatementTypeEnum.ASSESSMENT_RESULT.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.POAM.value)\
.update(statement_type=StatementTypeEnum.POAM.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.value)\
.update(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.name)

apps.get_model("controls", "Statement")\
.objects\
.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value)\
.update(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name)

operations = [
migrations.RunPython(update_statement_type_to_enum_name),
]
42 changes: 21 additions & 21 deletions controls/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def create_prototype(self):
return self.prototype
# check if prototype content is the same, report error if not, or overwrite if permission approved
prototype = deepcopy(self)
prototype.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value
prototype.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name
prototype.consumer_element_id = None
prototype.id = None
prototype.save()
Expand All @@ -131,15 +131,15 @@ def create_system_control_smt_from_component_prototype_smt(self, consumer_elemen
"""Creates a control_implementation statement instance for a system's root_element from an existing control implementation prototype statement"""

# Check statement is a prototype
if self.statement_type != StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value:
if self.statement_type != StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name:
return None

# Return if statement already has instance associated with consumer_element
if self.instances.filter(consumer_element__id=consumer_element_id).exists():
return self.prototype

instance = deepcopy(self)
instance.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value
instance.statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name
instance.consumer_element_id = consumer_element_id
instance.id = None
# Set prototype attribute to newly created instance
Expand All @@ -151,7 +151,7 @@ def create_system_control_smt_from_component_prototype_smt(self, consumer_elemen
def prototype_synched(self):
"""Returns one of STATEMENT_SYNCHED, STATEMENT_NOT_SYNCHED, STATEMENT_ORPHANED for control_implementations"""

if self.statement_type == "control_implementation":
if self.statement_type == StatementTypeEnum.CONTROL_IMPLEMENTATION.name:
if self.prototype:
if self.body == self.prototype.body:
return STATEMENT_SYNCHED
Expand All @@ -166,7 +166,7 @@ def prototype_synched(self):
def diff_prototype_main(self):
"""Generate a diff of statement of type `control_implementation` and its prototype"""

if self.statement_type != 'control_implementation':
if self.statement_type != StatementTypeEnum.CONTROL_IMPLEMENTATION.name:
# TODO: Should we return None or raise error because statement is not of type control_implementation?
return None
if self.prototype is None:
Expand All @@ -180,7 +180,7 @@ def diff_prototype_main(self):
def diff_prototype_prettyHtml(self):
"""Generate a diff of statement of type `control_implementation` and its prototype"""

if self.statement_type != 'control_implementation':
if self.statement_type != StatementTypeEnum.CONTROL_IMPLEMENTATION.name:
# TODO: Should we return None or raise error because statement is not of type control_implementation?
return None
if self.prototype is None:
Expand Down Expand Up @@ -240,7 +240,7 @@ class Element(auto_prefetch.Model, TagModelMixin):
# e.statements_consumed.all()
#
# Retrieve statements that are control implementations
# e.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value)
# e.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name)

def __str__(self):
return "'%s id=%d'" % (self.name, self.id)
Expand Down Expand Up @@ -347,7 +347,7 @@ def consuming_systems(self):
def get_control_impl_smts_prototype_count(self):
"""Return count of statements with this element as producer_element"""

smt_count = Statement.objects.filter(producer_element=self, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.value).count()
smt_count = Statement.objects.filter(producer_element=self, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name).count()

return smt_count

Expand All @@ -369,7 +369,7 @@ def copy(self, name=None):
e_copy.name = self.name + " copy"
e_copy.save()
# Copy prototype statements from existing element
for smt in self.statements("control_implementation_prototype"):
for smt in self.statements(StatementTypeEnum.CONTROL_IMPLEMENTATION_PROTOTYPE.name):
smt_copy = deepcopy(smt)
smt_copy.producer_element = e_copy
smt_copy.consumer_element_id = None
Expand Down Expand Up @@ -480,7 +480,7 @@ class System(auto_prefetch.Model):
# Notes
# Retrieve system implementation statements
# system = System.objects.get(pk=2)
# system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value)
# system.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name)
#
# Retrieve system common controls statements
# system = System.objects.get(pk=2)
Expand Down Expand Up @@ -533,7 +533,7 @@ def remove_control(self, control_id):
control = ElementControl.objects.get(pk=control_id)

# Delete Control Statements
self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value,
self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name,
sid_class=control.oscal_catalog_key,
sid=control.oscal_ctl_id
).delete()
Expand All @@ -546,28 +546,28 @@ def set_fisma_impact_level(self, fisma_impact_level):
# TODO: Fisma impact level is actually the security-sensitivity-level as defined in oscal ssp schema.

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

@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=StatementTypeEnum.FISMA_IMPACT_LEVEL.value, producer_element=self.root_element,consumer_element=self.root_element)
smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.FISMA_IMPACT_LEVEL.name, producer_element=self.root_element,consumer_element=self.root_element)
fisma_impact_level = smt.body
return fisma_impact_level

@transaction.atomic
def set_security_impact_level(self, security_impact_level):
"""Assign Security impact levels to system"""

security_objective_smt = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value)
security_objective_smt = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name)
if security_objective_smt.exists():
security_objective_smt.update(body=security_impact_level)
else:
# Set the security_impact_level smt for element; should only have 1 statement
security_objective_smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element,consumer_element=self.root_element, body=security_impact_level)
security_objective_smt, created = Statement.objects.get_or_create(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name, producer_element=self.root_element,consumer_element=self.root_element, body=security_impact_level)
return security_impact_level, security_objective_smt

@property
Expand All @@ -576,7 +576,7 @@ def get_security_impact_level(self):

# Get the security_impact_level smt for element; should only have 1 statement
try:
smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.value, producer_element=self.root_element, consumer_element=self.root_element)
smt = Statement.objects.get(statement_type=StatementTypeEnum.SECURITY_IMPACT_LEVEL.name, producer_element=self.root_element, consumer_element=self.root_element)
security_impact_level = eval(smt.body)# Evaluate string of dictionary
return security_impact_level
except Statement.DoesNotExist:
Expand All @@ -595,7 +595,7 @@ def smts_common_controls_as_dict(self):

@property
def smts_control_implementation_as_dict(self):
smts = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).order_by('pid')
smts = self.root_element.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).order_by('pid')
smts_as_dict = {}
for smt in smts:
if smt.sid in smts_as_dict:
Expand All @@ -612,7 +612,7 @@ def control_implementation_as_dict(self):
elm = self.root_element
selected_controls = elm.controls.all().values("oscal_ctl_id", "uuid")
# Get the smts_control_implementations ordered by part, e.g. pid
smts = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).order_by('pid')
smts = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).order_by('pid')

smts_as_dict = {}

Expand Down Expand Up @@ -699,12 +699,12 @@ def controls_status_count(self):
# Fetch all selected controls
elm = self.root_element

counts = Statement.objects.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value, status__in=status_list).values('status').order_by('status').annotate(count=Count('status'))
counts = Statement.objects.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name, 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
# Get overall controls addressed (e.g., covered)
status_stats['Addressed'] = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).values('sid').distinct().count()
status_stats['Addressed'] = elm.statements_consumed.filter(statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).values('sid').distinct().count()
return status_stats

@cached_property
Expand Down Expand Up @@ -739,7 +739,7 @@ def get_producer_elements(self):
def set_component_control_status(self, element, status):
"""Batch update status of system control implementation statements for a specific element."""

self.root_element.statements_consumed.filter(producer_element=element, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value).update(status=status)
self.root_element.statements_consumed.filter(producer_element=element, statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.name).update(status=status)
return True

class CommonControlProvider(models.Model):
Expand Down
Loading

0 comments on commit 57688c2

Please sign in to comment.