Support added for ADFS 2.0 and Live Authentication.
This required additional changes and some refactoring in FormsAuthenticationHandler

Code Review : https://codereview.appspot.com/83730043/
diff --git a/src/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManager.java b/src/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManager.java
new file mode 100644
index 0000000..1625484
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManager.java
@@ -0,0 +1,256 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClient;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClientImpl;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.PostResponseInfo;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerConfigurationException;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * SamlHandshakeManager implementation to support ADFS 2.0 
+ * to request ADFS authentication token and extract authentication cookie.
+ */
+public class AdfsHandshakeManager implements SamlHandshakeManager {
+  private static final Logger log
+      = Logger.getLogger(AdfsHandshakeManager.class.getName());
+  
+  private static final String DEFAULT_LOGIN = "/_layouts/Authenticate.aspx";
+  private static final String DEFAULT_TRUST = "/_trust";
+  
+  protected final String login;
+  protected final String username;
+  protected final String password;
+  protected final String sharePointUrl;
+  protected final String stsendpoint;
+  protected final String stsrealm;
+  protected final HttpPostClient httpClient;
+  protected final String trustLocation;
+  private static final String reqXML
+      = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+      + "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" "
+      + "xmlns:a=\"http://www.w3.org/2005/08/addressing\" "
+      + "xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/"
+      + "oasis-200401-wss-wssecurity-utility-1.0.xsd\"><s:Header>"
+      + "<a:Action s:mustUnderstand=\"1\">"
+      + "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>"
+      + "<a:ReplyTo><a:Address>"
+      + "http://www.w3.org/2005/08/addressing/anonymous</a:Address>"
+      + "</a:ReplyTo><a:To s:mustUnderstand=\"1\">"
+      + "%s</a:To>" // stsendpont
+      + "<o:Security s:mustUnderstand=\"1\" "
+      + "xmlns:o=\"http://docs.oasis-open.org/wss/2004/01/"
+      + "oasis-200401-wss-wssecurity-secext-1.0.xsd\">"
+      + "<o:UsernameToken><o:Username>%s</o:Username>" //username
+      + "<o:Password>%s</o:Password></o:UsernameToken>" //password
+      + "</o:Security></s:Header><s:Body>"
+      + "<t:RequestSecurityToken "
+      + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+      + "<wsp:AppliesTo xmlns:wsp=\""
+      + "http://schemas.xmlsoap.org/ws/2004/09/policy\">"
+      + "<a:EndpointReference><a:Address>%s</a:Address>" //stsrealm
+      + "</a:EndpointReference></wsp:AppliesTo>"
+      + "<t:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey"
+      + "</t:KeyType>"
+      + "<t:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue"
+      + "</t:RequestType>"
+      + "<t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion</t:TokenType>"
+      + "</t:RequestSecurityToken></s:Body></s:Envelope>";
+
+  @VisibleForTesting
+  AdfsHandshakeManager(String sharePointUrl, String username,
+      String password, String stsendpoint, String stsrealm, String login,
+      String trustLocation, HttpPostClient httpClient) {
+    this.sharePointUrl = sharePointUrl;
+    this.username = username;
+    this.password = password;
+    this.stsendpoint = stsendpoint;
+    this.stsrealm = stsrealm;
+    this.login = login;
+    this.trustLocation = trustLocation;
+    this.httpClient = httpClient;
+  }
+
+  public static class Builder {
+    private final String username;
+    private final String password;
+    private final String sharePointUrl;
+    private final String stsendpoint;
+    private final String stsrealm;
+    private final HttpPostClient httpClient;
+    private String login;
+    private String trustLocation;    
+
+    public Builder(String sharePointUrl, String username,
+      String password, String stsendpoint, String stsrealm) {
+      this(sharePointUrl, username, password, stsendpoint, stsrealm,
+          new HttpPostClientImpl());
+    }
+
+    @VisibleForTesting
+    Builder(String sharePointUrl, String username,
+      String password, String stsendpoint, String stsrealm,
+      HttpPostClient httpClient) {
+      if (sharePointUrl == null || username == null || password == null
+          || stsendpoint == null || httpClient == null || stsrealm == null) {
+        throw new NullPointerException();
+      }
+      this.sharePointUrl = sharePointUrl;
+      this.username = username;
+      this.password = password;
+      this.stsendpoint = stsendpoint;
+      this.stsrealm = stsrealm;
+      this.httpClient = httpClient;
+      this.login = sharePointUrl + DEFAULT_LOGIN;
+      this.trustLocation = sharePointUrl + DEFAULT_TRUST;
+    }
+
+    public Builder setLoginUrl(String login) {      
+      this.login = login;
+      return this;
+    }
+
+    public Builder setTrustLocation(String trustLocation) {      
+      this.trustLocation = trustLocation;
+      return this;
+    }
+
+    public AdfsHandshakeManager build() {
+      if (Strings.isNullOrEmpty(trustLocation) 
+          || Strings.isNullOrEmpty(login)) {
+        throw new NullPointerException();
+      }
+      return new AdfsHandshakeManager(sharePointUrl, username,
+          password, stsendpoint, stsrealm, login, trustLocation, httpClient);
+    }
+  }
+
+  @Override
+  public String requestToken() throws IOException {
+    String saml = generateSamlRequest();    
+    URL u = new URL(stsendpoint);
+    Map<String, String> requestHeaders = new HashMap<String, String>();
+    requestHeaders.put("SOAPAction", stsendpoint);
+    requestHeaders.put("Content-Type",
+        "application/soap+xml; charset=utf-8");
+    PostResponseInfo postResponse
+        = httpClient.issuePostRequest(u, requestHeaders, saml);
+    String result = postResponse.getPostContents();
+    return extractToken(result);
+  }
+
+  @Override
+  public String getAuthenticationCookie(String token) throws IOException {    
+    URL u = new URL(trustLocation);
+    String param = "wa=wsignin1.0"
+        + "&wctx=" + URLEncoder.encode(login,"UTF-8")
+        + "&wresult=" + URLEncoder.encode(token, "UTF-8");
+
+    Map<String, String> requestHeaders = new HashMap<String, String>();
+    requestHeaders.put("SOAPAction", stsendpoint);
+    PostResponseInfo postResponse
+        = httpClient.issuePostRequest(u, requestHeaders, param);
+    String cookie = postResponse.getPostResponseHeaderField("Set-Cookie");
+    return cookie;
+  }
+
+  private String generateSamlRequest() {
+    return String.format(reqXML, escapeCdata(stsendpoint),
+        escapeCdata(username), escapeCdata(password), escapeCdata(stsrealm));   
+  }
+
+  @VisibleForTesting
+  String extractToken(String tokenResponse) throws IOException {
+    if (tokenResponse == null) {
+      throw new IOException("tokenResponse is null");
+    }
+    try {
+      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+      dbf.setNamespaceAware(true);
+      DocumentBuilder db = dbf.newDocumentBuilder();
+      Document document 
+          = db.parse(new InputSource(new StringReader(tokenResponse)));
+      NodeList nodes
+          = document.getElementsByTagNameNS(
+              "http://schemas.xmlsoap.org/ws/2005/02/trust",
+              "RequestSecurityTokenResponse");
+      if (nodes.getLength() == 0) {
+        log.log(Level.WARNING,
+            "ADFS token not available in response {0}", tokenResponse);
+        throw new IOException("ADFS token not available in response");
+      }
+      Node responseToken = nodes.item(0);
+      String token = getOuterXml(responseToken);
+      log.log(Level.FINER, "ADFS Authentication Token {0}", token);
+      return token;
+    } catch (ParserConfigurationException ex) {
+      throw new IOException("Error parsing result", ex);      
+    } catch (SAXException ex) {
+      throw new IOException("Error parsing result", ex);
+    }
+  }
+
+  private String getOuterXml(Node node) throws IOException {
+    try {
+      Transformer transformer 
+          = TransformerFactory.newInstance().newTransformer();
+      transformer.setOutputProperty("omit-xml-declaration", "yes");
+      StringWriter writer = new StringWriter();
+      transformer.transform(new DOMSource(node), new StreamResult(writer));
+      return writer.toString();
+    } catch (TransformerConfigurationException ex) {
+      throw new IOException(ex);
+    } catch (TransformerException ex) {
+      throw new IOException(ex);
+    }
+  }
+  
+  @VisibleForTesting
+  String escapeCdata(String input) {
+    if (Strings.isNullOrEmpty(input)) {
+      return "";
+    }  
+    return "<![CDATA[" + input.replace("]]>", "]]]]><![CDATA[>") + "]]>";
+  }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactory.java b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactory.java
new file mode 100644
index 0000000..5626b14
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactory.java
@@ -0,0 +1,38 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
+
+import java.io.IOException;
+
+/**
+ * Authentication Factory to return appropriate authentication client for
+ * FormsAuthenticationHandler implementation.
+ */
+public interface AuthenticationClientFactory {  
+  public AuthenticationSoap newSharePointFormsAuthentication(
+      String virtualServer, String username, String password)
+      throws IOException;
+
+  public SamlHandshakeManager newAdfsAuthentication(String virtualServer,
+      String username, String password, String stsendpoint, String stsrealm,
+      String login, String trustlocation) throws IOException;
+
+  public SamlHandshakeManager newLiveAuthentication(String virtualServer,
+      String username, String password) throws IOException;
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactoryImpl.java b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactoryImpl.java
new file mode 100644
index 0000000..7e92880
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationClientFactoryImpl.java
@@ -0,0 +1,123 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import com.google.common.base.Strings;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
+
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.namespace.QName;
+import javax.xml.ws.EndpointReference;
+import javax.xml.ws.Service;
+import javax.xml.ws.wsaddressing.W3CEndpointReferenceBuilder;
+
+/**
+ * Authentication Factory implementation to return appropriate
+ * authentication client for FormsAuthenticationHandler implementation.
+ */
+public class AuthenticationClientFactoryImpl 
+    implements AuthenticationClientFactory {
+  /** SharePoint's namespace. */
+  private static final String XMLNS
+      = "http://schemas.microsoft.com/sharepoint/soap/";
+
+  private static final Logger log
+      = Logger.getLogger(AuthenticationClientFactoryImpl.class.getName());
+
+  private final Service authenticationService;
+    
+    public AuthenticationClientFactoryImpl() {
+      this.authenticationService = Service.create(
+          AuthenticationSoap.class.getResource("Authentication.wsdl"),
+          new QName(XMLNS, "Authentication"));
+    }
+
+    private static String handleEncoding(String endpoint) {
+      // Handle Unicode. Java does not properly encode the POST path.
+      return URI.create(endpoint).toASCIIString();
+    }
+
+    private static URI spUrlToUri(String url) throws IOException {
+      // Because SP is silly, the path of the URI is unencoded, but the rest of
+      // the URI is correct. Thus, we split up the path from the host, and then
+      // turn them into URIs separately, and then turn everything into a
+      // properly-escaped string.
+      String[] parts = url.split("/", 4);
+      if (parts.length < 3) {
+        throw new IllegalArgumentException("Too few '/'s: " + url);
+      }
+      String host = parts[0] + "/" + parts[1] + "/" + parts[2];
+      // Host must be properly-encoded already.
+      URI hostUri = URI.create(host);
+      if (parts.length == 3) {
+        // There was no path.
+        return hostUri;
+      }
+      URI pathUri;
+      try {
+        pathUri = new URI(null, null, "/" + parts[3], null);
+      } catch (URISyntaxException ex) {
+        throw new IOException(ex);
+      }
+      return hostUri.resolve(pathUri);
+    }
+
+    @Override
+    public AuthenticationSoap newSharePointFormsAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      String authenticationEndPoint = spUrlToUri(virtualServer
+          + "/_vti_bin/Authentication.asmx").toString();
+      EndpointReference endpointRef = new W3CEndpointReferenceBuilder()
+          .address(handleEncoding(authenticationEndPoint)).build();       
+          authenticationService.getPort(endpointRef, AuthenticationSoap.class);
+      return 
+          authenticationService.getPort(endpointRef, AuthenticationSoap.class);
+    }
+
+    @Override
+    public SamlHandshakeManager newAdfsAuthentication(String virtualServer,
+        String username, String password, String stsendpoint, String stsrealm,
+      String login, String trustlocation) throws IOException {      
+      AdfsHandshakeManager.Builder manager 
+          = new AdfsHandshakeManager.Builder(virtualServer, username,
+              password, stsendpoint, stsrealm);
+      if (!Strings.isNullOrEmpty(login)) {
+        log.log(Level.CONFIG,
+            "Using non default login value for ADFS [{0}]", login);
+        manager.setLoginUrl(login);
+      }
+      if (!Strings.isNullOrEmpty(trustlocation)) {
+        log.log(Level.CONFIG, "Using non default trust location for ADFS [{0}]",
+            trustlocation);
+        manager.setTrustLocation(trustlocation);
+      }
+      return manager.build();
+    }
+
+    @Override
+    public SamlHandshakeManager newLiveAuthentication(String virtualServer,
+        String username, String password) throws IOException {   
+      return new LiveAuthenticationHandshakeManager.Builder(
+              virtualServer, username, password).build();
+    }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java b/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
index a223818..cccd7cc 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
@@ -29,7 +29,7 @@
 /**
  * Helper class to handle forms authentication.
  */
-class FormsAuthenticationHandler {
+abstract class FormsAuthenticationHandler {
   /** SharePoint's namespace. */
   private static final String XMLNS
       = "http://schemas.microsoft.com/sharepoint/soap/";
@@ -39,40 +39,36 @@
   // Default time out for forms authentication with .NET is 30 mins
   private static final long DEFAULT_COOKIE_TIMEOUT_SECONDS = 30 * 60;
 
-  private final String userName;
-  private final String password;
+  protected final String username;
+  protected final String password;
   private final ScheduledExecutorService executor;
   private final Runnable refreshRunnable = new RefreshRunnable();  
   private final List<String> authenticationCookiesList 
-      = new CopyOnWriteArrayList<String>();
-  private final AuthenticationHandler authenticationClient;
+      = new CopyOnWriteArrayList<String>();  
   private boolean isFormsAuthentication = false;
 
   @VisibleForTesting    
-  FormsAuthenticationHandler(String userName, String password,
-      ScheduledExecutorService executor,
-      AuthenticationHandler authenticationClient) {
-    if (userName == null || password == null || executor == null || 
-        authenticationClient == null) {
+  FormsAuthenticationHandler(String username, String password,
+      ScheduledExecutorService executor) {
+    if (username == null || password == null || executor == null) {
       throw new NullPointerException();
     }
-    this.userName = userName;
+    this.username = username;
     this.password = password;
-    this.executor = executor;
-    this.authenticationClient = authenticationClient;
+    this.executor = executor;   
   }
 
   public List<String> getAuthenticationCookies() {
     return Collections.unmodifiableList(authenticationCookiesList);
   }
+  // TODO : Remove isFormAuthentication.
+  abstract boolean isFormsAuthentication() throws IOException;
   
-  public boolean isFormsAuthentication() {
-    return isFormsAuthentication;
-  }
+  abstract AuthenticationResult authenticate() throws IOException;
   
   private void refreshCookies() throws IOException {
     
-    if ("".equals(userName) || "".equals(password)) {
+    if ("".equals(username) || "".equals(password)) {
       log.log(Level.FINE, 
           "Empty username / password. Using authentication mode as Windows");
        isFormsAuthentication = false;
@@ -84,7 +80,7 @@
     }
 
     log.log(Level.FINE, "About to refresh authentication cookie.");
-    AuthenticationResult result = authenticationClient.authenticate();
+    AuthenticationResult result = authenticate();
     log.log(Level.FINE, "Authentication Result {0}", result.getErrorCode());
 
     String cookie = result.getCookie();
@@ -107,13 +103,13 @@
  }
  
   public void start() throws IOException {
-    if ("".equals(userName) || "".equals(password)) {
+    if ("".equals(username) || "".equals(password)) {
       log.log(Level.FINE, "Empty username or password. Using windows"
           + " integrated authentication.");
        isFormsAuthentication = false;
        return;
     }
-    isFormsAuthentication = authenticationClient.isFormsAuthentication();
+    isFormsAuthentication = isFormsAuthentication();
     refreshCookies();
   }
 
@@ -129,9 +125,4 @@
       }
     }
   }
-  
-  interface AuthenticationHandler {
-    AuthenticationResult authenticate() throws IOException;
-    boolean isFormsAuthentication() throws IOException;
-  }  
 }
\ No newline at end of file
diff --git a/src/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManager.java b/src/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManager.java
new file mode 100644
index 0000000..a44878f
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManager.java
@@ -0,0 +1,153 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClient;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClientImpl;
+
+import java.io.IOException;
+import java.io.StringReader;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.w3c.dom.DOMException;
+import org.w3c.dom.Document;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * SamlHandshakeManager implementation for Live Authentication 
+ * to request Live authentication token and extract authentication cookie.
+ */
+public class LiveAuthenticationHandshakeManager
+      extends AdfsHandshakeManager {
+  private static final Logger log
+      = Logger.getLogger(LiveAuthenticationHandshakeManager.class.getName());
+  private static final String LIVE_STS
+      = "https://login.microsoftonline.com/extSTS.srf";
+  private static final String LIVE_LOGIN_URL
+      = "/_forms/default.aspx?wa=wsignin1.0";
+
+  public static class Builder {
+    private final String username;
+    private final String password;
+    private final String sharePointUrl;
+    private String stsendpoint;
+    private final String stsrealm;
+    private final HttpPostClient httpClient;
+    private String login;
+    private String trustLocation;
+
+    public Builder(String sharePointUrl, String username, String password) {
+      this(sharePointUrl, username, password, new HttpPostClientImpl());
+    }
+
+    @VisibleForTesting
+    Builder(String sharePointUrl, String username, String password,
+        HttpPostClient httpClient) {
+      if (sharePointUrl == null || username == null || password == null
+          || httpClient == null) {
+        throw new NullPointerException();
+      }
+      this.sharePointUrl = sharePointUrl;
+      this.username = username;
+      this.password = password;
+      this.httpClient = httpClient;
+      this.login = sharePointUrl + LIVE_LOGIN_URL;
+      this.trustLocation = "";
+      this.stsendpoint = LIVE_STS;
+      this.stsrealm = sharePointUrl;
+    }
+
+    public Builder setLoginUrl(String login) {
+      this.login = login;
+      return this;
+    }
+
+    public Builder setStsendpoint(String stsendpoint) {
+      this.stsendpoint = stsendpoint;
+      return this;
+    }
+
+    public LiveAuthenticationHandshakeManager build() {
+      if (Strings.isNullOrEmpty(stsendpoint) || Strings.isNullOrEmpty(login)) {
+        throw new NullPointerException();
+      }
+      return new LiveAuthenticationHandshakeManager(sharePointUrl, username,
+          password, stsendpoint, stsrealm, login, trustLocation, httpClient);
+    }
+  }
+
+  private LiveAuthenticationHandshakeManager(String sharePointUrl,
+      String username, String password, String stsendpoint, String stsrealm,
+      String login, String trustLocation, HttpPostClient httpClient) {
+    super(sharePointUrl, username, password, stsendpoint,
+        stsrealm, login, trustLocation, httpClient);
+  }
+
+  @Override
+  @VisibleForTesting
+  String extractToken(String tokenResponse) throws IOException {
+    if (tokenResponse == null) {
+      throw new IOException("tokenResponse is null");
+    }
+    try {
+      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+      dbf.setNamespaceAware(true);
+      DocumentBuilder db = dbf.newDocumentBuilder();
+      Document document 
+          = db.parse(new InputSource(new StringReader(tokenResponse)));
+      NodeList nodes
+          = document.getElementsByTagNameNS(
+              "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-"
+                  + "wssecurity-secext-1.0.xsd", "BinarySecurityToken");
+      if (nodes == null || nodes.getLength() == 0) {
+        log.log(Level.WARNING, "Live Authentication token not available"
+            + " in response {0}", tokenResponse);
+        throw new IOException(
+            "Live Authentication token not available in response");
+      }
+      String token = nodes.item(0).getTextContent();    
+      log.log(Level.FINER, "Live Authentication Token {0}", token);
+      return token;
+    } catch (ParserConfigurationException ex) {
+      throw new IOException(ex);
+    } catch (SAXException ex) {
+      throw new IOException(ex);
+    } catch (DOMException ex) {
+      throw new IOException(ex);
+    }
+  }
+
+  @Override
+  public String getAuthenticationCookie(String token) throws IOException {  
+    URL u = new URL(login);
+    Map<String, String> requestProperties = new HashMap<String, String>();
+    requestProperties.put("SOAPAction", stsendpoint);
+    SamlAuthenticationHandler.PostResponseInfo postResponse
+        = httpClient.issuePostRequest(u, requestProperties, token);
+    return postResponse.getPostResponseHeaderField("Set-Cookie");
+  }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandler.java b/src/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandler.java
new file mode 100644
index 0000000..d7c64ae
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandler.java
@@ -0,0 +1,201 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.enterprise.adaptor.IOHelper;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * FormsAuthenticationHandler for SAML based authentication.
+ */
+public class SamlAuthenticationHandler extends FormsAuthenticationHandler {
+
+  private static final Logger log
+      = Logger.getLogger(SamlAuthenticationHandler.class.getName());
+  private static final int DEFAULT_COOKIE_TIMEOUT_SECONDS = 600;
+  private static final Charset CHARSET = Charset.forName("UTF-8");
+
+  private final SamlHandshakeManager samlClient;
+
+  private SamlAuthenticationHandler(String username, String password,
+      ScheduledExecutorService executor, SamlHandshakeManager samlClient) {
+    super(username, password, executor);
+    this.samlClient = samlClient;
+  }
+  
+  public static class Builder {
+    private final String username;
+    private final String password;
+    private final ScheduledExecutorService executor;
+    private final SamlHandshakeManager samlClient;
+    public Builder(String username, String password,
+        ScheduledExecutorService executor, SamlHandshakeManager samlClient) {
+      if (username == null || password == null || executor == null
+          || samlClient == null) {
+        throw new NullPointerException();        
+      }
+      this.username = username;
+      this.password = password;
+      this.executor = executor;
+      this.samlClient = samlClient;      
+    }
+    
+    public SamlAuthenticationHandler build() {
+      SamlAuthenticationHandler authenticationHandler
+          = new SamlAuthenticationHandler(username, password, executor,
+              samlClient);      
+      return authenticationHandler;
+    }
+    
+  }
+
+  @Override
+  public AuthenticationResult authenticate() throws IOException {
+    String token = samlClient.requestToken();
+    if (Strings.isNullOrEmpty(token)) {
+      throw new IOException("Invalid SAML token");
+    }    
+    String cookie = samlClient.getAuthenticationCookie(token);
+    log.log(Level.FINER, "Authentication Cookie {0}", cookie);
+    return new AuthenticationResult(cookie,
+        DEFAULT_COOKIE_TIMEOUT_SECONDS, "NO_ERROR");
+  }
+
+  @Override
+  public boolean isFormsAuthentication() throws IOException {
+    return true;
+  }
+
+  @VisibleForTesting
+  interface SamlHandshakeManager {
+    public String requestToken() throws IOException;
+    public String getAuthenticationCookie(String token) throws IOException;
+  }
+
+  @VisibleForTesting
+  interface HttpPostClient {
+    public PostResponseInfo issuePostRequest(URL url,
+        Map<String, String> connectionProperties, String requestBody)
+        throws IOException;
+  }
+
+  @VisibleForTesting
+  static class HttpPostClientImpl implements HttpPostClient{
+    @Override
+    public PostResponseInfo issuePostRequest(URL url,
+        Map<String, String> connectionProperties, String requestBody)
+        throws IOException {
+
+      // Handle Unicode. Java does not properly encode the GET.
+      try {
+        url = new URL(url.toURI().toASCIIString());
+      } catch (URISyntaxException ex) {
+        throw new IOException(ex);
+      }
+
+      HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+      try {
+        connection.setDoOutput(true);
+        connection.setDoInput(true);
+        connection.setRequestMethod("POST");
+        connection.setInstanceFollowRedirects(false);
+
+        for(String key : connectionProperties.keySet()) {
+          connection.addRequestProperty(key, connectionProperties.get(key));
+        }
+
+        if (!connectionProperties.containsKey("Content-Length")) {
+          connection.addRequestProperty("Content-Length",
+              Integer.toString(requestBody.length()));
+        }
+
+        OutputStream out = connection.getOutputStream();
+        Writer wout = new OutputStreamWriter(out);
+        wout.write(requestBody);
+        wout.flush();
+        wout.close();
+        InputStream in = connection.getInputStream();
+        String result = IOHelper.readInputStreamToString(in, CHARSET);
+        return new PostResponseInfo(result, connection.getHeaderFields());
+      } finally {
+        InputStream inputStream = connection.getResponseCode() >= 400
+            ? connection.getErrorStream() : connection.getInputStream();
+        if (inputStream != null) {
+          inputStream.close();
+        }
+      }
+    }
+  }
+
+  @VisibleForTesting
+  static class PostResponseInfo {
+    /** Non-null contents. */
+    private final String contents;
+    /** Non-null headers. */
+    private final Map<String, List<String>> headers;
+
+    PostResponseInfo(
+        String contents, Map<String, List<String>> headers) {
+      this.contents = contents;
+      this.headers  = (headers == null)
+          ? new HashMap<String, List<String>>() 
+          : new HashMap<String, List<String>>(headers);
+    }
+
+    public String getPostContents() {
+      return contents;
+    }
+
+    public Map<String, List<String>> getPostResponseHeaders() {
+      return Collections.unmodifiableMap(headers);
+    }
+
+    public String getPostResponseHeaderField(String header) {
+      if (headers == null || !headers.containsKey(header)) {
+        return null;
+      }
+      if (headers.get(header) == null || headers.get(header).isEmpty()) {
+        return null;
+      }
+      StringBuilder sbValues = new StringBuilder();
+      for(String value : headers.get(header)) {
+        if ("".equals(value)) {
+          continue;
+        }
+        sbValues.append(value);
+        sbValues.append(";");
+      }
+      return sbValues.toString();
+    }
+  }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
index ff5b128..eb422f0 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
@@ -36,6 +36,7 @@
 import com.google.enterprise.adaptor.sharepoint.RareModificationCache.CachedList;
 import com.google.enterprise.adaptor.sharepoint.RareModificationCache.CachedVirtualServer;
 import com.google.enterprise.adaptor.sharepoint.RareModificationCache.CachedWeb;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
 import com.google.enterprise.adaptor.sharepoint.SiteDataClient.CursorPaginator;
 import com.google.enterprise.adaptor.sharepoint.SiteDataClient.Paginator;
 import com.google.enterprise.adaptor.sharepoint.SiteDataClient.XmlProcessingException;
@@ -348,6 +349,8 @@
   /** Client for initiating raw HTTP connections. */
   private final HttpClient httpClient;
   private final Callable<ExecutorService> executorFactory;
+
+  private final AuthenticationClientFactory authenticationClientFactory;
   private ExecutorService executor;
   private boolean xmlValidation;
   private int feedMaxUrls;
@@ -404,18 +407,21 @@
 
   public SharePointAdaptor() {
     this(new SoapFactoryImpl(), new HttpClientImpl(),
-        new CachedThreadPoolFactory());
+        new CachedThreadPoolFactory(), new AuthenticationClientFactoryImpl());
   }
 
   @VisibleForTesting
   SharePointAdaptor(SoapFactory soapFactory, HttpClient httpClient,
-      Callable<ExecutorService> executorFactory) {
-    if (soapFactory == null || httpClient == null || executorFactory == null) {
+      Callable<ExecutorService> executorFactory,
+      AuthenticationClientFactory authenticationClientFactory) {
+    if (soapFactory == null || httpClient == null || executorFactory == null 
+        || authenticationClientFactory == null) {
       throw new NullPointerException();
     }
     this.soapFactory = soapFactory;
     this.httpClient = httpClient;
     this.executorFactory = executorFactory;
+    this.authenticationClientFactory = authenticationClientFactory;
   }
 
   /**
@@ -442,6 +448,22 @@
     // because the GSA won't see links outside of that content.
     config.addKey("sharepoint.maxIndexableSize", "2097152");
     config.addKey("adaptor.namespace", "Default");
+    // When running against ADFS authentication, set this to ADFS endpoint.
+    config.addKey("sharepoint.sts.endpoint", "");
+    // When running against ADFS authentication, set this to realm value.
+    // Normally realm value is either http://sharepointurl/_trust or
+    // urn:sharepointenv:com format. You can use 
+    // Get-SPTrustedIdentityTokenIssuer to get "DefaultProviderRealm" value
+    config.addKey("sharepoint.sts.realm", "");
+    // You can override default value of http://sharepointurl/_trust by 
+    // specifying this property.
+    config.addKey("sharepoint.sts.trustLocation", "");
+    // You can override default value of 
+    // http://sharepointurl/_layouts/Authenticate.aspx by specifying this 
+    // property.
+    config.addKey("sharepoint.sts.login", "");
+    // Set this to true when using Live authentication.
+    config.addKey("sharepoint.useLiveAuthentication", "false");
   }
 
   @Override
@@ -462,11 +484,19 @@
     maxIndexableSize = Integer.parseInt(
         config.getValue("sharepoint.maxIndexableSize"));
     defaultNamespace = config.getValue("adaptor.namespace");
+    String stsendpoint = config.getValue("sharepoint.sts.endpoint");
+    String stsrealm = config.getValue("sharepoint.sts.realm");
+    boolean useLiveAuthentication = Boolean.parseBoolean(
+        config.getValue("sharepoint.useLiveAuthentication"));
 
     log.log(Level.CONFIG, "VirtualServer: {0}", virtualServer);
     log.log(Level.CONFIG, "Username: {0}", username);
     log.log(Level.CONFIG, "Password: {0}", password);
     log.log(Level.CONFIG, "Default Namespace: {0}", defaultNamespace);
+    log.log(Level.CONFIG, "STS Endpoint: {0}", stsendpoint);
+    log.log(Level.CONFIG, "STS Realm: {0}", stsrealm);
+    log.log(Level.CONFIG, "Use Live Authentication: {0}",
+        useLiveAuthentication);
 
     ntlmAuthenticator = new NtlmAuthenticator(username, password);
     // Unfortunately, this is a JVM-wide modification.
@@ -474,14 +504,28 @@
     URL virtualServerUrl = new URL(virtualServer);
     ntlmAuthenticator.addPermitForHost(virtualServerUrl);
     scheduledExecutor = new ScheduledThreadPoolExecutor(1);
-    String authenticationEndPoint = spUrlToUri(
-        virtualServer + "/_vti_bin/Authentication.asmx").toString();
-    SharePointFormsAuthenticationHandler authenticationClient 
-        = new SharePointFormsAuthenticationHandler(
-            soapFactory.newAuthentication(authenticationEndPoint),
-            username, password);
-    authenticationHandler = new FormsAuthenticationHandler(username,
-        password, scheduledExecutor, authenticationClient);
+   
+    
+    if (useLiveAuthentication)  {
+      SamlHandshakeManager manager = authenticationClientFactory
+          .newLiveAuthentication(virtualServer, username, password);
+      authenticationHandler = new SamlAuthenticationHandler.Builder(username,
+          password, scheduledExecutor, manager).build();     
+    } else if (!"".equals(stsendpoint) && !"".equals(stsrealm)) {
+      SamlHandshakeManager manager = authenticationClientFactory
+          .newAdfsAuthentication(virtualServer, username, password, stsendpoint,
+              stsrealm, config.getValue("sharepoint.sts.login"),
+              config.getValue("sharepoint.sts.trustLocation"));
+      authenticationHandler = new SamlAuthenticationHandler.Builder(username,
+          password, scheduledExecutor, manager).build();            
+    } else {    
+      AuthenticationSoap authenticationSoap = authenticationClientFactory
+          .newSharePointFormsAuthentication(virtualServer, username, password);
+      authenticationHandler = new SharePointFormsAuthenticationHandler
+          .Builder(username, password, scheduledExecutor, authenticationSoap)
+          .build();
+    }
+   
     try {
       authenticationHandler.start();
       executor = executorFactory.call();
@@ -2674,8 +2718,6 @@
 
     public UserGroupSoap newUserGroup(String endpoint);
     
-    public AuthenticationSoap newAuthentication(String endpoint);
-    
     public PeopleSoap newPeople(String endpoint);
   }
 
@@ -2683,7 +2725,6 @@
   static class SoapFactoryImpl implements SoapFactory {
     private final Service siteDataService;
     private final Service userGroupService;
-    private final Service authenticationService;
     private final Service peopleService;
 
     public SoapFactoryImpl() {
@@ -2691,9 +2732,6 @@
       this.userGroupService = Service.create(
           UserGroupSoap.class.getResource("UserGroup.wsdl"),
           new QName(XMLNS_DIRECTORY, "UserGroup"));
-      this.authenticationService = Service.create(
-          AuthenticationSoap.class.getResource("Authentication.wsdl"),
-          new QName(XMLNS, "Authentication"));
       this.peopleService = Service.create(
           PeopleSoap.class.getResource("People.wsdl"),
           new QName(XMLNS, "People"));
@@ -2717,14 +2755,6 @@
           .address(handleEncoding(endpoint)).build();
       return userGroupService.getPort(endpointRef, UserGroupSoap.class);
     }
-    
-    @Override
-    public AuthenticationSoap newAuthentication(String endpoint) {
-      EndpointReference endpointRef = new W3CEndpointReferenceBuilder()
-          .address(handleEncoding(endpoint)).build();
-      return 
-          authenticationService.getPort(endpointRef, AuthenticationSoap.class);
-    }
 
     @Override
     public PeopleSoap newPeople(String endpoint) {
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java
index c3c9406..37f314e 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java
@@ -14,7 +14,6 @@
 
 package com.google.enterprise.adaptor.sharepoint;
 
-import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandler.AuthenticationHandler;
 import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationMode;
 import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
 import com.microsoft.schemas.sharepoint.soap.authentication.LoginErrorCode;
@@ -22,6 +21,7 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import javax.xml.ws.BindingProvider;
@@ -33,26 +33,48 @@
  * using Authentication.asmx web service.
 */
 public class SharePointFormsAuthenticationHandler 
-    implements AuthenticationHandler {
+    extends FormsAuthenticationHandler {
   private static final Logger log
       = Logger.getLogger(SharePointFormsAuthenticationHandler.class.getName());
   // Default time out for forms authentication with .NET is 30 mins
   private static final int DEFAULT_COOKIE_TIMEOUT_SECONDS = 30 * 60;
   private final AuthenticationSoap authenticationClient;
   private AuthenticationMode authenticationMode;
-  private final String username;
-  private final String password;
 
-  SharePointFormsAuthenticationHandler(AuthenticationSoap authenticationClient,
-      String username, String password) {
-    if (authenticationClient == null || username == null || password == null) {
-      throw new NullPointerException();
-    }
+  private SharePointFormsAuthenticationHandler(String username, String password,
+      ScheduledExecutorService executor,
+      AuthenticationSoap authenticationClient) {
+    super(username, password, executor);
     this.authenticationClient = authenticationClient;
-    this.username = username;
-    this.password = password;
+  }
+  
+  public static class Builder {
+    private final String username;
+    private final String password;
+    private final ScheduledExecutorService executor;
+    private final AuthenticationSoap authenticationClient;
+    public Builder(String username, String password,
+        ScheduledExecutorService executor,
+        AuthenticationSoap authenticationClient) {
+      if (username == null || password == null || executor == null
+          || authenticationClient == null) {
+        throw new NullPointerException();        
+      }
+      this.username = username;
+      this.password = password;
+      this.executor = executor;
+      this.authenticationClient = authenticationClient;      
+    }
+    
+    public SharePointFormsAuthenticationHandler build() {
+      SharePointFormsAuthenticationHandler authenticationHandler
+          = new SharePointFormsAuthenticationHandler(
+              username, password, executor, authenticationClient);
+      return authenticationHandler;
+    }    
   }
 
+  @Override
   public AuthenticationResult authenticate() throws IOException {
     if (!isFormsAuthentication()) {
       return new AuthenticationResult(null, DEFAULT_COOKIE_TIMEOUT_SECONDS,
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
index c4e3b87..6e91ec1 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
@@ -26,6 +26,7 @@
 import com.google.enterprise.adaptor.PollingIncrementalLister;
 import com.google.enterprise.adaptor.Request;
 import com.google.enterprise.adaptor.Response;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
 import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
 
 import com.microsoft.webservices.sharepointportalserver.userprofilechangeservice.ArrayOfUserProfileChangeData;
@@ -141,6 +142,7 @@
   private String mySiteHost;
   private NtlmAuthenticator ntlmAuthenticator;
   private final UserProfileServiceFactory userProfileServiceFactory;
+  private final AuthenticationClientFactory authenticationClientFactory;
 
   private String userProfileChangeToken;
   private boolean setAcl = true;
@@ -149,21 +151,27 @@
   private ScheduledThreadPoolExecutor scheduledExecutor 
       = new ScheduledThreadPoolExecutor(1);
 
+  private FormsAuthenticationHandler authenticationHandler;
+
   public static void main(String[] args) {
     AbstractAdaptor.main(new SharePointUserProfileAdaptor(), args);
   }
 
   public SharePointUserProfileAdaptor() {
-    this(new UserProfileServiceFactoryImpl());
+    this(new UserProfileServiceFactoryImpl(),
+        new AuthenticationClientFactoryImpl());
   }
 
   @VisibleForTesting
   SharePointUserProfileAdaptor(
-      UserProfileServiceFactory userProfileServiceFactory) {
-    if (userProfileServiceFactory == null) {
+      UserProfileServiceFactory userProfileServiceFactory,
+      AuthenticationClientFactory authenticationClientFactory) {
+    if (userProfileServiceFactory == null 
+        || authenticationClientFactory == null) {
       throw new NullPointerException();
     }
     this.userProfileServiceFactory = userProfileServiceFactory;
+    this.authenticationClientFactory = authenticationClientFactory;
   }
 
   @VisibleForTesting
@@ -187,6 +195,22 @@
     config.addKey("profile.setacl", "true");
     config.addKey("adaptor.namespace", "Default");
     config.addKey("profile.mysitehost", "");
+    // When running against ADFS authentication, set this to ADFS endpoint.
+    config.addKey("sharepoint.sts.endpoint", "");
+    // When running against ADFS authentication, set this to realm value.
+    // Normally realm value is either http://sharepointurl/_trust or
+    // urn:sharepointenv:com format. You can use 
+    // Get-SPTrustedIdentityTokenIssuer to get "DefaultProviderRealm" value
+    config.addKey("sharepoint.sts.realm", "");
+    // You can override default value of http://sharepointurl/_trust by 
+    // specifying this property.
+    config.addKey("sharepoint.sts.trustLocation", "");
+    // You can override default value of 
+    // http://sharepointurl/_layouts/Authenticate.aspx by specifying this 
+    // property.
+    config.addKey("sharepoint.sts.login", "");
+    // Set this to true when using Live authentication.
+    config.addKey("sharepoint.useLiveAuthentication", "false");
   }
 
   @Override
@@ -203,11 +227,19 @@
         config.getValue("sharepoint.password"));
     setAcl = Boolean.parseBoolean(config.getValue("profile.setacl"));
     namespace = config.getValue("adaptor.namespace");
+    String stsendpoint = config.getValue("sharepoint.sts.endpoint");
+    String stsrealm = config.getValue("sharepoint.sts.realm");
+    boolean useLiveAuthentication = Boolean.parseBoolean(
+        config.getValue("sharepoint.useLiveAuthentication"));
 
     log.log(Level.CONFIG, "virtualServer: {0}", virtualServer);
     log.log(Level.CONFIG, "Username: {0}", username);
     log.log(Level.CONFIG, "setAcl: {0}", setAcl);
     log.log(Level.CONFIG, "Namespace: {0}", namespace);
+    log.log(Level.CONFIG, "STS Endpoint: {0}", stsendpoint);
+    log.log(Level.CONFIG, "STS Realm: {0}", stsrealm);
+    log.log(Level.CONFIG, "Use Live Authentication: {0}",
+        useLiveAuthentication);
     
     mySiteHost = config.getValue("profile.mysitehost");
     log.log(Level.CONFIG, "mySiteHost: {0}", mySiteHost);
@@ -224,15 +256,26 @@
     ntlmAuthenticator = new NtlmAuthenticator(username, password);
     // Unfortunately, this is a JVM-wide modification.
     Authenticator.setDefault(ntlmAuthenticator);
-    String authenticationEndPoint 
-        =  virtualServer + "/_vti_bin/Authentication.asmx";
-    SharePointFormsAuthenticationHandler authenticationClient 
-        = new SharePointFormsAuthenticationHandler(
-            userProfileServiceFactory.newAuthentication(authenticationEndPoint),
-            username, password); 
-    FormsAuthenticationHandler authenticationHandler 
-        = new FormsAuthenticationHandler(username, password, scheduledExecutor,
-        authenticationClient);
+     
+    if (useLiveAuthentication)  {
+      SamlHandshakeManager manager = authenticationClientFactory
+          .newLiveAuthentication(virtualServer, username, password);
+      authenticationHandler = new SamlAuthenticationHandler.Builder(username,
+          password, scheduledExecutor, manager).build();     
+    } else if (!"".equals(stsendpoint) && !"".equals(stsrealm)) {
+      SamlHandshakeManager manager = authenticationClientFactory
+          .newAdfsAuthentication(virtualServer, username, password, stsendpoint,
+              stsrealm, config.getValue("sharepoint.sts.login"),
+              config.getValue("sharepoint.sts.trustLocation"));
+      authenticationHandler = new SamlAuthenticationHandler.Builder(username,
+          password, scheduledExecutor, manager).build();            
+    } else {    
+      AuthenticationSoap authenticationSoap = authenticationClientFactory
+          .newSharePointFormsAuthentication(virtualServer, username, password);
+      authenticationHandler = new SharePointFormsAuthenticationHandler
+          .Builder(username, password, scheduledExecutor, authenticationSoap)
+          .build();
+    }
     authenticationHandler.start();
     log.log(Level.FINEST, "Initializing User profile Service Client for {0}",
         virtualServer + USER_PROFILE_SERVICE_ENDPOINT);
@@ -298,15 +341,12 @@
   interface UserProfileServiceFactory {
     public UserProfileServiceWS newUserProfileService(String endpoint,
         String endpointChangeService, List<String> cookies);
-    public AuthenticationSoap newAuthentication(String endpoint);
-    
   }
 
   private static class UserProfileServiceFactoryImpl
       implements UserProfileServiceFactory {
     private final Service userProfileServiceSoap;
     private final Service userProfileChangeServiceSoap;
-    private final Service authenticationService;
 
     public UserProfileServiceFactoryImpl() {
       URL urlUserProfileService =
@@ -320,9 +360,6 @@
       QName qnameChange = new QName(XMLNS_CHANGE, "UserProfileChangeService");
       this.userProfileChangeServiceSoap = Service.create(
           urlUserProfileChangeService, qnameChange);
-      this.authenticationService = Service.create(
-          AuthenticationSoap.class.getResource("Authentication.wsdl"),
-          new QName(AUTH_XMLNS, "Authentication"));
     }
 
     @Override
@@ -363,14 +400,6 @@
       port.getRequestContext().put(MessageContext.HTTP_REQUEST_HEADERS, 
           Collections.singletonMap(
             "X-FORMS_BASED_AUTH_ACCEPTED", Collections.singletonList("f")));
-    } 
-
-    @Override
-    public AuthenticationSoap newAuthentication(String endpoint) {
-      EndpointReference endpointRef = new W3CEndpointReferenceBuilder()
-          .address(endpoint).build();
-      return 
-          authenticationService.getPort(endpointRef, AuthenticationSoap.class);      
     }
   }
 
diff --git a/test/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManagerTest.java b/test/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManagerTest.java
new file mode 100644
index 0000000..2c6a06e
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/sharepoint/AdfsHandshakeManagerTest.java
@@ -0,0 +1,294 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandlerTest.MockScheduledExecutor;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClient;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.PostResponseInfo;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLEncoder;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+
+public class AdfsHandshakeManagerTest {
+  
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+  
+  private static class UnsupportedHttpPostClient implements HttpPostClient {
+    @Override
+    public SamlAuthenticationHandler.PostResponseInfo issuePostRequest(
+        URL url, Map<String, String> connectionProperties, String requestBody)
+        throws IOException {
+      throw new UnsupportedOperationException();
+    }    
+  }
+  
+  private static class MockHttpPostClient implements HttpPostClient {
+
+    private Map<URL, PostResponseInfo> responseMap;
+    private Map<URL, String> receivedRequestBodyMap;
+    public MockHttpPostClient() {
+      responseMap = new HashMap<URL, PostResponseInfo>();
+      receivedRequestBodyMap = new HashMap<URL, String>();      
+    }
+    
+    @Override
+    public SamlAuthenticationHandler.PostResponseInfo issuePostRequest(
+        URL url, Map<String, String> connectionProperties, String requestBody)
+        throws IOException {
+      if (!responseMap.containsKey(url)) {
+        throw new UnsupportedOperationException(
+            "Unexpected Http Post for URL " + url);
+      }
+      // log incoming request body
+      receivedRequestBodyMap.put(url, requestBody);
+      return responseMap.get(url);
+    }
+  }
+
+  @Test
+  public void testConstructor() {
+    new AdfsHandshakeManager.Builder(
+        "http://endpoint", "username", "password", "https://sts", "realm")
+        .build();    
+  }
+
+  @Test
+  public void testNullUsername() {
+    thrown.expect(NullPointerException.class);
+    new AdfsHandshakeManager.Builder(
+        "http://endpoint", null, "password", "https://sts", "realm").build();
+  }
+
+  @Test
+  public void testNullPassword() {
+    thrown.expect(NullPointerException.class);
+    new AdfsHandshakeManager.Builder(
+        "http://endpoint", "username", null, "https://sts", "realm").build();
+  }
+
+  @Test
+  public void testNullEndpoint() {
+    thrown.expect(NullPointerException.class);
+    new AdfsHandshakeManager.Builder(
+        null, "username", "password","https://sts", "realm").build();
+  }
+
+  @Test
+  public void testNullSts() {
+    thrown.expect(NullPointerException.class);
+    new AdfsHandshakeManager.Builder(
+        "http://endpoint", "username", "password", null, "realm").build();
+  }
+
+  @Test
+  public void testNullRealm() {
+    thrown.expect(NullPointerException.class);
+    new AdfsHandshakeManager.Builder(
+        "http://endpoint", "username", "password", "http://sts", null).build();
+  }
+
+  @Test
+  public void testRequestToken() throws IOException{
+    MockHttpPostClient postClient = new MockHttpPostClient();
+    AdfsHandshakeManager manager = new AdfsHandshakeManager.Builder(
+        "https://sharepoint.intranet.com", "username@domain", "pass]]>word&123", 
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed",
+        "urn:realm:sharepoint", postClient).build();
+    URL tokenRequest = new URL(
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed");
+    String tokenResponse = "<s:Envelope "
+        + "xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">"
+        + "<s:Header>Some header</s:Header>"
+        + "<t:RequestSecurityTokenResponse "
+        + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+        + "This is requested token"
+        + "</t:RequestSecurityTokenResponse>"        
+        + "</s:Envelope>";
+    postClient.responseMap.put(tokenRequest,
+        new PostResponseInfo(tokenResponse, null));    
+    assertEquals("<t:RequestSecurityTokenResponse "
+        + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+        + "This is requested token"
+        + "</t:RequestSecurityTokenResponse>", manager.requestToken());
+
+    String expectedRequestBody 
+        = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+        + "<s:Envelope xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\" "
+        + "xmlns:a=\"http://www.w3.org/2005/08/addressing\" "
+        + "xmlns:u=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-"
+        + "wssecurity-utility-1.0.xsd\"><s:Header><a:Action "
+        + "s:mustUnderstand=\"1\">"
+        + "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>"
+        + "<a:ReplyTo><a:Address>http://www.w3.org/2005/08/addressing/anonymous"
+        + "</a:Address></a:ReplyTo><a:To s:mustUnderstand=\"1\"><![CDATA["
+        + "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed]]>"
+        + "</a:To><o:Security s:mustUnderstand=\"1\" "
+        + "xmlns:o=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-"
+        + "wssecurity-secext-1.0.xsd\"><o:UsernameToken>"
+        + "<o:Username><![CDATA[username@domain]]></o:Username>"
+        + "<o:Password><![CDATA[pass]]]]><![CDATA[>word&123]]>"
+        + "</o:Password></o:UsernameToken></o:Security></s:Header>"
+        + "<s:Body><t:RequestSecurityToken "
+        + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+        + "<wsp:AppliesTo "
+        + "xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\">"
+        + "<a:EndpointReference><a:Address>"
+        + "<![CDATA[urn:realm:sharepoint]]></a:Address>"
+        + "</a:EndpointReference></wsp:AppliesTo><t:KeyType>"
+        + "http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey"
+        + "</t:KeyType><t:RequestType>"
+        + "http://schemas.xmlsoap.org/ws/2005/02/trust/Issue"
+        + "</t:RequestType><t:TokenType>urn:oasis:names:tc:SAML:1.0:assertion"
+        + "</t:TokenType></t:RequestSecurityToken></s:Body></s:Envelope>";
+
+    assertEquals(expectedRequestBody,
+        postClient.receivedRequestBodyMap.get(tokenRequest));
+    
+  }
+
+  @Test
+  public void testNullRequestToken() throws IOException{    
+    MockHttpPostClient postClient = new MockHttpPostClient();    
+    AdfsHandshakeManager manager = new AdfsHandshakeManager.Builder(
+        "https://sharepoint.intranet.com", "username@domain", "password&123", 
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed",
+        "urn:realm:sharepoint", postClient).build();
+    URL tokenRequest = new URL(
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed");
+    postClient.responseMap.put(tokenRequest,
+        new PostResponseInfo("<data>some invalid content</data>", null));
+    thrown.expect(IOException.class);
+    String token = manager.requestToken();    
+  }
+
+  @Test
+  public void testGetAuthenticationCookie() throws IOException{
+    MockHttpPostClient postClient = new MockHttpPostClient();    
+    AdfsHandshakeManager manager = new AdfsHandshakeManager.Builder(
+        "https://sharepoint.intranet.com", "username@domain", "password&123", 
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed",
+        "urn:realm:sharepoint", postClient).build();   
+
+    URL submitToken = new URL("https://sharepoint.intranet.com/_trust");
+
+    Map<String, List<String>> responseHeaders 
+        = new HashMap<String, List<String>>();
+    responseHeaders.put("some-header", Arrays.asList("some value"));
+    responseHeaders.put("Set-Cookie", Arrays.asList("FedAuth=AutheCookie"));
+
+    postClient.responseMap.put(submitToken,
+        new PostResponseInfo("submit token response", responseHeaders));
+    String cookie = manager.getAuthenticationCookie(
+        "<t:RequestSecurityTokenResponse "
+        + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+        + "This is requested token"
+        + "</t:RequestSecurityTokenResponse>");
+
+    assertEquals("FedAuth=AutheCookie;", cookie);
+
+    String expectedSubmitTokenRequest = "wa=wsignin1.0&wctx=" 
+      + URLEncoder.encode("https://sharepoint.intranet.com/_layouts/"
+          + "Authenticate.aspx","UTF-8")
+      + "&wresult=" + URLEncoder.encode("<t:RequestSecurityTokenResponse "
+          + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+          + "This is requested token"
+          + "</t:RequestSecurityTokenResponse>", "UTF-8");  
+    assertEquals(expectedSubmitTokenRequest,
+        postClient.receivedRequestBodyMap.get(submitToken));
+  }
+
+  @Test
+  public void testAuthenticateInSamlHandlerWithADFS() throws IOException{
+    MockHttpPostClient postClient = new MockHttpPostClient();
+    String username = "username@domain";
+    String password = "password&123";
+    AdfsHandshakeManager manager = new AdfsHandshakeManager.Builder(
+        "https://sharepoint.intranet.com", username, password, 
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed",
+        "urn:realm:sharepoint", postClient).build();
+    URL tokenRequest = new URL(
+        "https://sts.dmain.com/adfs/services/trust/2005/usernamemixed");
+    String tokenResponse = "<s:Envelope "
+        + "xmlns:s=\"http://www.w3.org/2003/05/soap-envelope\">"
+        + "<s:Header>Some header</s:Header>"
+        + "<t:RequestSecurityTokenResponse "
+        + "xmlns:t=\"http://schemas.xmlsoap.org/ws/2005/02/trust\">"
+        + "This is requested token"
+        + "</t:RequestSecurityTokenResponse>"        
+        + "</s:Envelope>";
+    postClient.responseMap.put(tokenRequest,
+        new PostResponseInfo(tokenResponse, null));    
+    URL submitToken = new URL("https://sharepoint.intranet.com/_trust");    
+    Map<String, List<String>> responseHeaders 
+        = new HashMap<String, List<String>>();
+    responseHeaders.put("some-header", Arrays.asList("some value"));
+    responseHeaders.put("Set-Cookie", Arrays.asList("FedAuth=AutheCookie"));
+
+    postClient.responseMap.put(submitToken,
+        new PostResponseInfo(null, responseHeaders));
+
+    SamlAuthenticationHandler authenticationHandler 
+        = new SamlAuthenticationHandler.Builder(username, password,
+            new MockScheduledExecutor(), manager).build();
+    AuthenticationResult result = authenticationHandler.authenticate();
+
+    assertEquals("FedAuth=AutheCookie;", result.getCookie());
+    assertEquals("NO_ERROR", result.getErrorCode());
+    assertEquals(600, result.getCookieTimeOut());    
+  }
+
+  @Test
+  public void testEscapeCdata() {
+
+    AdfsHandshakeManager manager = new AdfsHandshakeManager.Builder(
+        "http://endpoint", "username", "password", "https://sts", "realm")
+        .build();
+
+     assertEquals("<![CDATA[This is simple]]>",
+         manager.escapeCdata("This is simple"));
+
+     assertEquals(
+         "<![CDATA[This is simple]]]]><![CDATA[>with additional text]]>",
+         manager.escapeCdata("This is simple]]>with additional text"));
+
+     assertEquals(
+         "<![CDATA[This is > & simple]]]]><![CDATA[>]]>",
+         manager.escapeCdata("This is > & simple]]>"));
+
+     assertEquals(
+         "<![CDATA[]]]]><![CDATA[>]]>",
+         manager.escapeCdata("]]>"));
+
+     assertEquals("<![CDATA[<![CDATA[This is simple]]]]><![CDATA[>]]>",
+         manager.escapeCdata("<![CDATA[This is simple]]>"));    
+
+     assertEquals("<![CDATA[This is simple]]]]>"
+         + "<![CDATA[>with multiple]]]]><![CDATA[>]]>",
+         manager.escapeCdata("This is simple]]>with multiple]]>"));
+  }
+}
diff --git a/test/com/google/enterprise/adaptor/sharepoint/CallerRunsExecutor.java b/test/com/google/enterprise/adaptor/sharepoint/CallerRunsExecutor.java
index 8dbf573..28c5bba 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/CallerRunsExecutor.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/CallerRunsExecutor.java
@@ -17,7 +17,10 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.AbstractExecutorService;
+import java.util.concurrent.Callable;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -60,5 +63,5 @@
   public List<Runnable> shutdownNow() {
     shutdown();
     return Collections.emptyList();
-  }
+  }  
 }
diff --git a/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java b/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java
index 84ec9da..fb699af 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java
@@ -16,11 +16,8 @@
 
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandler.AuthenticationHandler;
-
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
@@ -36,47 +33,9 @@
 public class FormsAuthenticationHandlerTest {
   
   @Rule
-  public ExpectedException thrown = ExpectedException.none();
+  public ExpectedException thrown = ExpectedException.none();   
   
-  private static class UnsupportedAuthenticationHandler 
-      implements AuthenticationHandler {
-
-    public AuthenticationResult authenticate() throws IOException {
-      throw new UnsupportedOperationException();
-    }
-
-    public boolean isFormsAuthentication() throws IOException {
-      throw new UnsupportedOperationException();
-    }
-  }
-  
-  private static class MockAuthenticationHandler 
-      extends UnsupportedAuthenticationHandler {
-    
-    private boolean isFormsAuthentication;
-    private AuthenticationResult authenticationResult;
-    
-    MockAuthenticationHandler(boolean isFormsAuthentication,
-        AuthenticationResult authenticationResult) {
-      this.isFormsAuthentication = isFormsAuthentication;
-      this.authenticationResult = authenticationResult;      
-    }
-
-    @Override
-    public AuthenticationResult authenticate() throws IOException {
-      if (authenticationResult == null) {
-        throw new UnsupportedOperationException();
-      }
-      return authenticationResult;
-    }
-
-    @Override
-    public boolean isFormsAuthentication() throws IOException {
-      return isFormsAuthentication;
-    }
-  }
-  
-  private static class UnsupportedScheduledExecutor extends CallerRunsExecutor 
+  static class UnsupportedScheduledExecutor extends CallerRunsExecutor 
       implements ScheduledExecutorService {
 
     public ScheduledFuture<?> schedule(Runnable command, long delay,
@@ -101,7 +60,7 @@
     
   }
   
-  private static class MockScheduledExecutor 
+  static class MockScheduledExecutor 
       extends UnsupportedScheduledExecutor {
     long executionDelay;
     TimeUnit executionTimeUnit;   
@@ -115,69 +74,73 @@
     }
   }
   
+  static class MockFormsAuthenticationHandler
+      extends FormsAuthenticationHandler {
+
+    public MockFormsAuthenticationHandler(String username, String password,
+        ScheduledExecutorService executor) {
+      super(username, password, executor);
+    }
+
+    @Override
+    boolean isFormsAuthentication() throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    AuthenticationResult authenticate() throws IOException {
+      throw new UnsupportedOperationException();
+    }    
+  }  
+  
   @Test
   public void testConstructor() {
-    new FormsAuthenticationHandler("username", "password",
-        new UnsupportedScheduledExecutor(),
-        new UnsupportedAuthenticationHandler());
+    new MockFormsAuthenticationHandler("username", "password",
+        new UnsupportedScheduledExecutor());
   }
   
   @Test
   public void testNullUserName() {
     thrown.expect(NullPointerException.class);
-    new FormsAuthenticationHandler(null, "password",
-        new UnsupportedScheduledExecutor(),
-        new UnsupportedAuthenticationHandler());
+    new MockFormsAuthenticationHandler(null, "password",
+        new UnsupportedScheduledExecutor());
   }
   
   @Test
   public void testNullPassword() {
     thrown.expect(NullPointerException.class);
-    new FormsAuthenticationHandler("username", null,
-        new UnsupportedScheduledExecutor(),
-        new UnsupportedAuthenticationHandler());
+    new MockFormsAuthenticationHandler("username", null,
+        new UnsupportedScheduledExecutor());
   }
   
   @Test
   public void testNullScheduledExecutor() {
     thrown.expect(NullPointerException.class);
-    new FormsAuthenticationHandler("username", "password", null,
-        new UnsupportedAuthenticationHandler());
-  }
-  
-  @Test
-  public void testNullAuthenticationHandler() {
-    thrown.expect(NullPointerException.class);
-    new FormsAuthenticationHandler("username", "password",
-        new UnsupportedScheduledExecutor(),null);
-  }
-  
-  @Test
-  public void testWindowsAuthentication() throws IOException {
-    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
-        "username", "password",  new UnsupportedScheduledExecutor(),
-        new MockAuthenticationHandler(false, null));
-    formsHandler.start();
-    assertFalse(formsHandler.isFormsAuthentication());
-    assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
-  }
+    new MockFormsAuthenticationHandler("username", "password", null);
+  } 
   
   @Test
   public void testEmptyUsernamePassword() throws IOException {
-    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
-        "", "",  new UnsupportedScheduledExecutor(),
-        new UnsupportedAuthenticationHandler());
-    formsHandler.start();
-    assertFalse(formsHandler.isFormsAuthentication());
+    FormsAuthenticationHandler formsHandler 
+        = new MockFormsAuthenticationHandler("", "",
+            new UnsupportedScheduledExecutor());
+    formsHandler.start();    
     assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
   }
   
   @Test
   public void testFormsAuthenticationNoError() throws IOException {
-    MockScheduledExecutor executor = new MockScheduledExecutor();    
-    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
-        "username", "password", executor, new MockAuthenticationHandler(true,
-            new AuthenticationResult("AuthenCookie", 99, "NO_ERROR")));
+    MockScheduledExecutor executor = new MockScheduledExecutor();
+    FormsAuthenticationHandler formsHandler 
+        = new MockFormsAuthenticationHandler("username", "password", executor) {
+          @Override public boolean isFormsAuthentication() throws IOException {
+            return true;
+          }
+          @Override public AuthenticationResult authenticate()
+              throws IOException {
+            return new AuthenticationResult("AuthenCookie", 99, "NO_ERROR");
+          }
+        };
     formsHandler.start();
     assertTrue(formsHandler.isFormsAuthentication());
     assertEquals(Collections.unmodifiableList(Arrays.asList("AuthenCookie")),
@@ -189,10 +152,17 @@
   
   @Test
   public void testFormsAuthenticationPasswordMismatch() throws IOException {
-    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
-        "username", "password", new UnsupportedScheduledExecutor(),
-        new MockAuthenticationHandler(true,
-            new AuthenticationResult(null, 99, "PASSWORD_NOT_MATCH")));
+    FormsAuthenticationHandler formsHandler 
+        = new MockFormsAuthenticationHandler("username", "password",
+            new UnsupportedScheduledExecutor()) {
+          @Override public boolean isFormsAuthentication() throws IOException {
+            return true;
+          }
+          @Override public AuthenticationResult authenticate()
+              throws IOException {
+            return new AuthenticationResult(null, 99, "PASSWORD_MISMATCH");
+          }
+        };
     formsHandler.start();
     assertTrue(formsHandler.isFormsAuthentication());
     assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
diff --git a/test/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManagerTest.java b/test/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManagerTest.java
new file mode 100644
index 0000000..c7d08e9
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/sharepoint/LiveAuthenticationHandshakeManagerTest.java
@@ -0,0 +1,215 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandlerTest.MockScheduledExecutor;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.HttpPostClient;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.PostResponseInfo;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+
+public class LiveAuthenticationHandshakeManagerTest {
+  
+  private static final String LIVE_AUTHENTICATION_RESPONSE 
+      = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+      + "<S:Envelope xmlns:S=\"http://www.w3.org/2003/05/soap-envelope\" "
+      + "xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-"
+      + "wssecurity-secext-1.0.xsd\" "
+      + "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-"
+      + "wss-wssecurity-utility-1.0.xsd\" "
+      + "xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">"
+      + "<S:Header><wsa:Action "
+      + "xmlns:S=\"http://www.w3.org/2003/05/soap-envelope\" "
+      + "xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" "
+      + "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-"
+      + "wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"Action\" "
+      + "S:mustUnderstand=\"1\">"
+      + "http://schemas.xmlsoap.org/ws/2005/02/trust/RSTR/Issue"
+      + "</wsa:Action><wsa:To "
+      + "xmlns:S=\"http://www.w3.org/2003/05/soap-envelope\" "
+      + "xmlns:wsa=\"http://www.w3.org/2005/08/addressing\" "
+      + "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-"
+      + "wss-wssecurity-utility-1.0.xsd\" "
+      + "wsu:Id=\"To\" S:mustUnderstand=\"1\">"
+      + "http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous"
+      + "</wsa:To><wsse:Security S:mustUnderstand=\"1\">"
+      + "<wsu:Timestamp xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/"
+      + "oasis-200401-wss-wssecurity-utility-1.0.xsd\" wsu:Id=\"TS\">"
+      + "<wsu:Created>2014-03-27T20:56:38Z</wsu:Created><wsu:Expires>"
+      + "2014-03-27T21:01:38Z</wsu:Expires></wsu:Timestamp></wsse:Security>"
+      + "</S:Header><S:Body><wst:RequestSecurityTokenResponse "
+      + "xmlns:S=\"http://www.w3.org/2003/05/soap-envelope\" "
+      + "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\" "
+      + "xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-"
+      + "wss-wssecurity-secext-1.0.xsd\" "
+      + "xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-"
+      + "wss-wssecurity-utility-1.0.xsd\" "
+      + "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" "
+      + "xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\" "
+      + "xmlns:psf=\"http://schemas.microsoft.com/Passport"
+      + "/SoapServices/SOAPFault\"><wst:TokenType>urn:passport:compact"
+      + "</wst:TokenType><wsp:AppliesTo "
+      + "xmlns:wsa=\"http://www.w3.org/2005/08/addressing\">"
+      + "<wsa:EndpointReference><wsa:Address>"
+      + "https://sharepoint.example.com"
+      + "</wsa:Address></wsa:EndpointReference>"
+      + "</wsp:AppliesTo><wst:Lifetime><wsu:Created>2014-03-27T20:56:38Z"
+      + "</wsu:Created><wsu:Expires>2014-03-28T20:56:38Z</wsu:Expires>"
+      + "</wst:Lifetime><wst:RequestedSecurityToken>"
+      + "<wsse:BinarySecurityToken Id=\"Compact0\">"
+      + "t=This is live authentication token to extract"
+      + "</wsse:BinarySecurityToken></wst:RequestedSecurityToken>"
+      + "<wst:RequestedAttachedReference><wsse:SecurityTokenReference>"
+      + "<wsse:Reference URI=\"euzZqFurd7rgUGVjTUnCah09kbA=\">"
+      + "</wsse:Reference></wsse:SecurityTokenReference>"
+      + "</wst:RequestedAttachedReference><wst:RequestedUnattachedReference>"
+      + "<wsse:SecurityTokenReference><wsse:Reference "
+      + "URI=\"euzZqFurd7rgUGVjTUnCah09kbA=\"></wsse:Reference>"
+      + "</wsse:SecurityTokenReference></wst:RequestedUnattachedReference>"
+      + "</wst:RequestSecurityTokenResponse></S:Body></S:Envelope>";
+  
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();  
+  
+  @Test
+  public void testConstructor() {
+    new LiveAuthenticationHandshakeManager.Builder(
+        "http://sharepointurl", "username", "password")
+        .build();    
+  }
+  
+  @Test
+  public void testNullUsername() {
+    thrown.expect(NullPointerException.class);
+    new LiveAuthenticationHandshakeManager.Builder(
+        "http://endpoint", null, "password").build();
+  }
+  
+  @Test
+  public void testNullPassword() {
+    thrown.expect(NullPointerException.class);
+    new LiveAuthenticationHandshakeManager.Builder(
+        "http://endpoint", "username", null).build();
+  }
+  
+  @Test
+  public void testNullSharePointUrl() {
+    thrown.expect(NullPointerException.class);
+    new LiveAuthenticationHandshakeManager.Builder(
+        null, "username", "password").build();
+  }
+  
+  @Test
+  public void testExtractToken() throws IOException {
+    LiveAuthenticationHandshakeManager manager 
+        = new LiveAuthenticationHandshakeManager.Builder(
+            "https://sharepoint.example.com", "username", "password").build();    
+    
+    assertEquals("t=This is live authentication token to extract",
+        manager.extractToken(LIVE_AUTHENTICATION_RESPONSE));
+  }  
+  
+  @Test
+  public void testExtractTokenWithInvalidInput() throws IOException {
+    LiveAuthenticationHandshakeManager manager 
+        = new LiveAuthenticationHandshakeManager.Builder(
+            "https://sharepoint.example.com", "username", "password").build();
+    
+    String tokenResponse = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+        + "<S:Envelope xmlns:S=\"http://www.w3.org/2003/05/soap-envelope\">"       
+        + "<data>Something went wrong this is invalid</data>"
+        + "</S:Envelope>";
+    
+    thrown.expect(IOException.class);
+    String extractedToken = manager.extractToken(tokenResponse);   
+  }
+  
+  @Test
+  public void testExtractTokenWithNullInput() throws IOException {
+    LiveAuthenticationHandshakeManager manager 
+        = new LiveAuthenticationHandshakeManager.Builder(
+            "https://sharepoint.example.com", "username", "password").build();
+    thrown.expect(IOException.class);
+    String extractedToken = manager.extractToken(null);   
+  }
+  
+  @Test
+  public void testAuthenticateInSamlHandlerWithLive() throws IOException{
+    MockHttpPostClient postClient = new MockHttpPostClient();    
+    LiveAuthenticationHandshakeManager manager 
+        = new LiveAuthenticationHandshakeManager.Builder(
+        "https://sharepoint.example.com", "username@domain", "password&123",
+        postClient).build();
+    URL tokenRequest = new URL(
+        "https://login.microsoftonline.com/extSTS.srf");   
+    postClient.responseMap.put(tokenRequest,
+        new PostResponseInfo(LIVE_AUTHENTICATION_RESPONSE, null));    
+    URL submitToken = new URL(
+        "https://sharepoint.example.com/_forms/default.aspx?wa=wsignin1.0");    
+    Map<String, List<String>> responseHeaders 
+        = new HashMap<String, List<String>>();
+    responseHeaders.put("some-header", Arrays.asList("some value"));
+    responseHeaders.put("Set-Cookie",
+        Arrays.asList("FedAuth=AutheCookie", "rfta=rftaValue"));
+    
+    postClient.responseMap.put(submitToken,
+        new PostResponseInfo(null, responseHeaders));
+    
+    SamlAuthenticationHandler authenticationHandler 
+        = new SamlAuthenticationHandler.Builder("username@domain",
+            "password&123", new MockScheduledExecutor(), manager).build();
+    AuthenticationResult result = authenticationHandler.authenticate();
+    
+    assertEquals("FedAuth=AutheCookie;rfta=rftaValue;", result.getCookie());
+    assertEquals("NO_ERROR", result.getErrorCode());
+    assertEquals(600, result.getCookieTimeOut());    
+  }
+  
+  
+  private static class MockHttpPostClient implements HttpPostClient {
+
+    private Map<URL, SamlAuthenticationHandler.PostResponseInfo> responseMap;
+    private Map<URL, String> receivedRequestBodyMap;
+    public MockHttpPostClient() {
+      responseMap = new HashMap<URL, PostResponseInfo>();
+      receivedRequestBodyMap = new HashMap<URL, String>();      
+    }
+    
+    @Override    
+    public PostResponseInfo issuePostRequest(
+        URL url, Map<String, String> connectionProperties, String requestBody)
+        throws IOException {
+      if (!responseMap.containsKey(url)) {
+        throw new UnsupportedOperationException(
+            "Unexpected Http Post for URL " + url);
+      }
+      // log incoming request body
+      receivedRequestBodyMap.put(url, requestBody);
+      return responseMap.get(url);
+    }
+  }  
+}
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandlerTest.java b/test/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandlerTest.java
new file mode 100644
index 0000000..4b05103
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/sharepoint/SamlAuthenticationHandlerTest.java
@@ -0,0 +1,119 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandlerTest.UnsupportedScheduledExecutor;
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandlerTest.MockScheduledExecutor;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
+
+import java.io.IOException;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+
+public class SamlAuthenticationHandlerTest {
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+  
+  @Test
+  public void testBuilder() {
+    new SamlAuthenticationHandler.Builder("username", "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedSamlHandshakeManager()).build();
+  }
+  
+  @Test
+  public void testNullSamlClient() {
+    thrown.expect(NullPointerException.class);
+    new SamlAuthenticationHandler.Builder("username", "password",
+         new UnsupportedScheduledExecutor(), null).build();
+  }
+  
+  @Test
+  public void testIsFormsAutentication() throws IOException {
+    assertTrue(new SamlAuthenticationHandler.Builder("username", "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedSamlHandshakeManager()).build().isFormsAuthentication());    
+  }
+  
+  @Test
+  public void testNullToken() throws IOException {
+    SamlAuthenticationHandler handler = new SamlAuthenticationHandler.Builder(
+        "username", "password", new UnsupportedScheduledExecutor(),
+        new MockSamlHandshakeManager(null, null) {
+          @Override public String getAuthenticationCookie(String token) {
+            throw new UnsupportedOperationException();
+          }      
+        }).build();
+    
+    assertTrue(handler.isFormsAuthentication());
+    thrown.expect(IOException.class);
+    AuthenticationResult result = handler.authenticate();    
+  }
+  
+  @Test
+  public void testSAMLAutentication() throws IOException {
+    SamlAuthenticationHandler handler 
+        = new SamlAuthenticationHandler.Builder("username", "password", 
+            new MockScheduledExecutor(),
+            new MockSamlHandshakeManager("token", "AuthenticationCookie"))
+            .build();    
+    assertTrue(handler.isFormsAuthentication());
+    AuthenticationResult result = handler.authenticate();
+    assertNotNull(result);
+    assertEquals("AuthenticationCookie", result.getCookie());
+    assertEquals(600, result.getCookieTimeOut());
+    assertEquals("NO_ERROR", result.getErrorCode());    
+  }
+  
+  private static class UnsupportedSamlHandshakeManager 
+      implements SamlHandshakeManager {
+    @Override
+    public String requestToken() throws IOException {
+      throw new UnsupportedOperationException(); 
+    }
+
+    @Override
+    public String getAuthenticationCookie(String token) throws IOException {
+      throw new UnsupportedOperationException();
+    }    
+  }
+  
+  private static class MockSamlHandshakeManager 
+      extends UnsupportedSamlHandshakeManager {
+    private String token;
+    private String cookie;
+    MockSamlHandshakeManager(String token, String cookie) {
+      this.token = token;
+      this.cookie = cookie;      
+    }
+    
+    @Override
+    public String requestToken() {
+      return token;
+    }
+    
+    @Override
+    public String getAuthenticationCookie(String token) throws IOException {
+      return cookie;
+    }
+  }  
+}
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
index 4606c23..4162017 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointAdaptorTest.java
@@ -35,6 +35,7 @@
 import com.google.enterprise.adaptor.Metadata;
 import com.google.enterprise.adaptor.Principal;
 import com.google.enterprise.adaptor.UserPrincipal;
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
 import com.google.enterprise.adaptor.sharepoint.SharePointAdaptor.SiteUserIdMappingCallable;
 import com.google.enterprise.adaptor.sharepoint.SharePointAdaptor.SoapFactory;
 
@@ -116,6 +117,9 @@
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.atomic.AtomicLong;
+import javax.xml.ws.Binding;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.EndpointReference;
 
 import javax.xml.ws.Holder;
 import javax.xml.ws.WebServiceException;
@@ -244,7 +248,6 @@
       };
   private final MockSoapFactory initableSoapFactory
       = MockSoapFactory.blank()
-      .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
       .endpoint(VS_ENDPOINT, MockSiteData.blank()
           .register(VS_CONTENT_EXCHANGE)
           .register(CD_CONTENT_EXCHANGE));
@@ -316,26 +319,90 @@
   @Test
   public void testNullSoapFactory() {
     thrown.expect(NullPointerException.class);
-    new SharePointAdaptor(null, new UnsupportedHttpClient(), executorFactory);
+    new SharePointAdaptor(null, new UnsupportedHttpClient(), executorFactory,
+        new UnsupportedAuthenticationClientFactory());
   }
 
   @Test
   public void testNullHttpClient() {
     thrown.expect(NullPointerException.class);
-    new SharePointAdaptor(MockSoapFactory.blank(), null, executorFactory);
+    new SharePointAdaptor(MockSoapFactory.blank(), null, executorFactory,
+        new UnsupportedAuthenticationClientFactory());
   }
 
   @Test
   public void testNullExecutorFactory() {
     thrown.expect(NullPointerException.class);
     new SharePointAdaptor(MockSoapFactory.blank(), new UnsupportedHttpClient(),
-        null);
+        null, new UnsupportedAuthenticationClientFactory());
   }
 
   @Test
   public void testInitDestroy() throws Exception {
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
+    adaptor.init(new MockAdaptorContext(config, pusher));
+    adaptor.destroy();
+    adaptor = null;
+  }
+  
+  @Test
+  public void testAdaptorInitWithAdfs() throws Exception {
+    SoapFactory siteDataFactory = MockSoapFactory.blank()
+        .endpoint(VS_ENDPOINT, MockSiteData.blank()
+            .register(VS_CONTENT_EXCHANGE)
+            .register(CD_CONTENT_EXCHANGE))
+        .endpoint("http://localhost:1/_vti_bin/People.asmx",
+            new MockPeopleSoap())
+        .endpoint("http://localhost:1/_vti_bin/UserGroup.asmx",
+            new MockUserGroupSoap(null));
+    
+    adaptor = new SharePointAdaptor(siteDataFactory,
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryAdfs());
+    config.overrideKey("sharepoint.sts.endpoint", "https://stsendpoint");
+    config.overrideKey("sharepoint.sts.realm", "urn:sharepoint:com");
+    adaptor.init(new MockAdaptorContext(config, pusher));
+    adaptor.destroy();
+    adaptor = null;
+  }
+  
+  @Test
+  public void testAdaptorInitWithMissingRelam() throws Exception {
+    SoapFactory siteDataFactory = MockSoapFactory.blank()
+        .endpoint(VS_ENDPOINT, MockSiteData.blank()
+            .register(VS_CONTENT_EXCHANGE)
+            .register(CD_CONTENT_EXCHANGE))
+        .endpoint("http://localhost:1/_vti_bin/People.asmx",
+            new MockPeopleSoap())
+        .endpoint("http://localhost:1/_vti_bin/UserGroup.asmx",
+            new MockUserGroupSoap(null));
+    
+    adaptor = new SharePointAdaptor(siteDataFactory,
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
+    config.overrideKey("sharepoint.sts.endpoint", "https://stsendpoint");   
+    adaptor.init(new MockAdaptorContext(config, pusher));
+    adaptor.destroy();
+    adaptor = null;
+  }
+  
+  @Test
+  public void testAdaptorInitWithLive() throws Exception {
+    SoapFactory siteDataFactory = MockSoapFactory.blank()
+        .endpoint(VS_ENDPOINT, MockSiteData.blank()
+            .register(VS_CONTENT_EXCHANGE)
+            .register(CD_CONTENT_EXCHANGE))
+        .endpoint("http://localhost:1/_vti_bin/People.asmx",
+            new MockPeopleSoap())
+        .endpoint("http://localhost:1/_vti_bin/UserGroup.asmx",
+            new MockUserGroupSoap(null));
+    
+    adaptor = new SharePointAdaptor(siteDataFactory,
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryLive());
+    config.overrideKey("sharepoint.useLiveAuthentication", "true");   
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.destroy();
     adaptor = null;
@@ -344,7 +411,8 @@
   @Test
   public void testInitDestroyInitDestroy() throws Exception {
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());    
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.destroy();
     adaptor.init(new MockAdaptorContext(config, pusher));
@@ -355,7 +423,8 @@
   @Test
   public void testTrailingSlashInit() throws Exception {
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     config.overrideKey("sharepoint.server", "http://localhost:1/");
     adaptor.init(new MockAdaptorContext(config, pusher));
   }
@@ -416,8 +485,7 @@
 
   @Test
   public void testGetDocContentWrongServer() throws Exception {
-    SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
+    SoapFactory siteDataFactory = MockSoapFactory.blank()        
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -425,7 +493,8 @@
                 "http://wronghost:1/", 1, null, null)));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -439,7 +508,6 @@
   public void testGetDocContentWrongPage() throws Exception {
     final String wrongPage = "http://localhost:1/wrongPage";
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -449,7 +517,8 @@
                 wrongPage, false, null, null, null, null)));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(new DocId(wrongPage));
@@ -467,14 +536,14 @@
     mockPeople.addToResult("GDC-PSL\\Administrator", "dministrator", 
         SPPrincipalType.USER);
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE))
         .endpoint("http://localhost:1/_vti_bin/People.asmx", mockPeople);
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsResponse response = new GetContentsResponse(baos);
@@ -526,7 +595,6 @@
         SPPrincipalType.USER);
     
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE
               .replaceInContent("</Policies>", claimsPolicyUsers))
@@ -534,7 +602,8 @@
         .endpoint("http://localhost:1/_vti_bin/People.asmx", mockPeople);
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory, 
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsResponse response = new GetContentsResponse(baos);
@@ -555,7 +624,6 @@
   @Test
   public void testGetDocContentSiteCollection() throws Exception {
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -566,7 +634,8 @@
             .register(SITES_SITECOLLECTION_SC_CONTENT_EXCHANGE));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -621,7 +690,6 @@
   @Test
   public void testGetDocContentSiteCollectionWithAdGroup() throws Exception {
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -640,7 +708,8 @@
                 "IsDomainGroup=\"True\"")));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -667,7 +736,6 @@
         + "<permission memberid='15' mask='756052856929' />"        
         + "<permission memberid='19' mask='756052856929' /></permissions>";
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -681,7 +749,8 @@
 
     
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -713,7 +782,6 @@
       throws Exception {
     ReferenceSiteData siteData = new ReferenceSiteData();
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
             .register(VS_CONTENT_EXCHANGE)
             .register(CD_CONTENT_EXCHANGE)
@@ -734,7 +802,8 @@
               .replaceInContent("spuser2", "spuser100"));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
 
     // This populates the cache, but otherwise doesn't test anything new.
@@ -781,7 +850,8 @@
               .replaceInContent("NoIndex=\"False\"", "NoIndex=\"True\"")));
 
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -800,7 +870,8 @@
         .register(SITES_SITECOLLECTION_S_CONTENT_EXCHANGE);
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -858,7 +929,8 @@
           .replaceInContent("NoIndex=\"False\"", "NoIndex=\"True\""));
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -883,7 +955,8 @@
           + "/NonDefault.aspx", false, null, null, null, null))
         .register(SITES_SITECOLLECTION_LISTS_CUSTOMLIST_L_CONTENT_EXCHANGE);
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -913,7 +986,8 @@
                true, null, null, "{6F33949A-B3FF-4B0C-BA99-93CB518AC2C0}",
                null));
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -972,7 +1046,7 @@
         return "http://localhost:1/sites/SiteCollection/Lists/Custom List"
             + "/AllItems.aspx";
       }
