Skip to content

Commit

Permalink
Use saved username and domain in case active directory is unreachable
Browse files Browse the repository at this point in the history
Bug: 2893225
Change-Id: I5b540038d24cd4540563c1a78f2fc2534939a31d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3057964
Auto-Submit: Yusuf Sengul <yusufsn@google.com>
Reviewed-by: Rakesh Soma <rakeshsoma@google.com>
Reviewed-by: Robin Lewis <wrlewis@google.com>
Commit-Queue: Yusuf Sengul <yusufsn@google.com>
Cr-Commit-Position: refs/heads/master@{#907323}
  • Loading branch information
g-yusufsn authored and Chromium LUCI CQ committed Jul 31, 2021
1 parent c8d5c99 commit 2cda750
Show file tree
Hide file tree
Showing 5 changed files with 272 additions and 17 deletions.
4 changes: 2 additions & 2 deletions chrome/credential_provider/gaiacp/BUILD.gn
Expand Up @@ -26,8 +26,6 @@ source_set("common") {
"gcp_crash_reporting_utils.h",
"gem_device_details_manager.cc",
"gem_device_details_manager.h",
"os_user_manager.cc",
"os_user_manager.h",
]
public_configs = [ ":common_config" ]
public_deps = [ "//chrome/credential_provider/common:common_constants" ]
Expand Down Expand Up @@ -71,6 +69,8 @@ source_set("util") {
"gcpw_strings.h",
"logging.cc",
"logging.h",
"os_user_manager.cc",
"os_user_manager.h",
"reg_utils.cc",
"reg_utils.h",
"scoped_handle.h",
Expand Down
49 changes: 34 additions & 15 deletions chrome/credential_provider/gaiacp/gaia_credential_base.cc
Expand Up @@ -489,7 +489,6 @@ HRESULT MakeUsernameForAccount(const base::Value& result,
LOGFN(VERBOSE) << "Failed fetching Sid from email : " << putHR(hr);
}

bool has_existing_user_sid = false;
// Check if the machine is domain joined and get the domain name if domain
// joined.
if (SUCCEEDED(hr)) {
Expand All @@ -498,20 +497,34 @@ HRESULT MakeUsernameForAccount(const base::Value& result,
// GCPW.
LOGFN(VERBOSE) << "Found existing SID created in GCPW registry entry = "
<< sid;
has_existing_user_sid = true;

HRESULT hr = FindUserBySidWithRegistryFallback(
sid, username, username_length, domain, domain_length);
if (FAILED(hr)) {
*error_text =
CGaiaCredentialBase::AllocErrorString(IDS_INVALID_AD_UPN_BASE);
}
return hr;

} else if (CGaiaCredentialBase::IsCloudAssociationEnabled()) {
LOGFN(VERBOSE) << "Lookup cloud association.";

std::string refresh_token = GetDictStringUTF8(result, kKeyRefreshToken);
hr = FindExistingUserSidIfAvailable(refresh_token, email, sid, sid_length,
error_text);

has_existing_user_sid = true;
if (hr == NTE_NOT_FOUND) {
if (SUCCEEDED(hr)) {
HRESULT hr = OSUserManager::Get()->FindUserBySID(
sid, username, username_length, domain, domain_length);
if (FAILED(hr)) {
*error_text =
CGaiaCredentialBase::AllocErrorString(IDS_INVALID_AD_UPN_BASE);
}
return hr;
} else if (hr == NTE_NOT_FOUND) {
LOGFN(ERROR) << "No valid sid mapping found."
<< "Fallback to create a new local user account. hr="
<< putHR(hr);
has_existing_user_sid = false;
} else if (FAILED(hr)) {
LOGFN(ERROR) << "Failed finding existing user sid for GCPW user. hr="
<< putHR(hr);
Expand All @@ -522,15 +535,6 @@ HRESULT MakeUsernameForAccount(const base::Value& result,
LOGFN(VERBOSE) << "Fallback to create a new local user account";
}

if (has_existing_user_sid) {
HRESULT hr = OSUserManager::Get()->FindUserBySID(
sid, username, username_length, domain, domain_length);
if (FAILED(hr))
*error_text =
CGaiaCredentialBase::AllocErrorString(IDS_INTERNAL_ERROR_BASE);
return hr;
}

LOGFN(VERBOSE) << "No existing user found associated to gaia id:" << *gaia_id;
wcscpy_s(domain, domain_length, OSUserManager::GetLocalDomain().c_str());
username[0] = 0;
Expand Down Expand Up @@ -2180,6 +2184,8 @@ HRESULT
RegisterAssociation(const std::wstring& sid,
const std::wstring& id,
const std::wstring& email,
const std::wstring& domain,
const std::wstring& username,
const std::wstring& token_handle) {
// Save token handle. This handle will be used later to determine if the
// the user has changed their password since the account was created.
Expand All @@ -2201,6 +2207,18 @@ RegisterAssociation(const std::wstring& sid,
return hr;
}

hr = SetUserProperty(sid, base::UTF8ToWide(kKeyDomain), domain);
if (FAILED(hr)) {
LOGFN(ERROR) << "SetUserProperty(domain) hr=" << putHR(hr);
return hr;
}

hr = SetUserProperty(sid, base::UTF8ToWide(kKeyUsername), username);
if (FAILED(hr)) {
LOGFN(ERROR) << "SetUserProperty(user) hr=" << putHR(hr);
return hr;
}

if (IsGemEnabled()) {
hr = SetUserProperty(sid, kKeyAcceptTos, 1u);
if (FAILED(hr)) {
Expand Down Expand Up @@ -2253,7 +2271,8 @@ HRESULT CGaiaCredentialBase::ReportResult(
// forked process fails to save association, it will enforce re-auth due to
// invalid token handle.
std::wstring sid = OLE2CW(user_sid_);
HRESULT hr = RegisterAssociation(sid, gaia_id, email, L"");
HRESULT hr = RegisterAssociation(sid, gaia_id, email, (BSTR)domain_,
(BSTR)username_, /*token_handle*/ L"");
if (FAILED(hr))
return hr;

Expand Down
199 changes: 199 additions & 0 deletions chrome/credential_provider/gaiacp/gaia_credential_base_unittests.cc
Expand Up @@ -1764,10 +1764,209 @@ TEST_F(GcpGaiaCredentialBaseAdScenariosTest,
ASSERT_EQ(S_OK, hr);
ASSERT_EQ(1u, accept_tos);

// Verify that the registry entry for the domain name was created.
wchar_t domain_reg[256];
ULONG domain_reg_length = base::size(domain_reg);
ASSERT_TRUE(
SUCCEEDED(GetUserProperty(sid_str.c_str(), base::UTF8ToWide(kKeyDomain),
domain_reg, &domain_reg_length)));
ASSERT_TRUE(domain_reg[0]);
EXPECT_TRUE(wcscmp(domain_reg, domain_name) == 0);

// Verify that the registry entry for the username was created.
wchar_t username_reg[256];
ULONG username_reg_length = base::size(username_reg);
ASSERT_TRUE(
SUCCEEDED(GetUserProperty(sid_str.c_str(), base::UTF8ToWide(kKeyUsername),
username_reg, &username_reg_length)));
ASSERT_TRUE(username_reg[0]);
EXPECT_TRUE(wcscmp(username_reg, user_name) == 0);

// Verify that the authentication results dictionary is now empty.
ASSERT_TRUE(test->IsAuthenticationResultsEmpty());
}

// Test various active directory specific sign in scenarios.
class GcpGaiaCredentialBaseAdOfflineScenariosTest
: public GcpGaiaCredentialBaseTest {
protected:
void SetUp() override;

// 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 GcpGaiaCredentialBaseAdOfflineScenariosTest::SetUp() {
GcpGaiaCredentialBaseTest::SetUp();

// Set the device as a domain joined machine.
fake_os_user_manager()->SetIsDeviceDomainJoined(true);

// Override registry to enable cloud association with google.
constexpr wchar_t kRegCloudAssociation[] = L"enable_cloud_association";
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegCloudAssociation, 1));
// Set |kKeyEnableGemFeatures| registry entry
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kKeyEnableGemFeatures, 1u));
}

// Customer configured a valid AD UPN but user is trying to login first time via
// GCPW to an account when domain controller is unreachable.
TEST_F(GcpGaiaCredentialBaseAdOfflineScenariosTest,
GetSerialization_WithAD_FirstTimeLoginUnreachableDomainController) {
// Add the user as a domain joined user.
const wchar_t user_name[] = L"ad_user";
const wchar_t password[] = L"password";

const wchar_t domain_name[] = L"ad_domain";
CComBSTR existing_user_sid;
DWORD error;
HRESULT add_domain_user_hr = fake_os_user_manager()->AddUser(
user_name, password, L"fullname", L"comment", true, domain_name,
&existing_user_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\": {\"Enhanced_desktop_security\": {\"AD_accounts\":"
"[{ \"value\": \"%ls\\\\%ls\" }]}}}",
domain_name, user_name);
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(get_cd_user_url_.c_str()), FakeWinHttpUrlFetcher::Headers(),
admin_sdk_response);

// Create provider and start logon.
Microsoft::WRL::ComPtr<ICredentialProviderCredential> cred_;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred_));

Microsoft::WRL::ComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred_.As(&test));

fake_os_user_manager()->FailFindUserBySID(existing_user_sid, 1);

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_INVALID_AD_UPN_BASE));
}

