Skip to content

Commit

Permalink
Add prototype to add/edit the Vulnerability analysis #98
Browse files Browse the repository at this point in the history
Signed-off-by: tdruez <[email protected]>
  • Loading branch information
tdruez committed Nov 6, 2024
1 parent 02e67c5 commit c14c470
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 5 deletions.
4 changes: 4 additions & 0 deletions dejacode/static/css/dejacode_bootstrap.css
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,10 @@ table.purldb-table .column-license_expression {
vertical-align: middle;
}

#vulnerability-analysis-form fieldset legend {
font-size: 1rem;
}

/* -- Object form (add/edit) -- */
.datepicker {
width: 130px;
Expand Down
50 changes: 50 additions & 0 deletions product_portfolio/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
from product_portfolio.models import Product
from product_portfolio.models import ProductComponent
from product_portfolio.models import ProductPackage
from product_portfolio.models import ProductVulnerabilityAnalysis
from product_portfolio.models import ScanCodeProject


Expand Down Expand Up @@ -945,3 +946,52 @@ def submit(self, product, user):
scancodeproject_uuid=scancode_project.uuid,
)
)


class ProductVulnerabilityAnalysisForm(DataspacedModelForm):
# TODO: Replace this by proper model field
responses = forms.MultipleChoiceField(
choices=ProductVulnerabilityAnalysis.Response.choices,
widget=forms.CheckboxSelectMultiple,
required=False,
help_text=_(
"A response to the vulnerability by the manufacturer, supplier, or project "
"responsible for the affected component or service. "
"More than one response is allowed. "
"Responses are strongly encouraged for vulnerabilities where the analysis "
"state is exploitable."
),
)

class Meta:
model = ProductVulnerabilityAnalysis
fields = [
"product",
"vulnerability",
"state",
"justification",
"responses",
"detail",
]
widgets = {
"product": forms.widgets.HiddenInput,
"vulnerability": forms.widgets.HiddenInput,
"detail": forms.Textarea(attrs={"rows": 3}),
}

def __init__(self, user, *args, **kwargs):
super().__init__(user, *args, **kwargs)

product_field = self.fields["product"]
perms = ["view_product", "change_product"]
product_field.queryset = Product.objects.get_queryset(user, perms=perms)

@property
def helper(self):
helper = FormHelper()
helper.form_method = "post"
helper.form_id = "product-vulnerability-analysis-form"
helper.form_tag = False
helper.modal_title = "Vulnerability analysis"
helper.modal_id = "vulnerability-analysis-modal"
return helper
2 changes: 1 addition & 1 deletion product_portfolio/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
from dje.models import DataspacedQuerySet
from dje.models import History
from dje.models import HistoryFieldsMixin
from dje.models import ReferenceNotesMixin
from dje.models import HistoryUserFieldsMixin
from dje.models import ReferenceNotesMixin
from dje.models import colored_icon_mixin_factory
from dje.validators import generic_uri_validator
from dje.validators import validate_url_segment
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{% load crispy_forms_tags %}
<div id="vulnerability-analysis-modal" class="modal modal-lg" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Vulnerability analysis</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<form autocomplete="off" method="post" id="vulnerability-analysis-form">
<div class="modal-body bg-body-tertiary" id="vulnerability-analysis-modal-body">
</div>
<div class="modal-footer">
<input type="button" name="close" value="Close" class="btn btn-secondary" data-bs-dismiss="modal">
<input type="submit" id="submit-vulnerability-analysis-form" value="Submit" class="btn btn-primary btn-success">
</div>
</form>
</div>
</div>
</div>
56 changes: 56 additions & 0 deletions product_portfolio/templates/product_portfolio/product_details.html
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
{% if tabsets.Imports %}
{% include 'product_portfolio/includes/scancode_project_status_modal.html' %}
{% endif %}
{% include 'product_portfolio/modals/vulnerability_analysis_modal.html' %}
{% endblock %}

{% block extrastyle %}
Expand Down Expand Up @@ -232,6 +233,61 @@
</script>
{% endif %}