-    }, executorFactory);
+    }, executorFactory, new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1032,7 +1106,7 @@
         return "http://localhost:1/sites/SiteCollection/Lists/Custom List"
             + "/AllItems.aspx";
       }
-    }, executorFactory);
+    }, executorFactory, new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1061,7 +1135,8 @@
         .register(SITES_SITECOLLECTION_LISTS_CUSTOMLIST_2_A_CONTENT_EXCHANGE);
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1215,7 +1290,8 @@
             "http://localhost:1/sites/SiteCollection/Lists/Custom List/2_.000",
           true, null, null, "{6F33949A-B3FF-4B0C-BA99-93CB518AC2C0}", "2"));
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1264,7 +1340,8 @@
                 "ows_ScopeId='2;#{f9cb02b3-7f29-4cac-804f-ba6e14f1eb39}'"));
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1309,7 +1386,8 @@
               "http://localhost:1/sites/SiteCollection/_vti_bin/UserGroup.asmx",
               mockUserGroupSoap)
           .endpoint(SITES_SITECOLLECTION_ENDPOINT, new UnsupportedSiteData()),
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1415,7 +1493,8 @@
         .register(SITES_SITECOLLECTION_LISTS_CUSTOMLIST_1_F_CONTENT_EXCHANGE);
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1566,7 +1645,8 @@
         .register(SITES_SITECOLLECTION_LISTS_CUSTOMLIST_1_F_CONTENT_EXCHANGE);
 
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     ByteArrayOutputStream baos = new ByteArrayOutputStream();
     GetContentsRequest request = new GetContentsRequest(
@@ -1603,7 +1683,6 @@
     // Force a full batch of 2 and a final batch of 1.
     config.overrideKey("feed.maxUrls", "2");
     adaptor = new SharePointAdaptor(MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, MockSiteData.blank()
           .register(VS_CONTENT_EXCHANGE)
           .register(CD_CONTENT_EXCHANGE
@@ -1612,7 +1691,8 @@
           .register(SITES_SITECOLLECTION_SAW_EXCHANGE))
         .endpoint(SITES_SITECOLLECTION_ENDPOINT, MockSiteData.blank()
           .register(SITES_SITECOLLECTION_SC_CONTENT_EXCHANGE)),
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     assertEquals(0, pusher.getRecords().size());
@@ -1697,10 +1777,10 @@
       }
     };
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, countingSiteData);
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     siteData.setSiteDataSoap(state0);
     adaptor.init(new MockAdaptorContext(config, pusher));
