Skip to content

Commit

Permalink
Merge branch 'master' into SECURITY-367_UNRESTRICTED_GROOVY_SCRIPTING…
Browse files Browse the repository at this point in the history
…_VULN
  • Loading branch information
Wadeck committed Mar 7, 2018
2 parents d5020d5 + 0db5380 commit 6e0a733
Show file tree
Hide file tree
Showing 8 changed files with 1,238 additions and 53 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,25 @@ public String toString() {
return name + "=" + value;
}

}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;

Parameter parameter = (Parameter) o;

if (name != null ? !name.equals(parameter.name) : parameter.name != null)
return false;

return value != null ? value.equals(parameter.value) : parameter.value == null;
}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jenkinsci.plugins.scriptler.builder;

import com.gargoylesoftware.htmlunit.HttpMethod;
import com.gargoylesoftware.htmlunit.WebRequest;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.util.NameValuePair;
import com.gargoylesoftware.htmlunit.xml.XmlPage;
import hudson.model.FileParameterValue;
import hudson.model.FreeStyleProject;
import net.sf.json.JSONObject;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.io.FileUtils;
import org.jenkinsci.plugins.scriptler.ScriptlerManagement;
import org.jenkinsci.plugins.scriptler.ScriptlerManagementHelper;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.RestartableJenkinsRule;

import java.io.File;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

/**
* Warning: a user without RUN_SCRIPT can currently only clone an existing builder INSIDE a project.
* You can search CLONE_NOTE inside this test to see the cases
*/
public class ScriptlerBuilderWithRestartTest {
@Rule
public RestartableJenkinsRule r = new RestartableJenkinsRule();

private static final String SCRIPT_USABLE_1 = "script_usable_1.groovy";
private static final String SCRIPT_USABLE_2 = "script_usable_2.groovy";
private static final String SCRIPT_USABLE_3 = "script_usable_3.groovy";
private static final String SCRIPT_USABLE_4 = "script_usable_4.groovy";

private static final String SCRIPT_NOT_USABLE = "not_usable.groovy";

@Test
public void configRoundtrip() throws Exception {
r.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
r.j.jenkins.setCrumbIssuer(null);

ScriptlerManagement scriptler = r.j.jenkins.getExtensionList(ScriptlerManagement.class).get(0);
ScriptlerManagementHelper helper = new ScriptlerManagementHelper(scriptler);

setupScript(helper, SCRIPT_USABLE_1, true);
setupScript(helper, SCRIPT_USABLE_2, true);
setupScript(helper, SCRIPT_USABLE_3, true);
setupScript(helper, SCRIPT_USABLE_4, true);
setupScript(helper, SCRIPT_NOT_USABLE, false);

FreeStyleProject project = r.j.createFreeStyleProject("test");

JenkinsRule.WebClient wc = r.j.createWebClient();

WebRequest request = new WebRequest(new URL(r.j.getURL() + project.getShortUrl() + "configSubmit"), HttpMethod.POST);

final String projectName = project.getName();
request.setRequestParameters(Arrays.asList(new NameValuePair("json", JSONObject.fromObject(
new HashMap() {{
put("name", projectName);
put("builder", new HashMap() {{
put("kind", ScriptlerBuilder.class.getName());
put("builderId", "");
put("scriptlerScriptId", SCRIPT_USABLE_1);
put("propagateParams", true);
put("defineParams", new HashMap() {{
put("parameters", Arrays.asList(
new HashMap() {{
put("name", "param1");
put("value", "value1");
}},
new HashMap() {{
put("name", "param2");
put("value", "value2");
}}
));
}});
}});
}}
).toString())));
HtmlPage page = wc.getPage(request);
r.j.assertGoodStatus(page);

ScriptlerBuilder scriptlerBuilder = project.getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertEquals(true, scriptlerBuilder.isPropagateParams());
assertEquals(2, scriptlerBuilder.getParameters().length);
assertEquals("param1", scriptlerBuilder.getParameters()[0].getName());
assertEquals("value1", scriptlerBuilder.getParameters()[0].getValue());
assertEquals("param2", scriptlerBuilder.getParameters()[1].getName());
assertEquals("value2", scriptlerBuilder.getParameters()[1].getValue());
}
});

