| // 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 <credentialprovider.h> |
| |
| #include <tuple> |
| |
| #include "base/synchronization/waitable_event.h" |
| #include "base/win/registry.h" |
| #include "chrome/credential_provider/common/gcp_strings.h" |
| #include "chrome/credential_provider/gaiacp/associated_user_validator.h" |
| #include "chrome/credential_provider/gaiacp/auth_utils.h" |
| #include "chrome/credential_provider/gaiacp/gaia_credential_provider.h" |
| #include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h" |
| #include "chrome/credential_provider/gaiacp/mdm_utils.h" |
| #include "chrome/credential_provider/gaiacp/reg_utils.h" |
| #include "chrome/credential_provider/test/com_fakes.h" |
| #include "chrome/credential_provider/test/gcp_fakes.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace credential_provider { |
| |
| class GcpCredentialProviderTest : public ::testing::Test { |
| protected: |
| void CreateGCPWUser(const wchar_t* username, |
| const wchar_t* email, |
| const wchar_t* password, |
| const wchar_t* fullname, |
| const wchar_t* comment, |
| const wchar_t* gaia_id, |
| BSTR* sid) { |
| ASSERT_EQ(S_OK, |
| fake_os_user_manager_.CreateTestOSUser( |
| username, password, fullname, comment, gaia_id, email, sid)); |
| } |
| |
| FakeOSUserManager* fake_os_user_manager() { return &fake_os_user_manager_; } |
| FakeWinHttpUrlFetcherFactory* fake_http_url_fetcher_factory() { |
| return &fake_http_url_fetcher_factory_; |
| } |
| |
| void SetUp() override; |
| |
| private: |
| registry_util::RegistryOverrideManager registry_override_; |
| FakeOSUserManager fake_os_user_manager_; |
| FakeScopedLsaPolicyFactory fake_scoped_lsa_policy_factory_; |
| FakeWinHttpUrlFetcherFactory fake_http_url_fetcher_factory_; |
| }; |
| |
| void GcpCredentialProviderTest::SetUp() { |
| InitializeRegistryOverrideForTesting(®istry_override_); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, Basic) { |
| CComPtr<IGaiaCredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_IGaiaCredentialProvider, (void**)&provider)); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, SetUserArray_NoGaiaUsers) { |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ( |
| S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProviderSetUserArray, (void**)&user_array)); |
| |
| FakeCredentialProviderUserArray array; |
| array.AddUser(L"sid", L"username"); |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, user_array.QueryInterface(&provider)); |
| |
| // There should be no credentials. Only users with the requisite registry |
| // entry will be counted. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| EXPECT_EQ(0u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, CpusLogon) { |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| |
| // Start process for logon screen. |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Give list of users visible on welcome screen. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| array.AddUser(L"sid1", L"username1"); |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Check credentials. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| CComPtr<ICredentialProviderCredential> cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &cred)); |
| CComPtr<IGaiaCredential> gaia_cred; |
| EXPECT_EQ(S_OK, cred.QueryInterface(&gaia_cred)); |
| |
| // Get fields. |
| DWORD field_count; |
| ASSERT_EQ(S_OK, provider->GetFieldDescriptorCount(&field_count)); |
| EXPECT_EQ(FIELD_COUNT, field_count); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, CpusUnlock) { |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| |
| // Start process for logon screen. |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_UNLOCK_WORKSTATION, 0)); |
| |
| // Give list of users visible on welcome screen. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| array.AddUser(L"sid1", L"username1"); |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Check credentials. None should be available because the anonymous |
| // credential is not allowed during an unlock scenario. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(0u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| // Get fields. |
| DWORD field_count; |
| ASSERT_EQ(S_OK, provider->GetFieldDescriptorCount(&field_count)); |
| EXPECT_EQ(FIELD_COUNT, field_count); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, AutoLogonAfterUserRefresh) { |
| USES_CONVERSION; |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| |
| CComPtr<IGaiaCredentialProvider> gaia_provider; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&gaia_provider)); |
| |
| CComBSTR sid; |
| ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser( |
| L"username", L"passowrd", L"Full Name", L"Comment", L"", |
| L"", &sid)); |
| // Start process for logon screen. |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Give empty list of users so that only the anonymous credential is created. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Only the anonymous credential should exist. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| // Get the anonymous credential. |
| CComPtr<ICredentialProviderCredential> cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &cred)); |
| |
| // Notify that user access is denied to fake a forced recreation of the users. |
| ICredentialUpdateEventsHandler* update_handler = |
| static_cast<ICredentialUpdateEventsHandler*>( |
| static_cast<CGaiaCredentialProvider*>(provider.p)); |
| update_handler->UpdateCredentialsIfNeeded(true); |
| |
| // Credential changed event should have been received. |
| EXPECT_TRUE(events.CredentialsChangedReceived()); |
| events.ResetCredentialsChangedReceived(); |
| |
| // At the same time notify that a user has authenticated and requires a |
| // sign in. |
| { |
| // Temporary locker to prevent DCHECKs in OnUserAuthenticated |
| AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_update_locker( |
| AssociatedUserValidator::Get()); |
| ASSERT_EQ(S_OK, gaia_provider->OnUserAuthenticated( |
| cred, CComBSTR(L"username"), CComBSTR(L"password"), sid, |
| true)); |
| } |
| |
| // No credential changed should have been signalled here. |
| EXPECT_FALSE(events.CredentialsChangedReceived()); |
| |
| // GetCredentialCount should return back the same credential that was just |
| // auto logged on. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(0u, default_index); |
| EXPECT_TRUE(autologon); |
| |
| CComPtr<ICredentialProviderCredential> auto_logon_cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &auto_logon_cred)); |
| EXPECT_TRUE(auto_logon_cred.IsEqualObject(cred)); |
| |
| // The next call to GetCredentialCount should return re-created credentials. |
| |
| // Fake an update request with no access changes. The pending user refresh |
| // should be queued. |
| update_handler->UpdateCredentialsIfNeeded(false); |
| |
| // Credential changed event should have been received. |
| EXPECT_TRUE(events.CredentialsChangedReceived()); |
| |
| // GetCredentialCount should return new credentials with no auto logon. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| CComPtr<ICredentialProviderCredential> new_cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &new_cred)); |
| EXPECT_FALSE(new_cred.IsEqualObject(cred)); |
| |
| // Another request to refresh the credentials should yield no credential |
| // changed event or refresh of credentials. |
| events.ResetCredentialsChangedReceived(); |
| |
| update_handler->UpdateCredentialsIfNeeded(false); |
| |
| // No credential changed event should have been received. |
| EXPECT_FALSE(events.CredentialsChangedReceived()); |
| |
| // GetCredentialCount should return the same credentials with no change. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| CComPtr<ICredentialProviderCredential> unchanged_cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &unchanged_cred)); |
| EXPECT_TRUE(new_cred.IsEqualObject(unchanged_cred)); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, AutoLogonBeforeUserRefresh) { |
| USES_CONVERSION; |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| |
| CComPtr<IGaiaCredentialProvider> gaia_provider; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&gaia_provider)); |
| |
| CComBSTR sid; |
| ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser( |
| L"username", L"passowrd", L"Full Name", L"Comment", L"", |
| L"", &sid)); |
| // Start process for logon screen. |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Give empty list of users so that only the anonymous credential is created. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Only the anonymous credential should exist. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| // Get the anonymous credential. |
| CComPtr<ICredentialProviderCredential> cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &cred)); |
| |
| ICredentialUpdateEventsHandler* update_handler = |
| static_cast<ICredentialUpdateEventsHandler*>( |
| static_cast<CGaiaCredentialProvider*>(provider.p)); |
| |
| // Notify user auto logon first and then notify user access denied to ensure |
| // that auto logon always has precedence over user access denied. |
| { |
| // Temporary locker to prevent DCHECKs in OnUserAuthenticated |
| AssociatedUserValidator::ScopedBlockDenyAccessUpdate deny_update_locker( |
| AssociatedUserValidator::Get()); |
| ASSERT_EQ(S_OK, gaia_provider->OnUserAuthenticated( |
| cred, CComBSTR(L"username"), CComBSTR(L"password"), sid, |
| true)); |
| } |
| |
| // Credential changed event should have been received. |
| EXPECT_TRUE(events.CredentialsChangedReceived()); |
| events.ResetCredentialsChangedReceived(); |
| |
| // Notify that user access is denied. This should not cause a credential |
| // changed since an event was already processed. |
| update_handler->UpdateCredentialsIfNeeded(true); |
| |
| // No credential changed should have been signalled here. |
| EXPECT_FALSE(events.CredentialsChangedReceived()); |
| |
| // GetCredentialCount should return back the same credential that was just |
| // auto logged on. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(0u, default_index); |
| EXPECT_TRUE(autologon); |
| |
| CComPtr<ICredentialProviderCredential> auto_logon_cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &auto_logon_cred)); |
| EXPECT_TRUE(auto_logon_cred.IsEqualObject(cred)); |
| |
| // The next call to GetCredentialCount should return re-created credentials. |
| |
| // Fake an update request with no access changes. The pending user refresh |
| // should be queued. |
| update_handler->UpdateCredentialsIfNeeded(false); |
| |
| // Credential changed event should have been received. |
| EXPECT_TRUE(events.CredentialsChangedReceived()); |
| |
| // GetCredentialCount should return new credentials with no auto logon. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| CComPtr<ICredentialProviderCredential> new_cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(0, &new_cred)); |
| EXPECT_FALSE(new_cred.IsEqualObject(cred)); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| TEST_F(GcpCredentialProviderTest, AddPersonAfterUserRemove) { |
| FakeAssociatedUserValidator associated_user_validator; |
| FakeInternetAvailabilityChecker internet_checker; |
| |
| // Set up such that MDM is enabled, mulit-users is not, and a user already |
| // exists. |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com")); |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmSupportsMultiUser, 0)); |
| GoogleMdmEnrolledStatusForTesting forced_status(true); |
| |
| const wchar_t kDummyUsername[] = L"username"; |
| const wchar_t kDummyPassword[] = L"password"; |
| CComBSTR sid; |
| CreateGCPWUser(kDummyUsername, L"foo@gmail.com", kDummyPassword, L"Full Name", |
| L"Comment", L"gaia-id", &sid); |
| |
| // Start token handle refresh threads. |
| associated_user_validator.StartRefreshingTokenHandleValidity(); |
| |
| { |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Empty user array. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // In this case no credential should be returned. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(0u, count); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| // Delete the OS user. At this point, info in the HKLM registry about this |
| // user will still exist. However it gets properly cleaned up when the token |
| // validator starts its refresh of token handle validity. |
| ASSERT_EQ(S_OK, |
| fake_os_user_manager()->RemoveUser(kDummyUsername, kDummyPassword)); |
| |
| { |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Empty user array. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // This time a credential should be returned. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(1u, count); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| } |
| |
| // Tests auto logon enabled when set serialization is called. |
| // Parameters: |
| // 1. bool: are the users' token handles still valid. |
| // 2. CREDENTIAL_PROVIDER_USAGE_SCENARIO - the usage scenario. |
| class GcpCredentialProviderSetSerializationTest |
| : public GcpCredentialProviderTest, |
| public testing::WithParamInterface< |
| std::tuple<bool, CREDENTIAL_PROVIDER_USAGE_SCENARIO>> {}; |
| |
| TEST_P(GcpCredentialProviderSetSerializationTest, CheckAutoLogon) { |
| FakeAssociatedUserValidator associated_user_validator; |
| FakeInternetAvailabilityChecker internet_checker; |
| |
| const bool valid_token_handles = std::get<0>(GetParam()); |
| const CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus = std::get<1>(GetParam()); |
| |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com")); |
| GoogleMdmEnrolledStatusForTesting forced_status(true); |
| |
| CComBSTR first_sid; |
| constexpr wchar_t first_username[] = L"username"; |
| CreateGCPWUser(first_username, L"foo@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id", &first_sid); |
| |
| CComBSTR second_sid; |
| constexpr wchar_t second_username[] = L"username2"; |
| CreateGCPWUser(second_username, L"foo2@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id2", &second_sid); |
| |
| // Token fetch result. |
| fake_http_url_fetcher_factory()->SetFakeResponse( |
| GURL(AssociatedUserValidator::kTokenInfoUrl), |
| FakeWinHttpUrlFetcher::Headers(), |
| valid_token_handles ? "{\"expires_in\":1}" : "{}"); |
| |
| // Start token handle refresh threads. |
| associated_user_validator.StartRefreshingTokenHandleValidity(); |
| |
| // Lock users as needed based on the validity of their token handles. |
| associated_user_validator.DenySigninForUsersWithInvalidTokenHandles(cpus); |
| |
| // Build a dummy authentication buffer that can be passed to SetSerialization. |
| CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION cpcs; |
| base::string16 local_domain = OSUserManager::GetLocalDomain(); |
| base::string16 serialization_username = second_username; |
| base::string16 serialization_password = L"password"; |
| std::vector<wchar_t> dummy_domain( |
| local_domain.c_str(), local_domain.c_str() + local_domain.size() + 1); |
| std::vector<wchar_t> dummy_username( |
| serialization_username.c_str(), |
| serialization_username.c_str() + serialization_username.size() + 1); |
| std::vector<wchar_t> dummy_password( |
| serialization_password.c_str(), |
| serialization_password.c_str() + serialization_password.size() + 1); |
| ASSERT_EQ(S_OK, BuildCredPackAuthenticationBuffer( |
| &dummy_domain[0], &dummy_username[0], &dummy_password[0], |
| cpus, &cpcs)); |
| |
| cpcs.clsidCredentialProvider = CLSID_GaiaCredentialProvider; |
| |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ( |
| S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProviderSetUserArray, (void**)&user_array)); |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, user_array.QueryInterface(&provider)); |
| |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(cpus, 0)); |
| |
| ASSERT_EQ(S_OK, provider->SetSerialization(&cpcs)); |
| |
| ::CoTaskMemFree(cpcs.rgbSerialization); |
| |
| FakeCredentialProviderUserArray array; |
| array.AddUser(OLE2CW(first_sid), first_username); |
| array.AddUser(OLE2CW(second_sid), second_username); |
| |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Check the correct number of credentials are created and whether autologon |
| // is enabled based on the token handle validity. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| |
| bool should_autologon = !valid_token_handles; |
| EXPECT_EQ(valid_token_handles ? 0u : 2u, count); |
| EXPECT_EQ(autologon, should_autologon); |
| EXPECT_EQ(default_index, |
| should_autologon ? 1 : CREDENTIAL_PROVIDER_NO_DEFAULT); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GcpCredentialProviderSetSerializationTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Values(CPUS_UNLOCK_WORKSTATION, CPUS_LOGON))); |
| |
| // Tests the effect of the MDM settings on the credential provider. |
| // Parameters: |
| // bool: whether the MDM URL is configured. |
| // int: whether multi-users is supported: |
| // 0: set registry to 0 |
| // 1: set registry to 1 |
| // 2: don't set at all |
| // bool: whether an existing user exists. |
| class GcpCredentialProviderMdmTest |
| : public GcpCredentialProviderTest, |
| public testing::WithParamInterface<std::tuple<bool, int, bool>> {}; |
| |
| TEST_P(GcpCredentialProviderMdmTest, Basic) { |
| FakeAssociatedUserValidator associated_user_validator; |
| FakeInternetAvailabilityChecker internet_checker; |
| |
| const bool config_mdm_url = std::get<0>(GetParam()); |
| const int supports_multi_users = std::get<1>(GetParam()); |
| const bool user_exists = std::get<2>(GetParam()); |
| const DWORD expected_credential_count = |
| config_mdm_url && supports_multi_users != 1 && user_exists ? 0 : 1; |
| |
| bool mdm_enrolled = false; |
| if (config_mdm_url) { |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com")); |
| mdm_enrolled = true; |
| } |
| |
| GoogleMdmEnrolledStatusForTesting forced_status(mdm_enrolled); |
| |
| if (supports_multi_users != 2) { |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmSupportsMultiUser, |
| supports_multi_users)); |
| } |
| |
| if (user_exists) { |
| CComBSTR sid; |
| CreateGCPWUser(L"username", L"foo@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id", &sid); |
| } |
| |
| // Valid token fetch result. |
| fake_http_url_fetcher_factory()->SetFakeResponse( |
| GURL(AssociatedUserValidator::kTokenInfoUrl), |
| FakeWinHttpUrlFetcher::Headers(), "{\"expires_in\":1}"); |
| |
| associated_user_validator.StartRefreshingTokenHandleValidity(); |
| |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProvider, (void**)&provider)); |
| |
| // Start process for logon screen. |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| // Empty user array. |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ(S_OK, provider.QueryInterface(&user_array)); |
| FakeCredentialProviderUserArray array; |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(expected_credential_count, count); |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(GcpCredentialProviderMdmTest, |
| GcpCredentialProviderMdmTest, |
| ::testing::Combine(testing::Bool(), |
| testing::Range(0, 3), |
| testing::Bool())); |
| |
| // Check that reauth credentials only exist when the token handle for the |
| // associated user is no longer valid and internet is available. |
| // Parameters are: |
| // 1. bool - does the fake user have a token handle set. |
| // 2. bool - is the token handle for the fake user valid (i.e. the fetch of |
| // the token handle info from win_http_url_fetcher returns a valid json). |
| // 3. bool - is internet available. |
| |
| class GcpCredentialProviderWithGaiaUsersTest |
| : public GcpCredentialProviderTest, |
| public ::testing::WithParamInterface<std::tuple<bool, bool, bool>> {}; |
| |
| TEST_P(GcpCredentialProviderWithGaiaUsersTest, ReauthCredentialTest) { |
| const bool has_token_handle = std::get<0>(GetParam()); |
| const bool valid_token_handle = std::get<1>(GetParam()); |
| const bool has_internet = std::get<2>(GetParam()); |
| FakeAssociatedUserValidator associated_user_validator; |
| FakeInternetAvailabilityChecker internet_checker( |
| has_internet ? FakeInternetAvailabilityChecker::kHicForceYes |
| : FakeInternetAvailabilityChecker::kHicForceNo); |
| |
| CComBSTR sid; |
| CreateGCPWUser(L"username", L"foo@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id", &sid); |
| |
| if (!has_token_handle) |
| ASSERT_EQ(S_OK, SetUserProperty((BSTR)sid, kUserTokenHandle, L"")); |
| |
| // Token fetch result. |
| fake_http_url_fetcher_factory()->SetFakeResponse( |
| GURL(AssociatedUserValidator::kTokenInfoUrl), |
| FakeWinHttpUrlFetcher::Headers(), |
| valid_token_handle ? "{\"expires_in\":1}" : "{}"); |
| |
| // Start token handle refresh threads. |
| associated_user_validator.StartRefreshingTokenHandleValidity(); |
| |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ( |
| S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProviderSetUserArray, (void**)&user_array)); |
| |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, user_array.QueryInterface(&provider)); |
| |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(CPUS_LOGON, 0)); |
| |
| FakeCredentialProviderUserArray array; |
| array.AddUser(OLE2CW(sid), L"username"); |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| bool should_reauth_user = |
| has_internet && (!has_token_handle || !valid_token_handle); |
| |
| // Check if there is a IReauthCredential depending on the state of the token |
| // handle. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| // There should always be the anonymous credential and potentially a reauth |
| // credential. |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| ASSERT_EQ(should_reauth_user ? 2u : 1u, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| if (should_reauth_user) { |
| CComPtr<ICredentialProviderCredential> cred; |
| ASSERT_EQ(S_OK, provider->GetCredentialAt(1, &cred)); |
| CComPtr<IReauthCredential> reauth; |
| EXPECT_EQ(S_OK, cred.QueryInterface(&reauth)); |
| } |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| GcpCredentialProviderWithGaiaUsersTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool())); |
| |
| // Check that the correct reauth credential type is created based on various |
| // policy settings as well as usage scenarios. |
| // Parameters are: |
| // 1. bool - are the users' token handles still valid. |
| // 2. CREDENTIAL_PROVIDER_USAGE_SCENARIO - the usage scenario. |
| // 3. bool - is the other user tile available. |
| // 4. bool - is machine enrolled to mdm. |
| // 5. bool - is the second user locking the system. |
| class GcpCredentialProviderAvailableCredentialsTest |
| : public GcpCredentialProviderTest, |
| public ::testing::WithParamInterface< |
| std::tuple<bool, |
| CREDENTIAL_PROVIDER_USAGE_SCENARIO, |
| bool, |
| bool, |
| bool>> { |
| protected: |
| void SetUp() override; |
| }; |
| |
| void GcpCredentialProviderAvailableCredentialsTest::SetUp() { |
| GcpCredentialProviderTest::SetUp(); |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmUrl, L"https://mdm.com")); |
| ASSERT_EQ(S_OK, SetGlobalFlagForTesting(kRegMdmSupportsMultiUser, 0)); |
| } |
| |
| TEST_P(GcpCredentialProviderAvailableCredentialsTest, AvailableCredentials) { |
| FakeAssociatedUserValidator associated_user_validator; |
| FakeInternetAvailabilityChecker internet_checker; |
| FakeCredentialProviderUserArray array; |
| |
| const bool valid_token_handles = std::get<0>(GetParam()); |
| const CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus = std::get<1>(GetParam()); |
| const bool other_user_tile_available = std::get<2>(GetParam()); |
| const bool enrolled_to_mdm = std::get<3>(GetParam()); |
| const bool second_user_locking_system = std::get<4>(GetParam()); |
| |
| GoogleMdmEnrolledStatusForTesting forced_status(enrolled_to_mdm); |
| |
| if (other_user_tile_available) |
| array.SetAccountOptions(CPAO_EMPTY_LOCAL); |
| |
| CComBSTR first_sid; |
| constexpr wchar_t first_username[] = L"username"; |
| CreateGCPWUser(first_username, L"foo@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id", &first_sid); |
| |
| CComBSTR second_sid; |
| constexpr wchar_t second_username[] = L"username2"; |
| CreateGCPWUser(second_username, L"foo2@gmail.com", L"password", L"Full Name", |
| L"Comment", L"gaia-id2", &second_sid); |
| |
| // Token fetch result. |
| fake_http_url_fetcher_factory()->SetFakeResponse( |
| GURL(AssociatedUserValidator::kTokenInfoUrl), |
| FakeWinHttpUrlFetcher::Headers(), |
| valid_token_handles ? "{\"expires_in\":1}" : "{}"); |
| |
| // Start token handle refresh threads. |
| associated_user_validator.StartRefreshingTokenHandleValidity(); |
| |
| // Lock users as needed based on the validity of their token handles. |
| associated_user_validator.DenySigninForUsersWithInvalidTokenHandles(cpus); |
| |
| CComPtr<ICredentialProviderSetUserArray> user_array; |
| ASSERT_EQ( |
| S_OK, |
| CComCreator<CComObject<CGaiaCredentialProvider>>::CreateInstance( |
| nullptr, IID_ICredentialProviderSetUserArray, (void**)&user_array)); |
| CComPtr<ICredentialProvider> provider; |
| ASSERT_EQ(S_OK, user_array.QueryInterface(&provider)); |
| |
| ASSERT_EQ(S_OK, provider->SetUsageScenario(cpus, 0)); |
| |
| // All users are shown if the usage is not for unlocking the workstation. |
| bool all_users_shown = cpus != CPUS_UNLOCK_WORKSTATION; |
| // 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 || |
| (!second_user_locking_system && !other_user_tile_available)) { |
| array.AddUser(OLE2CW(first_sid), first_username); |
| } |
| if (all_users_shown || |
| (second_user_locking_system && !other_user_tile_available)) { |
| array.AddUser(OLE2CW(second_sid), second_username); |
| } |
| |
| ASSERT_EQ(S_OK, user_array->SetUserArray(&array)); |
| |
| // Activate the CP. |
| FakeCredentialProviderEvents events; |
| ASSERT_EQ(S_OK, provider->Advise(&events, 0)); |
| |
| // Check the correct number of credentials are created. |
| DWORD count; |
| DWORD default_index; |
| BOOL autologon; |
| ASSERT_EQ(S_OK, |
| provider->GetCredentialCount(&count, &default_index, &autologon)); |
| |
| DWORD expected_credentials = 0; |
| if (cpus != CPUS_UNLOCK_WORKSTATION) { |
| expected_credentials = valid_token_handles && enrolled_to_mdm ? 0 : 2; |
| if (other_user_tile_available) |
| expected_credentials += 1; |
| } else { |
| if (other_user_tile_available) { |
| expected_credentials = 1; |
| } else { |
| expected_credentials = valid_token_handles && enrolled_to_mdm ? 0 : 1; |
| } |
| } |
| |
| ASSERT_EQ(expected_credentials, count); |
| EXPECT_EQ(CREDENTIAL_PROVIDER_NO_DEFAULT, default_index); |
| EXPECT_FALSE(autologon); |
| |
| if (expected_credentials == 0) { |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| return; |
| } |
| |
| CComPtr<ICredentialProviderCredential> cred; |
| CComPtr<ICredentialProviderCredential2> cred2; |
| CComPtr<IReauthCredential> reauth; |
| |
| DWORD first_non_anonymous_cred_index = 0; |
| |
| // Other user tile is shown, we should create the anonymous tile as a |
| // ICredentialProviderCredential2 so that it is added to the "Other User" |
| // tile. |
| if (other_user_tile_available) { |
| EXPECT_EQ(S_OK, provider->GetCredentialAt(first_non_anonymous_cred_index++, |
| &cred)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&cred2)); |
| } |
| |
| // Not unlocking workstation: if there are more credentials then they should |
| // all the credentials should be shown as reauth credentials since all the |
| // users have expired token handles (or need mdm enrollment) and need to |
| // reauth. |
| |
| if (cpus != CPUS_UNLOCK_WORKSTATION) { |
| if (first_non_anonymous_cred_index < expected_credentials) { |
| EXPECT_EQ(S_OK, provider->GetCredentialAt( |
| first_non_anonymous_cred_index++, &cred)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&reauth)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&cred2)); |
| |
| EXPECT_EQ(S_OK, provider->GetCredentialAt( |
| first_non_anonymous_cred_index++, &cred)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&reauth)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&cred2)); |
| } |
| } else if (!other_user_tile_available) { |
| // Only the user who locked the computer should be returned as a credential |
| // and it should be a ICredentialProviderCredential2 with the correct sid. |
| EXPECT_EQ(S_OK, provider->GetCredentialAt(first_non_anonymous_cred_index++, |
| &cred)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&reauth)); |
| EXPECT_EQ(S_OK, cred.QueryInterface(&cred2)); |
| |
| wchar_t* sid; |
| EXPECT_EQ(S_OK, cred2->GetUserSid(&sid)); |
| EXPECT_EQ(second_user_locking_system ? second_sid : first_sid, |
| CComBSTR(W2COLE(sid))); |
| |
| // In the case that a real CReauthCredential is created, we expect that this |
| // credential will set the default credential provider for the user tile. |
| wchar_t guid_in_wchar[64]; |
| ::StringFromGUID2(CLSID_GaiaCredentialProvider, guid_in_wchar, |
| base::size(guid_in_wchar)); |
| |
| wchar_t guid_in_registry[64]; |
| ULONG length = base::size(guid_in_registry); |
| EXPECT_EQ(S_OK, GetMachineRegString(kLogonUiUserTileRegKey, sid, |
| guid_in_registry, &length)); |
| EXPECT_EQ(base::string16(guid_in_wchar), base::string16(guid_in_registry)); |
| ::CoTaskMemFree(sid); |
| } |
| |
| // Deactivate the CP. |
| ASSERT_EQ(S_OK, provider->UnAdvise()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| GcpCredentialProviderAvailableCredentialsTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Values(CPUS_UNLOCK_WORKSTATION, CPUS_LOGON), |
| ::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool())); |
| |
| } // namespace credential_provider |