// Customer configured a valid AD UPN but user is trying to login subsequent
// times via GCPW to an account when domain controller is unreachable.
TEST_F(GcpGaiaCredentialBaseAdOfflineScenariosTest,
GetSerialization_WithAD_SubsequentLoginUnreachableDomainController) {
// Add the user as a domain joined user.
const wchar_t user_name[] = L"ad_user";
const wchar_t password[] = L"password";

const wchar_t domain_name[] = L"ad_domain";
CComBSTR existing_user_sid;
DWORD error;
HRESULT add_domain_user_hr = fake_os_user_manager()->AddUser(
user_name, password, L"fullname", L"comment", true, domain_name,
&existing_user_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\": {\"Enhanced_desktop_security\": {\"AD_accounts\":"
"[{ \"value\": \"%ls\\\\%ls\" }]}}}",
domain_name, user_name);
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(get_cd_user_url_.c_str()), FakeWinHttpUrlFetcher::Headers(),
admin_sdk_response);

// Login first time when DC is online so that the registry fallbacks for
// username and domain are set.
{
Microsoft::WRL::ComPtr<ICredentialProviderCredential> cred_;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred_));

Microsoft::WRL::ComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred_.As(&test));

ASSERT_EQ(S_OK, StartLogonProcessAndWait());

EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
ASSERT_TRUE(test->IsAdJoinedUser());

// 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));

ASSERT_EQ(S_OK, ReleaseProvider());
}

{
Microsoft::WRL::ComPtr<ICredentialProviderCredential> cred_;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred_));

Microsoft::WRL::ComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred_.As(&test));

// Make sure DC lookup fails so that the registry fallback is used.
fake_os_user_manager()->FailFindUserBySID(existing_user_sid, 1);

ASSERT_EQ(S_OK, StartLogonProcessAndWait());

EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
ASSERT_TRUE(test->IsAdJoinedUser());

// 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));

ASSERT_EQ(S_OK, ReleaseProvider());
}
}

// Test various existing local account mapping specific in cloud sign in
// scenarios.
class GcpGaiaCredentialBaseCloudLocalAccountTest
Expand Down
28 changes: 28 additions & 0 deletions chrome/credential_provider/gaiacp/gcp_utils.cc
Expand Up @@ -50,6 +50,7 @@
#include "chrome/credential_provider/gaiacp/gaia_resources.h"
#include "chrome/credential_provider/gaiacp/gcpw_strings.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/os_user_manager.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/gaiacp/token_generator.h"
#include "chrome/installer/launcher_support/chrome_launcher_support.h"
Expand Down Expand Up @@ -1374,4 +1375,31 @@ base::TimeDelta GetTimeDeltaSinceLastFetch(const std::wstring& sid,
return base::TimeDelta::FromMilliseconds(time_delta_from_last_fetch_ms);
}

HRESULT FindUserBySidWithRegistryFallback(const wchar_t* sid,
wchar_t* username,
DWORD username_length,
wchar_t* domain,
DWORD domain_length) {
HRESULT hr = OSUserManager::Get()->FindUserBySID(
sid, username, username_length, domain, domain_length);

if (FAILED(hr)) {
// Although FindUserBySID is failed, we can still obtain the domain and
// username from the user properties. This is especially needed if an AD
// workstation can't reach domain controller to login an account which
// previously logged in on the same device.
if (SUCCEEDED(GetUserProperty(sid, base::UTF8ToWide(kKeyDomain), domain,
&domain_length)) &&
SUCCEEDED(GetUserProperty(sid, base::UTF8ToWide(kKeyUsername), username,
&username_length))) {
LOGFN(VERBOSE) << "Obtained domain: " << domain
<< " and user: " << username << " from registry!";
hr = S_OK;
} else {
hr = E_FAIL;
}
}
return hr;
}

} // namespace credential_provider
9 changes: 9 additions & 0 deletions chrome/credential_provider/gaiacp/gcp_utils.h
Expand Up @@ -426,6 +426,15 @@ std::unique_ptr<base::File> GetOpenedFileForUser(const std::wstring& sid,
base::TimeDelta GetTimeDeltaSinceLastFetch(const std::wstring& sid,
const std::wstring& flag);

// Finds the username and domain for the provided user sid. Function uses the
// user properties registries as a fallback if the user can't be found via a
// network lookup call.
HRESULT FindUserBySidWithRegistryFallback(const wchar_t* sid,
wchar_t* username,
DWORD username_length,
wchar_t* domain,
DWORD domain_length);

} // namespace credential_provider

#endif // CHROME_CREDENTIAL_PROVIDER_GAIACP_GCP_UTILS_H_

0 comments on commit 2cda750

Please sign in to comment.