@@ -1787,10 +1867,10 @@
       }
     };
     SoapFactory siteDataFactory = MockSoapFactory.blank()
-        .endpoint(AUTH_ENDPOINT, new MockAuthenticationSoap())
         .endpoint(VS_ENDPOINT, countingSiteData);
     adaptor = new SharePointAdaptor(siteDataFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
 
@@ -1808,7 +1888,8 @@
     final String getChangesContentDatabase
         = loadTestString("testModifiedGetDocIdsClient.changes-cd.xml");
     adaptor = new SharePointAdaptor(initableSoapFactory,
-        new UnsupportedHttpClient(), executorFactory);
+        new UnsupportedHttpClient(), executorFactory,
+        new MockAuthenticationClientFactoryForms());
     adaptor.init(new MockAdaptorContext(config, pusher));
     SPContentDatabase result = parseChanges(getChangesContentDatabase);
     List<DocId> docIds = new ArrayList<DocId>();
@@ -1967,8 +2048,9 @@
     }
   }
   
-  private static class UnsupportedPeopleSoap extends DelegatingPeopleSoap {
-    private final String endpoint;
+  private static class UnsupportedPeopleSoap extends DelegatingPeopleSoap
+      implements BindingProvider {
+    private final String endpoint; 
 
     public UnsupportedPeopleSoap() {
       this(null);
@@ -1986,10 +2068,38 @@
         throw new UnsupportedOperationException("Endpoint: " + endpoint);
       }
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+       throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EndpointReference> T getEndpointReference(
+        Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
   }
   
   private static class MockPeopleSoap extends UnsupportedPeopleSoap {
     private final ArrayOfPrincipalInfo result;
+    private Map<String, Object> requestContext = new HashMap<String, Object>();
+   
     public MockPeopleSoap() {
       this.result = new ArrayOfPrincipalInfo();
     }
@@ -2009,10 +2119,16 @@
       p.setPrincipalType(type);
       result.getPrincipalInfo().add(p);      
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return requestContext;
+    }    
   }
 
   private static class MockUserGroupSoap extends UnsupportedUserGroupSoap {
-    final Users users;    
+    final Users users;
+    private Map<String, Object> requestContext = new HashMap<String, Object>();
     public MockUserGroupSoap(Users users) {
       this.users = users;      
     }
@@ -2031,11 +2147,16 @@
       result.setGetUserCollectionFromSite(siteUsers);
       return result;      
     }
+        
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return requestContext;
+    }   
   }
 
   private static class UnsupportedUserGroupSoap
