Skip to content

Commit

Permalink
Merge pull request #1608 from GovReady/ge/cmpt-ctl-info
Browse files Browse the repository at this point in the history
Batch update cntl impl smts when component_statement changes
  • Loading branch information
alexanderward committed May 30, 2021
2 parents f97763b + f613b38 commit 19fe119
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 11 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ v999 (May XX, 2021)
* Can now edit a system componet's state and type in the detail page for a selected component.
* Improve project pages appearance: decrease action button width and left align text; widen from 9 to 10 columns main content.
* Remove "Refresh Documents" button on task finished page because caches are now automatically cleared and document content refreshed.
* Display system component component_state and component_type when component is listed for a system.

**Developer changes**

Expand All @@ -25,6 +26,7 @@ v999 (May XX, 2021)
* Task caches are now automatically cleared and document content refreshed when document downloaded.
* Add test for system control page.
* Refactor creating system control statements from component library prototype statements when adding a component from the library to a system and reduce by an order a magnitude the time it takes to add a component to system.
* Create System method to batch update an element's control implementation statements based on the component's state.

**Bug fix**

Expand Down
6 changes: 6 additions & 0 deletions controls/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,12 @@ def get_producer_elements(self):

producer_elements = cached_property(get_producer_elements)

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)
return True

class CommonControlProvider(models.Model):
name = models.CharField(max_length=150, help_text="Name of the CommonControlProvider", unique=False)
description = models.CharField(max_length=255, help_text="Brief description of the CommonControlProvider", unique=False)
Expand Down
50 changes: 43 additions & 7 deletions controls/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -673,14 +673,14 @@ def test_element_role(self):

class SystemUnitTests(TestCase):
def test_system_create(self):
e = Element.objects.create(name="New Element", full_name="New Element Full Name", element_type="system")
self.assertTrue(e.id is not None)
self.assertTrue(e.name == "New Element")
self.assertTrue(e.full_name == "New Element Full Name")
self.assertTrue(e.element_type == "system")
s = System(root_element=e)
sre = Element.objects.create(name="New Element", full_name="New Element Full Name", element_type="system")
self.assertTrue(sre.id is not None)
self.assertTrue(sre.name == "New Element")
self.assertTrue(sre.full_name == "New Element Full Name")
self.assertTrue(sre.element_type == "system")
s = System(root_element=sre)
s.save()
self.assertEqual(s.root_element.name,e.name)
self.assertEqual(s.root_element.name,sre.name)

u2 = User.objects.create(username="Jane2", email="jane@example.com")
# Test no permissions for user
Expand All @@ -696,6 +696,42 @@ def test_system_create(self):
self.assertIn('delete_system', perms)
self.assertIn('view_system', perms)

# Create an element with control implementation statements and assign to system
e = Element.objects.create(name="OAuth", full_name="OAuth Service", element_type="system_element", component_state="operational")
self.assertTrue(e.id is not None)
self.assertTrue(e.name == "OAuth")
e.save()
smt_1 = Statement.objects.create(
sid = "au-3",
sid_class = "NIST_SP-800-53_rev4",
body = "This is the first test statement.",
statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value,
status = "Implemented",
producer_element = e,
consumer_element = s.root_element
)
smt_1.save()
smt_2 = Statement.objects.create(
sid = "au-4",
sid_class = "NIST_SP-800-53_rev4",
body = "This is the first test statement.",
statement_type=StatementTypeEnum.CONTROL_IMPLEMENTATION.value,
status = "Implemented",
producer_element = e,
consumer_element = s.root_element
)
smt_2.save()

# Batch update system statements status by changing system component state
element = e
control_status = "planned"
s.set_component_control_status(element, control_status)
# Test the system's component's statements status were changed
smt_1_updated = Statement.objects.get(pk=smt_1.id)
self.assertTrue(smt_1_updated.status, control_status)
smt_2_updated = Statement.objects.get(pk=smt_2.id)
self.assertTrue(smt_2_updated.status, control_status)

class SystemUITests(OrganizationSiteFunctionalTests):

