Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@ <h5 class="modal-title">Scan Package</h5>
Submit Scan Request
</button>
{% else %}
<a id="submit-scan-request" class="btn btn-success" href="{% url 'component_catalog:package_scan' user.dataspace object.uuid %}">
Submit Scan Request
</a>
<form method="post" action="{% url 'component_catalog:package_scan' user.dataspace object.uuid %}">
{% csrf_token %}
<button id="submit-scan-request" type="submit" class="btn btn-success">
Submit Scan Request
</button>
</form>
{% endif %}
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ <h5 class="modal-title">Delete {% trans 'Scan' %}</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<a class="btn btn-danger delete-confirm" href="/">Delete Scan</a>
<form method="post" class="delete-confirm">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete Scan</button>
</form>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@ <h5 class="modal-title">Refresh {% trans 'Scan' %}</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<a class="btn btn-success refresh-confirm" href="/">Refresh Scan</a>
<form method="post" class="refresh-confirm">
{% csrf_token %}
<button type="submit" class="btn btn-success">Refresh Scan</button>
</form>
</div>
</div>
</div>
Expand Down
12 changes: 8 additions & 4 deletions component_catalog/templates/component_catalog/scan_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@
document.addEventListener('DOMContentLoaded', function () {
document.querySelectorAll('.scan_delete_link').forEach(link => {
link.addEventListener('click', function() {
let deleteUrl = this.getAttribute('data-delete-url');
document.querySelector('#scan-delete-modal a.delete-confirm').pathname = deleteUrl;
let deleteUrl = new URL(this.getAttribute('data-delete-url'), window.location.origin);
if (deleteUrl.origin === window.location.origin) {
document.querySelector('#scan-delete-modal form.delete-confirm').action = deleteUrl.pathname;
}
});
});
document.querySelectorAll('.scan_refresh_link').forEach(link => {
link.addEventListener('click', function() {
let refreshUrl = this.getAttribute('data-refresh-url');
document.querySelector('#scan-refresh-modal a.refresh-confirm').pathname = refreshUrl;
let refreshUrl = new URL(this.getAttribute('data-refresh-url'), window.location.origin);
if (refreshUrl.origin === window.location.origin) {
document.querySelector('#scan-refresh-modal form.refresh-confirm').action = refreshUrl.pathname;
}
});
});
});
Expand Down
14 changes: 9 additions & 5 deletions component_catalog/templates/component_catalog/tabs/tab_scan.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</div>
{% include 'component_catalog/includes/scan_package_modal.html' %}
<script>
document.querySelector('a#submit-scan-request').addEventListener('click', function() {
document.querySelector('#submit-scan-request').addEventListener('click', function() {
NEXB.displayOverlay("Submitting Scan Request...");
});
</script>
Expand All @@ -33,14 +33,18 @@
<script>
document.querySelectorAll('.scan_delete_link').forEach(link => {
link.addEventListener('click', function() {
let deleteUrl = this.getAttribute('data-delete-url');
document.querySelector('#scan-delete-modal a.delete-confirm').pathname = deleteUrl;
let deleteUrl = new URL(this.getAttribute('data-delete-url'), window.location.origin);
if (deleteUrl.origin === window.location.origin) {
document.querySelector('#scan-delete-modal form.delete-confirm').action = deleteUrl.pathname;
}
});
});
document.querySelectorAll('.scan_refresh_link').forEach(link => {
link.addEventListener('click', function() {
let refreshUrl = this.getAttribute('data-refresh-url');
document.querySelector('#scan-refresh-modal a.refresh-confirm').pathname = refreshUrl;
let refreshUrl = new URL(this.getAttribute('data-refresh-url'), window.location.origin);
if (refreshUrl.origin === window.location.origin) {
document.querySelector('#scan-refresh-modal form.refresh-confirm').action = refreshUrl.pathname;
}
});
});
</script>
Expand Down
97 changes: 93 additions & 4 deletions component_catalog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2074,6 +2074,14 @@ def test_package_details_view_scan_package_button(self):
response = self.client.get(self.package1_tab_scan_url)
self.assertContains(response, expected1)
self.assertContains(response, expected2)
scan_url = reverse(
"component_catalog:package_scan", args=[self.dataspace.name, self.package1.uuid]
)
expected_form = (
rf'<form method="post" action="{scan_url}">'
r'\s*<input type="hidden" name="csrfmiddlewaretoken" value="\w+">'
)
self.assertRegex(response.content.decode(), expected_form)

alternate = Dataspace.objects.create(name="Alternate")
alternate_user = create_superuser("alternate_user", alternate)
Expand Down Expand Up @@ -2990,30 +2998,111 @@ def test_delete_scan_view(self, mock_fetch_scan_list, mock_delete_scan):
project_uuid = "348df847-f48f-4ac7-b864-5785b44c65e2"
delete_url = reverse("component_catalog:scan_delete", args=[project_uuid])

response = self.client.get(delete_url)
response = self.client.post(delete_url)
self.assertRedirects(response, f"/login/?next={delete_url}")

self.client.login(username=self.super_user.username, password="secret")
response = self.client.get(delete_url)
self.assertEqual(405, response.status_code)
mock_delete_scan.assert_not_called()

response = self.client.post(delete_url)
self.assertEqual(404, response.status_code)

self.dataspace.enable_package_scanning = True
self.dataspace.save()
mock_fetch_scan_list.return_value = None
response = self.client.get(delete_url)
response = self.client.post(delete_url)
self.assertEqual(404, response.status_code)

mock_fetch_scan_list.return_value = {"count": 1}
mock_delete_scan.return_value = True
response = self.client.get(delete_url, follow=True)
response = self.client.post(delete_url, follow=True)
scan_list_url = reverse("component_catalog:scan_list")
self.assertRedirects(response, scan_list_url)
self.assertContains(response, "Scan deleted.")

mock_delete_scan.return_value = False
response = self.client.get(delete_url)
response = self.client.post(delete_url)
self.assertEqual(404, response.status_code)

@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.refresh_scan")
def test_refresh_scan_view(self, mock_refresh_scan):
project_uuid = "348df847-f48f-4ac7-b864-5785b44c65e2"
refresh_url = reverse("component_catalog:scan_refresh", args=[project_uuid])

response = self.client.post(refresh_url)
self.assertRedirects(response, f"/login/?next={refresh_url}")

self.client.login(username=self.super_user.username, password="secret")
response = self.client.get(refresh_url)
self.assertEqual(405, response.status_code)
mock_refresh_scan.assert_not_called()

response = self.client.post(refresh_url)
self.assertEqual(404, response.status_code)

self.dataspace.enable_package_scanning = True
self.dataspace.save()
mock_refresh_scan.return_value = mock.Mock(status_code=200)
response = self.client.post(refresh_url, follow=True)
self.assertRedirects(response, reverse("component_catalog:scan_list"))
self.assertContains(response, "Refresh Scan started.")
mock_refresh_scan.assert_called_once_with(project_uuid)

mock_refresh_scan.return_value = mock.Mock(status_code=400)
response = self.client.post(refresh_url)
self.assertEqual(404, response.status_code)

@mock.patch("component_catalog.views.tasks.scancodeio_submit_scan")
@mock.patch("component_catalog.views.is_available")
@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.is_configured")
def test_package_scan_view(self, mock_is_configured, mock_is_available, mock_submit_scan):
mock_is_configured.return_value = True
mock_is_available.return_value = True
self.dataspace.enable_package_scanning = True
self.dataspace.save()
self.package1.download_url = "https://download.url/package.zip"
self.package1.save()
scan_url = reverse(
"component_catalog:package_scan", args=[self.dataspace.name, self.package1.uuid]
)

response = self.client.post(scan_url)
self.assertRedirects(response, f"/login/?next={scan_url}")

self.client.login(username=self.super_user.username, password="secret")
response = self.client.get(scan_url)
self.assertEqual(405, response.status_code)
mock_submit_scan.assert_not_called()

response = self.client.post(scan_url, follow=True)
self.assertRedirects(response, f"{self.package1.details_url}#scan")
self.assertContains(response, "The Package URL was submitted to ScanCode.io for scanning.")
mock_submit_scan.assert_called_once_with(
uris=self.package1.download_url,
user_uuid=self.super_user.uuid,
dataspace_uuid=self.dataspace.uuid,
)

mock_is_configured.return_value = False
response = self.client.post(scan_url, headers={"HX-Request": "true"})
self.assertEqual(404, response.status_code)

def test_scan_modals_post_with_csrf_token(self):
self.client.login(username=self.super_user.username, password="secret")
self.dataspace.enable_package_scanning = True
self.dataspace.save()

with mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.fetch_scan_list") as fetch:
fetch.return_value = None
response = self.client.get(reverse("component_catalog:scan_list"))

content = response.content.decode()
csrf_input = r'\s*<input type="hidden" name="csrfmiddlewaretoken"'
self.assertRegex(content, r'<form method="post" class="delete-confirm">' + csrf_input)
self.assertRegex(content, r'<form method="post" class="refresh-confirm">' + csrf_input)

@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.fetch_scan_data")
@mock.patch("dejacode_toolkit.scancodeio.ScanCodeIO.is_available")
def test_send_scan_data_as_file_view(self, mock_is_available, mock_fetch_scan_data):
Expand Down
5 changes: 4 additions & 1 deletion component_catalog/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1432,6 +1432,7 @@ def post(self, request, *args, **kwargs):


@login_required
@require_POST
def package_scan_view(request, dataspace, uuid):
user = request.user
dataspace = user.dataspace
Expand Down Expand Up @@ -1477,7 +1478,7 @@ def package_scan_view(request, dataspace, uuid):
messages.error(request, scancode_msg)

if is_hxr:
return Http404
raise Http404

return redirect(f"{package.details_url}#scan")

Expand Down Expand Up @@ -1672,6 +1673,7 @@ def send_scan_data_as_file_view(request, project_uuid, filename):


@login_required
@require_POST
def delete_scan_view(request, project_uuid):
dataspace = request.user.dataspace
if not dataspace.enable_package_scanning:
Expand All @@ -1696,6 +1698,7 @@ def delete_scan_view(request, project_uuid):


@login_required
@require_POST
def refresh_scan_view(request, project_uuid):
user = request.user
dataspace = user.dataspace
Expand Down
4 changes: 2 additions & 2 deletions dejacode/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
from django.views.generic import RedirectView
from django.views.generic import TemplateView

from notifications.views import mark_all_as_read
from rest_framework.routers import DefaultRouter

from component_catalog.api import ComponentViewSet
Expand All @@ -42,6 +41,7 @@
from dje.views import api_docs_view
from dje.views import home_view
from dje.views import index_dispatch
from dje.views import mark_all_notifications_as_read_view
from dje.views import urn_resolve_view
from license_library.api import LicenseAnnotationViewSet
from license_library.api import LicenseViewSet
Expand Down Expand Up @@ -160,7 +160,7 @@
notification_patterns = [
path("", UnreadNotificationsList.as_view(), name="unread"),
path("all/", AllNotificationsList.as_view(), name="all"),
path("mark_all_as_read/", mark_all_as_read, name="mark_all_as_read"),
path("mark_all_as_read/", mark_all_notifications_as_read_view, name="mark_all_as_read"),
path(
"send_scan_notification/<str:key>/", send_scan_notification, name="send_scan_notification"
),
Expand Down
3 changes: 2 additions & 1 deletion dje/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from django.utils.html import mark_safe
from django.utils.http import urlencode
from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
from django.views.generic import RedirectView

from django_registration.backends.activation.views import RegistrationView
Expand Down Expand Up @@ -1697,7 +1698,7 @@ def get_urls(self):
),
path(
"<path:object_id>/send_activation_email/",
self.admin_site.admin_view(self.send_activation_email),
self.admin_site.admin_view(require_POST(self.send_activation_email)),
name="{}_{}_send_activation_email".format(*info),
),
]
Expand Down
3 changes: 2 additions & 1 deletion dje/templates/admin/dje/dataspace/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
{% endif %}
{% if change and template_dataspace and user.is_superuser and user.dataspace.is_reference %}
<li>
<a href="{% url opts|admin_urlname:'clonedataset' object_id %}" class="grp-state-focus">{% trans 'Clone dataset from' %} {{ template_dataspace }}</a>
<form id="clone-dataset-form" method="post" action="{% url opts|admin_urlname:'clonedataset' object_id %}" hidden>{% csrf_token %}</form>
<a href="javascript://" onclick="document.getElementById('clone-dataset-form').submit();" class="grp-state-focus">{% trans 'Clone dataset from' %} {{ template_dataspace }}</a>
</li>
{% endif %}
{% endblock %}
3 changes: 2 additions & 1 deletion dje/templates/admin/dje/dejacode_user/change_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
{{ block.super }}
{% if change %}
<li>
<a href="{% url opts|admin_urlname:'send_activation_email' object_id %}" class="grp-state-focus">{% trans 'Re-send activation email' %}</a>
<form id="send-activation-email-form" method="post" action="{% url opts|admin_urlname:'send_activation_email' object_id %}" hidden>{% csrf_token %}</form>
<a href="javascript://" onclick="document.getElementById('send-activation-email-form').submit();" class="grp-state-focus">{% trans 'Re-send activation email' %}</a>
</li>
{% endif %}
{% endblock %}
Expand Down
5 changes: 4 additions & 1 deletion dje/templates/notifications/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ <h1 class="header-title">
</h1>
</div>
<div class="col-auto">
<a class="btn btn-outline-dark btn-sm" href="{% url 'notifications:mark_all_as_read' %}">Mark all as read</a>
<form method="post" action="{% url 'notifications:mark_all_as_read' %}">
{% csrf_token %}
<button type="submit" class="btn btn-outline-dark btn-sm">Mark all as read</button>
</form>
</div>
</div>
</div>
Expand Down
14 changes: 13 additions & 1 deletion dje/tests/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,19 @@ def test_user_admin_send_activation_email_view(self):
self.assertTrue(self.other_user.is_active)

url = reverse("admin:dje_dejacodeuser_send_activation_email", args=[self.other_user.id])
response = self.client.get(url, follow=True)
response = self.client.get(url)
self.assertEqual(405, response.status_code)
self.other_user.refresh_from_db()
self.assertTrue(self.other_user.is_active)
self.assertEqual(len(mail.outbox), 0)

change_url = reverse("admin:dje_dejacodeuser_change", args=[self.other_user.id])
response = self.client.get(change_url)
self.assertContains(
response, f'<form id="send-activation-email-form" method="post" action="{url}" hidden>'
)

response = self.client.post(url, follow=True)
expected = "An activation email will be sent shortly to the email address."
self.assertContains(response, expected)

Expand Down
Loading
Loading