Skip to content

Commit 70b73e9

Browse files
committed
DAST admin: move the page script to a file so the CSP allows it
The DAST admin page carried its tab switching, its modals and the draft refresh in an inline script. The content security policy does not allow inline scripts, so in a browser none of it ran: the tabs did nothing, the Integrations tab among them, and the editor modals did not fill in. The script moves into a static file, the way the rest of the pages do it, and the draft state comes through a data attribute instead of being baked into the script.
1 parent bd7a1aa commit 70b73e9

2 files changed

Lines changed: 81 additions & 76 deletions

File tree

‎app/web/static/dast_admin.js‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/* DAST admin page: tabs, the rule and profile modals, select all, and the
2+
refresh while a draft is working. In a static file because the content
3+
security policy does not allow inline scripts. */
4+
(function () {
5+
"use strict";
6+
7+
// A header checkbox ticks or clears every rule in its class.
8+
document.addEventListener("change", function (e) {
9+
var head = e.target;
10+
if (!head.matches || !head.matches("[data-check-all]")) { return; }
11+
var form = head.closest("form");
12+
if (!form) { return; }
13+
Array.prototype.forEach.call(
14+
form.querySelectorAll('input[name="rule_id"]'),
15+
function (cb) { cb.checked = head.checked; });
16+
});
17+
18+
// Tabs: show one pane at a time, remember it in the address.
19+
var nav = document.querySelector(".section-nav");
20+
var tabs = Array.prototype.slice.call(document.querySelectorAll(".section-nav [data-tab]"));
21+
var panes = Array.prototype.slice.call(document.querySelectorAll("[data-pane]"));
22+
function show(name) {
23+
tabs.forEach(function (t) { t.classList.toggle("active", t.getAttribute("data-tab") === name); });
24+
panes.forEach(function (p) { p.hidden = p.getAttribute("data-pane") !== name; });
25+
}
26+
tabs.forEach(function (t) {
27+
t.addEventListener("click", function (e) {
28+
e.preventDefault();
29+
var name = t.getAttribute("data-tab");
30+
show(name);
31+
window.history.replaceState(null, "", "#tab-" + name);
32+
});
33+
});
34+
var hash = (window.location.hash || "").replace("#tab-", "");
35+
show(tabs.some(function (t) { return t.getAttribute("data-tab") === hash; }) ? hash : "rules");
36+
37+
// The rule modal fills its textarea from the button that opened it, and
38+
// pretty prints the stored spec so it is easy to edit.
39+
var spec = document.getElementById("rule-spec");
40+
var editing = document.getElementById("rule-editing");
41+
var origin = document.getElementById("rule-origin");
42+
function fill(text) {
43+
if (!spec) { return; }
44+
try { spec.value = JSON.stringify(JSON.parse(text), null, 2); }
45+
catch (e) { spec.value = text || ""; }
46+
}
47+
var fromDraft = document.getElementById("rule-from-draft");
48+
document.addEventListener("click", function (e) {
49+
var b = e.target.closest ? e.target.closest('[data-modal="rule-modal"]') : null;
50+
if (!b) { return; }
51+
if (editing) { editing.value = b.getAttribute("data-editing") || ""; }
52+
if (origin) { origin.value = b.getAttribute("data-origin") || "custom"; }
53+
if (fromDraft) { fromDraft.value = b.getAttribute("data-from-draft") || ""; }
54+
fill(b.getAttribute("data-spec") || "");
55+
});
56+
57+
// The scan profile modal fills from the button that opened it, and ticks
58+
// the rulesets the profile already runs.
59+
document.addEventListener("click", function (e) {
60+
var b = e.target.closest ? e.target.closest('[data-modal="profile-modal"]') : null;
61+
if (!b) { return; }
62+
var set = function (id, v) { var el = document.getElementById(id); if (el) { el.value = v; } };
63+
set("profile-editing", b.getAttribute("data-editing") || "");
64+
set("profile-name", b.getAttribute("data-name") || "");
65+
set("profile-depth", b.getAttribute("data-depth") || "premium");
66+
set("profile-notes", b.getAttribute("data-notes") || "");
67+
var chosen = [];
68+
try { chosen = JSON.parse(b.getAttribute("data-rulesets") || "[]"); } catch (x) { chosen = []; }
69+
Array.prototype.forEach.call(
70+
document.querySelectorAll('#profile-modal [name="rulesets"]'),
71+
function (cb) { cb.checked = chosen.indexOf(cb.value) !== -1; });
72+
});
73+
74+
// While a rule is being drafted in the background, refresh so it appears
75+
// under Drafted rules when it is ready, without losing the current tab.
76+
if (nav && nav.getAttribute("data-drafting") === "1") {
77+
window.setTimeout(function () { window.location.reload(); }, 5000);
78+
}
79+
})();

‎app/web/templates/dast_admin.html‎

