Quellcode durchsuchen

Do authentication re-tries on HTTP POST

There is at least one git server out there (GOGS) that does
not require authentication on the initial GET for
info/refs?service=git-receive-pack but that _does_ require
authentication for the subsequent POST to actually do the push.

This occurs on GOGS with public repositories; for private
repositories it wants authentication up front.

Handle this behavior by adding 401 handling to our POST request.
Note that this is suboptimal; we'll re-send the push data at
least twice if an authentication failure on POST occurs. It
would be much better if the server required authentication
up-front in the GET request.

Added authentication unit tests (using BASIC auth) to the
SmartClientSmartServerTest:

- clone with authentication
- clone with authentication but lacking CredentialsProvider
- clone with authentication and wrong password
- clone with authentication after redirect
- clone with authentication only on POST, but not on GET

Also tested manually in the wild using repositories at try.gogs.io.
That server offers only BASIC auth, so the other paths
(DIGEST, NEGOTIATE, fall back from DIGEST to BASIC) are untested
and I have no way to test them.

* public repository: GET unauthenticated, POST authenticated
  Also tested after clearing the credentials and then entering a
  wrong password: correctly asks three times during the HTTP
  POST for user name and password, then gives up.
* private repository: authentication already on GET; then gets
  applied correctly initially to the POST request, which succeeds.

Also fix the authentication to use the credentials for the redirected
URI if redirects had occurred. We must not present the credentials
for the original URI in that case. Consider a malicious redirect A->B:
this would allow server B to harvest the user credentials for server
A. The unit test for authentication after a redirect also tests for
this.

Bug: 513043
Change-Id: I97ee5058569efa1545a6c6f6edfd2b357c40592a
Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
tags/v4.9.0.201710071750-r
Thomas Wolf vor 7 Jahren
Ursprung
Commit
7ac1bfc834

+ 2
- 0
org.eclipse.jgit.http.test/META-INF/MANIFEST.MF Datei anzeigen

@@ -8,6 +8,8 @@ Bundle-Localization: plugin
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: javax.servlet;version="[2.5.0,3.2.0)",
javax.servlet.http;version="[2.5.0,3.2.0)",
org.apache.commons.codec;version="1.6.0",
org.apache.commons.codec.binary;version="1.6.0",
org.eclipse.jetty.continuation;version="[9.4.5,10.0.0)",
org.eclipse.jetty.http;version="[9.4.5,10.0.0)",
org.eclipse.jetty.io;version="[9.4.5,10.0.0)",

+ 252
- 9
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java Datei anzeigen

