Skip to content

Commit e6a6654

Browse files
authored
Merge pull request #913 from nevingeorgesunny/refactoring-forceSandboxTests
Use local mock server instead of jenkins.io in ScriptApprovalTest#forceSandboxTests
2 parents c6ff4ef + 6d7adf8 commit e6a6654

1 file changed

Lines changed: 103 additions & 60 deletions

File tree

src/test/java/org/jenkinsci/plugins/scriptsecurity/scripts/ScriptApprovalTest.java

Lines changed: 103 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript;
4646
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.TestGroovyRecorder;
4747
import org.jenkinsci.plugins.scriptsecurity.scripts.languages.GroovyLanguage;
48+
import edu.umd.cs.findbugs.annotations.NonNull;
4849
import org.junit.Rule;
4950
import org.junit.Test;
5051
import org.jvnet.hudson.test.Issue;
@@ -55,10 +56,14 @@
5556
import org.xml.sax.SAXException;
5657

5758
import java.io.IOException;
59+
import java.io.OutputStream;
60+
import java.net.InetSocketAddress;
5861
import java.net.URL;
62+
import java.nio.charset.StandardCharsets;
5963
import java.util.List;
6064
import java.util.concurrent.atomic.AtomicLong;
6165
import java.util.logging.Level;
66+
import com.sun.net.httpserver.HttpServer;
6267

6368
import static org.hamcrest.MatcherAssert.assertThat;
6469
import static org.hamcrest.Matchers.hasItemInArray;
@@ -128,19 +133,26 @@ public void malformedScriptApproval() throws Exception {
128133

129134
@Issue("SECURITY-1866")
130135
@Test public void classpathEntriesEscaped() throws Exception {
131-
// Add pending classpath entry.
132-
final UnapprovedClasspathException e = assertThrows(UnapprovedClasspathException.class, () ->
133-
ScriptApproval.get().using(new ClasspathEntry("https://www.example.com/#value=Hack<img id='xss' src=x onerror=alert(123)>Hack")));
134-
135-
// Check for XSS in pending approvals.
136-
JenkinsRule.WebClient wc = r.createWebClient();
137-
HtmlPage approvalPage = wc.goTo("scriptApproval");
138-
assertThat(approvalPage.getElementById("xss"), nullValue());
139-
// Approve classpath entry.
140-
ScriptApproval.get().approveClasspathEntry(e.getHash());
141-
// Check for XSS in approved classpath entries.
142-
HtmlPage approvedPage = wc.goTo("scriptApproval");
143-
assertThat(approvedPage.getElementById("xss"), nullValue());
136+
HttpServer mockJarServer = createAndStartMockJarHttpServer();
137+
String mockJarUrl = "http:/" + mockJarServer.getAddress() + "/library.jar#value=Hack<img id='xss' src=x onerror=alert(123)>Hack";
138+
139+
try {
140+
// Add pending classpath entry.
141+
final UnapprovedClasspathException e = assertThrows(UnapprovedClasspathException.class, () ->
142+
ScriptApproval.get().using(new ClasspathEntry(mockJarUrl)));
143+
144+
// Check for XSS in pending approvals.
145+
JenkinsRule.WebClient wc = r.createWebClient();
146+
HtmlPage approvalPage = wc.goTo("scriptApproval");
147+
assertThat(approvalPage.getElementById("xss"), nullValue());
148+
// Approve classpath entry.
149+
ScriptApproval.get().approveClasspathEntry(e.getHash());
150+
// Check for XSS in approved classpath entries.
151+
HtmlPage approvedPage = wc.goTo("scriptApproval");
152+
assertThat(approvedPage.getElementById("xss"), nullValue());
153+
}finally {
154+
mockJarServer.stop(0);
155+
}
144156
}
145157

146158
@Test public void clearMethodsLifeCycle() throws Exception {
@@ -209,71 +221,102 @@ public void reload() throws Exception {
209221
r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0).get()));
210222
}
211223

224+
212225
@Test
213226
public void forceSandboxTests() throws Exception {
214227
setBasicSecurity();
215228

216-
try (ACLContext ctx = ACL.as(User.getById("devel", true))) {
217-
assertTrue(ScriptApproval.get().isForceSandbox());
218-
assertTrue(ScriptApproval.get().isForceSandboxForCurrentUser());
229+
HttpServer mockJarServer = createAndStartMockJarHttpServer();
230+
String mockJarUrl = "http:/" + mockJarServer.getAddress() + "/library.jar";
219231

220-
final ApprovalContext ac = ApprovalContext.create();
232+
try {
233+
// Test with non-admin user
234+
try (ACLContext ctx = ACL.as(User.getById("devel", true))) {
235+
assertTrue(ScriptApproval.get().isForceSandbox());
236+
assertTrue(ScriptApproval.get().isForceSandboxForCurrentUser());
221237

222-
//Insert new PendingScript - As the user is not admin and ForceSandbox is enabled, nothing should be added
223-
{
224-
ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true);
225-
assertTrue(ScriptApproval.get().getPendingScripts().isEmpty());
226-
}
238+
final ApprovalContext ac = ApprovalContext.create();
227239

228-
//Insert new PendingSignature - As the user is not admin and ForceSandbox is enabled, nothing should be added
229-
{
230-
ScriptApproval.get().accessRejected(
231-
new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac);
232-
assertTrue(ScriptApproval.get().getPendingSignatures().isEmpty());
233-
}
240+
//Insert new PendingScript - As the user is not admin and ForceSandbox is enabled, nothing should be added
241+
{
242+
ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true);
243+
assertTrue(ScriptApproval.get().getPendingScripts().isEmpty());
244+
}
234245

235-
//Insert new Pending Classpath - As the user is not admin and ForceSandbox is enabled, nothing should be added
236-
{
237-
ClasspathEntry cpe = new ClasspathEntry("https://www.jenkins.io");
238-
ScriptApproval.get().configuring(cpe, ac);
239-
ScriptApproval.get().addPendingClasspathEntry(
240-
new ScriptApproval.PendingClasspathEntry("hash", new URL("https://www.jenkins.io"), ac));
241-
assertThrows(UnapprovedClasspathException.class, () -> ScriptApproval.get().using(cpe));
242-
// As we are forcing sandbox, none of the previous operations are able to create new pending ClasspathEntries
243-
assertTrue(ScriptApproval.get().getPendingClasspathEntries().isEmpty());
246+
//Insert new PendingSignature - As the user is not admin and ForceSandbox is enabled, nothing should be added
247+
{
248+
ScriptApproval.get().accessRejected(
249+
new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac);
250+
assertTrue(ScriptApproval.get().getPendingSignatures().isEmpty());
251+
}
252+
253+
//Insert new Pending Classpath - As the user is not admin and ForceSandbox is enabled, nothing should be added
254+
{
255+
ClasspathEntry cpe = new ClasspathEntry(mockJarUrl);
256+
ScriptApproval.get().configuring(cpe, ac);
257+
ScriptApproval.get().addPendingClasspathEntry(
258+
new ScriptApproval.PendingClasspathEntry("hash", new URL(mockJarUrl), ac));
259+
assertThrows(UnapprovedClasspathException.class, () -> ScriptApproval.get().using(cpe));
260+
// As we are forcing sandbox, none of the previous operations are able to create new pending ClasspathEntries
261+
assertTrue(ScriptApproval.get().getPendingClasspathEntries().isEmpty());
262+
}
244263
}
245-
}
246264

247-
try (ACLContext ctx = ACL.as(User.getById("admin", true))) {
248-
assertTrue(ScriptApproval.get().isForceSandbox());
249-
assertFalse(ScriptApproval.get().isForceSandboxForCurrentUser());
265+
// Test with admin user
266+
try (ACLContext ctx = ACL.as(User.getById("admin", true))) {
267+
assertTrue(ScriptApproval.get().isForceSandbox());
268+
assertFalse(ScriptApproval.get().isForceSandboxForCurrentUser());
250269

251-
final ApprovalContext ac = ApprovalContext.create();
270+
final ApprovalContext ac = ApprovalContext.create();
252271

253-
//Insert new PendingScript - As the user is admin, the behavior does not change
254-
{
255-
ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true);
256-
assertEquals(1, ScriptApproval.get().getPendingScripts().size());
257-
}
272+
//Insert new PendingScript - As the user is admin, the behavior does not change
273+
{
274+
ScriptApproval.get().configuring("testScript", GroovyLanguage.get(), ac, true);
275+
assertEquals(1, ScriptApproval.get().getPendingScripts().size());
276+
}
258277

259-
//Insert new PendingSignature - - As the user is admin, the behavior does not change
260-
{
261-
ScriptApproval.get().accessRejected(
262-
new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac);
263-
assertEquals(1, ScriptApproval.get().getPendingSignatures().size());
264-
}
278+
//Insert new PendingSignature - - As the user is admin, the behavior does not change
279+
{
280+
ScriptApproval.get().accessRejected(
281+
new RejectedAccessException("testSignatureType", "testSignatureDetails"), ac);
282+
assertEquals(1, ScriptApproval.get().getPendingSignatures().size());
283+
}
265284

266-
//Insert new Pending ClassPatch - - As the user is admin, the behavior does not change
267-
{
268-
ClasspathEntry cpe = new ClasspathEntry("https://www.jenkins.io");
269-
ScriptApproval.get().configuring(cpe, ac);
270-
ScriptApproval.get().addPendingClasspathEntry(
271-
new ScriptApproval.PendingClasspathEntry("hash", new URL("https://www.jenkins.io"), ac));
272-
assertEquals(1, ScriptApproval.get().getPendingClasspathEntries().size());
285+
//Insert new Pending ClassPatch - - As the user is admin, the behavior does not change
286+
{
287+
ClasspathEntry cpe = new ClasspathEntry(mockJarUrl);
288+
ScriptApproval.get().configuring(cpe, ac);
289+
ScriptApproval.get().addPendingClasspathEntry(
290+
new ScriptApproval.PendingClasspathEntry("hash", new URL(mockJarUrl), ac));
291+
assertEquals(1, ScriptApproval.get().getPendingClasspathEntries().size());
292+
}
273293
}
294+
} finally {
295+
mockJarServer.stop(0);
274296
}
275297
}
276298

299+
/**
300+
* Creates and starts a mock HTTP server that serves a dummy JAR file at the path "/library.jar".
301+
* <p>
302+
* The server listens on a random available port on localhost and responds with a simple string
303+
* as the JAR content. This is useful for tests that require a remote JAR resource.
304+
* </p>
305+
*/
306+
private static @NonNull HttpServer createAndStartMockJarHttpServer() throws IOException {
307+
HttpServer mockServer = HttpServer.create(new InetSocketAddress("localhost", 0), 0);
308+
mockServer.createContext("/library.jar", exchange -> {
309+
byte[] responseBytes = "Mock JAR content".getBytes(StandardCharsets.UTF_8);
310+
exchange.sendResponseHeaders(200, responseBytes.length);
311+
try (exchange; OutputStream os = exchange.getResponseBody()) {
312+
os.write(responseBytes);
313+
}
314+
});
315+
mockServer.setExecutor(null);
316+
mockServer.start();
317+
return mockServer;
318+
}
319+
277320
@Test
278321
public void forceSandboxScriptSignatureException() throws Exception {
279322
ScriptApproval.get().setForceSandbox(true);

0 commit comments

Comments
 (0)