r.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
FreeStyleProject p = r.j.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder = p.getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertEquals(true, scriptlerBuilder.isPropagateParams());
assertEquals(2, scriptlerBuilder.getParameters().length);
assertEquals("param1", scriptlerBuilder.getParameters()[0].getName());
assertEquals("value1", scriptlerBuilder.getParameters()[0].getValue());
assertEquals("param2", scriptlerBuilder.getParameters()[1].getName());
assertEquals("value2", scriptlerBuilder.getParameters()[1].getValue());
}
});
}

private void setupScript(ScriptlerManagementHelper helper, String scriptId, boolean nonAdministerUsing) throws Exception {
File f = new File(scriptId);
FileUtils.writeStringToFile(f, "print 'Hello World!'");
FileItem fi = new FileParameterValue.FileItemImpl(f);
helper.saveScript(fi, nonAdministerUsing, scriptId);
f.delete();
}

@Test
public void configRoundtripConfigXml() throws Exception {
r.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
r.j.jenkins.setCrumbIssuer(null);

ScriptlerManagement scriptler = r.j.jenkins.getExtensionList(ScriptlerManagement.class).get(0);
ScriptlerManagementHelper helper = new ScriptlerManagementHelper(scriptler);

setupScript(helper, SCRIPT_USABLE_1, true);
setupScript(helper, SCRIPT_USABLE_2, true);
setupScript(helper, SCRIPT_USABLE_3, true);
setupScript(helper, SCRIPT_USABLE_4, true);
setupScript(helper, SCRIPT_NOT_USABLE, false);

FreeStyleProject project = r.j.createFreeStyleProject("test");

JenkinsRule.WebClient wc = r.j.createWebClient();

XmlPage xmlPage = wc.goToXml(project.getShortUrl() + "config.xml");
r.j.assertGoodStatus(xmlPage);
String xml = xmlPage.getWebResponse().getContentAsString();

String modifiedXml = xml.replace("<builders/>", "" +
"<builders>\n" +
" <org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>\n" +
" <builderId></builderId>\n" +
" <scriptId>" + SCRIPT_USABLE_1 + "</scriptId>\n" +
" <propagateParams>true</propagateParams>\n" +
" <parameters>\n" +
" <org.jenkinsci.plugins.scriptler.config.Parameter>\n" +
" <name>param1</name>\n" +
" <value>value1</value>\n" +
" </org.jenkinsci.plugins.scriptler.config.Parameter>\n" +
" <org.jenkinsci.plugins.scriptler.config.Parameter>\n" +
" <name>param2</name>\n" +
" <value>value2</value>\n" +
" </org.jenkinsci.plugins.scriptler.config.Parameter>\n" +
" </parameters>\n" +
" </org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder>\n" +
"</builders>"
);

WebRequest request = new WebRequest(new URL(project.getAbsoluteUrl() + "config.xml"), HttpMethod.POST);
request.setRequestBody(modifiedXml);
request.setEncodingType(null);
HtmlPage page = wc.getPage(request);
r.j.assertGoodStatus(page);

project = r.j.jenkins.getItemByFullName(project.getFullName(), FreeStyleProject.class);
ScriptlerBuilder scriptlerBuilder = project.getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertTrue(scriptlerBuilder.getBuilderId().equals(""));
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertEquals(true, scriptlerBuilder.isPropagateParams());
assertEquals(2, scriptlerBuilder.getParameters().length);
assertEquals("param1", scriptlerBuilder.getParameters()[0].getName());
assertEquals("value1", scriptlerBuilder.getParameters()[0].getValue());
assertEquals("param2", scriptlerBuilder.getParameters()[1].getName());
assertEquals("value2", scriptlerBuilder.getParameters()[1].getValue());
}
});

