blob: 0bff9d4a48c46b1c3beb59dd086a027fe1c48f78 [file] [log] [blame]
// Copyright 2015 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 "components/signin/ios/browser/account_consistency_service.h"
#import <WebKit/WebKit.h>
#include <memory>
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/ios/ios_util.h"
#include "base/test/bind.h"
#import "base/test/ios/wait_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "components/content_settings/core/browser/cookie_settings.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/prefs/pref_service.h"
#include "components/signin/core/browser/account_reconcilor.h"
#include "components/signin/core/browser/account_reconcilor_delegate.h"
#include "components/signin/core/browser/chrome_connected_header_helper.h"
#include "components/signin/ios/browser/features.h"
#import "components/signin/ios/browser/manage_accounts_delegate.h"
#include "components/signin/public/base/list_accounts_test_utils.h"
#include "components/signin/public/base/test_signin_client.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
#include "components/signin/public/identity_manager/test_identity_manager_observer.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "ios/web/public/navigation/web_state_policy_decider.h"
#include "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_web_state.h"
#include "ios/web/public/test/web_task_environment.h"
#include "net/base/mac/url_conversions.h"
#include "net/cookies/cookie_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "third_party/ocmock/OCMock/OCMock.h"
#include "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// Fake identity email.
const char* kFakeEmail = "janedoe@gmail.com";
// Google domain.
const char* kGoogleDomain = "google.com";
// Youtube domain.
const char* kYoutubeDomain = "youtube.com";
// Google domain where the CHROME_CONNECTED cookie is set/removed.
const char* kCountryGoogleDomain = "google.de";
// Name of the histogram to record the state of the GAIA cookie for the
// navigation.
const char* kGAIACookieOnNavigationHistogram =
"Signin.IOSGaiaCookieStateOnSignedInNavigation";
// Returns a cookie domain that applies for all origins on |host_domain|.
std::string GetCookieDomain(const std::string& host_domain) {
DCHECK(net::cookie_util::DomainIsHostOnly(host_domain));
std::string cookie_domain = "." + host_domain;
DCHECK(!net::cookie_util::DomainIsHostOnly(cookie_domain));
return cookie_domain;
}
// Returns true if |cookies| contains a cookie with |name| and |domain|.
//
// Note: If |domain| is the empty string, then it returns true if any cookie
// with |name| is found.
bool ContainsCookie(const std::vector<net::CanonicalCookie>& cookies,
const std::string& name,
const std::string& domain) {
for (const auto& cookie : cookies) {
if (cookie.Name() == name) {
if (domain.empty() || cookie.Domain() == domain)
return true;
}
}
return false;
}
// Mock AccountReconcilor to catch call to OnReceivedManageAccountsResponse.
class MockAccountReconcilor : public AccountReconcilor {
public:
MockAccountReconcilor(SigninClient* client)
: AccountReconcilor(
nullptr,
client,
std::make_unique<signin::AccountReconcilorDelegate>()) {}
MOCK_METHOD1(OnReceivedManageAccountsResponse, void(signin::GAIAServiceType));
};
// FakeWebState that allows control over its policy decider.
class FakeWebState : public web::FakeWebState {
public:
FakeWebState() : web::FakeWebState(), decider_(nullptr) {}
void AddPolicyDecider(web::WebStatePolicyDecider* decider) override {
EXPECT_FALSE(decider_);
decider_ = decider;
}
void RemovePolicyDecider(web::WebStatePolicyDecider* decider) override {
EXPECT_EQ(decider_, decider);
decider_ = nullptr;
}
bool ShouldAllowResponse(NSURLResponse* response, bool for_main_frame) {
if (!decider_)
return true;
__block web::WebStatePolicyDecider::PolicyDecision policyDecision =
web::WebStatePolicyDecider::PolicyDecision::Allow();
auto callback =
base::BindOnce(^(web::WebStatePolicyDecider::PolicyDecision decision) {
policyDecision = decision;
});
decider_->ShouldAllowResponse(response, for_main_frame,
std::move(callback));
return policyDecision.ShouldAllowNavigation();
}
void WebStateDestroyed() {
if (!decider_)
return;
decider_->WebStateDestroyed();
}
private:
web::WebStatePolicyDecider* decider_;
};
} // namespace
class AccountConsistencyServiceTest : public PlatformTest {
public:
AccountConsistencyServiceTest()
: task_environment_(web::WebTaskEnvironment::Options::DEFAULT,
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
protected:
void SetUp() override {
PlatformTest::SetUp();
content_settings::CookieSettings::RegisterProfilePrefs(prefs_.registry());
HostContentSettingsMap::RegisterProfilePrefs(prefs_.registry());
signin_client_.reset(
new TestSigninClient(&prefs_, &test_url_loader_factory_));
identity_test_env_.reset(new signin::IdentityTestEnvironment(
/*test_url_loader_factory=*/nullptr, &prefs_,
signin::AccountConsistencyMethod::kDisabled, signin_client_.get()));
settings_map_ = new HostContentSettingsMap(
&prefs_, false /* is_off_the_record */, false /* store_last_modified */,
false /* restore_session */);
cookie_settings_ = new content_settings::CookieSettings(settings_map_.get(),
&prefs_, false, "");
account_reconcilor_ =
std::make_unique<MockAccountReconcilor>(signin_client_.get());
ResetAccountConsistencyService();
}
void TearDown() override {
// Destroy the web state before shutting down
// |account_consistency_service_|.
web_state_.WebStateDestroyed();
account_consistency_service_->Shutdown();
settings_map_->ShutdownOnUIThread();
account_reconcilor_->Shutdown();
identity_test_env_.reset();
PlatformTest::TearDown();
}
void ResetAccountConsistencyService() {
if (account_consistency_service_) {
account_consistency_service_->Shutdown();
}
account_consistency_service_.reset(new AccountConsistencyService(
&browser_state_, account_reconcilor_.get(), cookie_settings_,
identity_test_env_->identity_manager()));
}
// Identity APIs.
void SignIn() {
signin::MakePrimaryAccountAvailable(identity_test_env_->identity_manager(),
kFakeEmail);
WaitUntilAllCookieRequestsAreApplied();
}
void SignOut() {
signin::ClearPrimaryAccount(identity_test_env_->identity_manager());
WaitUntilAllCookieRequestsAreApplied();
}
// Cookie verification APIs.
void CheckDomainHasChromeConnectedCookie(const std::string& domain) {
EXPECT_TRUE(ContainsCookie(GetCookiesInCookieJar(),
signin::kChromeConnectedCookieName,
GetCookieDomain(domain)));
}
void CheckNoChromeConnectedCookieForDomain(const std::string& domain) {
EXPECT_FALSE(ContainsCookie(GetCookiesInCookieJar(),
signin::kChromeConnectedCookieName,
GetCookieDomain(domain)));
}
void CheckNoChromeConnectedCookies() {
EXPECT_FALSE(ContainsCookie(GetCookiesInCookieJar(),
signin::kChromeConnectedCookieName,
/*domain=*/std::string()));
}
// Verifies the time that the Gaia cookie was last updated for google.com.
void CheckGaiaCookieWithUpdateTime(base::Time time) {
EXPECT_EQ(
time,
account_consistency_service_->last_gaia_cookie_verification_time_);
}
// Navigation APIs.
void SimulateNavigateToURL(NSURLResponse* response,
id<ManageAccountsDelegate> delegate) {
SimulateNavigateToURL(response, delegate,
web::PageLoadCompletionStatus::SUCCESS,
/* expected_allowed_response=*/true);
}
void SimulateNavigateToURLWithPageLoadFailure(
NSURLResponse* response,
id<ManageAccountsDelegate> delegate) {
SimulateNavigateToURL(response, delegate,
web::PageLoadCompletionStatus::FAILURE,
/* expected_allowed_response=*/true);
}
void SimulateNavigateToURLWithInterruption(
NSURLResponse* response,
id<ManageAccountsDelegate> delegate) {
SimulateNavigateToURL(response, delegate,
web::PageLoadCompletionStatus::SUCCESS,
/* expected_allowed_response=*/false);
}
// Cookie APIs.
void WaitUntilAllCookieRequestsAreApplied() {
// Spinning the runloop is needed to ensure that the cookie manager requests
// are executed.
base::RunLoop().RunUntilIdle();
EXPECT_EQ(0, account_consistency_service_
->active_cookie_manager_requests_for_testing_);
}
// Simulate the action of GaiaCookieManagerService to cleanup the cookies
// once the sign-out is done.
void RemoveAllChromeConnectedCookies() {
base::RunLoop run_loop;
account_consistency_service_->RemoveAllChromeConnectedCookies(
run_loop.QuitClosure());
run_loop.Run();
}
// Simulate removing all cookies associated with the google.com domain through
// an external source.
void SimulateExternalSourceRemovesAllGoogleDomainCookies() {
network::mojom::CookieManager* cookie_manager =
browser_state_.GetCookieManager();
network::mojom::CookieDeletionFilterPtr filter =
network::mojom::CookieDeletionFilter::New();
filter->including_domains =
base::Optional<std::vector<std::string>>({kGoogleDomain});
cookie_manager->DeleteCookies(std::move(filter),
base::OnceCallback<void(uint)>());
}
// Properties available for tests.
// Creates test threads, necessary for ActiveStateManager that needs a UI
// thread.
web::WebTaskEnvironment task_environment_;
web::FakeBrowserState browser_state_;
sync_preferences::TestingPrefServiceSyncable prefs_;
FakeWebState web_state_;
network::TestURLLoaderFactory test_url_loader_factory_;
std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
std::unique_ptr<AccountConsistencyService> account_consistency_service_;
std::unique_ptr<MockAccountReconcilor> account_reconcilor_;
private:
void SimulateNavigateToURL(NSURLResponse* response,
id<ManageAccountsDelegate> delegate,
web::PageLoadCompletionStatus page_status,
bool expect_allowed_response) {
// If we have already added the |web_state_| with a previous |delegate|,
// remove it to enforce a one-to-one mapping between web state handler and
// web state.
if (!account_consistency_service_->web_state_handlers_.empty()) {
account_consistency_service_->RemoveWebStateHandler(&web_state_);
}
account_consistency_service_->SetWebStateHandler(&web_state_, delegate);
EXPECT_EQ(
expect_allowed_response,
web_state_.ShouldAllowResponse(response, /* for_main_frame = */ true));
web_state_.SetCurrentURL(net::GURLWithNSURL(response.URL));
web_state_.OnPageLoaded(page_status);
}
// Returns set of cookies available to the cookie manager.
std::vector<net::CanonicalCookie> GetCookiesInCookieJar() {
std::vector<net::CanonicalCookie> cookies_out;
base::RunLoop run_loop;
network::mojom::CookieManager* cookie_manager =
browser_state_.GetCookieManager();
cookie_manager->GetAllCookies(base::BindOnce(base::BindLambdaForTesting(
[&run_loop,
&cookies_out](const std::vector<net::CanonicalCookie>& cookies) {
cookies_out = cookies;
run_loop.Quit();
})));
run_loop.Run();
return cookies_out;
}
// Private properties.
std::unique_ptr<TestSigninClient> signin_client_;
scoped_refptr<HostContentSettingsMap> settings_map_;
scoped_refptr<content_settings::CookieSettings> cookie_settings_;
};
// Tests that main domains are added to the internal map when cookies are set in
// reaction to signin.
TEST_F(AccountConsistencyServiceTest, SigninAddCookieOnMainDomains) {
SignIn();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
}
// Tests that cookies that are added during SignIn and subsequent navigations
// are correctly removed during the SignOut.
TEST_F(AccountConsistencyServiceTest, SignInSignOut) {
SignIn();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
CheckNoChromeConnectedCookieForDomain(kCountryGoogleDomain);
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
NSDictionary* headers = [NSDictionary dictionary];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://google.de/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURL(response, delegate);
// Check that cookies was also added for |kCountryGoogleDomain|.
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
CheckDomainHasChromeConnectedCookie(kCountryGoogleDomain);
SignOut();
CheckNoChromeConnectedCookies();
}
// Tests that signing out with no domains known, still call the callback.
TEST_F(AccountConsistencyServiceTest, SignOutWithoutDomains) {
CheckNoChromeConnectedCookies();
SignOut();
CheckNoChromeConnectedCookies();
}
// Tests that the X-Chrome-Manage-Accounts header is ignored unless it comes
// from Gaia signon realm.
TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsNotOnGaia) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
NSDictionary* headers =
[NSDictionary dictionaryWithObject:@"action=DEFAULT"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://google.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURL(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that navigation to Gaia signon realm with no X-Chrome-Manage-Accounts
// header in the response are simply untouched.
TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsNoHeader) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
NSDictionary* headers = [NSDictionary dictionary];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURL(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the ManageAccountsDelegate is notified when a navigation on Gaia
// signon realm returns with a X-Chrome-Manage-Accounts header with action
// DEFAULT.
TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsDefault) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
// Default action is |onManageAccounts|.
[[delegate expect] onManageAccounts];
NSDictionary* headers =
[NSDictionary dictionaryWithObject:@"action=DEFAULT"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_DEFAULT))
.Times(1);
SimulateNavigateToURLWithInterruption(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the ManageAccountsDelegate is notified when a navigation on Gaia
// signon realm returns with a X-Chrome-Manage-Accounts header with show
// consistency promo and ADDSESSION action.
TEST_F(AccountConsistencyServiceTest,
ChromeManageAccountsShowConsistencyPromo) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate expect] onShowConsistencyPromo];
NSDictionary* headers = [NSDictionary
dictionaryWithObject:@"action=ADDSESSION,show_consistency_promo=true"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_ADDSESSION))
.Times(1);
SimulateNavigateToURL(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the consistency promo is not displayed when a page fails to load.
TEST_F(AccountConsistencyServiceTest,
ChromeManageAccountsNotShowConsistencyPromoOnPageLoadFailure) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate reject] onShowConsistencyPromo];
NSDictionary* headers = [NSDictionary
dictionaryWithObject:@"action=ADDSESSION,show_consistency_promo=true"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_ADDSESSION))
.Times(1);
SimulateNavigateToURLWithPageLoadFailure(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the consistency promo is not displayed when a page fails to load
// and user chooses another action.
TEST_F(AccountConsistencyServiceTest,
ChromeManageAccountsNotShowConsistencyPromoOnPageLoadFailureRedirect) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate expect] onAddAccount];
[[delegate reject] onShowConsistencyPromo];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_ADDSESSION))
.Times(2);
NSDictionary* headers = [NSDictionary
dictionaryWithObject:@"action=ADDSESSION,show_consistency_promo=true"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* responseSignin = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURLWithPageLoadFailure(responseSignin, delegate);
NSDictionary* headersAddAccount =
[NSDictionary dictionaryWithObject:@"action=ADDSESSION"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* responseAddAccount = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headersAddAccount];
SimulateNavigateToURLWithInterruption(responseAddAccount, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the consistency promo is not displayed when a non GAIA URL is
// committed.
TEST_F(AccountConsistencyServiceTest,
ChromeManageAccountsNotShowConsistencyPromoOnNonGaiaURL) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate reject] onShowConsistencyPromo];
NSDictionary* headers = [NSDictionary
dictionaryWithObject:@"action=ADDSESSION,show_consistency_promo=true"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://youtube.com//"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURL(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that the ManageAccountsDelegate is notified when a navigation on Gaia
// signon realm returns with a X-Chrome-Manage-Accounts header with ADDSESSION
// action.
TEST_F(AccountConsistencyServiceTest, ChromeManageAccountsShowAddAccount) {
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate expect] onAddAccount];
NSDictionary* headers =
[NSDictionary dictionaryWithObject:@"action=ADDSESSION"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_ADDSESSION))
.Times(1);
SimulateNavigateToURLWithInterruption(response, delegate);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Tests that domains with cookie are correctly loaded from the prefs on service
// startup.
TEST_F(AccountConsistencyServiceTest, DomainsWithCookieLoadedFromPrefs) {
SignIn();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
ResetAccountConsistencyService();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
SignOut();
CheckNoChromeConnectedCookies();
}
// Tests that domains with cookie are cleared when browsing data is removed.
TEST_F(AccountConsistencyServiceTest, DomainsClearedOnBrowsingDataRemoved) {
SignIn();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
// Sets Response to get IdentityManager::Observer::OnAccountsInCookieUpdated
// through GaiaCookieManagerService::OnCookieChange.
signin::SetListAccountsResponseNoAccounts(&test_url_loader_factory_);
base::RunLoop run_loop;
identity_test_env_->identity_manager_observer()
->SetOnAccountsInCookieUpdatedCallback(run_loop.QuitClosure());
// OnBrowsingDataRemoved triggers
// AccountsCookieMutator::ForceTriggerOnCookieChange and finally
// IdentityManager::Observer::OnAccountsInCookieUpdated is called.
account_consistency_service_->OnBrowsingDataRemoved();
run_loop.Run();
// AccountConsistency service is supposed to rebuild the CHROME_CONNECTED
// cookies when browsing data is removed.
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
}
// Tests that google.com domain cookies can be regenerated after an external
// source removes these cookies.
TEST_F(AccountConsistencyServiceTest,
AddChromeConnectedCookiesOnCookiesRemoved) {
SignIn();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
SimulateExternalSourceRemovesAllGoogleDomainCookies();
CheckNoChromeConnectedCookieForDomain(kGoogleDomain);
// Forcibly rebuild the CHROME_CONNECTED cookies.
account_consistency_service_->AddChromeConnectedCookies();
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
}
// Tests that the CHROME_CONNECTED cookie is set on Google and Google-associated
// domains when the account consistency service runs.
TEST_F(AccountConsistencyServiceTest, SetChromeConnectedCookie) {
SignIn();
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
NSDictionary* headers = [NSDictionary dictionary];
// HTTP response URL is eligible for Mirror (the test does not use google.com
// since the CHROME_CONNECTED cookie is generated for it by default.
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://youtube.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
SimulateNavigateToURL(response, delegate);
SimulateExternalSourceRemovesAllGoogleDomainCookies();
SimulateNavigateToURL(response, delegate);
CheckDomainHasChromeConnectedCookie(kGoogleDomain);
CheckDomainHasChromeConnectedCookie(kYoutubeDomain);
}
// Tests that the GAIA cookie update time is not updated before the scheduled
// interval.
TEST_F(AccountConsistencyServiceTest, SetGaiaCookieUpdateNotUpdateTime) {
SignIn();
// HTTP response URL is eligible for Mirror (the test does not use google.com
// since the CHROME_CONNECTED cookie is generated for it by default.
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://youtube.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:@{}];
SimulateNavigateToURL(response, nil);
// Advance clock, but stay within the one-hour Gaia update time.
base::TimeDelta oneMinuteDelta = base::TimeDelta::FromMinutes(1);
task_environment_.FastForwardBy(oneMinuteDelta);
SimulateNavigateToURL(response, nil);
CheckGaiaCookieWithUpdateTime(base::Time::Now() - oneMinuteDelta);
}
// Tests that the GAIA cookie update time is updated at the scheduled interval.
TEST_F(AccountConsistencyServiceTest, SetGaiaCookieUpdateAtUpdateTime) {
SignIn();
// HTTP response URL is eligible for Mirror (the test does not use google.com
// since the CHROME_CONNECTED cookie is generated for it by default.
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://youtube.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:@{}];
SimulateNavigateToURL(response, nil);
// Advance clock past one-hour Gaia update time.
task_environment_.FastForwardBy(base::TimeDelta::FromHours(2));
SimulateNavigateToURL(response, nil);
CheckGaiaCookieWithUpdateTime(base::Time::Now());
}
// Ensures that the presence or absence of GAIA cookies is logged even if the
// |kRestoreGAIACookiesIfDeleted| experiment is disabled.
TEST_F(AccountConsistencyServiceTest, GAIACookieStatusLoggedProperly) {
// HTTP response URL is eligible for Mirror (the test does not use google.com
// since the CHROME_CONNECTED cookie is generated for it by default.
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://youtube.com"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:@{}];
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(kGAIACookieOnNavigationHistogram, 0);
SimulateNavigateToURL(response, nil);
base::RunLoop().RunUntilIdle();
histogram_tester.ExpectTotalCount(kGAIACookieOnNavigationHistogram, 0);
SignIn();
SimulateNavigateToURL(response, nil);
base::RunLoop().RunUntilIdle();
histogram_tester.ExpectTotalCount(kGAIACookieOnNavigationHistogram, 1);
}
// Tests that navigating to accounts.google.com without a GAIA cookie is logged
// by the navigation histogram.
TEST_F(AccountConsistencyServiceTest, GAIACookieMissingOnSignin) {
SignIn();
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate expect] onAddAccount];
NSDictionary* headers =
[NSDictionary dictionaryWithObject:@"action=ADDSESSION"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
EXPECT_CALL(*account_reconcilor_, OnReceivedManageAccountsResponse(
signin::GAIA_SERVICE_TYPE_ADDSESSION))
.Times(2);
SimulateNavigateToURLWithInterruption(response, delegate);
base::HistogramTester histogram_tester;
histogram_tester.ExpectTotalCount(kGAIACookieOnNavigationHistogram, 0);
SimulateExternalSourceRemovesAllGoogleDomainCookies();
[[delegate expect] onAddAccount];
SimulateNavigateToURLWithInterruption(response, delegate);
histogram_tester.ExpectTotalCount(kGAIACookieOnNavigationHistogram, 1);
EXPECT_OCMOCK_VERIFY(delegate);
}
// Ensures that set and remove cookie operations are handled in the order
// they are called resulting in no cookies.
TEST_F(AccountConsistencyServiceTest, DeleteChromeConnectedCookiesAfterSet) {
SignIn();
// URLs must pass IsGoogleDomainURL and not duplicate sign-in domains
// |kGoogleDomain| or |kYouTubeDomain| otherwise they will not be reset since
// it is before the update time. Add multiple URLs to test for race conditions
// with remove call.
account_consistency_service_->SetChromeConnectedCookieWithUrls(
{GURL("https://google.ca"), GURL("https://google.fr"),
GURL("https://google.de")});
RemoveAllChromeConnectedCookies();
WaitUntilAllCookieRequestsAreApplied();
CheckNoChromeConnectedCookies();
}
// Ensures that set and remove cookie operations are handled in the order
// they are called resulting in one cookie.
TEST_F(AccountConsistencyServiceTest, SetChromeConnectedCookiesAfterDelete) {
SignIn();
// URLs must pass IsGoogleDomainURL and not duplicate sign-in domains
// |kGoogleDomain| or |kYouTubeDomain| otherwise they will not be reset since
// it is before the update time. Add multiple URLs to test for race conditions
// with remove call.
account_consistency_service_->SetChromeConnectedCookieWithUrls(
{GURL("https://google.ca"), GURL("https://google.fr"),
GURL("https://google.de")});
RemoveAllChromeConnectedCookies();
account_consistency_service_->SetChromeConnectedCookieWithUrls(
{GURL("https://google.ca")});
WaitUntilAllCookieRequestsAreApplied();
CheckDomainHasChromeConnectedCookie("google.ca");
}
// Ensures that CHROME_CONNECTED cookies are not set on google.com when the user
// is signed out and navigating to google.com for |kMobileIdentityConsistency|
// experiment.
TEST_F(AccountConsistencyServiceTest,
SetMiceChromeConnectedCookiesSignedOutGoogleVisitor) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(signin::kMobileIdentityConsistency);
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
NSDictionary* headers =
[NSDictionary dictionaryWithObject:@"action=ADDSESSION"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
CheckNoChromeConnectedCookies();
SimulateNavigateToURL(response, delegate);
CheckNoChromeConnectedCookies();
EXPECT_OCMOCK_VERIFY(delegate);
}
// Ensures that CHROME_CONNECTED cookies are only set on GAIA urls when the user
// is signed out and taps sign-in button for |kMobileIdentityConsistency|
// experiment. These cookies are immediately removed after the sign-in promo is
// shown.
TEST_F(AccountConsistencyServiceTest,
SetMiceChromeConnectedCookiesSignedOutGaiaVisitor) {
base::test::ScopedFeatureList feature_list;
feature_list.InitAndEnableFeature(signin::kMobileIdentityConsistency);
id delegate =
[OCMockObject mockForProtocol:@protocol(ManageAccountsDelegate)];
[[delegate expect] onShowConsistencyPromo];
NSDictionary* headers = [NSDictionary
dictionaryWithObject:@"action=ADDSESSION,show_consistency_promo=true"
forKey:@"X-Chrome-Manage-Accounts"];
NSHTTPURLResponse* response = [[NSHTTPURLResponse alloc]
initWithURL:[NSURL URLWithString:@"https://accounts.google.com/"]
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:headers];
account_consistency_service_->SetWebStateHandler(&web_state_, delegate);
EXPECT_TRUE(web_state_.ShouldAllowResponse(response,
/* for_main_frame = */ true));
CheckDomainHasChromeConnectedCookie("accounts.google.com");
web_state_.SetCurrentURL(net::GURLWithNSURL(response.URL));
web_state_.OnPageLoaded(web::PageLoadCompletionStatus::SUCCESS);
CheckNoChromeConnectedCookies();
EXPECT_OCMOCK_VERIFY(delegate);
}