blob: b2d080b330633054293a026af5047430ed570d78 [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 "chrome/credential_provider/gaiacp/stdafx.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/test_reg_util_win.h"
#include "base/time/time_override.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/gaiacp/token_handle_validator.h"
#include "chrome/credential_provider/test/gcp_fakes.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace credential_provider {
namespace {
base::string16 GetNewSidString(FakeOSUserManager* fake_os_user_manager) {
PSID sid;
HRESULT hr = fake_os_user_manager->CreateNewSID(&sid);
if (FAILED(hr))
return base::string16();
LPWSTR sid_string;
bool convert = ::ConvertSidToStringSid(sid, &sid_string);
::FreeSid(sid);
base::string16 result;
if (convert)
result = (sid_string);
::LocalFree(sid_string);
return result;
}
} // namespace
namespace testing {
class TokenHandleValidatorTest : public ::testing::Test {
protected:
void CreateDeletedGCPWUser(BSTR* sid) {
PSID sid_deleted;
ASSERT_EQ(S_OK, fake_os_user_manager_.CreateNewSID(&sid_deleted));
wchar_t* user_sid_string = nullptr;
ASSERT_TRUE(ConvertSidToStringSid(sid_deleted, &user_sid_string));
*sid = SysAllocString(W2COLE(user_sid_string));
ASSERT_EQ(S_OK, SetUserProperty(user_sid_string, kUserId, L"id_value"));
ASSERT_EQ(S_OK,
SetUserProperty(user_sid_string, kUserTokenHandle, L"th_value"));
LocalFree(user_sid_string);
}
TokenHandleValidatorTest();
~TokenHandleValidatorTest() override;
void SetUp() override;
FakeOSUserManager* fake_os_user_manager() { return &fake_os_user_manager_; }
FakeWinHttpUrlFetcherFactory* fake_http_url_fetcher_factory() {
return &fake_http_url_fetcher_factory_;
}
private:
FakeOSUserManager fake_os_user_manager_;
FakeWinHttpUrlFetcherFactory fake_http_url_fetcher_factory_;
registry_util::RegistryOverrideManager registry_override_;
};
TokenHandleValidatorTest::TokenHandleValidatorTest() = default;
TokenHandleValidatorTest ::~TokenHandleValidatorTest() = default;
void TokenHandleValidatorTest::SetUp() {
ASSERT_NO_FATAL_FAILURE(
registry_override_.OverrideRegistry(HKEY_LOCAL_MACHINE));
}
TEST_F(TokenHandleValidatorTest, CleanupStaleUsers) {
// Simulate a user created by GCPW that does not have a stale handle.
CComBSTR sid_good;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"Full Name", L"Comment",
L"gaia-id", L"foo@gmail.com", &sid_good));
ASSERT_EQ(S_OK,
SetUserProperty(OLE2W(sid_good), kUserTokenHandle, L"good-th"));
// Simulate a user created by GCPW that was deleted from the machine.
CComBSTR sid_bad;
CreateDeletedGCPWUser(&sid_bad);
// Simulate a user created by GCPW that has no gaia id.
CComBSTR sid_no_gaia_id;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username2", L"password", L"Full Name", L"Comment", L"",
L"foo2@gmail.com", &sid_no_gaia_id));
// Simulate a user created by GCPW that has a gaia id, but no token handle
// set.
CComBSTR sid_no_token_handle;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username3", L"password", L"Full Name", L"Comment",
L"gaia-id3", L"foo3@gmail.com", &sid_no_token_handle));
// Clear the token handle automatically created by CreateTestOSUser.
EXPECT_EQ(S_OK,
SetUserProperty((BSTR)sid_no_token_handle, kUserTokenHandle, L""));
wchar_t token_handle[256];
DWORD length = base::size(token_handle);
EXPECT_NE(S_OK, GetUserProperty((BSTR)sid_no_token_handle, kUserTokenHandle,
token_handle, &length));
// Create a token handle validator and start a refresh so that
// stale token handles are cleaned.
FakeTokenHandleValidator validator;
validator.StartRefreshingTokenHandleValidity();
// Expect "good" sid to still in the registry.
base::win::RegKey key;
ASSERT_EQ(ERROR_SUCCESS,
key.Open(HKEY_LOCAL_MACHINE, kGcpUsersRootKeyName, KEY_READ));
EXPECT_EQ(ERROR_SUCCESS, key.OpenKey(OLE2CW(sid_good), KEY_READ));
// For all bad users, token handle should be valid since the assumption is
// that if no user entry is found internally in the validator then it is an
// unassociated user and thus always has a valid token handle.
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2CW(sid_bad)));
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2CW(sid_no_gaia_id)));
EXPECT_FALSE(
validator.IsTokenHandleValidForUser(OLE2CW(sid_no_token_handle)));
// Expect deleted user and user with no gaia id to be deleted.
EXPECT_NE(ERROR_SUCCESS, key.OpenKey(OLE2CW(sid_bad), KEY_READ));
EXPECT_NE(ERROR_SUCCESS, key.OpenKey(OLE2CW(sid_no_gaia_id), KEY_READ));
// Expect user with no token handle to still not have a token handle set in
// the registry.
length = base::size(token_handle);
EXPECT_NE(S_OK, GetUserProperty((BSTR)sid_no_token_handle, kUserTokenHandle,
token_handle, &length));
}
TEST_F(TokenHandleValidatorTest, NoTokenHandles) {
FakeTokenHandleValidator validator;
validator.StartRefreshingTokenHandleValidity();
// If there is no associated user then all token handles are valid.
EXPECT_TRUE(validator.IsTokenHandleValidForUser(
GetNewSidString(fake_os_user_manager())));
EXPECT_EQ(0u, fake_http_url_fetcher_factory()->requests_created());
}
TEST_F(TokenHandleValidatorTest, ValidTokenHandle) {
FakeTokenHandleValidator validator;
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
// Valid token fetch result.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{\"expires_in\":1}");
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
}
TEST_F(TokenHandleValidatorTest, InvalidTokenHandle) {
FakeTokenHandleValidator validator;
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
// Invalid token fetch result.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{}");
EXPECT_FALSE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
}
TEST_F(TokenHandleValidatorTest, InvalidTokenHandleNoInternet) {
FakeTokenHandleValidator validator;
FakeInternetAvailabilityChecker internet_checker(
FakeInternetAvailabilityChecker::kHicForceNo);
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(0u, fake_http_url_fetcher_factory()->requests_created());
}
TEST_F(TokenHandleValidatorTest, InvalidTokenHandleTimeout) {
FakeTokenHandleValidator validator(base::TimeDelta::FromMilliseconds(50));
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
base::WaitableEvent http_fetcher_event;
// Invalid token fetch result.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{}", http_fetcher_event.handle());
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
http_fetcher_event.Signal();
}
TEST_F(TokenHandleValidatorTest, TokenHandleValidityStillFresh) {
FakeTokenHandleValidator validator;
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
// Valid token fetch result.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{\"expires_in\":1}");
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
}
class TimeClockOverrideValue {
public:
static base::Time NowOverride() { return current_time_; }
static base::Time current_time_;
};
base::Time TimeClockOverrideValue::current_time_;
TEST_F(TokenHandleValidatorTest, ValidTokenHandle_Refresh) {
// Save the current time and then override the time clock to return a fake
// time.
TimeClockOverrideValue::current_time_ = base::Time::Now();
base::subtle::ScopedTimeClockOverrides time_override(
&TimeClockOverrideValue::NowOverride, nullptr, nullptr);
FakeTokenHandleValidator validator;
CComBSTR sid;
ASSERT_EQ(S_OK, fake_os_user_manager()->CreateTestOSUser(
L"username", L"password", L"fullname", L"comment",
L"gaia-id", base::string16(), &sid));
ASSERT_EQ(S_OK, SetUserProperty(OLE2W(sid), kUserTokenHandle, L"th"));
validator.StartRefreshingTokenHandleValidity();
// Valid token fetch result.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{\"expires_in\":1}");
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
// Make the next token fetch result invalid.
fake_http_url_fetcher_factory()->SetFakeResponse(
GURL(TokenHandleValidator::kTokenInfoUrl),
FakeWinHttpUrlFetcher::Headers(), "{}");
// If the lifetime of the validity has not expired, even if the token is
// invalid, no new fetch will be performed yet.
EXPECT_TRUE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(1u, fake_http_url_fetcher_factory()->requests_created());
// Advance the time so that a new fetch will be done and retrieve the
// invalid result now.
TimeClockOverrideValue::current_time_ +=
TokenHandleValidator::kTokenHandleValidityLifetime +
base::TimeDelta::FromMilliseconds(1);
EXPECT_FALSE(validator.IsTokenHandleValidForUser(OLE2W(sid)));
EXPECT_EQ(2u, fake_http_url_fetcher_factory()->requests_created());
}
} // namespace testing
} // namespace credential_provider