Skip to content
Open
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ v0.9.14-dev (January xx, 2022)
**Developer changes**

* Add support for OIDC SSO configuration separate from OKTA SSO configuration.
* Add support for soft delete of components/elements by adding `deleted` field to `controls.Elements` model.
* Update Django, libraries.
* Remove debug-toolbar.

Expand All @@ -25,6 +26,10 @@ v0.9.13 (January 23, 2022)

**UI changes**

* Add button to mark component as deleted.

**Developer changes**

* Add sign-in warning message to which users need to agree.
* Reduce number of Group Django messages from question actions into single message for adding actions.
* Simplify new authoring tool. Move prompt from right to left. Only show first line of question prompt.
Expand Down
2 changes: 1 addition & 1 deletion controls/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class StatementRemoteAdmin(admin.ModelAdmin):
readonly_fields = ('created', 'updated', 'uuid')

class ElementAdmin(GuardedModelAdmin, ExportCsvMixin):
list_display = ('name', 'full_name', 'element_type', 'id', 'uuid')
list_display = ('name', 'full_name', 'element_type', 'id', 'uuid', 'deleted')
search_fields = ('name', 'full_name', 'uuid', 'id')
actions = ["export_as_csv"]

Expand Down
18 changes: 18 additions & 0 deletions controls/migrations/0066_element_deleted.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 3.2.10 on 2022-01-06 16:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('controls', '0065_auto_20211006_0240'),
]

operations = [
migrations.AddField(
model_name='element',
name='deleted',
field=models.BooleanField(default=False, help_text='Mark Component as deleted'),
),
]
3 changes: 2 additions & 1 deletion controls/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ class Element(auto_prefetch.Model, TagModelMixin):
unique=False, blank=True, null=True, help_text="The Import Record which created this Element.")
component_type = models.CharField(default="software", max_length=50, help_text="OSCAL Component Type.", unique=False, blank=True, null=True, choices=ComponentTypeEnum.choices())
component_state = models.CharField(default="operational", max_length=50, help_text="OSCAL Component State.", unique=False, blank=True, null=True, choices=ComponentStateEnum.choices())

deleted = models.BooleanField(default=False, help_text="Mark Component as deleted")
# Notes
# Retrieve Element controls where element is e to answer "What controls selected for a system?" (System is an element.)
# element_id = 8
Expand Down Expand Up @@ -847,6 +847,7 @@ class CommonControl(auto_prefetch.Model, BaseModel):
null=True)
legacy_imp_smt = models.TextField(help_text="Legacy large implementation statement", unique=False, blank=True,
null=True, )

common_control_provider = auto_prefetch.ForeignKey(CommonControlProvider, on_delete=models.CASCADE)

def __str__(self):
Expand Down
12 changes: 12 additions & 0 deletions controls/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ def test_element_create(self):
e.delete()
self.assertTrue(e.id is None)


def test_soft_delete(self):
e = Element.objects.create(name="New Element", full_name="New Element Full Name", element_type="system_element")
self.assertEqual(e.name, "New Element")
self.assertTrue(e.id is not None)
self.assertTrue(e.deleted == False)
e.deleted = True
e.save()
e2 = Element.objects.get(pk=e.id)
self.assertEqual(e2.deleted, True)


def test_element_assign_owner_permissions(self):
e = Element.objects.create(name="New Element", full_name="New Element Full Name", element_type="system")
e.save()
Expand Down
7 changes: 6 additions & 1 deletion controls/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from controls.models import Element
from siteapp.model_mixins.tags import TagView, build_tag_urls
# delete_tag_urls

admin.autodiscover()

Expand Down Expand Up @@ -89,10 +90,14 @@
url(r'^elements/(\d+)/__edit$', views.edit_element, name="edit_element"),
*build_tag_urls(r"^elements/(\d+)/", model=Element), # Tag Urls

url(r'^elements/(\d+)/__delete$', views.delete_element, name="delete_element"),
# *delete_component(r"^elements/(\d+)/", model=Element), # Tag Urls


# Controls
url(r'^catalogs/(?P<catalog_key>.*)/group/(?P<g_id>.*)', views.group, name="control_group"),
url(r'^catalogs/(?P<catalog_key>.*)/control/(?P<cl_id>.*)', views.control, name="control_info"),
url(r'^api/controlsselect/', views.api_controls_select, name="api_controls_select"),
url(r'^api/controls/select/', views.api_controls_select, name="api_controls_select"),

# System Security plan
url(r'^(?P<system_id>.*)/export/oscal', views.OSCAL_ssp_export, name="ssp_export_oscal"),
Expand Down
10 changes: 10 additions & 0 deletions controls/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,16 @@ def edit_element(request, element_id):
msg_list = [f"{e.title()} - {errors[e][0]['message']}" for e in errors.keys()]
return JsonResponse({"status": "err", "message": "Please fix the following problems:<br>"+"<br>".join(msg_list)})

