Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Batch update cntl impl smts when component_statement changes #1608

Merged
merged 1 commit into from
May 30, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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