-      extends DelegatingUserGroupSoap {
-    private final String endpoint;
+      extends DelegatingUserGroupSoap  implements BindingProvider {
+    private final String endpoint;   
 
     public UnsupportedUserGroupSoap() {
       this(null);
@@ -2053,6 +2174,32 @@
         throw new UnsupportedOperationException("Endpoint: " + endpoint);
       }
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+       throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EndpointReference> T 
+        getEndpointReference(Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
   }
 
   private abstract static class DelegatingUserGroupSoap
@@ -2366,11 +2513,38 @@
   /**
    * Throw UnsupportedOperationException for all calls.
    */
-  private static class UnsupportedSiteData extends DelegatingSiteData {
+  private static class UnsupportedSiteData extends DelegatingSiteData
+      implements BindingProvider{   
     @Override
     protected SiteDataSoap delegate() {
       throw new UnsupportedOperationException();
     }
+
+    @Override
+    public Map<String, Object> getRequestContext() {
+       throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public <T extends EndpointReference> T 
+        getEndpointReference(Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
   }
 
   private static class UnsupportedCallable<V> implements Callable<V> {
@@ -2383,50 +2557,40 @@
   private static class MockSoapFactory implements SoapFactory {
     private final String expectedEndpoint;
     private final SiteDataSoap siteData;
-    private final UserGroupSoap userGroup;
-    private final AuthenticationSoap authentication;
+    private final UserGroupSoap userGroup;  
     private final PeopleSoap people;
     private final MockSoapFactory chain;
 
     private MockSoapFactory(String expectedEndpoint, SiteDataSoap siteData,
-        UserGroupSoap userGroup, PeopleSoap people,
-        AuthenticationSoap authentication,  MockSoapFactory chain) {
+        UserGroupSoap userGroup, PeopleSoap people, MockSoapFactory chain) {
       this.expectedEndpoint = expectedEndpoint;
       this.siteData = siteData;
       this.userGroup = userGroup;
-      this.people = people;
-      // Tests will always use windows authentication.
-      this.authentication = authentication;
+      this.people = people;   
       this.chain = chain;
     }
 
     public static MockSoapFactory blank() {
-      return new MockSoapFactory(null, null, null, null, null, null);
+      return new MockSoapFactory(null, null, null, null, null);
     }
 
     public MockSoapFactory endpoint(String expectedEndpoint,
         SiteDataSoap siteData) {
       return new MockSoapFactory(
-          expectedEndpoint, siteData, null, null, null, this);
+          expectedEndpoint, siteData, null, null, this);
     }
 
     public MockSoapFactory endpoint(String expectedEndpoint,
         UserGroupSoap userGroup) {
       return new MockSoapFactory(
-          expectedEndpoint, null, userGroup, null, null, this);
+          expectedEndpoint, null, userGroup, null, this);
     }
     
     public MockSoapFactory endpoint(String expectedEndpoint,
         PeopleSoap people) {
       return new MockSoapFactory(
-          expectedEndpoint, null, null, people, null, this);
-    }
-    
-    public MockSoapFactory endpoint(String expectedEndpoint,
-        AuthenticationSoap authentication) {
-      return new MockSoapFactory(
-          expectedEndpoint, null, null, null, authentication, this);
-    }
+          expectedEndpoint, null, null, people, this);
+    }    
 
     @Override
     public SiteDataSoap newSiteData(String endpoint) {
@@ -2438,17 +2602,6 @@
       }
       return chain.newSiteData(endpoint);
     }
-    
-    @Override 
-    public AuthenticationSoap newAuthentication(String endpoint) {
-      if (chain == null) {
-        fail("Could not find endpoint " + endpoint);
-      }
-      if (expectedEndpoint.equals(endpoint) && authentication != null) {
-        return authentication;
-      }
-      return chain.newAuthentication(endpoint);
-    }
 
     @Override
     public UserGroupSoap newUserGroup(String endpoint) {
@@ -2477,7 +2630,7 @@
   }
 
   private static class ReferenceSiteData extends DelegatingSiteData {
-    private volatile SiteDataSoap siteData = new UnsupportedSiteData();
+    private volatile SiteDataSoap siteData = new UnsupportedSiteData();   
 
     @Override
     protected SiteDataSoap delegate() {
@@ -2497,6 +2650,7 @@
     private final List<ContentExchange> contentList;
     private final List<ChangesExchange> changesList;
     private final List<SiteAndWebExchange> siteAndWebList;
+    private Map<String, Object> requestContext = new HashMap<String, Object>();
 
     private MockSiteData() {
       this.urlSegmentsList = Collections.emptyList();
@@ -2592,6 +2746,11 @@
       }
       fail("Could not find " + strUrl);
     }
+    
+    @Override
+    public Map<String, Object> getRequestContext() {
+      return requestContext;
+    }
 
     public static MockSiteData blank() {
       return new MockSiteData();
@@ -2622,7 +2781,7 @@
       List<T> l = new ArrayList<T>(existingList);
       l.add(item);
       return Collections.unmodifiableList(l);
-    }
+    }    
   }
 
   private static class URLSegmentsExchange {
@@ -2734,4 +2893,81 @@
       return this;
     }
   }
