text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include <cstring>
  #include <memory>
  #include <string>
  #include <utility>
  
  #include "base/bind.h"
  #include "base/bind_helpers.h"
  #include "base/callback.h"
  #include "base/command_line.h"
  #include "base/files/file_path.h"
  #include "base/files/file_util.h"
  #include "base/files/scoped_temp_dir.h"
  #include "base/location.h"
  #include "base/macros.h"
  #include "base/memory/ref_counted.h"
  #include "base/path_service.h"
  #include "base/run_loop.h"
  #include "base/strings/string16.h"
  #include "base/strings/string_util.h"
  #include "base/strings/stringprintf.h"
  #include "base/strings/utf_string_conversions.h"
  #include "base/values.h"
  #include "chrome/browser/chrome_notification_types.h"
  #include "chrome/browser/chromeos/login/existing_user_controller.h"
  #include "chrome/browser/chromeos/login/startup_utils.h"
  #include "chrome/browser/chromeos/login/test/https_forwarder.h"
  #include "chrome/browser/chromeos/login/test/oobe_base_test.h"
  #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
  #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h"
  #include "chrome/browser/chromeos/login/ui/webui_login_display.h"
  #include "chrome/browser/chromeos/login/users/chrome_user_manager.h"
  #include "chrome/browser/chromeos/login/wizard_controller.h"
  #include "chrome/browser/chromeos/policy/affiliation_test_helper.h"
  #include "chrome/browser/chromeos/policy/device_policy_builder.h"
  #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
  #include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h"
  #include "chrome/browser/chromeos/profiles/profile_helper.h"
  #include "chrome/browser/chromeos/settings/cros_settings.h"
  #include "chrome/browser/policy/test/local_policy_test_server.h"
  #include "chrome/browser/profiles/profile.h"
  #include "chrome/browser/ui/webui/signin/signin_utils.h"
  #include "chrome/common/chrome_constants.h"
  #include "chrome/common/chrome_paths.h"
  #include "chrome/common/chrome_switches.h"
  #include "chrome/grit/generated_resources.h"
  #include "chrome/test/base/in_process_browser_test.h"
  #include "chromeos/chromeos_switches.h"
  #include "chromeos/cryptohome/system_salt_getter.h"
  #include "chromeos/dbus/cryptohome/key.pb.h"
  #include "chromeos/dbus/cryptohome/rpc.pb.h"
  #include "chromeos/dbus/cryptohome_client.h"
  #include "chromeos/dbus/dbus_thread_manager.h"
  #include "chromeos/dbus/fake_cryptohome_client.h"
  #include "chromeos/dbus/fake_session_manager_client.h"
  #include "chromeos/dbus/session_manager_client.h"
  #include "chromeos/dbus/shill_manager_client.h"
  #include "chromeos/login/auth/key.h"
  #include "chromeos/settings/cros_settings_names.h"
  #include "components/guest_view/browser/test_guest_view_manager.h"
  #include "components/policy/core/browser/browser_policy_connector.h"
  #include "components/policy/core/common/mock_configuration_policy_provider.h"
  #include "components/policy/core/common/policy_map.h"
  #include "components/policy/core/common/policy_switches.h"
  #include "components/policy/core/common/policy_types.h"
  #include "components/policy/policy_constants.h"
  #include "components/policy/proto/device_management_backend.pb.h"
  #include "components/user_manager/user.h"
  #include "components/user_manager/user_manager.h"
  #include "content/public/browser/browser_thread.h"
  #include "content/public/browser/render_frame_host.h"
  #include "content/public/browser/web_contents.h"
  #include "content/public/browser/web_contents_observer.h"
  #include "content/public/test/browser_test_utils.h"
  #include "content/public/test/test_utils.h"
  #include "extensions/browser/guest_view/web_view/web_view_guest.h"
  #include "extensions/common/features/feature_channel.h"
  #include "google_apis/gaia/fake_gaia.h"
  #include "google_apis/gaia/gaia_constants.h"
  #include "google_apis/gaia/gaia_switches.h"
  #include "google_apis/gaia/gaia_urls.h"
  #include "net/base/url_util.h"
  #include "net/cookies/canonical_cookie.h"
  #include "net/cookies/cookie_store.h"
  #include "net/dns/mock_host_resolver.h"
  #include "net/test/embedded_test_server/embedded_test_server.h"
  #include "net/test/embedded_test_server/http_request.h"
  #include "net/test/embedded_test_server/http_response.h"
  #include "net/url_request/url_request_context.h"
  #include "net/url_request/url_request_context_getter.h"
  #include "testing/gmock/include/gmock/gmock.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "ui/base/l10n/l10n_util.h"
  #include "url/gurl.h"
  
  namespace em = enterprise_management;
  
  using net::test_server::BasicHttpResponse;
  using net::test_server::HttpRequest;
  using net::test_server::HttpResponse;
  using testing::Return;
  using testing::_;
  
  namespace chromeos {
  
  namespace {
  
  const char kGAIASIDCookieName[] = "SID";
  const char kGAIALSIDCookieName[] = "LSID";
  
  const char kTestAuthSIDCookie1[] = "fake-auth-SID-cookie-1";
  const char kTestAuthSIDCookie2[] = "fake-auth-SID-cookie-2";
  const char kTestAuthLSIDCookie1[] = "fake-auth-LSID-cookie-1";
  const char kTestAuthLSIDCookie2[] = "fake-auth-LSID-cookie-2";
  
  const char kFirstSAMLUserEmail[] = "bob@example.com";
  const char kSecondSAMLUserEmail[] = "alice@example.com";
  const char kHTTPSAMLUserEmail[] = "carol@example.com";
  const char kNonSAMLUserEmail[] = "dan@example.com";
  const char kDifferentDomainSAMLUserEmail[] = "eve@example.test";
  
  const char kIdPHost[] = "login.example.com";
  const char kAdditionalIdPHost[] = "login2.example.com";
  
  const char kSAMLIdPCookieName[] = "saml";
  const char kSAMLIdPCookieValue1[] = "value-1";
  const char kSAMLIdPCookieValue2[] = "value-2";
  
  const char kRelayState[] = "RelayState";
  
  const char kTestUserinfoToken[] = "fake-userinfo-token";
  const char kTestRefreshToken[] = "fake-refresh-token";
  const char kPolicy[] = "{\"managed_users\": [\"*\"]}";
  
  const char kAffiliationID[] = "some-affiliation-id";
  
  // FakeSamlIdp serves IdP auth form and the form submission. The form is
  // served with the template's RelayState placeholder expanded to the real
  // RelayState parameter from request. The form submission redirects back to
  // FakeGaia with the same RelayState.
  class FakeSamlIdp {
   public:
    FakeSamlIdp();
    ~FakeSamlIdp();
  
    void SetUp(const std::string& base_path, const GURL& gaia_url);
  
    void SetLoginHTMLTemplate(const std::string& template_file);
    void SetLoginAuthHTMLTemplate(const std::string& template_file);
    void SetRefreshURL(const GURL& refresh_url);
    void SetCookieValue(const std::string& cookie_value);
  
    std::unique_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
  
   private:
    std::unique_ptr<HttpResponse> BuildHTMLResponse(
        const std::string& html_template,
        const std::string& relay_state,
        const std::string& next_path);
  
    base::FilePath html_template_dir_;
  
    std::string login_path_;
    std::string login_auth_path_;
  
    std::string login_html_template_;
    std::string login_auth_html_template_;
    GURL gaia_assertion_url_;
    GURL refresh_url_;
    std::string cookie_value_;
  
    DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp);
  };
  
  FakeSamlIdp::FakeSamlIdp() {
  }
  
  FakeSamlIdp::~FakeSamlIdp() {
  }
  
  void FakeSamlIdp::SetUp(const std::string& base_path, const GURL& gaia_url) {
    base::FilePath test_data_dir;
    ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
    html_template_dir_ = test_data_dir.Append("login");
  
    login_path_ = base_path;
    login_auth_path_ = base_path + "Auth";
    gaia_assertion_url_ = gaia_url.Resolve("/SSO");
  }
  
  void FakeSamlIdp::SetLoginHTMLTemplate(const std::string& template_file) {
    EXPECT_TRUE(base::ReadFileToString(
        html_template_dir_.Append(template_file),
        &login_html_template_));
  }
  
  void FakeSamlIdp::SetLoginAuthHTMLTemplate(const std::string& template_file) {
    EXPECT_TRUE(base::ReadFileToString(
        html_template_dir_.Append(template_file),
        &login_auth_html_template_));
  }
  
  void FakeSamlIdp::SetRefreshURL(const GURL& refresh_url) {
    refresh_url_ = refresh_url;
  }
  
  void FakeSamlIdp::SetCookieValue(const std::string& cookie_value) {
    cookie_value_ = cookie_value;
  }
  
  std::unique_ptr<HttpResponse> FakeSamlIdp::HandleRequest(
      const HttpRequest& request) {
    // The scheme and host of the URL is actually not important but required to
    // get a valid GURL in order to parse |request.relative_url|.
    GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
    std::string request_path = request_url.path();
  
    if (request_path == login_path_) {
      std::string relay_state;
      net::GetValueForKeyInQuery(request_url, kRelayState, &relay_state);
      return BuildHTMLResponse(login_html_template_,
                               relay_state,
                               login_auth_path_);
    }
  
    if (request_path != login_auth_path_) {
      // Request not understood.
      return std::unique_ptr<HttpResponse>();
    }
  
    std::string relay_state;
    FakeGaia::GetQueryParameter(request.content, kRelayState, &relay_state);
    GURL redirect_url = gaia_assertion_url_;
  
    if (!login_auth_html_template_.empty()) {
      return BuildHTMLResponse(login_auth_html_template_,
                               relay_state,
                               redirect_url.spec());
    }
  
    redirect_url = net::AppendQueryParameter(
        redirect_url, "SAMLResponse", "fake_response");
    redirect_url = net::AppendQueryParameter(
        redirect_url, kRelayState, relay_state);
  
    std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
    http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
    http_response->AddCustomHeader("Location", redirect_url.spec());
    http_response->AddCustomHeader(
        "Set-cookie",
        base::StringPrintf("saml=%s", cookie_value_.c_str()));
    return std::move(http_response);
  }
  
  std::unique_ptr<HttpResponse> FakeSamlIdp::BuildHTMLResponse(
      const std::string& html_template,
      const std::string& relay_state,
      const std::string& next_path) {
    std::string response_html = html_template;
    base::ReplaceSubstringsAfterOffset(
        &response_html, 0, "$RelayState", relay_state);
    base::ReplaceSubstringsAfterOffset(
        &response_html, 0, "$Post", next_path);
    base::ReplaceSubstringsAfterOffset(
        &response_html, 0, "$Refresh", refresh_url_.spec());
  
    std::unique_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
    http_response->set_code(net::HTTP_OK);
    http_response->set_content(response_html);
    http_response->set_content_type("text/html");
  
    return std::move(http_response);
  }
  
  // A FakeCryptohomeClient that stores the salted and hashed secret passed to
  // MountEx().
  class SecretInterceptingFakeCryptohomeClient : public FakeCryptohomeClient {
   public:
    SecretInterceptingFakeCryptohomeClient();
  
    void MountEx(const cryptohome::Identification& id,
                 const cryptohome::AuthorizationRequest& auth,
                 const cryptohome::MountRequest& request,
                 const ProtobufMethodCallback& callback) override;
  
    const std::string& salted_hashed_secret() { return salted_hashed_secret_; }
  
   private:
    std::string salted_hashed_secret_;
  
    DISALLOW_COPY_AND_ASSIGN(SecretInterceptingFakeCryptohomeClient);
  };
  
  SecretInterceptingFakeCryptohomeClient::
      SecretInterceptingFakeCryptohomeClient() {
  }
  
  void SecretInterceptingFakeCryptohomeClient::MountEx(
      const cryptohome::Identification& id,
      const cryptohome::AuthorizationRequest& auth,
      const cryptohome::MountRequest& request,
      const ProtobufMethodCallback& callback) {
    salted_hashed_secret_ = auth.key().secret();
    FakeCryptohomeClient::MountEx(id, auth, request, callback);
  }
  
  }  // namespace
  
  class SamlTest : public OobeBaseTest {
   public:
    SamlTest() : cryptohome_client_(new SecretInterceptingFakeCryptohomeClient) {
      set_initialize_fake_merge_session(false);
    }
    ~SamlTest() override {}
  
    void SetUpCommandLine(base::CommandLine* command_line) override {
      command_line->AppendSwitch(switches::kOobeSkipPostLogin);
      command_line->AppendSwitch(
          chromeos::switches::kAllowFailedPolicyFetchForTest);
  
      const GURL gaia_url = gaia_https_forwarder_.GetURLForSSLHost("");
      const GURL saml_idp_url = saml_https_forwarder_.GetURLForSSLHost("SAML");
      fake_saml_idp_.SetUp(saml_idp_url.path(), gaia_url);
      fake_gaia_->RegisterSamlUser(kFirstSAMLUserEmail, saml_idp_url);
      fake_gaia_->RegisterSamlUser(kSecondSAMLUserEmail, saml_idp_url);
      fake_gaia_->RegisterSamlUser(
          kHTTPSAMLUserEmail,
          embedded_test_server()->base_url().Resolve("/SAML"));
      fake_gaia_->RegisterSamlUser(kDifferentDomainSAMLUserEmail, saml_idp_url);
      fake_gaia_->RegisterSamlDomainRedirectUrl("example.com", saml_idp_url);
  
      OobeBaseTest::SetUpCommandLine(command_line);
    }
  
    void SetUpInProcessBrowserTestFixture() override {
      DBusThreadManager::GetSetterForTesting()->SetCryptohomeClient(
          std::unique_ptr<CryptohomeClient>(cryptohome_client_));
  
      OobeBaseTest::SetUpInProcessBrowserTestFixture();
    }
  
    void SetUpOnMainThread() override {
      fake_gaia_->SetFakeMergeSessionParams(
          kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
  
      embedded_test_server()->RegisterRequestHandler(base::Bind(
          &FakeSamlIdp::HandleRequest, base::Unretained(&fake_saml_idp_)));
  
      OobeBaseTest::SetUpOnMainThread();
    }
  
    void SetupAuthFlowChangeListener() {
      ASSERT_TRUE(content::ExecuteScript(
          GetLoginUI()->GetWebContents(),
          "$('gaia-signin').gaiaAuthHost_.addEventListener('authFlowChange',"
              "function f() {"
                "$('gaia-signin').gaiaAuthHost_.removeEventListener("
                    "'authFlowChange', f);"
                "window.domAutomationController.setAutomationId(0);"
                "window.domAutomationController.send("
                    "$('gaia-signin').isSAML() ? 'SamlLoaded' : 'GaiaLoaded');"
              "});"));
    }
  
    virtual void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) {
      WaitForSigninScreen();
  
      SetupAuthFlowChangeListener();
  
      content::DOMMessageQueue message_queue;  // Start observe before SAML.
      GetLoginDisplay()->ShowSigninScreenForCreds(gaia_email, "");
  
      std::string message;
      ASSERT_TRUE(message_queue.WaitForMessage(&message));
      EXPECT_EQ("\"SamlLoaded\"", message);
    }
  
    void SendConfirmPassword(const std::string& password_to_confirm) {
      std::string js =
          "$('saml-confirm-password').$.passwordInput.value='$Password';"
          "$('saml-confirm-password').$.inputForm.submit();";
      base::ReplaceSubstringsAfterOffset(
          &js, 0, "$Password", password_to_confirm);
      ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js));
    }
  
    void SetManualPasswords(const std::string& password,
                            const std::string& confirm_password) {
      std::string js =
          "$('saml-confirm-password').$.passwordInput.value='$Password';"
          "$('saml-confirm-password').$$('#confirmPasswordInput').value="
          "    '$ConfirmPassword';"
          "$('saml-confirm-password').$.inputForm.submit();";
      base::ReplaceSubstringsAfterOffset(&js, 0, "$Password", password);
      base::ReplaceSubstringsAfterOffset(&js, 0, "$ConfirmPassword",
                                         confirm_password);
      ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js));
    }
  
    std::string WaitForAndGetFatalErrorMessage() {
      OobeScreenWaiter(OobeScreen::SCREEN_FATAL_ERROR).Wait();
      std::string message_element = "$('fatal-error-card')";
      std::string error_message;
      if (!content::ExecuteScriptAndExtractString(
              GetLoginUI()->GetWebContents(),
              "window.domAutomationController.send(" + message_element +
                  ".textContent);",
              &error_message)) {
        ADD_FAILURE();
      }
      return error_message;
    }
  
    FakeSamlIdp* fake_saml_idp() { return &fake_saml_idp_; }
  
   protected:
    void InitHttpsForwarders() override {
      ASSERT_TRUE(saml_https_forwarder_.Initialize(
          kIdPHost, embedded_test_server()->base_url()));
      OobeBaseTest::InitHttpsForwarders();
    }
  
    HTTPSForwarder saml_https_forwarder_;
  
    SecretInterceptingFakeCryptohomeClient* cryptohome_client_;
  
   private:
    FakeSamlIdp fake_saml_idp_;
  
    DISALLOW_COPY_AND_ASSIGN(SamlTest);
  };
  
  // Tests that signin frame should have 'saml' class and 'cancel' button is
  // visible when SAML IdP page is loaded. And 'cancel' button goes back to
  // gaia on clicking.
  //
  // Times out on CrOS MSAN. https://crbug.com/504141
  #if defined(MEMORY_SANITIZER)
  #define MAYBE_SamlUI DISABLED_SamlUI
  #else
  #define MAYBE_SamlUI SamlUI
  #endif
  IN_PROC_BROWSER_TEST_F(SamlTest, MAYBE_SamlUI) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Saml flow UI expectations.
    JsExpect("$('gaia-signin').classList.contains('full-width')");
    JsExpect("!$('saml-notice-container').hidden");
    std::string js = "$('saml-notice-message').textContent.indexOf('$Host') > -1";
    base::ReplaceSubstringsAfterOffset(&js, 0, "$Host", kIdPHost);
    JsExpect(js);
  
    SetupAuthFlowChangeListener();
  
    // Click on 'cancel'.
    content::DOMMessageQueue message_queue;  // Observe before 'cancel'.
    ASSERT_TRUE(
        content::ExecuteScript(GetLoginUI()->GetWebContents(),
                               "$('gaia-navigation').$.closeButton.click();"));
  
    // Auth flow should change back to Gaia.
    std::string message;
    do {
      ASSERT_TRUE(message_queue.WaitForMessage(&message));
    } while (message != "\"GaiaLoaded\"");
  
    // Saml flow is gone.
    JsExpect("!$('gaia-signin').classList.contains('full-width')");
  }
  
  // Tests the sign-in flow when the credentials passing API is used.
  //
  // Flaky. See http://crbug.com/659992
  #if defined(OS_LINUX)
  #define MAYBE_CredentialPassingAPI DISABLED_CredentialPassingAPI
  #else
  #define MAYBE_CredentialPassingAPI CredentialPassingAPI
  #endif
  IN_PROC_BROWSER_TEST_F(SamlTest, MAYBE_CredentialPassingAPI) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
    fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Dummy", "not_the_password");
    SetSignFormField("Password", "actual_password");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    // Login should finish login and a session should start.
    session_start_waiter.Wait();
  
    // Regression test for http://crbug.com/490737: Verify that the user's actual
    // password was used, not the contents of the first type=password input field
    // found on the page.
    Key key("actual_password");
    key.Transform(Key::KEY_TYPE_SALTED_SHA256_TOP_HALF,
                  SystemSaltGetter::ConvertRawSaltToHexString(
                      FakeCryptohomeClient::GetStubSystemSalt()));
    EXPECT_EQ(key.GetSecret(), cryptohome_client_->salted_hashed_secret());
  }
  
  // Tests the single password scraped flow.
  IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedSingle) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Make sure that the password is scraped correctly.
    ASSERT_TRUE(content::ExecuteScript(
        GetLoginUI()->GetWebContents(),
        "$('gaia-signin').gaiaAuthHost_.addEventListener('authCompleted',"
        "    function(e) {"
        "      var password = e.detail.password;"
        "      window.domAutomationController.setAutomationId(0);"
        "      window.domAutomationController.send(password);"
        "    });"));
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
  
    // Scraping a single password should finish the login and start the session.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    content::DOMMessageQueue message_queue;
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
    std::string message;
    do {
      ASSERT_TRUE(message_queue.WaitForMessage(&message));
    } while (message != "\"fake_password\"");
  
    session_start_waiter.Wait();
  }
  
  // Tests password scraping from a dynamically created password field.
  IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedDynamic) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    ExecuteJsInSigninFrame(
      "(function() {"
        "var newPassInput = document.createElement('input');"
        "newPassInput.id = 'DynamicallyCreatedPassword';"
        "newPassInput.type = 'password';"
        "newPassInput.name = 'Password';"
        "document.forms[0].appendChild(newPassInput);"
      "})();");
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("DynamicallyCreatedPassword", "fake_password");
  
    // Scraping a single password should finish the login and start the session.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
    session_start_waiter.Wait();
  }
  
  // Tests the multiple password scraped flow.
  IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedMultiple) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html");
  
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
    SetSignFormField("Password1", "password1");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    // Lands on confirm password screen.
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("!$('saml-confirm-password').manualInput");
  
    // Entering an unknown password should go back to the confirm password screen.
    SendConfirmPassword("wrong_password");
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("!$('saml-confirm-password').manualInput");
  
    // Either scraped password should be able to sign-in.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    SendConfirmPassword("password1");
    session_start_waiter.Wait();
  }
  
  // Tests the no password scraped flow.
  IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedNone) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login_no_passwords.html");
  
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    SetSignFormField("Email", "fake_user");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    // Lands on confirm password screen with manual input state.
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("$('saml-confirm-password').manualInput");
    // Entering passwords that don't match will make us land again in the same
    // page.
    SetManualPasswords("Test1", "Test2");
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("$('saml-confirm-password').manualInput");
  
    // Two matching passwords should let the user to sign in.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    SetManualPasswords("Test1", "Test1");
    session_start_waiter.Wait();
  }
  
  // Types |bob@example.com| into the GAIA login form but then authenticates as
  // |alice@example.com| via SAML. Verifies that the logged-in user is correctly
  // identified as Alice.
  IN_PROC_BROWSER_TEST_F(SamlTest, UseAutenticatedUserEmailAddress) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    // Type |bob@example.com| into the GAIA login form.
    StartSamlAndWaitForIdpPageLoad(kSecondSAMLUserEmail);
  
    // Authenticate as alice@example.com via SAML (the |Email| provided here is
    // irrelevant - the authenticated user's e-mail address that FakeGAIA
    // reports was set via |SetFakeMergeSessionParams|.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
  
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
    session_start_waiter.Wait();
  
    const user_manager::User* user =
        user_manager::UserManager::Get()->GetActiveUser();
    ASSERT_TRUE(user);
    EXPECT_EQ(kFirstSAMLUserEmail, user->GetAccountId().GetUserEmail());
  }
  
  // Verifies that if the authenticated user's e-mail address cannot be retrieved,
  // an error message is shown.
  IN_PROC_BROWSER_TEST_F(SamlTest, FailToRetrieveAutenticatedUserEmailAddress) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    fake_gaia_->SetFakeMergeSessionParams("", kTestAuthSIDCookie1,
                                          kTestAuthLSIDCookie1);
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    EXPECT_EQ(l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_NO_ACCOUNT_DETAILS),
              WaitForAndGetFatalErrorMessage());
  }
  
  // Tests the password confirm flow when more than one password is scraped: show
  // error on the first failure and fatal error on the second failure.
  IN_PROC_BROWSER_TEST_F(SamlTest, PasswordConfirmFlow) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
    SetSignFormField("Password1", "password1");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    // Lands on confirm password screen with no error message.
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("!$('saml-confirm-password').manualInput");
    JsExpect("!$('saml-confirm-password').$.passwordInput.isInvalid");
  
    // Enter an unknown password for the first time should go back to confirm
    // password screen with error message.
    SendConfirmPassword("wrong_password");
    OobeScreenWaiter(OobeScreen::SCREEN_CONFIRM_PASSWORD).Wait();
    JsExpect("!$('saml-confirm-password').manualInput");
    JsExpect("$('saml-confirm-password').$.passwordInput.isInvalid");
  
    // Enter an unknown password 2nd time should go back fatal error message.
    SendConfirmPassword("wrong_password");
    EXPECT_EQ(
        l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_PASSWORD_VERIFICATION),
        WaitForAndGetFatalErrorMessage());
  }
  
  // Verifies that when the login flow redirects from one host to another, the
  // notice shown to the user is updated. This guards against regressions of
  // http://crbug.com/447818.
  IN_PROC_BROWSER_TEST_F(SamlTest, NoticeUpdatedOnRedirect) {
    // Start another https server at |kAdditionalIdPHost|.
    HTTPSForwarder saml_https_forwarder_2;
    ASSERT_TRUE(saml_https_forwarder_2.Initialize(
        kAdditionalIdPHost, embedded_test_server()->base_url()));
  
    // Make the login flow redirect to |kAdditionalIdPHost|.
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login_instant_meta_refresh.html");
    fake_saml_idp()->SetRefreshURL(
        saml_https_forwarder_2.GetURLForSSLHost("simple.html"));
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Wait until the notice shown to the user is updated to contain
    // |kAdditionalIdPHost|.
    std::string js =
        "var sendIfHostFound = function() {"
        "  var found ="
        "      $('saml-notice-message').textContent.indexOf('$Host') > -1;"
        "  if (found)"
        "    window.domAutomationController.send(true);"
        "  return found;"
        "};"
        "var processEventsAndSendIfHostFound = function() {"
        "  window.setTimeout(function() {"
        "    if (sendIfHostFound()) {"
        "      $('gaia-signin').gaiaAuthHost_.removeEventListener("
        "          'authDomainChange',"
        "          processEventsAndSendIfHostFound);"
        "    }"
        "  }, 0);"
        "};"
        "if (!sendIfHostFound()) {"
        "  $('gaia-signin').gaiaAuthHost_.addEventListener("
        "      'authDomainChange',"
        "      processEventsAndSendIfHostFound);"
        "}";
    base::ReplaceSubstringsAfterOffset(&js, 0, "$Host", kAdditionalIdPHost);
    bool dummy;
    EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
        GetLoginUI()->GetWebContents(), js, &dummy));
  
    // Verify that the notice is visible.
    JsExpect("!$('saml-notice-container').hidden");
  }
  
  // Verifies that when GAIA attempts to redirect to a SAML IdP served over http,
  // not https, the redirect is blocked and an error message is shown.
  IN_PROC_BROWSER_TEST_F(SamlTest, HTTPRedirectDisallowed) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
  
    WaitForSigninScreen();
    GetLoginDisplay()->ShowSigninScreenForCreds(kHTTPSAMLUserEmail, "");
  
    const GURL url = embedded_test_server()->base_url().Resolve("/SAML");
    EXPECT_EQ(l10n_util::GetStringFUTF8(
                  IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL,
                  base::UTF8ToUTF16(url.spec())),
              WaitForAndGetFatalErrorMessage());
  }
  
  // Verifies that when GAIA attempts to redirect to a page served over http, not
  // https, via an HTML meta refresh, the redirect is blocked and an error message
  // is shown. This guards against regressions of http://crbug.com/359515.
  IN_PROC_BROWSER_TEST_F(SamlTest, MetaRefreshToHTTPDisallowed) {
    const GURL url = embedded_test_server()->base_url().Resolve("/SSO");
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login_instant_meta_refresh.html");
    fake_saml_idp()->SetRefreshURL(url);
  
    WaitForSigninScreen();
    GetLoginDisplay()->ShowSigninScreenForCreds(kFirstSAMLUserEmail, "");
  
    EXPECT_EQ(l10n_util::GetStringFUTF8(
                  IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL,
                  base::UTF8ToUTF16(url.spec())),
              WaitForAndGetFatalErrorMessage());
  }
  
  class SAMLEnrollmentTest : public SamlTest,
                             public content::WebContentsObserver {
   public:
    SAMLEnrollmentTest();
    ~SAMLEnrollmentTest() override;
  
    // SamlTest:
    void SetUp() override;
    void SetUpCommandLine(base::CommandLine* command_line) override;
    void SetUpOnMainThread() override;
    void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) override;
  
    // content::WebContentsObserver:
    void DidFinishLoad(content::RenderFrameHost* render_frame_host,
                       const GURL& validated_url) override;
  
    void WaitForEnrollmentSuccess();
    guest_view::TestGuestViewManager* GetGuestViewManager();
    content::WebContents* GetEnrollmentContents();
  
   private:
    std::unique_ptr<policy::LocalPolicyTestServer> test_server_;
    base::ScopedTempDir temp_dir_;
  
    std::unique_ptr<base::RunLoop> run_loop_;
  
    guest_view::TestGuestViewManagerFactory guest_view_manager_factory_;
  
    DISALLOW_COPY_AND_ASSIGN(SAMLEnrollmentTest);
  };
  
  SAMLEnrollmentTest::SAMLEnrollmentTest() {
    guest_view::GuestViewManager::set_factory_for_testing(
        &guest_view_manager_factory_);
    gaia_frame_parent_ = "oauth-enroll-auth-view";
  }
  
  SAMLEnrollmentTest::~SAMLEnrollmentTest() {
  }
  
  void SAMLEnrollmentTest::SetUp() {
    ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
    const base::FilePath policy_file =
        temp_dir_.GetPath().AppendASCII("policy.json");
    ASSERT_EQ(static_cast<int>(strlen(kPolicy)),
              base::WriteFile(policy_file, kPolicy, strlen(kPolicy)));
  
    test_server_.reset(new policy::LocalPolicyTestServer(policy_file));
    ASSERT_TRUE(test_server_->Start());
  
    SamlTest::SetUp();
  }
  
  void SAMLEnrollmentTest::SetUpCommandLine(base::CommandLine* command_line) {
    command_line->AppendSwitchASCII(policy::switches::kDeviceManagementUrl,
                                    test_server_->GetServiceURL().spec());
    command_line->AppendSwitch(policy::switches::kDisablePolicyKeyVerification);
  
    SamlTest::SetUpCommandLine(command_line);
  }
  
  void SAMLEnrollmentTest::SetUpOnMainThread() {
    FakeGaia::AccessTokenInfo token_info;
    token_info.token = kTestUserinfoToken;
    token_info.scopes.insert(GaiaConstants::kDeviceManagementServiceOAuth);
    token_info.scopes.insert(GaiaConstants::kOAuthWrapBridgeUserInfoScope);
    token_info.audience = GaiaUrls::GetInstance()->oauth2_chrome_client_id();
    token_info.email = kFirstSAMLUserEmail;
    fake_gaia_->IssueOAuthToken(kTestRefreshToken, token_info);
  
    SamlTest::SetUpOnMainThread();
  }
  
  void SAMLEnrollmentTest::StartSamlAndWaitForIdpPageLoad(
      const std::string& gaia_email) {
    WaitForSigninScreen();
    run_loop_.reset(new base::RunLoop);
    ExistingUserController::current_controller()->OnStartEnterpriseEnrollment();
    while (!GetEnrollmentContents()) {
      GetGuestViewManager()->WaitForNextGuestCreated();
    }
    Observe(GetEnrollmentContents());
    run_loop_->Run();
  
    SetSignFormField("identifier", gaia_email);
  
    run_loop_.reset(new base::RunLoop);
    ExecuteJsInSigninFrame("document.getElementById('nextButton').click();");
    run_loop_->Run();
  }
  
  void SAMLEnrollmentTest::DidFinishLoad(
      content::RenderFrameHost* render_frame_host,
      const GURL& validated_url) {
    const GURL origin = validated_url.GetOrigin();
    if (origin != gaia_https_forwarder_.GetURLForSSLHost(std::string()) &&
        origin != saml_https_forwarder_.GetURLForSSLHost(std::string())) {
      return;
    }
  
    // The GAIA or SAML IdP login form finished loading.
    if (run_loop_)
      run_loop_->Quit();
  }
  
  // Waits until the class |oauth-enroll-state-success| becomes set for the
  // enrollment screen, indicating enrollment success.
  void SAMLEnrollmentTest::WaitForEnrollmentSuccess() {
    bool done = false;
    ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
        GetLoginUI()->GetWebContents(),
        "var enrollmentScreen = document.getElementById('oauth-enrollment');"
        "function SendReplyIfEnrollmentDone() {"
        "  if (enrollmentScreen.classList.contains("
        "          'oauth-enroll-state-attribute-prompt')) {"
        "    $('oauth-enroll-attribute-prompt-card').fire('submit');"
        "    return false;"
        "  }"
        "  if (!enrollmentScreen.classList.contains("
        "           'oauth-enroll-state-success')) {"
        "    return false;"
        "  }"
        "  domAutomationController.send(true);"
        "  observer.disconnect();"
        "  return true;"
        "}"
        "var observer = new MutationObserver(SendReplyIfEnrollmentDone);"
        "if (!SendReplyIfEnrollmentDone()) {"
        "  var options = { attributes: true, attributeFilter: [ 'class' ] };"
        "  observer.observe(enrollmentScreen, options);"
        "}",
        &done));
  }
  
  guest_view::TestGuestViewManager* SAMLEnrollmentTest::GetGuestViewManager() {
    using namespace guest_view;
    return static_cast<TestGuestViewManager*>(
        TestGuestViewManager::FromBrowserContext(
            ProfileHelper::GetSigninProfile()));
  }
  
  content::WebContents* SAMLEnrollmentTest::GetEnrollmentContents() {
    content::RenderFrameHost* frame_host =
        signin::GetAuthFrame(GetLoginUI()->GetWebContents(), gaia_frame_parent_);
    if (!frame_host)
      return nullptr;
    return content::WebContents::FromRenderFrameHost(frame_host);
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLEnrollmentTest, WithoutCredentialsPassingAPI) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    WaitForEnrollmentSuccess();
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLEnrollmentTest, WithCredentialsPassingAPI) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
    fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html");
    StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
  
    // Fill-in the SAML IdP form and submit.
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
  
    WaitForEnrollmentSuccess();
  }
  
  class SAMLPolicyTest : public SamlTest {
   public:
    SAMLPolicyTest();
    ~SAMLPolicyTest() override;
  
    // SamlTest:
    void SetUpInProcessBrowserTestFixture() override;
    void SetUpOnMainThread() override;
  
    void SetSAMLOfflineSigninTimeLimitPolicy(int limit);
    void EnableTransferSAMLCookiesPolicy();
    void SetLoginBehaviorPolicyToSAMLInterstitial();
  
    void ShowGAIALoginForm();
    void ShowSAMLInterstitial();
    void ClickNextOnSAMLInterstitialPage();
    void ClickChangeAccountOnSAMLInterstitialPage();
    void LogInWithSAML(const std::string& user_id,
                       const std::string& auth_sid_cookie,
                       const std::string& auth_lsid_cookie);
  
    std::string GetCookieValue(const std::string& name);
  
    void GetCookies();
  
   protected:
    void GetCookiesOnIOThread(
        const scoped_refptr<net::URLRequestContextGetter>& request_context,
        const base::Closure& callback);
    void StoreCookieList(const base::Closure& callback,
                         const net::CookieList& cookie_list);
  
    policy::DevicePolicyCrosTestHelper test_helper_;
  
    // FakeDBusThreadManager uses FakeSessionManagerClient.
    FakeSessionManagerClient* fake_session_manager_client_;
    policy::DevicePolicyBuilder* device_policy_;
  
    policy::MockConfigurationPolicyProvider provider_;
  
    net::CookieList cookie_list_;
  
   private:
    DISALLOW_COPY_AND_ASSIGN(SAMLPolicyTest);
  };
  
  SAMLPolicyTest::SAMLPolicyTest()
      : fake_session_manager_client_(new FakeSessionManagerClient),
        device_policy_(test_helper_.device_policy()) {
  }
  
  SAMLPolicyTest::~SAMLPolicyTest() {
  }
  
  void SAMLPolicyTest::SetUpInProcessBrowserTestFixture() {
    DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient(
        std::unique_ptr<SessionManagerClient>(fake_session_manager_client_));
  
    SamlTest::SetUpInProcessBrowserTestFixture();
  
    // Initialize device policy.
    std::set<std::string> device_affiliation_ids;
    device_affiliation_ids.insert(kAffiliationID);
    policy::affiliation_test_helper::SetDeviceAffiliationID(
        &test_helper_, fake_session_manager_client_, device_affiliation_ids);
  
    // Initialize user policy.
    EXPECT_CALL(provider_, IsInitializationComplete(_))
        .WillRepeatedly(Return(true));
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
  }
  
  void SAMLPolicyTest::SetUpOnMainThread() {
    SamlTest::SetUpOnMainThread();
  
    // Pretend that the test users' OAuth tokens are valid.
    user_manager::UserManager::Get()->SaveUserOAuthStatus(
        AccountId::FromUserEmail(kFirstSAMLUserEmail),
        user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
    user_manager::UserManager::Get()->SaveUserOAuthStatus(
        AccountId::FromUserEmail(kNonSAMLUserEmail),
        user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
    user_manager::UserManager::Get()->SaveUserOAuthStatus(
        AccountId::FromUserEmail(kDifferentDomainSAMLUserEmail),
        user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
  
    // Give affiliated users appropriate affiliation IDs.
    std::set<std::string> user_affiliation_ids;
    user_affiliation_ids.insert(kAffiliationID);
    chromeos::ChromeUserManager::Get()->SetUserAffiliation(kFirstSAMLUserEmail,
                                                           user_affiliation_ids);
    chromeos::ChromeUserManager::Get()->SetUserAffiliation(kSecondSAMLUserEmail,
                                                           user_affiliation_ids);
    chromeos::ChromeUserManager::Get()->SetUserAffiliation(kHTTPSAMLUserEmail,
                                                           user_affiliation_ids);
    chromeos::ChromeUserManager::Get()->SetUserAffiliation(kNonSAMLUserEmail,
                                                           user_affiliation_ids);
  
    // Set up fake networks.
    DBusThreadManager::Get()
        ->GetShillManagerClient()
        ->GetTestInterface()
        ->SetupDefaultEnvironment();
  }
  
  void SAMLPolicyTest::SetSAMLOfflineSigninTimeLimitPolicy(int limit) {
    policy::PolicyMap user_policy;
    user_policy.Set(policy::key::kSAMLOfflineSigninTimeLimit,
                    policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                    policy::POLICY_SOURCE_CLOUD,
                    base::MakeUnique<base::FundamentalValue>(limit), nullptr);
    provider_.UpdateChromePolicy(user_policy);
    base::RunLoop().RunUntilIdle();
  }
  
  void SAMLPolicyTest::EnableTransferSAMLCookiesPolicy() {
    em::ChromeDeviceSettingsProto& proto(device_policy_->payload());
    proto.mutable_saml_settings()->set_transfer_saml_cookies(true);
  
    base::RunLoop run_loop;
    std::unique_ptr<CrosSettings::ObserverSubscription> observer =
        CrosSettings::Get()->AddSettingsObserver(kAccountsPrefTransferSAMLCookies,
                                                 run_loop.QuitClosure());
    device_policy_->SetDefaultSigningKey();
    device_policy_->Build();
    fake_session_manager_client_->set_device_policy(device_policy_->GetBlob());
    fake_session_manager_client_->OnPropertyChangeComplete(true);
    run_loop.Run();
  }
  
  void SAMLPolicyTest::SetLoginBehaviorPolicyToSAMLInterstitial() {
    em::ChromeDeviceSettingsProto& proto(device_policy_->payload());
    proto.mutable_login_authentication_behavior()
        ->set_login_authentication_behavior(
            em::LoginAuthenticationBehaviorProto_LoginBehavior_SAML_INTERSTITIAL);
  
    base::RunLoop run_loop;
    std::unique_ptr<CrosSettings::ObserverSubscription> observer =
        CrosSettings::Get()->AddSettingsObserver(kLoginAuthenticationBehavior,
                                                 run_loop.QuitClosure());
    device_policy_->SetDefaultSigningKey();
    device_policy_->Build();
    fake_session_manager_client_->set_device_policy(device_policy_->GetBlob());
    fake_session_manager_client_->OnPropertyChangeComplete(true);
    run_loop.Run();
  }
  
  void SAMLPolicyTest::ShowGAIALoginForm() {
    login_screen_load_observer_->Wait();
    ASSERT_TRUE(content::ExecuteScript(
        GetLoginUI()->GetWebContents(),
        "$('gaia-signin').gaiaAuthHost_.addEventListener('ready', function() {"
        "  window.domAutomationController.setAutomationId(0);"
        "  window.domAutomationController.send('ready');"
        "});"
        "$('add-user-button').click();"));
    content::DOMMessageQueue message_queue;
    std::string message;
    ASSERT_TRUE(message_queue.WaitForMessage(&message));
    EXPECT_EQ("\"ready\"", message);
  }
  
  void SAMLPolicyTest::ShowSAMLInterstitial() {
    login_screen_load_observer_->Wait();
    ASSERT_TRUE(content::ExecuteScript(
        GetLoginUI()->GetWebContents(),
        "$('saml-interstitial').addEventListener("
        "    'samlInterstitialPageReady',"
        "    function() {"
        "        window.domAutomationController.setAutomationId(0);"
        "        window.domAutomationController.send("
        "            'samlInterstitialPageReady');"
        "    });"
        "$('add-user-button').click();"));
  
    content::DOMMessageQueue message_queue;
    std::string message;
    ASSERT_TRUE(message_queue.WaitForMessage(&message));
    EXPECT_EQ("\"samlInterstitialPageReady\"", message);
  }
  
  void SAMLPolicyTest::ClickNextOnSAMLInterstitialPage() {
    login_screen_load_observer_->Wait();
  
    content::DOMMessageQueue message_queue;
    SetupAuthFlowChangeListener();
  
    ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(),
                                       "$('saml-interstitial').submit();"));
  
    std::string message;
    do {
      ASSERT_TRUE(message_queue.WaitForMessage(&message));
    } while (message != "\"SamlLoaded\"");
    EXPECT_EQ("\"SamlLoaded\"", message);
  }
  
  void SAMLPolicyTest::ClickChangeAccountOnSAMLInterstitialPage() {
    login_screen_load_observer_->Wait();
    ASSERT_TRUE(content::ExecuteScript(
        GetLoginUI()->GetWebContents(),
        "$('gaia-signin').gaiaAuthHost_.addEventListener('ready', function() {"
        "  window.domAutomationController.setAutomationId(0);"
        "  window.domAutomationController.send('ready');"
        "});"
        "$('saml-interstitial').changeAccountLink.click();"));
  
    content::DOMMessageQueue message_queue;
    std::string message;
    ASSERT_TRUE(message_queue.WaitForMessage(&message));
    EXPECT_EQ("\"ready\"", message);
  }
  
  void SAMLPolicyTest::LogInWithSAML(const std::string& user_id,
                                     const std::string& auth_sid_cookie,
                                     const std::string& auth_lsid_cookie) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    StartSamlAndWaitForIdpPageLoad(user_id);
  
    fake_gaia_->SetFakeMergeSessionParams(user_id, auth_sid_cookie,
                                          auth_lsid_cookie);
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
  
    // Scraping a single password should finish the login right away.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
    session_start_waiter.Wait();
  }
  
  std::string SAMLPolicyTest::GetCookieValue(const std::string& name) {
    for (net::CookieList::const_iterator it = cookie_list_.begin();
         it != cookie_list_.end(); ++it) {
      if (it->Name() == name)
        return it->Value();
    }
    return std::string();
  }
  
  void SAMLPolicyTest::GetCookies() {
    Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe(
        user_manager::UserManager::Get()->GetActiveUser());
    ASSERT_TRUE(profile);
    base::RunLoop run_loop;
    content::BrowserThread::PostTask(
        content::BrowserThread::IO,
        FROM_HERE,
        base::Bind(&SAMLPolicyTest::GetCookiesOnIOThread,
                   base::Unretained(this),
                   scoped_refptr<net::URLRequestContextGetter>(
                       profile->GetRequestContext()),
                   run_loop.QuitClosure()));
    run_loop.Run();
  }
  
  void SAMLPolicyTest::GetCookiesOnIOThread(
      const scoped_refptr<net::URLRequestContextGetter>& request_context,
      const base::Closure& callback) {
    request_context->GetURLRequestContext()->cookie_store()->GetAllCookiesAsync(
        base::Bind(&SAMLPolicyTest::StoreCookieList, base::Unretained(this),
                   callback));
  }
  
  void SAMLPolicyTest::StoreCookieList(
      const base::Closure& callback,
      const net::CookieList& cookie_list) {
    cookie_list_ = cookie_list;
    content::BrowserThread::PostTask(content::BrowserThread::UI,
                                     FROM_HERE,
                                     callback);
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_NoSAML) {
    // Set the offline login time limit for SAML users to zero.
    SetSAMLOfflineSigninTimeLimitPolicy(0);
  
    WaitForSigninScreen();
  
    // Log in without SAML.
    GetLoginDisplay()->ShowSigninScreenForCreds(kNonSAMLUserEmail, "password");
  
    content::WindowedNotificationObserver(
      chrome::NOTIFICATION_SESSION_STARTED,
      content::NotificationService::AllSources()).Wait();
  }
  
  // Verifies that the offline login time limit does not affect a user who
  // authenticated without SAML.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, NoSAML) {
    login_screen_load_observer_->Wait();
    // Verify that offline login is allowed.
    JsExpect(
        "window.getComputedStyle(document.querySelector("
        "    '#pod-row .reauth-hint-container')).display == 'none'");
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLNoLimit) {
    // Remove the offline login time limit for SAML users.
    SetSAMLOfflineSigninTimeLimitPolicy(-1);
  
    LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
  }
  
  // Verifies that when no offline login time limit is set, a user who
  // authenticated with SAML is allowed to log in offline.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLNoLimit) {
    login_screen_load_observer_->Wait();
    // Verify that offline login is allowed.
    JsExpect(
        "window.getComputedStyle(document.querySelector("
        "    '#pod-row .reauth-hint-container')).display == 'none'");
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLZeroLimit) {
    // Set the offline login time limit for SAML users to zero.
    SetSAMLOfflineSigninTimeLimitPolicy(0);
  
    LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
  }
  
  // Verifies that when the offline login time limit is exceeded for a user who
  // authenticated via SAML, that user is forced to log in online the next time.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLZeroLimit) {
    login_screen_load_observer_->Wait();
    // Verify that offline login is not allowed.
    JsExpect(
        "window.getComputedStyle(document.querySelector("
        "    '#pod-row .reauth-hint-container')).display != 'none'");
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_PRE_TransferCookiesAffiliated) {
    fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1);
    LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
  
    GetCookies();
    EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName));
    EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName));
    EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName));
  }
  
  // Verifies that when the DeviceTransferSAMLCookies policy is not enabled, SAML
  // IdP cookies are not transferred to a user's profile on subsequent login, even
  // if the user belongs to the domain that the device is enrolled into. Also
  // verifies that GAIA cookies are not transferred.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesAffiliated) {
    fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    ShowGAIALoginForm();
    LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie2, kTestAuthLSIDCookie2);
  
    GetCookies();
    EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName));
    EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName));
    EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName));
  }
  
  // Verifies that when the DeviceTransferSAMLCookies policy is enabled, SAML IdP
  // cookies are transferred to a user's profile on subsequent login when the user
  // belongs to the domain that the device is enrolled into. Also verifies that
  // GAIA cookies are not transferred.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesAffiliated) {
    fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    ShowGAIALoginForm();
  
    EnableTransferSAMLCookiesPolicy();
    LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie2, kTestAuthLSIDCookie2);
  
    GetCookies();
    EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName));
    EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName));
    EXPECT_EQ(kSAMLIdPCookieValue2, GetCookieValue(kSAMLIdPCookieName));
  }
  
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesUnaffiliated) {
    fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1);
    LogInWithSAML(kDifferentDomainSAMLUserEmail,
                  kTestAuthSIDCookie1,
                  kTestAuthLSIDCookie1);
  
    GetCookies();
    EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName));
    EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName));
    EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName));
  }
  
  // Verifies that even if the DeviceTransferSAMLCookies policy is enabled, SAML
  // IdP are not transferred to a user's profile on subsequent login if the user
  // does not belong to the domain that the device is enrolled into. Also verifies
  // that GAIA cookies are not transferred.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesUnaffiliated) {
    fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    ShowGAIALoginForm();
  
    EnableTransferSAMLCookiesPolicy();
    LogInWithSAML(kDifferentDomainSAMLUserEmail,
                  kTestAuthSIDCookie1,
                  kTestAuthLSIDCookie1);
  
    GetCookies();
    EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName));
    EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName));
    EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName));
  }
  
  // Tests that the SAML interstitial page is loaded when the authentication
  // behavior device policy is set to SAML_INTERSTITIAL, and when the user clicks
  // the "change account" link, we go back to the default GAIA signin screen.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLInterstitialChangeAccount) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    SetLoginBehaviorPolicyToSAMLInterstitial();
    WaitForSigninScreen();
  
    ShowSAMLInterstitial();
    JsExpect("$('signin-frame').hidden == true");
    JsExpect("$('offline-gaia').hidden == true");
    JsExpect("$('saml-interstitial').hidden == false");
  
    // Click the "change account" link on the SAML interstitial page.
    ClickChangeAccountOnSAMLInterstitialPage();
  
    // Expects that only the gaia signin frame is visible and shown.
    JsExpect("$('signin-frame').classList.contains('show')");
    JsExpect("$('signin-frame').hidden == false");
    JsExpect("$('offline-gaia').hidden == true");
    JsExpect("$('saml-interstitial').hidden == true");
  }
  
  // Tests that clicking "Next" in the SAML interstitial page successfully
  // triggers a SAML redirect request, and the SAML IdP authentication page is
  // loaded and authenticaing there is successful.
  IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLInterstitialNext) {
    fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
    fake_gaia_->SetFakeMergeSessionParams(
        kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1);
    SetLoginBehaviorPolicyToSAMLInterstitial();
    WaitForSigninScreen();
  
    ShowSAMLInterstitial();
    ClickNextOnSAMLInterstitialPage();
  
    SetSignFormField("Email", "fake_user");
    SetSignFormField("Password", "fake_password");
  
    // Scraping one password should finish login.
    content::WindowedNotificationObserver session_start_waiter(
        chrome::NOTIFICATION_SESSION_STARTED,
        content::NotificationService::AllSources());
    ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
    session_start_waiter.Wait();
  }
  
  }  // namespace chromeos
  