{# TODO: Replace by htmx %]
<script>
$(document).ready(function () {
let vulnerability_modal = $('#vulnerability-analysis-modal');
vulnerability_modal.on('show.bs.modal', function (event) {
let modal_body = $('#vulnerability-analysis-modal-body');
modal_body.html(''); // Reset the modal content

let button = $(event.relatedTarget); // Button that triggered the modal
// Extract info from data-* attributes
let edit_url = button.data('edit-url');
let is_addition = button.data('is-addition');
let submit_label = is_addition ? 'Add' : 'Update';

$('#submit-vulnerability-analysis-form').data('edit-url', edit_url);
<!-- $('#edit-productrelation-modal #title-add-update').text(submit_label);-->

$.ajax({
url: edit_url,
success: function(data) {
modal_body.html(data);
},
error: function() {
modal_body.html('Error.');
}
});
});

$('#submit-vulnerability-analysis-form').on('click', function(event){
event.preventDefault();
let modal_body = $('#vulnerability-analysis-modal-body');
let edit_url = $('#submit-vulnerability-analysis-form').data('edit-url');

$.ajax({
url: edit_url,
type: 'POST',
headers: {'X-CSRFToken': csrftoken},
data: $('#vulnerability-analysis-form').serialize(),
success: function(data) {
if (data['success']) {
location.reload();
return false;
}
modal_body.html(data);
edit_modal.animate({scrollTop: 0});
},
error: function(){
modal_body.html('Error.');
}
});
});

});
</script>

{% if purldb_enabled %}
<script>
document.addEventListener('DOMContentLoaded', function () {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,25 @@
<td>
{% if vulnerability.product_vulnerability_analyses.get %}
<ul class="list-unstyled mb-0">
<li>State: {{ vulnerability.product_vulnerability_analyses.get.state }}</li>
<li>Justification: {{ vulnerability.product_vulnerability_analyses.get.justification }}</li>
<li>Responses: {{ vulnerability.product_vulnerability_analyses.get.responses }}</li>
<li>Detail: {{ vulnerability.product_vulnerability_analyses.get.detail }}</li>
{% if vulnerability.product_vulnerability_analyses.get.state %}
<li>State: <strong>{{ vulnerability.product_vulnerability_analyses.get.state }}</strong></li>
{% endif %}
{% if vulnerability.product_vulnerability_analyses.get.justification %}
<li>Justification: <strong>{{ vulnerability.product_vulnerability_analyses.get.justification }}</strong></li>
{% endif %}
{% if vulnerability.product_vulnerability_analyses.get.responses %}
<li>Responses: <strong>{{ vulnerability.product_vulnerability_analyses.get.responses|join:", " }}</strong></li>
{% endif %}
{% if vulnerability.product_vulnerability_analyses.get.detail %}
<li>Detail: {{ vulnerability.product_vulnerability_analyses.get.detail }}</li>
{% endif %}
</ul>
{% endif %}
<span data-bs-toggle="modal" data-bs-target="#vulnerability-analysis-modal" class=""
data-edit-url="{{ product.get_absolute_url }}vulnerability_analysis_ajax_view/?vulnerability_id={{ vulnerability.vulnerability_id }}"
>
<button type="button" data-bs-toggle="tooltip" title="Edit" class="btn btn-link p-0" aria-label="Edit"><i class="far fa-edit fa-sm"></i></button>
</span>
</td>
</tr>
{% empty %}
Expand Down
2 changes: 2 additions & 0 deletions product_portfolio/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from product_portfolio.views import license_summary_view
from product_portfolio.views import scan_all_packages_view
from product_portfolio.views import scancodeio_project_status_view
from product_portfolio.views import vulnerability_analysis_ajax_view

# WARNING: For views that takes a Product instance in the URL, we need
# 2 patterns to support Product with and without version, as the version
Expand Down Expand Up @@ -80,6 +81,7 @@ def product_path(path_segment, view):
name="edit_productrelation_ajax",
),
*product_path("add_customcomponent_ajax", add_customcomponent_ajax_view),
*product_path("vulnerability_analysis_ajax_view", vulnerability_analysis_ajax_view),
*product_path("scan_all_packages", scan_all_packages_view),
*product_path("improve_packages_from_purldb", improve_packages_from_purldb_view),
*product_path("about_files", ProductSendAboutFilesView.as_view()),
Expand Down
30 changes: 30 additions & 0 deletions product_portfolio/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
from product_portfolio.forms import ProductGridConfigurationForm
from product_portfolio.forms import ProductPackageForm
from product_portfolio.forms import ProductPackageInlineForm
from product_portfolio.forms import ProductVulnerabilityAnalysisForm
from product_portfolio.forms import PullProjectDataForm
from product_portfolio.forms import TableInlineFormSetHelper
from product_portfolio.models import CodebaseResource
Expand Down Expand Up @@ -2416,3 +2417,32 @@ def improve_packages_from_purldb_view(request, dataspace, name, version=""):
)
messages.success(request, "Improve Packages from PurlDB in progress...")
return redirect(f"{product.get_absolute_url()}#imports")


@login_required
@csrf_exempt # TODO: Replace this
def vulnerability_analysis_ajax_view(request, dataspace, name, version=""):
user = request.user
form_class = ProductVulnerabilityAnalysisForm

qs = Product.objects.get_queryset(user, perms="change_product")
product = get_object_or_404(qs, name=unquote_plus(name), version=unquote_plus(version))

# TODO: Replace the following by proper view argument
vulnerability_id = request.GET.get("vulnerability_id")
vulnerability_qs = Vulnerability.objects.scope(user.dataspace)
vulnerability = get_object_or_404(vulnerability_qs, vulnerability_id=vulnerability_id)

if request.method == "POST":
form = form_class(user, data=request.POST)
if form.is_valid():
form.save()
messages.success(request, "Vulnerability analysis successfully updated.")
return JsonResponse({"success": "updated"}, status=200)
else:
initial = {"product": product, "vulnerability": vulnerability}
form = form_class(user, initial=initial)

rendered_form = render_crispy_form(form)

return HttpResponse(rendered_form)

0 comments on commit c14c470

Please sign in to comment.