+  
+  private static class UnsupportedAuthenticationClientFactory 
+      implements AuthenticationClientFactory {
+
+    @Override
+    public AuthenticationSoap newSharePointFormsAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SamlHandshakeManager newAdfsAuthentication(String virtualServer,
+        String username, String password, String stsendpoint, String stsrelam,
+      String login, String trustlocation) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SamlHandshakeManager newLiveAuthentication(String virtualServer,
+        String username, String password) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+    
+  }
+  
+  private static class MockAuthenticationClientFactoryForms 
+      extends UnsupportedAuthenticationClientFactory {
+    @Override
+    public AuthenticationSoap newSharePointFormsAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      return new MockAuthenticationSoap();
+    }    
+  }
+  
+  private static class MockAuthenticationClientFactoryAdfs 
+      extends UnsupportedAuthenticationClientFactory {
+    @Override
+    public SamlHandshakeManager newAdfsAuthentication(
+        String virtualServer, String username, String password,
+        String stsendpoint, String stsrelam, String login,
+        String trustlocation) throws IOException {
+      return new MockSamlHandshakeManager("Token", "rtf=authenticationCookie;");
+    }
+  }
+  
+  private static class MockAuthenticationClientFactoryLive
+      extends UnsupportedAuthenticationClientFactory {
+    @Override
+    public SamlHandshakeManager newLiveAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      return new MockSamlHandshakeManager("Token", "rtf=authenticationCookie;");
+    }
+  }
+  
+  private static class MockSamlHandshakeManager 
+      implements SamlHandshakeManager {
+    private String token;
+    private String cookie;
+    MockSamlHandshakeManager(String token, String cookie) {
+      this.token = token;
+      this.cookie = cookie;      
+    }
+    
+    @Override
+    public String requestToken() {
+      return token;
+    }
+    
+    @Override
+    public String getAuthenticationCookie(String token) throws IOException {
+      return cookie;
+    }
+  }
+  
 }
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java
index fb9251f..2782a51 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java
@@ -20,6 +20,8 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandlerTest.UnsupportedScheduledExecutor;
+
 import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationMode;
 import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
 import com.microsoft.schemas.sharepoint.soap.authentication.LoginErrorCode;
