Fix the failing tests on win-asan for [1]. Seems like returning an empty "username" as part of
MakeUserNameForAccount() doesn't fail creation of user on some of the windows instances.

This fix explicitly returns a failure when we want to fail the login UI instead of depending on
"create user" to fail for "empty username" instead.

Note that currently the error_text is set as IDS_INTERNAL_ERROR_BASE. Explicit error handling
with appropriate error messages would be done in a followup change (already filed a bug for it).

[1] https://chromium-review.googlesource.com/c/chromium/src/+/1659512/24

Bug: 976543
Change-Id: Ic4d92f5ef647d0f3cd5fd50c1f52b588c94a3afd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1672234
Commit-Queue: Rakesh Soma <rakeshsoma@google.com>
Reviewed-by: Tien Mai <tienmai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#672133}
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base.cc b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
index 0c5128d..a09208b 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base.cc
@@ -23,6 +23,8 @@
 #include "base/path_service.h"
 #include "base/stl_util.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
 #include "base/win/current_module.h"
@@ -46,11 +48,13 @@
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/gaiacp/scoped_lsa_policy.h"
 #include "chrome/credential_provider/gaiacp/scoped_user_profile.h"
+#include "chrome/credential_provider/gaiacp/win_http_url_fetcher.h"
 #include "chrome/installer/launcher_support/chrome_launcher_support.h"
 #include "content/public/common/content_switches.h"
 #include "google_apis/gaia/gaia_auth_util.h"
 #include "google_apis/gaia/gaia_switches.h"
 #include "google_apis/gaia/gaia_urls.h"
