Skip to content
Draft
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
83 changes: 81 additions & 2 deletions tcms/testplans/static/testplans/js/search.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,19 @@
initializeDateTimePicker('#id_before')
initializeDateTimePicker('#id_after')

const multiCloneButton = {
text: '<i class="fa fa-clone"> </i>',
titleAttr: 'Clone',
action: function (e, dt, node, config) {
const selectedTestPlans = getSelectedTestPlans()
if (selectedTestPlans.length === 0) {
return false
}

window.location.assign(`/plan/0/clone/?p=${selectedTestPlans.join('&p=')}`)

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium test

DOM text
is reinterpreted as HTML without escaping meta-characters.

Copilot Autofix

AI 7 months ago

To fix the issue, we need to sanitize or escape the DOM text before using it in the URL. The escapeHTML utility function, which is already imported in the file, can be used to escape special characters in the extracted text. This ensures that any malicious input is neutralized before being included in the URL. The fix involves applying escapeHTML to the id values returned by getChildRows before they are added to the tpIds array.


Suggested changeset 1
tcms/testplans/static/testplans/js/search.js

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tcms/testplans/static/testplans/js/search.js b/tcms/testplans/static/testplans/js/search.js
--- a/tcms/testplans/static/testplans/js/search.js
+++ b/tcms/testplans/static/testplans/js/search.js
@@ -252,3 +252,3 @@
     const parentRow = $('#resultsTable').DataTable().row($(parentRowId).closest('tr'))
-    const id = $(parentRowId).closest('tr').find('td:nth-child(3)').text().trim()
+    const id = escapeHTML($(parentRowId).closest('tr').find('td:nth-child(3)').text().trim())
     const children = hiddenChildRows[id]
EOF
@@ -252,3 +252,3 @@
const parentRow = $('#resultsTable').DataTable().row($(parentRowId).closest('tr'))
const id = $(parentRowId).closest('tr').find('td:nth-child(3)').text().trim()
const id = escapeHTML($(parentRowId).closest('tr').find('td:nth-child(3)').text().trim())
const children = hiddenChildRows[id]
Copilot is powered by AI and may make mistakes. Always verify output.
}
}

const rowsNotShownMessage = $('#main-element').data('trans-some-rows-not-shown')
const table = $('#resultsTable').DataTable({
pageLength: $('#navbar').data('defaultpagesize'),
Expand Down Expand Up @@ -93,6 +106,11 @@
dataTableJsonRPC('TestPlan.filter', params, callbackF, preProcessData)
},
columns: [
{
data: null,
orderable: false,
render: function () { return '<input type="checkbox" class="row-select">' }
},
{
data: null,
defaultContent: '',
Expand Down Expand Up @@ -159,13 +177,34 @@
})
},
dom: 'Bptp',
buttons: exportButtons,
buttons: [
multiCloneButton,
...exportButtons
],
language: {
loadingRecords: '<div class="spinner spinner-lg"></div>',
processing: '<div class="spinner spinner-lg"></div>',
zeroRecords: 'No records found'
},
order: [[1, 'asc']]
order: [[2, 'asc']],
initComplete: function () {
$('.js-toolbar-select-all').on('change', function () {
const checked = this.checked
$('#resultsTable tbody input.row-select')
.prop('checked', checked)
.trigger('change')
})
}
})

// row checkbox handler
$('#resultsTable tbody').on('change', 'input.row-select', function () {
const $tr = $(this).closest('tr')
if (this.checked) {
table.row($tr).select()
} else {
table.row($tr).deselect()
}
})

// Add event listener for opening and closing nested test plans
Expand All @@ -192,6 +231,41 @@
$('#id_product').change(updateVersionSelectFromProduct)
}

function getSelectedTestPlans () {
const inputs = $('#resultsTable tbody input.row-select:checked').closest('tr:visible')
const tpIds = []

inputs.each(function (_, el) {
// Check if the row has collapsed children and add their IDs
tpIds.push(...getChildRows(el))
})

if (tpIds.length === 0) {
alert($('#main-element').data('trans-no-testplans-selected'))
}

return tpIds
}

function getChildRows (parentRowId) {
const tpIds = []
const parentRow = $('#resultsTable').DataTable().row($(parentRowId).closest('tr'))
const id = $(parentRowId).closest('tr').find('td:nth-child(3)').text().trim()
const children = hiddenChildRows[id]

if (id) {
tpIds.push(id)
}

if (children && !parentRow.child.isShown()) {
children.forEach(function (childRow) {
tpIds.push(...getChildRows(childRow))
})
return tpIds
}
return tpIds
}

function hideExpandedChildren (table, parentRow) {
const children = hiddenChildRows[parentRow.data().id]
children.forEach(
Expand All @@ -214,5 +288,10 @@
// this is an array of previously hidden rows
const children = hiddenChildRows[data.id]
$(children).find('td').css('border', '0').css('padding-left', `${childPadding}px`)

if ($(parentRow).find('input.row-select').prop('checked')) {
$(children).find('input.row-select').prop('checked', true).trigger('change')
}

return $(children).show()
}
4 changes: 4 additions & 0 deletions tcms/testplans/templates/testplans/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
class="container-fluid container-cards-pf"
id="main-element"
data-trans-some-rows-not-shown="{% trans 'Some child test plans do not match search criteria'%}"
data-trans-no-testplans-selected="{% trans 'No rows selected! Please select at least one!'%}"
>
<form class="form-horizontal" method="get">
{% csrf_token %}
Expand Down Expand Up @@ -110,6 +111,9 @@
<table class="table table-striped table-bordered table-hover" id="resultsTable">
<thead>
<tr>
<th>
<input type="checkbox" class="js-toolbar-select-all">
</th>
<th></th>
<th>{% trans "ID" %}</th>
<th>{% trans "Test plan" %}</th>
Expand Down
Loading