summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java200
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java71
4 files changed, 254 insertions, 21 deletions
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
index 89a254184d..887e970a0c 100644
--- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
+++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java
@@ -936,26 +936,8 @@ public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase {
}
}
- @Test
- public void testInitialClone_WithAuthentication() throws Exception {
- try (Repository dst = createBareRepository();
- Transport t = Transport.open(dst, authURI)) {
- assertFalse(dst.getObjectDatabase().has(A_txt));
- t.setCredentialsProvider(testCredentials);
- t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
- assertTrue(dst.getObjectDatabase().has(A_txt));
- assertEquals(B, dst.exactRef(master).getObjectId());
- fsck(dst, B);
- }
-
- List<AccessEvent> requests = getRequests();
- assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
-
- AccessEvent info = requests.get(0);
- assertEquals("GET", info.getMethod());
- assertEquals(401, info.getStatus());
-
- info = requests.get(1);
+ private void assertFetchRequests(List<AccessEvent> requests, int index) {
+ AccessEvent info = requests.get(index++);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
@@ -967,7 +949,7 @@ public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase {
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));
}
- for (int i = 2; i < requests.size(); i++) {
+ for (int i = index; i < requests.size(); i++) {
AccessEvent service = requests.get(i);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
@@ -984,6 +966,182 @@ public class SmartClientSmartServerTest extends AllProtocolsHttpTestCase {
}
@Test
+ public void testInitialClone_WithAuthentication() throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+
+ assertFetchRequests(requests, 1);
+ }
+
+ @Test
+ public void testInitialClone_WithPreAuthentication() throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
+
+ assertFetchRequests(requests, 0);
+ }
+
+ @Test
+ public void testInitialClone_WithPreAuthenticationCleared()
+ throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password);
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(null, null);
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+
+ assertFetchRequests(requests, 1);
+ }
+
+ @Test
+ public void testInitialClone_PreAuthenticationTooLate() throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
+ assertFetchRequests(requests, 0);
+ assertThrows(IllegalStateException.class,
+ () -> ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password));
+ assertThrows(IllegalStateException.class, () -> ((TransportHttp) t)
+ .setPreemptiveBasicAuthentication(null, null));
+ }
+ }
+
+ @Test
+ public void testInitialClone_WithWrongPreAuthenticationAndCredentialProvider()
+ throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password + 'x');
+ t.setCredentialsProvider(testCredentials);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 4 : 3, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+
+ assertFetchRequests(requests, 1);
+ }
+
+ @Test
+ public void testInitialClone_WithWrongPreAuthentication() throws Exception {
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, authURI)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password + 'x');
+ TransportException e = assertThrows(TransportException.class,
+ () -> t.fetch(NullProgressMonitor.INSTANCE,
+ mirror(master)));
+ String msg = e.getMessage();
+ assertTrue("Unexpected exception message: " + msg,
+ msg.contains("no CredentialsProvider"));
+ }
+ List<AccessEvent> requests = getRequests();
+ assertEquals(1, requests.size());
+
+ AccessEvent info = requests.get(0);
+ assertEquals("GET", info.getMethod());
+ assertEquals(401, info.getStatus());
+ }
+
+ @Test
+ public void testInitialClone_WithUserInfo() throws Exception {
+ URIish withUserInfo = authURI.setUser(AppServer.username)
+ .setPass(AppServer.password);
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, withUserInfo)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
+
+ assertFetchRequests(requests, 0);
+ }
+
+ @Test
+ public void testInitialClone_PreAuthOverridesUserInfo() throws Exception {
+ URIish withUserInfo = authURI.setUser(AppServer.username)
+ .setPass(AppServer.password + 'x');
+ try (Repository dst = createBareRepository();
+ Transport t = Transport.open(dst, withUserInfo)) {
+ assertFalse(dst.getObjectDatabase().has(A_txt));
+ ((TransportHttp) t).setPreemptiveBasicAuthentication(
+ AppServer.username, AppServer.password);
+ t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
+ assertTrue(dst.getObjectDatabase().has(A_txt));
+ assertEquals(B, dst.exactRef(master).getObjectId());
+ fsck(dst, B);
+ }
+
+ List<AccessEvent> requests = getRequests();
+ assertEquals(enableProtocolV2 ? 3 : 2, requests.size());
+
+ assertFetchRequests(requests, 0);
+ }
+
+ @Test
public void testInitialClone_WithAuthenticationNoCredentials()
throws Exception {
try (Repository dst = createBareRepository();
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 3d4df6b63c..a8b2e563f2 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -312,6 +312,8 @@ hoursAgo={0} hours ago
httpConfigCannotNormalizeURL=Cannot normalize URL path {0}: too many .. segments
httpConfigInvalidURL=Cannot parse URL from subsection http.{0} in git config; ignored.
httpFactoryInUse=Changing the HTTP connection factory after an HTTP connection has already been opened is not allowed.
+httpPreAuthTooLate=HTTP Basic preemptive authentication cannot be set once an HTTP connection has already been opened.
+httpUserInfoDecodeError=Cannot decode user info from URL {}; ignored.
httpWrongConnectionType=Wrong connection type: expected {0}, got {1}.
hugeIndexesAreNotSupportedByJgitYet=Huge indexes are not supported by jgit, yet
hunkBelongsToAnotherFile=Hunk belongs to another file
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index a62efa9894..07fb59ddf3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -340,6 +340,8 @@ public class JGitText extends TranslationBundle {
/***/ public String httpConfigCannotNormalizeURL;
/***/ public String httpConfigInvalidURL;
/***/ public String httpFactoryInUse;
+ /***/ public String httpPreAuthTooLate;
+ /***/ public String httpUserInfoDecodeError;
/***/ public String httpWrongConnectionType;
/***/ public String hugeIndexesAreNotSupportedByJgitYet;
/***/ public String hunkBelongsToAnotherFile;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
index 1cad78b535..2e5d18dc15 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java
@@ -41,6 +41,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.InterruptedIOException;
import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
import java.net.HttpCookie;
import java.net.MalformedURLException;
import java.net.Proxy;
@@ -49,6 +50,7 @@ import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
@@ -408,6 +410,41 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
return factory;
}
+ /**
+ * Sets preemptive Basic HTTP authentication. If the given {@code username}
+ * or {@code password} is empty or {@code null}, no preemptive
+ * authentication will be done. If {@code username} and {@code password} are
+ * set, they will override authority information from the URI
+ * ("user:password@").
+ * <p>
+ * If the connection encounters redirects, the pre-authentication will be
+ * cleared if the redirect goes to a different host.
+ * </p>
+ *
+ * @param username
+ * to use
+ * @param password
+ * to use
+ * @throws IllegalStateException
+ * if an HTTP/HTTPS connection has already been opened on this
+ * {@link TransportHttp} instance
+ * @since 5.11
+ */
+ public void setPreemptiveBasicAuthentication(String username,
+ String password) {
+ if (factoryUsed) {
+ throw new IllegalStateException(JGitText.get().httpPreAuthTooLate);
+ }
+ if (StringUtils.isEmptyOrNull(username)
+ || StringUtils.isEmptyOrNull(password)) {
+ authMethod = authFromUri(currentUri);
+ } else {
+ HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
+ basic.authorize(username, password);
+ authMethod = basic;
+ }
+ }
+
/** {@inheritDoc} */
@Override
public FetchConnection openFetch() throws TransportException,
@@ -563,6 +600,28 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
return new NoRemoteRepositoryException(u, text);
}
+ private HttpAuthMethod authFromUri(URIish u) {
+ String user = u.getUser();
+ String pass = u.getPass();
+ if (user != null && pass != null) {
+ try {
+ // User/password are _not_ application/x-www-form-urlencoded. In
+ // particular the "+" sign would be replaced by a space.
+ user = URLDecoder.decode(user.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$
+ StandardCharsets.UTF_8.name());
+ pass = URLDecoder.decode(pass.replace("+", "%2B"), //$NON-NLS-1$ //$NON-NLS-2$
+ StandardCharsets.UTF_8.name());
+ HttpAuthMethod basic = HttpAuthMethod.Type.BASIC.method(null);
+ basic.authorize(user, pass);
+ return basic;
+ } catch (IllegalArgumentException
+ | UnsupportedEncodingException e) {
+ LOG.warn(JGitText.get().httpUserInfoDecodeError, u);
+ }
+ }
+ return HttpAuthMethod.Type.NONE.method(null);
+ }
+
private HttpConnection connect(String service)
throws TransportException, NotSupportedException {
return connect(service, null);
@@ -572,6 +631,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
TransferConfig.ProtocolVersion protocolVersion)
throws TransportException, NotSupportedException {
URL u = getServiceURL(service);
+ if (HttpAuthMethod.Type.NONE.equals(authMethod.getType())) {
+ authMethod = authFromUri(currentUri);
+ }
int authAttempts = 1;
int redirects = 0;
Collection<Type> ignoreTypes = null;
@@ -878,7 +940,13 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
try {
URI redirectTo = new URI(location);
+ // Reset authentication if the redirect has user/password info or
+ // if the host is different.
+ boolean resetAuth = !StringUtils
+ .isEmptyOrNull(redirectTo.getUserInfo());
+ String currentHost = currentUrl.getHost();
redirectTo = currentUrl.toURI().resolve(redirectTo);
+ resetAuth = resetAuth || !currentHost.equals(redirectTo.getHost());
String redirected = redirectTo.toASCIIString();
if (!isValidRedirect(baseUrl, redirected, checkFor)) {
throw new TransportException(uri,
@@ -887,6 +955,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
redirected = redirected.substring(0, redirected.indexOf(checkFor));
URIish result = new URIish(redirected);
+ if (resetAuth) {
+ authMethod = HttpAuthMethod.Type.NONE.method(null);
+ }
if (LOG.isInfoEnabled()) {
LOG.info(MessageFormat.format(JGitText.get().redirectHttp,
uri.setPass(null),