@@ -83,6 +83,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jgit.errors.RemoteRepositoryException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.http.server.GitServlet;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
@@ -105,12 +106,15 @@ import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.transport.CredentialItem;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.eclipse.jgit.transport.FetchConnection;
import org.eclipse.jgit.transport.HttpTransport;
import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.transport.Transport;
import org.eclipse.jgit.transport.TransportHttp;
import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.eclipse.jgit.transport.http.HttpConnectionFactory;
import org.eclipse.jgit.transport.http.JDKHttpConnectionFactory;
import org.eclipse.jgit.transport.http.apache.HttpClientConnectionFactory;
@@ -129,12 +133,19 @@ public class SmartClientSmartServerTest extends HttpTestCase {

private Repository remoteRepository;

private CredentialsProvider testCredentials = new UsernamePasswordCredentialsProvider(
AppServer.username, AppServer.password);

private URIish remoteURI;

private URIish brokenURI;

private URIish redirectURI;

private URIish authURI;

private URIish authOnPostURI;

private RevBlob A_txt;

private RevCommit A, B;
@@ -169,7 +180,11 @@ public class SmartClientSmartServerTest extends HttpTestCase {

ServletContextHandler broken = addBrokenContext(gs, src, srcName);

ServletContextHandler redirect = addRedirectContext(gs, src, srcName);
ServletContextHandler redirect = addRedirectContext(gs);

ServletContextHandler auth = addAuthContext(gs, "auth");

ServletContextHandler authOnPost = addAuthContext(gs, "pauth", "POST");

server.setUp();

@@ -177,6 +192,8 @@ public class SmartClientSmartServerTest extends HttpTestCase {
remoteURI = toURIish(app, srcName);
brokenURI = toURIish(broken, srcName);
redirectURI = toURIish(redirect, srcName);
authURI = toURIish(auth, srcName);
authOnPostURI = toURIish(authOnPost, srcName);

A_txt = src.blob("A");
A = src.commit().add("A_txt", A_txt).create();
@@ -271,9 +288,14 @@ public class SmartClientSmartServerTest extends HttpTestCase {
return broken;
}

@SuppressWarnings("unused")
private ServletContextHandler addRedirectContext(GitServlet gs,
TestRepository<Repository> src, String srcName) {
private ServletContextHandler addAuthContext(GitServlet gs,
String contextPath, String... methods) {
ServletContextHandler auth = server.addContext('/' + contextPath);
auth.addServlet(new ServletHolder(gs), "/*");
return server.authBasic(auth, methods);
}

private ServletContextHandler addRedirectContext(GitServlet gs) {
ServletContextHandler redirect = server.addContext("/redirect");
redirect.addFilter(new FilterHolder(new Filter() {

@@ -283,6 +305,11 @@ public class SmartClientSmartServerTest extends HttpTestCase {
private Pattern responsePattern = Pattern
.compile("/response/(\\d+)/(30[1237])/");

// Enables tests to specify the context that the request should be
// redirected to in the end. If not present, redirects got to the
// normal /git context.
private Pattern targetPattern = Pattern.compile("/target(/\\w+)/");

@Override
public void init(FilterConfig filterConfig)
throws ServletException {
@@ -322,18 +349,25 @@ public class SmartClientSmartServerTest extends HttpTestCase {
.parseUnsignedInt(matcher.group(1));
responseCode = Integer.parseUnsignedInt(matcher.group(2));
if (--nofRedirects <= 0) {
urlString = fullUrl.substring(0, matcher.start()) + '/'
+ fullUrl.substring(matcher.end());
urlString = urlString.substring(0, matcher.start())
+ '/' + urlString.substring(matcher.end());
} else {
urlString = fullUrl.substring(0, matcher.start())
urlString = urlString.substring(0, matcher.start())
+ "/response/" + nofRedirects + "/"
+ responseCode + '/'
+ fullUrl.substring(matcher.end());
+ urlString.substring(matcher.end());
}
}
httpServletResponse.setStatus(responseCode);
if (nofRedirects <= 0) {
urlString = urlString.replace("/redirect", "/git");
String targetContext = "/git";
matcher = targetPattern.matcher(urlString);
if (matcher.find()) {
urlString = urlString.substring(0, matcher.start())
+ '/' + urlString.substring(matcher.end());
targetContext = matcher.group(1);
}
urlString = urlString.replace("/redirect", targetContext);
}
httpServletResponse.setHeader(HttpSupport.HDR_LOCATION,
urlString);
@@ -668,6 +702,215 @@ public class SmartClientSmartServerTest extends HttpTestCase {
}
}

@Test
public void testInitialClone_WithAuthentication() throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));

try (Transport t = Transport.open(dst, authURI)) {
t.setCredentialsProvider(testCredentials);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
}

assertTrue(dst.hasObject(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(401, info.getStatus());

info = requests.get(1);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));

AccessEvent service = requests.get(2);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}

@Test
public void testInitialClone_WithAuthenticationNoCredentials()
throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));

try (Transport t = Transport.open(dst, authURI)) {
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
fail("Should not have succeeded -- no authentication");
} catch (TransportException e) {
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_WithAuthenticationWrongCredentials()
throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));

try (Transport t = Transport.open(dst, authURI)) {
t.setCredentialsProvider(new UsernamePasswordCredentialsProvider(
AppServer.username, "wrongpassword"));
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
fail("Should not have succeeded -- wrong password");
} catch (TransportException e) {
String msg = e.getMessage();
assertTrue("Unexpected exception message: " + msg,
msg.contains("auth"));
}
List<AccessEvent> requests = getRequests();
// Once without authentication plus three re-tries with authentication
assertEquals(4, requests.size());

for (AccessEvent event : requests) {
assertEquals("GET", event.getMethod());
assertEquals(401, event.getStatus());
}
}

@Test
public void testInitialClone_WithAuthenticationAfterRedirect()
throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));

URIish cloneFrom = extendPath(redirectURI, "/target/auth");
CredentialsProvider uriSpecificCredentialsProvider = new UsernamePasswordCredentialsProvider(
"unknown", "none") {
@Override
public boolean get(URIish uri, CredentialItem... items)
throws UnsupportedCredentialItem {
// Only return the true credentials if the uri path starts with
// /auth. This ensures that we do provide the correct
// credentials only for the URi after the redirect, making the
// test fail if we should be asked for the credentials for the
// original URI.
if (uri.getPath().startsWith("/auth")) {
return testCredentials.get(uri, items);
}
return super.get(uri, items);
}
};
try (Transport t = Transport.open(dst, cloneFrom)) {
t.setCredentialsProvider(uriSpecificCredentialsProvider);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
}

