| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/tpcd/support/validity_service.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "chrome/browser/content_settings/cookie_settings_factory.h" |
| #include "chrome/browser/content_settings/host_content_settings_map_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tpcd/support/tpcd_support_service.h" |
| #include "chrome/browser/tpcd/support/tpcd_support_service_factory.h" |
| #include "components/content_settings/core/browser/content_settings_type_set.h" |
| #include "components/content_settings/core/browser/cookie_settings.h" |
| #include "components/content_settings/core/browser/host_content_settings_map.h" |
| #include "components/content_settings/core/common/content_settings.h" |
| #include "components/content_settings/core/common/content_settings_types.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/cookie_access_details.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/origin_trials_controller_delegate.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_features.h" |
| #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| #include "net/cookies/site_for_cookies.h" |
| #include "net/cookies/static_cookie_policy.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| |
| namespace tpcd::trial { |
| |
| namespace { |
| |
| using ThirdPartyCookieAllowMechanism = |
| content_settings::CookieSettingsBase::ThirdPartyCookieAllowMechanism; |
| |
| bool g_disabled_for_testing = false; |
| |
| bool IsThirdParty(const GURL& url, const GURL& first_party_url) { |
| return !net::SiteForCookies::FromUrl(first_party_url).IsFirstParty(url); |
| } |
| |
| std::optional<ContentSettingsType> GetTrialContentSettingsType( |
| ThirdPartyCookieAllowMechanism mechanism) { |
| switch (mechanism) { |
| case ThirdPartyCookieAllowMechanism::kAllowBy3PCD: |
| return ContentSettingsType::TPCD_TRIAL; |
| case ThirdPartyCookieAllowMechanism::kAllowByTopLevel3PCD: |
| return ContentSettingsType::TOP_LEVEL_TPCD_TRIAL; |
| default: |
| // The other mechanisms do not map to a |ContentSettingsType| for a |
| // third-party cookie deprecation trial. |
| return std::nullopt; |
| } |
| } |
| |
| } // namespace |
| |
| /* static */ |
| void ValidityService::DisableForTesting() { |
| g_disabled_for_testing = true; |
| } |
| |
| /* static */ |
| void ValidityService::MaybeCreateForWebContents( |
| content::WebContents* web_contents) { |
| auto* tpcd_trial_service = TpcdTrialServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(web_contents->GetBrowserContext())); |
| if (!tpcd_trial_service) { |
| return; |
| } |
| |
| ValidityService::CreateForWebContents(web_contents); |
| } |
| |
| ValidityService::ValidityService(content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents), |
| content::WebContentsUserData<ValidityService>(*web_contents) {} |
| |
| ValidityService::~ValidityService() = default; |
| |
| void ValidityService::UpdateTrialSettings( |
| const ContentSettingsType trial_settings_type, |
| const GURL& url, |
| const GURL& first_party_url, |
| bool enabled) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| if (g_disabled_for_testing || enabled) { |
| return; |
| } |
| |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile( |
| web_contents()->GetBrowserContext()); |
| CHECK(settings_map); |
| |
| // Find the setting that permitted the cookie access for the pair. |
| content_settings::SettingInfo info; |
| bool setting_exists = CheckTrialContentSetting(url, first_party_url, |
| trial_settings_type, &info); |
| |
| // If a matching setting no longer exists, there is no need to update |
| // |settings_map|. |
| if (!setting_exists) { |
| return; |
| } |
| |
| // Because the same token is used to enable the trial for the request origin |
| // under all top-level origins, only the primary_pattern is checked here. This |
| // means all settings created with the same token as the setting represented |
| // by |info| should be deleted. |
| auto matches = [&](const ContentSettingPatternSource& setting) -> bool { |
| return (setting.primary_pattern == info.primary_pattern); |
| }; |
| settings_map->ClearSettingsForOneTypeWithPredicate(trial_settings_type, |
| matches); |
| |
| SyncTrialSettingsToNetworkService( |
| trial_settings_type, |
| settings_map->GetSettingsForOneType(trial_settings_type)); |
| } |
| |
| void ValidityService::SyncTrialSettingsToNetworkService( |
| const ContentSettingsType trial_settings_type, |
| const ContentSettingsForOneType& trial_settings) { |
| web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition() |
| ->GetCookieManagerForBrowserProcess() |
| ->SetContentSettings(trial_settings_type, std::move(trial_settings), |
| base::NullCallback()); |
| } |
| |
| void ValidityService::OnCookiesAccessed( |
| content::RenderFrameHost* render_frame_host, |
| const content::CookieAccessDetails& details) { |
| OnCookiesAccessedImpl(details); |
| } |
| |
| void ValidityService::OnCookiesAccessed( |
| content::NavigationHandle* navigation_handle, |
| const content::CookieAccessDetails& details) { |
| OnCookiesAccessedImpl(details); |
| } |
| |
| void ValidityService::OnCookiesAccessedImpl( |
| const content::CookieAccessDetails& details) { |
| if (details.blocked_by_policy || |
| !IsThirdParty(details.url, details.first_party_url)) { |
| return; |
| } |
| |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| |
| // If third-party cookies are allowed globally, there's no reason to continue |
| // with performing checks. |
| if (!CookieSettingsFactory::GetForProfile(profile) |
| ->ShouldBlockThirdPartyCookies()) { |
| return; |
| } |
| |
| scoped_refptr<content_settings::CookieSettings> cookie_settings = |
| CookieSettingsFactory::GetForProfile(profile); |
| CHECK(cookie_settings); |
| |
| // Check for an existing trial setting applicable to the pair. |
| ThirdPartyCookieAllowMechanism allow_mechanism = |
| cookie_settings->GetThirdPartyCookieAllowMechanism( |
| details.url, net::SiteForCookies::FromUrl(details.first_party_url), |
| details.first_party_url, details.cookie_setting_overrides); |
| std::optional<ContentSettingsType> setting_type = |
| GetTrialContentSettingsType(allow_mechanism); |
| |
| if (setting_type.has_value()) { |
| CheckTrialStatusAsync( |
| base::BindOnce(&ValidityService::UpdateTrialSettings, |
| weak_factory_.GetWeakPtr(), setting_type.value()), |
| setting_type.value(), details.url, details.first_party_url); |
| } |
| } |
| |
| void ValidityService::CheckTrialStatusAsync( |
| ContentSettingUpdateCallback update_callback, |
| const ContentSettingsType trial_settings_type, |
| const GURL& url, |
| const GURL& first_party_url) { |
| content::GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(&ValidityService::CheckTrialStatusOnUiThread, |
| weak_factory_.GetWeakPtr(), |
| std::move(update_callback), trial_settings_type, |
| std::move(url), std::move(first_party_url))); |
| } |
| |
| // Persistent origin trials can only be checked on the UI thread. |
| void ValidityService::CheckTrialStatusOnUiThread( |
| ContentSettingUpdateCallback update_callback, |
| const ContentSettingsType trial_settings_type, |
| const GURL& url, |
| const GURL& first_party_url) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| content::OriginTrialsControllerDelegate* trial_delegate = |
| WebContentsObserver::web_contents() |
| ->GetBrowserContext() |
| ->GetOriginTrialsControllerDelegate(); |
| if (!trial_delegate) { |
| return; |
| } |
| |
| bool enabled = false; |
| url::Origin partition_origin = url::Origin::Create(first_party_url); |
| |
| switch (trial_settings_type) { |
| case ContentSettingsType::TPCD_TRIAL: |
| enabled = trial_delegate->IsFeaturePersistedForOrigin( |
| url::Origin::Create(url), partition_origin, |
| blink::mojom::OriginTrialFeature::kTpcd, base::Time::Now()); |
| break; |
| case ContentSettingsType::TOP_LEVEL_TPCD_TRIAL: |
| enabled = trial_delegate->IsFeaturePersistedForOrigin( |
| url::Origin::Create(first_party_url), partition_origin, |
| blink::mojom::OriginTrialFeature::kTopLevelTpcd, base::Time::Now()); |
| |
| break; |
| default: |
| NOTREACHED() << "ContentSettingsType::" << trial_settings_type |
| << " is not associated with a 3PCD trial."; |
| } |
| |
| std::move(update_callback) |
| .Run(std::move(url), std::move(first_party_url), enabled); |
| } |
| |
| bool ValidityService::CheckTrialContentSetting( |
| const GURL& url, |
| const GURL& first_party_url, |
| ContentSettingsType trial_settings_type, |
| content_settings::SettingInfo* info) { |
| HostContentSettingsMap* settings_map = |
| HostContentSettingsMapFactory::GetForProfile( |
| web_contents()->GetBrowserContext()); |
| CHECK(settings_map); |
| |
| switch (trial_settings_type) { |
| case ContentSettingsType::TPCD_TRIAL: |
| return (settings_map->GetContentSetting(url, first_party_url, |
| trial_settings_type, |
| info) == CONTENT_SETTING_ALLOW); |
| case ContentSettingsType::TOP_LEVEL_TPCD_TRIAL: |
| // Top-level 3pcd trial settings use |
| // |WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE| by default and as a result |
| // only use a primary pattern (with wildcard placeholder for the secondary |
| // pattern). |
| return (settings_map->GetContentSetting(first_party_url, first_party_url, |
| trial_settings_type, |
| info) == CONTENT_SETTING_ALLOW); |
| default: |
| NOTREACHED() << "ContentSettingsType::" << trial_settings_type |
| << " is not associated with a 3PCD trial."; |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(ValidityService); |
| |
| } // namespace tpcd::trial |