Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 156 additions & 0 deletions src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@
import static org.jenkinsci.plugins.gitclient.CliGitAPIImpl.TIMEOUT_LOG_PREFIX;

import com.cloudbees.jenkins.plugins.sshcredentials.SSHUserPrivateKey;
import com.cloudbees.plugins.credentials.CredentialsDescriptor;
import com.cloudbees.plugins.credentials.CredentialsScope;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernamePasswordCredentials;
import com.cloudbees.plugins.credentials.common.UsernameCredentials;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
Expand Down Expand Up @@ -53,6 +56,7 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
Expand Down Expand Up @@ -727,6 +731,143 @@
return url != null && unsupportedProtocol(url.toString());
}

/**
* Self contained descriptor so that {@code getDescriptor()} is safe to call on agents, where no
* Jenkins instance is available.
*/
private static class EmbeddedCredentialsDescriptor extends CredentialsDescriptor {

EmbeddedCredentialsDescriptor() {
super(EmbeddedCredentials.class);
}

@Override
@NonNull
public String getDisplayName() {
return "Embedded URL credentials";
}
}

/**
* Static inner class to hold embedded credentials extracted from URLs.
* This avoids SpotBugs warnings about serializable inner classes.
*/
private static class EmbeddedCredentials implements StandardUsernamePasswordCredentials {
@Serial
private static final long serialVersionUID = 1L;

private static final CredentialsDescriptor DESCRIPTOR = new EmbeddedCredentialsDescriptor();

private final String username;
private final Secret password;
private final String host;

EmbeddedCredentials(String username, String password, String host) {
this.username = username;
this.password = Secret.fromString(password);
this.host = host;
}

@Override
@NonNull
public String getDescription() {
return "Credentials extracted from repository URL";
}

@Override
@NonNull
public String getId() {
return "embedded-url-credentials-" + username + "@" + host;
}

@Override
public CredentialsScope getScope() {
return CredentialsScope.GLOBAL;

Check warning on line 785 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 774-785 are not covered by tests
}

@Override
@NonNull
public CredentialsDescriptor getDescriptor() {
return DESCRIPTOR;
}

@Override
@NonNull
public String getUsername() {
return username;
}

@Override
@NonNull
public Secret getPassword() {
return password;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {

Check warning on line 808 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 808 is only partially covered, one branch is missing
return true;

Check warning on line 809 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 809 is not covered by tests
}
if (!(obj instanceof EmbeddedCredentials other)) {
return false;
}
return Objects.equals(username, other.username)

Check warning on line 814 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 814 is only partially covered, one branch is missing
&& Objects.equals(password, other.password)
&& Objects.equals(host, other.host);

Check warning on line 816 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 816 is only partially covered, one branch is missing
}

@Override
public int hashCode() {
return Objects.hash(username, password, host);
}
}

/**
* Returns true if the value is a bare remote name such as {@code origin} rather than a
* repository location.
*/
private static boolean isRemoteName(URIish url) {
if (url.getScheme() != null || url.getHost() != null) {

Check warning on line 830 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 830 is only partially covered, one branch is missing
return false;
}
String value = url.toString();
return !value.isEmpty() && !value.contains("/") && !value.contains("\\");

Check warning on line 834 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 834 is only partially covered, 2 branches are missing
}

/**
* Adds credentials embedded in an http or https URL to the credentials provider, since JGit does
* not use credentials embedded in a URL resolved from git config (JENKINS-69507). Other
* protocols are ignored.
*
* @param url the URL which may contain embedded credentials
*/
private void extractAndAddEmbeddedCredentials(URIish url) {
if (url == null) {
return;
}

String scheme = url.getScheme();
if (scheme == null || !(scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https"))) {

Check warning on line 850 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 850 is only partially covered, one branch is missing
return;
}

String user = url.getUser();
String pass = url.getPass();
if (user == null || user.isEmpty() || pass == null || pass.isEmpty()) {

Check warning on line 856 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 856 is only partially covered, 2 branches are missing
return;
}

String host = url.getHost();
if (host == null || host.isEmpty()) {

Check warning on line 861 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 861 is only partially covered, 2 branches are missing
host = "unknown-host";

Check warning on line 862 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered line

Line 862 is not covered by tests
}

StandardUsernamePasswordCredentials embeddedCredentials = new EmbeddedCredentials(user, pass, host);

addCredentials(url.toString(), embeddedCredentials);
addCredentials(url.setUser(null).setPass(null).toString(), embeddedCredentials);
}

/**
* fetch_.
*
Expand Down Expand Up @@ -816,6 +957,21 @@
if (unsupportedProtocol(url)) {
throw new GitException("unsupported protocol in URL " + url);
}

/* JENKINS-69507 */
URIish urlForCredentials = url;
if (isRemoteName(url)) {
String resolvedUrl = repo.getConfig().getString("remote", url.toString(), "url");
if (resolvedUrl != null) {

Check warning on line 965 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 965 is only partially covered, one branch is missing
try {
urlForCredentials = new URIish(resolvedUrl);
} catch (URISyntaxException e) {
LOGGER.log(Level.FINE, e, () -> "Could not parse the URL configured for remote " + url);

Check warning on line 969 in src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 968-969 are not covered by tests
}
}
}
extractAndAddEmbeddedCredentials(urlForCredentials);

fetch.setRemote(url.toString());
fetch.setCredentialsProvider(getProvider());
fetch.setTransportConfigCallback(getTransportConfigCallback());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -334,15 +334,15 @@ static List<Arguments> gitRepoUrls() throws Exception {
false,
lfsSpecificTest);
repos.add(repo);
/* Add embedded credentials test case if valid username, valid password, CLI git, and http protocol */
/* Add embedded credentials test case if valid username, valid password, CLI git or JGit, and http protocol */
if (username != null
&& !username.matches(".*[@:].*")
&& // Skip special cases of username
password != null
&& !password.matches(".*[@:].*")
&& // Skip special cases of password
implementation.equals("git")
&& // Embedded credentials only implemented for CLI git
(implementation.equals("git") || implementation.equals("jgit"))
&& // Embedded credentials implemented for both CLI git and JGit (JENKINS-69507)
repoURL.startsWith("http")) {
/* Use existing username and password to create an embedded credentials test case */
String repoURLwithCredentials = repoURL.replaceAll(
Expand Down
Loading
Loading