assertTrue(dst.hasObject(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);

List<AccessEvent> requests = getRequests();
assertEquals(4, requests.size());

AccessEvent redirect = requests.get(0);
assertEquals("GET", redirect.getMethod());
assertEquals(join(cloneFrom, "info/refs"), redirect.getPath());
assertEquals(301, redirect.getStatus());

AccessEvent info = requests.get(1);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(401, info.getStatus());

info = requests.get(2);
assertEquals("GET", info.getMethod());
assertEquals(join(authURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));

AccessEvent service = requests.get(3);
assertEquals("POST", service.getMethod());
assertEquals(join(authURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}

@Test
public void testInitialClone_WithAuthenticationOnPostOnly()
throws Exception {
Repository dst = createBareRepository();
assertFalse(dst.hasObject(A_txt));

try (Transport t = Transport.open(dst, authOnPostURI)) {
t.setCredentialsProvider(testCredentials);
t.fetch(NullProgressMonitor.INSTANCE, mirror(master));
}

assertTrue(dst.hasObject(A_txt));
assertEquals(B, dst.exactRef(master).getObjectId());
fsck(dst, B);

List<AccessEvent> requests = getRequests();
assertEquals(3, requests.size());

AccessEvent info = requests.get(0);
assertEquals("GET", info.getMethod());
assertEquals(join(authOnPostURI, "info/refs"), info.getPath());
assertEquals(1, info.getParameters().size());
assertEquals("git-upload-pack", info.getParameter("service"));
assertEquals(200, info.getStatus());
assertEquals("application/x-git-upload-pack-advertisement",
info.getResponseHeader(HDR_CONTENT_TYPE));
assertEquals("gzip", info.getResponseHeader(HDR_CONTENT_ENCODING));

AccessEvent service = requests.get(1);
assertEquals("POST", service.getMethod());
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
assertEquals(401, service.getStatus());

service = requests.get(2);
assertEquals("POST", service.getMethod());
assertEquals(join(authOnPostURI, "git-upload-pack"), service.getPath());
assertEquals(0, service.getParameters().size());
assertNotNull("has content-length",
service.getRequestHeader(HDR_CONTENT_LENGTH));
assertNull("not chunked",
service.getRequestHeader(HDR_TRANSFER_ENCODING));

assertEquals(200, service.getStatus());
assertEquals("application/x-git-upload-pack-result",
service.getResponseHeader(HDR_CONTENT_TYPE));
}

@Test
public void testFetch_FewLocalCommits() throws Exception {
// Bootstrap by doing the clone.

+ 29
- 10
org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AppServer.java Datei anzeigen

@@ -55,6 +55,7 @@ import java.net.UnknownHostException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@@ -97,6 +98,9 @@ public class AppServer {
/** SSL keystore password; must have at least 6 characters. */
private static final String keyPassword = "mykeys";

/** Role for authentication. */
private static final String authRole = "can-access";

static {
// Install a logger that throws warning messages.
//
@@ -136,10 +140,10 @@ public class AppServer {

/**
* @param port
* for https, may be zero to allocate a port dynamically
* for http, may be zero to allocate a port dynamically
* @param sslPort
* for https,may be zero to allocate a port dynamically. If
* negative, the server will be set up without https support..
* negative, the server will be set up without https support.
* @since 4.9
*/
public AppServer(int port, int sslPort) {
@@ -264,9 +268,10 @@ public class AppServer {
return ctx;
}

public ServletContextHandler authBasic(ServletContextHandler ctx) {
public ServletContextHandler authBasic(ServletContextHandler ctx,
String... methods) {
assertNotYetSetUp();
auth(ctx, new BasicAuthenticator());
auth(ctx, new BasicAuthenticator(), methods);
return ctx;
}

@@ -301,22 +306,36 @@ public class AppServer {
}
}

private void auth(ServletContextHandler ctx, Authenticator authType) {
final String role = "can-access";

AbstractLoginService users = new TestMappedLoginService(role);
private ConstraintMapping createConstraintMapping() {
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(new Constraint());
cm.getConstraint().setAuthenticate(true);
cm.getConstraint().setDataConstraint(Constraint.DC_NONE);
cm.getConstraint().setRoles(new String[] { role });
cm.getConstraint().setRoles(new String[] { authRole });
cm.setPathSpec("/*");
return cm;
}

private void auth(ServletContextHandler ctx, Authenticator authType,
String... methods) {
AbstractLoginService users = new TestMappedLoginService(authRole);
List<ConstraintMapping> mappings = new ArrayList<>();
if (methods == null || methods.length == 0) {
mappings.add(createConstraintMapping());
} else {
for (String method : methods) {
ConstraintMapping cm = createConstraintMapping();
cm.setMethod(method.toUpperCase(Locale.ROOT));
mappings.add(cm);
}
}

ConstraintSecurityHandler sec = new ConstraintSecurityHandler();
sec.setRealmName(realm);
sec.setAuthenticator(authType);
sec.setLoginService(users);
sec.setConstraintMappings(new ConstraintMapping[] { cm });
sec.setConstraintMappings(
mappings.toArray(new ConstraintMapping[mappings.size()]));
sec.setHandler(ctx);

contexts.removeHandler(ctx);

+ 3
- 2
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties Datei anzeigen

@@ -365,7 +365,7 @@ invalidPathContainsSeparator=Invalid path (contains separator ''{0}''): {1}
invalidPathPeriodAtEndWindows=Invalid path (period at end is ignored by Windows): {0}
invalidPathSpaceAtEndWindows=Invalid path (space at end is ignored by Windows): {0}
invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1}
invalidRedirectLocation=Redirect or URI ''{0}'': invalid redirect location {1} -> {2}
invalidRedirectLocation=Invalid redirect location {1} -> {2}
invalidReflogRevision=Invalid reflog revision: {0}
invalidRefName=Invalid ref name: {0}
invalidReftableBlock=Invalid reftable block
@@ -587,7 +587,7 @@ secondsAgo={0} seconds ago
selectingCommits=Selecting commits
sequenceTooLargeForDiffAlgorithm=Sequence too large for difference algorithm.
serviceNotEnabledNoName=Service not enabled
serviceNotPermitted={0} not permitted
serviceNotPermitted={1} not permitted on ''{0}''
sha1CollisionDetected1=SHA-1 collision detected on {0}
shallowCommitsAlreadyInitialized=Shallow commits have already been initialized
shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk
@@ -636,6 +636,7 @@ timeIsUncertain=Time is uncertain
timerAlreadyTerminated=Timer already terminated
tooManyCommands=Too many commands
tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)?
tooManyRedirects=Too many redirects; stopped after {0} redirects at ''{1}''
topologicalSortRequired=Topological sort required.
transactionAborted=transaction aborted
transportExceptionBadRef=Empty ref: {0}: {1}

+ 1
- 0
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java Datei anzeigen

@@ -696,6 +696,7 @@ public class JGitText extends TranslationBundle {
/***/ public String timerAlreadyTerminated;
/***/ public String tooManyCommands;
/***/ public String tooManyIncludeRecursions;
/***/ public String tooManyRedirects;
/***/ public String topologicalSortRequired;
/***/ public String transportExceptionBadRef;
/***/ public String transportExceptionEmptyRef;

+ 120
- 22
org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java Datei anzeigen

@@ -323,6 +323,13 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
}
}

/**
* The current URI we're talking to. The inherited (final) field
* {@link #uri} stores the original URI; {@code currentUri} may be different
* after redirects.
*/
private URIish currentUri;

private URL baseUrl;

private URL objectsUrl;
@@ -360,6 +367,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
*/
protected void setURI(final URIish uri) throws NotSupportedException {
try {
currentUri = uri;
baseUrl = toURL(uri);
objectsUrl = new URL(baseUrl, "objects/"); //$NON-NLS-1$
} catch (MalformedURLException e) {
@@ -584,9 +592,10 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
throw new TransportException(uri,
JGitText.get().noCredentialsProvider);
if (authAttempts > 1)
credentialsProvider.reset(uri);
credentialsProvider.reset(currentUri);
if (3 < authAttempts
|| !authMethod.authorize(uri, credentialsProvider)) {
|| !authMethod.authorize(currentUri,
credentialsProvider)) {
throw new TransportException(uri,
JGitText.get().notAuthorized);
}
@@ -1096,8 +1105,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
buf = out;
}

HttpAuthMethod authenticator = null;
Collection<Type> ignoreTypes = EnumSet.noneOf(Type.class);
// Counts number of repeated authentication attempts using the same
// authentication scheme
int authAttempts = 1;
int redirects = 0;
for (;;) {
// The very first time we will try with the authentication
// method used on the initial GET request. This is a hint only;
// it may fail. If so, we'll then re-try with proper 401
// handling, going through the available authentication schemes.
openStream();
if (buf != out) {
conn.setRequestProperty(HDR_CONTENT_ENCODING, ENCODING_GZIP);
@@ -1107,31 +1125,111 @@ public class TransportHttp extends HttpTransport implements WalkTransport,
buf.writeTo(httpOut, null);
}

if (http.followRedirects == HttpRedirectMode.TRUE) {
final int status = HttpSupport.response(conn);
switch (status) {
case HttpConnection.HTTP_MOVED_PERM:
case HttpConnection.HTTP_MOVED_TEMP:
case HttpConnection.HTTP_11_MOVED_TEMP:
// SEE_OTHER after a POST doesn't make sense for a git
// server, so we don't handle it here and thus we'll
// report an error in openResponse() later on.
URIish newUri = redirect(
conn.getHeaderField(HDR_LOCATION),
'/' + serviceName, redirects++);
try {
baseUrl = toURL(newUri);
} catch (MalformedURLException e) {
throw new TransportException(MessageFormat.format(
JGitText.get().invalidRedirectLocation,
uri, baseUrl, newUri), e);
final int status = HttpSupport.response(conn);
switch (status) {
case HttpConnection.HTTP_OK:
// We're done.
return;

case HttpConnection.HTTP_NOT_FOUND:
throw new NoRemoteRepositoryException(uri, MessageFormat
.format(JGitText.get().uriNotFound, conn.getURL()));

case HttpConnection.HTTP_FORBIDDEN:
throw new TransportException(uri,
MessageFormat.format(
JGitText.get().serviceNotPermitted,
baseUrl, serviceName));

case HttpConnection.HTTP_MOVED_PERM:
case HttpConnection.HTTP_MOVED_TEMP:
case HttpConnection.HTTP_11_MOVED_TEMP:
// SEE_OTHER after a POST doesn't make sense for a git
// server, so we don't handle it here and thus we'll
// report an error in openResponse() later on.
if (http.followRedirects != HttpRedirectMode.TRUE) {
// Let openResponse() issue an error
return;
}
currentUri = redirect(
conn.getHeaderField(HDR_LOCATION),
'/' + serviceName, redirects++);
try {
baseUrl = toURL(currentUri);
} catch (MalformedURLException e) {
throw new TransportException(uri, MessageFormat.format(
JGitText.get().invalidRedirectLocation,
baseUrl, currentUri), e);
}
continue;

case HttpConnection.HTTP_UNAUTHORIZED:
HttpAuthMethod nextMethod = HttpAuthMethod
.scanResponse(conn, ignoreTypes);
switch (nextMethod.getType()) {
case NONE:
throw new TransportException(uri,
MessageFormat.format(
JGitText.get().authenticationNotSupported,
conn.getURL()));
case NEGOTIATE:
// RFC 4559 states "When using the SPNEGO [...] with
// [...] POST, the authentication should be complete
// [...] before sending the user data." So in theory
// the initial GET should have been authenticated
// already. (Unless there was a redirect?)
//
// We try this only once:
ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
if (authenticator != null) {
ignoreTypes.add(authenticator.getType());
}
continue;
authAttempts = 1;
// We only do the Kerberos part of SPNEGO, which
// requires only one attempt. We do *not* to the
// NTLM part of SPNEGO; it's a multi-round
// negotiation and among other problems it would
// be unclear when to stop if no HTTP_OK is
// forthcoming. In theory a malicious server
// could keep sending requests for another NTLM
// round, keeping a client stuck here.
break;
default:
// DIGEST or BASIC. Let's be sure we ignore NEGOTIATE;
// if it was available, we have tried it before.
ignoreTypes.add(HttpAuthMethod.Type.NEGOTIATE);
if (authenticator == null || authenticator
.getType() != nextMethod.getType()) {
if (authenticator != null) {
ignoreTypes.add(authenticator.getType());
}
authAttempts = 1;
}
break;
}
authMethod = nextMethod;
authenticator = nextMethod;
CredentialsProvider credentialsProvider = getCredentialsProvider();
if (credentialsProvider == null) {
throw new TransportException(uri,
JGitText.get().noCredentialsProvider);
}
if (authAttempts > 1) {
credentialsProvider.reset(currentUri);
}
if (3 < authAttempts || !authMethod.authorize(currentUri,
credentialsProvider)) {
throw new TransportException(uri,
JGitText.get().notAuthorized);
}
authAttempts++;
continue;

default:
// Just return here; openResponse() will report an appropriate
// error.
return;
}
break;
}
}


Laden…
Abbrechen
Speichern