@login_required
def delete_element(request, element_id):

if request.method == 'POST':
Element.objects.filter(pk=element_id).update(deleted = True)
messages.add_message(request, messages.INFO, f"Element has been marked deleted.")
return HttpResponseRedirect('/controls/components')
else:
return HttpResponseRedirect('/controls/components')

class SelectedComponentsList(ListView):
"""
Display System's selected components view
Expand Down
1 change: 1 addition & 0 deletions siteapp/model_mixins/tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ def build_tag_urls(path_prefix, model):
url(rf'{path_prefix}tags/$', lambda *args, **kwargs: TagView.list_tags(*args, model, **kwargs),
name=f"list_element_{model.__name__.lower()}"),
]

34 changes: 33 additions & 1 deletion templates/components/element_detail_tabs.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@

.control-id-text { font-weight: bold; }

.cmpt-deleted { color:red; font-weight: bold; }


</style>
{% endblock %}
Expand Down Expand Up @@ -110,7 +112,11 @@ <h2 class="">
<li id="opencontrol-tab" role="presentation"><a href="#opencontrol" aria-controls="opencontrol" role="tab" data-toggle="tab"><span class="glyphicon glyphicon-file"></span> OpenControl </a></li>
{% endif %}
<div style="margin: 0px 12px 0px 0px;">
<a id="copy_component" class="btn btn btn-default pull-right" href={% url 'component_library_component_copy' element_id=element.id %} role="button" style="color: black;">Clone component</a>
<a id="copy_component" class="btn btn btn-default pull-right" href={% url 'component_library_component_copy' element_id=element.id %} role="button" style="color: black;">Clone component</a>&nbsp;
<a id="delete_component" class="btn btn btn-default pull-right"
role="button"
style="color: black;"
onclick="return delete_component()">Delete component</a>
</div>
</ul>

Expand All @@ -122,6 +128,9 @@ <h2 class="">
<div class="row">
<div class="col-xs-8 col-sm-8 col-md-8 col-lg-8 col-xl-8 description-text">
<h3>About</h3>
{% if element.deleted %}
<p><span class="cmpt-deleted">DELETED</span> This component has been marked as deleted.</p>
{% endif %}
{% if element.description %}
{{ element.description }}
{% else %}
Expand Down Expand Up @@ -354,6 +363,7 @@ <h3>Systems</h3>

<!-- local modals -->
{% include "edit-component-modal.html" %}
{% include "delete-component-modal.html" %}

{{ block.super }}
{% endblock %}
Expand Down Expand Up @@ -515,6 +525,28 @@ <h3>Systems</h3>
});
});
}

function delete_component() {
show_delete_component_modal("{{element.name}}","{{element.description}}",(componentName, reason)=>{
ajax_with_indicator({
url: '{% url "delete_element" element.id %}',
method: "POST",
data: {name: componentName,description: reason},
keep_indicator_forever: true,
success: function(res) {

if(res["status"]=="ok"){
hide_edit_component_modal();
window.location.reload();
}
if(res["status"]=="err"){
show_delete_component_modal_error(res["message"])
}
}
});
});
}

</script>

{% endblock %}
49 changes: 49 additions & 0 deletions templates/delete-component-modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<div id="delete-component-modal" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="invitation_modal_title">Delete Component</h4>
</div>
<form role="form" method="POST" action='/controls/elements/{{element.id}}/__delete'>
<div class="modal-body">
{% csrf_token %}
<div class="form-group" data-children-count="1">
<input type="text" name="name" maxlength="250" class="form-control" placeholder="Name" title="Common name or acronym of the element" required="" id="delete-name-input" data-kwimpalastatus="alive" data-kwimpalaid="1609952239310-0">
</div>
<div class="form-group" data-children-count="1">
<label class="control-label" for="id_name">Reason</label>
<input type="text" name="name" maxlength="250" class="form-control" placeholder="Reason" title="Reason for delete" required="" id="delete-reason-input" data-kwimpalastatus="alive" data-kwimpalaid="1609952239310-0">

</div>

</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button id="submit-btn" type="submit" style="background-color: #ff0000; color:white; border-radius: 4px; height: 30px">Delete</button>
</div>
</form>
</div>
</div>
</div>


{% block scripts %}
<script>
function show_delete_component_modal(name,reason,callback) {
document.getElementById("delete-name-input").value = name;
document.getElementById("delete-reason-input").value = reason;
$('#error-alert').hide();
$('#delete-component-modal').modal();
}
function hide_edit_component_modal(){
$('#delete-component-modal').modal('hide');
}
function show_edit_component_modal_error(errorMessage){
$("#error-alert").html(errorMessage);
$('#error-alert').fadeIn();
}


</script>
{% endblock %}