r.addStep(new Statement() {
@Override public void evaluate() throws Throwable {
FreeStyleProject p = r.j.jenkins.getItemByFullName("test", FreeStyleProject.class);

ScriptlerBuilder scriptlerBuilder = p.getBuildersList().get(ScriptlerBuilder.class);
assertNotNull(scriptlerBuilder);
assertNotNull(scriptlerBuilder.getBuilderId());
assertEquals(SCRIPT_USABLE_1, scriptlerBuilder.getScriptId());
assertEquals(true, scriptlerBuilder.isPropagateParams());
assertEquals(2, scriptlerBuilder.getParameters().length);
assertEquals("param1", scriptlerBuilder.getParameters()[0].getName());
assertEquals("value1", scriptlerBuilder.getParameters()[0].getValue());
assertEquals("param2", scriptlerBuilder.getParameters()[1].getName());
assertEquals("value2", scriptlerBuilder.getParameters()[1].getValue());
}
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.jenkinsci.plugins.scriptler.util;

import junit.framework.TestCase;
import net.sf.json.JSONObject;
import net.sf.json.JSONSerializer;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.scriptler.config.Parameter;

import java.io.IOException;
import java.io.InputStream;

public class UIHelperTest extends TestCase {
public void testExtractParameters1() throws Exception {
JSONObject json = getJsonFromFile("/simple1.json");
final Parameter[] extractParameters = UIHelper.extractParameters(json);
assertNotNull("no parameters extracted", extractParameters);
assertEquals("not all params extracted", 2, extractParameters.length);
}

public void testExtractParameters2() throws Exception {
JSONObject json = getJsonFromFile("/simple2.json");
final Parameter[] extractParameters = UIHelper.extractParameters(json);
assertNotNull("no parameters extracted", extractParameters);
assertEquals("not all params extracted", 2, extractParameters.length);
}

public void testExtractParameters_JENKINS_13518() throws Exception {
JSONObject json = getJsonFromFile("/JENKINS-13518.json");
final Parameter[] extractParameters = UIHelper.extractParameters(json);
assertNotNull("no parameters extracted", extractParameters);
assertEquals("not all params extracted", 0, extractParameters.length);
}

private JSONObject getJsonFromFile(String resource) throws IOException {
InputStream is = UIHelperTest.class.getResourceAsStream(resource);
String jsonTxt = IOUtils.toString(is);
JSONObject json = (JSONObject) JSONSerializer.toJSON(jsonTxt);
return json;
}
}
2 changes: 1 addition & 1 deletion src/test/resources/JENKINS-13518.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"":"","builder":{"backupJobName":"","builderId":"","defineParams":false,"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder","parameters":[{"name":"","value":""},{"name":"","value":""}],"scriptlerScriptId":"testOutput.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"},"core:apply":"","description":"","displayNameOrNull":"","name":"test","properties":{"hudson-model-ParametersDefinitionProperty":{},"org-jenkinsci-plugins-envinject-EnvInjectJobProperty":{},"stapler-class-bag":"true"},"scm":{"value":"0"}}
{"":"","builder":{"builderId":"","defineParams":false,"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder","parameters":[{"name":"","value":""},{"name":"","value":""}],"scriptlerScriptId":"testOutput.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"},"core:apply":"","description":"","displayNameOrNull":"","name":"test","properties":{"hudson-model-ParametersDefinitionProperty":{},"org-jenkinsci-plugins-envinject-EnvInjectJobProperty":{},"stapler-class-bag":"true"},"scm":{"value":"0"}}
2 changes: 1 addition & 1 deletion src/test/resources/simple1.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"backupJobName":"new","builderId":"1331321162149_1","defineParams":{"parameters":[{"name":"jobName","value":"${JOB}"},{"name":"buildRange","value":"1-10"}]},"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder$DescriptorImpl","scriptlerScriptId":"bulkDeleteBuilds.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"}
{"builderId":"1331321162149_1","defineParams":{"parameters":[{"name":"jobName","value":"${JOB}"},{"name":"buildRange","value":"1-10"}]},"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder$DescriptorImpl","scriptlerScriptId":"bulkDeleteBuilds.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"}
2 changes: 1 addition & 1 deletion src/test/resources/simple2.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"backupJobName":"","builderId":"","defineParams":{"parameters":[{"name":"param1","value":"value1"},{"name":"param2","value":"value2"}]},"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder$DescriptorImpl","scriptlerScriptId":"bulkDeleteBuilds.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"}
{"builderId":"","defineParams":{"parameters":[{"name":"param1","value":"value1"},{"name":"param2","value":"value2"}]},"kind":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder$DescriptorImpl","scriptlerScriptId":"bulkDeleteBuilds.groovy","stapler-class":"org.jenkinsci.plugins.scriptler.builder.ScriptlerBuilder"}

0 comments on commit 6e0a733

Please sign in to comment.