+#include "net/base/escape.h"
 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 
 namespace credential_provider {
@@ -58,6 +62,23 @@
 namespace {
 
 constexpr wchar_t kEmailDomainsKey[] = L"ed";
+constexpr char kGetAccessTokenBodyWithScopeFormat[] =
+    "client_id=%s&"
+    "client_secret=%s&"
+    "grant_type=refresh_token&"
+    "refresh_token=%s&"
+    "scope=%s";
+constexpr wchar_t kRegEnableADAssociation[] = L"enable_ad_association";
+// The access scopes should be separated by single space.
+constexpr char kAccessScopes[] =
+    "https://www.googleapis.com/auth/admin.directory.user";
+constexpr int kHttpTimeout = 3000;  // in milliseconds
+
+// Names of keys used to fetch the custom attributes from google admin sdk
+// users directory api.
+constexpr char kKeyCustomSchemas[] = "customSchemas";
+constexpr char kKeyEmployeeData[] = "employeeData";
+constexpr char kKeyAdUpn[] = "ad_upn";
 
 base::string16 GetEmailDomains() {
   std::vector<wchar_t> email_domains(16);
@@ -75,6 +96,178 @@
   return base::string16(&email_domains[0]);
 }
 
+bool EnableAdToGoogleAssociation() {
+  DWORD enable_ad_association = 0;
+  HRESULT hr = GetGlobalFlag(kRegEnableADAssociation, &enable_ad_association);
+  return SUCCEEDED(hr) && enable_ad_association;
+}
+
+// Use WinHttpUrlFetcher to communicate with the admin sdk and fetch the active
+// directory UPN from the admin configured custom attributes.
+HRESULT GetAdUpnFromCloudDirectory(const base::string16& email,
+                                   const std::string& access_token,
+                                   std::string* ad_upn) {
+  DCHECK(email.size() > 0);
+  DCHECK(access_token.size() > 0);
+  DCHECK(ad_upn);
+
+  std::string escape_url_encoded_email =
+      net::EscapeUrlEncodedData(base::UTF16ToUTF8(email), true);
+  std::string get_cd_user_url = base::StringPrintf(
+      "https://www.googleapis.com/admin/directory/v1/users/"
+      "%s?projection=full&viewType=domain_public",
+      escape_url_encoded_email.c_str());
+  LOGFN(INFO) << "Encoded URL : " << get_cd_user_url;
+  auto fetcher = WinHttpUrlFetcher::Create(GURL(get_cd_user_url));
+  fetcher->SetRequestHeader("Accept", "application/json");
+  fetcher->SetHttpRequestTimeout(kHttpTimeout);
+
+  std::string access_token_header =
+      base::StringPrintf("Bearer %s", access_token.c_str());
+  fetcher->SetRequestHeader("Authorization", access_token_header.c_str());
+  std::vector<char> cd_user_response;
+  HRESULT hr = fetcher->Fetch(&cd_user_response);
+  std::string cd_user_response_json_string =
+      std::string(cd_user_response.begin(), cd_user_response.end());
+  if (FAILED(hr)) {
+    LOGFN(INFO) << "fetcher->Fetch hr=" << putHR(hr);
+    return hr;
+  }
+
+  *ad_upn = SearchForKeyInStringDictUTF8(
+      cd_user_response_json_string,
+      {kKeyCustomSchemas, kKeyEmployeeData, kKeyAdUpn});
+  return S_OK;
+}
+
+// Request a downscoped access token using the refresh token provided in the
+// input.
+HRESULT RequestDownscopedAccessToken(const std::string& refresh_token,
+                                     std::string* access_token) {
+  DCHECK(refresh_token.size() > 0);
+  DCHECK(access_token);
+
+  GaiaUrls* gaia_urls = GaiaUrls::GetInstance();
+  std::string enc_client_id =
+      net::EscapeUrlEncodedData(gaia_urls->oauth2_chrome_client_id(), true);
+  std::string enc_client_secret =
+      net::EscapeUrlEncodedData(gaia_urls->oauth2_chrome_client_secret(), true);
+  std::string enc_refresh_token =
+      net::EscapeUrlEncodedData(refresh_token, true);
+  std::string get_access_token_body = base::StringPrintf(
+      kGetAccessTokenBodyWithScopeFormat, enc_client_id.c_str(),
+      enc_client_secret.c_str(), enc_refresh_token.c_str(),
+      net::EscapeUrlEncodedData(kAccessScopes, true).c_str());
+  std::string get_oauth_token_url =
+      base::StringPrintf("%s", gaia_urls->oauth2_token_url().spec().c_str());
+
+  auto oauth_fetcher = WinHttpUrlFetcher::Create(GURL(get_oauth_token_url));
+  oauth_fetcher->SetRequestBody(get_access_token_body.c_str());
+  oauth_fetcher->SetRequestHeader("content-type",
+                                  "application/x-www-form-urlencoded");
+  oauth_fetcher->SetHttpRequestTimeout(kHttpTimeout);
+
+  std::vector<char> oauth_response;
+  HRESULT oauth_hr = oauth_fetcher->Fetch(&oauth_response);
+  if (FAILED(oauth_hr)) {
+    LOGFN(ERROR) << "oauth_fetcher.Fetch hr=" << putHR(oauth_hr);
+    return oauth_hr;
+  }
+
+  std::string oauth_response_json_string =
+      std::string(oauth_response.begin(), oauth_response.end());
+  *access_token = SearchForKeyInStringDictUTF8(oauth_response_json_string,
+                                               {kKeyAccessToken});
+  if (access_token->empty()) {
+    LOGFN(ERROR) << "Fetched access token with new scopes is empty.";
+    return E_FAIL;
+  }
+  return S_OK;
+}
+
+// Find an AD account associated with GCPW user if one exists.
+// (1) Verifies if the gaia user has a corresponding mapping in Google
+//   Admin SDK Users Directory and contains the custom_schema that contains
+//   the ad_upn or local_user_name for the corresponding user.
+// (2) If there is an entry in cloud directory, gcpw would search for the SID
+//   corresponding to that user entry on the device.
+// (3) If a SID is found, then it would log the user onto the device using
+//   username extracted from Google Admin SDK Users Directory and password
+//   being the same as the gaia entity.
+// (4) If there is no entry found in cloud directory, gcpw would fallback to
+//   attempting creation of a new user on the device.
+//
+// Below are the failure scenarios :
+// (1) If an invalid upn is set in the custom attributes, the login would fail.
+// (2) If an attempt to find SID from domain controller failed, then we fail
+//     the login.
+// Note that if an empty upn is found in the custom attribute, then the login
+// would try and attempt to create local user.
+HRESULT FindAdUserSidIfAvailable(const std::string& refresh_token,
+                                 const base::string16& email,
+                                 wchar_t* sid,
+                                 const DWORD sid_length) {
+  // Step 1: Get the downscoped access token with required admin sdk scopes.
+  std::string access_token;
+  HRESULT hr = RequestDownscopedAccessToken(refresh_token, &access_token);
+
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "RequestDownscopedAccessToken hr=" << putHR(hr);
+    return hr;
+  }
+
+  // Step 2: Make a get call to admin sdk using the fetched access_token and
+  // retrieve the ad_upn.
+  std::string ad_upn;
+  hr = GetAdUpnFromCloudDirectory(email, access_token, &ad_upn);
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "GetAdUpnFromCloudDirectory hr=" << putHR(hr);
+    return hr;
+  }
+
+  base::string16 ad_domain;
+  base::string16 ad_user;
+  if (ad_upn.empty()) {
+    LOGFN(INFO) << "Found empty ad_upn in cloud directory. Fall back to "
+                   "creating local account";
+    return S_FALSE;
+  }
+
+  // The format for ad_upn custom attribute is domainName/userName.
+  const base::char16 kSlashDelimiter[] = STRING16_LITERAL("/");
+  std::vector<base::string16> tokens =
+      base::SplitString(base::UTF8ToUTF16(ad_upn), kSlashDelimiter,
+                        base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
+
+  // Values fetched from custom attribute shouldn't be empty.
+  if (tokens.size() != 2) {
+    LOGFN(ERROR) << "Found unparseable ad_upn in cloud directory : " << ad_upn;
+    return E_FAIL;
+  }
+
+  ad_domain = tokens.at(0);
+  ad_user = tokens.at(1);
+
+  OSUserManager* os_user_manager = OSUserManager::Get();
+  DCHECK(os_user_manager);
+  base::string16 existing_sid = base::string16();
+
+  LOGFN(INFO) << "Get user sid for user " << ad_user << " and domain name "
+              << ad_domain;
+  hr = os_user_manager->GetUserSID(ad_domain.c_str(), ad_user.c_str(),
+                                   &existing_sid);
+  LOGFN(INFO) << "GetUserSID result=" << hr;
+
+  if (existing_sid.length() > 0) {
+    LOGFN(INFO) << "Found existing SID = " << existing_sid;
+    wcscpy_s(sid, sid_length, existing_sid.c_str());
+    return S_OK;
+  } else {
+    LOGFN(ERROR) << "No existing sid found with UPN : " << ad_upn;
+    return E_FAIL;
+  }
+}
+
 // Tries to find a user associated to the gaia_id stored in |result| under the
 // key |kKeyId|. If one exists, then this function will fill out |gaia_id|,
 // |username|, |domain| and |sid| with the user's information. If not this
@@ -83,15 +276,15 @@
 // since only local users can be created. |sid| will be empty until the user is
 // created later on. |is_consumer_account| will be set to true if the email used
 // to sign in is gmail or googlemail.
-void MakeUsernameForAccount(const base::Value& result,
-                            base::string16* gaia_id,
-                            wchar_t* username,
-                            DWORD username_length,
-                            wchar_t* domain,
-                            DWORD domain_length,
-                            wchar_t* sid,
-                            DWORD sid_length,
-                            bool* is_consumer_account) {
+HRESULT MakeUsernameForAccount(const base::Value& result,
+                               base::string16* gaia_id,
+                               wchar_t* username,
+                               DWORD username_length,
+                               wchar_t* domain,
+                               DWORD domain_length,
+                               wchar_t* sid,
+                               DWORD sid_length,
+                               bool* is_consumer_account) {
   DCHECK(gaia_id);
   DCHECK(username);
   DCHECK(domain);
@@ -108,15 +301,44 @@
   *is_consumer_account = consumer_domain_pos != base::string16::npos;
 
   *gaia_id = GetDictString(result, kKeyId);
+
   // First try to detect if this gaia account has been used to create an OS
   // user already.  If so, return the OS username of that user.
   HRESULT hr = GetSidFromId(*gaia_id, sid, sid_length);
+
+  bool has_existing_user_sid = false;
+  // Check if the machine is domain joined and get the domain name if domain
+  // joined.
   if (SUCCEEDED(hr)) {
-    hr = OSUserManager::Get()->FindUserBySID(sid, username, username_length,
-                                             domain, domain_length);
-    if (SUCCEEDED(hr))
-      return;
+    // This makes sure that we don't invoke the network calls on every login
+    // attempt and instead fallback to the SID to gaia id mapping created by
+    // GCPW.
+    LOGFN(INFO) << "Found existing SID created in GCPW registry entry = "
+                << sid;
+    has_existing_user_sid = true;
+  } else if (EnableAdToGoogleAssociation() &&
+             OSUserManager::Get()->IsDeviceDomainJoined()) {
+    LOGFN(INFO) << "No existing SID found in the GCPW registry.";
+
+    std::string refresh_token = GetDictStringUTF8(result, kKeyRefreshToken);
+    hr = FindAdUserSidIfAvailable(refresh_token, email, sid, sid_length);
+    if (FAILED(hr)) {
+      LOGFN(ERROR) << "FindAdUserSidIfAvailable hr=" << putHR(hr);
+      return hr;
+    } else if (hr == S_OK) {
+      has_existing_user_sid = true;
+    }
+  } else {
+    LOGFN(INFO) << "Falling back to creation of new user";
   }
+
+  if (has_existing_user_sid) {
+    HRESULT hr = OSUserManager::Get()->FindUserBySID(
+        sid, username, username_length, domain, domain_length);
+    if (SUCCEEDED(hr))
+      return hr;
+  }
+
   LOGFN(INFO) << "No existing user found associated to gaia id:" << *gaia_id;
   wcscpy_s(domain, domain_length, OSUserManager::GetLocalDomain().c_str());
   username[0] = 0;
@@ -162,9 +384,11 @@
   }
 
   wcscpy_s(username, username_length, os_username.c_str());
+
+  return S_OK;
 }
 
-// Waits for the login UI to completes and returns the result of the operation.
+// Waits for the login UI to complete and returns the result of the operation.
 // This function returns S_OK on success, E_UNEXPECTED on failure, and E_ABORT
 // if the user aborted or timed out (or was killed during cleanup).
 HRESULT WaitForLoginUIAndGetResult(
@@ -1657,10 +1881,18 @@
   wchar_t found_sid[kWindowsSidBufferLength];
   bool is_consumer_account = false;
   base::string16 gaia_id;
-  MakeUsernameForAccount(result, &gaia_id, found_username,
-                         base::size(found_username), found_domain,
-                         base::size(found_domain), found_sid,
-                         base::size(found_sid), &is_consumer_account);
+  HRESULT hr = MakeUsernameForAccount(
+      result, &gaia_id, found_username, base::size(found_username),
+      found_domain, base::size(found_domain), found_sid, base::size(found_sid),
+      &is_consumer_account);
+
+  if (FAILED(hr)) {
+    LOGFN(ERROR) << "MakeUsernameForAccount hr=" << putHR(hr);
+    // TODO(crbug.com/976406): Set the error text appropriate messages
+    // instead of falling back to IDS_INTERNAL_ERROR_BASE.
+    *error_text = AllocErrorString(IDS_INTERNAL_ERROR_BASE);
+    return hr;
+  }
 
   // Disallow consumer accounts when mdm enrollment is enabled and the global
   // flag to allow consumer accounts is not set.
@@ -1679,8 +1911,8 @@
   // If an existing user associated to the gaia id was found, make sure that it
   // is valid for this credential.
   if (found_sid[0]) {
-    HRESULT hr = ValidateExistingUser(found_username, found_domain, found_sid,
-                                      error_text);
+    hr = ValidateExistingUser(found_username, found_domain, found_sid,
+                              error_text);
 
     if (FAILED(hr)) {
       LOGFN(ERROR) << "ValidateExistingUser hr=" << putHR(hr);
@@ -1716,7 +1948,7 @@
   base::string16 local_password = GetDictString(result, kKeyPassword);
   base::string16 local_fullname = GetDictString(result, kKeyFullname);
   base::string16 comment(GetStringResource(IDS_USER_ACCOUNT_COMMENT_BASE));
-  HRESULT hr = CreateNewUser(
+  hr = CreateNewUser(
       OSUserManager::Get(), found_username, local_password.c_str(),
       local_fullname.c_str(), comment.c_str(),
       /*add_to_users_group=*/true, kMaxUsernameAttempts, username, sid);
diff --git a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
index dbec687..404858d 100644
--- a/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
+++ b/chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
@@ -6,6 +6,7 @@
 
 #include <sddl.h>  // For ConvertSidToStringSid()
 
+#include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "chrome/credential_provider/common/gcp_strings.h"
 #include "chrome/credential_provider/gaiacp/gaia_credential_base.h"
@@ -15,6 +16,8 @@
 #include "chrome/credential_provider/gaiacp/reg_utils.h"
 #include "chrome/credential_provider/test/gls_runner_test_base.h"
 #include "chrome/credential_provider/test/test_credential.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/escape.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace credential_provider {
@@ -822,6 +825,287 @@
   EXPECT_EQ(test->GetFinalEmail(), email);
 }
 
+/** Test various active directory sign in scenarios. */
+
+class GcpGaiaCredentialBaseAdScenariosTest : public GcpGaiaCredentialBaseTest {
+ protected:
+  void SetUp() override;
+
+  // Create provider and start logon.
+  CComPtr<ICredentialProviderCredential> cred_;
+  // The admin sdk users directory get URL.
+  std::string get_cd_user_url_ = base::StringPrintf(
+      "https://www.googleapis.com/admin/directory/v1/users/"
+      "%s?projection=full&viewType=domain_public",
+      net::EscapeUrlEncodedData(kDefaultEmail, true).c_str());
+  GaiaUrls* gaia_urls_ = GaiaUrls::GetInstance();
+};
+
+void GcpGaiaCredentialBaseAdScenariosTest::SetUp() {
+  GcpGaiaCredentialBaseTest::SetUp();
+
+  // Set the device as a domain joined machine.
+  fake_os_user_manager()->SetIsDeviceDomainJoined(true);
+
+  // Override registry to enable AD association with google.
+  constexpr wchar_t kRegEnableADAssociation[] = L"enable_ad_association";
+  ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegEnableADAssociation, 1));
+
+  ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred_));
+}
+
+// Fetching downscoped access token required for calling admin sdk failed.
+// The login attempt would fail in this scenario.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithAD_CallToFetchDownscopedAccessTokenFailed) {
+  // Attempt to fetch the token from gaia fails.
+  fake_http_url_fetcher_factory()->SetFakeFailedResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()), E_FAIL);
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred_.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  ASSERT_TRUE(base::size(test->GetFinalEmail()) == 0);
+
+  // Make sure no user was created and the login attempt failed.
+  PSID sid = nullptr;
+  EXPECT_EQ(
+      HRESULT_FROM_WIN32(NERR_UserNotFound),
+      fake_os_user_manager()->GetUserSID(
+          OSUserManager::GetLocalDomain().c_str(), kDefaultUsername, &sid));
+  ASSERT_EQ(nullptr, sid);
+
+  // No new user is created.
+  EXPECT_EQ(1ul, fake_os_user_manager()->GetUserCount());
+
+  // TODO(crbug.com/976406): Set the error message appropriately for failure
+  // scenarios.
+  ASSERT_EQ(S_OK, FinishLogonProcess(
+                      /*expected_success=*/false,
+                      /*expected_credentials_change_fired=*/false,
+                      IDS_INTERNAL_ERROR_BASE));
+}
+
+// Empty access token returned.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithAD_EmptyAccessTokenReturned) {
+  // Set token result to not contain any access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{}");
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred_.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  ASSERT_TRUE(base::size(test->GetFinalEmail()) == 0);
+
+  // Make sure no user was created and the login attempt failed.
+  PSID sid = nullptr;
+  EXPECT_EQ(
+      HRESULT_FROM_WIN32(NERR_UserNotFound),
+      fake_os_user_manager()->GetUserSID(
+          OSUserManager::GetLocalDomain().c_str(), kDefaultUsername, &sid));
+  ASSERT_EQ(nullptr, sid);
+
+  // No new user is created.
+  EXPECT_EQ(1ul, fake_os_user_manager()->GetUserCount());
+
+  ASSERT_EQ(S_OK, FinishLogonProcess(
+                      /*expected_success=*/false,
+                      /*expected_credentials_change_fired=*/false,
+                      IDS_INTERNAL_ERROR_BASE));
+}
+
+// Empty AD UPN entry is returned via admin sdk.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithAD_NoAdUpnFoundFromAdminSdk) {
+  // Set token result a valid access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}");
+
+  // Set empty response from admin sdk.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(get_cd_user_url_.c_str()), FakeWinHttpUrlFetcher::Headers(), "{}");
+
+  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));
+  ::LocalFree(sid);
+
+  // New user should be created.
+  EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
+}
+
+// Call to the admin sdk to fetch the AD UPN failed.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithAD_CallToAdminSdkFailed) {
+  // Set token result a valid access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}");
+
+  // Fail the call from admin sdk.
+  fake_http_url_fetcher_factory()->SetFakeFailedResponse(
+      GURL(get_cd_user_url_.c_str()), E_FAIL);
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred_.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  ASSERT_TRUE(base::size(test->GetFinalEmail()) == 0);
+
+  // Make sure no user was created and the login attempt failed.
+  PSID sid = nullptr;
+  EXPECT_EQ(
+      HRESULT_FROM_WIN32(NERR_UserNotFound),
+      fake_os_user_manager()->GetUserSID(
+          OSUserManager::GetLocalDomain().c_str(), kDefaultUsername, &sid));
+  ASSERT_EQ(nullptr, sid);
+
+  // No new user is created.
+  EXPECT_EQ(1ul, fake_os_user_manager()->GetUserCount());
+
+  ASSERT_EQ(S_OK, FinishLogonProcess(
+                      /*expected_success=*/false,
+                      /*expected_credentials_change_fired=*/false,
+                      IDS_INTERNAL_ERROR_BASE));
+}
+
+// Customer configured invalid ad upn.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithAD_InvalidADUPNConfigured) {
+  // Add the user as a domain joined user.
+  const wchar_t user_name[] = L"ad_user";
+  const wchar_t domain_name[] = L"ad_domain";
+  const wchar_t password[] = L"password";
+
+  CComBSTR ad_sid;
+  DWORD error;
+  HRESULT add_domain_user_hr = fake_os_user_manager()->AddUser(
+      user_name, password, L"fullname", L"comment", true, domain_name, &ad_sid,
+      &error);
+  ASSERT_EQ(S_OK, add_domain_user_hr);
+  ASSERT_EQ(0u, error);
+
+  // Set token result a valid access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}");
+
+  // Invalid configuration in admin sdk. Don't set the username.
+  std::string admin_sdk_response = base::StringPrintf(
+      "{\"customSchemas\": {\"employeeData\": {\"ad_upn\":"
+      " \"%ls/\"}}}",
+      domain_name);
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(get_cd_user_url_.c_str()), FakeWinHttpUrlFetcher::Headers(),
+      admin_sdk_response);
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred_.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  ASSERT_TRUE(base::size(test->GetFinalEmail()) == 0);
+
+  // Make sure no user was created and the login attempt failed.
+  PSID sid = nullptr;
+  EXPECT_EQ(
+      HRESULT_FROM_WIN32(NERR_UserNotFound),
+      fake_os_user_manager()->GetUserSID(
+          OSUserManager::GetLocalDomain().c_str(), kDefaultUsername, &sid));
+  ASSERT_EQ(nullptr, sid);
+
+  // No new user is created.
+  EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
+
+  ASSERT_EQ(S_OK, FinishLogonProcess(
+                      /*expected_success=*/false,
+                      /*expected_credentials_change_fired=*/false,
+                      IDS_INTERNAL_ERROR_BASE));
+}
+
+// This is the success scenario where all preconditions are met in the
+// AD login scenario. The user is successfully logged in.
+TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
+       GetSerialization_WithADSuccessScenario) {
+  // Add the user as a domain joined user.
+  const wchar_t user_name[] = L"ad_user";
+  const wchar_t domain_name[] = L"ad_domain";
+  const wchar_t password[] = L"password";
+
+  CComBSTR ad_sid;
+  DWORD error;
+  HRESULT add_domain_user_hr = fake_os_user_manager()->AddUser(
+      user_name, password, L"fullname", L"comment", true, domain_name, &ad_sid,
+      &error);
+  ASSERT_EQ(S_OK, add_domain_user_hr);
+  ASSERT_EQ(0u, error);
+
+  // Set token result as a valid access token.
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(gaia_urls_->oauth2_token_url().spec().c_str()),
+      FakeWinHttpUrlFetcher::Headers(), "{\"access_token\": \"dummy_token\"}");
+
+  // Set valid response from admin sdk.
+  std::string admin_sdk_response = base::StringPrintf(
+      "{\"customSchemas\": {\"employeeData\": {\"ad_upn\":"
+      " \"%ls/%ls\"}}}",
+      domain_name, user_name);
+  fake_http_url_fetcher_factory()->SetFakeResponse(
+      GURL(get_cd_user_url_.c_str()), FakeWinHttpUrlFetcher::Headers(),
+      admin_sdk_response);
+
+  CComPtr<ITestCredential> test;
+  ASSERT_EQ(S_OK, cred_.QueryInterface(&test));
+
+  ASSERT_EQ(S_OK, StartLogonProcessAndWait());
+
+  EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
+
+  // Make sure no user was created and the login happens on the
+  // existing user instead.
+  PSID sid = nullptr;
+  EXPECT_EQ(
+      HRESULT_FROM_WIN32(NERR_UserNotFound),
+      fake_os_user_manager()->GetUserSID(
+          OSUserManager::GetLocalDomain().c_str(), kDefaultUsername, &sid));
+  ASSERT_EQ(nullptr, sid);
+
+  // Finishing logon process should trigger credential changed and trigger
+  // GetSerialization.
+  ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
+
+  // Verify that the registry entry for the user was created.
+  wchar_t gaia_id[256];
+  ULONG length = base::size(gaia_id);
+  std::wstring sid_str(ad_sid, SysStringLen(ad_sid));
+  ::SysFreeString(ad_sid);
+
+  HRESULT gaia_id_hr =
+      GetUserProperty(sid_str.c_str(), 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());
+}
+
 // Tests various sign in scenarios with consumer and non-consumer domains.
 // Parameters are:
 // 1. Is mdm enrollment enabled.
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.cc b/chrome/credential_provider/gaiacp/gcp_utils.cc
index a5d0b618..c43b0ca 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.cc
+++ b/chrome/credential_provider/gaiacp/gcp_utils.cc
@@ -29,10 +29,12 @@
 #include "base/files/file.h"
 #include "base/files/file_enumerator.h"
 #include "base/files/file_util.h"
+#include "base/json/json_reader.h"
 #include "base/macros.h"
 #include "base/no_destructor.h"
 #include "base/path_service.h"
 #include "base/stl_util.h"
+#include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/win/current_module.h"
@@ -727,6 +729,22 @@
     ::RtlSecureZeroMemory(buffer, length);
 }
 
+std::string SearchForKeyInStringDictUTF8(
+    const std::string& json_string,
+    const std::initializer_list<base::StringPiece>& path) {
+  DCHECK(path.size() > 0);
+
+  base::Optional<base::Value> json_obj =
+      base::JSONReader::Read(json_string, base::JSON_ALLOW_TRAILING_COMMAS);
+  if (!json_obj || !json_obj->is_dict()) {
+    LOGFN(ERROR) << "base::JSONReader::Read failed to translate to JSON";
+    return std::string();
+  }
+  const std::string* value =
+      json_obj->FindStringPath(base::JoinString(path, "."));
+  return value ? *value : std::string();
+}
+
 base::string16 GetDictString(const base::Value& dict, const char* name) {
   DCHECK(name);
   DCHECK(dict.is_dict());
diff --git a/chrome/credential_provider/gaiacp/gcp_utils.h b/chrome/credential_provider/gaiacp/gcp_utils.h
index 8689f2c..1958cbc 100644
--- a/chrome/credential_provider/gaiacp/gcp_utils.h
+++ b/chrome/credential_provider/gaiacp/gcp_utils.h
@@ -232,9 +232,18 @@
 
 // Helpers to get strings from base::Values that are expected to be
 // DictionaryValues.
+
 base::string16 GetDictString(const base::Value& dict, const char* name);
 base::string16 GetDictString(const std::unique_ptr<base::Value>& dict,
                              const char* name);
+// Perform a recursive search on a nested dictionary object. Note that the
+// names provided in the input should be in order. Below is an example : Lets
+// say the json object is {"key1": {"key2": {"key3": "value1"}}, "key4":
+// "value2"}. Then to search for the key "key3", this method should be called
+// by providing the names vector as {"key1", "key2", "key3"}.
+std::string SearchForKeyInStringDictUTF8(
+    const std::string& json_string,
+    const std::initializer_list<base::StringPiece>& path);
 std::string GetDictStringUTF8(const base::Value& dict, const char* name);
 std::string GetDictStringUTF8(const std::unique_ptr<base::Value>& dict,
                               const char* name);
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.cc b/chrome/credential_provider/gaiacp/os_user_manager.cc
index 80e0299..ed45672 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.cc
+++ b/chrome/credential_provider/gaiacp/os_user_manager.cc
@@ -73,6 +73,11 @@
 }
 
 // static
