From edcb137791b2db0afce5df75c3f49003d0e63e95 Mon Sep 17 00:00:00 2001 From: Devin Nusbaum Date: Tue, 2 Sep 2025 14:20:21 -0400 Subject: [PATCH] Contextualize credentials used by GitSCMFileSystem where possible --- .../jenkins/plugins/git/GitSCMFileSystem.java | 3 + .../plugins/git/GitSCMFileSystemTest.java | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java b/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java index bb5cb13710..77c0a68294 100644 --- a/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java +++ b/src/main/java/jenkins/plugins/git/GitSCMFileSystem.java @@ -381,6 +381,9 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull GitClient.CREDENTIALS_MATCHER ) ); + if (_build != null && credential != null && credential.forRun(_build) instanceof StandardCredentials standardCredential) { + credential = standardCredential; + } client.addDefaultCredentials(credential); CredentialsProvider.track(owner, credential); } diff --git a/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java b/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java index 64eb3718d5..44436a2d2c 100644 --- a/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java +++ b/src/test/java/jenkins/plugins/git/GitSCMFileSystemTest.java @@ -25,11 +25,18 @@ package jenkins.plugins.git; +import com.cloudbees.plugins.credentials.CredentialsProvider; +import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials; +import com.cloudbees.plugins.credentials.domains.Domain; +import com.cloudbees.plugins.credentials.impl.BaseStandardCredentials; import hudson.EnvVars; +import hudson.Extension; +import hudson.model.Run; import hudson.model.TaskListener; import hudson.plugins.git.BranchSpec; import hudson.plugins.git.GitSCM; import hudson.plugins.git.GitException; +import hudson.util.Secret; import java.io.ByteArrayOutputStream; import java.io.File; import java.util.Collections; @@ -47,12 +54,15 @@ import org.eclipse.jgit.lib.ObjectId; import org.jenkinsci.plugins.gitclient.Git; import org.jenkinsci.plugins.gitclient.GitClient; +import org.jenkinsci.plugins.workflow.cps.CpsScmFlowDefinition; +import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.junit.BeforeClass; import org.junit.ClassRule; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.Issue; import org.jvnet.hudson.test.JenkinsRule; +import org.kohsuke.stapler.DataBoundConstructor; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; @@ -477,6 +487,67 @@ public void null_pointer_exception() throws Exception { assertEquals(Constants.R_HEADS, result1.prefix); } + @Test + public void filesystem_supports_credential_contextualization() throws Exception { + sampleRepo.init(); + sampleRepo.git("checkout", "-b", "dev"); + sampleRepo.write("Jenkinsfile", "echo 'Hello, world!'"); + sampleRepo.git("add", "Jenkinsfile"); + sampleRepo.git("commit", "--all", "--message=dev"); + var store = CredentialsProvider.lookupStores(r.jenkins).iterator().next(); + store.addCredentials(Domain.global(), new ContextualizableCredentials("my-creds")); + var job = r.createProject(WorkflowJob.class); + var scm = new GitSCM( + GitSCM.createRepoList(sampleRepo.toString(), "my-creds"), + Collections.singletonList(new BranchSpec("*/dev")), + null, + null, + Collections.emptyList()); + var flowDefinition = new CpsScmFlowDefinition(scm, "Jenkinsfile"); + flowDefinition.setLightweight(true); // Triggers use of GitSCMFileSystem + job.setDefinition(flowDefinition); + var run = r.buildAndAssertSuccess(job); + } + + private static class ContextualizableCredentials extends BaseStandardCredentials implements StandardUsernamePasswordCredentials { + private final boolean contextualized; + + @DataBoundConstructor + public ContextualizableCredentials(String id) { + super(id, null); + this.contextualized = false; + } + + private ContextualizableCredentials(ContextualizableCredentials base) { + super(base.getId(), null); + this.contextualized = true; + } + + @Override + public String getUsername() { + if (contextualized) { + return "username"; + } + throw new IllegalStateException("Requires contextualization"); + } + + @Override + public Secret getPassword() { + if (contextualized) { + return Secret.fromString("s3cr3t"); + } + throw new IllegalStateException("Requires contextualization"); + } + + @Override + public ContextualizableCredentials forRun(Run run) { + return new ContextualizableCredentials(this); + } + + @Extension + public static class DescriptorImpl extends BaseStandardCredentialsDescriptor {} + } + /** inline ${@link hudson.Functions#isWindows()} to prevent a transient remote classloader issue */ private boolean isWindows() { return java.io.File.pathSeparatorChar==';';