Commit a299fabe authored by Rakesh Soma's avatar Rakesh Soma Committed by Commit Bot

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: default avatarTien Mai <tienmai@chromium.org>
Cr-Commit-Position: refs/heads/master@{#672133}
parent f27ee9fe
......@@ -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 credential_provider {
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 @@ base::string16 GetEmailDomains() {
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 @@ base::string16 GetEmailDomains() {
// 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 @@ void MakeUsernameForAccount(const base::Value& result,
*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);
// 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;
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 @@ void MakeUsernameForAccount(const base::Value& result,
}
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 @@ HRESULT CGaiaCredentialBase::ValidateOrCreateUser(const base::Value& result,
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 @@ HRESULT CGaiaCredentialBase::ValidateOrCreateUser(const base::Value& result,
// 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 @@ HRESULT CGaiaCredentialBase::ValidateOrCreateUser(const base::Value& result,
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);
......
......@@ -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 @@ TEST_F(GcpGaiaCredentialBaseTest, EmailIsAtDotCom) {
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.
......
......@@ -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 @@ void SecurelyClearBuffer(void* buffer, size_t length) {
::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());
......
......@@ -232,9 +232,18 @@ void SecurelyClearBuffer(void* buffer, size_t length);
// 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);
......
......@@ -72,6 +72,11 @@ void OSUserManager::SetInstanceForTesting(OSUserManager* instance) {
*GetInstanceStorage() = instance;
}
// 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.
......
......@@ -99,6 +99,9 @@ class [[clang::lto_visibility_public]] OSUserManager {
// to another.
static void SetInstanceForTesting(OSUserManager* instance);
// Checks if the device is domain joined.
virtual bool IsDeviceDomainJoined();
protected:
OSUserManager() {}
......
......@@ -69,6 +69,12 @@ HRESULT WinHttpUrlFetcher::SetRequestBody(const char* body) {
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);
......@@ -93,6 +99,19 @@ HRESULT WinHttpUrlFetcher::Fetch(std::vector<char>* response) {
connect.Set(connect_tmp);
}
{
// 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(
......
......@@ -27,6 +27,7 @@ class WinHttpUrlFetcher {
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 @@ class WinHttpUrlFetcher {
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.
......
......@@ -141,6 +141,7 @@ HRESULT FakeOSUserManager::AddUser(const wchar_t* username,
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 @@ HRESULT FakeOSUserManager::AddUser(const wchar_t* username,
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 @@ HRESULT FakeOSUserManager::CreateLogonToken(const wchar_t* domain,
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 @@ void FakeWinHttpUrlFetcherFactory::SetFakeResponse(
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 @@ bool FakeWinHttpUrlFetcher::IsValid() const {
}
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);
......
......@@ -69,6 +69,15 @@ class FakeOSUserManager : public OSUserManager {
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 @@ class FakeOSUserManager : public OSUserManager {
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 @@ class FakeOSUserManager : public OSUserManager {
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 @@ class FakeWinHttpUrlFetcherFactory {
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 @@ class FakeWinHttpUrlFetcherFactory {
};
std::map<GURL, Response> fake_responses_;
std::map<GURL, HRESULT> failed_http_fetch_hr_;
size_t requests_created_ = 0;
};
......@@ -316,6 +329,7 @@ class FakeWinHttpUrlFetcher : public WinHttpUrlFetcher {
Headers response_headers_;
std::string response_;
HANDLE send_response_event_handle_;
HRESULT response_hr_ = S_OK;
};
///////////////////////////////////////////////////////////////////////////////
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment