diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java
index 5bce32055..f585abd20 100644
--- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java
+++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval.java
@@ -816,7 +816,6 @@ private String[][] reconfigure() throws IOException {
save();
}
- // TODO nicer would be to allow the user to actually edit the list directly (with syntax checks)
@Restricted(NoExternalUse.class) // for use from AJAX
@JavaScriptMethod public synchronized String[][] clearApprovedSignatures() throws IOException {
Jenkins.getInstance().checkPermission(Jenkins.RUN_SCRIPTS);
@@ -849,6 +848,32 @@ private String[][] reconfigure() throws IOException {
return reconfigure();
}
+ @Restricted(NoExternalUse.class) // for use from AJAX
+ @JavaScriptMethod public synchronized String[][] clearSelectedSignatures(String[] signatures) throws IOException {
+ Jenkins.getInstance().checkPermission(Jenkins.RUN_SCRIPTS);
+
+ for(String sig : signatures) {
+ Iterator it = approvedSignatures.iterator();
+ while (it.hasNext()) {
+ if (sig.equals(it.next())) {
+ it.remove();
+ break;
+ }
+ }
+
+ it = aclApprovedSignatures.iterator();
+ while (it.hasNext()) {
+ if (sig.equals(it.next())) {
+ it.remove();
+ break;
+ }
+ }
+ }
+
+ save();
+ return reconfigure();
+ }
+
@Restricted(NoExternalUse.class)
public synchronized List getApprovedClasspathEntries() {
ArrayList r = new ArrayList(approvedClasspathEntries);
diff --git a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/index.jelly b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/index.jelly
index 6a9ed46b9..90f580f1d 100644
--- a/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/index.jelly
+++ b/src/main/resources/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApproval/index.jelly
@@ -24,6 +24,30 @@ THE SOFTWARE.
-->
+
@@ -45,10 +69,91 @@ THE SOFTWARE.
}
function updateApprovedSignatures(r) {
var both = r.responseObject();
- $('approvedSignatures').value = both[0].join('\n');
- $('aclApprovedSignatures').value = both[1].join('\n');
- $('dangerousApprovedSignatures').value = both[2].join('\n');
+
+ var newApprovedSignatures = document.createElement('tbody');
+ var oldApprovedSignatures = $('approvedSignaturesBody');
+ newApprovedSignatures.setAttribute('class', 'approvals');
+ newApprovedSignatures.setAttribute('id', 'approvedSignaturesBody');
+ both[0].forEach(function (approvedSig) {
+ var tr = document.createElement('tr');
+
+ var td = document.createElement('td');
+ td.setAttribute('onclick', 'selectRow(this)');
+ td.setAttribute('class', 'text_data');
+
+ var txt = document.createTextNode(approvedSig);
+
+ td.appendChild(txt);
+ tr.appendChild(td);
+ newApprovedSignatures.appendChild(tr);
+ });
+
+ //case where this is the first approvals to be added
+ if(oldApprovedSignatures === null) {
+ $('approvedSignatures').appendChild(newApprovedSignatures);
+ } else {
+ $('approvedSignatures').replaceChild(newApprovedSignatures, oldApprovedSignatures);
+ }
+ //case where the last approval has been removed
+ if(both[0].length === 0 && $('approvedSignatures').firstChild !== null) {
+ $('approvedSignatures').removeChild($('approvedSignatures').firstChild);
+ }
+
+ var newApprovedAclSignatures = document.createElement('tbody');
+ var oldApprovedAclSignatures = $('aclApprovedSignaturesBody');
+ newApprovedAclSignatures.setAttribute('class', 'approvals');
+ newApprovedAclSignatures.setAttribute('id', 'aclApprovedSignaturesBody');
+ both[1].forEach(function (aclApprovedSig) {
+ var tr = document.createElement('tr');
+
+ var td = document.createElement('td');
+ td.setAttribute('onclick', 'selectRow(this)');
+ td.setAttribute('class', 'text_data');
+
+ var txt = document.createTextNode(aclApprovedSig);
+
+ td.appendChild(txt);
+ tr.appendChild(td);
+ newApprovedAclSignatures.appendChild(tr);
+ });
+
+ if(oldApprovedAclSignatures === null) {
+ $('aclApprovedSignatures').appendChild(newApprovedAclSignatures);
+ } else {
+ $('aclApprovedSignatures').replaceChild(newApprovedAclSignatures, oldApprovedAclSignatures);
+ }
+ if(both[1].length === 0 && $('aclApprovedSignatures').firstChild !== null) {
+ $('aclApprovedSignatures').removeChild($('aclApprovedSignatures').firstChild);
+ }
+
+ var newApprovedDangerousSignatures = document.createElement('tbody');
+ var oldApprovedDangerousSignatures = $('dangerousApprovedSignaturesBody');
+ newApprovedDangerousSignatures.setAttribute('class', 'approvals');
+ newApprovedDangerousSignatures.setAttribute('id', 'dangerousApprovedSignaturesBody');
+ both[2].forEach(function (aclApprovedSig) {
+ var tr = document.createElement('tr');
+
+ var td = document.createElement('td');
+ td.setAttribute('onclick', 'selectRow(this)');
+ td.setAttribute('class', 'text_data');
+
+ var txt = document.createTextNode(aclApprovedSig);
+
+ td.appendChild(txt);
+ tr.appendChild(td);
+ newApprovedDangerousSignatures.appendChild(tr);
+ });
+
+ if(oldApprovedDangerousSignatures === null) {
+ $('dangerousApprovedSignatures').appendChild(newApprovedDangerousSignatures);
+ } else {
+ $('dangerousApprovedSignatures').replaceChild(newApprovedDangerousSignatures, oldApprovedDangerousSignatures);
+ }
+ if(both[2].length === 0 && $('dangerousApprovedSignatures').firstChild !== null) {
+ $('dangerousApprovedSignatures').removeChild($('dangerousApprovedSignatures').firstChild);
+ }
}
+
function approveSignature(signature, hash) {
mgr.approveSignature(signature, function(r) {
updateApprovedSignatures(r);
@@ -75,6 +180,16 @@ THE SOFTWARE.
updateApprovedSignatures(r);
});
}
+ function clearSelectedSignatures() {
+ var rows = document.getElementsByClassName('selected');
+ var signatures = [];
+ for(var i = 0; i < rows.length; i++) {
+ signatures.push(rows[i].firstChild.innerText);
+ }
+ mgr.clearSelectedSignatures(signatures, function(r) {
+ updateApprovedSignatures(r);
+ });
+ }
function renderPendingClasspathEntries(pendingClasspathEntries) {
if (pendingClasspathEntries.length == 0) {
@@ -174,6 +289,15 @@ THE SOFTWARE.
renderClasspaths(r);
});
}
+
+ function selectRow(row) {
+ var tr = row.parentElement
+ if(tr.hasClassName("selected")) {
+ tr.removeClassName("selected");
+ } else {
+ tr.addClassName("selected");
+ }
+ }
Event.observe(window, "load", function(){
mgr.getClasspathRenderInfo(function(r) {
@@ -203,6 +327,10 @@ THE SOFTWARE.
You can also remove all previous script approvals:
+
+ Remove selected signatures:
+
+
@@ -230,24 +358,58 @@ THE SOFTWARE.
Signatures already approved:
-
+
Signatures already approved assuming permission check:
-
+
+
+
+
+
+
+
+
-
- Signatures already approved which may have introduced a security vulnerability (recommend clearing):
-
-
+ Signatures already approved which may have introduced a security vulnerability (recommend clearing):
+
+
+
+
+
+
+
+
You can also remove all previous signature approvals:
+
+ Remove selected signatures:
+
+
Or you can just remove the dangerous ones:
diff --git a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java
index 99f8bb595..689272822 100644
--- a/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java
+++ b/src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java
@@ -25,6 +25,7 @@
package org.jenkinsci.plugins.scriptsecurity.scripts;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
+import com.gargoylesoftware.htmlunit.html.HtmlTable;
import com.gargoylesoftware.htmlunit.html.HtmlTextArea;
import hudson.model.FreeStyleProject;
import hudson.model.Result;
@@ -103,8 +104,8 @@ public void malformedScriptApproval() throws Exception {
assertThat(managePageBodyText, Matchers.containsString("1 dangerous signatures previously approved which ought not have been."));
HtmlPage scriptApprovalPage = managePage.getAnchorByHref("scriptApproval").click();
- HtmlTextArea approvedTextArea = scriptApprovalPage.getHtmlElementById("approvedSignatures");
- HtmlTextArea dangerousTextArea = scriptApprovalPage.getHtmlElementById("dangerousApprovedSignatures");
+ HtmlTable approvedTextArea = scriptApprovalPage.getHtmlElementById("approvedSignatures");
+ HtmlTable dangerousTextArea = scriptApprovalPage.getHtmlElementById("dangerousApprovedSignatures");
assertThat(approvedTextArea.getTextContent(), Matchers.containsString(DANGEROUS_SIGNATURE));
assertThat(dangerousTextArea.getTextContent(), Matchers.containsString(DANGEROUS_SIGNATURE));
@@ -140,6 +141,27 @@ public void malformedScriptApproval() throws Exception {
assertEquals(0, sa.getDangerousApprovedSignatures().length);
}
+ @Test public void clearSelectedMethodLifeCycle() throws Exception {
+ ScriptApproval sa = ScriptApproval.get();
+
+ String signature1 = "method java.io.Writer write java.lang.String";
+ String signature2 = "method java.lang.AutoCloseable close";
+
+ sa.approveSignature(WHITELISTED_SIGNATURE);
+ sa.approveSignature(DANGEROUS_SIGNATURE);
+ sa.approveSignature(signature1);
+ sa.approveSignature(signature2);
+ assertEquals(4, sa.getApprovedSignatures().length);
+
+ String[] toBeRemoved = {WHITELISTED_SIGNATURE};
+ sa.clearSelectedSignatures(toBeRemoved);
+ assertEquals(3, sa.getApprovedSignatures().length);
+
+ toBeRemoved = new String[]{signature1, signature2};
+ sa.clearSelectedSignatures(toBeRemoved);
+ assertEquals(1, sa.getApprovedSignatures().length);
+ }
+
@Issue("JENKINS-57563")
@LocalData // Just a scriptApproval.xml that whitelists 'staticMethod jenkins.model.Jenkins getInstance'
@Test