@@ -35,6 +37,10 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
 
 import javax.xml.ws.Binding;
 import javax.xml.ws.BindingProvider;
@@ -92,41 +98,46 @@
   }
   
   @Test
-  public void testConstructor() {
-    new SharePointFormsAuthenticationHandler (
-        new UnsupportedAuthenticationSoap(), "username", "password");
+  public void testBuilder() {
+    new SharePointFormsAuthenticationHandler.Builder("username", "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationSoap()).build();       
   }
   
   @Test
   public void testNullUserName() {
     thrown.expect(NullPointerException.class);
-    new SharePointFormsAuthenticationHandler (
-        new UnsupportedAuthenticationSoap(), null, "password");
+    new SharePointFormsAuthenticationHandler.Builder(null, "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationSoap()).build();
   }
   
   @Test
   public void testNullPassword() {
     thrown.expect(NullPointerException.class);
-    new SharePointFormsAuthenticationHandler (
-        new UnsupportedAuthenticationSoap(), "username", null);
+    new SharePointFormsAuthenticationHandler.Builder("username", null,
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationSoap()).build();
   }
   
   @Test
   public void testNullAuthenticationClient() {
     thrown.expect(NullPointerException.class);
-    new SharePointFormsAuthenticationHandler ( null, "username", "password");
+    new SharePointFormsAuthenticationHandler.Builder("username", "password",
+        new UnsupportedScheduledExecutor(), null).build();
   }
   
   @Test
   public void testSharePointWithWindowsAuthentication() throws IOException{
     SharePointFormsAuthenticationHandler authenHandler 
-        = new SharePointFormsAuthenticationHandler(
+        = new SharePointFormsAuthenticationHandler.Builder("username",
+            "password", new UnsupportedScheduledExecutor(),
             new MockFormsAuthenticationSoap(){
                 @Override public AuthenticationMode mode()
                 {
                   return AuthenticationMode.WINDOWS;
                 }
-            }, "username", "password");
+            }).build();
     
     assertFalse(authenHandler.isFormsAuthentication());
     AuthenticationResult ar = authenHandler.authenticate();
@@ -139,7 +150,8 @@
   @Test
   public void testSharePointWithFormsPasswordMismatch() throws IOException {
     SharePointFormsAuthenticationHandler authenHandler 
-        = new SharePointFormsAuthenticationHandler(
+        = new SharePointFormsAuthenticationHandler.Builder("username",
+            "password", new UnsupportedScheduledExecutor(),
             new MockFormsAuthenticationSoap(){                
                 @Override public LoginResult login(
                     String username, String password) {
@@ -147,7 +159,7 @@
                   lr.setErrorCode(LoginErrorCode.PASSWORD_NOT_MATCH);
                   return lr;                  
                 }
-            }, "username", "password");
+            }).build();
     
     assertTrue(authenHandler.isFormsAuthentication());
     AuthenticationResult ar = authenHandler.authenticate();
@@ -160,8 +172,9 @@
   @Test
   public void testSharePointWithFormsAuthentication() throws IOException {
     SharePointFormsAuthenticationHandler authenHandler 
-        = new SharePointFormsAuthenticationHandler(
-            new MockFormsAuthenticationSoap() {
+        = new SharePointFormsAuthenticationHandler.Builder("username",
+            "password", new UnsupportedScheduledExecutor(),
+            new MockFormsAuthenticationSoap(){
               @Override public LoginResult login(
                     String username, String password) {
                 LoginResult lr = new LoginResult();
@@ -180,7 +193,7 @@
                     Collections.unmodifiableMap(responseHeaders));
                 return  Collections.unmodifiableMap(responseContext);                
               }
-            }, "username", "password");
+            }).build();    
     assertTrue(authenHandler.isFormsAuthentication());
     AuthenticationResult ar = authenHandler.authenticate();
     assertNotNull(ar);
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
index a69f72b..7b2bd1d 100644
--- a/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptorTest.java
@@ -25,6 +25,7 @@
 import com.google.enterprise.adaptor.DocId;
 import com.google.enterprise.adaptor.GroupPrincipal;
 
+import com.google.enterprise.adaptor.sharepoint.SamlAuthenticationHandler.SamlHandshakeManager;
 import com.google.enterprise.adaptor.sharepoint.SharePointUserProfileAdaptor.UserProfileServiceClient;
 import com.google.enterprise.adaptor.sharepoint.SharePointUserProfileAdaptor.UserProfileServiceFactory;
 import com.google.enterprise.adaptor.sharepoint.SharePointUserProfileAdaptor.UserProfileServiceWS;
@@ -76,6 +77,8 @@
   private Config config;
   private SharePointUserProfileAdaptor adaptor;
   private final Charset charset = Charset.forName("UTF-8");
+  private AuthenticationClientFactory authenticationFactory 
+      = new MockAuthenticationClientFactoryForms();
 
   @Before
   public void setup() {
@@ -130,7 +133,8 @@
     assertEquals(adaptorConfig.getValue("sharepoint.password"), "");
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(null);
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(adaptorConfig, pusher));
   }
@@ -161,7 +165,8 @@
     // since profile properties are null
     serviceFactory.addUserProfileToCollection(5, 6, "user4", null, null);
 
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     assertEquals(0, pusher.getRecords().size());
@@ -174,7 +179,8 @@
       throws IOException, InterruptedException {
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(null);
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     assertEquals(0, pusher.getRecords().size());
@@ -229,7 +235,8 @@
 
     serviceFactory.addUserProfileToCollection(1, 2, "domain\\user1",
         profile, colleaguesData);
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory
+        ,authenticationFactory);
     config.overrideKey("adaptor.namespace", "ns1");
 
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
@@ -308,7 +315,8 @@
   public void testGetDocContentNotFound() throws IOException {
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(null);
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
 
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
@@ -330,7 +338,8 @@
         SharePointUserProfileAdaptor.PROFILE_ACCOUNTNAME_PROPERTY,
         new String[] {"user1"});
     serviceFactory.addUserProfileToCollection(1, 2, "user1", profile, null);
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
 
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
@@ -349,7 +358,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "change token on mock repository");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.setUserProfileChangeToken(null);
@@ -366,7 +376,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "same current token");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.setUserProfileChangeToken("same current token");
@@ -383,7 +394,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "new token");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.setUserProfileChangeToken("old token");
@@ -420,7 +432,8 @@
     //batch 5 -user6
     serviceFactory.addChangeLogForUser("user6");
 
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.setUserProfileChangeToken("old token");
@@ -437,7 +450,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "sp token");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     AccumulatingDocIdPusher pusher = new AccumulatingDocIdPusher();
     adaptor.init(new MockAdaptorContext(config, pusher));
     adaptor.setUserProfileChangeToken("invalid");
