blob: 7768c42e54b5bde4ba7d548ac0843d15560608ea [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "gls_runner_test_base.h"
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/json/json_writer.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "chrome/credential_provider/gaiacp/gaia_credential_provider_filter.h"
#include "chrome/credential_provider/gaiacp/scoped_lsa_policy.h"
#include "chrome/credential_provider/test/test_credential.h"
#include "testing/multiprocess_func_list.h"
namespace credential_provider {
namespace switches {
constexpr char kDefaultExitCode[] = "default-exit-code";
constexpr char kIgnoreExpectedGaiaId[] = "ignore-expected-gaia-id";
constexpr char kGlsUserEmail[] = "gls-user-email";
constexpr char kStartGlsEventName[] = "start-gls-event-name";
constexpr char kOverrideGaiaId[] = "override-gaia-id";
constexpr char kOverrideGaiaPassword[] = "override-gaia-password";
} // namespace switches
namespace testing {
// Corresponding default email and username for tests that don't override them.
const char kDefaultEmail[] = "foo@gmail.com";
const char kDefaultGaiaId[] = "test-gaia-id";
const wchar_t kDefaultUsername[] = L"foo";
const char kDefaultInvalidTokenHandleResponse[] = "{}";
const char kDefaultValidTokenHandleResponse[] = "{\"expires_in\":1}";
namespace {
// Generates a common signin result given an email pass through the command
// line and writes this result to stdout. This is used as a fake GLS process
// for testing.
MULTIPROCESS_TEST_MAIN(gls_main) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
// If a start event name is specified, the process waits for an event from the
// tester telling it that it can start running.
if (command_line->HasSwitch(switches::kStartGlsEventName)) {
base::string16 start_event_name =
command_line->GetSwitchValueNative(switches::kStartGlsEventName);
if (!start_event_name.empty()) {
base::win::ScopedHandle start_event_handle(::CreateEvent(
nullptr, false, false, base::UTF16ToWide(start_event_name).c_str()));
if (start_event_handle.IsValid()) {
base::WaitableEvent start_event(std::move(start_event_handle));
start_event.Wait();
}
}
}
int default_exit_code = kUiecSuccess;
EXPECT_TRUE(base::StringToInt(
command_line->GetSwitchValueASCII(switches::kDefaultExitCode),
&default_exit_code));
std::string gls_email =
command_line->GetSwitchValueASCII(switches::kGlsUserEmail);
std::string gaia_id_override =
command_line->GetSwitchValueASCII(switches::kOverrideGaiaId);
std::string gaia_password =
command_line->GetSwitchValueASCII(switches::kOverrideGaiaPassword);
std::string expected_gaia_id =
command_line->GetSwitchValueASCII(kGaiaIdSwitch);
std::string expected_email =
command_line->GetSwitchValueASCII(kPrefillEmailSwitch);
if (expected_email.empty()) {
expected_email = gls_email;
} else {
EXPECT_EQ(gls_email, std::string());
}
if (expected_gaia_id.empty())
expected_gaia_id = kDefaultGaiaId;
if (gaia_password.empty())
gaia_password = "password";
if (command_line->HasSwitch(switches::kIgnoreExpectedGaiaId)) {
DCHECK(!gaia_id_override.empty());
expected_gaia_id = gaia_id_override;
}
base::Value dict(base::Value::Type::DICTIONARY);
if (!gaia_id_override.empty() && gaia_id_override != expected_gaia_id) {
dict.SetIntKey(kKeyExitCode, kUiecEMailMissmatch);
} else {
dict.SetIntKey(kKeyExitCode, static_cast<UiExitCodes>(default_exit_code));
dict.SetStringKey(kKeyEmail, expected_email);
dict.SetStringKey(kKeyFullname, "Full Name");
dict.SetStringKey(kKeyId, expected_gaia_id);
dict.SetStringKey(kKeyAccessToken, "at-123456");
dict.SetStringKey(kKeyMdmIdToken, "idt-123456");
dict.SetStringKey(kKeyPassword, gaia_password);
dict.SetStringKey(kKeyRefreshToken, "rt-123456");
dict.SetStringKey(kKeyTokenHandle, "th-123456");
}
std::string json;
if (!base::JSONWriter::Write(dict, &json))
return -1;
HANDLE hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
if (::WriteFile(hstdout, json.c_str(), json.length(), &written, nullptr)) {
return 0;
}
return -1;
}
} // namespace
GlsRunnerTestBase::GlsRunnerTestBase()
: cpus_(CPUS_LOGON),
default_token_handle_response_(kDefaultValidTokenHandleResponse) {}
GlsRunnerTestBase::~GlsRunnerTestBase() = default;
void GlsRunnerTestBase::SetUp() {
// Create the special gaia account used to run GLS and save its password.
BSTR sid;
DWORD error;
EXPECT_EQ(S_OK, fake_os_user_manager()->AddUser(
kDefaultGaiaAccountName, L"password", L"fullname",
L"comment", true, &sid, &error));
auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);
EXPECT_EQ(S_OK, policy->StorePrivateData(kLsaKeyGaiaUsername,
kDefaultGaiaAccountName));
EXPECT_EQ(S_OK, policy->StorePrivateData(kLsaKeyGaiaPassword, L"password"));
// Make sure not to read random GCPW settings from the machine that is running
// the tests.
InitializeRegistryOverrideForTesting(&registry_override_);
}
void GlsRunnerTestBase::TearDown() {
// If credential has not been explicitly completed and the logon process
// was started, then complete here under the assumption that it will
// complete successfully.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
ASSERT_EQ(S_OK, ReleaseProvider());
}
HRESULT GlsRunnerTestBase::ReleaseProvider() {
if (!gaia_provider_)
return S_OK;
// If any logon are still pending, they are about to be killed now.
logon_process_started_successfully_ = false;
HRESULT hr = S_OK;
// Complete the release of the provider.
// Unadvise all the credentials.
DWORD count;
DWORD default_index;
BOOL autologon;
HRESULT get_count_hr =
gaia_provider_->GetCredentialCount(&count, &default_index, &autologon);
if (SUCCEEDED(get_count_hr)) {
for (DWORD i = 0; i < count; ++i) {
CComPtr<ICredentialProviderCredential> credential;
HRESULT get_hr = gaia_provider_->GetCredentialAt(i, &credential);
EXPECT_EQ(get_hr, S_OK);
if (SUCCEEDED(get_hr)) {
get_hr = credential->UnAdvise();
if (FAILED(get_hr))
hr = get_hr;
} else {
hr = get_hr;
}
}
} else {
hr = get_count_hr;
}
// Unadvise the provider.
HRESULT unadvise_hr = gaia_provider_->UnAdvise();
if (FAILED(unadvise_hr))
hr = unadvise_hr;
gaia_provider_.Release();
return hr;
}
HRESULT
GlsRunnerTestBase::InitializeProviderWithCredentials(
DWORD* credential_count,
ICredentialProvider** provider) {
HRESULT hr = InternalInitializeProvider(nullptr, credential_count);
if (FAILED(hr))
return hr;
return gaia_provider_.QueryInterface(provider);
}
HRESULT GlsRunnerTestBase::InitializeProviderWithRemoteCredentials(
const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs_in,
ICredentialProvider** provider) {
HRESULT hr = InternalInitializeProvider(pcpcs_in, nullptr);
if (FAILED(hr))
return hr;
return gaia_provider_.QueryInterface(provider);
}
HRESULT GlsRunnerTestBase::InitializeProviderAndGetCredential(
DWORD index,
ICredentialProviderCredential** credential) {
DCHECK(credential);
*credential = nullptr;
DWORD count = 0;
HRESULT hr = InternalInitializeProvider(nullptr, &count);
if (FAILED(hr))
return hr;
if (index >= count)
return E_FAIL;
// Reference specific credential that was requested.
hr = gaia_provider_->GetCredentialAt(index, &testing_cred_);
if (FAILED(hr))
return hr;
EXPECT_EQ(S_OK, testing_cred_.QueryInterface(credential));
return S_OK;
}
HRESULT GlsRunnerTestBase::InternalInitializeProvider(
const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs_in,
DWORD* count) {
if (count)
*count = 0;
CComPtr<ICredentialProvider> provider;
HRESULT hr =
CComCreator<CComObject<CTestGaiaCredentialProvider>>::CreateInstance(
nullptr, IID_ICredentialProvider,
reinterpret_cast<void**>(&provider));
if (FAILED(hr))
return hr;
// Apply the filter and get remote credentials as needed.
{
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION remote_credentials;
HRESULT update_remote_credentials_hr;
hr = ApplyProviderFilter(provider, pcpcs_in, &remote_credentials,
&update_remote_credentials_hr);
if (FAILED(hr))
return hr;
// Start process for logon screen.
hr = provider->SetUsageScenario(cpus_, 0);
if (FAILED(hr))
return hr;
if (SUCCEEDED(update_remote_credentials_hr) &&
remote_credentials.rgbSerialization) {
// Apply remote credentials if any.
if (remote_credentials.clsidCredentialProvider ==
CLSID_GaiaCredentialProvider) {
provider->SetSerialization(&remote_credentials);
}
::CoTaskMemFree(remote_credentials.rgbSerialization);
}
}
// Give list of users visible on welcome screen.
CComPtr<ICredentialProviderSetUserArray> provider_user_array;
hr = provider.QueryInterface(&provider_user_array);
if (FAILED(hr))
return hr;
// All users are shown if the usage is not for unlocking the workstation.
bool all_users_shown = cpus_ != CPUS_UNLOCK_WORKSTATION;
CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS cpao;
ICredentialProviderUserArray* user_array = fake_user_array();
hr = user_array->GetAccountOptions(&cpao);
if (FAILED(hr))
return hr;
bool other_user_tile_available = cpao == CPAO_EMPTY_LOCAL;
for (auto& sid_and_username : fake_os_user_manager_.GetUsers()) {
// If not all the users are shown, the user that locked the system is
// the only one that is in the user array (if the other user tile is
// not available).
if (!all_users_shown &&
((!sid_locking_workstation_.empty() &&
sid_locking_workstation_ != sid_and_username.first) ||
other_user_tile_available)) {
continue;
}
fake_user_array_.AddUser(sid_and_username.first.c_str(),
sid_and_username.second.c_str());
}
hr = provider_user_array->SetUserArray(&fake_user_array_);
if (FAILED(hr))
return hr;
// Activate the CP.
FakeCredentialProviderEvents events;
hr = provider->Advise(&fake_provider_events_, 0);
if (FAILED(hr))
return hr;
// This class can now take ownership of the provider so that it can
// be correctly uninitialized later.
gaia_provider_ = provider;
// GetCredentialCount must be called to initialize the credentials (if
// desired).
if (count) {
DWORD default_index;
BOOL autologon;
hr = gaia_provider_->GetCredentialCount(count, &default_index, &autologon);
if (FAILED(hr))
return hr;
EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index);
EXPECT_FALSE(autologon);
// Advise all the credentials
for (DWORD i = 0; i < *count; ++i) {
CComPtr<ICredentialProviderCredential> current_credential;
hr = gaia_provider_->GetCredentialAt(i, &current_credential);
if (FAILED(hr))
break;
hr = current_credential->Advise(nullptr);
if (FAILED(hr))
break;
}
}
// Verify fields.
DWORD field_count;
hr = gaia_provider_->GetFieldDescriptorCount(&field_count);
if (FAILED(hr))
return hr;
for (DWORD i = 0; i < field_count; ++i) {
CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR* ppcpfd;
hr = gaia_provider_->GetFieldDescriptorAt(i, &ppcpfd);
if (FAILED(hr))
return hr;
}
return S_OK;
}
HRESULT GlsRunnerTestBase::ApplyProviderFilter(
const CComPtr<ICredentialProvider>& provider,
const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs_in,
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs_out,
HRESULT* update_remote_credentials_hr) {
if (update_remote_credentials_hr)
*update_remote_credentials_hr = E_NOTIMPL;
// Filter only lives long enough to apply filter and get serialization
// credentials.
CComPtr<ICredentialProviderFilter> filter;
HRESULT hr =
CComCreator<CComObject<CGaiaCredentialProviderFilter>>::CreateInstance(
nullptr, IID_ICredentialProviderFilter, (void**)&filter);
if (FAILED(hr))
return hr;
// Set token fetch result before starting the filter.
fake_http_url_fetcher_factory_.SetFakeResponse(
GURL(AssociatedUserValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), default_token_handle_response_);
// Start initial refresh of token handles. The filter will apply user access
// restrictions as needed.
fake_associated_user_validator_.StartRefreshingTokenHandleValidity();
// Perform initial filter code.
hr = filter->Filter(cpus_, 0, nullptr, nullptr, 0);
if (FAILED(hr))
return hr;
// Apply remote credentials if any.
if (pcpcs_in && pcpcs_out && update_remote_credentials_hr)
*update_remote_credentials_hr =
filter->UpdateRemoteCredential(pcpcs_in, pcpcs_out);
return S_OK;
}
HRESULT GlsRunnerTestBase::StartLogonProcess(bool succeeds) {
DCHECK(testing_cred_);
DCHECK(!logon_process_started_successfully_);
BOOL auto_login;
EXPECT_EQ(S_OK, testing_cred_->SetSelected(&auto_login));
// Logging on is an async process, so the call to GetSerialization() starts
// the process, but when it returns it has not completed.
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE cpgsr;
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION cpcs;
wchar_t* status_text;
CREDENTIAL_PROVIDER_STATUS_ICON status_icon;
EXPECT_EQ(S_OK, testing_cred_->GetSerialization(&cpgsr, &cpcs, &status_text,
&status_icon));
EXPECT_EQ(CPSI_NONE, status_icon);
if (succeeds) {
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
logon_process_started_successfully_ = true;
} else {
EXPECT_NE(nullptr, status_text);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_FINISHED, cpgsr);
}
return S_OK;
}
HRESULT GlsRunnerTestBase::WaitForLogonProcess() {
CComPtr<testing::ITestCredential> test;
HRESULT hr = testing_cred_->QueryInterface(&test);
if (FAILED(hr))
return hr;
return test->WaitForGls();
}
HRESULT GlsRunnerTestBase::StartLogonProcessAndWait() {
HRESULT hr = StartLogonProcess(/*succeeds=*/true);
if (FAILED(hr))
return hr;
return WaitForLogonProcess();
}
// static
HRESULT GlsRunnerTestBase::GetFakeGlsCommandline(
UiExitCodes default_exit_code,
const std::string& gls_email,
const std::string& gaia_id_override,
const std::string& gaia_password,
const base::string16& start_gls_event_name,
bool ignore_expected_gaia_id,
base::CommandLine* command_line) {
*command_line = base::GetMultiProcessTestChildBaseCommandLine();
command_line->AppendSwitchASCII(::switches::kTestChildProcess, "gls_main");
command_line->AppendSwitchASCII(switches::kGlsUserEmail, gls_email);
command_line->AppendSwitchNative(switches::kDefaultExitCode,
base::NumberToString16(default_exit_code));
if (ignore_expected_gaia_id)
command_line->AppendSwitch(switches::kIgnoreExpectedGaiaId);
if (!gaia_id_override.empty()) {
command_line->AppendSwitchASCII(switches::kOverrideGaiaId,
gaia_id_override);
}
if (!gaia_password.empty()) {
command_line->AppendSwitchASCII(switches::kOverrideGaiaPassword,
gaia_password);
}
if (!start_gls_event_name.empty()) {
command_line->AppendSwitchNative(switches::kStartGlsEventName,
start_gls_event_name);
}
return S_OK;
}
HRESULT GlsRunnerTestBase::FinishLogonProcess(
bool expected_success,
bool expected_credentials_change_fired,
int expected_error_message) {
// If no logon process was started, there is nothing to finish.
if (!logon_process_started_successfully_)
return S_OK;
CComPtr<ICredentialProviderCredential> local_testing_cred = testing_cred_;
// Release ownership on the testing_cred_ which should be finishing.
testing_cred_.Release();
HRESULT hr = FinishLogonProcessWithCred(
expected_success, expected_credentials_change_fired,
expected_error_message, local_testing_cred);
EXPECT_EQ(hr, S_OK);
if (FAILED(hr))
return hr;
if (expected_credentials_change_fired) {
hr = ReportLogonProcessResult(local_testing_cred);
EXPECT_EQ(hr, S_OK);
return hr;
}
return S_OK;
}
HRESULT GlsRunnerTestBase::FinishLogonProcessWithCred(
bool expected_success,
bool expected_credentials_change_fired,
int expected_error_message,
const CComPtr<ICredentialProviderCredential>& local_testing_cred) {
// If no logon process was started, there is nothing to finish.
if (!logon_process_started_successfully_)
return S_OK;
logon_process_started_successfully_ = false;
DCHECK(gaia_provider_);
CComPtr<ITestCredential> test_cred;
HRESULT hr = local_testing_cred.QueryInterface(&test_cred);
if (FAILED(hr))
return hr;
CComPtr<ITestCredentialProvider> test_provider;
hr = gaia_provider_.QueryInterface(&test_provider);
if (FAILED(hr))
return hr;
EXPECT_EQ(test_provider->credentials_changed_fired(),
expected_success && expected_credentials_change_fired);
if (!expected_success) {
// Check that values were not propagated to the provider.
EXPECT_EQ(0u, test_provider->username().Length());
EXPECT_EQ(0u, test_provider->password().Length());
EXPECT_EQ(0u, test_provider->sid().Length());
if (expected_error_message) {
EXPECT_STREQ(test_cred->GetErrorText(),
GetStringResource(expected_error_message).c_str());
} else {
EXPECT_EQ(test_cred->GetErrorText(), nullptr);
}
return S_OK;
}
// Call final GetSerialization and expect it to be finished.
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE cpgsr;
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION cpcs;
wchar_t* status_text;
CREDENTIAL_PROVIDER_STATUS_ICON status_icon;
hr = local_testing_cred->GetSerialization(&cpgsr, &cpcs, &status_text,
&status_icon);
if (FAILED(hr))
return hr;
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPSI_SUCCESS, status_icon);
EXPECT_EQ(CPGSR_RETURN_CREDENTIAL_FINISHED, cpgsr);
EXPECT_LT(0u, cpcs.cbSerialization);
EXPECT_NE(nullptr, cpcs.rgbSerialization);
// Check that values were propagated to the provider.
if (expected_credentials_change_fired) {
EXPECT_NE(0u, test_provider->username().Length());
EXPECT_NE(0u, test_provider->password().Length());
EXPECT_NE(0u, test_provider->sid().Length());
}
EXPECT_EQ(test_cred->GetErrorText(), nullptr);
return S_OK;
}
HRESULT GlsRunnerTestBase::ReportLogonProcessResult(
const CComPtr<ICredentialProviderCredential>& local_testing_cred) {
CComPtr<ITestCredential> test_cred;
HRESULT hr = local_testing_cred.QueryInterface(&test_cred);
if (FAILED(hr))
return hr;
// State was not reset.
EXPECT_TRUE(test_cred->AreCredentialsValid());
wchar_t* report_status_text = nullptr;
CREDENTIAL_PROVIDER_STATUS_ICON report_icon;
hr =
local_testing_cred->ReportResult(0, 0, &report_status_text, &report_icon);
if (FAILED(hr))
return hr;
// State was reset.
EXPECT_FALSE(test_cred->AreCredentialsValid());
return S_OK;
}
} // namespace testing
} // namespace credential_provider