Adds support for SPNEGO

Adds support for Negotiate(SPNEGO) HTTP authentication method. This method
is set to have a higher priority as Digest HTTP authentication method.

Bug: 428836
Change-Id: Ib181096d39f538df1dd7d3f36516843777bf12ae
Signed-off-by: Laurent Goujon <lgoujon@twitter.com>
Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
index 5a64b45..5233013 100644
--- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/HttpAuthTest.java
@@ -64,22 +64,31 @@ public class HttpAuthTest {
 
 	private static String bearerHeader = "WWW-Authenticate: Bearer";
 
+	private static String negotiateHeader = "WWW-Authenticate: Negotiate";
+
 	private static String URL_SAMPLE = "http://everyones.loves.git/u/2";
 
 	private static String BASIC = "Basic";
 
 	private static String DIGEST = "Digest";
 
+	private static String NEGOTIATE = "Negotiate";
+
 	@Test
 	public void testHttpAuthScanResponse() {
 		checkResponse(new String[] { basicHeader }, BASIC);
 		checkResponse(new String[] { digestHeader }, DIGEST);
+		checkResponse(new String[] { negotiateHeader }, NEGOTIATE);
 		checkResponse(new String[] { basicHeader, digestHeader }, DIGEST);
 		checkResponse(new String[] { digestHeader, basicHeader }, DIGEST);
+		checkResponse(new String[] { digestHeader, negotiateHeader }, NEGOTIATE);
+		checkResponse(new String[] { negotiateHeader, digestHeader }, NEGOTIATE);
 		checkResponse(new String[] { ntlmHeader, basicHeader, digestHeader,
 				bearerHeader }, DIGEST);
 		checkResponse(new String[] { ntlmHeader, basicHeader, bearerHeader },
 				BASIC);
+		checkResponse(new String[] { ntlmHeader, basicHeader, digestHeader,
+				negotiateHeader, bearerHeader }, NEGOTIATE);
 	}
 
 	private static void checkResponse(String[] headers,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
index 4d80acc..6c30824 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java
@@ -61,6 +61,12 @@
 
 import org.eclipse.jgit.transport.http.HttpConnection;
 import org.eclipse.jgit.util.Base64;
+import org.eclipse.jgit.util.GSSManagerFactory;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
 
 /**
  * Support class to populate user authentication data on a connection.
@@ -91,6 +97,12 @@ public HttpAuthMethod method(String hdr) {
 			public HttpAuthMethod method(String hdr) {
 				return new Digest(hdr);
 			}
+		},
+		NEGOTIATE {
+			@Override
+			public HttpAuthMethod method(String hdr) {
+				return new Negotiate(hdr);
+			}
 		};
 		/**
 		 * Creates a HttpAuthMethod instance configured with the provided HTTP
@@ -458,4 +470,55 @@ private static Map<String, String> parse(String auth) {
 			return p;
 		}
 	}
+
+	private static class Negotiate extends HttpAuthMethod {
+		private static final GSSManagerFactory GSS_MANAGER_FACTORY = GSSManagerFactory
+				.detect();
+
+		private static final Oid OID;
+		static {
+			try {
+				// OID for SPNEGO
+				OID = new Oid("1.3.6.1.5.5.2"); //$NON-NLS-1$
+			} catch (GSSException e) {
+				throw new Error("Cannot create NEGOTIATE oid.", e); //$NON-NLS-1$
+			}
+		}
+
+		private final byte[] prevToken;
+
+		public Negotiate(String hdr) {
+			super(Type.NEGOTIATE);
+			prevToken = Base64.decode(hdr);
+		}
+
+		@Override
+		void authorize(String user, String pass) {
+			// not used
+		}
+
+		@Override
+		void configureRequest(HttpConnection conn) throws IOException {
+			GSSManager gssManager = GSS_MANAGER_FACTORY.newInstance(conn
+					.getURL());
+			String host = conn.getURL().getHost();
+			String peerName = "HTTP@" + host.toLowerCase(); //$NON-NLS-1$
+			try {
+				GSSName gssName = gssManager.createName(peerName,
+						GSSName.NT_HOSTBASED_SERVICE);
+				GSSContext context = gssManager.createContext(gssName, OID,
+						null, GSSContext.DEFAULT_LIFETIME);
+				// Respect delegation policy in HTTP/SPNEGO.
+				context.requestCredDeleg(true);
+
+				byte[] token = context.initSecContext(prevToken, 0,
+						prevToken.length);
+
+				conn.setRequestProperty(HDR_AUTHORIZATION, getType().name()
+						+ " " + Base64.encodeBytes(token)); //$NON-NLS-1$
+			} catch (GSSException e) {
+				throw new IOException(e);
+			}
+		}
+	}
 }