Wait for report result prior to resetting internal state for the scenario
where login attempt using CGaiaCredential is performed.

Bug: 973231
Change-Id: Ib062717efd220d558d849c0c7d2965e56ebbdc55
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1652715
Commit-Queue: Rakesh Soma <rakeshsoma@google.com>
Reviewed-by: Tien Mai <tienmai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#669485}
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index 4888f69b..5001663a 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -632,6 +632,7 @@
   LOGFN(INFO);
   username_.Empty();
   domain_.Empty();
+  wait_for_report_result_ = false;
 
   SecurelyClearBuffer((BSTR)password_, password_.ByteLength());
   password_.Empty();
@@ -940,14 +941,22 @@
 HRESULT CGaiaCredentialBase::SetDeselected(void) {
   LOGFN(INFO);
 
-  // Cancel logon so that the next time this credential is clicked everything
-  // has to be re-entered by the user. This prevents a Windows password
-  // entered into the password field by the user from being persisted too
-  // long. The behaviour is similar to that of the normal windows password
-  // text box. Whenever a different user is selected and then the original
-  // credential is selected again, the password is cleared.
-  ResetInternalState();
-
+  // This check is trying to handle the scenario when GetSerialization finishes
+  // with cpgsr set as CPGSR_RETURN_CREDENTIAL_FINISHED which indicates that
+  // the windows autologon is ready to go. In this case ideally ReportResult
+  // should be invoked by the windows login UI process prior to SetDeselected.
+  // But for OtherUserCredential scenario, SetDeselected is being invoked
+  // prior to ReportResult which is leading to clearing of the internalstate
+  // prior to saving the account user info in ReportResult.
+  if (!wait_for_report_result_) {
+    // Cancel logon so that the next time this credential is clicked everything
+    // has to be re-entered by the user. This prevents a Windows password
+    // entered into the password field by the user from being persisted too
+    // long. The behaviour is similar to that of the normal windows password
+    // text box. Whenever a different user is selected and then the original
+    // credential is selected again, the password is cleared.
+    ResetInternalState();
+  }
   return S_OK;
 }
 
@@ -1175,6 +1184,13 @@
   if (submit_button_enabled)
     token_update_locker_.reset();
 
+  // If cpgsr is CPGSR_RETURN_CREDENTIAL_FINISHED and the status is S_OK, then
+  // report result would be invoked. So we shouldn't be resetting the internal
+  // state prior to report result getting triggered.
+  if (*cpgsr == CPGSR_RETURN_CREDENTIAL_FINISHED && hr == S_OK) {
+    wait_for_report_result_ = true;
+  }
+
   // Otherwise, keep the ui disabled forever now. ReportResult will eventually
   // be called on success or failure and the reset of the state of the
   // credential will be done there.
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.h b/chrome/credential_provider/gaiacp/gaia_credential_base.h
index 632abbe..051bdf5 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.h
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.h
@@ -288,6 +288,10 @@
   bool needs_windows_password_ = false;
   bool request_force_password_change_ = false;
 
+  // Boolean to indicate if we should wait for ReportResult() prior to clearing
+  // internal state.
+  bool wait_for_report_result_ = false;
+
   // The password entered into the FID_CURRENT_PASSWORD_FIELD to update the
   // Windows password with the gaia password.
   CComBSTR current_windows_password_;
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index 51590eb..6eee29a 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -2,6 +2,10 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <windows.h>
+
+#include <sddl.h>  // For ConvertSidToStringSid()
+
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential_base.h"
@@ -91,6 +95,62 @@
   EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
 }
 
+// This test emulates the scenario where SetDeselected is triggered by the
+// Windows Login UI process after GetSerialization prior to invocation of
+// ReportResult. Note: This currently happens only for OtherUser credential
+// workflow.
+TEST_F(GcpGaiaCredentialBaseTest,
+       GetSerialization_SetDeselectedBeforeReportResult) {
+  // Create provider and start logon.
+  CComPtr<ICredentialProviderCredential> cred;
+
+  ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
+
+  // Make sure a "foo" user was created.
+  PSID sid;
+  EXPECT_EQ(S_OK, fake_os_user_manager()->GetUserSID(
+                      OSUserManager::GetLocalDomain().c_str(), kDefaultUsername,
+                      &sid));
+
+  // New user should be created.
+  EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
+
+  // Finishing logon process should trigger credential changed and trigger
+  // GetSerialization.
+  ASSERT_EQ(S_OK, FinishLogonProcessWithCred(true, true, 0, cred));
+
+  // Trigger SetDeselected prior to ReportResult is invoked.
+  cred->SetDeselected();
+
+  // Verify that the authentication results dictionary is not empty.
+  ASSERT_FALSE(test->IsAuthenticationResultsEmpty());
+
+  // Trigger ReportResult and verify that the authentication results are saved
+  // into registry and ResetInternalState is triggered.
+  ReportLogonProcessResult(cred);
+
+  // Verify that the registry entry for the user was created.
+  wchar_t gaia_id[256];
+  ULONG length = base::size(gaia_id);
+  wchar_t* sidstr = nullptr;
+  ::ConvertSidToStringSid(sid, &sidstr);
+  ::LocalFree(sid);
+
+  HRESULT gaia_id_hr = GetUserProperty(sidstr, kUserId, gaia_id, &length);
+  ASSERT_EQ(S_OK, gaia_id_hr);
+  ASSERT_TRUE(gaia_id[0]);
+
+  // Verify that the authentication results dictionary is now empty.
+  ASSERT_TRUE(test->IsAuthenticationResultsEmpty());
+}
+
 TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Abort) {
   // Create provider and start logon.
   CComPtr<ICredentialProviderCredential> cred;
diff --git a/chrome/credential_provider/test/test_credential.h b/chrome/credential_provider/test/test_credential.h
index eaa197d7..720297a 100644
--- a/chrome/credential_provider/test/test_credential.h
+++ b/chrome/credential_provider/test/test_credential.h
@@ -44,6 +44,7 @@
   SetStartGlsEventName(const base::string16& event_name) = 0;
   virtual BSTR STDMETHODCALLTYPE GetFinalUsername() = 0;
   virtual std::string STDMETHODCALLTYPE GetFinalEmail() = 0;
+  virtual bool STDMETHODCALLTYPE IsAuthenticationResultsEmpty() = 0;
   virtual BSTR STDMETHODCALLTYPE GetErrorText() = 0;
   virtual bool STDMETHODCALLTYPE AreCredentialsValid() = 0;
   virtual bool STDMETHODCALLTYPE CanAttemptWindowsLogon() = 0;
@@ -80,6 +81,7 @@
       const base::string16& event_name) override;
   BSTR STDMETHODCALLTYPE GetFinalUsername() override;
   std::string STDMETHODCALLTYPE GetFinalEmail() override;
+  bool STDMETHODCALLTYPE IsAuthenticationResultsEmpty() override;
   BSTR STDMETHODCALLTYPE GetErrorText() override;
   bool STDMETHODCALLTYPE AreCredentialsValid() override;
   bool STDMETHODCALLTYPE CanAttemptWindowsLogon() override;
@@ -186,6 +188,13 @@
 }
 
 template <class T>
+bool CTestCredentialBase<T>::IsAuthenticationResultsEmpty() {
+  auto& results = this->get_authentication_results();
+
+  return !results || (results->is_dict() && results->DictEmpty());
+}
+
+template <class T>
 std::string CTestCredentialBase<T>::GetFinalEmail() {
   auto& results = this->get_authentication_results();