def test_deployments_page_exists(self):
Expand Down
11 changes: 11 additions & 0 deletions controls/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,9 +895,20 @@ def edit_component_state(request, system_id, element_id):
# Retrieve related selected controls if user has permission on system
if request.user.has_perm('change_system', system):
# Retrieve element
# TODO: Make atomic transaction
element = Element.objects.get(id=element_id)
element.component_state = request.POST['state_change']
element.save()
logger.info(event=f"change_system update_component_state {element} {element.component_state}",
object={"object": "system", "id": system.id},
user={"id": request.user.id, "username": request.user.username})
# Batch update status of control implementation statements provided by the element to the system
state_status = {"operational": "Implemented", "under-development": "Partially Implemented", "planned": "Planned"}
control_status = state_status.get(request.POST['state_change']) or "Not Implemented"
system.set_component_control_status(element, control_status)
logger.info(event=f"change_system batch_update_component_control_status {element} {control_status}",
object={"object": "system", "id": system.id},
user={"id": request.user.id, "username": request.user.username})
return redirect(reverse('system_element', args=[system_id, element_id]))

def edit_component_type(request, system_id, element_id):
Expand Down
2 changes: 2 additions & 0 deletions siteapp/static/css/govready-q.css
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,8 @@ form #id_json_content { width: 100%; overflow: auto !important; } /* The !import

.component-tag { font-size: 0.7em; color: #999; padding: 0px 6px 0px 6px; border: 1px solid #bbb; border-radius: 24px; }
.component-tag a { color: #999; }
.component-state { font-size: 0.8em; color: #444; padding: 0px 6px 0px 6px; border: 1px solid #bbb; border-radius: 24px; }
.component-type { font-size: 0.8em; color: #444; padding: 0px 6px 0px 6px; border: 1px solid #bbb; border-radius: 24px; }

.component-form {
float: left;
Expand Down
5 changes: 5 additions & 0 deletions templates/controls/editor.html
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,12 @@ <h2 class="control-heading" style="">
<div class="row statement-text">
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-3 col-xl-3">
<span id="producer_element-{{ forloop.counter }}-control" class="control-id-text">{{ smt.producer_element.name }}</span>
<div>
<span class="component-type">{{ smt.producer_element.component_type }}</span>
<span class="component-state">{{ smt.producer_element.component_state }}</span>
</div>
</div>
<div>
<div class="col-xs-6 col-sm-6 col-md-6 col-lg-6 col-xl-6 statement-text-block">{% if smt.pid is not None and smt.pid != "" %}<div class="panel-heading-smt">{{ smt.pid }}.</div>{% endif %}{{ smt.body }}</div>
<div class="col-xs-3 col-sm-3 col-md-3 col-lg-3 col-xl-3 remark-text-block">
{% spaceless %}
Expand Down
11 changes: 7 additions & 4 deletions templates/systems/components_selected.html
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@
{% for component in system_elements %}
{# Each "component" is a Element model object. #}
<div id="tab-content" class="row row-control">
<div id="" class="col-xs-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
<div id="" class="col-xs-3 col-sm-3 col-md-3 col-lg-3 col-xl-3">
<a href={% url 'system_element' system_id=system.id element_id=component.id %}>{{ component.name }}</a>
</div>
<div id="" class="col-xs-5 col-sm-5 col-md-5 col-lg-5 col-xl-5">
<div id="" class="col-xs-2 col-sm-2 col-md-2 col-lg-2 col-xl-2">
<span class="component-type">{{ component.component_type }}</span>
<span class="component-state">{{ component.component_state }}</span>
</div>
<div id="" class="col-xs-4 col-sm-4 col-md-4 col-lg-4 col-xl-4">
{% if component.description %}{{ component.description }}{% else %}<span class="not-provided">No description provided.</span>{% endif %}
<div>{% for tag in component.tags.all %}<span class="component-tag">{{ tag.label }}</span> {% endfor %}</div>
</div>
Expand All @@ -57,13 +61,12 @@
<span class="pull-right">{% if ctl_count %}{{ ctl_count }} control{{ ctl_count|pluralize }}</span>{% else %}None{% endif %}
</div>
{% endwith %}

{% get_obj_perms request.user for system as "system_perms" %}
{% if "change_system" in system_perms %}
<div id="" class="col-xs-1 col-sm-1 col-md-1 col-lg-1 col-xl-1 pull-right">
<a href="{% url 'system_element_remove' system_id=system.id element_id=component.id %}">
<small>
<span class="glyphicon glyphicon-trash" title="remove component"></span>
<span class="glyphicon glyphicon-trash pull-right" title="remove component"></span>
</small>
</a>
</div>
Expand Down

0 comments on commit 19fe119

Please sign in to comment.