Skip to content

Commit

Permalink
Merge pull request #490 from MarkEWaite/track-credential-use
Browse files Browse the repository at this point in the history
[JENKINS-38827] Track credentials use
  • Loading branch information
MarkEWaite committed Apr 19, 2017
2 parents 383f5ab + 35b3fc0 commit efeb022
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 10 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Expand Up @@ -291,6 +291,12 @@
<version>${workflow.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<scm>
Expand Down
1 change: 1 addition & 0 deletions src/main/java/hudson/plugins/git/GitSCM.java
Expand Up @@ -764,6 +764,7 @@ public GitClient createClient(TaskListener listener, EnvVars environment, Run<?,
StandardUsernameCredentials credentials = CredentialsMatchers.firstOrNull(urlCredentials, idMatcher);
if (credentials != null) {
c.addCredentials(url, credentials);
CredentialsProvider.track(project.getLastBuild(), credentials);
}
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/hudson/plugins/git/UserRemoteConfig.java
Expand Up @@ -185,7 +185,9 @@ public FormValidation doCheckUrl(@AncestorInPath Item item,
GitClient git = Git.with(TaskListener.NULL, environment)
.using(GitTool.getDefaultInstallation().getGitExe())
.getClient();
git.addDefaultCredentials(lookupCredentials(item, credentialsId, url));
StandardCredentials credential = lookupCredentials(item, credentialsId, url);
git.addDefaultCredentials(credential);
CredentialsProvider.track(item, credential);

// attempt to connect the provided URL
try {
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/jenkins/plugins/git/GitSCMFileSystem.java
Expand Up @@ -27,6 +27,7 @@

import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import edu.umd.cs.findbugs.annotations.CheckForNull;
Expand Down Expand Up @@ -257,7 +258,7 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull
GitClient client = git.getClient();
String credentialsId = config.getCredentialsId();
if (credentialsId != null) {
client.addDefaultCredentials(CredentialsMatchers.firstOrNull(
StandardCredentials credential = CredentialsMatchers.firstOrNull(
CredentialsProvider.lookupCredentials(
StandardUsernameCredentials.class,
owner,
Expand All @@ -268,8 +269,9 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull
CredentialsMatchers.withId(credentialsId),
GitClient.CREDENTIALS_MATCHER
)
)
);
);
client.addDefaultCredentials(credential);
CredentialsProvider.track(owner, credential);
}

if (!client.hasGitRepo()) {
Expand Down
78 changes: 73 additions & 5 deletions src/test/java/hudson/plugins/git/AbstractGitTestCase.java
@@ -1,5 +1,10 @@
package hudson.plugins.git;

import com.cloudbees.plugins.credentials.CredentialsNameProvider;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.IdCredentials;
import com.cloudbees.plugins.credentials.common.StandardCredentials;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.Launcher;
Expand All @@ -9,6 +14,8 @@
import hudson.model.Result;
import hudson.model.TaskListener;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.FreeStyleProject;
import hudson.model.Node;
import hudson.plugins.git.extensions.GitSCMExtension;
Expand All @@ -21,6 +28,8 @@
import hudson.plugins.git.extensions.impl.UserExclusion;
import hudson.remoting.VirtualChannel;
import hudson.slaves.EnvironmentVariablesNodeProperty;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.triggers.SCMTrigger;
import hudson.util.StreamTaskListener;

Expand All @@ -41,8 +50,10 @@
import jenkins.plugins.git.GitSampleRepoRule;
import org.jvnet.hudson.test.CaptureEnvironmentBuilder;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import org.kohsuke.stapler.DataBoundConstructor;

/**
* Base class for single repository git plugin tests.
Expand Down Expand Up @@ -100,6 +111,10 @@ protected List<UserRemoteConfig> createRemoteRepositories() throws IOException {
return testRepo.remoteConfigs();
}

protected List<UserRemoteConfig> createRemoteRepositories(StandardCredentials credential) throws IOException {
return testRepo.remoteConfigs(credential);
}

protected FreeStyleProject createFreeStyleProject() throws IOException {
return rule.createFreeStyleProject();
}
Expand Down Expand Up @@ -140,34 +155,45 @@ protected FreeStyleProject setupProject(String branchString, boolean authorOrCom
includedRegions);
}

protected FreeStyleProject setupProject(String branchString, StandardCredentials credential) throws Exception {
return setupProject(Collections.singletonList(new BranchSpec(branchString)),
false, null, null,
null, null, false,
null, null, credential);
}

protected FreeStyleProject setupProject(List<BranchSpec> branches, boolean authorOrCommitter,
String relativeTargetDir, String excludedRegions,
String excludedUsers, String localBranch, boolean fastRemotePoll,
String includedRegions) throws Exception {
return setupProject(branches,
authorOrCommitter, relativeTargetDir, excludedRegions,
excludedUsers, localBranch, fastRemotePoll,
includedRegions, null);
includedRegions, null, null);
}

protected FreeStyleProject setupProject(String branchString, List<SparseCheckoutPath> sparseCheckoutPaths) throws Exception {
return setupProject(Collections.singletonList(new BranchSpec(branchString)),
false, null, null,
null, null, false,
null, sparseCheckoutPaths);
null, sparseCheckoutPaths, null);
}

protected FreeStyleProject setupProject(List<BranchSpec> branches, boolean authorOrCommitter,
String relativeTargetDir, String excludedRegions,
String excludedUsers, String localBranch, boolean fastRemotePoll,
String includedRegions, List<SparseCheckoutPath> sparseCheckoutPaths) throws Exception {
String includedRegions, List<SparseCheckoutPath> sparseCheckoutPaths,
StandardCredentials credential) throws Exception {
FreeStyleProject project = createFreeStyleProject();
GitSCM scm = new GitSCM(
createRemoteRepositories(),
createRemoteRepositories(credential),
branches,
false, Collections.<SubmoduleConfig>emptyList(),
null, null,
Collections.<GitSCMExtension>emptyList());
if (credential != null) {
project.getBuildersList().add(new HasCredentialBuilder(credential.getId()));
}
scm.getExtensions().add(new DisableRemotePoll()); // don't work on a file:// repository
if (relativeTargetDir!=null)
scm.getExtensions().add(new RelativeTargetDirectory(relativeTargetDir));
Expand Down Expand Up @@ -293,4 +319,46 @@ public void showRepo(TestGitRepo repo, String msg) throws Exception {
System.out.println(out.toString());
}
}

public static class HasCredentialBuilder extends Builder {

private final String id;

@DataBoundConstructor
public HasCredentialBuilder(String id) {
this.id = id;
}

public String getId() {
return id;
}

@Override
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener)
throws InterruptedException, IOException {
IdCredentials credentials = CredentialsProvider.findCredentialById(id, IdCredentials.class, build);
if (credentials == null) {
listener.getLogger().printf("Could not find any credentials with id %s%n", id);
build.setResult(Result.FAILURE);
return false;
} else {
listener.getLogger().printf("Found %s credentials with id %s%n", CredentialsNameProvider.name(credentials), id);
return true;
}
}

@TestExtension
public static class DescriptorImpl extends BuildStepDescriptor<Builder> {

@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return true;
}

@Override
public String getDisplayName() {
return "Check that credentials exist";
}
}
}
}
73 changes: 73 additions & 0 deletions src/test/java/hudson/plugins/git/GitSCMTest.java
@@ -1,5 +1,15 @@
package hudson.plugins.git;

import com.cloudbees.plugins.credentials.Credentials;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.CredentialsStore;
import com.cloudbees.plugins.credentials.SystemCredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.domains.Domain;
import com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
Expand Down Expand Up @@ -62,15 +72,18 @@
import static org.hamcrest.Matchers.*;
import static org.hamcrest.CoreMatchers.instanceOf;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;

import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.BeforeClass;

import org.mockito.Mockito;
import static org.mockito.Matchers.*;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import jenkins.model.Jenkins;
import jenkins.plugins.git.CliGitCommand;
import jenkins.plugins.git.GitSampleRepoRule;

Expand All @@ -82,12 +95,72 @@ public class GitSCMTest extends AbstractGitTestCase {
@Rule
public GitSampleRepoRule secondRepo = new GitSampleRepoRule();

private CredentialsStore store = null;

@BeforeClass
public static void setGitDefaults() throws Exception {
CliGitCommand gitCmd = new CliGitCommand(null);
gitCmd.setDefaults();
}

@Before
public void enableSystemCredentialsProvider() throws Exception {
SystemCredentialsProvider.getInstance().setDomainCredentialsMap(
Collections.singletonMap(Domain.global(), Collections.<Credentials>emptyList()));
for (CredentialsStore s : CredentialsProvider.lookupStores(Jenkins.getInstance())) {
if (s.getProvider() instanceof SystemCredentialsProvider.ProviderImpl) {
store = s;
break;

}
}
assertThat("The system credentials provider is enabled", store, notNullValue());
}

private StandardCredentials getInvalidCredential() {
String username = "bad-user";
String password = "bad-password";
CredentialsScope scope = CredentialsScope.GLOBAL;
String id = "username-" + username + "-password-" + password;
return new UsernamePasswordCredentialsImpl(scope, id, "desc: " + id, username, password);
}

@Test
public void trackCredentials() throws Exception {
StandardCredentials credential = getInvalidCredential();
store.addCredentials(Domain.global(), credential);

Fingerprint fingerprint = CredentialsProvider.getFingerprintOf(credential);
assertThat("Fingerprint should not be set before job definition", fingerprint, nullValue());

JenkinsRule.WebClient wc = rule.createWebClient();
HtmlPage page = wc.goTo("credentials/store/system/domain/_/credentials/" + credential.getId());
assertThat("Have usage tracking reported", page.getElementById("usage"), notNullValue());
assertThat("No fingerprint created until first use", page.getElementById("usage-missing"), notNullValue());
assertThat("No fingerprint created until first use", page.getElementById("usage-present"), nullValue());

FreeStyleProject project = setupProject("master", credential);

fingerprint = CredentialsProvider.getFingerprintOf(credential);
assertThat("Fingerprint should not be set before first build", fingerprint, nullValue());

final String commitFile1 = "commitFile1";
commit(commitFile1, johnDoe, "Commit number 1");
build(project, Result.SUCCESS, commitFile1);

fingerprint = CredentialsProvider.getFingerprintOf(credential);
assertThat("Fingerprint should be set after first build", fingerprint, notNullValue());
assertThat(fingerprint.getJobs(), hasItem(is(project.getFullName())));
Fingerprint.RangeSet rangeSet = fingerprint.getRangeSet(project);
assertThat(rangeSet, notNullValue());
assertThat(rangeSet.includes(project.getLastBuild().getNumber()), is(true));

page = wc.goTo("credentials/store/system/domain/_/credentials/" + credential.getId());
assertThat(page.getElementById("usage-missing"), nullValue());
assertThat(page.getElementById("usage-present"), notNullValue());
assertThat(page.getAnchorByText(project.getFullDisplayName()), notNullValue());
}

/**
* Basic test - create a GitSCM based project, check it out and build for the first time.
* Next test that polling works correctly, make another commit, check that polling finds it,
Expand Down
8 changes: 7 additions & 1 deletion src/test/java/hudson/plugins/git/TestGitRepo.java
@@ -1,5 +1,6 @@
package hudson.plugins.git;

import com.cloudbees.plugins.credentials.common.StandardCredentials;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.TaskListener;
Expand Down Expand Up @@ -128,8 +129,13 @@ public void tag(String tagName, String comment) throws GitException, Interrupted
}

public List<UserRemoteConfig> remoteConfigs() throws IOException {
return remoteConfigs(null);
}

List<UserRemoteConfig> remoteConfigs(StandardCredentials credentials) {
String credentialsId = credentials == null ? null : credentials.getId();
List<UserRemoteConfig> list = new ArrayList<>();
list.add(new UserRemoteConfig(gitDir.getAbsolutePath(), "origin", "", null));
list.add(new UserRemoteConfig(gitDir.getAbsolutePath(), "origin", "", credentialsId));
return list;
}
}

0 comments on commit efeb022

Please sign in to comment.