blob: f16c3dbd0c63faf60d26a03f1802572f8d89476a [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 <atlbase.h>
#include <atlcom.h>
#include <atlcomcli.h>
#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_writer.h"
#include "base/path_service.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/multiprocess_test.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/gaia_credential.h"
#include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h"
#include "chrome/credential_provider/test/com_fakes.h"
#include "chrome/credential_provider/test/gcp_fakes.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace credential_provider {
namespace testing {
// Corresponding default email and username for tests that don't override them.
const char kDefaultEmail[] = "foo@gmail.com";
const wchar_t kDefaultUsername[] = L"foo";
namespace switches {
const char kGlsOutputFile[] = "gls-output-file";
} // namespace switches
class DECLSPEC_UUID("3710aa3a-13c7-44c2-bc38-09ba137804d8") ITestCredential
: public IUnknown {
public:
virtual HRESULT STDMETHODCALLTYPE SetGlsEmailAddress(const char* email) = 0;
virtual HRESULT STDMETHODCALLTYPE WaitForGls() = 0;
virtual BSTR STDMETHODCALLTYPE GetFinalUsername() = 0;
virtual bool STDMETHODCALLTYPE AreCredentialsValid() = 0;
};
// Test implementation of an ICredentialProviderCredential backed by a Gaia
// account. This class overrides some methods for testing purposes.
class ATL_NO_VTABLE CTestCredential
: public CComObjectRootEx<CComMultiThreadModel>,
public CGaiaCredentialBase,
public ITestCredential {
public:
DECLARE_NO_REGISTRY()
CTestCredential();
~CTestCredential();
HRESULT FinalConstruct() { return S_OK; }
void FinalRelease() {}
private:
BEGIN_COM_MAP(CTestCredential)
COM_INTERFACE_ENTRY(IGaiaCredential)
COM_INTERFACE_ENTRY(ICredentialProviderCredential)
COM_INTERFACE_ENTRY(ITestCredential)
END_COM_MAP()
// ITestCredential.
IFACEMETHODIMP SetGlsEmailAddress(const char* email) override;
IFACEMETHODIMP WaitForGls() override;
BSTR STDMETHODCALLTYPE GetFinalUsername() override;
bool STDMETHODCALLTYPE AreCredentialsValid() override;
// IGaiaCredential.
IFACEMETHODIMP FinishAuthentication(BSTR username,
BSTR password,
BSTR fullname,
BSTR* sid,
BSTR* error_text) override;
IFACEMETHODIMP OnUserAuthenticated(BSTR username,
BSTR password,
BSTR sid) override;
// Overrides to build a dummy command line for testing.
HRESULT GetGlsCommandline(const wchar_t* email,
base::CommandLine* command_line) override;
// Override to prevent messagebox from showing up in tests.
void DisplayErrorInUI(LONG status, LONG substatus, BSTR status_text) override;
DECLARE_PROTECT_FINAL_CONSTRUCT()
// Temporary file used to store JSON response from fake GLS. This file is
// used as the stdout of GLS.
base::FilePath temp_json_file_;
std::string gls_email_ = kDefaultEmail;
base::WaitableEvent gls_done_;
};
CTestCredential::CTestCredential()
: gls_done_(base::WaitableEvent::ResetPolicy::MANUAL,
base::WaitableEvent::InitialState::NOT_SIGNALED) {
base::CreateTemporaryFile(&temp_json_file_);
}
CTestCredential::~CTestCredential() {
if (base::PathExists(temp_json_file_))
base::DeleteFile(temp_json_file_, false);
}
HRESULT CTestCredential::SetGlsEmailAddress(const char* email) {
gls_email_ = email;
return S_OK;
}
HRESULT CTestCredential::WaitForGls() {
return gls_done_.TimedWait(base::TimeDelta::FromSeconds(30))
? S_OK
: HRESULT_FROM_WIN32(WAIT_TIMEOUT);
}
BSTR CTestCredential::GetFinalUsername() {
return get_username();
}
bool CTestCredential::AreCredentialsValid() {
return CGaiaCredentialBase::AreCredentialsValid();
}
HRESULT CTestCredential::FinishAuthentication(BSTR username,
BSTR password,
BSTR fullname,
BSTR* sid,
BSTR* error_text) {
DCHECK(error_text);
*error_text = nullptr;
base::string16 comment(GetStringResource(IDS_USER_ACCOUNT_COMMENT));
HRESULT hr = CreateNewUser(OSUserManager::Get(), OLE2CW(username),
OLE2CW(password), OLE2CW(fullname),
comment.c_str(), /*add_to_users_group=*/true, sid);
EXPECT_EQ(S_OK, hr);
return hr;
}
HRESULT CTestCredential::OnUserAuthenticated(BSTR username,
BSTR password,
BSTR sid) {
HRESULT hr = FinishOnUserAuthenticated(username, password, sid);
gls_done_.Signal();
return hr;
}
HRESULT CTestCredential::GetGlsCommandline(const wchar_t* /*email*/,
base::CommandLine* command_line) {
base::DictionaryValue dict;
dict.SetString(kKeyEmail, gls_email_);
dict.SetString(kKeyFullname, "Full Name");
dict.SetString(kKeyId, "1234567890");
dict.SetString(kKeyMdmIdToken, "idt-123456");
dict.SetString(kKeyPassword, "password");
dict.SetString(kKeyRefreshToken, "rt-123456");
dict.SetString(kKeyTokenHandle, "th-123456");
std::string json;
if (!base::JSONWriter::Write(dict, &json))
return E_FAIL;
if (base::WriteFile(temp_json_file_, json.c_str(), json.length()) == -1)
return HRESULT_FROM_WIN32(::GetLastError());
base::FilePath system_dir;
if (!base::PathService::Get(base::DIR_SYSTEM, &system_dir))
return HRESULT_FROM_WIN32(::GetLastError());
*command_line = base::GetMultiProcessTestChildBaseCommandLine();
command_line->AppendSwitchASCII(::switches::kTestChildProcess, "gls_main");
command_line->AppendSwitchPath(switches::kGlsOutputFile, temp_json_file_);
// Reset the manual event since GLS will be started upon return.
gls_done_.Reset();
return S_OK;
}
void CTestCredential::DisplayErrorInUI(LONG status,
LONG substatus,
BSTR status_text) {
// This function is called instead of OnUserAuthenticated() when errors occur,
// so signal that GLS is done.
gls_done_.Signal();
}
// Writes the file specified by the command line argument kGlsOutputFile to
// stdout. This is used as a fake GLS process for testing. The files will
// be very small, maybe a couple of hundred characters, so fine to load into
// memory.
MULTIPROCESS_TEST_MAIN(gls_main) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
base::FilePath path =
command_line->GetSwitchValuePath(switches::kGlsOutputFile);
std::string contents;
if (base::ReadFileToString(path, &contents)) {
HANDLE hstdout = ::GetStdHandle(STD_OUTPUT_HANDLE);
DWORD written;
if (::WriteFile(hstdout, contents.c_str(), contents.length(), &written,
nullptr)) {
return 0;
}
}
return -1;
}
} // namespace testing
namespace {
HRESULT CreateCredential(ICredentialProviderCredential** credential) {
return CComCreator<CComObject<testing::CTestCredential>>::CreateInstance(
nullptr, IID_ICredentialProviderCredential,
reinterpret_cast<void**>(credential));
}
HRESULT CreateCredentialWithProvider(
IGaiaCredentialProvider* provider,
IGaiaCredential** gaia_credential,
ICredentialProviderCredential** credential) {
HRESULT hr = CreateCredential(credential);
if (SUCCEEDED(hr)) {
hr = (*credential)
->QueryInterface(IID_IGaiaCredential,
reinterpret_cast<void**>(gaia_credential));
if (SUCCEEDED(hr))
hr = (*gaia_credential)->Initialize(provider);
}
return hr;
}
} // namespace
class GcpGaiaCredentialBaseTest : public ::testing::Test {
public:
GcpGaiaCredentialBaseTest();
HRESULT StartLogonProcessAndWait(ICredentialProviderCredential* cred);
FakeOSUserManager* fake_os_user_manager() { return &fake_os_user_manager_; }
private:
FakeOSProcessManager fake_os_process_manager_;
FakeOSUserManager fake_os_user_manager_;
FakeScopedLsaPolicyFactory fake_scoped_lsa_policy_factory_;
};
GcpGaiaCredentialBaseTest::GcpGaiaCredentialBaseTest() {
// 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(kGaiaAccountName, L"password",
L"fullname", L"comment", true,
&sid, &error));
auto policy = ScopedLsaPolicy::Create(POLICY_ALL_ACCESS);
EXPECT_EQ(S_OK, policy->StorePrivateData(kLsaKeyGaiaPassword, L"password"));
}
HRESULT GcpGaiaCredentialBaseTest::StartLogonProcessAndWait(
ICredentialProviderCredential* cred) {
BOOL auto_login;
EXPECT_EQ(S_OK, 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,
cred->GetSerialization(&cpgsr, &cpcs, &status_text, &status_icon));
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPSI_NONE, status_icon);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
CComPtr<testing::ITestCredential> test;
EXPECT_EQ(S_OK, cred->QueryInterface(__uuidof(testing::ITestCredential),
reinterpret_cast<void**>(&test)));
EXPECT_EQ(S_OK, test->WaitForGls());
return S_OK;
}
TEST_F(GcpGaiaCredentialBaseTest, Advise) {
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredential(&cred));
ASSERT_EQ(S_OK, cred->Advise(nullptr));
ASSERT_EQ(S_OK, cred->UnAdvise());
}
TEST_F(GcpGaiaCredentialBaseTest, SetSelected) {
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredential(&cred));
// A credential that has not attempted to sign in a user yet should return
// false for |auto_login|.
BOOL auto_login;
ASSERT_EQ(S_OK, cred->SetSelected(&auto_login));
ASSERT_FALSE(auto_login);
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Start) {
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Finish) {
FakeGaiaCredentialProvider provider;
// Start logon.
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
// Now finish the logon.
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE cpgsr;
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION cpcs;
wchar_t* status_text;
CREDENTIAL_PROVIDER_STATUS_ICON status_icon;
ASSERT_EQ(S_OK,
cred->GetSerialization(&cpgsr, &cpcs, &status_text, &status_icon));
ASSERT_EQ(nullptr, status_text);
ASSERT_EQ(CPSI_NONE, status_icon);
ASSERT_EQ(CPGSR_RETURN_CREDENTIAL_FINISHED, cpgsr);
ASSERT_LT(0u, cpcs.cbSerialization);
ASSERT_NE(nullptr, cpcs.rgbSerialization);
// State was reset.
ASSERT_FALSE(test->AreCredentialsValid());
// Make sure a "foo" user was created.
PSID sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->GetUserSID(testing::kDefaultUsername,
&sid));
::LocalFree(sid);
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("foo@imfl.info"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"foo_imfl"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD_Gmail) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("bar@gmail.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"bar"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD_Googlemail) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("toto@googlemail.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"toto"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, InvalidUsernameCharacters) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("a\\[]:|<>+=;?*z@gmail.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"a____________z"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, EmailTooLong) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK,
test->SetGlsEmailAddress("areallylongemailadressdude@gmail.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"areallylongemailadre"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, EmailTooLong2) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("foo@areallylongdomaindude.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"foo_areallylongdomai"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsNoAt) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("foo"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"foo_gmail"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsAtCom) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("@com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"_com"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsAtDotCom) {
USES_CONVERSION;
FakeGaiaCredentialProvider provider;
CComPtr<IGaiaCredential> gaia_cred;
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, CreateCredentialWithProvider(&provider, &gaia_cred, &cred));
CComPtr<testing::ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress("@.com"));
ASSERT_EQ(S_OK, StartLogonProcessAndWait(cred));
ASSERT_STREQ(W2COLE(L"_.com"), test->GetFinalUsername());
ASSERT_EQ(S_OK, gaia_cred->Terminate());
}
} // namespace credential_provider