@@ -452,7 +466,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "sp token");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     UserProfileServiceClient client = adaptor.new UserProfileServiceClient(null);
 
     ArrayOfContactData colleaguesData = new ArrayOfContactData();
@@ -484,7 +499,8 @@
     MockUserProfileServiceFactoryImpl serviceFactory =
         new MockUserProfileServiceFactoryImpl(
             "sp token");
-    adaptor = new SharePointUserProfileAdaptor(serviceFactory);
+    adaptor = new SharePointUserProfileAdaptor(serviceFactory,
+        authenticationFactory);
     UserProfileServiceClient client =
         adaptor.new UserProfileServiceClient(null);
     // Get an instance of factory
@@ -648,11 +664,6 @@
     public void addChangeLogForUser(String userName) {
       proxy.addChangeLogForUser(userName);
     }
-
-    @Override
-    public AuthenticationSoap newAuthentication(String endpoint) {
-      return new MockAuthenticationSoap();
-    }
   }
   
   private static class MockAuthenticationSoap implements AuthenticationSoap {
@@ -666,4 +677,39 @@
       return AuthenticationMode.WINDOWS;
     }    
   }
+  
+  private static class UnsupportedAuthenticationClientFactory 
+      implements AuthenticationClientFactory {
+
+    @Override
+    public AuthenticationSoap newSharePointFormsAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SamlHandshakeManager newAdfsAuthentication(String virtualServer,
+        String username, String password, String stsendpoint, String stsrelam,
+      String login, String trustlocation) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public SamlHandshakeManager newLiveAuthentication(String virtualServer,
+        String username, String password) throws IOException {
+      throw new UnsupportedOperationException();
+    }
+    
+  }
+  
+  private static class MockAuthenticationClientFactoryForms 
+      extends UnsupportedAuthenticationClientFactory {
+    @Override
+    public AuthenticationSoap newSharePointFormsAuthentication(
+        String virtualServer, String username, String password)
+        throws IOException {
+      return new MockAuthenticationSoap();
+    }    
+  }
 }