+bool OSUserManager::IsDeviceDomainJoined() {
+  return base::win::IsEnrolledToDomain();
+}
+
+// static
 base::string16 OSUserManager::GetLocalDomain() {
   // If the domain is the current computer, then there is no domain controller.
   wchar_t computer_name[MAX_COMPUTERNAME_LENGTH + 1];
diff --git a/chrome/credential_provider/gaiacp/os_user_manager.h b/chrome/credential_provider/gaiacp/os_user_manager.h
index ff982534..4376cd8 100644
--- a/chrome/credential_provider/gaiacp/os_user_manager.h
+++ b/chrome/credential_provider/gaiacp/os_user_manager.h
@@ -99,6 +99,9 @@
   // to another.
   static void SetInstanceForTesting(OSUserManager* instance);
 
+  // Checks if the device is domain joined.
+  virtual bool IsDeviceDomainJoined();
+
  protected:
   OSUserManager() {}
 
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
index d5eca95..26f6e22 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.cc
@@ -69,6 +69,12 @@
   return S_OK;
 }
 
+HRESULT WinHttpUrlFetcher::SetHttpRequestTimeout(const int timeout_in_millis) {
+  DCHECK(timeout_in_millis);
+  timeout_in_millis_ = timeout_in_millis;
+  return S_OK;
+}
+
 HRESULT WinHttpUrlFetcher::Fetch(std::vector<char>* response) {
   USES_CONVERSION;
   DCHECK(response);
@@ -94,6 +100,19 @@
   }
 
   {
+    // Set timeout if specified.
+    if (timeout_in_millis_ != 0) {
+      if (!::WinHttpSetTimeouts(session_.Get(), timeout_in_millis_,
+                                timeout_in_millis_, timeout_in_millis_,
+                                timeout_in_millis_)) {
+        HRESULT hr = HRESULT_FROM_WIN32(::GetLastError());
+        LOGFN(ERROR) << "WinHttpSetTimeouts hr=" << putHR(hr);
+        return hr;
+      }
+    }
+  }
+
+  {
     bool use_post = !body_.empty();
     ScopedWinHttpHandle::Handle request = ::WinHttpOpenRequest(
         connect.Get(), use_post ? L"POST" : L"GET",
diff --git a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
index 341f6c4..3295c2d 100644
--- a/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
+++ b/chrome/credential_provider/gaiacp/win_http_url_fetcher.h
@@ -27,6 +27,7 @@
 
   virtual HRESULT SetRequestHeader(const char* name, const char* value);
   virtual HRESULT SetRequestBody(const char* body);
+  virtual HRESULT SetHttpRequestTimeout(const int timeout_in_millis);
   virtual HRESULT Fetch(std::vector<char>* response);
   virtual HRESULT Close();
 
@@ -48,6 +49,7 @@
   std::string body_;
   ScopedWinHttpHandle session_;
   ScopedWinHttpHandle request_;
+  int timeout_in_millis_ = 0;
 
   // Gets storage of the function pointer used to create instances of this
   // class for tests.
diff --git a/chrome/credential_provider/test/gcp_fakes.cc b/chrome/credential_provider/test/gcp_fakes.cc
index 91094a3..69ca05a 100644
--- a/chrome/credential_provider/test/gcp_fakes.cc
+++ b/chrome/credential_provider/test/gcp_fakes.cc
@@ -141,6 +141,7 @@
   return AddUser(username, password, fullname, comment, add_to_users_group,
                  OSUserManager::GetLocalDomain().c_str(), sid, error);
 }
+
 HRESULT FakeOSUserManager::AddUser(const wchar_t* username,
                                    const wchar_t* password,
                                    const wchar_t* fullname,
@@ -159,6 +160,11 @@
   if (should_fail_user_creation_)
     return E_FAIL;
 
+  // Username or password cannot be empty.
+  if (username == nullptr || !username[0] || password == nullptr ||
+      !password[0])
+    return E_FAIL;
+
   bool user_found = username_to_info_.count(username) > 0;
 
   if (user_found) {
@@ -271,6 +277,10 @@
   return token->IsValid() ? S_OK : HRESULT_FROM_WIN32(::GetLastError());
 }
 
+bool FakeOSUserManager::IsDeviceDomainJoined() {
+  return is_device_domain_joined_;
+}
+
 HRESULT FakeOSUserManager::GetUserSID(const wchar_t* domain,
                                       const wchar_t* username,
                                       PSID* sid) {
@@ -579,17 +589,30 @@
       Response(headers, response, send_response_event_handle);
 }
 
+void FakeWinHttpUrlFetcherFactory::SetFakeFailedResponse(const GURL& url,
+                                                         HRESULT failed_hr) {
+  // Make sure that the HRESULT set is a failed attempt.
+  DCHECK(FAILED(failed_hr));
+  failed_http_fetch_hr_[url] = failed_hr;
+}
+
 std::unique_ptr<WinHttpUrlFetcher> FakeWinHttpUrlFetcherFactory::Create(
     const GURL& url) {
-  if (fake_responses_.count(url) == 0)
+  if (fake_responses_.count(url) == 0 && failed_http_fetch_hr_.count(url) == 0)
     return nullptr;
 
-  const Response& response = fake_responses_[url];
-
   FakeWinHttpUrlFetcher* fetcher = new FakeWinHttpUrlFetcher(std::move(url));
-  fetcher->response_headers_ = response.headers;
-  fetcher->response_ = response.response;
-  fetcher->send_response_event_handle_ = response.send_response_event_handle;
+
+  if (fake_responses_.count(url) != 0) {
+    const Response& response = fake_responses_[url];
+
+    fetcher->response_headers_ = response.headers;
+    fetcher->response_ = response.response;
+    fetcher->send_response_event_handle_ = response.send_response_event_handle;
+  } else {
+    DCHECK(failed_http_fetch_hr_.count(url) > 0);
+    fetcher->response_hr_ = failed_http_fetch_hr_[url];
+  }
   ++requests_created_;
 
   return std::unique_ptr<WinHttpUrlFetcher>(fetcher);
@@ -605,6 +628,9 @@
 }
 
 HRESULT FakeWinHttpUrlFetcher::Fetch(std::vector<char>* response) {
+  if (FAILED(response_hr_))
+    return response_hr_;
+
   if (send_response_event_handle_ != INVALID_HANDLE_VALUE)
     ::WaitForSingleObject(send_response_event_handle_, INFINITE);
 
diff --git a/chrome/credential_provider/test/gcp_fakes.h b/chrome/credential_provider/test/gcp_fakes.h
index 368d5d7..088e98b1 100644
--- a/chrome/credential_provider/test/gcp_fakes.h
+++ b/chrome/credential_provider/test/gcp_fakes.h
@@ -69,6 +69,15 @@
                   bool add_to_users_group,
                   BSTR* sid,
                   DWORD* error) override;
+  // Add a user to the OS with domain associated with it.
+  HRESULT AddUser(const wchar_t* username,
+                  const wchar_t* password,
+                  const wchar_t* fullname,
+                  const wchar_t* comment,
+                  bool add_to_users_group,
+                  const wchar_t* domain,
+                  BSTR* sid,
+                  DWORD* error);
   HRESULT ChangeUserPassword(const wchar_t* domain,
                              const wchar_t* username,
                              const wchar_t* password,
@@ -103,10 +112,16 @@
                                          const wchar_t* username,
                                          bool allow) override;
 
+  bool IsDeviceDomainJoined() override;
+
   void SetShouldFailUserCreation(bool should_fail) {
     should_fail_user_creation_ = should_fail;
   }
 
+  void SetIsDeviceDomainJoined(bool is_device_domain_joined) {
+    is_device_domain_joined_ = is_device_domain_joined;
+  }
+
   struct UserInfo {
     UserInfo(const wchar_t* domain,
              const wchar_t* password,
@@ -165,16 +180,7 @@
   DWORD next_rid_ = 0;
   std::map<base::string16, UserInfo> username_to_info_;
   bool should_fail_user_creation_ = false;
-
-  // Add a user to the OS with domain associated with it.
-  HRESULT AddUser(const wchar_t* username,
-                  const wchar_t* password,
-                  const wchar_t* fullname,
-                  const wchar_t* comment,
-                  bool add_to_users_group,
-                  const wchar_t* domain,
-                  BSTR* sid,
-                  DWORD* error);
+  bool is_device_domain_joined_ = false;
 };
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -273,6 +279,12 @@
       const WinHttpUrlFetcher::Headers& headers,
       const std::string& response,
       HANDLE send_response_event_handle = INVALID_HANDLE_VALUE);
+
+  // Sets the response as a failed http attempt. The return result
+  // from http_url_fetcher.Fetch() would be set as the input HRESULT
+  // to this method.
+  void SetFakeFailedResponse(const GURL& url, HRESULT failed_hr);
+
   size_t requests_created() const { return requests_created_; }
 
  private:
@@ -293,6 +305,7 @@
   };
 
   std::map<GURL, Response> fake_responses_;
+  std::map<GURL, HRESULT> failed_http_fetch_hr_;
   size_t requests_created_ = 0;
 };
 
@@ -316,6 +329,7 @@
   Headers response_headers_;
   std::string response_;
   HANDLE send_response_event_handle_;
+  HRESULT response_hr_ = S_OK;
 };
 
 ///////////////////////////////////////////////////////////////////////////////