| // 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 "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/logging.h" |
| #import "base/mac/foundation_util.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/google/core/common/google_util.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "components/signin/core/browser/account_reconcilor.h" |
| #include "components/signin/core/browser/signin_header_helper.h" |
| #include "components/signin/ios/browser/features.h" |
| #include "components/signin/public/base/account_consistency_method.h" |
| #include "components/signin/public/identity_manager/accounts_cookie_mutator.h" |
| #include "components/signin/public/identity_manager/accounts_in_cookie_jar_info.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "ios/web/common/web_view_creation_util.h" |
| #include "ios/web/public/browser_state.h" |
| #import "ios/web/public/navigation/web_state_policy_decider.h" |
| #import "ios/web/public/web_state.h" |
| #include "ios/web/public/web_state_observer.h" |
| #include "net/base/mac/url_conversions.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "url/gurl.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace { |
| |
| // The validity of CHROME_CONNECTED cookies is one day maximum as a |
| // precaution to ensure that the cookie is regenerated in the case that it |
| // is removed or invalidated. |
| constexpr base::TimeDelta kDelayThresholdToUpdateChromeConnectedCookie = |
| base::TimeDelta::FromHours(24); |
| // The validity of the Gaia cookie on the Google domain is one hour to |
| // ensure that Mirror account consistency is respected in light of the more |
| // restrictive Intelligent Tracking Prevention (ITP) guidelines in iOS 14 |
| // that may remove or invalidate Gaia cookies on the Google domain. |
| constexpr base::TimeDelta kDelayThresholdToUpdateGaiaCookie = |
| base::TimeDelta::FromHours(1); |
| |
| const char* kGoogleUrl = "https://google.com"; |
| const char* kYoutubeUrl = "https://youtube.com"; |
| const char* kGaiaDomain = "accounts.google.com"; |
| |
| // Returns the registered, organization-identifying host, but no subdomains, |
| // from the given GURL. Returns an empty string if the GURL is invalid. |
| static std::string GetDomainFromUrl(const GURL& url) { |
| if (gaia::IsGaiaSignonRealm(url.GetOrigin())) { |
| return kGaiaDomain; |
| } |
| return net::registry_controlled_domains::GetDomainAndRegistry( |
| url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); |
| } |
| |
| // The Gaia cookie state on navigation for a signed-in Chrome user. |
| // These values are persisted to logs. Entries should not be renumbered and |
| // numeric values should never be reused. |
| enum class GaiaCookieStateOnSignedInNavigation { |
| kGaiaCookiePresentOnNavigation = 0, |
| kGaiaCookieAbsentOnGoogleAssociatedDomainNavigation = 1, |
| kGaiaCookieAbsentOnAddSessionNavigation = 2, |
| kMaxValue = kGaiaCookieAbsentOnAddSessionNavigation |
| }; |
| |
| // Records the state of Gaia cookies for a navigation in UMA histogram. |
| void LogIOSGaiaCookiesState(GaiaCookieStateOnSignedInNavigation state) { |
| base::UmaHistogramEnumeration("Signin.IOSGaiaCookieStateOnSignedInNavigation", |
| state); |
| } |
| |
| // Allows for manual testing by reducing the polling interval for verifying the |
| // existence of the GAIA cookie. |
| base::TimeDelta GetDelayThresholdToUpdateGaiaCookie() { |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if (command_line->HasSwitch( |
| signin::kDelayThresholdMinutesToUpdateGaiaCookie)) { |
| std::string delayString = command_line->GetSwitchValueASCII( |
| signin::kDelayThresholdMinutesToUpdateGaiaCookie); |
| int commandLineDelay = 0; |
| if (base::StringToInt(delayString, &commandLineDelay)) { |
| return base::TimeDelta::FromMinutes(commandLineDelay); |
| } |
| } |
| return kDelayThresholdToUpdateGaiaCookie; |
| } |
| |
| // WebStatePolicyDecider that monitors the HTTP headers on Gaia responses, |
| // reacting on the X-Chrome-Manage-Accounts header and notifying its delegate. |
| // It also notifies the AccountConsistencyService of domains it should add the |
| // CHROME_CONNECTED cookie to. |
| class AccountConsistencyHandler : public web::WebStatePolicyDecider, |
| public web::WebStateObserver { |
| public: |
| AccountConsistencyHandler(web::WebState* web_state, |
| AccountConsistencyService* service, |
| AccountReconcilor* account_reconcilor, |
| signin::IdentityManager* identity_manager, |
| id<ManageAccountsDelegate> delegate); |
| |
| void WebStateDestroyed(web::WebState* web_state) override; |
| |
| private: |
| // web::WebStateObserver override. |
| void PageLoaded( |
| web::WebState* web_state, |
| web::PageLoadCompletionStatus load_completion_status) override; |
| |
| // web::WebStatePolicyDecider override. |
| WebStatePolicyDecider::PolicyDecision ShouldAllowRequest( |
| NSURLRequest* request, |
| const web::WebStatePolicyDecider::RequestInfo& request_info) override; |
| // Decides on navigation corresponding to |response| whether the navigation |
| // should continue and updates authentication cookies on Google domains. |
| void ShouldAllowResponse( |
| NSURLResponse* response, |
| bool for_main_frame, |
| base::OnceCallback<void(PolicyDecision)> callback) override; |
| void WebStateDestroyed() override; |
| |
| // Marks that GAIA cookies have been restored. |
| void MarkGaiaCookiesRestored(); |
| |
| // Loads |url| in the current tab. |
| void NavigateToURL(GURL url); |
| |
| bool show_consistency_promo_ = false; |
| bool gaia_cookies_restored_ = false; |
| AccountConsistencyService* account_consistency_service_; // Weak. |
| AccountReconcilor* account_reconcilor_; // Weak. |
| signin::IdentityManager* identity_manager_; |
| web::WebState* web_state_; |
| __weak id<ManageAccountsDelegate> delegate_; |
| base::WeakPtrFactory<AccountConsistencyHandler> weak_ptr_factory_; |
| }; |
| } // namespace |
| |
| AccountConsistencyHandler::AccountConsistencyHandler( |
| web::WebState* web_state, |
| AccountConsistencyService* service, |
| AccountReconcilor* account_reconcilor, |
| signin::IdentityManager* identity_manager, |
| id<ManageAccountsDelegate> delegate) |
| : web::WebStatePolicyDecider(web_state), |
| account_consistency_service_(service), |
| account_reconcilor_(account_reconcilor), |
| identity_manager_(identity_manager), |
| web_state_(web_state), |
| delegate_(delegate), |
| weak_ptr_factory_(this) { |
| web_state->AddObserver(this); |
| } |
| |
| web::WebStatePolicyDecider::PolicyDecision |
| AccountConsistencyHandler::ShouldAllowRequest( |
| NSURLRequest* request, |
| const web::WebStatePolicyDecider::RequestInfo& request_info) { |
| GURL url = net::GURLWithNSURL(request.URL); |
| if (base::FeatureList::IsEnabled(signin::kRestoreGaiaCookiesOnUserAction) && |
| signin::IsUrlEligibleForMirrorCookie(url) && |
| identity_manager_->HasPrimaryAccount()) { |
| // CHROME_CONNECTED cookies are added asynchronously on google.com and |
| // youtube.com domains when Chrome detects that the user is signed-in. By |
| // continuing to fulfill the navigation once the cookie request is sent, |
| // Chrome adopts a best-effort strategy for signing the user into the web if |
| // necessary. |
| account_consistency_service_->AddChromeConnectedCookies(); |
| } |
| return PolicyDecision::Allow(); |
| } |
| |
| void AccountConsistencyHandler::ShouldAllowResponse( |
| NSURLResponse* response, |
| bool for_main_frame, |
| base::OnceCallback<void(PolicyDecision)> callback) { |
| NSHTTPURLResponse* http_response = |
| base::mac::ObjCCast<NSHTTPURLResponse>(response); |
| if (!http_response) { |
| std::move(callback).Run(PolicyDecision::Allow()); |
| return; |
| } |
| |
| GURL url = net::GURLWithNSURL(http_response.URL); |
| // User is showing intent to navigate to a Google-owned domain. Set GAIA and |
| // CHROME_CONNECTED cookies if the user is signed in or if they are not signed |
| // in and navigating to a GAIA sign-on (this is filtered in |
| // ChromeConnectedHelper). |
| if (signin::IsUrlEligibleForMirrorCookie(url)) { |
| account_consistency_service_->SetChromeConnectedCookieWithUrls( |
| {url, GURL(kGoogleUrl)}); |
| } |
| |
| // Chrome monitors GAIA cookies when navigating to Google associated domains |
| // to ensure that signed-in users remain signed-in to their Google services on |
| // the web. This includes redirects to accounts.google.com. |
| if (google_util::IsGoogleAssociatedDomainUrl(url)) { |
| // TODO(crbug.com/1131027): Disable GAIA cookie restore on Google URLs that |
| // may display cookie consent in the content area that conflict with the |
| // sign-in notification. This will be removed once we perform cookie |
| // restoration before sending a navigation request. |
| if (!(google_util::IsGoogleHomePageUrl(url) || |
| google_util::IsGoogleSearchUrl(url))) { |
| // Reset boolean that tracks displaying the sign-in notification infobar. |
| // This ensures that only the most recent navigation will trigger an |
| // infobar. |
| gaia_cookies_restored_ = false; |
| account_consistency_service_->SetGaiaCookiesIfDeleted( |
| base::BindOnce(&AccountConsistencyHandler::MarkGaiaCookiesRestored, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| if (!gaia::IsGaiaSignonRealm(url.GetOrigin())) { |
| std::move(callback).Run(PolicyDecision::Allow()); |
| return; |
| } |
| NSString* manage_accounts_header = [[http_response allHeaderFields] |
| objectForKey: |
| [NSString stringWithUTF8String:signin::kChromeManageAccountsHeader]]; |
| if (!manage_accounts_header) { |
| std::move(callback).Run(PolicyDecision::Allow()); |
| return; |
| } |
| |
| signin::ManageAccountsParams params = signin::BuildManageAccountsParams( |
| base::SysNSStringToUTF8(manage_accounts_header)); |
| |
| account_reconcilor_->OnReceivedManageAccountsResponse(params.service_type); |
| // Reset boolean that tracks displaying the sign-in consistency promo. This |
| // ensures that the promo is cancelled once navigation has started and the |
| // WKWebView is cancelling previous navigations. |
| show_consistency_promo_ = false; |
| |
| switch (params.service_type) { |
| case signin::GAIA_SERVICE_TYPE_INCOGNITO: { |
| GURL continue_url = GURL(params.continue_url); |
| DLOG_IF(ERROR, !params.continue_url.empty() && !continue_url.is_valid()) |
| << "Invalid continuation URL: \"" << continue_url << "\""; |
| [delegate_ onGoIncognito:continue_url]; |
| break; |
| } |
| case signin::GAIA_SERVICE_TYPE_SIGNUP: |
| case signin::GAIA_SERVICE_TYPE_ADDSESSION: |
| // This situation is only possible if the all cookies have been deleted by |
| // ITP restrictions and Chrome has not triggered a cookie refresh. |
| if (identity_manager_->HasPrimaryAccount()) { |
| LogIOSGaiaCookiesState(GaiaCookieStateOnSignedInNavigation:: |
| kGaiaCookieAbsentOnAddSessionNavigation); |
| if (base::FeatureList::IsEnabled( |
| signin::kRestoreGaiaCookiesOnUserAction)) { |
| // Reset boolean that tracks displaying the sign-in notification |
| // infobar. This ensures that only the most recent navigation will |
| // trigger an infobar. |
| gaia_cookies_restored_ = false; |
| GURL continue_url = GURL(params.continue_url); |
| DLOG_IF(ERROR, |
| !params.continue_url.empty() && !continue_url.is_valid()) |
| << "Invalid continuation URL: \"" << continue_url << "\""; |
| identity_manager_->GetAccountsCookieMutator() |
| ->ForceTriggerOnCookieChange(); |
| account_consistency_service_->AddCookieRestoreCallback( |
| base::BindOnce(&AccountConsistencyHandler::NavigateToURL, |
| weak_ptr_factory_.GetWeakPtr(), continue_url)); |
| return; |
| } |
| } |
| if (params.show_consistency_promo) { |
| show_consistency_promo_ = true; |
| // Allows the URL response to load before showing the consistency promo. |
| // The promo should always be displayed in the foreground of Gaia |
| // sign-on. |
| std::move(callback).Run(PolicyDecision::Allow()); |
| return; |
| } else { |
| [delegate_ onAddAccount]; |
| } |
| break; |
| case signin::GAIA_SERVICE_TYPE_SIGNOUT: |
| case signin::GAIA_SERVICE_TYPE_DEFAULT: |
| [delegate_ onManageAccounts]; |
| break; |
| case signin::GAIA_SERVICE_TYPE_NONE: |
| NOTREACHED(); |
| break; |
| } |
| |
| // WKWebView loads a blank page even if the response code is 204 |
| // ("No Content"). http://crbug.com/368717 |
| // |
| // Manage accounts responses are handled via native UI. Abort this request |
| // for the following reasons: |
| // * Avoid loading a blank page in WKWebView. |
| // * Avoid adding this request to history. |
| std::move(callback).Run(PolicyDecision::Cancel()); |
| } |
| |
| void AccountConsistencyHandler::MarkGaiaCookiesRestored() { |
| gaia_cookies_restored_ = true; |
| } |
| |
| void AccountConsistencyHandler::NavigateToURL(GURL url) { |
| gaia_cookies_restored_ = true; |
| web_state_->OpenURL(web::WebState::OpenURLParams( |
| url, web::Referrer(), WindowOpenDisposition::CURRENT_TAB, |
| ui::PAGE_TRANSITION_AUTO_TOPLEVEL, false)); |
| } |
| |
| void AccountConsistencyHandler::PageLoaded( |
| web::WebState* web_state, |
| web::PageLoadCompletionStatus load_completion_status) { |
| const GURL& url = web_state->GetLastCommittedURL(); |
| if (load_completion_status == web::PageLoadCompletionStatus::FAILURE || |
| !google_util::IsGoogleDomainUrl( |
| url, google_util::ALLOW_SUBDOMAIN, |
| google_util::DISALLOW_NON_STANDARD_PORTS)) { |
| return; |
| } |
| |
| // Displays the sign-in notification infobar if GAIA cookies have been |
| // restored. This occurs once the URL has been loaded to avoid a race |
| // condition in which the infobar is dismissed prior to the page load. |
| if (gaia_cookies_restored_) { |
| [delegate_ onRestoreGaiaCookies]; |
| gaia_cookies_restored_ = false; |
| } |
| |
| if (show_consistency_promo_ && gaia::IsGaiaSignonRealm(url.GetOrigin())) { |
| [delegate_ onShowConsistencyPromo]; |
| show_consistency_promo_ = false; |
| |
| // Chrome uses the CHROME_CONNECTED cookie to determine whether the |
| // eligibility promo should be shown. Once it is shown we should remove the |
| // cookie, since it should otherwise not be used unless the user is signed |
| // in. |
| account_consistency_service_->RemoveAllChromeConnectedCookies( |
| base::OnceClosure()); |
| } |
| } |
| |
| void AccountConsistencyHandler::WebStateDestroyed(web::WebState* web_state) {} |
| |
| void AccountConsistencyHandler::WebStateDestroyed() { |
| account_consistency_service_->RemoveWebStateHandler(web_state()); |
| } |
| |
| const char AccountConsistencyService::kChromeConnectedCookieName[] = |
| "CHROME_CONNECTED"; |
| |
| const char AccountConsistencyService::kGaiaCookieName[] = "SAPISID"; |
| |
| const char AccountConsistencyService::kDomainsWithCookiePref[] = |
| "signin.domains_with_cookie"; |
| |
| AccountConsistencyService::AccountConsistencyService( |
| web::BrowserState* browser_state, |
| PrefService* prefs, |
| AccountReconcilor* account_reconcilor, |
| scoped_refptr<content_settings::CookieSettings> cookie_settings, |
| signin::IdentityManager* identity_manager) |
| : browser_state_(browser_state), |
| prefs_(prefs), |
| account_reconcilor_(account_reconcilor), |
| cookie_settings_(cookie_settings), |
| identity_manager_(identity_manager), |
| active_cookie_manager_requests_for_testing_(0) { |
| identity_manager_->AddObserver(this); |
| LoadFromPrefs(); |
| if (identity_manager_->HasPrimaryAccount()) { |
| AddChromeConnectedCookies(); |
| } else { |
| RemoveAllChromeConnectedCookies(base::OnceClosure()); |
| } |
| } |
| |
| AccountConsistencyService::~AccountConsistencyService() { |
| } |
| |
| // static |
| void AccountConsistencyService::RegisterPrefs(PrefRegistrySimple* registry) { |
| registry->RegisterDictionaryPref( |
| AccountConsistencyService::kDomainsWithCookiePref); |
| } |
| |
| void AccountConsistencyService::SetWebStateHandler( |
| web::WebState* web_state, |
| id<ManageAccountsDelegate> delegate) { |
| DCHECK_EQ(0u, web_state_handlers_.count(web_state)); |
| web_state_handlers_[web_state].reset(new AccountConsistencyHandler( |
| web_state, this, account_reconcilor_, identity_manager_, delegate)); |
| } |
| |
| void AccountConsistencyService::RemoveWebStateHandler( |
| web::WebState* web_state) { |
| DCHECK_LT(0u, web_state_handlers_.count(web_state)); |
| web_state->RemoveObserver( |
| (AccountConsistencyHandler*)web_state_handlers_[web_state].get()); |
| web_state_handlers_.erase(web_state); |
| } |
| |
| void AccountConsistencyService::AddCookieRestoreCallback( |
| base::OnceClosure cookies_restored_callback) { |
| gaia_cookies_restored_callbacks_.push_back( |
| std::move(cookies_restored_callback)); |
| } |
| |
| void AccountConsistencyService::SetGaiaCookiesIfDeleted( |
| base::OnceClosure cookies_restored_callback) { |
| // We currently enforce a time threshold to update the Gaia cookie |
| // for signed-in users to prevent calling the expensive method |
| // |GetAllCookies| in the cookie manager. |
| if (base::Time::Now() - last_gaia_cookie_verification_time_ < |
| GetDelayThresholdToUpdateGaiaCookie() || |
| !identity_manager_->HasPrimaryAccount()) { |
| return; |
| } |
| network::mojom::CookieManager* cookie_manager = |
| browser_state_->GetCookieManager(); |
| cookie_manager->GetCookieList( |
| GaiaUrls::GetInstance()->secure_google_url(), |
| net::CookieOptions::MakeAllInclusive(), |
| base::BindOnce( |
| &AccountConsistencyService::TriggerGaiaCookieChangeIfDeleted, |
| base::Unretained(this), std::move(cookies_restored_callback))); |
| last_gaia_cookie_verification_time_ = base::Time::Now(); |
| } |
| |
| void AccountConsistencyService::TriggerGaiaCookieChangeIfDeleted( |
| base::OnceClosure cookies_restored_callback, |
| const net::CookieAccessResultList& cookie_list, |
| const net::CookieAccessResultList& unused_excluded_cookies) { |
| for (const auto& cookie : cookie_list) { |
| if (cookie.cookie.Name() == kGaiaCookieName) { |
| LogIOSGaiaCookiesState( |
| GaiaCookieStateOnSignedInNavigation::kGaiaCookiePresentOnNavigation); |
| return; |
| } |
| } |
| |
| // The SAPISID cookie may have been deleted previous to this update due to |
| // ITP restrictions marking Google domains as potential trackers. |
| LogIOSGaiaCookiesState( |
| GaiaCookieStateOnSignedInNavigation:: |
| kGaiaCookieAbsentOnGoogleAssociatedDomainNavigation); |
| |
| if (!base::FeatureList::IsEnabled(signin::kRestoreGaiaCookiesIfDeleted)) { |
| return; |
| } |
| |
| // Re-generate cookie to ensure that the user is properly signed in. |
| identity_manager_->GetAccountsCookieMutator()->ForceTriggerOnCookieChange(); |
| AddCookieRestoreCallback(std::move(cookies_restored_callback)); |
| } |
| |
| void AccountConsistencyService::RemoveAllChromeConnectedCookies( |
| base::OnceClosure callback) { |
| DCHECK(!browser_state_->IsOffTheRecord()); |
| if (last_cookie_update_map_.empty()) { |
| if (!callback.is_null()) |
| std::move(callback).Run(); |
| return; |
| } |
| |
| network::mojom::CookieManager* cookie_manager = |
| browser_state_->GetCookieManager(); |
| |
| network::mojom::CookieDeletionFilterPtr filter = |
| network::mojom::CookieDeletionFilter::New(); |
| filter->cookie_name = kChromeConnectedCookieName; |
| |
| ++active_cookie_manager_requests_for_testing_; |
| cookie_manager->DeleteCookies( |
| std::move(filter), |
| base::BindOnce(&AccountConsistencyService::OnDeleteCookiesFinished, |
| base::Unretained(this), std::move(callback))); |
| ResetInternalState(); |
| } |
| |
| void AccountConsistencyService::OnDeleteCookiesFinished( |
| base::OnceClosure callback, |
| uint32_t unused_num_cookies_deleted) { |
| --active_cookie_manager_requests_for_testing_; |
| if (!callback.is_null()) { |
| std::move(callback).Run(); |
| } |
| } |
| |
| void AccountConsistencyService::SetChromeConnectedCookieWithUrls( |
| const std::vector<const GURL>& urls) { |
| SetChromeConnectedCookieWithUrls( |
| urls, kDelayThresholdToUpdateChromeConnectedCookie); |
| } |
| |
| void AccountConsistencyService::SetChromeConnectedCookieWithUrls( |
| const std::vector<const GURL>& urls, |
| const base::TimeDelta& cookie_refresh_interval) { |
| for (const GURL& url : urls) { |
| const std::string domain = GetDomainFromUrl(url); |
| if (!ShouldSetChromeConnectedCookieToDomain(domain, |
| cookie_refresh_interval)) { |
| continue; |
| } |
| last_cookie_update_map_[domain] = base::Time::Now(); |
| SetChromeConnectedCookieWithUrl(url); |
| } |
| } |
| |
| bool AccountConsistencyService::ShouldSetChromeConnectedCookieToDomain( |
| const std::string& domain, |
| const base::TimeDelta& cookie_refresh_interval) { |
| auto domain_iterator = last_cookie_update_map_.find(domain); |
| bool domain_not_found = domain_iterator == last_cookie_update_map_.end(); |
| return domain_not_found || ((base::Time::Now() - domain_iterator->second) > |
| cookie_refresh_interval); |
| } |
| |
| void AccountConsistencyService::LoadFromPrefs() { |
| const base::DictionaryValue* dict = |
| prefs_->GetDictionary(kDomainsWithCookiePref); |
| for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { |
| last_cookie_update_map_[it.key()] = base::Time(); |
| } |
| } |
| |
| void AccountConsistencyService::Shutdown() { |
| identity_manager_->RemoveObserver(this); |
| web_state_handlers_.clear(); |
| } |
| |
| void AccountConsistencyService::SetChromeConnectedCookieWithUrl( |
| const GURL& url) { |
| const std::string domain = GetDomainFromUrl(url); |
| std::string cookie_value = signin::BuildMirrorRequestCookieIfPossible( |
| url, identity_manager_->GetPrimaryAccountInfo().gaia, |
| signin::AccountConsistencyMethod::kMirror, cookie_settings_.get(), |
| signin::PROFILE_MODE_DEFAULT); |
| if (cookie_value.empty()) { |
| last_cookie_update_map_.erase(domain); |
| return; |
| } |
| |
| std::unique_ptr<net::CanonicalCookie> cookie = |
| net::CanonicalCookie::CreateSanitizedCookie( |
| url, |
| /*name=*/kChromeConnectedCookieName, cookie_value, |
| /*domain=*/domain, |
| /*path=*/std::string(), |
| /*creation_time=*/base::Time::Now(), |
| // Create expiration date of Now+2y to roughly follow the SAPISID |
| // cookie. |
| /*expiration_time=*/base::Time::Now() + |
| base::TimeDelta::FromDays(730), |
| /*last_access_time=*/base::Time(), |
| /*secure=*/true, |
| /*httponly=*/false, net::CookieSameSite::LAX_MODE, |
| net::COOKIE_PRIORITY_DEFAULT, /*same_party=*/false); |
| net::CookieOptions options; |
| options.set_include_httponly(); |
| options.set_same_site_cookie_context( |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive()); |
| |
| ++active_cookie_manager_requests_for_testing_; |
| |
| network::mojom::CookieManager* cookie_manager = |
| browser_state_->GetCookieManager(); |
| cookie_manager->SetCanonicalCookie( |
| *cookie, url, options, |
| base::BindOnce( |
| &AccountConsistencyService::OnChromeConnectedCookieFinished, |
| base::Unretained(this), domain)); |
| } |
| |
| void AccountConsistencyService::OnChromeConnectedCookieFinished( |
| const std::string& domain, |
| net::CookieAccessResult cookie_access_result) { |
| DCHECK(cookie_access_result.status.IsInclude()); |
| DictionaryPrefUpdate update( |
| prefs_, AccountConsistencyService::kDomainsWithCookiePref); |
| // Add request.domain to prefs, use |true| as a dummy value (that is |
| // never used), as the dictionary is used as a set. |
| update->SetKey(domain, base::Value(true)); |
| --active_cookie_manager_requests_for_testing_; |
| } |
| |
| void AccountConsistencyService::AddChromeConnectedCookies() { |
| DCHECK(!browser_state_->IsOffTheRecord()); |
| // These cookie requests are preventive and not a strong signal (unlike |
| // navigation to a domain). Don't force update the old cookies in this case. |
| SetChromeConnectedCookieWithUrls({GURL(kGoogleUrl), GURL(kYoutubeUrl)}, |
| base::TimeDelta::Max()); |
| } |
| |
| void AccountConsistencyService::ResetInternalState() { |
| last_cookie_update_map_.clear(); |
| last_gaia_cookie_verification_time_ = base::Time(); |
| base::DictionaryValue dict; |
| prefs_->Set(kDomainsWithCookiePref, dict); |
| } |
| |
| void AccountConsistencyService::OnBrowsingDataRemoved() { |
| // CHROME_CONNECTED cookies have been removed, update internal state |
| // accordingly. |
| ResetInternalState(); |
| |
| // SAPISID cookie has been removed, notify the GCMS. |
| // TODO(https://crbug.com/930582) : Remove the need to expose this method |
| // or move it to the network::CookieManager. |
| identity_manager_->GetAccountsCookieMutator()->ForceTriggerOnCookieChange(); |
| } |
| |
| void AccountConsistencyService::OnPrimaryAccountSet( |
| const CoreAccountInfo& account_info) { |
| AddChromeConnectedCookies(); |
| } |
| |
| void AccountConsistencyService::OnPrimaryAccountCleared( |
| const CoreAccountInfo& previous_account_info) { |
| RemoveAllChromeConnectedCookies(base::OnceClosure()); |
| } |
| |
| void AccountConsistencyService::OnAccountsInCookieUpdated( |
| const signin::AccountsInCookieJarInfo& accounts_in_cookie_jar_info, |
| const GoogleServiceAuthError& error) { |
| AddChromeConnectedCookies(); |
| |
| // If signed-in accounts have been recently restored through GAIA cookie |
| // restoration then run the relevant callback to finish the update process. |
| if (accounts_in_cookie_jar_info.signed_in_accounts.size() > 0 && |
| !gaia_cookies_restored_callbacks_.empty()) { |
| std::vector<base::OnceClosure> callbacks; |
| std::swap(gaia_cookies_restored_callbacks_, callbacks); |
| for (base::OnceClosure& callback : callbacks) { |
| std::move(callback).Run(); |
| } |
| } |
| } |