-
-
Notifications
You must be signed in to change notification settings - Fork 398
[JENKINS-69507] JGit fails 2nd and later https fetch with embedded username & password #1728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
…extract and use credentials from URLs, and update tests to avoid SpotBugs warnings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a bug where JGit fails on subsequent fetch operations when credentials are embedded in HTTPS URLs stored in git config (JENKINS-69507). The issue occurs when a repository URL with embedded credentials (e.g., https://user:pass@host/repo.git) is stored as a git remote, and subsequent fetch operations using the remote name fail to authenticate because JGit doesn't automatically extract and use the embedded credentials.
Changes:
- Added logic to extract embedded credentials from URLs resolved from git config during fetch operations
- Created an
EmbeddedCredentialsclass to hold extracted username/password pairs - Enabled embedded credentials integration tests for JGit in addition to CLI git
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| src/main/java/org/jenkinsci/plugins/gitclient/JGitAPIImpl.java | Implements the core fix by adding credential extraction logic to the fetch operation and creating an EmbeddedCredentials class to store extracted credentials |
| src/test/java/org/jenkinsci/plugins/gitclient/JGitEmbeddedCredentialsTest.java | Adds unit tests for credential extraction and URL handling scenarios |
| src/test/java/org/jenkinsci/plugins/gitclient/CredentialsTest.java | Enables existing integration tests to run with JGit for embedded credentials scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * If the URL looks like a remote name (not a full URL), resolve it from git config first | ||
| */ | ||
| URIish urlForCredentials = url; | ||
| if (!url.isRemote() && !org.apache.commons.lang3.StringUtils.containsAny(url.toString(), ":@/\\")) { |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The condition on line 904 appears to have redundant or confusing logic. The check !url.isRemote() already determines if the URL is a remote name vs a full URL. The additional check !StringUtils.containsAny(url.toString(), ":@/\\") may be unnecessary since a remote name that passes !url.isRemote() would typically not contain these characters anyway. Consider simplifying this to just !url.isRemote() or add a comment explaining why both checks are needed.
| if (!url.isRemote() && !org.apache.commons.lang3.StringUtils.containsAny(url.toString(), ":@/\\")) { | |
| if (!url.isRemote()) { |
|
|
||
| addCredentials(url.toString(), embeddedCredentials); |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When adding credentials on line 806, the URL key includes the embedded credentials (e.g., "https://user:pass@host/repo.git"). However, JGit's TransportHttp may query the credentials provider with a URL that has the credentials stripped out (e.g., "https://host/repo.git"). While the SmartCredentialsProvider has fallback logic to match by scheme/host/port (lines 132-149 in SmartCredentialsProvider.java), it would be more robust and efficient to also add the credentials using a URL without the embedded credentials as an additional key. This would avoid reliance on the fallback matching logic and improve performance.
| addCredentials(url.toString(), embeddedCredentials); | |
| // Add credentials keyed by the full URL (including embedded credentials) | |
| addCredentials(url.toString(), embeddedCredentials); | |
| // Also add credentials keyed by the URL without embedded credentials, | |
| // since JGit's TransportHttp may query using a stripped URL. | |
| try { | |
| URIish urlWithoutCredentials = new URIish(url).setUser(null).setPass(null); | |
| addCredentials(urlWithoutCredentials.toString(), embeddedCredentials); | |
| } catch (URISyntaxException e) { | |
| // Fall back to the original key only if stripping credentials fails | |
| LOGGER.log(Level.FINE, "Failed to create URI without embedded credentials for " + url, e); | |
| } |
| @Test | ||
| void testRemoteNameResolutionWithEmbeddedCredentials() throws Exception { | ||
| TaskListener listener = StreamTaskListener.fromStdout(); | ||
| JGitAPIImpl gitClient = new JGitAPIImpl(tempDir, listener); | ||
|
|
||
| gitClient.init_().workspace(tempDir.getAbsolutePath()).execute(); | ||
|
|
||
| String urlWithCreds = "https://testuser:[email protected]/repo.git"; | ||
| gitClient.setRemoteUrl("origin", urlWithCreds); | ||
|
|
||
| String storedUrl = gitClient.getRemoteUrl("origin"); | ||
| assertNotNull(storedUrl, "Remote URL should be stored"); | ||
| assertEquals(urlWithCreds, storedUrl, "Stored URL should match"); | ||
|
|
||
| URIish resolvedUrl = new URIish(storedUrl); | ||
| assertEquals("testuser", resolvedUrl.getUser(), "Username should be in URL"); | ||
| assertEquals("testpass", resolvedUrl.getPass(), "Password should be in URL"); | ||
| } |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test doesn't actually verify that the extractAndAddEmbeddedCredentials method is called or that credentials are properly extracted during a fetch operation. The test only verifies that a URL with embedded credentials can be stored in git config and retrieved. Consider adding a test that actually performs a fetch operation (or mocks it) to verify that credentials are extracted when resolving a remote name to a URL with embedded credentials. This would better align with the actual bug fix for JENKINS-69507.
| /** | ||
| * Test that extractAndAddEmbeddedCredentials properly extracts username and password | ||
| * from a URL and adds them to the credentials provider. | ||
| */ | ||
| @Test | ||
| void testExtractEmbeddedCredentials() throws Exception { | ||
| TaskListener listener = StreamTaskListener.fromStdout(); | ||
| JGitAPIImpl gitClient = new JGitAPIImpl(tempDir, listener); | ||
|
|
||
| URIish urlWithCredentials = new URIish("https://testuser:[email protected]/repo.git"); | ||
|
|
||
| SmartCredentialsProvider provider = gitClient.getProvider(); | ||
|
|
||
| var credsBefore = provider.getCredentials(); | ||
| assertFalse( | ||
| credsBefore.containsKey("https://testuser:[email protected]/repo.git") | ||
| || credsBefore.containsKey("https://example.com/repo.git"), | ||
| "Credentials should not exist before extraction"); | ||
|
|
||
| gitClient.addCredentials(urlWithCredentials.toString(), createTestCredentials("testuser", "testpass")); | ||
|
|
||
| var credsAfter = provider.getCredentials(); | ||
| assertTrue(credsAfter.size() > credsBefore.size(), "Credentials should be added"); | ||
| } |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test manually calls addCredentials but doesn't verify that the extractAndAddEmbeddedCredentials method works correctly. The test description mentions "extractAndAddEmbeddedCredentials properly extracts username and password" but the method being tested is actually just the public addCredentials method. Since extractAndAddEmbeddedCredentials is private and called internally during fetch operations, this test doesn't actually validate the fix for JENKINS-69507. Consider renaming the test to better reflect what it's testing or adding integration tests that exercise the fetch operation.
| StandardUsernamePasswordCredentials embeddedCredentials = | ||
| new EmbeddedCredentials(user, pass, url.getHost()); |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The EmbeddedCredentials constructor receives url.getHost() which could potentially be null for certain URL formats (e.g., file URLs or malformed URLs). While the method extractAndAddEmbeddedCredentials checks for null URL and valid user/pass, it doesn't validate that url.getHost() is non-null before creating EmbeddedCredentials. Consider adding a null check for the host or handling this case gracefully.
| StandardUsernamePasswordCredentials embeddedCredentials = | |
| new EmbeddedCredentials(user, pass, url.getHost()); | |
| String host = url.getHost(); | |
| if (host == null || host.isEmpty()) { | |
| host = "unknown-host"; | |
| } | |
| StandardUsernamePasswordCredentials embeddedCredentials = | |
| new EmbeddedCredentials(user, pass, host); |
| @Override | ||
| @NonNull | ||
| public CredentialsDescriptor getDescriptor() { | ||
| throw new UnsupportedOperationException("Descriptor not available for embedded credentials"); |
Copilot
AI
Jan 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The getDescriptor() method throws UnsupportedOperationException. While this is unlikely to be called for embedded credentials, if any code in the Jenkins ecosystem attempts to retrieve the descriptor (e.g., for serialization or UI purposes), it will fail. Consider implementing a minimal descriptor or documenting why this is safe to throw an exception here.
| @Override | |
| @NonNull | |
| public CredentialsDescriptor getDescriptor() { | |
| throw new UnsupportedOperationException("Descriptor not available for embedded credentials"); | |
| private static final CredentialsDescriptor DESCRIPTOR = new CredentialsDescriptor() { | |
| @Override | |
| public String getDisplayName() { | |
| return "Embedded URL credentials"; | |
| } | |
| }; | |
| @Override | |
| @NonNull | |
| public CredentialsDescriptor getDescriptor() { | |
| return DESCRIPTOR; |
JGit fails 2nd and later https fetch with embedded username & password
Fixes #1657
Testing done
Submitter checklist