blob: dbec6870441907adc2b107ba6decde87f5d4546d [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 <windows.h>
#include <sddl.h> // For ConvertSidToStringSid()
#include "base/strings/utf_string_conversions.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/gaia_credential_base.h"
#include "chrome/credential_provider/gaiacp/gaia_resources.h"
#include "chrome/credential_provider/gaiacp/mdm_utils.h"
#include "chrome/credential_provider/gaiacp/password_recovery_manager.h"
#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 "testing/gtest/include/gtest/gtest.h"
namespace credential_provider {
namespace testing {
constexpr char kFakeResourceId[] = "fake_resource_id";
class GcpGaiaCredentialBaseTest : public GlsRunnerTestBase {};
TEST_F(GcpGaiaCredentialBaseTest, Advise) {
// Create provider with credentials. This should Advise the credential.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
// Release ref count so the credential can be deleted by the call to
// ReleaseProvider.
cred.Release();
// Release the provider. This should unadvise the credential.
ASSERT_EQ(S_OK, ReleaseProvider());
}
TEST_F(GcpGaiaCredentialBaseTest, SetSelected) {
// Create provider and credential only.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &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_NoInternet) {
FakeInternetAvailabilityChecker internet_checker(
FakeInternetAvailabilityChecker::kHicForceNo);
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcess(/*succeeds=*/false));
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Start) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Finish) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
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());
}
// This test emulates the scenario where SetDeselected is triggered by the
// Windows Login UI process after GetSerialization prior to invocation of
// ReportResult. Note: This currently happens only for OtherUser credential
// workflow.
TEST_F(GcpGaiaCredentialBaseTest,
GetSerialization_SetDeselectedBeforeReportResult) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
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));
// New user should be created.
EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
// Finishing logon process should trigger credential changed and trigger
// GetSerialization.
ASSERT_EQ(S_OK, FinishLogonProcessWithCred(true, true, 0, cred));
// Trigger SetDeselected prior to ReportResult is invoked.
cred->SetDeselected();
// Verify that the authentication results dictionary is not empty.
ASSERT_FALSE(test->IsAuthenticationResultsEmpty());
// Trigger ReportResult and verify that the authentication results are saved
// into registry and ResetInternalState is triggered.
ReportLogonProcessResult(cred);
// Verify that the registry entry for the user was created.
wchar_t gaia_id[256];
ULONG length = base::size(gaia_id);
wchar_t* sidstr = nullptr;
::ConvertSidToStringSid(sid, &sidstr);
::LocalFree(sid);
HRESULT gaia_id_hr = GetUserProperty(sidstr, 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());
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Abort) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetDefaultExitCode(kUiecAbort));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Logon process should not signal credentials change or raise an error
// message.
ASSERT_EQ(S_OK, FinishLogonProcess(false, false, 0));
}
TEST_F(GcpGaiaCredentialBaseTest,
GetSerialization_AssociateToMatchingAssociatedUser) {
USES_CONVERSION;
// Create a fake user that has the same gaia id as the test gaia id.
CComBSTR first_sid;
base::string16 username(L"foo");
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
username, L"password", L"name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(),
&first_sid));
ASSERT_EQ(2ul, fake_os_user_manager()->GetUserCount());
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// User should have been associated.
EXPECT_EQ(test->GetFinalUsername(), username);
// Email should be the same as the default one.
EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
// No new user should be created.
EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_MultipleCalls) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr wchar_t kStartGlsEventName[] =
L"GetSerialization_MultipleCalls_Wait";
base::win::ScopedHandle start_event_handle(
::CreateEvent(nullptr, false, false, kStartGlsEventName));
ASSERT_TRUE(start_event_handle.IsValid());
ASSERT_EQ(S_OK, test->SetStartGlsEventName(kStartGlsEventName));
base::WaitableEvent start_event(std::move(start_event_handle));
ASSERT_EQ(S_OK, StartLogonProcess(/*succeeds=*/true));
// Calling GetSerialization again while the credential is waiting for the
// logon process should yield CPGSR_NO_CREDENTIAL_NOT_FINISHED as a
// response.
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);
// Signal that the gls process can finish.
start_event.Signal();
ASSERT_EQ(S_OK, WaitForLogonProcess());
}
TEST_F(GcpGaiaCredentialBaseTest,
GetSerialization_PasswordChangedForAssociatedUser) {
USES_CONVERSION;
// Create a fake user for which the windows password does not match the gaia
// password supplied by the test gls process.
CComBSTR sid;
CComBSTR windows_password = L"password2";
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
L"foo", (BSTR)windows_password, L"Full Name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Check that the process has not finished yet.
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));
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
// Credentials should still be available.
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Set an invalid password and try to get serialization again. Credentials
// should still be valid but serialization is not complete.
CComBSTR invalid_windows_password = L"a";
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, invalid_windows_password);
EXPECT_EQ(nullptr, status_text);
ASSERT_EQ(S_OK,
cred->GetSerialization(&cpgsr, &cpcs, &status_text, &status_icon));
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
// Update the Windows password to be the real password created for the user.
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, windows_password);
// Sign in information should still be available.
EXPECT_TRUE(test->GetFinalEmail().length());
// Both Windows and Gaia credentials should be valid now
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_FALSE, test->IsWindowsPasswordValidForStoredUser());
// Finish logon successfully but with no credential changed event.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
}
TEST_F(GcpGaiaCredentialBaseTest,
GetSerialization_ForgotPasswordForAssociatedUser) {
USES_CONVERSION;
// Create a fake user for which the windows password does not match the gaia
// password supplied by the test gls process.
CComBSTR sid;
CComBSTR windows_password = L"password2";
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
L"foo", (BSTR)windows_password, L"Full Name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Check that the process has not finished yet.
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));
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
// Credentials should still be available.
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Simulate a click on the "Forgot Password" link.
cred->CommandLinkClicked(FID_FORGOT_PASSWORD_LINK);
// Finish logon successfully but with no credential changed event.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
}
TEST_F(GcpGaiaCredentialBaseTest,
GetSerialization_AlternateForgotPasswordAssociatedUser) {
USES_CONVERSION;
// Create a fake user for which the windows password does not match the gaia
// password supplied by the test gls process.
CComBSTR sid;
CComBSTR windows_password = L"password2";
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
L"foo", (BSTR)windows_password, L"Full Name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Check that the process has not finished yet.
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));
EXPECT_EQ(nullptr, status_text);
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
// Credentials should still be available.
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_OK, test->IsWindowsPasswordValidForStoredUser());
// Simulate a click on the "Forgot Password" link.
cred->CommandLinkClicked(FID_FORGOT_PASSWORD_LINK);
// Go back to windows password entry.
cred->CommandLinkClicked(FID_FORGOT_PASSWORD_LINK);
// Set an invalid password and try to get serialization again. Credentials
// should still be valid but serialization is not complete.
CComBSTR invalid_windows_password = L"a";
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, invalid_windows_password);
EXPECT_EQ(nullptr, status_text);
ASSERT_EQ(S_OK,
cred->GetSerialization(&cpgsr, &cpcs, &status_text, &status_icon));
EXPECT_EQ(CPGSR_NO_CREDENTIAL_NOT_FINISHED, cpgsr);
// Update the Windows password to be the real password created for the user.
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, windows_password);
// Sign in information should still be available.
EXPECT_TRUE(test->GetFinalEmail().length());
// Both Windows and Gaia credentials should be valid now
EXPECT_TRUE(test->CanAttemptWindowsLogon());
EXPECT_EQ(S_FALSE, test->IsWindowsPasswordValidForStoredUser());
// Finish logon successfully but with no credential changed event.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
}
TEST_F(GcpGaiaCredentialBaseTest, GetSerialization_Cancel) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
// This event is merely used to keep the gls running while it is cancelled
// through SetDeselected().
constexpr wchar_t kStartGlsEventName[] = L"GetSerialization_Cancel_Signal";
base::win::ScopedHandle start_event_handle(
::CreateEvent(nullptr, false, false, kStartGlsEventName));
ASSERT_TRUE(start_event_handle.IsValid());
ASSERT_EQ(S_OK, test->SetStartGlsEventName(kStartGlsEventName));
base::WaitableEvent start_event(std::move(start_event_handle));
ASSERT_EQ(S_OK, StartLogonProcess(/*succeeds=*/true));
// Deselect the credential provider so that it cancels the GLS process and
// returns.
ASSERT_EQ(S_OK, cred->SetDeselected());
ASSERT_EQ(S_OK, WaitForLogonProcess());
// Logon process should not signal credentials change or raise an error
// message.
ASSERT_EQ(S_OK, FinishLogonProcess(false, false, 0));
}
TEST_F(GcpGaiaCredentialBaseTest, FailedUserCreation) {
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
// Fail user creation.
fake_os_user_manager()->SetShouldFailUserCreation(true);
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Logon process should fail with an internal error.
ASSERT_EQ(S_OK, FinishLogonProcess(false, false, IDS_INTERNAL_ERROR_BASE));
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "foo@imfl.info";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"foo_imfl"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, NewUserDisabledThroughUsageScenario) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
// Set the other user tile so that we can get the anonymous credential
// that may try create a new user.
fake_user_array()->SetAccountOptions(CPAO_EMPTY_LOCAL);
SetUsageScenario(CPUS_UNLOCK_WORKSTATION);
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Sign in should fail with an error stating that no new users can be created.
ASSERT_EQ(S_OK, FinishLogonProcess(false, false,
IDS_INVALID_UNLOCK_WORKSTATION_USER_BASE));
}
TEST_F(GcpGaiaCredentialBaseTest, NewUserDisabledThroughMdm) {
USES_CONVERSION;
// Enforce single user mode for MDM.
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmSupportsMultiUser, 0));
GoogleMdmEnrolledStatusForTesting force_success(true);
// Create a fake user that is already associated so when the user tries to
// sign on and create a new user, it fails.
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"foo_registered", L"password", L"name", L"comment",
L"gaia-id-registered", base::string16(), &sid));
// Populate the associated users list. The created user's token handle
// should be valid so that no reauth credential is created.
fake_associated_user_validator()->StartRefreshingTokenHandleValidity();
// Set the other user tile so that we can get the anonymous credential
// that may try to sign in a user.
fake_user_array()->SetAccountOptions(CPAO_EMPTY_LOCAL);
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Sign in should fail with an error stating that no new users can be created.
ASSERT_EQ(S_OK,
FinishLogonProcess(false, false, IDS_ADD_USER_DISALLOWED_BASE));
}
TEST_F(GcpGaiaCredentialBaseTest, InvalidUserUnlockedAfterSignin) {
// Enforce token handle verification with user locking when the token handle
// is not valid.
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
GoogleMdmEnrollmentStatusForTesting force_success(true);
USES_CONVERSION;
// Create a fake user that has the same gaia id as the test gaia id.
CComBSTR sid;
base::string16 username(L"foo");
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
username, L"password", L"name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
ASSERT_EQ(2ul, fake_os_user_manager()->GetUserCount());
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
// Create with invalid token handle response.
SetDefaultTokenHandleResponse(kDefaultInvalidTokenHandleResponse);
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
// User should have invalid token handle and be locked.
EXPECT_FALSE(
fake_associated_user_validator()->IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(true,
fake_associated_user_validator()->IsUserAccessBlockedForTesting(
OLE2W(sid)));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// User should have been associated.
EXPECT_EQ(test->GetFinalUsername(), username);
// Email should be the same as the default one.
EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
// Now finish the logon, this should unlock the user.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
EXPECT_EQ(false,
fake_associated_user_validator()->IsUserAccessBlockedForTesting(
OLE2W(sid)));
// No new user should be created.
EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
}
TEST_F(GcpGaiaCredentialBaseTest, DenySigninBlockedDuringSignin) {
USES_CONVERSION;
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmSupportsMultiUser, 1));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
GoogleMdmEnrolledStatusForTesting force_success(true);
// Create a fake user that has the same gaia id as the test gaia id.
CComBSTR first_sid;
base::string16 username(L"foo");
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
username, L"password", L"name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(),
&first_sid));
ASSERT_EQ(2ul, fake_os_user_manager()->GetUserCount());
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
// Create with valid token handle response and sign in the anonymous
// credential with the user that should still be valid.
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Change token response to an invalid one.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(AssociatedUserValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{}");
// Force refresh of all token handles on the next query.
fake_associated_user_validator()->ForceRefreshTokenHandlesForTesting();
// Signin process has already started. User should not be locked even if their
// token handle is invalid.
EXPECT_FALSE(fake_associated_user_validator()
->DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
EXPECT_FALSE(fake_associated_user_validator()->IsUserAccessBlockedForTesting(
OLE2W(first_sid)));
// Now finish the logon.
ASSERT_EQ(S_OK, FinishLogonProcessWithCred(true, true, 0, cred));
// User should have been associated.
EXPECT_EQ(test->GetFinalUsername(), username);
// Email should be the same as the default one.
EXPECT_EQ(test->GetFinalEmail(), kDefaultEmail);
// Result has not been reported yet, user signin should still not be denied.
EXPECT_FALSE(fake_associated_user_validator()
->DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
EXPECT_FALSE(fake_associated_user_validator()->IsUserAccessBlockedForTesting(
OLE2W(first_sid)));
ReportLogonProcessResult(cred);
// Now signin can be denied for the user if their token handle is invalid.
EXPECT_TRUE(fake_associated_user_validator()
->DenySigninForUsersWithInvalidTokenHandles(CPUS_LOGON));
EXPECT_TRUE(fake_associated_user_validator()->IsUserAccessBlockedForTesting(
OLE2W(first_sid)));
// No new user should be created.
EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD_Gmail) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "bar@gmail.com";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"bar"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, StripEmailTLD_Googlemail) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "toto@googlemail.com";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"toto"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, InvalidUsernameCharacters) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "a\\[]:|<>+=;?*z@gmail.com";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"a____________z"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, EmailTooLong) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "areallylongemailadressdude@gmail.com";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"areallylongemailadre"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, EmailTooLong2) {
USES_CONVERSION;
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
constexpr char email[] = "foo@areallylongdomaindude.com";
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"foo_areallylongdomai"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsNoAt) {
USES_CONVERSION;
constexpr char email[] = "foo";
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"foo_gmail"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsAtCom) {
USES_CONVERSION;
constexpr char email[] = "@com";
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"_com"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
TEST_F(GcpGaiaCredentialBaseTest, EmailIsAtDotCom) {
USES_CONVERSION;
constexpr char email[] = "@.com";
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(email));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
ASSERT_STREQ(W2COLE(L"_.com"), test->GetFinalUsername());
EXPECT_EQ(test->GetFinalEmail(), email);
}
// Tests various sign in scenarios with consumer and non-consumer domains.
// Parameters are:
// 1. Is mdm enrollment enabled.
// 2. The mdm_aca reg key setting:
// - 0: Set reg key to 0.
// - 1: Set reg key to 1.
// - 2: Don't set reg key.
// 3. Whether the mdm_aca reg key is set to 1 or 0.
// 4. Whether an existing associated user is already present.
// 5. Whether the user being created (or existing) uses a consumer account.
class GcpGaiaCredentialBaseConsumerEmailTest
: public GcpGaiaCredentialBaseTest,
public ::testing::WithParamInterface<std::tuple<bool, int, bool, bool>> {
};
TEST_P(GcpGaiaCredentialBaseConsumerEmailTest, ConsumerEmailSignin) {
USES_CONVERSION;
const bool mdm_enabled = std::get<0>(GetParam());
const int mdm_consumer_accounts_reg_key_setting = std::get<1>(GetParam());
const bool user_created = std::get<2>(GetParam());
const bool user_is_consumer = std::get<3>(GetParam());
FakeAssociatedUserValidator validator;
FakeInternetAvailabilityChecker internet_checker;
GoogleMdmEnrollmentStatusForTesting force_success(true);
if (mdm_enabled)
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
const bool mdm_consumer_accounts_reg_key_set =
mdm_consumer_accounts_reg_key_setting >= 0 &&
mdm_consumer_accounts_reg_key_setting < 2;
if (mdm_consumer_accounts_reg_key_set) {
ASSERT_EQ(S_OK,
SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts,
mdm_consumer_accounts_reg_key_setting));
}
std::string user_email = user_is_consumer ? kDefaultEmail : "foo@imfl.info";
CComBSTR sid;
base::string16 username(user_is_consumer ? L"foo" : L"foo_imfl");
// Create a fake user that has the same gaia id as the test gaia id.
if (user_created) {
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
username, L"password", L"name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId),
base::UTF8ToUTF16(user_email), &sid));
ASSERT_EQ(2ul, fake_os_user_manager()->GetUserCount());
}
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
test->SetGlsEmailAddress(user_email);
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
bool should_signin_succeed = !mdm_enabled ||
(mdm_consumer_accounts_reg_key_set &&
mdm_consumer_accounts_reg_key_setting) ||
!user_is_consumer;
// Sign in success.
if (should_signin_succeed) {
// User should have been associated.
EXPECT_EQ(test->GetFinalUsername(), username);
// Email should be the same as the default one.
EXPECT_EQ(test->GetFinalEmail(), user_email);
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
} else {
// Error message concerning invalid domain is sent.
ASSERT_EQ(S_OK,
FinishLogonProcess(false, false, IDS_INVALID_EMAIL_DOMAIN_BASE));
}
if (user_created) {
// No new user should be created.
EXPECT_EQ(2ul, fake_os_user_manager()->GetUserCount());
} else {
// New user created only if their domain is valid for the sign in.
EXPECT_EQ(1ul + (should_signin_succeed ? 1ul : 0ul),
fake_os_user_manager()->GetUserCount());
}
}
INSTANTIATE_TEST_SUITE_P(,
GcpGaiaCredentialBaseConsumerEmailTest,
::testing::Combine(::testing::Bool(),
::testing::Values(0, 1, 2),
::testing::Bool(),
::testing::Bool()));
// Test password recovery system for various failure success cases.
// Parameters are:
// 1. int - The expected result of the initial public key retrieval for storing
// the password. Values are 0 - success, 1 - failure, 2 - timeout.
// 2. int - The expected result of the initial public private retrieval for
// decrypting the password. Values are 0 - success, 1 - failure,
// 2 - timeout.
// 3. int - The expected result of the initial public private retrieval for
// decrypting the password. Values are 0 - success, 1 - failure,
// 2 - timeout.
class GcpGaiaCredentialBasePasswordRecoveryTest
: public GcpGaiaCredentialBaseTest,
public ::testing::WithParamInterface<std::tuple<int, int, int>> {};
TEST_P(GcpGaiaCredentialBasePasswordRecoveryTest, PasswordRecovery) {
// Enable standard escrow service features in non-Chrome builds so that
// the escrow service code can be tested by the build machines.
#if !defined(GOOGLE_CHROME_BUILD)
GoogleMdmEscrowServiceEnablerForTesting escrow_service_enabler(true);
#endif
USES_CONVERSION;
int generate_public_key_result = std::get<0>(GetParam());
int get_private_key_result = std::get<1>(GetParam());
int generate_public_key_again_result = std::get<2>(GetParam());
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmEscrowServiceServerUrl,
L"https://escrow.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
GoogleMdmEnrolledStatusForTesting force_success(true);
// Create a fake user associated to a gaia id.
CComBSTR sid;
constexpr wchar_t kOldPassword[] = L"password";
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
kDefaultUsername, kOldPassword, L"Full Name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
// Change token response to an invalid one.
SetDefaultTokenHandleResponse(kDefaultInvalidTokenHandleResponse);
// Make a dummy response for successful public key generation and private key
// retrieval.
std::string generate_success_response =
fake_password_recovery_manager()->MakeGenerateKeyPairResponseForTesting(
"public_key", kFakeResourceId);
std::string get_key_success_response =
fake_password_recovery_manager()->MakeGetPrivateKeyResponseForTesting(
"private_key");
// Make timeout events for the various escrow service requests if needed.
std::unique_ptr<base::WaitableEvent> get_key_event;
std::unique_ptr<base::WaitableEvent> generate_key_event;
if (generate_public_key_result == 2)
get_key_event.reset(new base::WaitableEvent());
if (get_private_key_result == 2)
generate_key_event.reset(new base::WaitableEvent());
if (get_key_event || generate_key_event) {
fake_password_recovery_manager()->SetRequestTimeoutForTesting(
base::TimeDelta::FromMilliseconds(50));
}
fake_http_url_fetcher_factory()->SetFakeResponse(
fake_password_recovery_manager()->GetEscrowServiceGenerateKeyPairUrl(),
FakeWinHttpUrlFetcher::Headers(),
generate_public_key_result != 1 ? generate_success_response : "{}",
generate_key_event ? generate_key_event->handle() : INVALID_HANDLE_VALUE);
fake_http_url_fetcher_factory()->SetFakeResponse(
fake_password_recovery_manager()->GetEscrowServiceGetPrivateKeyUrl(
kFakeResourceId),
FakeWinHttpUrlFetcher::Headers(),
get_private_key_result != 1 ? get_key_success_response : "{}",
get_key_event ? get_key_event->handle() : INVALID_HANDLE_VALUE);
bool should_store_succeed = generate_public_key_result == 0;
bool should_recover_succeed = get_private_key_result == 0;
// Sign on once to store the password in the LSA
{
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Finish logon successfully to propagate password recovery information to
// LSA.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
ASSERT_EQ(S_OK, ReleaseProvider());
}
// If there was a timeout for the generation of the public key, signal it now
// so that the request thread can complete. Also delete the event in case it
// needs to be used again on the sign in after the password was retrieved.
if (generate_key_event) {
generate_key_event->Signal();
generate_key_event.reset();
}
if (generate_public_key_again_result == 2)
generate_key_event.reset(new base::WaitableEvent());
if (generate_key_event) {
fake_password_recovery_manager()->SetRequestTimeoutForTesting(
base::TimeDelta::FromMilliseconds(50));
}
fake_http_url_fetcher_factory()->SetFakeResponse(
fake_password_recovery_manager()->GetEscrowServiceGenerateKeyPairUrl(),
FakeWinHttpUrlFetcher::Headers(),
generate_public_key_again_result != 1 ? generate_success_response : "{}",
generate_key_event ? generate_key_event->handle() : INVALID_HANDLE_VALUE);
constexpr char kNewPassword[] = "password2";
// Sign in a second time with a different password and see if it is updated
// automatically.
{
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
// Send back a different gaia password to force a password update.
ASSERT_EQ(S_OK, test->SetGlsGaiaPassword(kNewPassword));
// Don't send a forced e-mail. It will be sent from the user that was
// updated during the last sign in.
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(std::string()));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
CComPtr<ITestCredentialProvider> test_provider;
ASSERT_EQ(S_OK, created_provider().QueryInterface(&test_provider));
// If either password storage or recovery failed then the user will need to
// enter their old Windows password.
if (!should_store_succeed || !should_recover_succeed) {
// Logon should not complete but there is no error message.
EXPECT_EQ(test_provider->credentials_changed_fired(), false);
// Set the correct old password so that the user can sign in.
ASSERT_EQ(S_OK,
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, kOldPassword));
// Finish logon successfully now which should update the password.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
} else {
// Make sure the new password is sent to the provider.
EXPECT_STREQ(A2OLE(kNewPassword), OLE2CW(test_provider->password()));
// Finish logon successfully but with no credential changed event.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
}
// Make sure the user has the new password internally.
EXPECT_EQ(S_OK, fake_os_user_manager()->IsWindowsPasswordValid(
OSUserManager::GetLocalDomain().c_str(),
kDefaultUsername, A2OLE(kNewPassword)));
ASSERT_EQ(S_OK, ReleaseProvider());
}
// Complete the private key retrieval request if it was waiting.
if (get_key_event)
get_key_event->Signal();
// If generate of the second public key failed, the next sign in would
// need to re-enter their password
if (generate_public_key_again_result != 0) {
constexpr char kNewPassword2[] = "password3";
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
// Send back a different gaia password to force a password update.
ASSERT_EQ(S_OK, test->SetGlsGaiaPassword(kNewPassword2));
// Don't send a forced e-mail. It will be sent from the user that was
// updated during the last sign in.
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(std::string()));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
CComPtr<ITestCredentialProvider> test_provider;
ASSERT_EQ(S_OK, created_provider().QueryInterface(&test_provider));
// Logon should not complete but there is no error message.
EXPECT_EQ(test_provider->credentials_changed_fired(), false);
// Set the correct old password so that the user can sign in.
ASSERT_EQ(S_OK,
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD,
base::UTF8ToUTF16(kNewPassword).c_str()));
// Finish logon successfully now which should update the password.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
// Make sure the user has the new password internally.
EXPECT_EQ(S_OK, fake_os_user_manager()->IsWindowsPasswordValid(
OSUserManager::GetLocalDomain().c_str(),
kDefaultUsername, A2OLE(kNewPassword2)));
ASSERT_EQ(S_OK, ReleaseProvider());
}
}
INSTANTIATE_TEST_SUITE_P(,
GcpGaiaCredentialBasePasswordRecoveryTest,
::testing::Combine(::testing::Values(0, 1, 2),
::testing::Values(0, 1, 2),
::testing::Values(0, 1, 2)));
// Test password recovery system being disabled by registry settings.
// Parameter is a pointer to an escrow service url. Can be empty or nullptr.
class GcpGaiaCredentialBasePasswordRecoveryDisablingTest
: public GcpGaiaCredentialBaseTest,
public ::testing::WithParamInterface<const wchar_t*> {};
TEST_P(GcpGaiaCredentialBasePasswordRecoveryDisablingTest,
PasswordRecovery_Disabled) {
// Enable standard escrow service features in non-Chrome builds so that
// the escrow service code can be tested by the build machines.
#if !defined(GOOGLE_CHROME_BUILD)
GoogleMdmEscrowServiceEnablerForTesting escrow_service_enabler(true);
#endif
USES_CONVERSION;
const wchar_t* escrow_service_url = GetParam();
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com"));
ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmAllowConsumerAccounts, 1));
if (escrow_service_url) {
base::win::RegKey key;
ASSERT_EQ(ERROR_SUCCESS,
key.Create(HKEY_LOCAL_MACHINE, kGcpRootKeyName, KEY_WRITE));
ASSERT_EQ(ERROR_SUCCESS, key.WriteValue(kRegMdmEscrowServiceServerUrl,
escrow_service_url));
}
GoogleMdmEnrolledStatusForTesting force_success(true);
// Create a fake user associated to a gaia id.
CComBSTR sid;
constexpr wchar_t kOldPassword[] = L"password";
ASSERT_EQ(S_OK,
fake_os_user_manager()->CreateTestOSUser(
kDefaultUsername, kOldPassword, L"Full Name", L"comment",
base::UTF8ToUTF16(kDefaultGaiaId), base::string16(), &sid));
// Change token response to an invalid one.
SetDefaultTokenHandleResponse(kDefaultInvalidTokenHandleResponse);
// Make a dummy response for successful public key generation and private key
// retrieval.
std::string generate_success_response =
fake_password_recovery_manager()->MakeGenerateKeyPairResponseForTesting(
"public_key", kFakeResourceId);
std::string get_key_success_response =
fake_password_recovery_manager()->MakeGetPrivateKeyResponseForTesting(
"private_key");
fake_http_url_fetcher_factory()->SetFakeResponse(
fake_password_recovery_manager()->GetEscrowServiceGenerateKeyPairUrl(),
FakeWinHttpUrlFetcher::Headers(), generate_success_response);
fake_http_url_fetcher_factory()->SetFakeResponse(
fake_password_recovery_manager()->GetEscrowServiceGetPrivateKeyUrl(
kFakeResourceId),
FakeWinHttpUrlFetcher::Headers(), get_key_success_response);
// Sign on once to store the password in the LSA
{
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
// Finish logon successfully to propagate password recovery information to
// LSA.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
ASSERT_EQ(S_OK, ReleaseProvider());
}
// Sign in a second time with a different password and see if it is updated
// automatically.
{
constexpr char kNewPassword[] = "password2";
// Create provider and start logon.
CComPtr<ICredentialProviderCredential> cred;
ASSERT_EQ(S_OK, InitializeProviderAndGetCredential(0, &cred));
CComPtr<ITestCredential> test;
ASSERT_EQ(S_OK, cred.QueryInterface(&test));
// Send back a different gaia password to force a password update.
ASSERT_EQ(S_OK, test->SetGlsGaiaPassword(kNewPassword));
// Don't send a forced e-mail. It will be sent from the user that was
// updated during the last sign in.
ASSERT_EQ(S_OK, test->SetGlsEmailAddress(std::string()));
ASSERT_EQ(S_OK, StartLogonProcessAndWait());
CComPtr<ITestCredentialProvider> test_provider;
ASSERT_EQ(S_OK, created_provider().QueryInterface(&test_provider));
// Null or empty escrow service url will disable password
// recovery and force the user to enter their password.
if (!escrow_service_url || escrow_service_url[0] == '\0') {
// Logon should not complete but there is no error message.
EXPECT_EQ(test_provider->credentials_changed_fired(), false);
// Set the correct old password so that the user can sign in.
ASSERT_EQ(S_OK,
cred->SetStringValue(FID_CURRENT_PASSWORD_FIELD, kOldPassword));
// Finish logon successfully now which should update the password.
ASSERT_EQ(S_OK, FinishLogonProcess(true, false, 0));
} else {
// Make sure the new password is sent to the provider.
EXPECT_STREQ(A2OLE(kNewPassword), OLE2CW(test_provider->password()));
// Finish logon successfully but with no credential changed event.
ASSERT_EQ(S_OK, FinishLogonProcess(true, true, 0));
}
// Make sure the user has the new password internally.
EXPECT_EQ(S_OK, fake_os_user_manager()->IsWindowsPasswordValid(
OSUserManager::GetLocalDomain().c_str(),
kDefaultUsername, A2OLE(kNewPassword)));
ASSERT_EQ(S_OK, ReleaseProvider());
}
}
INSTANTIATE_TEST_SUITE_P(,
GcpGaiaCredentialBasePasswordRecoveryDisablingTest,
::testing::Values(nullptr,
L"",
L"https://escrowservice.com"));
} // namespace testing
} // namespace credential_provider