diff --git a/src/main/java/jenkins/plugins/http_request/auth/CredentialBasicAuthentication.java b/src/main/java/jenkins/plugins/http_request/auth/CredentialBasicAuthentication.java index 1085221a..57ecaffb 100644 --- a/src/main/java/jenkins/plugins/http_request/auth/CredentialBasicAuthentication.java +++ b/src/main/java/jenkins/plugins/http_request/auth/CredentialBasicAuthentication.java @@ -40,7 +40,7 @@ public void addCredentials(HttpHost host, StandardUsernamePasswordCredentials cr if (host == null || credentials == null) { throw new IllegalArgumentException("Null target host or credentials"); } - extraCredentials.put(host, credential); + extraCredentials.put(host, credentials); } @Override diff --git a/src/test/java/jenkins/plugins/http_request/ProxyAuthenticationTest.java b/src/test/java/jenkins/plugins/http_request/ProxyAuthenticationTest.java new file mode 100644 index 00000000..9e2625b6 --- /dev/null +++ b/src/test/java/jenkins/plugins/http_request/ProxyAuthenticationTest.java @@ -0,0 +1,193 @@ +package jenkins.plugins.http_request; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.io.BufferedReader; +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; +import org.jenkinsci.plugins.workflow.job.WorkflowRun; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; +import org.jvnet.hudson.test.junit.jupiter.WithJenkins; + +import hudson.model.Result; + +/** + * Verifies that requests sent through an authenticating proxy carry the proxy + * credential in the {@code Proxy-Authorization} header (issue #314). + * + *
Uses a minimal socket-level proxy rather than a real one: it rejects + * requests whose {@code Proxy-Authorization} does not match the expected + * credential with {@code 407 Proxy Authentication Required}. To keep a + * wrong-credential request from looping forever it downgrades to {@code 403} + * after a few challenged attempts.
+ */ +@WithJenkins +class ProxyAuthenticationTest extends HttpRequestTestBase { + + private static final String PROXY_USER = "proxyUser1"; + private static final String PROXY_PASSWORD = "proxyPassword1"; + private static final String TARGET_USER = "targetUser1"; + private static final String TARGET_PASSWORD = "targetPassword1"; + + @Test + @Timeout(180) + void proxyAuthenticationWithoutTargetAuthSendsProxyCredentials() throws Exception { + try (AuthenticatingProxy proxy = new AuthenticatingProxy()) { + registerBasicCredential("proxy-creds", PROXY_USER, PROXY_PASSWORD); + + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proxyOnly"); + proj.setDefinition(new CpsFlowDefinition( + "def response = httpRequest url:'" + baseURL() + "/StatusOk',\n" + + " httpProxy: 'http://127.0.0.1:" + proxy.port() + "',\n" + + " proxyAuthentication: 'proxy-creds'\n" + + "println('Status: '+response.getStatus())\n", + true)); + + WorkflowRun run = proj.scheduleBuild2(0).get(); + + j.assertBuildStatusSuccess(run); + j.assertLogContains("Status: 200", run); + assertThat(proxy.lastProxyAuthorization(), is(proxy.expectedAuthorization())); + } + } + + @Test + @Timeout(180) + void proxyAuthenticationWithBasicTargetAuthSendsProxyCredentials() throws Exception { + try (AuthenticatingProxy proxy = new AuthenticatingProxy()) { + registerBasicCredential("proxy-creds", PROXY_USER, PROXY_PASSWORD); + registerBasicCredential("target-creds", TARGET_USER, TARGET_PASSWORD); + + WorkflowJob proj = j.jenkins.createProject(WorkflowJob.class, "proxyAndTarget"); + proj.setDefinition(new CpsFlowDefinition( + "def response = httpRequest url:'" + baseURL() + "/StatusOk',\n" + + " httpProxy: 'http://127.0.0.1:" + proxy.port() + "',\n" + + " proxyAuthentication: 'proxy-creds',\n" + + " authentication: 'target-creds'\n" + + "println('Status: '+response.getStatus())\n", + true)); + + WorkflowRun run = proj.scheduleBuild2(0).get(); + + j.assertBuildStatus(Result.SUCCESS, run); + j.assertLogContains("Status: 200", run); + // The proxy must receive the proxy credential, not the target one + assertThat(proxy.lastProxyAuthorization(), is(proxy.expectedAuthorization())); + } + } + + /** + * Minimal authenticating HTTP proxy. Serves requests on a loopback socket, + * answering {@code 200} when {@code Proxy-Authorization} matches the + * expected Basic credential, {@code 407} otherwise. + */ + private static final class AuthenticatingProxy implements Closeable { + private final ServerSocket serverSocket; + private final String expectedAuthorization; + private final AtomicReference