Lines changed: 2 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
</div>
1515
</div>
1616

17-
<nav class="section-nav" role="tablist" aria-label="DAST admin">
17+
<nav class="section-nav" role="tablist" aria-label="DAST admin" data-drafting="{{ '1' if drafting else '' }}">
1818
<a href="#tab-rules" role="tab" data-tab="rules" class="active">Premium Curated Rules</a>
1919
<a href="#tab-profiles" role="tab" data-tab="profiles">Scan profiles</a>
2020
<a href="#tab-integrations" role="tab" data-tab="integrations">Integrations</a>
@@ -358,79 +358,5 @@
358358
</div>
359359
{% endif %}
360360

361-
<script>
362-
(function () {
363-
// A header checkbox ticks or clears every rule in its class.
364-
document.addEventListener("change", function (e) {
365-
var head = e.target;
366-
if (!head.matches || !head.matches("[data-check-all]")) { return; }
367-
var form = head.closest("form");
368-
if (!form) { return; }
369-
Array.prototype.forEach.call(
370-
form.querySelectorAll('input[name="rule_id"]'),
371-
function (cb) { cb.checked = head.checked; });
372-
});
373-
374-
// Tabs: show one pane at a time, remember it in the address.
375-
var tabs = Array.prototype.slice.call(document.querySelectorAll(".section-nav [data-tab]"));
376-
var panes = Array.prototype.slice.call(document.querySelectorAll("[data-pane]"));
377-
function show(name) {
378-
tabs.forEach(function (t) { t.classList.toggle("active", t.getAttribute("data-tab") === name); });
379-
panes.forEach(function (p) { p.hidden = p.getAttribute("data-pane") !== name; });
380-
}
381-
tabs.forEach(function (t) {
382-
t.addEventListener("click", function (e) {
383-
e.preventDefault();
384-
var name = t.getAttribute("data-tab");
385-
show(name);
386-
window.history.replaceState(null, "", "#tab-" + name);
387-
});
388-
});
389-
var hash = (window.location.hash || "").replace("#tab-", "");
390-
show(tabs.some(function (t) { return t.getAttribute("data-tab") === hash; }) ? hash : "rules");
391-
392-
// The rule modal fills its textarea from the button that opened it, and
393-
// pretty prints the stored spec so it is easy to edit.
394-
var spec = document.getElementById("rule-spec");
395-
var editing = document.getElementById("rule-editing");
396-
var origin = document.getElementById("rule-origin");
397-
function fill(text) {
398-
if (!spec) { return; }
399-
try { spec.value = JSON.stringify(JSON.parse(text), null, 2); }
400-
catch (e) { spec.value = text || ""; }
401-
}
402-
var fromDraft = document.getElementById("rule-from-draft");
403-
document.addEventListener("click", function (e) {
404-
var b = e.target.closest ? e.target.closest('[data-modal="rule-modal"]') : null;
405-
if (!b) { return; }
406-
if (editing) { editing.value = b.getAttribute("data-editing") || ""; }
407-
if (origin) { origin.value = b.getAttribute("data-origin") || "custom"; }
408-
if (fromDraft) { fromDraft.value = b.getAttribute("data-from-draft") || ""; }
409-
fill(b.getAttribute("data-spec") || "");
410-
});
411-
412-
// The scan profile modal fills from the button that opened it, and ticks
413-
// the rulesets the profile already runs.
414-
document.addEventListener("click", function (e) {
415-
var b = e.target.closest ? e.target.closest('[data-modal="profile-modal"]') : null;
416-
if (!b) { return; }
417-
var set = function (id, v) { var el = document.getElementById(id); if (el) { el.value = v; } };
418-
set("profile-editing", b.getAttribute("data-editing") || "");
419-
set("profile-name", b.getAttribute("data-name") || "");
420-
set("profile-depth", b.getAttribute("data-depth") || "premium");
421-
set("profile-notes", b.getAttribute("data-notes") || "");
422-
var chosen = [];
423-
try { chosen = JSON.parse(b.getAttribute("data-rulesets") || "[]"); } catch (x) { chosen = []; }
424-
Array.prototype.forEach.call(
425-
document.querySelectorAll('#profile-modal [name="rulesets"]'),
426-
function (cb) { cb.checked = chosen.indexOf(cb.value) !== -1; });
427-
});
428-
429-
// While a rule is being drafted in the background, refresh so it appears
430-
// under Drafted rules when it is ready, without losing the current tab.
431-
if ({{ 'true' if drafting else 'false' }}) {
432-
window.setTimeout(function () { window.location.reload(); }, 5000);
433-
}
434-
})();
435-
</script>
361+
<script src="{{ base }}/static/dast_admin.js?v={{ asset_v('dast_admin.js') }}"></script>
436362
{% endblock %}

0 commit comments

Comments
 (0)