From 57158144d8f8f205b91c904cad47d58a8e91625e Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Fri, 26 Apr 2024 13:09:58 -0700 Subject: [PATCH 1/9] Bootstrap Web Discovery Native, add credential manager and server config loading --- android/BUILD.gn | 2 + .../org/chromium/base/BraveFeatureList.java | 1 + .../settings/BravePrivacySettings.java | 24 + .../res/xml/brave_privacy_preferences.xml | 6 + browser/android/preferences/BUILD.gn | 1 + browser/brave_local_state_prefs.cc | 8 + browser/brave_profile_prefs.cc | 12 +- browser/brave_tab_helpers.cc | 1 - ...browser_context_keyed_service_factories.cc | 9 + .../api/settings_private/brave_prefs_util.cc | 14 +- browser/profiles/brave_profile_manager.cc | 9 + browser/resources/settings/BUILD.gn | 2 + .../brave_search_engines_page.html | 27 +- .../brave_search_engines_page.ts | 4 + .../search_engines/search_engine_tracker.cc | 41 +- .../search_engines/search_engine_tracker.h | 5 +- .../search_engine_tracker_browsertest.cc | 4 +- browser/sources.gni | 10 + browser/ui/BUILD.gn | 8 + .../android/strings/android_brave_strings.grd | 6 + browser/ui/webui/brave_settings_ui.cc | 7 + .../webui/welcome_page/welcome_dom_handler.cc | 2 +- browser/web_discovery/BUILD.gn | 30 ++ .../web_discovery/web_discovery_cta_util.cc | 17 +- .../web_discovery_infobar_delegate.cc | 15 +- .../web_discovery_service_factory.cc | 63 +++ .../web_discovery_service_factory.h | 41 ++ .../web_discovery_service_factory_unittest.cc | 32 ++ .../web_discovery/web_discovery_unittest.cc | 6 +- .../java/templates/BraveConfig.template | 1 + chromium_src/chrome/browser/DEPS | 1 + .../flags/android/chrome_feature_list.cc | 4 +- chromium_src/chrome/renderer/DEPS | 2 + .../chrome_content_renderer_client.cc | 1 + components/constants/pref_names.h | 3 +- components/web_discovery/browser/BUILD.gn | 59 ++- components/web_discovery/browser/DEPS | 7 + .../browser/credential_manager.cc | 463 ++++++++++++++++++ .../browser/credential_manager.h | 132 +++++ .../browser/credential_manager_unittest.cc | 196 ++++++++ .../browser/credential_signer.cc | 12 + .../web_discovery/browser/credential_signer.h | 39 ++ components/web_discovery/browser/patterns.cc | 321 ++++++++++++ components/web_discovery/browser/patterns.h | 174 +++++++ .../browser/patterns_unittest.cc | 345 +++++++++++++ components/web_discovery/browser/pref_names.h | 32 ++ components/web_discovery/browser/rsa.cc | 83 ++++ components/web_discovery/browser/rsa.h | 36 ++ .../browser/server_config_loader.cc | 446 +++++++++++++++++ .../browser/server_config_loader.h | 143 ++++++ .../browser/server_config_loader_unittest.cc | 281 +++++++++++ components/web_discovery/browser/util.cc | 85 ++++ components/web_discovery/browser/util.h | 64 +++ .../browser/web_discovery_service.cc | 117 +++++ .../browser/web_discovery_service.h | 78 +++ components/web_discovery/common/BUILD.gn | 18 + .../web_discovery/common/buildflags/BUILD.gn | 12 + .../common/buildflags/buildflags.gni | 8 + components/web_discovery/common/features.cc | 14 + components/web_discovery/common/features.h | 19 + script/brave_license_helper.py | 1 + test/BUILD.gn | 5 + .../credential_keys_and_responses.json | 10 + test/data/web_discovery/hpn-config.json | 146 ++++++ test/data/web_discovery/page.html | 21 + test/data/web_discovery/patterns.gz | Bin 0 -> 3762 bytes test/data/web_discovery/patterns.json | 200 ++++++++ test/data/web_discovery/quorum-config.json | 1 + 68 files changed, 3954 insertions(+), 33 deletions(-) create mode 100644 browser/web_discovery/web_discovery_service_factory.cc create mode 100644 browser/web_discovery/web_discovery_service_factory.h create mode 100644 browser/web_discovery/web_discovery_service_factory_unittest.cc create mode 100644 components/web_discovery/browser/DEPS create mode 100644 components/web_discovery/browser/credential_manager.cc create mode 100644 components/web_discovery/browser/credential_manager.h create mode 100644 components/web_discovery/browser/credential_manager_unittest.cc create mode 100644 components/web_discovery/browser/credential_signer.cc create mode 100644 components/web_discovery/browser/credential_signer.h create mode 100644 components/web_discovery/browser/patterns.cc create mode 100644 components/web_discovery/browser/patterns.h create mode 100644 components/web_discovery/browser/patterns_unittest.cc create mode 100644 components/web_discovery/browser/pref_names.h create mode 100644 components/web_discovery/browser/rsa.cc create mode 100644 components/web_discovery/browser/rsa.h create mode 100644 components/web_discovery/browser/server_config_loader.cc create mode 100644 components/web_discovery/browser/server_config_loader.h create mode 100644 components/web_discovery/browser/server_config_loader_unittest.cc create mode 100644 components/web_discovery/browser/util.cc create mode 100644 components/web_discovery/browser/util.h create mode 100644 components/web_discovery/browser/web_discovery_service.cc create mode 100644 components/web_discovery/browser/web_discovery_service.h create mode 100644 components/web_discovery/common/BUILD.gn create mode 100644 components/web_discovery/common/buildflags/BUILD.gn create mode 100644 components/web_discovery/common/buildflags/buildflags.gni create mode 100644 components/web_discovery/common/features.cc create mode 100644 components/web_discovery/common/features.h create mode 100644 test/data/web_discovery/credential_keys_and_responses.json create mode 100644 test/data/web_discovery/hpn-config.json create mode 100644 test/data/web_discovery/page.html create mode 100644 test/data/web_discovery/patterns.gz create mode 100644 test/data/web_discovery/patterns.json create mode 100644 test/data/web_discovery/quorum-config.json diff --git a/android/BUILD.gn b/android/BUILD.gn index 117250e02569..8fcd7824dfdb 100644 --- a/android/BUILD.gn +++ b/android/BUILD.gn @@ -5,6 +5,7 @@ import("//brave/components/ai_chat/core/common/buildflags/buildflags.gni") import("//brave/components/p3a/buildflags.gni") +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//brave/components/webcompat_reporter/buildflags/buildflags.gni") import("//build/config/android/rules.gni") @@ -26,5 +27,6 @@ java_cpp_template("brave_config_java") { "BRAVE_ANDROID_P3A_ENABLED=$brave_p3a_enabled", "BRAVE_ANDROID_WEBCOMPAT_REPORT_ENDPOINT=\"$webcompat_report_api_endpoint\"", "BRAVE_ANDROID_AI_CHAT_ENABLED=$enable_ai_chat", + "BRAVE_ANDROID_WEB_DISCOVERY_ENABLED=$enable_web_discovery_native", ] } diff --git a/android/java/org/chromium/base/BraveFeatureList.java b/android/java/org/chromium/base/BraveFeatureList.java index bc876d07ae92..1f3976658213 100644 --- a/android/java/org/chromium/base/BraveFeatureList.java +++ b/android/java/org/chromium/base/BraveFeatureList.java @@ -34,4 +34,5 @@ public abstract class BraveFeatureList { "BraveShowStrictFingerprintingMode"; public static final String BRAVE_DAY_ZERO_EXPERIMENT = "BraveDayZeroExperiment"; public static final String BRAVE_FALLBACK_DOH_PROVIDER = "BraveFallbackDoHProvider"; + public static final String BRAVE_WEB_DISCOVERY_NATIVE = "BraveWebDiscoveryNative"; } diff --git a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java index 322a5e7c0007..5cd65887f143 100644 --- a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java +++ b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java @@ -84,6 +84,7 @@ public class BravePrivacySettings extends PrivacySettings implements ConnectionE public static final String PREF_FINGERPRINTING_PROTECTION2 = "fingerprinting_protection2"; private static final String PREF_CLOSE_TABS_ON_EXIT = "close_tabs_on_exit"; private static final String PREF_SEND_P3A = "send_p3a_analytics"; + private static final String PREF_SEND_WEB_DISCOVERY = "send_web_discovery"; private static final String PREF_SEND_CRASH_REPORTS = "send_crash_reports"; private static final String PREF_BRAVE_STATS_USAGE_PING = "brave_stats_usage_ping"; public static final String PREF_APP_LINKS = "app_links"; @@ -149,6 +150,7 @@ public class BravePrivacySettings extends PrivacySettings implements ConnectionE PREF_PHONE_AS_A_SECURITY_KEY, PREF_CLOSE_TABS_ON_EXIT, PREF_SEND_P3A, + PREF_SEND_WEB_DISCOVERY, PREF_SEND_CRASH_REPORTS, PREF_BRAVE_STATS_USAGE_PING, PREF_USAGE_STATS, @@ -177,6 +179,7 @@ public class BravePrivacySettings extends PrivacySettings implements ConnectionE private ChromeSwitchPreference mForgetFirstPartyStoragePref; private ChromeSwitchPreference mCloseTabsOnExitPref; private ChromeSwitchPreference mSendP3A; + private ChromeSwitchPreference mSendWebDiscovery; private ChromeSwitchPreference mSendCrashReports; private ChromeSwitchPreference mBraveStatsUsagePing; private ChromeSwitchPreference mBlockCookieConsentNoticesPref; @@ -308,6 +311,14 @@ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { mSendP3A = (ChromeSwitchPreference) findPreference(PREF_SEND_P3A); mSendP3A.setOnPreferenceChangeListener(this); + if (BraveConfig.WEB_DISCOVERY_ENABLED + && ChromeFeatureList.isEnabled(BraveFeatureList.BRAVE_WEB_DISCOVERY_NATIVE)) { + mSendWebDiscovery = (ChromeSwitchPreference) findPreference(PREF_SEND_WEB_DISCOVERY); + mSendWebDiscovery.setOnPreferenceChangeListener(this); + } else { + removePreferenceIfPresent(PREF_SEND_WEB_DISCOVERY); + } + mSendCrashReports = (ChromeSwitchPreference) findPreference(PREF_SEND_CRASH_REPORTS); mSendCrashReports.setOnPreferenceChangeListener(this); mBraveStatsUsagePing = (ChromeSwitchPreference) findPreference(PREF_BRAVE_STATS_USAGE_PING); @@ -522,6 +533,9 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } else if (PREF_SEND_P3A.equals(key)) { BraveLocalState.get().setBoolean(BravePref.P3A_ENABLED, (boolean) newValue); BraveLocalState.commitPendingWrite(); + } else if (PREF_SEND_WEB_DISCOVERY.equals(key)) { + UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) + .setBoolean(BravePref.WEB_DISCOVERY_NATIVE_ENABLED, (boolean) newValue); } else if (PREF_SEND_CRASH_REPORTS.equals(key)) { UmaSessionStats.changeMetricsReportingConsent( (boolean) newValue, ChangeMetricsReportingStateCalledFrom.UI_SETTINGS); @@ -682,6 +696,16 @@ private void updateBravePreferences() { getPreferenceScreen().removePreference(mSendP3A); } + if (mSendWebDiscovery != null) { + mSendWebDiscovery.setTitle( + getActivity().getResources().getString(R.string.send_web_discovery_title)); + mSendWebDiscovery.setSummary( + getActivity().getResources().getString(R.string.send_web_discovery_summary)); + mSendWebDiscovery.setChecked( + UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) + .getBoolean(BravePref.WEB_DISCOVERY_NATIVE_ENABLED)); + } + mSendCrashReports.setChecked(mPrivacyPrefManager.isUsageAndCrashReportingPermittedByUser()); mBraveStatsUsagePing.setChecked( diff --git a/android/java/res/xml/brave_privacy_preferences.xml b/android/java/res/xml/brave_privacy_preferences.xml index bcbb84f3883c..6a7c4ebc9bfe 100644 --- a/android/java/res/xml/brave_privacy_preferences.xml +++ b/android/java/res/xml/brave_privacy_preferences.xml @@ -167,6 +167,12 @@ android:title="@string/send_p3a_analytics_title" android:summary="@string/send_p3a_analytics_summary" android:defaultValue="false" /> + RegisterBooleanPref(kWebDiscoveryEnabled, false); + registry->RegisterBooleanPref(kWebDiscoveryExtensionEnabled, false); registry->RegisterDictionaryPref(kWebDiscoveryCTAState); #endif @@ -497,6 +501,10 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { registry->SetDefaultPrefValue(prefs::kSearchSuggestEnabled, base::Value(false)); + +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + web_discovery::WebDiscoveryService::RegisterProfilePrefs(registry); +#endif } } // namespace brave diff --git a/browser/brave_tab_helpers.cc b/browser/brave_tab_helpers.cc index face0640def6..924fa3cd0c85 100644 --- a/browser/brave_tab_helpers.cc +++ b/browser/brave_tab_helpers.cc @@ -21,7 +21,6 @@ #include "brave/browser/misc_metrics/process_misc_metrics.h" #include "brave/browser/ntp_background/ntp_tab_helper.h" #include "brave/browser/ui/bookmark/brave_bookmark_tab_helper.h" -#include "brave/browser/ui/brave_ui_features.h" #include "brave/components/ai_chat/core/common/buildflags/buildflags.h" #include "brave/components/brave_perf_predictor/browser/perf_predictor_tab_helper.h" #include "brave/components/brave_wayback_machine/buildflags/buildflags.h" diff --git a/browser/browser_context_keyed_service_factories.cc b/browser/browser_context_keyed_service_factories.cc index 639d6f2d9f18..dd05c70346b2 100644 --- a/browser/browser_context_keyed_service_factories.cc +++ b/browser/browser_context_keyed_service_factories.cc @@ -42,6 +42,7 @@ #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" @@ -102,6 +103,10 @@ #include "brave/components/ai_chat/content/browser/model_service_factory.h" #endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/browser/web_discovery/web_discovery_service_factory.h" +#endif + namespace brave { void EnsureBrowserContextKeyedServiceFactoriesBuilt() { @@ -200,6 +205,10 @@ void EnsureBrowserContextKeyedServiceFactoriesBuilt() { ai_chat::AIChatServiceFactory::GetInstance(); ai_chat::ModelServiceFactory::GetInstance(); #endif + +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + web_discovery::WebDiscoveryServiceFactory::GetInstance(); +#endif } } // namespace brave diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index 2be2205416ec..a5cebc019e78 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -23,6 +23,9 @@ #include "brave/components/request_otr/common/pref_names.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "chrome/browser/content_settings/cookie_settings_factory.h" +#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/extensions/api/settings_private/prefs_util.h" #include "chrome/common/extensions/api/settings_private.h" #include "chrome/common/pref_names.h" @@ -60,6 +63,10 @@ #include "brave/components/playlist/browser/pref_names.h" #endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/browser/pref_names.h" +#endif + namespace extensions { using ntp_background_images::prefs::kNewTabPageShowBackgroundImage; @@ -184,7 +191,12 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::kNumber; #if BUILDFLAG(ENABLE_EXTENSIONS) // Web discovery prefs - (*s_brave_allowlist)[kWebDiscoveryEnabled] = settings_api::PrefType::kBoolean; + (*s_brave_allowlist)[kWebDiscoveryExtensionEnabled] = + settings_api::PrefType::kBoolean; +#endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + (*s_brave_allowlist)[web_discovery::kWebDiscoveryNativeEnabled] = + settings_api::PrefType::kBoolean; #endif // Clear browsing data on exit prefs. (*s_brave_allowlist)[browsing_data::prefs::kDeleteBrowsingHistoryOnExit] = diff --git a/browser/profiles/brave_profile_manager.cc b/browser/profiles/brave_profile_manager.cc index 9077bd1308ae..c545038c542a 100644 --- a/browser/profiles/brave_profile_manager.cc +++ b/browser/profiles/brave_profile_manager.cc @@ -26,6 +26,7 @@ #include "brave/components/ntp_background_images/common/pref_names.h" #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile_attributes_entry.h" @@ -46,6 +47,10 @@ #include "brave/components/tor/tor_constants.h" #endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/browser/web_discovery_service.h" +#endif + using brave_shields::ControlType; using content::BrowserThread; using ntp_background_images::prefs::kNewTabPageShowBackgroundImage; @@ -138,6 +143,10 @@ void BraveProfileManager::InitProfileUserPrefs(Profile* profile) { brave::SetDefaultThirdPartyCookieBlockValue(profile); perf::MaybeEnableBraveFeatureForPerfTesting(profile); MigrateHttpsUpgradeSettings(profile); +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + web_discovery::WebDiscoveryService::SetExtensionPrefIfNativeDisabled( + profile->GetPrefs()); +#endif } void BraveProfileManager::DoFinalInitForServices(Profile* profile, diff --git a/browser/resources/settings/BUILD.gn b/browser/resources/settings/BUILD.gn index 67bbc5e76d88..2905c82c9241 100644 --- a/browser/resources/settings/BUILD.gn +++ b/browser/resources/settings/BUILD.gn @@ -8,6 +8,7 @@ import("//brave/build/config.gni") import("//brave/components/brave_vpn/common/buildflags/buildflags.gni") import("//brave/components/brave_wayback_machine/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//brave/resources/brave_grit.gni") import("//chrome/common/features.gni") import("//extensions/buildflags/buildflags.gni") @@ -75,6 +76,7 @@ preprocess_if_expr("preprocess") { "enable_brave_vpn_wireguard=$enable_brave_vpn_wireguard", "enable_extensions=$enable_extensions", "enable_pin_shortcut=$enable_pin_shortcut", + "enable_web_discovery_native=$enable_web_discovery_native", ] out_folder = "$root_gen_dir/chrome/browser/resources/settings/$preprocess_folder" diff --git a/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html b/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html index 432f2f467d14..7cb5c2619c64 100644 --- a/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html +++ b/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html @@ -18,13 +18,26 @@ - - + + + + GetBoolean(kWebDiscoveryEnabled)); - UMA_HISTOGRAM_BOOLEAN(kWebDiscoveryAndAdsMetric, - profile_prefs_->GetBoolean(kWebDiscoveryEnabled) && - profile_prefs_->GetBoolean( - brave_ads::prefs::kOptedInToNotificationAds)); + bool enabled = false; +#if BUILDFLAG(ENABLE_EXTENSIONS) + enabled = profile_prefs_->GetBoolean(kWebDiscoveryExtensionEnabled); +#endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + if (base::FeatureList::IsEnabled( + web_discovery::features::kWebDiscoveryNative)) { + enabled = + profile_prefs_->GetBoolean(web_discovery::kWebDiscoveryNativeEnabled); + } +#endif + UMA_HISTOGRAM_BOOLEAN(kWebDiscoveryEnabledMetric, enabled); + UMA_HISTOGRAM_BOOLEAN( + kWebDiscoveryAndAdsMetric, + enabled && profile_prefs_->GetBoolean( + brave_ads::prefs::kOptedInToNotificationAds)); } #endif diff --git a/browser/search_engines/search_engine_tracker.h b/browser/search_engines/search_engine_tracker.h index 0d5243467549..25cc168e4572 100644 --- a/browser/search_engines/search_engine_tracker.h +++ b/browser/search_engines/search_engine_tracker.h @@ -11,6 +11,7 @@ #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "brave/components/time_period_storage/weekly_event_storage.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" #include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "components/keyed_service/core/keyed_service.h" #include "components/prefs/pref_change_registrar.h" @@ -112,7 +113,7 @@ class SearchEngineTracker : public KeyedService, // TemplateURLServiceObserver overrides: void OnTemplateURLServiceChanged() override; -#if BUILDFLAG(ENABLE_EXTENSIONS) +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) void RecordWebDiscoveryEnabledP3A(); #endif @@ -132,7 +133,7 @@ class SearchEngineTracker : public KeyedService, raw_ptr template_url_service_ = nullptr; -#if BUILDFLAG(ENABLE_EXTENSIONS) +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) PrefChangeRegistrar pref_change_registrar_; #endif }; diff --git a/browser/search_engines/search_engine_tracker_browsertest.cc b/browser/search_engines/search_engine_tracker_browsertest.cc index 2652d018472a..714aa69c61cf 100644 --- a/browser/search_engines/search_engine_tracker_browsertest.cc +++ b/browser/search_engines/search_engine_tracker_browsertest.cc @@ -163,7 +163,7 @@ IN_PROC_BROWSER_TEST_F(SearchEngineProviderP3ATest, WebDiscoveryEnabledP3A) { histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 0, 1); PrefService* prefs = browser()->profile()->GetPrefs(); - prefs->SetBoolean(kWebDiscoveryEnabled, true); + prefs->SetBoolean(kWebDiscoveryExtensionEnabled, true); histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 1, 1); @@ -171,7 +171,7 @@ IN_PROC_BROWSER_TEST_F(SearchEngineProviderP3ATest, WebDiscoveryEnabledP3A) { prefs->SetBoolean(brave_ads::prefs::kOptedInToNotificationAds, true); histogram_tester_->ExpectBucketCount(kWebDiscoveryAndAdsMetric, 1, 1); - prefs->SetBoolean(kWebDiscoveryEnabled, false); + prefs->SetBoolean(kWebDiscoveryExtensionEnabled, false); histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 0, 2); histogram_tester_->ExpectBucketCount(kWebDiscoveryAndAdsMetric, 0, 3); diff --git a/browser/sources.gni b/browser/sources.gni index 7eb5d58e8855..ee58cb8034aa 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -52,6 +52,7 @@ import("//brave/components/brave_webtorrent/browser/buildflags/buildflags.gni") import("//brave/components/commander/common/buildflags/buildflags.gni") import("//brave/components/greaselion/browser/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//extensions/buildflags/buildflags.gni") brave_chrome_browser_visibility = [ @@ -214,6 +215,8 @@ brave_chrome_browser_deps = [ "//brave/components/speedreader/common/buildflags", "//brave/components/tor/buildflags", "//brave/components/version_info", + "//brave/components/web_discovery/common", + "//brave/components/web_discovery/common/buildflags", "//brave/components/webcompat/content/browser", "//brave/components/webcompat/core/common", "//brave/services/network/public/cpp", @@ -358,6 +361,13 @@ if (enable_ai_rewriter) { brave_chrome_browser_deps += [ "//brave/components/ai_rewriter/common" ] } +if (enable_web_discovery_native) { + brave_chrome_browser_deps += [ + "//brave/browser/web_discovery", + "//brave/components/web_discovery/browser", + ] +} + if (enable_brave_vpn) { brave_chrome_browser_deps += [ "//brave/components/brave_vpn/browser", diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 4530823438f3..36790fea8178 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -17,6 +17,7 @@ import("//brave/components/request_otr/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") import("//brave/components/text_recognition/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//build/config/features.gni") import("//chrome/common/features.gni") import("//components/gcm_driver/config.gni") @@ -901,6 +902,13 @@ source_set("ui") { } } + if (enable_web_discovery_native) { + deps += [ + "//brave/components/web_discovery/common", + "//brave/components/web_discovery/common/buildflags", + ] + } + if (is_linux) { sources += [ "views/brave_views_delegate_linux.cc", diff --git a/browser/ui/android/strings/android_brave_strings.grd b/browser/ui/android/strings/android_brave_strings.grd index 0edd57ccd7aa..700dd7642d48 100644 --- a/browser/ui/android/strings/android_brave_strings.grd +++ b/browser/ui/android/strings/android_brave_strings.grd @@ -758,6 +758,12 @@ This file contains all "about" strings. It is set to NOT be translated, in tran Anonymized P3A info helps Brave estimate overall usage, and ensure we’re improving popular features. + + Web Discovery Project + + + Contribute some anonymous search & browsing data to refine Brave Search. + Automatically send diagnostic reports diff --git a/browser/ui/webui/brave_settings_ui.cc b/browser/ui/webui/brave_settings_ui.cc index c81298d8861d..88883cbb3718 100644 --- a/browser/ui/webui/brave_settings_ui.cc +++ b/browser/ui/webui/brave_settings_ui.cc @@ -45,6 +45,8 @@ #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "brave/components/version_info/version_info.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/common/features.h" #include "build/build_config.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/settings/metrics_reporting_handler.h" @@ -192,6 +194,11 @@ void BraveSettingsUI::AddResources(content::WebUIDataSource* html_source, ShouldExposeElementsForTesting()); html_source->AddBoolean("enable_extensions", BUILDFLAG(ENABLE_EXTENSIONS)); +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + html_source->AddBoolean("isWebDiscoveryNativeEnabled", + base::FeatureList::IsEnabled( + web_discovery::features::kWebDiscoveryNative)); +#endif html_source->AddBoolean("extensionsManifestV2Feature", base::FeatureList::IsEnabled(kExtensionsManifestV2)); diff --git a/browser/ui/webui/welcome_page/welcome_dom_handler.cc b/browser/ui/webui/welcome_page/welcome_dom_handler.cc index 11f13039ef92..0478aaaaab8a 100644 --- a/browser/ui/webui/welcome_page/welcome_dom_handler.cc +++ b/browser/ui/webui/welcome_page/welcome_dom_handler.cc @@ -170,7 +170,7 @@ void WelcomeDOMHandler::HandleSetMetricsReportingEnabled( void WelcomeDOMHandler::HandleEnableWebDiscovery( const base::Value::List& args) { DCHECK(profile_); - profile_->GetPrefs()->SetBoolean(kWebDiscoveryEnabled, true); + profile_->GetPrefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); } void WelcomeDOMHandler::SetLocalStateBooleanEnabled( diff --git a/browser/web_discovery/BUILD.gn b/browser/web_discovery/BUILD.gn index e14979f3939c..aa506028751e 100644 --- a/browser/web_discovery/BUILD.gn +++ b/browser/web_discovery/BUILD.gn @@ -3,13 +3,35 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//extensions/buildflags/buildflags.gni") +if (enable_web_discovery_native) { + source_set("web_discovery") { + sources = [ + "web_discovery_service_factory.cc", + "web_discovery_service_factory.h", + ] + deps = [ + "//base", + "//brave/components/web_discovery/browser", + "//brave/components/web_discovery/common", + "//chrome/browser:browser_process", + "//chrome/common:constants", + "//components/keyed_service/content", + "//components/user_prefs", + "//content/public/browser", + "//third_party/re2", + ] + } +} + source_set("unit_tests") { if (enable_extensions) { testonly = true sources = [ "web_discovery_unittest.cc" ] + deps = [ "//brave/components/constants", "//brave/components/search_engines", @@ -22,6 +44,14 @@ source_set("unit_tests") { "//content/test:test_support", "//testing/gtest", ] + + if (enable_web_discovery_native) { + sources += [ "web_discovery_service_factory_unittest.cc" ] + deps += [ + ":web_discovery", + "//brave/components/web_discovery/common", + ] + } } } diff --git a/browser/web_discovery/web_discovery_cta_util.cc b/browser/web_discovery/web_discovery_cta_util.cc index 9251daac6c70..0a053023faeb 100644 --- a/browser/web_discovery/web_discovery_cta_util.cc +++ b/browser/web_discovery/web_discovery_cta_util.cc @@ -14,6 +14,7 @@ #include "brave/components/constants/pref_names.h" #include "brave/components/constants/url_constants.h" #include "brave/components/search_engines/brave_prepopulated_engines.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/search_engines/template_url.h" @@ -21,6 +22,11 @@ #include "net/base/url_util.h" #include "url/gurl.h" +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/common/features.h" +#endif + namespace { constexpr int kMaxDisplayCount = 5; @@ -87,8 +93,17 @@ bool ShouldShowWebDiscoveryInfoBar(TemplateURLService* service, PrefService* prefs, const WebDiscoveryCTAState& state, base::Clock* test_clock) { - if (prefs->GetBoolean(kWebDiscoveryEnabled)) + const char* enabled_pref_name = kWebDiscoveryExtensionEnabled; +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + if (base::FeatureList::IsEnabled( + web_discovery::features::kWebDiscoveryNative)) { + enabled_pref_name = web_discovery::kWebDiscoveryNativeEnabled; + } +#endif + + if (prefs->GetBoolean(enabled_pref_name)) { return false; + } if (!service || !IsBraveSearchDefault(service)) return false; diff --git a/browser/web_discovery/web_discovery_infobar_delegate.cc b/browser/web_discovery/web_discovery_infobar_delegate.cc index c9db24eaa35b..b604262e05b1 100644 --- a/browser/web_discovery/web_discovery_infobar_delegate.cc +++ b/browser/web_discovery/web_discovery_infobar_delegate.cc @@ -7,9 +7,15 @@ #include "brave/browser/web_discovery/web_discovery_cta_util.h" #include "brave/components/constants/pref_names.h" +#include "brave/components/web_discovery/common/buildflags/buildflags.h" #include "components/infobars/core/infobar.h" #include "components/prefs/pref_service.h" +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/common/features.h" +#endif + WebDiscoveryInfoBarDelegate::WebDiscoveryInfoBarDelegate(PrefService* prefs) : prefs_(prefs) {} @@ -42,6 +48,13 @@ void WebDiscoveryInfoBarDelegate::Close(bool dismiss) { } void WebDiscoveryInfoBarDelegate::EnableWebDiscovery() { - prefs_->SetBoolean(kWebDiscoveryEnabled, true); + const char* pref_name = kWebDiscoveryExtensionEnabled; +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + if (base::FeatureList::IsEnabled( + web_discovery::features::kWebDiscoveryNative)) { + pref_name = web_discovery::kWebDiscoveryNativeEnabled; + } +#endif + prefs_->SetBoolean(pref_name, true); infobar()->RemoveSelf(); } diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc new file mode 100644 index 000000000000..093226b23b23 --- /dev/null +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -0,0 +1,63 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/web_discovery/web_discovery_service_factory.h" + +#include "base/path_service.h" +#include "brave/components/web_discovery/browser/web_discovery_service.h" +#include "brave/components/web_discovery/common/features.h" +#include "chrome/browser/browser_process.h" +#include "chrome/common/chrome_paths.h" +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/storage_partition.h" + +namespace web_discovery { + +WebDiscoveryService* WebDiscoveryServiceFactory::GetForBrowserContext( + content::BrowserContext* context) { + return static_cast( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +WebDiscoveryServiceFactory* WebDiscoveryServiceFactory::GetInstance() { + static base::NoDestructor instance; + return instance.get(); +} + +WebDiscoveryServiceFactory::WebDiscoveryServiceFactory() + : BrowserContextKeyedServiceFactory( + "WebDiscoveryService", + BrowserContextDependencyManager::GetInstance()) {} + +WebDiscoveryServiceFactory::~WebDiscoveryServiceFactory() = default; + +KeyedService* WebDiscoveryServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + auto* default_storage_partition = context->GetDefaultStoragePartition(); + auto shared_url_loader_factory = + default_storage_partition->GetURLLoaderFactoryForBrowserProcess(); + base::FilePath user_data_dir = + base::PathService::CheckedGet(chrome::DIR_USER_DATA); + return new WebDiscoveryService(g_browser_process->local_state(), + user_prefs::UserPrefs::Get(context), + user_data_dir, shared_url_loader_factory); +} + +content::BrowserContext* WebDiscoveryServiceFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + if (!base::FeatureList::IsEnabled(features::kWebDiscoveryNative)) { + return nullptr; + } + // Prevents creation of service instance for incognito/OTR profiles + return context->IsOffTheRecord() ? nullptr : context; +} + +bool WebDiscoveryServiceFactory::ServiceIsCreatedWithBrowserContext() const { + return true; +} + +} // namespace web_discovery diff --git a/browser/web_discovery/web_discovery_service_factory.h b/browser/web_discovery/web_discovery_service_factory.h new file mode 100644 index 000000000000..48687f69b531 --- /dev/null +++ b/browser/web_discovery/web_discovery_service_factory.h @@ -0,0 +1,41 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_WEB_DISCOVERY_WEB_DISCOVERY_SERVICE_FACTORY_H_ +#define BRAVE_BROWSER_WEB_DISCOVERY_WEB_DISCOVERY_SERVICE_FACTORY_H_ + +#include "base/no_destructor.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace web_discovery { + +class WebDiscoveryService; + +class WebDiscoveryServiceFactory : public BrowserContextKeyedServiceFactory { + public: + static WebDiscoveryServiceFactory* GetInstance(); + static WebDiscoveryService* GetForBrowserContext( + content::BrowserContext* context); + + private: + friend base::NoDestructor; + + WebDiscoveryServiceFactory(); + ~WebDiscoveryServiceFactory() override; + + WebDiscoveryServiceFactory(const WebDiscoveryServiceFactory&) = delete; + WebDiscoveryServiceFactory& operator=(const WebDiscoveryServiceFactory&) = + delete; + + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const override; + bool ServiceIsCreatedWithBrowserContext() const override; +}; + +} // namespace web_discovery + +#endif // BRAVE_BROWSER_WEB_DISCOVERY_WEB_DISCOVERY_SERVICE_FACTORY_H_ diff --git a/browser/web_discovery/web_discovery_service_factory_unittest.cc b/browser/web_discovery/web_discovery_service_factory_unittest.cc new file mode 100644 index 000000000000..b4717b1749e7 --- /dev/null +++ b/browser/web_discovery/web_discovery_service_factory_unittest.cc @@ -0,0 +1,32 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/web_discovery/web_discovery_service_factory.h" + +#include "base/test/scoped_feature_list.h" +#include "brave/components/web_discovery/common/features.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile_manager.h" +#include "content/public/test/browser_task_environment.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace web_discovery { + +TEST(WebDiscoveryServiceFactoryTest, PrivateNotCreated) { + content::BrowserTaskEnvironment task_environment; + base::test::ScopedFeatureList scoped_features(features::kWebDiscoveryNative); + auto* browser_process = TestingBrowserProcess::GetGlobal(); + TestingProfileManager profile_manager(browser_process); + ASSERT_TRUE(profile_manager.SetUp()); + + auto* profile = profile_manager.CreateTestingProfile("test"); + + EXPECT_TRUE(WebDiscoveryServiceFactory::GetForBrowserContext(profile)); + EXPECT_FALSE(WebDiscoveryServiceFactory::GetForBrowserContext( + profile->GetOffTheRecordProfile( + Profile::OTRProfileID::CreateUniqueForTesting(), true))); +} + +} // namespace web_discovery diff --git a/browser/web_discovery/web_discovery_unittest.cc b/browser/web_discovery/web_discovery_unittest.cc index a52a74702100..f0c01983bedf 100644 --- a/browser/web_discovery/web_discovery_unittest.cc +++ b/browser/web_discovery/web_discovery_unittest.cc @@ -104,7 +104,7 @@ class WebDiscoveryCTATest : public testing::Test { }; TEST_F(WebDiscoveryCTATest, InitialDataTest) { - EXPECT_FALSE(prefs()->GetBoolean(kWebDiscoveryEnabled)); + EXPECT_FALSE(prefs()->GetBoolean(kWebDiscoveryExtensionEnabled)); const auto& info_value = prefs()->GetDict(kWebDiscoveryCTAState); EXPECT_TRUE(info_value.empty()); @@ -137,10 +137,10 @@ TEST_F(WebDiscoveryCTATest, ShouldShowInfoBarTest) { EXPECT_TRUE(ShouldShowWebDiscoveryInfoBar()); // Don't show if already enabled. - prefs()->SetBoolean(kWebDiscoveryEnabled, true); + prefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); EXPECT_FALSE(ShouldShowWebDiscoveryInfoBar()); - prefs()->SetBoolean(kWebDiscoveryEnabled, false); + prefs()->SetBoolean(kWebDiscoveryExtensionEnabled, false); EXPECT_TRUE(ShouldShowWebDiscoveryInfoBar()); WebDiscoveryCTAState state = GetCurrentCTAState(); diff --git a/build/android/java/templates/BraveConfig.template b/build/android/java/templates/BraveConfig.template index 025d376ef274..3eb00424a345 100644 --- a/build/android/java/templates/BraveConfig.template +++ b/build/android/java/templates/BraveConfig.template @@ -13,4 +13,5 @@ public class BraveConfig { public static final boolean P3A_ENABLED = BRAVE_ANDROID_P3A_ENABLED; public static final boolean AI_CHAT_ENABLED = BRAVE_ANDROID_AI_CHAT_ENABLED; public static final String WEBCOMPAT_REPORT_ENDPOINT = BRAVE_ANDROID_WEBCOMPAT_REPORT_ENDPOINT; + public static final boolean WEB_DISCOVERY_ENABLED = BRAVE_ANDROID_WEB_DISCOVERY_ENABLED; } diff --git a/chromium_src/chrome/browser/DEPS b/chromium_src/chrome/browser/DEPS index b0e763ad1418..a76c71b81b32 100644 --- a/chromium_src/chrome/browser/DEPS +++ b/chromium_src/chrome/browser/DEPS @@ -48,6 +48,7 @@ include_rules = [ "+brave/components/url_sanitizer", "+brave/components/vector_icons", "+brave/components/version_info", + "+brave/components/web_discovery/common", "+brave/components/webcompat", "+brave/net", "+brave/services/network/public", diff --git a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc index 9d9be4cbc041..c836c47732d8 100644 --- a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc @@ -18,6 +18,7 @@ #include "brave/components/playlist/common/features.h" #include "brave/components/request_otr/common/features.h" #include "brave/components/speedreader/common/features.h" +#include "brave/components/web_discovery/common/features.h" #include "brave/components/webcompat/core/common/features.h" #include "net/base/features.h" #include "third_party/blink/public/common/features.h" @@ -49,7 +50,8 @@ &google_sign_in_permission::features::kBraveGoogleSignInPermission, \ &net::features::kBraveForgetFirstPartyStorage, \ &brave_shields::features::kBraveShowStrictFingerprintingMode, \ - &brave_shields::features::kBraveLocalhostAccessPermission + &brave_shields::features::kBraveLocalhostAccessPermission, \ + &web_discovery::features::kWebDiscoveryNative // clang-format on diff --git a/chromium_src/chrome/renderer/DEPS b/chromium_src/chrome/renderer/DEPS index 24268d37bd82..2eace837fd59 100644 --- a/chromium_src/chrome/renderer/DEPS +++ b/chromium_src/chrome/renderer/DEPS @@ -8,6 +8,8 @@ include_rules = [ "+brave/components/content_settings/renderer", "+brave/components/tor/buildflags", "+brave/components/tor/renderer", + "+brave/components/web_discovery/common", + "+brave/components/web_discovery/renderer", "+brave/renderer", "+media/base/key_system_info.h", "+services/network/public/cpp", diff --git a/chromium_src/chrome/renderer/chrome_content_renderer_client.cc b/chromium_src/chrome/renderer/chrome_content_renderer_client.cc index 09e020080ff1..33e73297a60f 100644 --- a/chromium_src/chrome/renderer/chrome_content_renderer_client.cc +++ b/chromium_src/chrome/renderer/chrome_content_renderer_client.cc @@ -3,6 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at https://mozilla.org/MPL/2.0/. */ +#include "base/feature_list.h" #include "brave/components/ai_chat/core/common/buildflags/buildflags.h" #include "brave/components/ai_rewriter/common/buildflags/buildflags.h" #include "brave/components/content_settings/renderer/brave_content_settings_agent_impl.h" diff --git a/components/constants/pref_names.h b/components/constants/pref_names.h index 051eee42df44..c0c832447925 100644 --- a/components/constants/pref_names.h +++ b/components/constants/pref_names.h @@ -85,7 +85,8 @@ inline constexpr char kBraveShieldsSettingsVersion[] = inline constexpr char kDefaultBrowserPromptEnabled[] = "brave.default_browser_prompt_enabled"; -inline constexpr char kWebDiscoveryEnabled[] = "brave.web_discovery_enabled"; +inline constexpr char kWebDiscoveryExtensionEnabled[] = + "brave.web_discovery_enabled"; inline constexpr char kWebDiscoveryCTAState[] = "brave.web_discovery.cta_state"; inline constexpr char kDontAskEnableWebDiscovery[] = "brave.dont_ask_enable_web_discovery"; diff --git a/components/web_discovery/browser/BUILD.gn b/components/web_discovery/browser/BUILD.gn index 175ce801e08b..aa3c8dc31878 100644 --- a/components/web_discovery/browser/BUILD.gn +++ b/components/web_discovery/browser/BUILD.gn @@ -3,6 +3,63 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at https://mozilla.org/MPL/2.0/. +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") + +assert(enable_web_discovery_native) + static_library("browser") { - deps = [ "anonymous_credentials/rs/cxx:rust_lib" ] + sources = [ + "credential_manager.cc", + "credential_manager.h", + "credential_signer.cc", + "credential_signer.h", + "patterns.cc", + "patterns.h", + "pref_names.h", + "rsa.cc", + "rsa.h", + "server_config_loader.cc", + "server_config_loader.h", + "util.cc", + "util.h", + "web_discovery_service.cc", + "web_discovery_service.h", + ] + deps = [ + "anonymous_credentials/rs/cxx:rust_lib", + "//base", + "//brave/brave_domains", + "//brave/components/constants", + "//brave/components/web_discovery/common", + "//components/keyed_service/core", + "//components/prefs", + "//content/public/browser", + "//crypto", + "//extensions/buildflags", + "//net", + "//services/network/public/cpp", + "//third_party/boringssl", + "//third_party/re2", + "//third_party/zlib/google:compression_utils", + "//url", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "credential_manager_unittest.cc", + "patterns_unittest.cc", + "server_config_loader_unittest.cc", + ] + deps = [ + ":browser", + "//base/test:test_support", + "//brave/components/constants", + "//components/prefs:test_support", + "//crypto", + "//services/network:test_support", + "//services/network/public/cpp", + "//testing/gtest", + ] } diff --git a/components/web_discovery/browser/DEPS b/components/web_discovery/browser/DEPS new file mode 100644 index 000000000000..8a3c47daae89 --- /dev/null +++ b/components/web_discovery/browser/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+services/network/public", + "+extensions/buildflags/buildflags.h", + "+services/service_manager/public/cpp", + "+third_party/re2", + "+third_party/zlib", +] diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc new file mode 100644 index 000000000000..562d3d18a90f --- /dev/null +++ b/components/web_discovery/browser/credential_manager.cc @@ -0,0 +1,463 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/credential_manager.h" + +#include + +#include "base/base64.h" +#include "base/functional/bind.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/task/sequenced_task_runner.h" +#include "base/task/thread_pool.h" +#include "base/threading/thread_restrictions.h" +#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/browser/util.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "crypto/rsa_private_key.h" +#include "crypto/sha2.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "services/network/public/mojom/url_response_head.mojom.h" + +namespace web_discovery { + +namespace { + +constexpr char kJoinPath[] = "/join"; +constexpr char kJoinContentType[] = "application/json"; + +constexpr char kJoinDateField[] = "ts"; +constexpr char kJoinMessageField[] = "joinMsg"; +constexpr char kJoinRSAPublicKeyField[] = "pk"; +constexpr char kJoinRSASignatureField[] = "sig"; +constexpr char kJoinResponseField[] = "joinResponse"; + +constexpr char kGSKDictKey[] = "gsk"; +constexpr char kCredentialDictKey[] = "credential"; + +constexpr net::NetworkTrafficAnnotationTag kJoinNetworkTrafficAnnotation = + net::DefineNetworkTrafficAnnotation("wdp_join", R"( + semantics { + sender: "Brave Web Discovery HPNv2 Join" + description: + "Retrieves anonymous credentials in order to sign Web Discovery + measurements sent via the HumanWeb Proxy Network." + trigger: + "Requests are automatically sent on daily intervals " + "while Brave is running." + data: "Configuration attributes" + destination: WEBSITE + } + policy { + cookies_allowed: NO + setting: + "Users can opt-in or out via brave://settings/search" + })"); + +std::optional GenerateJoinRequest( + anonymous_credentials::CredentialManager* anonymous_credential_manager, + crypto::RSAPrivateKey* rsa_private_key, + std::string pre_challenge) { + base::AssertLongCPUWorkAllowed(); + base::span pre_challenge_span( + reinterpret_cast(pre_challenge.data()), pre_challenge.size()); + auto challenge = crypto::SHA256Hash(pre_challenge_span); + + auto join_request = anonymous_credential_manager->start_join( + rust::Slice(challenge.data(), challenge.size())); + + auto signature = RSASign(rsa_private_key, join_request.join_request); + + if (!signature) { + VLOG(1) << "RSA signature failed"; + return std::nullopt; + } + + return GenerateJoinRequestResult{.start_join_result = join_request, + .signature = *signature}; +} + +std::optional FinishJoin( + anonymous_credentials::CredentialManager* anonymous_credential_manager, + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes) { + base::AssertLongCPUWorkAllowed(); + auto pub_key_result = anonymous_credentials::load_group_public_key( + rust::Slice(group_pub_key.data(), group_pub_key.size())); + auto gsk_result = anonymous_credentials::load_credential_big( + rust::Slice(gsk.data(), gsk.size())); + auto join_resp_result = anonymous_credentials::load_join_response( + rust::Slice(join_resp_bytes.data(), join_resp_bytes.size())); + if (!pub_key_result.error_message.empty() || + !gsk_result.error_message.empty() || + !join_resp_result.error_message.empty()) { + VLOG(1) << "Failed to finish credential join due to deserialization error " + "with group pub key, gsk, or join response: " + << pub_key_result.error_message.c_str() + << gsk_result.error_message.c_str() + << join_resp_result.error_message.c_str(); + return std::nullopt; + } + auto finish_res = anonymous_credential_manager->finish_join( + *pub_key_result.value, *gsk_result.value, + std::move(join_resp_result.value)); + if (!finish_res.error_message.empty()) { + VLOG(1) << "Failed to finish credential join for " << date << ": " + << finish_res.error_message.c_str(); + return std::nullopt; + } + return base::Base64Encode(finish_res.data); +} + +std::optional> PerformSign( + anonymous_credentials::CredentialManager* anonymous_credential_manager, + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes) { + base::AssertLongCPUWorkAllowed(); + if (gsk_bytes && credential_bytes) { + auto gsk_result = anonymous_credentials::load_credential_big( + rust::Slice(reinterpret_cast(gsk_bytes->data()), + gsk_bytes->size())); + auto credential_result = anonymous_credentials::load_user_credentials( + rust::Slice(reinterpret_cast(credential_bytes->data()), + credential_bytes->size())); + if (!gsk_result.error_message.empty() || + !credential_result.error_message.empty()) { + VLOG(1) << "Failed to sign due to deserialization error with gsk, or " + "user credential: " + << gsk_result.error_message.c_str() + << credential_result.error_message.c_str(); + return std::nullopt; + } + anonymous_credential_manager->set_gsk_and_credentials( + std::move(gsk_result.value), std::move(credential_result.value)); + } + auto sig_res = anonymous_credential_manager->sign( + rust::Slice(msg.data(), msg.size()), + rust::Slice(basename.data(), basename.size())); + if (!sig_res.error_message.empty()) { + VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); + return std::nullopt; + } + return std::vector(sig_res.data.begin(), sig_res.data.end()); +} + +} // namespace + +CredentialManager::CredentialManager( + PrefService* profile_prefs, + network::SharedURLLoaderFactory* shared_url_loader_factory, + const ServerConfigLoader* server_config_loader) + : profile_prefs_(profile_prefs), + shared_url_loader_factory_(shared_url_loader_factory), + server_config_loader_(server_config_loader), + join_url_(GetDirectHPNHost() + kJoinPath), + backoff_entry_(&kBackoffPolicy), + sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})), + anonymous_credential_manager_( + new rust::Box(anonymous_credentials::new_credential_manager()), + base::OnTaskRunnerDeleter(sequenced_task_runner_)), + rsa_private_key_(nullptr, + base::OnTaskRunnerDeleter(sequenced_task_runner_)) {} + +CredentialManager::~CredentialManager() = default; + +bool CredentialManager::LoadRSAKey() { + std::string private_key_b64 = + profile_prefs_->GetString(kCredentialRSAPrivateKey); + rsa_public_key_b64_ = profile_prefs_->GetString(kCredentialRSAPublicKey); + + if (private_key_b64.empty() || rsa_public_key_b64_->empty()) { + rsa_public_key_b64_ = std::nullopt; + return true; + } + + rsa_private_key_.reset(ImportRSAKeyPair(private_key_b64).release()); + if (!rsa_private_key_) { + VLOG(1) << "Failed to import stored RSA key"; + return false; + } + + return true; +} + +void CredentialManager::OnNewRSAKey(std::unique_ptr key_info) { + if (!key_info) { + VLOG(1) << "RSA key generation failed"; + return; + } + + rsa_private_key_.reset(key_info->key_pair.release()); + rsa_public_key_b64_ = key_info->public_key_b64; + + profile_prefs_->SetString(kCredentialRSAPrivateKey, + key_info->private_key_b64); + profile_prefs_->SetString(kCredentialRSAPublicKey, *rsa_public_key_b64_); + + JoinGroups(); +} + +void CredentialManager::JoinGroups() { + const auto& server_config = server_config_loader_->GetLastServerConfig(); + auto today_date = FormatServerDate(base::Time::Now().UTCMidnight()); + const auto& anon_creds_dict = + profile_prefs_->GetDict(kAnonymousCredentialsDict); + for (const auto& [date, group_pub_key_b64] : server_config.group_pub_keys) { + if (date < today_date || join_url_loaders_.contains(date) || + anon_creds_dict.contains(date)) { + continue; + } + + if (rsa_private_key_ == nullptr) { + if (!LoadRSAKey()) { + return; + } + if (rsa_private_key_ == nullptr) { + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce(&GenerateRSAKeyPair), + base::BindOnce(&CredentialManager::OnNewRSAKey, + weak_ptr_factory_.GetWeakPtr())); + return; + } + } + + StartJoinGroup(date, group_pub_key_b64); + } +} + +void CredentialManager::StartJoinGroup(const std::string& date, + const std::string& group_pub_key_b64) { + auto group_pub_key = base::Base64Decode(group_pub_key_b64); + if (!group_pub_key) { + VLOG(1) << "Failed to decode group public key for " << date; + return; + } + std::vector group_pub_key_const(group_pub_key->begin(), + group_pub_key->end()); + + auto challenge_elements = base::Value::List::with_capacity(2); + challenge_elements.Append(*rsa_public_key_b64_); + challenge_elements.Append(group_pub_key_b64); + + std::string pre_challenge; + base::JSONWriter::Write(challenge_elements, &pre_challenge); + + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&GenerateJoinRequest, &**anonymous_credential_manager_, + rsa_private_key_.get(), pre_challenge), + base::BindOnce(&CredentialManager::OnJoinRequestReady, + weak_ptr_factory_.GetWeakPtr(), date, + group_pub_key_const)); +} + +void CredentialManager::OnJoinRequestReady( + std::string date, + std::vector group_pub_key, + std::optional generate_join_result) { + if (!generate_join_result) { + return; + } + base::Value::Dict body_fields; + + body_fields.Set(kJoinDateField, date); + body_fields.Set( + kJoinMessageField, + base::Base64Encode(generate_join_result->start_join_result.join_request)); + body_fields.Set(kJoinRSAPublicKeyField, *rsa_public_key_b64_); + body_fields.Set(kJoinRSASignatureField, generate_join_result->signature); + + std::string json_body; + if (!base::JSONWriter::Write(body_fields, &json_body)) { + VLOG(1) << "Join body serialization failed"; + return; + } + + auto gsk = std::vector( + generate_join_result->start_join_result.gsk.begin(), + generate_join_result->start_join_result.gsk.end()); + + auto resource_request = CreateResourceRequest(join_url_); + resource_request->headers.SetHeader(kVersionHeader, + base::NumberToString(kCurrentVersion)); + resource_request->method = net::HttpRequestHeaders::kPostMethod; + + join_url_loaders_[date] = network::SimpleURLLoader::Create( + std::move(resource_request), kJoinNetworkTrafficAnnotation); + auto& url_loader = join_url_loaders_[date]; + + url_loader->AttachStringForUpload(json_body, kJoinContentType); + + url_loader->DownloadToString( + shared_url_loader_factory_.get(), + base::BindOnce(&CredentialManager::OnJoinResponse, base::Unretained(this), + date, group_pub_key, gsk), + kMaxResponseSize); +} + +void CredentialManager::OnJoinResponse( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::optional response_body) { + bool result = ProcessJoinResponse(date, group_pub_key, gsk, response_body); + if (!result) { + HandleJoinResponseStatus(date, result); + } +} + +void CredentialManager::HandleJoinResponseStatus(const std::string& date, + bool result) { + join_url_loaders_.erase(date); + // TODO(djandries): what if the last request succeeds and the other requests + // fail? fix + if (join_url_loaders_.empty()) { + backoff_entry_.InformOfRequest(result); + + if (!result) { + retry_timer_.Start( + FROM_HERE, base::Time::Now() + backoff_entry_.GetTimeUntilRelease(), + base::BindOnce(&CredentialManager::JoinGroups, + base::Unretained(this))); + } + } +} + +bool CredentialManager::ProcessJoinResponse( + const std::string& date, + const std::vector& group_pub_key, + const std::vector& gsk, + const std::optional& response_body) { + CHECK(join_url_loaders_[date]); + auto& url_loader = join_url_loaders_[date]; + auto* response_info = url_loader->ResponseInfo(); + if (!response_body || !response_info || + response_info->headers->response_code() != 200) { + VLOG(1) << "Failed to fetch credentials for " << date; + return false; + } + + auto parsed_json = base::JSONReader::ReadAndReturnValueWithError( + *response_body, base::JSON_PARSE_RFC); + + if (!parsed_json.has_value()) { + VLOG(1) << "Failed to parse join response json"; + return false; + } + + const auto* root = parsed_json.value().GetIfDict(); + if (!root) { + VLOG(1) << "Failed to parse join response json: not a dict"; + return false; + } + + const auto* join_resp = root->FindString(kJoinResponseField); + if (!join_resp) { + VLOG(1) << "Failed to find content in join response json"; + return false; + } + + auto join_resp_bytes = base::Base64Decode(*join_resp); + if (!join_resp_bytes) { + VLOG(1) << "Failed to decode join response base64"; + return false; + } + std::vector join_resp_bytes_const(join_resp_bytes->begin(), + join_resp_bytes->end()); + + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&FinishJoin, &**anonymous_credential_manager_, date, + group_pub_key, gsk, join_resp_bytes_const), + base::BindOnce(&CredentialManager::OnCredentialsReady, + weak_ptr_factory_.GetWeakPtr(), date, gsk)); + return true; +} + +void CredentialManager::OnCredentialsReady( + std::string date, + std::vector gsk, + std::optional credentials) { + if (!credentials) { + HandleJoinResponseStatus(date, false); + return; + } + ScopedDictPrefUpdate update(profile_prefs_, kAnonymousCredentialsDict); + auto* date_dict = update->EnsureDict(date); + date_dict->Set(kGSKDictKey, base::Base64Encode(gsk)); + date_dict->Set(kCredentialDictKey, *credentials); + HandleJoinResponseStatus(date, true); +} + +bool CredentialManager::CredentialExistsForToday() { + return profile_prefs_->GetDict(kAnonymousCredentialsDict) + .contains(FormatServerDate(base::Time::Now())); +} + +bool CredentialManager::Sign(std::vector msg, + std::vector basename, + SignCallback callback) { + auto today_date = FormatServerDate(base::Time::Now().UTCMidnight()); + const auto& anon_creds_dict = + profile_prefs_->GetDict(kAnonymousCredentialsDict); + std::optional> gsk_bytes; + std::optional> credential_bytes; + if (!loaded_credential_date_ || loaded_credential_date_ != today_date) { + auto* today_cred_dict = anon_creds_dict.FindDict(today_date); + if (!today_cred_dict) { + VLOG(1) << "Failed to sign due to unavailability of credentials"; + return false; + } + auto* gsk_b64 = today_cred_dict->FindString(kGSKDictKey); + auto* credential_b64 = today_cred_dict->FindString(kCredentialDictKey); + if (!gsk_b64 || !credential_b64) { + VLOG(1) << "Failed to sign due to unavailability of gsk/credential"; + return false; + } + gsk_bytes = base::Base64Decode(*gsk_b64); + credential_bytes = base::Base64Decode(*credential_b64); + if (!gsk_bytes || !credential_bytes) { + VLOG(1) << "Failed to sign due to bad gsk/credential base64"; + return false; + } + } + + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&PerformSign, &**anonymous_credential_manager_, msg, + basename, gsk_bytes, credential_bytes), + base::BindOnce(&CredentialManager::OnSignResult, + weak_ptr_factory_.GetWeakPtr(), today_date, + std::move(callback))); + return true; +} + +void CredentialManager::OnSignResult( + std::string credential_date, + SignCallback callback, + std::optional> signed_message) { + loaded_credential_date_ = credential_date; + std::move(callback).Run(signed_message); +} + +void CredentialManager::UseFixedSeedForTesting() { + anonymous_credential_manager_ = + std::unique_ptr, + base::OnTaskRunnerDeleter>( + new rust::Box( + anonymous_credentials::new_credential_manager_with_fixed_seed()), + base::OnTaskRunnerDeleter(sequenced_task_runner_)); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/credential_manager.h b/components/web_discovery/browser/credential_manager.h new file mode 100644 index 000000000000..e5550a37ca0f --- /dev/null +++ b/components/web_discovery/browser/credential_manager.h @@ -0,0 +1,132 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_MANAGER_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_MANAGER_H_ + +#include +#include +#include +#include + +#include "base/functional/callback.h" +#include "base/memory/raw_ptr.h" +#include "base/task/sequenced_task_runner.h" +#include "base/timer/wall_clock_timer.h" +#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/credential_signer.h" +#include "brave/components/web_discovery/browser/rsa.h" +#include "brave/components/web_discovery/browser/server_config_loader.h" +#include "crypto/rsa_private_key.h" +#include "net/base/backoff_entry.h" + +class PrefService; + +namespace network { +class SharedURLLoaderFactory; +class SimpleURLLoader; +} // namespace network + +namespace web_discovery { + +struct GenerateJoinRequestResult { + anonymous_credentials::StartJoinResult start_join_result; + std::string signature; +}; + +// Manages and utilizes anonymous credentials used for communicating +// with Web Discovery servers. These Direct Anonymous Attestation credentials +// are used to prevent Sybil attacks on the servers. +// The manager provides two key functions: +// +// a) "joining": acquires credentials from the Web Discovery server. Join +// requests +// are signed with a random RSA key that is persisted with the profile. +// b) "signing": uses the previously acquired credentials to sign submissions +// which is required in order for the servers to accept the request. +class CredentialManager : public CredentialSigner { + public: + CredentialManager(PrefService* profile_prefs, + network::SharedURLLoaderFactory* shared_url_loader_factory, + const ServerConfigLoader* server_config_loader); + ~CredentialManager() override; + + CredentialManager(const CredentialManager&) = delete; + CredentialManager& operator=(const CredentialManager&) = delete; + + // Acquires credentials for all dates/"group public keys" published in + // the server config, if not stored already. + void JoinGroups(); + + // CredentialSigner: + bool CredentialExistsForToday() override; + + bool Sign(std::vector msg, + std::vector basename, + SignCallback callback) override; + + // Uses a fixed seed in the anonymous credential manager + // to provide deterministic credentials & signatures which + // are useful for testing. + void UseFixedSeedForTesting(); + + private: + bool LoadRSAKey(); + bool GenerateRSAKey(); + void OnNewRSAKey(std::unique_ptr key_info); + + void StartJoinGroup(const std::string& date, + const std::string& group_pub_key_b64); + + void OnJoinRequestReady( + std::string date, + std::vector group_pub_key, + std::optional generate_join_result); + + void OnJoinResponse(std::string date, + std::vector group_pub_key, + std::vector gsk, + std::optional response_body); + void HandleJoinResponseStatus(const std::string& date, bool result); + bool ProcessJoinResponse(const std::string& date, + const std::vector& group_pub_key, + const std::vector& gsk, + const std::optional& response_body); + void OnCredentialsReady(std::string date, + std::vector gsk, + std::optional credentials); + + void OnSignResult(std::string credential_date, + SignCallback callback, + std::optional> signed_message); + + raw_ptr profile_prefs_; + raw_ptr shared_url_loader_factory_; + raw_ptr server_config_loader_; + + GURL join_url_; + base::flat_map> + join_url_loaders_; + net::BackoffEntry backoff_entry_; + base::WallClockTimer retry_timer_; + + scoped_refptr sequenced_task_runner_; + + std::unique_ptr, + base::OnTaskRunnerDeleter> + anonymous_credential_manager_; + + std::unique_ptr + rsa_private_key_; + std::optional rsa_public_key_b64_; + + std::optional loaded_credential_date_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_MANAGER_H_ diff --git a/components/web_discovery/browser/credential_manager_unittest.cc b/components/web_discovery/browser/credential_manager_unittest.cc new file mode 100644 index 000000000000..98e12761a8b1 --- /dev/null +++ b/components/web_discovery/browser/credential_manager_unittest.cc @@ -0,0 +1,196 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/credential_manager.h" + +#include +#include +#include + +#include "base/files/file_util.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/path_service.h" +#include "base/test/bind.h" +#include "base/test/task_environment.h" +#include "brave/components/constants/brave_paths.h" +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/browser/util.h" +#include "brave/components/web_discovery/browser/web_discovery_service.h" +#include "components/prefs/testing_pref_service.h" +#include "services/network/public/cpp/resource_request.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace web_discovery { + +class WebDiscoveryCredentialManagerTest : public testing::Test { + public: + WebDiscoveryCredentialManagerTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), + shared_url_loader_factory_( + base::MakeRefCounted( + &url_loader_factory_)) {} + ~WebDiscoveryCredentialManagerTest() override = default; + + // testing::Test: + void SetUp() override { + base::Time set_time; + ASSERT_TRUE(base::Time::FromUTCString("2024-06-22", &set_time)); + task_environment_.AdvanceClock(set_time - base::Time::Now()); + + WebDiscoveryService::RegisterProfilePrefs(profile_prefs_.registry()); + + base::FilePath data_path = + base::PathService::CheckedGet(brave::DIR_TEST_DATA); + std::string test_data_json; + ASSERT_TRUE(base::ReadFileToString( + data_path.AppendASCII( + "web_discovery/credential_keys_and_responses.json"), + &test_data_json)); + auto test_data_value = base::JSONReader::Read(test_data_json); + ASSERT_TRUE(test_data_value); + const auto& test_data_dict = test_data_value->GetDict(); + const auto* rsa_priv_key = test_data_dict.FindString("rsa_priv_key"); + const auto* rsa_pub_key = test_data_dict.FindString("rsa_pub_key"); + const auto* group_pub_key = test_data_dict.FindString("group_pub_key"); + const auto* join_responses = test_data_dict.FindDict("join_responses"); + ASSERT_TRUE(rsa_priv_key && rsa_pub_key && group_pub_key && join_responses); + + profile_prefs_.SetString(kCredentialRSAPublicKey, *rsa_pub_key); + profile_prefs_.SetString(kCredentialRSAPrivateKey, *rsa_priv_key); + + server_config_loader_ = std::make_unique( + nullptr, base::FilePath(), nullptr, base::DoNothing(), + base::DoNothing()); + + auto server_config = std::make_unique(); + for (const auto [date, join_response] : *join_responses) { + server_config->group_pub_keys[date] = *group_pub_key; + join_responses_[date] = join_response.GetString(); + } + server_config_loader_->SetLastServerConfigForTesting( + std::move(server_config)); + + url_loader_factory_.SetInterceptor( + base::BindRepeating(&WebDiscoveryCredentialManagerTest::HandleRequest, + base::Unretained(this))); + + SetUpCredentialManager(); + } + + protected: + void SetUpCredentialManager() { + credential_manager_ = std::make_unique( + &profile_prefs_, shared_url_loader_factory_.get(), + server_config_loader_.get()); + credential_manager_->UseFixedSeedForTesting(); + } + + base::test::TaskEnvironment task_environment_; + std::unique_ptr credential_manager_; + TestingPrefServiceSimple profile_prefs_; + size_t join_requests_made_ = 0; + + private: + void HandleRequest(const network::ResourceRequest& request) { + url_loader_factory_.ClearResponses(); + std::string response; + const auto* elements = request.request_body->elements(); + ASSERT_EQ(elements->size(), 1u); + ASSERT_EQ(elements->at(0).type(), network::DataElement::Tag::kBytes); + auto body_json = + elements->at(0).As().AsStringPiece(); + + auto body_value = base::JSONReader::Read(body_json); + ASSERT_TRUE(body_value && body_value->is_dict()); + const auto* ts = body_value->GetDict().FindString("ts"); + ASSERT_TRUE(ts); + ASSERT_TRUE(join_responses_.contains(*ts)); + ASSERT_EQ(request.url.spec(), GetDirectHPNHost() + "/join"); + + base::Value::Dict dict; + dict.Set("joinResponse", join_responses_.at(*ts)); + ASSERT_TRUE( + base::JSONWriter::Write(base::Value(std::move(dict)), &response)); + url_loader_factory_.AddResponse(request.url.spec(), response); + join_requests_made_++; + } + + base::flat_map join_responses_; + std::unique_ptr server_config_loader_; + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr shared_url_loader_factory_; +}; + +TEST_F(WebDiscoveryCredentialManagerTest, JoinGroups) { + credential_manager_->JoinGroups(); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(join_requests_made_, 3u); + join_requests_made_ = 0; + + for (size_t i = 0; i < 3; i++) { + EXPECT_TRUE(credential_manager_->CredentialExistsForToday()); + task_environment_.FastForwardBy(base::Days(1)); + } + EXPECT_FALSE(credential_manager_->CredentialExistsForToday()); + EXPECT_EQ(join_requests_made_, 0u); +} + +TEST_F(WebDiscoveryCredentialManagerTest, LoadKeysFromStorage) { + credential_manager_->JoinGroups(); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(join_requests_made_, 3u); + join_requests_made_ = 0; + + SetUpCredentialManager(); + for (size_t i = 0; i < 3; i++) { + ASSERT_TRUE(credential_manager_->CredentialExistsForToday()); + task_environment_.FastForwardBy(base::Days(1)); + } + ASSERT_FALSE(credential_manager_->CredentialExistsForToday()); + EXPECT_EQ(join_requests_made_, 0u); +} + +TEST_F(WebDiscoveryCredentialManagerTest, Sign) { + std::vector message({0, 1, 2, 3, 4}); + std::vector basename({5, 6, 7, 8, 9}); + credential_manager_->Sign( + message, basename, + base::BindLambdaForTesting( + [&](const std::optional> signature) { + EXPECT_FALSE(signature); + })); + task_environment_.RunUntilIdle(); + + credential_manager_->JoinGroups(); + task_environment_.RunUntilIdle(); + + base::flat_set> signatures; + for (size_t i = 0; i < 3; i++) { + credential_manager_->Sign( + message, basename, + base::BindLambdaForTesting( + [&](const std::optional> signature) { + ASSERT_TRUE(signature); + EXPECT_FALSE(signature->empty()); + EXPECT_FALSE(signatures.contains(*signature)); + signatures.insert(*signature); + })); + task_environment_.FastForwardBy(base::Days(1)); + } + credential_manager_->Sign( + message, basename, + base::BindLambdaForTesting( + [&](const std::optional> signature) { + EXPECT_FALSE(signature); + })); + task_environment_.RunUntilIdle(); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/credential_signer.cc b/components/web_discovery/browser/credential_signer.cc new file mode 100644 index 000000000000..ca8fce0c3b51 --- /dev/null +++ b/components/web_discovery/browser/credential_signer.cc @@ -0,0 +1,12 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/credential_signer.h" + +namespace web_discovery { + +CredentialSigner::~CredentialSigner() = default; + +} // namespace web_discovery diff --git a/components/web_discovery/browser/credential_signer.h b/components/web_discovery/browser/credential_signer.h new file mode 100644 index 000000000000..b7e167cf1e49 --- /dev/null +++ b/components/web_discovery/browser/credential_signer.h @@ -0,0 +1,39 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_SIGNER_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_SIGNER_H_ + +#include +#include + +#include "base/functional/callback.h" + +namespace web_discovery { + +class CredentialSigner { + public: + using SignCallback = + base::OnceCallback>)>; + virtual ~CredentialSigner(); + + // Returns true is a credential is available for the current date. + // The caller can expect future calls to `Sign` to succeed, if made today. + virtual bool CredentialExistsForToday() = 0; + + // Signs a message for a given basename. The server has the ability + // to check whether two messages with the same basename were signed + // with the same credential without revealing the credential used, + // preventing Sybil attacks. + // See signature_basename.h/cc for more information on how the basename + // should be generated. + virtual bool Sign(std::vector msg, + std::vector basename, + SignCallback callback) = 0; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_CREDENTIAL_SIGNER_H_ diff --git a/components/web_discovery/browser/patterns.cc b/components/web_discovery/browser/patterns.cc new file mode 100644 index 000000000000..34cce886417e --- /dev/null +++ b/components/web_discovery/browser/patterns.cc @@ -0,0 +1,321 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/patterns.h" + +#include + +#include "base/containers/fixed_flat_map.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "third_party/re2/src/re2/re2.h" + +namespace web_discovery { + +namespace { + +constexpr char kNormalPatternsKey[] = "normal"; +constexpr char kStrictPatternsKey[] = "strict"; +constexpr char kUrlPatternsKey[] = "urlPatterns"; +constexpr char kSearchEnginesKey[] = "searchEngines"; +constexpr char kIdMappingKey[] = "idMapping"; +constexpr char kScrapeRulesKey[] = "scrape"; +constexpr char kSubSelectorKey[] = "item"; +constexpr char kRuleTypeKey[] = "type"; +constexpr char kAttributeKey[] = "etype"; +constexpr char kResultTypeKey[] = "results"; +constexpr char kActionKey[] = "action"; +constexpr char kFieldsKey[] = "fields"; +constexpr char kPayloadsKey[] = "payloads"; +constexpr char kJoinFieldAction[] = "join"; +constexpr char kFunctionsAppliedKey[] = "functionsApplied"; +constexpr char kQueryTemplateKey[] = "queryTemplate"; +constexpr char kQueryTemplatePrefixKey[] = "prefix"; + +constexpr auto kScrapeRuleTypeMap = + base::MakeFixedFlatMap({ + {"standard", ScrapeRuleType::kStandard}, + {"searchQuery", ScrapeRuleType::kSearchQuery}, + {"widgetTitle", ScrapeRuleType::kWidgetTitle}, + }); +constexpr auto kPayloadRuleTypeMap = + base::MakeFixedFlatMap({ + {"query", PayloadRuleType::kQuery}, + {"single", PayloadRuleType::kSingle}, + }); +constexpr auto kPayloadResultTypeMap = + base::MakeFixedFlatMap({ + {"single", PayloadResultType::kSingle}, + {"clustered", PayloadResultType::kClustered}, + {"custom", PayloadResultType::kCustom}, + }); + +bool ParsePayloadRule(const base::Value& rule_value, PayloadRule* rule_out) { + auto* rule_list = rule_value.GetIfList(); + if (!rule_list || rule_list->size() < 2) { + VLOG(1) << "Payload rule details is not a list of appropiate size"; + return false; + } + auto* selector = (*rule_list)[0].GetIfString(); + auto* payload_key = (*rule_list)[1].GetIfString(); + if (!selector || !payload_key) { + VLOG(1) << "Selector or key missing from payload rule"; + return false; + } + rule_out->selector = *selector; + rule_out->key = *payload_key; + if (rule_list->size() > 2) { + auto* field_action = (*rule_list)[2].GetIfString(); + if (field_action && *field_action == kJoinFieldAction) { + rule_out->is_join = true; + } + } + return true; +} + +std::optional> ParsePayloadRules( + const base::Value::Dict* payload_dict) { + std::vector result(payload_dict->size()); + + auto rule_group_it = result.begin(); + for (const auto [key, rule_group_value] : *payload_dict) { + auto* rule_group_dict = rule_group_value.GetIfDict(); + if (!rule_group_dict) { + VLOG(1) << "Payload rule group is not a dict"; + return std::nullopt; + } + auto* action = rule_group_dict->FindString(kActionKey); + auto* fields = rule_group_dict->FindList(kFieldsKey); + auto* rule_type_str = rule_group_dict->FindString(kRuleTypeKey); + auto* result_type_str = rule_group_dict->FindString(kResultTypeKey); + if (!action || !rule_type_str || !result_type_str) { + VLOG(1) << "Payload rule group attributes missing"; + return std::nullopt; + } + auto rule_type_it = kPayloadRuleTypeMap.find(*rule_type_str); + auto result_type_it = kPayloadResultTypeMap.find(*result_type_str); + if (rule_type_it == kPayloadRuleTypeMap.end() || + result_type_it == kPayloadResultTypeMap.end()) { + VLOG(1) << "Payload rule or result types unknown"; + return std::nullopt; + } + rule_group_it->action = *action; + rule_group_it->key = key; + rule_group_it->result_type = result_type_it->second; + rule_group_it->rule_type = rule_type_it->second; + if (fields) { + rule_group_it->rules = std::vector(fields->size()); + + auto rule_it = rule_group_it->rules.begin(); + for (const auto& rule_value : *fields) { + if (!ParsePayloadRule(rule_value, rule_it.base())) { + return std::nullopt; + } + rule_it = std::next(rule_it); + } + } + + rule_group_it = std::next(rule_group_it); + } + return result; +} + +RefineFunctionList ParseFunctionsApplied(const base::Value::List* list) { + RefineFunctionList result; + for (const auto& function_val : *list) { + const auto* function_list = function_val.GetIfList(); + if (!function_list || function_list->size() <= 1) { + continue; + } + std::vector function_vec; + for (const auto& element : *function_list) { + function_vec.push_back(element.Clone()); + } + result.push_back(std::move(function_vec)); + } + return result; +} + +std::optional> ParseScrapeRules( + const base::Value::Dict* scrape_url_dict) { + base::flat_map result; + + for (const auto [selector, rule_group_value] : *scrape_url_dict) { + auto* rule_group_dict = rule_group_value.GetIfDict(); + if (!rule_group_dict) { + VLOG(1) << "Scrape rule group is not a dict"; + return std::nullopt; + } + + auto& rule_group = result[selector]; + for (const auto [report_key, rule_value] : *rule_group_dict) { + auto* rule_dict = rule_value.GetIfDict(); + if (!rule_dict) { + VLOG(1) << "Scrape rule details is not a dict"; + return std::nullopt; + } + auto* sub_selector = rule_dict->FindString(kSubSelectorKey); + auto* attribute = rule_dict->FindString(kAttributeKey); + auto* rule_type_str = rule_dict->FindString(kRuleTypeKey); + auto* functions_applied = rule_dict->FindList(kFunctionsAppliedKey); + if (!attribute) { + VLOG(1) << "Attribute missing from rule"; + return std::nullopt; + } + auto rule = std::make_unique(); + rule->rule_type = ScrapeRuleType::kOther; + if (rule_type_str) { + auto rule_type_it = kScrapeRuleTypeMap.find(*rule_type_str); + if (rule_type_it != kScrapeRuleTypeMap.end()) { + rule->rule_type = rule_type_it->second; + } + } + rule->attribute = *attribute; + if (sub_selector) { + rule->sub_selector = *sub_selector; + } + if (functions_applied) { + rule->functions_applied = ParseFunctionsApplied(functions_applied); + } + rule_group[report_key] = std::move(rule); + } + } + return result; +} + +std::optional> ParsePatternsURLDetails( + const base::Value::Dict* root_dict) { + auto* url_patterns_list = root_dict->FindList(kUrlPatternsKey); + auto* search_engines_list = root_dict->FindList(kSearchEnginesKey); + auto* scrape_dict = root_dict->FindDict(kScrapeRulesKey); + auto* payloads_dict = root_dict->FindDict(kPayloadsKey); + auto* id_mapping_dict = root_dict->FindDict(kIdMappingKey); + auto* query_templates_dict = root_dict->FindDict(kQueryTemplateKey); + if (!url_patterns_list || !search_engines_list || !scrape_dict || + !id_mapping_dict) { + VLOG(1) << "Missing URL patterns, search engines, scrape rules, id " + "mapping, or query templates"; + return std::nullopt; + } + + std::vector result(url_patterns_list->size()); + + for (size_t i = 0; i < url_patterns_list->size(); i++) { + auto* url_regex = (*url_patterns_list)[i].GetIfString(); + if (!url_regex) { + VLOG(1) << "URL pattern is not string"; + return std::nullopt; + } + auto& details = result[i]; + + details.url_regex = std::make_unique(*url_regex); + + std::string i_str = base::NumberToString(i); + + auto* id = id_mapping_dict->FindString(i_str); + auto* scrape_url_dict = scrape_dict->FindDict(i_str); + auto* payloads_url_dict = payloads_dict->FindDict(i_str); + if (!id || !scrape_url_dict) { + VLOG(1) << "ID or scrape dict missing for pattern"; + return std::nullopt; + } + details.id = *id; + + details.is_search_engine = + base::ranges::find(search_engines_list->begin(), + search_engines_list->end(), + i_str) != search_engines_list->end(); + + auto scrape_rule_groups = ParseScrapeRules(scrape_url_dict); + if (!scrape_rule_groups) { + return std::nullopt; + } + details.scrape_rule_groups = std::move(*scrape_rule_groups); + if (payloads_url_dict) { + auto payload_rule_groups = ParsePayloadRules(payloads_url_dict); + if (!payload_rule_groups) { + return std::nullopt; + } + details.payload_rule_groups = std::move(*payload_rule_groups); + } + if (query_templates_dict) { + auto* query_template_dict = query_templates_dict->FindDict(i_str); + if (query_template_dict) { + auto* prefix = query_template_dict->FindString(kQueryTemplatePrefixKey); + if (prefix) { + details.search_template_prefix = *prefix; + } + } + } + } + + return result; +} + +} // namespace + +ScrapeRule::ScrapeRule() = default; +ScrapeRule::~ScrapeRule() = default; + +PayloadRule::PayloadRule() = default; +PayloadRule::~PayloadRule() = default; + +PayloadRuleGroup::PayloadRuleGroup() = default; +PayloadRuleGroup::~PayloadRuleGroup() = default; + +PatternsURLDetails::PatternsURLDetails() = default; +PatternsURLDetails::~PatternsURLDetails() = default; + +PatternsGroup::PatternsGroup() = default; +PatternsGroup::~PatternsGroup() = default; + +const PatternsURLDetails* PatternsGroup::GetMatchingURLPattern( + const GURL& url, + bool is_strict_scrape) const { + const auto& patterns = is_strict_scrape ? strict_patterns : normal_patterns; + for (const auto& pattern : patterns) { + if (re2::RE2::PartialMatch(url.spec(), *pattern.url_regex) && + !pattern.scrape_rule_groups.empty()) { + return &pattern; + } + } + return nullptr; +} + +std::unique_ptr ParsePatterns(const std::string& patterns_json) { + auto result = std::make_unique(); + auto patterns_value = base::JSONReader::Read(patterns_json); + if (!patterns_value || !patterns_value->is_dict()) { + VLOG(1) << "Patterns is not JSON or is not dict"; + return nullptr; + } + const auto& patterns_dict = patterns_value->GetDict(); + + auto* normal_dict = patterns_dict.FindDict(kNormalPatternsKey); + auto* strict_dict = patterns_dict.FindDict(kStrictPatternsKey); + if (!normal_dict && !strict_dict) { + VLOG(1) << "No normal or strict rules in patterns"; + return nullptr; + } + + if (normal_dict) { + auto details = ParsePatternsURLDetails(normal_dict); + if (!details) { + return nullptr; + } + result->normal_patterns = std::move(*details); + } + if (strict_dict) { + auto details = ParsePatternsURLDetails(strict_dict); + if (!details) { + return nullptr; + } + result->strict_patterns = std::move(*details); + } + return result; +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/patterns.h b/components/web_discovery/browser/patterns.h new file mode 100644 index 000000000000..b9e3b749e104 --- /dev/null +++ b/components/web_discovery/browser/patterns.h @@ -0,0 +1,174 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PATTERNS_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PATTERNS_H_ + +#include +#include +#include +#include + +#include "base/containers/flat_map.h" +#include "base/values.h" +#include "url/gurl.h" + +namespace re2 { +class RE2; +} // namespace re2 + +namespace web_discovery { + +enum class ScrapeRuleType { + // Will retrieve a value not defined in the DOM, such as the client country + // code or the current url. + kStandard, + // If the following two types are used for a rule, the value will be marked + // as the search query, which will be used for privacy checks. + kSearchQuery, + kWidgetTitle, + // All other rules should have this type. No special processing will be + // performed. + kOther +}; +enum class PayloadRuleType { + // Coupled with the `kClustered` result type. + // All instances of a given attribute will be grouped into a single payload. + kQuery, + // Coupled with the `kSingle` result type. + // Each instance of a given attribute will have its own payload. + kSingle +}; +enum class PayloadResultType { + // Coupled with the `kSingle` rule type. + kSingle, + // Coupled with the `kClustered` rule type. + kClustered, + // Currently unsupported/ignored. + kCustom +}; + +// Contains functions for refining the scraped value. The inner vector +// contains the function name and arguments for the function. +using RefineFunctionList = std::vector>; + +// Defines rule for scraping an attribute from a given selected element. +struct ScrapeRule { + ScrapeRule(); + ~ScrapeRule(); + + ScrapeRule(const ScrapeRule&) = delete; + ScrapeRule& operator=(const ScrapeRule&) = delete; + + // An optional selector for an element within the current selected element. + // The attribute will be retrieved from the embedded element. + std::optional sub_selector; + ScrapeRuleType rule_type; + // The name of the attribute to retrieve for a DOM element. + std::string attribute; + // Functions used to refine the retrieved value. See the "func ids" defined + // in content_scraper.cc for all possible functions. + RefineFunctionList functions_applied; +}; + +// A map of keys (arbitrary IDs used for storing the scraped result) to scrape +// rules. +using ScrapeRuleGroup = + base::flat_map>; + +// A rule for provided a single key/value pair within the submission payload. +struct PayloadRule { + PayloadRule(); + ~PayloadRule(); + + PayloadRule(const PayloadRule&) = delete; + PayloadRule& operator=(const PayloadRule&) = delete; + + // The DOM selector of the scraped attribute. + std::string selector; + // The arbitrary key associated with the scraped value. + std::string key; + // If set to true, an array-like Dict (each dict key is an index) + // will be rendered. + // Each value in the Dict will be a Dict containing all keys/values + // associated with the selector. This is commonly used for listing search + // results. + bool is_join = false; +}; + +// Contains rules for generating a payload for submission. +struct PayloadRuleGroup { + PayloadRuleGroup(); + ~PayloadRuleGroup(); + + PayloadRuleGroup(const PayloadRuleGroup&) = delete; + PayloadRuleGroup& operator=(const PayloadRuleGroup&) = delete; + + // An arbitrary ID for the rule group. Current, this isn't used in the + // payload. + std::string key; + PayloadRuleType rule_type; + PayloadResultType result_type; + // The name of the "action" for the given payload. + std::string action; + // The rules for generating the fields within the payload. + std::vector rules; +}; + +// Contains settings and rule groups associated with a particular URL. +struct PatternsURLDetails { + PatternsURLDetails(); + ~PatternsURLDetails(); + + PatternsURLDetails(const PatternsURLDetails&) = delete; + PatternsURLDetails& operator=(const PatternsURLDetails&) = delete; + + // The regex used to match the URL in the address bar. + std::unique_ptr url_regex; + bool is_search_engine; + // The two or three-letter arbitrary id associated with the site. + std::string id; + // The search path prefix used for constructing private search queries + // for double fetching. + std::optional search_template_prefix; + // The scraping rules for the site. A map of DOM selectors + // to rule groups. + base::flat_map scrape_rule_groups; + // The payload generation rules used for generating submissions + // from scraped attributes. + std::vector payload_rule_groups; +}; + +// The full "patterns" configuration provided by the Web Discovery server. +// The configuration provides rules for scraping certain pages. +struct PatternsGroup { + PatternsGroup(); + ~PatternsGroup(); + + PatternsGroup(const PatternsGroup&) = delete; + PatternsGroup& operator=(const PatternsGroup&) = delete; + + // Checks URL against all URL regexes in either the "normal" or "strict" set, + // and returns the URL details/rules if available. + const PatternsURLDetails* GetMatchingURLPattern(const GURL& url, + bool is_strict_scrape) const; + + // A list of URLs and rules used for scraping pages in the renderer, + // pre-"double fetch". These rules typically scrape simple attributes which + // are used to determine whether a page is private (i.e. the search query). + std::vector normal_patterns; + // A list of URLS and rules used for scraping contents from a "double fetch". + // The rules are usually more involved than the "normal" rules. In the case of + // search engine result pages, the rules will be used to retrieve the + // search results and any other relevant details. + std::vector strict_patterns; +}; + +// Returns nullptr if parsing fails. +std::unique_ptr ParsePatterns(const std::string& patterns_json); + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PATTERNS_H_ diff --git a/components/web_discovery/browser/patterns_unittest.cc b/components/web_discovery/browser/patterns_unittest.cc new file mode 100644 index 000000000000..f25f07091d62 --- /dev/null +++ b/components/web_discovery/browser/patterns_unittest.cc @@ -0,0 +1,345 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/patterns.h" + +#include +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/path_service.h" +#include "brave/components/constants/brave_paths.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/re2/src/re2/re2.h" + +namespace web_discovery { + +class WebDiscoveryPatternsTest : public testing::Test { + public: + ~WebDiscoveryPatternsTest() override = default; + + protected: + std::unique_ptr LoadAndParsePatterns() { + base::FilePath data_path = + base::PathService::CheckedGet(brave::DIR_TEST_DATA); + + std::string patterns_json; + bool read_result = base::ReadFileToString( + data_path.AppendASCII("web_discovery/patterns.json"), &patterns_json); + EXPECT_TRUE(read_result); + + auto parsed_patterns = ParsePatterns(patterns_json); + EXPECT_TRUE(parsed_patterns); + EXPECT_EQ(parsed_patterns->normal_patterns.size(), 3u); + EXPECT_EQ(parsed_patterns->strict_patterns.size(), 3u); + + return parsed_patterns; + } +}; + +TEST_F(WebDiscoveryPatternsTest, GroupURLDetails) { + auto parsed_patterns = LoadAndParsePatterns(); + ASSERT_TRUE(parsed_patterns); + + const auto* normal_pattern = &parsed_patterns->normal_patterns[0]; + EXPECT_EQ(normal_pattern->id, "ex1"); + EXPECT_EQ(normal_pattern->url_regex->pattern(), "^https://example1.com"); + EXPECT_TRUE(normal_pattern->is_search_engine); + EXPECT_FALSE(normal_pattern->search_template_prefix); + EXPECT_TRUE(normal_pattern->payload_rule_groups.empty()); + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 1u); + + normal_pattern = &parsed_patterns->normal_patterns[1]; + EXPECT_EQ(normal_pattern->id, "nase"); + EXPECT_EQ(normal_pattern->url_regex->pattern(), + "^https://notasearchengine.biz"); + EXPECT_FALSE(normal_pattern->is_search_engine); + EXPECT_FALSE(normal_pattern->search_template_prefix); + EXPECT_FALSE(normal_pattern->payload_rule_groups.empty()); + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 2u); + + normal_pattern = &parsed_patterns->normal_patterns[2]; + EXPECT_EQ(normal_pattern->id, "ex2"); + EXPECT_EQ(normal_pattern->url_regex->pattern(), + "^https://search.example2.com"); + EXPECT_TRUE(normal_pattern->is_search_engine); + EXPECT_FALSE(normal_pattern->search_template_prefix); + EXPECT_TRUE(normal_pattern->payload_rule_groups.empty()); + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 1u); + + const auto* strict_pattern = &parsed_patterns->strict_patterns[0]; + EXPECT_EQ(strict_pattern->id, "ex1"); + EXPECT_EQ(strict_pattern->url_regex->pattern(), "^https://example1.com."); + EXPECT_TRUE(strict_pattern->is_search_engine); + EXPECT_EQ(strict_pattern->search_template_prefix, "search?query="); + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 3u); + + strict_pattern = &parsed_patterns->strict_patterns[1]; + EXPECT_EQ(strict_pattern->id, "nase"); + EXPECT_EQ(strict_pattern->url_regex->pattern(), + "^https://notasearchengine.biz"); + EXPECT_FALSE(strict_pattern->is_search_engine); + EXPECT_EQ(strict_pattern->search_template_prefix, "directory?query="); + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 2u); + + strict_pattern = &parsed_patterns->strict_patterns[2]; + EXPECT_EQ(strict_pattern->id, "ex2"); + EXPECT_EQ(strict_pattern->url_regex->pattern(), + "^https://search.example2.com"); + EXPECT_TRUE(strict_pattern->is_search_engine); + EXPECT_FALSE(strict_pattern->search_template_prefix); + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 1u); +} + +TEST_F(WebDiscoveryPatternsTest, NormalScrapeRules) { + auto parsed_patterns = LoadAndParsePatterns(); + ASSERT_TRUE(parsed_patterns); + const auto* normal_pattern = &parsed_patterns->normal_patterns[0]; + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 1u); + + auto rule_group_it = normal_pattern->scrape_rule_groups.find("form .search"); + ASSERT_TRUE(rule_group_it != normal_pattern->scrape_rule_groups.end()); + const auto* rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + auto rule_it = rule_group->find("q"); + ASSERT_TRUE(rule_it != rule_group->end()); + const auto* rule = rule_it->second.get(); + + EXPECT_EQ(rule->sub_selector, "input"); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kSearchQuery); + EXPECT_EQ(rule->attribute, "value"); + EXPECT_TRUE(rule->functions_applied.empty()); + + normal_pattern = &parsed_patterns->normal_patterns[1]; + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 2u); + + rule_group_it = normal_pattern->scrape_rule_groups.find(".field1 input"); + ASSERT_TRUE(rule_group_it != normal_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("t"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kOther); + EXPECT_EQ(rule->attribute, "href"); + EXPECT_TRUE(rule->functions_applied.empty()); + + rule_group_it = normal_pattern->scrape_rule_groups.find(".field2 input"); + ASSERT_TRUE(rule_group_it != normal_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 2u); + + rule_it = rule_group->find("t"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kOther); + EXPECT_EQ(rule->attribute, "href"); + EXPECT_EQ(rule->functions_applied.size(), 1u); + EXPECT_EQ(rule->functions_applied[0].size(), 3u); + EXPECT_EQ(rule->functions_applied[0][0], "parseU"); + EXPECT_EQ(rule->functions_applied[0][1], "qs"); + EXPECT_EQ(rule->functions_applied[0][2], "t"); + + rule_it = rule_group->find("b"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kOther); + EXPECT_EQ(rule->attribute, "textContent"); + EXPECT_TRUE(rule->functions_applied.empty()); + + normal_pattern = &parsed_patterns->normal_patterns[2]; + EXPECT_EQ(normal_pattern->scrape_rule_groups.size(), 1u); + + rule_group_it = normal_pattern->scrape_rule_groups.find("form .search-box"); + ASSERT_TRUE(rule_group_it != normal_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("q"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_EQ(rule->sub_selector, "input"); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kSearchQuery); + EXPECT_EQ(rule->attribute, "value"); + EXPECT_TRUE(rule->functions_applied.empty()); +} + +TEST_F(WebDiscoveryPatternsTest, StrictScrapeRules) { + auto parsed_patterns = LoadAndParsePatterns(); + ASSERT_TRUE(parsed_patterns); + const auto* strict_pattern = &parsed_patterns->strict_patterns[0]; + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 3u); + + auto rule_group_it = strict_pattern->scrape_rule_groups.find("form .search"); + ASSERT_TRUE(rule_group_it != strict_pattern->scrape_rule_groups.end()); + const auto* rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + auto rule_it = rule_group->find("q"); + ASSERT_TRUE(rule_it != rule_group->end()); + const auto* rule = rule_it->second.get(); + + EXPECT_EQ(rule->sub_selector, "input"); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kSearchQuery); + EXPECT_EQ(rule->attribute, "value"); + EXPECT_TRUE(rule->functions_applied.empty()); + + rule_group_it = strict_pattern->scrape_rule_groups.find("qurl"); + ASSERT_TRUE(rule_group_it != strict_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("qurl"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kStandard); + EXPECT_EQ(rule->attribute, "url"); + EXPECT_EQ(rule->functions_applied.size(), 1u); + EXPECT_EQ(rule->functions_applied[0].size(), 3u); + EXPECT_EQ(rule->functions_applied[0][0], "maskU"); + EXPECT_EQ(rule->functions_applied[0][1], false); + EXPECT_EQ(rule->functions_applied[0][2], false); + + rule_group_it = strict_pattern->scrape_rule_groups.find("ctry"); + ASSERT_TRUE(rule_group_it != strict_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("ctry"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kStandard); + EXPECT_EQ(rule->attribute, "ctry"); + EXPECT_TRUE(rule->functions_applied.empty()); + + strict_pattern = &parsed_patterns->strict_patterns[1]; + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 2u); + + rule_group_it = strict_pattern->scrape_rule_groups.find("#content .a1"); + ASSERT_TRUE(rule_group_it != strict_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("age"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_FALSE(rule->sub_selector); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kOther); + EXPECT_EQ(rule->attribute, "textContent"); + EXPECT_TRUE(rule->functions_applied.empty()); + + strict_pattern = &parsed_patterns->strict_patterns[2]; + EXPECT_EQ(strict_pattern->scrape_rule_groups.size(), 1u); + + rule_group_it = strict_pattern->scrape_rule_groups.find("form .search-box"); + ASSERT_TRUE(rule_group_it != strict_pattern->scrape_rule_groups.end()); + rule_group = &rule_group_it->second; + EXPECT_EQ(rule_group->size(), 1u); + + rule_it = rule_group->find("age"); + ASSERT_TRUE(rule_it != rule_group->end()); + rule = rule_it->second.get(); + + EXPECT_EQ(rule->sub_selector, ".created-at"); + EXPECT_EQ(rule->rule_type, ScrapeRuleType::kOther); + EXPECT_EQ(rule->attribute, "value"); + EXPECT_TRUE(rule->functions_applied.empty()); +} + +TEST_F(WebDiscoveryPatternsTest, NormalPayloadRules) { + auto parsed_patterns = LoadAndParsePatterns(); + ASSERT_TRUE(parsed_patterns); + + const auto* normal_pattern = &parsed_patterns->normal_patterns[0]; + EXPECT_TRUE(normal_pattern->payload_rule_groups.empty()); + normal_pattern = &parsed_patterns->normal_patterns[2]; + EXPECT_TRUE(normal_pattern->payload_rule_groups.empty()); + + normal_pattern = &parsed_patterns->normal_patterns[1]; + EXPECT_EQ(normal_pattern->payload_rule_groups.size(), 1u); + + const auto& payload_group_it = normal_pattern->payload_rule_groups.front(); + EXPECT_EQ(payload_group_it.key, "key1"); + EXPECT_EQ(payload_group_it.rule_type, PayloadRuleType::kSingle); + EXPECT_EQ(payload_group_it.result_type, PayloadResultType::kSingle); + EXPECT_EQ(payload_group_it.action, "t"); + EXPECT_TRUE(payload_group_it.rules.empty()); +} + +TEST_F(WebDiscoveryPatternsTest, StrictPayloadRules) { + auto parsed_patterns = LoadAndParsePatterns(); + ASSERT_TRUE(parsed_patterns); + + const auto* strict_pattern = &parsed_patterns->strict_patterns[0]; + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + + const auto* payload_group = &strict_pattern->payload_rule_groups[0]; + EXPECT_EQ(payload_group->key, "key1"); + EXPECT_EQ(payload_group->rule_type, PayloadRuleType::kSingle); + EXPECT_EQ(payload_group->result_type, PayloadResultType::kSingle); + EXPECT_EQ(payload_group->action, "query"); + EXPECT_TRUE(payload_group->rules.empty()); + + strict_pattern = &parsed_patterns->strict_patterns[1]; + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + + payload_group = &strict_pattern->payload_rule_groups[0]; + EXPECT_EQ(payload_group->key, "key1"); + EXPECT_EQ(payload_group->rule_type, PayloadRuleType::kQuery); + EXPECT_EQ(payload_group->result_type, PayloadResultType::kClustered); + EXPECT_EQ(payload_group->action, "age-info"); + EXPECT_EQ(payload_group->rules.size(), 2u); + + const auto* payload_rule = &payload_group->rules[0]; + EXPECT_EQ(payload_rule->selector, "#content .a1"); + EXPECT_EQ(payload_rule->key, "age"); + EXPECT_FALSE(payload_rule->is_join); + + payload_rule = &payload_group->rules[1]; + EXPECT_EQ(payload_rule->selector, "ctry"); + EXPECT_EQ(payload_rule->key, "ctry"); + EXPECT_FALSE(payload_rule->is_join); + + strict_pattern = &parsed_patterns->strict_patterns[2]; + EXPECT_EQ(strict_pattern->payload_rule_groups.size(), 1u); + + payload_group = &strict_pattern->payload_rule_groups[0]; + EXPECT_EQ(payload_group->key, "key2"); + EXPECT_EQ(payload_group->rule_type, PayloadRuleType::kQuery); + EXPECT_EQ(payload_group->result_type, PayloadResultType::kClustered); + EXPECT_EQ(payload_group->action, "age-info"); + EXPECT_EQ(payload_group->rules.size(), 1u); + + payload_rule = &payload_group->rules[0]; + EXPECT_EQ(payload_rule->selector, "form .search-box"); + EXPECT_EQ(payload_rule->key, "age"); + EXPECT_TRUE(payload_rule->is_join); +} + +TEST_F(WebDiscoveryPatternsTest, BadPatterns) { + ASSERT_FALSE(ParsePatterns("ABC")); + ASSERT_FALSE(ParsePatterns("{}")); + ASSERT_FALSE(ParsePatterns(R"({"normal":{}, "strict":{}})")); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/pref_names.h b/components/web_discovery/browser/pref_names.h new file mode 100644 index 000000000000..b2f39a2cb7f6 --- /dev/null +++ b/components/web_discovery/browser/pref_names.h @@ -0,0 +1,32 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PREF_NAMES_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PREF_NAMES_H_ + +namespace web_discovery { + +// Profile prefs +inline constexpr char kWebDiscoveryNativeEnabled[] = + "brave.web_discovery.wdp_native_enabled"; + +// The following pref values are used for generating +// anonymous signatures for user submissions. +// Since they are not used for encrypting sensitive data, +// they do not require secure storage. +inline constexpr char kCredentialRSAPrivateKey[] = + "brave.web_discovery.rsa_priv_key"; +inline constexpr char kCredentialRSAPublicKey[] = + "brave.web_discovery.rsa_pub_key"; +inline constexpr char kAnonymousCredentialsDict[] = + "brave.web_discovery.anon_creds"; + +// Local state +inline constexpr char kPatternsRetrievalTime[] = + "brave.web_discovery.patterns_retrieval_time"; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_PREF_NAMES_H_ diff --git a/components/web_discovery/browser/rsa.cc b/components/web_discovery/browser/rsa.cc new file mode 100644 index 000000000000..6b2b50cf16bd --- /dev/null +++ b/components/web_discovery/browser/rsa.cc @@ -0,0 +1,83 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/rsa.h" + +#include +#include + +#include "base/base64.h" +#include "base/threading/thread_restrictions.h" +#include "crypto/rsa_private_key.h" +#include "crypto/signature_creator.h" + +namespace web_discovery { + +namespace { + +constexpr size_t kRsaKeySize = 2048; + +} // namespace + +RSAKeyInfo::RSAKeyInfo() = default; +RSAKeyInfo::~RSAKeyInfo() = default; + +std::unique_ptr GenerateRSAKeyPair() { + base::AssertLongCPUWorkAllowed(); + auto info = std::make_unique(); + + auto private_key = crypto::RSAPrivateKey::Create(kRsaKeySize); + if (!private_key) { + return nullptr; + } + + info->key_pair = std::move(private_key); + + std::vector encoded_public_key; + std::vector encoded_private_key; + + if (!info->key_pair->ExportPrivateKey(&encoded_private_key) || + !info->key_pair->ExportPublicKey(&encoded_public_key)) { + return nullptr; + } + + info->public_key_b64 = base::Base64Encode(encoded_public_key); + info->private_key_b64 = base::Base64Encode(encoded_private_key); + + return info; +} + +std::unique_ptr ImportRSAKeyPair( + const std::string& private_key_b64) { + auto decoded_key = base::Base64Decode(private_key_b64); + if (!decoded_key) { + return nullptr; + } + + return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(*decoded_key); +} + +std::optional RSASign(crypto::RSAPrivateKey* key, + base::span message) { + base::AssertLongCPUWorkAllowed(); + CHECK(key); + + std::vector signature; + auto signature_creator = crypto::SignatureCreator::Create( + key, crypto::SignatureCreator::HashAlgorithm::SHA256); + if (!signature_creator) { + return std::nullopt; + } + if (!signature_creator->Update(message.data(), message.size())) { + return std::nullopt; + } + if (!signature_creator->Final(&signature)) { + return std::nullopt; + } + + return base::Base64Encode(signature); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/rsa.h b/components/web_discovery/browser/rsa.h new file mode 100644 index 000000000000..e940649fcdd1 --- /dev/null +++ b/components/web_discovery/browser/rsa.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_RSA_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_RSA_H_ + +#include +#include +#include + +#include "base/containers/span.h" +#include "crypto/rsa_private_key.h" + +namespace web_discovery { + +struct RSAKeyInfo { + RSAKeyInfo(); + ~RSAKeyInfo(); + std::unique_ptr key_pair; + std::string private_key_b64; + std::string public_key_b64; +}; + +std::unique_ptr GenerateRSAKeyPair(); + +std::unique_ptr ImportRSAKeyPair( + const std::string& private_key_b64); + +std::optional RSASign(crypto::RSAPrivateKey* key, + base::span message); + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_RSA_H_ diff --git a/components/web_discovery/browser/server_config_loader.cc b/components/web_discovery/browser/server_config_loader.cc new file mode 100644 index 000000000000..1f4d0c3a7fca --- /dev/null +++ b/components/web_discovery/browser/server_config_loader.cc @@ -0,0 +1,446 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/server_config_loader.h" + +#include + +#include "base/barrier_callback.h" +#include "base/base64.h" +#include "base/containers/fixed_flat_set.h" +#include "base/files/file_util.h" +#include "base/functional/bind.h" +#include "base/json/json_reader.h" +#include "base/location.h" +#include "base/rand_util.h" +#include "base/task/thread_pool.h" +#include "base/threading/thread_restrictions.h" +#include "base/values.h" +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/browser/util.h" +#include "components/prefs/pref_service.h" +#include "net/traffic_annotation/network_traffic_annotation.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/network/public/cpp/simple_url_loader.h" +#include "services/network/public/mojom/url_response_head.mojom.h" +#include "third_party/zlib/google/compression_utils.h" + +namespace web_discovery { + +namespace { + +constexpr base::TimeDelta kMinReloadInterval = base::Hours(1); +constexpr base::TimeDelta kMaxReloadInterval = base::Hours(4); + +constexpr base::TimeDelta kPatternsMaxAge = base::Hours(2); +constexpr base::TimeDelta kPatternsRequestLatestDelay = base::Minutes(3); +constexpr base::TimeDelta kPatternsRequestInitDelay = base::Seconds(15); + +constexpr size_t kPatternsMaxFileSize = 128000; + +constexpr net::NetworkTrafficAnnotationTag kNetworkTrafficAnnotation = + net::DefineNetworkTrafficAnnotation("wdp_config", R"( + semantics { + sender: "Brave Web Discovery Server Configuration Fetch" + description: + "Requests server configuration needed to send Web Discovery " + "measurements to Brave servers." + trigger: + "Requests are automatically sent at intervals " + "while Brave is running." + data: "Configuration attributes" + destination: WEBSITE + } + policy { + cookies_allowed: NO + setting: + "Users can opt-in or out via brave://settings/search" + })"); + +constexpr char kGroupPubKeysFieldName[] = "groupPubKeys"; +constexpr char kPubKeysFieldName[] = "pubKeys"; +constexpr char kMinVersionFieldName[] = "minVersion"; +constexpr char kKeysFieldName[] = "keys"; +constexpr char kLimitFieldName[] = "limit"; +constexpr char kPeriodFieldName[] = "period"; +constexpr char kSourceMapFieldName[] = "sourceMap"; +constexpr char kSourceMapActionsFieldName[] = "actions"; +constexpr char kLocationFieldName[] = "location"; + +constexpr char kCollectorConfigPathWithFields[] = + "/config?fields=minVersion,groupPubKeys,pubKeys,sourceMap"; +constexpr char kQuorumConfigPath[] = "/config"; +constexpr char kPatternsFilename[] = "wdp_patterns.json"; + +constexpr char kOmittedLocationValue[] = "--"; +constexpr auto kAllowedReportLocations = + base::MakeFixedFlatSet( + {"ar", "at", "au", "be", "br", "ca", "ch", "cn", "cz", "de", + "dk", "es", "fi", "fr", "gb", "gr", "hu", "in", "it", "jp", + "mx", "nl", "no", "pl", "ro", "ru", "se", "ua", "us"}); + +KeyMap ParseKeys(const base::Value::Dict& encoded_keys) { + KeyMap map; + for (const auto [date, key_b64] : encoded_keys) { + std::vector decoded_data; + // Decode to check for valid base64 + if (!base::Base64Decode(key_b64.GetString())) { + continue; + } + map[date] = key_b64.GetString(); + } + return map; +} + +base::flat_map> +ParseSourceMapActionConfigs(const base::Value::Dict& configs_dict) { + base::flat_map> map; + for (const auto [action, config_dict_val] : configs_dict) { + auto* config_dict = config_dict_val.GetIfDict(); + if (!config_dict) { + continue; + } + auto& action_config = map[action]; + if (!action_config) { + action_config = std::make_unique(); + } + auto* keys_list = config_dict->FindList(kKeysFieldName); + if (keys_list) { + for (const auto& key_val : *keys_list) { + if (key_val.is_string()) { + action_config->keys.push_back(key_val.GetString()); + } + } + } + auto limit = config_dict->FindInt(kLimitFieldName); + auto period = config_dict->FindInt(kPeriodFieldName); + + action_config->limit = limit && limit > 0 ? *limit : 1; + action_config->period = period && period > 0 ? *period : 24; + } + return map; +} + +std::optional GunzipContents(std::string gzipped_contents) { + base::AssertLongCPUWorkAllowed(); + std::string result; + if (!compression::GzipUncompress(gzipped_contents, &result)) { + return std::nullopt; + } + return result; +} + +bool WritePatternsFile(base::FilePath patterns_path, std::string contents) { + return base::WriteFile(patterns_path, contents); +} + +std::optional ReadPatternsFile(base::FilePath patterns_path) { + std::string contents; + if (!base::ReadFileToStringWithMaxSize(patterns_path, &contents, + kPatternsMaxFileSize)) { + return std::nullopt; + } + return contents; +} + +} // namespace + +SourceMapActionConfig::SourceMapActionConfig() = default; +SourceMapActionConfig::~SourceMapActionConfig() = default; + +ServerConfig::ServerConfig() = default; +ServerConfig::~ServerConfig() = default; + +ServerConfigLoader::ServerConfigLoader( + PrefService* local_state, + base::FilePath user_data_dir, + network::SharedURLLoaderFactory* shared_url_loader_factory, + base::RepeatingClosure config_callback, + base::RepeatingClosure patterns_callback) + : local_state_(local_state), + sequenced_task_runner_( + base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})), + shared_url_loader_factory_(shared_url_loader_factory), + config_callback_(config_callback), + patterns_callback_(patterns_callback), + config_backoff_entry_(&kBackoffPolicy), + patterns_backoff_entry_(&kBackoffPolicy) { + collector_config_url_ = + GURL(GetAnonymousHPNHost() + kCollectorConfigPathWithFields); + quorum_config_url_ = GURL(GetQuorumHost() + kQuorumConfigPath); + patterns_url_ = GetPatternsEndpoint(); + + patterns_path_ = user_data_dir.AppendASCII(kPatternsFilename); +} + +ServerConfigLoader::~ServerConfigLoader() = default; + +const ServerConfig& ServerConfigLoader::GetLastServerConfig() const { + CHECK(last_loaded_server_config_); + return *last_loaded_server_config_; +} + +const PatternsGroup& ServerConfigLoader::GetLastPatterns() const { + CHECK(last_loaded_patterns_); + return *last_loaded_patterns_; +} + +void ServerConfigLoader::SetLastServerConfigForTesting( + std::unique_ptr server_config) { + last_loaded_server_config_ = std::move(server_config); +} + +void ServerConfigLoader::SetLastPatternsForTesting( + std::unique_ptr patterns) { + last_loaded_patterns_ = std::move(patterns); +} + +void ServerConfigLoader::LoadConfigs() { + if (collector_config_url_loader_ || quorum_config_url_loader_) { + // Another request is in progress + return; + } + auto collector_resource_request = + CreateResourceRequest(collector_config_url_); + auto quorum_resource_request = CreateResourceRequest(quorum_config_url_); + + collector_config_url_loader_ = network::SimpleURLLoader::Create( + std::move(collector_resource_request), kNetworkTrafficAnnotation); + quorum_config_url_loader_ = network::SimpleURLLoader::Create( + std::move(quorum_resource_request), kNetworkTrafficAnnotation); + + auto callback = base::BarrierCallback>( + 2, base::BindOnce(&ServerConfigLoader::OnConfigResponses, + base::Unretained(this))); + + collector_config_url_loader_->DownloadToString( + shared_url_loader_factory_.get(), callback, kMaxResponseSize); + quorum_config_url_loader_->DownloadToString(shared_url_loader_factory_.get(), + callback, kMaxResponseSize); +} + +void ServerConfigLoader::OnConfigResponses( + std::vector> response_bodies) { + CHECK_EQ(response_bodies.size(), 2u); + base::Time update_time = base::Time::Now(); + bool result = ProcessConfigResponses(response_bodies[0], response_bodies[1]); + + config_backoff_entry_.InformOfRequest(result); + + collector_config_url_loader_ = nullptr; + quorum_config_url_loader_ = nullptr; + + if (!result) { + update_time += config_backoff_entry_.GetTimeUntilRelease(); + } else { + update_time += base::RandTimeDelta(kMinReloadInterval, kMaxReloadInterval); + + SchedulePatternsRequest(); + } + + config_update_timer_.Start( + FROM_HERE, update_time, + base::BindOnce(&ServerConfigLoader::LoadConfigs, base::Unretained(this))); +} + +bool ServerConfigLoader::ProcessConfigResponses( + const std::optional& collector_response_body, + const std::optional& quorum_response_body) { + auto* collector_response_info = collector_config_url_loader_->ResponseInfo(); + auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); + if (!collector_response_body || !collector_response_info || + !quorum_response_body || !collector_response_info || + collector_response_info->headers->response_code() != 200 || + quorum_response_info->headers->response_code() != 200) { + VLOG(1) << "Failed to fetch server config"; + return false; + } + + auto collector_parsed_json = base::JSONReader::ReadAndReturnValueWithError( + *collector_response_body, base::JSON_PARSE_RFC); + auto quorum_parsed_json = base::JSONReader::ReadAndReturnValueWithError( + *quorum_response_body, base::JSON_PARSE_RFC); + + if (!collector_parsed_json.has_value() || !quorum_parsed_json.has_value()) { + VLOG(1) << "Failed to parse server config json"; + return false; + } + + const auto* collector_root = collector_parsed_json.value().GetIfDict(); + const auto* quorum_root = quorum_parsed_json.value().GetIfDict(); + if (!collector_root || !quorum_root) { + VLOG(1) << "Failed to parse server config: not a dict"; + return false; + } + + const auto min_version = collector_root->FindInt(kMinVersionFieldName); + if (min_version && *min_version > kCurrentVersion) { + VLOG(1) << "Server minimum version is higher than current version, failing"; + return false; + } + + auto config = std::make_unique(); + + const auto* group_pub_keys = collector_root->FindDict(kGroupPubKeysFieldName); + if (!group_pub_keys) { + VLOG(1) << "Failed to retrieve groupPubKeys from server config"; + return false; + } + const auto* pub_keys = collector_root->FindDict(kPubKeysFieldName); + if (!pub_keys) { + VLOG(1) << "Failed to retrieve pubKeys from server config"; + return false; + } + const auto* source_map = collector_root->FindDict(kSourceMapFieldName); + const auto* source_map_actions = + source_map ? source_map->FindDict(kSourceMapActionsFieldName) : nullptr; + if (!source_map_actions) { + VLOG(1) << "Failed to retrieve sourceMap from server config"; + return false; + } + + const auto* location = quorum_root->FindString(kLocationFieldName); + if (location && kAllowedReportLocations.contains(*location)) { + config->location = *location; + } else { + config->location = kOmittedLocationValue; + } + + config->group_pub_keys = ParseKeys(*group_pub_keys); + config->pub_keys = ParseKeys(*pub_keys); + config->source_map_actions = ParseSourceMapActionConfigs(*source_map_actions); + + last_loaded_server_config_ = std::move(config); + config_callback_.Run(); + return true; +} + +void ServerConfigLoader::LoadStoredPatterns() { + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce(&ReadPatternsFile, patterns_path_), + base::BindOnce(&ServerConfigLoader::OnPatternsFileLoaded, + weak_ptr_factory_.GetWeakPtr())); +} + +void ServerConfigLoader::OnPatternsFileLoaded( + std::optional patterns_json) { + if (!patterns_json) { + VLOG(1) << "Failed to load local patterns file"; + local_state_->ClearPref(kPatternsRetrievalTime); + SchedulePatternsRequest(); + return; + } + auto parsed_patterns = ParsePatterns(*patterns_json); + if (!parsed_patterns) { + local_state_->ClearPref(kPatternsRetrievalTime); + SchedulePatternsRequest(); + return; + } + last_loaded_patterns_ = std::move(parsed_patterns); + patterns_callback_.Run(); +} + +void ServerConfigLoader::SchedulePatternsRequest() { + base::Time update_time = base::Time::Now(); + base::TimeDelta time_since_last_retrieval = + base::Time::Now() - local_state_->GetTime(kPatternsRetrievalTime); + if (time_since_last_retrieval >= kPatternsMaxAge) { + update_time += kPatternsRequestInitDelay; + } else { + if (!patterns_first_request_made_) { + LoadStoredPatterns(); + } + update_time += kPatternsMaxAge - time_since_last_retrieval + + base::RandTimeDelta({}, kPatternsRequestLatestDelay); + } + patterns_first_request_made_ = true; + patterns_update_timer_.Start( + FROM_HERE, update_time, + base::BindOnce(&ServerConfigLoader::RequestPatterns, + base::Unretained(this))); +} + +void ServerConfigLoader::RequestPatterns() { + if (patterns_url_loader_) { + return; + } + auto resource_request = CreateResourceRequest(patterns_url_); + + patterns_url_loader_ = network::SimpleURLLoader::Create( + std::move(resource_request), kNetworkTrafficAnnotation); + + patterns_url_loader_->DownloadToString( + shared_url_loader_factory_.get(), + base::BindOnce(&ServerConfigLoader::OnPatternsResponse, + base::Unretained(this)), + kMaxResponseSize); +} + +void ServerConfigLoader::HandlePatternsStatus(bool result) { + patterns_url_loader_ = nullptr; + patterns_backoff_entry_.InformOfRequest(result); + + if (result) { + SchedulePatternsRequest(); + return; + } + + patterns_update_timer_.Start( + FROM_HERE, + base::Time::Now() + patterns_backoff_entry_.GetTimeUntilRelease(), + base::BindOnce(&ServerConfigLoader::RequestPatterns, + base::Unretained(this))); +} + +void ServerConfigLoader::OnPatternsResponse( + std::optional response_body) { + auto* response_info = patterns_url_loader_->ResponseInfo(); + if (!response_body || !response_info || + response_info->headers->response_code() != 200) { + VLOG(1) << "Failed to retrieve patterns file"; + HandlePatternsStatus(false); + return; + } + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce(&GunzipContents, *response_body), + base::BindOnce(&ServerConfigLoader::OnPatternsGunzip, + weak_ptr_factory_.GetWeakPtr())); +} + +void ServerConfigLoader::OnPatternsGunzip( + std::optional patterns_json) { + if (!patterns_json) { + VLOG(1) << "Failed to decompress patterns file"; + HandlePatternsStatus(false); + return; + } + auto parsed_patterns = ParsePatterns(*patterns_json); + if (!parsed_patterns) { + HandlePatternsStatus(false); + return; + } + sequenced_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&WritePatternsFile, patterns_path_, *patterns_json), + base::BindOnce(&ServerConfigLoader::OnPatternsWritten, + weak_ptr_factory_.GetWeakPtr(), + std::move(parsed_patterns))); +} + +void ServerConfigLoader::OnPatternsWritten( + std::unique_ptr parsed_group, + bool result) { + if (!result) { + VLOG(1) << "Failed to write patterns file"; + HandlePatternsStatus(false); + return; + } + local_state_->SetTime(kPatternsRetrievalTime, base::Time::Now()); + HandlePatternsStatus(true); + last_loaded_patterns_ = std::move(parsed_group); + patterns_callback_.Run(); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/server_config_loader.h b/components/web_discovery/browser/server_config_loader.h new file mode 100644 index 000000000000..10ad1f55fa4d --- /dev/null +++ b/components/web_discovery/browser/server_config_loader.h @@ -0,0 +1,143 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_SERVER_CONFIG_LOADER_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_SERVER_CONFIG_LOADER_H_ + +#include +#include +#include +#include + +#include "base/containers/flat_map.h" +#include "base/files/file_path.h" +#include "base/memory/raw_ptr.h" +#include "base/timer/wall_clock_timer.h" +#include "brave/components/web_discovery/browser/patterns.h" +#include "net/base/backoff_entry.h" +#include "url/gurl.h" + +class PrefService; + +namespace network { +class SharedURLLoaderFactory; +class SimpleURLLoader; +} // namespace network + +namespace web_discovery { + +using KeyMap = base::flat_map; + +struct SourceMapActionConfig { + SourceMapActionConfig(); + ~SourceMapActionConfig(); + + SourceMapActionConfig(const SourceMapActionConfig&) = delete; + SourceMapActionConfig& operator=(const SourceMapActionConfig&) = delete; + + std::vector keys; + size_t limit; + size_t period; +}; + +struct ServerConfig { + ServerConfig(); + ~ServerConfig(); + + ServerConfig(const ServerConfig&) = delete; + ServerConfig& operator=(const ServerConfig&) = delete; + + KeyMap group_pub_keys; + KeyMap pub_keys; + + base::flat_map> + source_map_actions; + + std::string location; +}; + +// Handles retrieval, updating and caching of the following server +// configurations: +// - HPN server config: contains public keys, and "source maps" used +// for generating basenames. +// - "quorum" config: contains the country code of the user +// - patterns: contains the rules for scraping/submission of certain pages +class ServerConfigLoader { + public: + ServerConfigLoader(PrefService* local_state, + base::FilePath user_data_dir, + network::SharedURLLoaderFactory* shared_url_loader_factory, + base::RepeatingClosure config_callback, + base::RepeatingClosure patterns_callback); + ~ServerConfigLoader(); + + ServerConfigLoader(const ServerConfigLoader&) = delete; + ServerConfigLoader& operator=(const ServerConfigLoader&) = delete; + + // Loads all three server configurations. Update requests will be scheduled + // once complete. + void LoadConfigs(); + + // Returns the last loaded server config, which is a combination of the + // HPN and "quorum" configs. May only call after the config_callback is + // triggered. + const ServerConfig& GetLastServerConfig() const; + // Returns the pattern config. May only call after the patterns_callback is + // triggered. + const PatternsGroup& GetLastPatterns() const; + + void SetLastServerConfigForTesting( + std::unique_ptr server_config); + void SetLastPatternsForTesting(std::unique_ptr patterns); + + private: + void OnConfigResponses( + std::vector> response_bodies); + bool ProcessConfigResponses( + const std::optional& collector_response_body, + const std::optional& quorum_response_body); + + void LoadStoredPatterns(); + void OnPatternsFileLoaded(std::optional patterns_json); + void SchedulePatternsRequest(); + void RequestPatterns(); + void OnPatternsResponse(std::optional response_body); + void OnPatternsGunzip(std::optional patterns_json); + void OnPatternsWritten(std::unique_ptr parsed_group, + bool result); + void HandlePatternsStatus(bool result); + + raw_ptr local_state_; + + scoped_refptr sequenced_task_runner_; + + GURL collector_config_url_; + GURL quorum_config_url_; + GURL patterns_url_; + base::FilePath patterns_path_; + raw_ptr shared_url_loader_factory_; + + base::RepeatingClosure config_callback_; + base::RepeatingClosure patterns_callback_; + + std::unique_ptr collector_config_url_loader_; + std::unique_ptr quorum_config_url_loader_; + std::unique_ptr patterns_url_loader_; + net::BackoffEntry config_backoff_entry_; + net::BackoffEntry patterns_backoff_entry_; + + base::WallClockTimer config_update_timer_; + base::WallClockTimer patterns_update_timer_; + bool patterns_first_request_made_ = false; + + std::unique_ptr last_loaded_server_config_; + std::unique_ptr last_loaded_patterns_; + + base::WeakPtrFactory weak_ptr_factory_{this}; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_SERVER_CONFIG_LOADER_H_ diff --git a/components/web_discovery/browser/server_config_loader_unittest.cc b/components/web_discovery/browser/server_config_loader_unittest.cc new file mode 100644 index 000000000000..fe69cbf7520e --- /dev/null +++ b/components/web_discovery/browser/server_config_loader_unittest.cc @@ -0,0 +1,281 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/server_config_loader.h" + +#include + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/path_service.h" +#include "base/test/task_environment.h" +#include "brave/components/constants/brave_paths.h" +#include "brave/components/web_discovery/browser/util.h" +#include "brave/components/web_discovery/browser/web_discovery_service.h" +#include "components/prefs/testing_pref_service.h" +#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" +#include "services/network/test/test_url_loader_factory.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace web_discovery { + +class WebDiscoveryServerConfigLoaderTest : public testing::Test { + public: + WebDiscoveryServerConfigLoaderTest() + : task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME), + shared_url_loader_factory_( + base::MakeRefCounted( + &url_loader_factory_)) {} + ~WebDiscoveryServerConfigLoaderTest() override = default; + + // testing::Test: + void SetUp() override { + WebDiscoveryService::RegisterLocalStatePrefs(local_state_.registry()); + + ASSERT_TRUE(install_dir_.CreateUniqueTempDir()); + + base::FilePath data_path = + base::PathService::CheckedGet(brave::DIR_TEST_DATA) + .AppendASCII("web_discovery"); + ASSERT_TRUE(base::ReadFileToString(data_path.AppendASCII("hpn-config.json"), + &hpn_config_contents_)); + ASSERT_TRUE(base::ReadFileToString( + data_path.AppendASCII("quorum-config.json"), &quorum_config_contents_)); + ASSERT_TRUE(base::ReadFileToString(data_path.AppendASCII("patterns.gz"), + &patterns_gz_contents_)); + + url_loader_factory_.SetInterceptor( + base::BindRepeating(&WebDiscoveryServerConfigLoaderTest::HandleRequest, + base::Unretained(this))); + SetupServerConfigLoader(); + } + + protected: + void SetupServerConfigLoader() { + server_config_loader_ = std::make_unique( + &local_state_, install_dir_.GetPath(), shared_url_loader_factory_.get(), + base::BindRepeating( + &WebDiscoveryServerConfigLoaderTest::HandleConfigReady, + base::Unretained(this)), + base::BindRepeating( + &WebDiscoveryServerConfigLoaderTest::HandlePatternsReady, + base::Unretained(this))); + } + + void ResetCounts() { + hpn_config_requests_made_ = 0; + quorum_config_requests_made_ = 0; + patterns_requests_made_ = 0; + config_ready_calls_made_ = 0; + patterns_ready_calls_made_ = 0; + } + + bool PatternsFileExists() { + return base::PathExists( + install_dir_.GetPath().AppendASCII("wdp_patterns.json")); + } + + base::test::TaskEnvironment task_environment_; + std::unique_ptr server_config_loader_; + base::ScopedTempDir install_dir_; + + size_t hpn_config_requests_made_ = 0; + size_t quorum_config_requests_made_ = 0; + size_t patterns_requests_made_ = 0; + + size_t config_ready_calls_made_ = 0; + size_t patterns_ready_calls_made_ = 0; + + net::HttpStatusCode hpn_config_status_code_ = net::HTTP_OK; + net::HttpStatusCode quorum_config_status_code_ = net::HTTP_OK; + net::HttpStatusCode patterns_status_code_ = net::HTTP_OK; + + private: + void HandleRequest(const network::ResourceRequest& request) { + url_loader_factory_.ClearResponses(); + + ASSERT_EQ(request.method, net::HttpRequestHeaders::kGetMethod); + if (request.url.spec().starts_with(GetAnonymousHPNHost() + "/config")) { + hpn_config_requests_made_++; + url_loader_factory_.AddResponse(request.url.spec(), hpn_config_contents_, + hpn_config_status_code_); + } else if (request.url.spec() == GetQuorumHost() + "/config") { + quorum_config_requests_made_++; + url_loader_factory_.AddResponse(request.url.spec(), + quorum_config_contents_, + quorum_config_status_code_); + } else if (request.url.spec() == GetPatternsEndpoint()) { + patterns_requests_made_++; + url_loader_factory_.AddResponse(request.url.spec(), patterns_gz_contents_, + patterns_status_code_); + } else { + FAIL(); + } + } + + void HandleConfigReady() { config_ready_calls_made_++; } + + void HandlePatternsReady() { patterns_ready_calls_made_++; } + + std::string patterns_gz_contents_; + std::string quorum_config_contents_; + std::string hpn_config_contents_; + + TestingPrefServiceSimple local_state_; + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr shared_url_loader_factory_; +}; + +TEST_F(WebDiscoveryServerConfigLoaderTest, LoadConfigs) { + EXPECT_EQ(hpn_config_requests_made_, 0u); + EXPECT_EQ(quorum_config_requests_made_, 0u); + EXPECT_EQ(patterns_requests_made_, 0u); + EXPECT_EQ(config_ready_calls_made_, 0u); + EXPECT_EQ(patterns_ready_calls_made_, 0u); + + task_environment_.FastForwardBy(base::Seconds(40)); + + server_config_loader_->LoadConfigs(); + task_environment_.RunUntilIdle(); + + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 0u); + EXPECT_EQ(config_ready_calls_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 0u); + + EXPECT_FALSE(PatternsFileExists()); + + const auto& server_config = server_config_loader_->GetLastServerConfig(); + EXPECT_EQ(server_config.location, "ca"); + EXPECT_EQ(server_config.group_pub_keys.size(), 4u); + EXPECT_EQ(server_config.pub_keys.size(), 4u); + EXPECT_EQ(server_config.source_map_actions.size(), 27u); + + task_environment_.FastForwardBy(base::Seconds(40)); + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 1u); + EXPECT_EQ(config_ready_calls_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 1u); + + EXPECT_TRUE(PatternsFileExists()); + + const auto& patterns = server_config_loader_->GetLastPatterns(); + EXPECT_EQ(patterns.normal_patterns.size(), 9u); + EXPECT_EQ(patterns.strict_patterns.size(), 8u); +} + +TEST_F(WebDiscoveryServerConfigLoaderTest, ReloadConfigs) { + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + + for (size_t i = 0; i < 3; i++) { + ResetCounts(); + task_environment_.FastForwardBy(base::Hours(4)); + + EXPECT_GE(hpn_config_requests_made_, 1u); + EXPECT_GE(quorum_config_requests_made_, 1u); + EXPECT_GE(patterns_requests_made_, 1u); + EXPECT_GE(config_ready_calls_made_, 1u); + EXPECT_GE(patterns_ready_calls_made_, 1u); + EXPECT_TRUE(PatternsFileExists()); + } +} + +TEST_F(WebDiscoveryServerConfigLoaderTest, LoadPatternsFromStorage) { + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + EXPECT_TRUE(PatternsFileExists()); + + ResetCounts(); + SetupServerConfigLoader(); + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 0u); + EXPECT_EQ(config_ready_calls_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 1u); + EXPECT_TRUE(PatternsFileExists()); + + task_environment_.AdvanceClock(base::Hours(3)); + ResetCounts(); + SetupServerConfigLoader(); + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 1u); + EXPECT_EQ(config_ready_calls_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 1u); + EXPECT_TRUE(PatternsFileExists()); +} + +TEST_F(WebDiscoveryServerConfigLoaderTest, ConfigRetry) { + hpn_config_status_code_ = net::HTTP_INTERNAL_SERVER_ERROR; + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_GE(hpn_config_requests_made_, 1u); + EXPECT_GE(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 0u); + EXPECT_EQ(config_ready_calls_made_, 0u); + EXPECT_EQ(patterns_ready_calls_made_, 0u); + + ResetCounts(); + hpn_config_status_code_ = net::HTTP_OK; + quorum_config_status_code_ = net::HTTP_INTERNAL_SERVER_ERROR; + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_GE(hpn_config_requests_made_, 1u); + EXPECT_GE(quorum_config_requests_made_, 1u); + EXPECT_EQ(patterns_requests_made_, 0u); + EXPECT_EQ(config_ready_calls_made_, 0u); + EXPECT_EQ(patterns_ready_calls_made_, 0u); + EXPECT_FALSE(PatternsFileExists()); + + ResetCounts(); + quorum_config_status_code_ = net::HTTP_OK; + task_environment_.FastForwardBy(base::Seconds(90)); + + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_EQ(config_ready_calls_made_, 1u); + + task_environment_.FastForwardBy(base::Seconds(40)); + EXPECT_EQ(patterns_requests_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 1u); + EXPECT_TRUE(PatternsFileExists()); +} + +TEST_F(WebDiscoveryServerConfigLoaderTest, PatternsRetry) { + patterns_status_code_ = net::HTTP_INTERNAL_SERVER_ERROR; + server_config_loader_->LoadConfigs(); + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_EQ(hpn_config_requests_made_, 1u); + EXPECT_EQ(quorum_config_requests_made_, 1u); + EXPECT_GE(patterns_requests_made_, 1u); + EXPECT_EQ(config_ready_calls_made_, 1u); + EXPECT_EQ(patterns_ready_calls_made_, 0u); + EXPECT_FALSE(PatternsFileExists()); + + ResetCounts(); + patterns_status_code_ = net::HTTP_OK; + task_environment_.FastForwardBy(base::Seconds(40)); + + EXPECT_EQ(hpn_config_requests_made_, 0u); + EXPECT_EQ(quorum_config_requests_made_, 0u); + EXPECT_EQ(patterns_requests_made_, 1u); + EXPECT_EQ(config_ready_calls_made_, 0u); + EXPECT_EQ(patterns_ready_calls_made_, 1u); + EXPECT_TRUE(PatternsFileExists()); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/util.cc b/components/web_discovery/browser/util.cc new file mode 100644 index 000000000000..acb38bdf63e2 --- /dev/null +++ b/components/web_discovery/browser/util.cc @@ -0,0 +1,85 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/util.h" + +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "brave/brave_domains/service_domains.h" +#include "url/url_util.h" + +namespace web_discovery { + +namespace { +constexpr char kCollectorHostPrefix[] = "collector.wdp"; +constexpr char kQuorumHostPrefix[] = "quorum.wdp"; +constexpr char kPatternsHostPrefix[] = "patterns.wdp"; +constexpr char kPatternsPath[] = "/patterns.gz"; +} // namespace + +std::string GetDirectHPNHost() { + // TODO(djandries): Replace with non-proxied endpoint once available + return GetAnonymousHPNHost(); +} + +std::string GetAnonymousHPNHost() { + auto* cmd_line = base::CommandLine::ForCurrentProcess(); + if (cmd_line->HasSwitch(kCollectorHostSwitch)) { + return cmd_line->GetSwitchValueASCII(kCollectorHostSwitch); + } + return base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator, + brave_domains::GetServicesDomain(kCollectorHostPrefix)}); +} + +std::string GetQuorumHost() { + return base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator, + brave_domains::GetServicesDomain(kQuorumHostPrefix)}); +} + +GURL GetPatternsEndpoint() { + return GURL(base::StrCat( + {url::kHttpsScheme, url::kStandardSchemeSeparator, + brave_domains::GetServicesDomain(kPatternsHostPrefix), kPatternsPath})); +} + +std::unique_ptr CreateResourceRequest(GURL url) { + auto resource_request = std::make_unique(); + resource_request->url = url; + resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; + return resource_request; +} + +std::string FormatServerDate(const base::Time& date) { + base::Time::Exploded exploded; + date.UTCExplode(&exploded); + return base::StringPrintf("%04d%02d%02d", exploded.year, exploded.month, + exploded.day_of_month); +} + +std::string DecodeURLComponent(const std::string_view value) { + url::RawCanonOutputT result; + url::DecodeURLEscapeSequences(value, url::DecodeURLMode::kUTF8OrIsomorphic, + &result); + return base::UTF16ToUTF8(result.view()); +} + +std::optional ExtractValueFromQueryString( + const std::string_view query_string, + const std::string_view key) { + url::Component query_slice(0, query_string.length()); + url::Component key_slice; + url::Component value_slice; + while (url::ExtractQueryKeyValue(query_string, &query_slice, &key_slice, + &value_slice)) { + if (query_string.substr(key_slice.begin, key_slice.len) != key) { + continue; + } + return DecodeURLComponent( + query_string.substr(value_slice.begin, value_slice.len)); + } + return std::nullopt; +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/util.h b/components/web_discovery/browser/util.h new file mode 100644 index 000000000000..9792b4622d55 --- /dev/null +++ b/components/web_discovery/browser/util.h @@ -0,0 +1,64 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_UTIL_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_UTIL_H_ + +#include +#include +#include + +#include "base/time/time.h" +#include "net/base/backoff_entry.h" +#include "services/network/public/cpp/resource_request.h" +#include "url/gurl.h" + +namespace web_discovery { + +inline constexpr size_t kMaxResponseSize = 16 * 1024; +inline constexpr char kCollectorHostSwitch[] = "wdp-collector-host"; +inline constexpr char kVersionHeader[] = "Version"; +inline constexpr int kCurrentVersion = 1; + +// The default backoff policy to use for scheduling retry requests. +inline constexpr net::BackoffEntry::Policy kBackoffPolicy = { + .num_errors_to_ignore = 0, + .initial_delay_ms = 10 * 1000, + .multiply_factor = 2.0, + .jitter_factor = 0.1, + .maximum_backoff_ms = 10 * 60 * 1000, + .entry_lifetime_ms = -1, + .always_use_initial_delay = false}; + +// Returns the non-proxied HPN host, used for acquiring anonymous credentials. +std::string GetDirectHPNHost(); +// Returns the proxied HPN host, used for retrieving server config and page +// content submission. +std::string GetAnonymousHPNHost(); +// Returns the "quorum" host, used for location config and page event +// submission. +std::string GetQuorumHost(); +// Returns the full URL for the patterns config. +GURL GetPatternsEndpoint(); + +// Creates a new ResourceRequest with the given URL and credentials omitted. +std::unique_ptr CreateResourceRequest(GURL url); + +// Formats a given date as a string in the format "YYYYMMDD", in the UTC +// timezone. +std::string FormatServerDate(const base::Time& date); + +// Decodes URL-encoded components, converting escape sequences to their +// corresponding characters. +std::string DecodeURLComponent(const std::string_view value); + +// Extracts the value associated with a given key from a URL query string. +std::optional ExtractValueFromQueryString( + const std::string_view query_string, + const std::string_view key); + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_UTIL_H_ diff --git a/components/web_discovery/browser/web_discovery_service.cc b/components/web_discovery/browser/web_discovery_service.cc new file mode 100644 index 000000000000..6acb7c733565 --- /dev/null +++ b/components/web_discovery/browser/web_discovery_service.cc @@ -0,0 +1,117 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/web_discovery_service.h" + +#include + +#include "base/feature_list.h" +#include "base/functional/bind.h" +#include "brave/components/constants/pref_names.h" +#include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/browser/server_config_loader.h" +#include "brave/components/web_discovery/common/features.h" +#include "components/prefs/pref_registry_simple.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "extensions/buildflags/buildflags.h" +#include "services/network/public/cpp/shared_url_loader_factory.h" +#include "services/service_manager/public/cpp/interface_provider.h" + +namespace web_discovery { + +WebDiscoveryService::WebDiscoveryService( + PrefService* local_state, + PrefService* profile_prefs, + base::FilePath user_data_dir, + scoped_refptr shared_url_loader_factory) + : local_state_(local_state), + profile_prefs_(profile_prefs), + user_data_dir_(user_data_dir), + shared_url_loader_factory_(shared_url_loader_factory) { +#if BUILDFLAG(ENABLE_EXTENSIONS) + if (profile_prefs_->GetBoolean(kWebDiscoveryExtensionEnabled)) { + profile_prefs_->ClearPref(kWebDiscoveryExtensionEnabled); + profile_prefs_->SetBoolean(kWebDiscoveryNativeEnabled, true); + } +#endif + + pref_change_registrar_.Init(profile_prefs); + pref_change_registrar_.Add( + kWebDiscoveryNativeEnabled, + base::BindRepeating(&WebDiscoveryService::OnEnabledChange, + base::Unretained(this))); + + if (profile_prefs_->GetBoolean(kWebDiscoveryNativeEnabled)) { + Start(); + } +} + +WebDiscoveryService::~WebDiscoveryService() = default; + +void WebDiscoveryService::RegisterLocalStatePrefs( + PrefRegistrySimple* registry) { + registry->RegisterTimePref(kPatternsRetrievalTime, {}); +} + +void WebDiscoveryService::RegisterProfilePrefs(PrefRegistrySimple* registry) { + registry->RegisterBooleanPref(kWebDiscoveryNativeEnabled, false); + registry->RegisterDictionaryPref(kAnonymousCredentialsDict); + registry->RegisterStringPref(kCredentialRSAPrivateKey, {}); + registry->RegisterStringPref(kCredentialRSAPublicKey, {}); +} + +void WebDiscoveryService::SetExtensionPrefIfNativeDisabled( + PrefService* profile_prefs) { +#if BUILDFLAG(ENABLE_EXTENSIONS) + if (!base::FeatureList::IsEnabled(features::kWebDiscoveryNative) && + profile_prefs->GetBoolean(kWebDiscoveryNativeEnabled)) { + profile_prefs->SetBoolean(kWebDiscoveryExtensionEnabled, true); + } +#endif +} + +void WebDiscoveryService::Start() { + if (!server_config_loader_) { + server_config_loader_ = std::make_unique( + local_state_, user_data_dir_, shared_url_loader_factory_.get(), + base::BindRepeating(&WebDiscoveryService::OnConfigChange, + base::Unretained(this)), + base::BindRepeating(&WebDiscoveryService::OnPatternsLoaded, + base::Unretained(this))); + server_config_loader_->LoadConfigs(); + } + if (!credential_manager_) { + credential_manager_ = std::make_unique( + profile_prefs_, shared_url_loader_factory_.get(), + server_config_loader_.get()); + } +} + +void WebDiscoveryService::Stop() { + server_config_loader_ = nullptr; + credential_manager_ = nullptr; + + profile_prefs_->ClearPref(kWebDiscoveryNativeEnabled); + profile_prefs_->ClearPref(kAnonymousCredentialsDict); + profile_prefs_->ClearPref(kCredentialRSAPrivateKey); + profile_prefs_->ClearPref(kCredentialRSAPublicKey); +} + +void WebDiscoveryService::OnEnabledChange() { + if (profile_prefs_->GetBoolean(kWebDiscoveryNativeEnabled)) { + Start(); + } else { + Stop(); + } +} + +void WebDiscoveryService::OnConfigChange() { + credential_manager_->JoinGroups(); +} + +void WebDiscoveryService::OnPatternsLoaded() {} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/web_discovery_service.h b/components/web_discovery/browser/web_discovery_service.h new file mode 100644 index 000000000000..52cc9b74193c --- /dev/null +++ b/components/web_discovery/browser/web_discovery_service.h @@ -0,0 +1,78 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_WEB_DISCOVERY_SERVICE_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_WEB_DISCOVERY_SERVICE_H_ + +#include +#include +#include + +#include "base/files/file_path.h" +#include "base/memory/raw_ptr.h" +#include "brave/components/web_discovery/browser/credential_manager.h" +#include "brave/components/web_discovery/browser/server_config_loader.h" +#include "components/keyed_service/core/keyed_service.h" +#include "components/prefs/pref_change_registrar.h" + +class PrefRegistrySimple; +class PrefService; + +namespace content { +class RenderFrameHost; +} + +namespace network { +class SharedURLLoaderFactory; +} // namespace network + +namespace web_discovery { + +// The main service for the native re-implementation of Web Discovery Project. +// Handles scraping and reporting of relevant pages for opted-in users. +class WebDiscoveryService : public KeyedService { + public: + WebDiscoveryService( + PrefService* local_state, + PrefService* profile_prefs, + base::FilePath user_data_dir, + scoped_refptr shared_url_loader_factory); + ~WebDiscoveryService() override; + + WebDiscoveryService(const WebDiscoveryService&) = delete; + WebDiscoveryService& operator=(const WebDiscoveryService&) = delete; + + static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); + static void RegisterProfilePrefs(PrefRegistrySimple* registry); + + // Sets the extension preference to true if the preference for the native + // implementation is set to true and the feature is disabled. + // Relevant for a Griffin/variations rollback. + static void SetExtensionPrefIfNativeDisabled(PrefService* profile_prefs); + + private: + void Start(); + void Stop(); + + void OnEnabledChange(); + + void OnConfigChange(); + void OnPatternsLoaded(); + + raw_ptr local_state_; + raw_ptr profile_prefs_; + PrefChangeRegistrar pref_change_registrar_; + + base::FilePath user_data_dir_; + + scoped_refptr shared_url_loader_factory_; + + std::unique_ptr server_config_loader_; + std::unique_ptr credential_manager_; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_WEB_DISCOVERY_SERVICE_H_ diff --git a/components/web_discovery/common/BUILD.gn b/components/web_discovery/common/BUILD.gn new file mode 100644 index 000000000000..a50ead3e6ff5 --- /dev/null +++ b/components/web_discovery/common/BUILD.gn @@ -0,0 +1,18 @@ +# Copyright (c) 2024 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") + +static_library("common") { + sources = [ + "features.cc", + "features.h", + ] + + deps = [ + "//base", + "//brave/components/web_discovery/common/buildflags", + ] +} diff --git a/components/web_discovery/common/buildflags/BUILD.gn b/components/web_discovery/common/buildflags/BUILD.gn new file mode 100644 index 000000000000..5d0fc823c167 --- /dev/null +++ b/components/web_discovery/common/buildflags/BUILD.gn @@ -0,0 +1,12 @@ +# Copyright (c) 2024 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//build/buildflag_header.gni") + +buildflag_header("buildflags") { + header = "buildflags.h" + flags = [ "ENABLE_WEB_DISCOVERY_NATIVE=$enable_web_discovery_native" ] +} diff --git a/components/web_discovery/common/buildflags/buildflags.gni b/components/web_discovery/common/buildflags/buildflags.gni new file mode 100644 index 000000000000..4b6d5e413031 --- /dev/null +++ b/components/web_discovery/common/buildflags/buildflags.gni @@ -0,0 +1,8 @@ +# Copyright (c) 2024 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +declare_args() { + enable_web_discovery_native = is_win || is_mac || is_linux || is_android +} diff --git a/components/web_discovery/common/features.cc b/components/web_discovery/common/features.cc new file mode 100644 index 000000000000..6eec658cfc8b --- /dev/null +++ b/components/web_discovery/common/features.cc @@ -0,0 +1,14 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/common/features.h" + +namespace web_discovery::features { + +BASE_FEATURE(kWebDiscoveryNative, + "BraveWebDiscoveryNative", + base::FEATURE_DISABLED_BY_DEFAULT); + +} // namespace web_discovery::features diff --git a/components/web_discovery/common/features.h b/components/web_discovery/common/features.h new file mode 100644 index 000000000000..45db0afd2555 --- /dev/null +++ b/components/web_discovery/common/features.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_COMMON_FEATURES_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_COMMON_FEATURES_H_ + +#include "base/feature_list.h" + +namespace web_discovery::features { + +// Enables the native re-implementation of the Web Discovery Project. +// If enabled, the Web Discovery component of the extension should be disabled. +BASE_DECLARE_FEATURE(kWebDiscoveryNative); + +} // namespace web_discovery::features + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_COMMON_FEATURES_H_ diff --git a/script/brave_license_helper.py b/script/brave_license_helper.py index 61d8fe044a3e..6d88e10c714f 100644 --- a/script/brave_license_helper.py +++ b/script/brave_license_helper.py @@ -48,6 +48,7 @@ def AddBraveCredits(root, prune_paths, special_cases, prune_dirs, os.path.join('brave', 'third_party', 'rust', 'challenge_bypass_ristretto_cxx'), os.path.join('brave', 'third_party', 'rust', 'constellation_cxx'), + os.path.join('brave', 'third_party', 'rust', 'document_extractor_cxx'), os.path.join('brave', 'third_party', 'rust', 'json_cxx'), os.path.join('brave', 'third_party', 'rust', 'filecoin_cxx'), os.path.join('brave', 'third_party', 'rust', 'skus'), diff --git a/test/BUILD.gn b/test/BUILD.gn index 91f98cd195e6..5c3f1fad0810 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -17,6 +17,7 @@ import("//brave/components/playlist/common/buildflags/buildflags.gni") import("//brave/components/request_otr/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/web_discovery/common/buildflags/buildflags.gni") import("//brave/test/testing.gni") import("//brave/updater/config.gni") import("//chrome/common/features.gni") @@ -376,6 +377,10 @@ test("brave_unit_tests") { ] } + if (enable_web_discovery_native) { + deps += [ "//brave/components/web_discovery/browser:unit_tests" ] + } + data = [ "data/" ] if (enable_custom_background) { diff --git a/test/data/web_discovery/credential_keys_and_responses.json b/test/data/web_discovery/credential_keys_and_responses.json new file mode 100644 index 000000000000..9fbc3cce99e8 --- /dev/null +++ b/test/data/web_discovery/credential_keys_and_responses.json @@ -0,0 +1,10 @@ +{ + "rsa_priv_key": "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCsD7x4pzqvukjWYyPQiH4KTgxV+ep6hsW+3V3z5vo0M86a4fXcvPnVJ2N4doYz3CsC4YJWftZKiLyY1h/e8ZB9ERd6bkaxRD6lXg26Gry/YPO+gFlhszolLPnFxYaBtTTq4Pt34xseD5jQkiTNXSYNSbpxX/ZNiCn/C55IcUWf7LL6Buufr1ckqz4GIFPclV3+W1+qCb82I3ToMjluKIcApHjXhvZmw7EPogAaxFHXWJmixAE0EVIesBR+DGlPZqqDayiOw2BKZl5zlzvnL04AGyFX7/BWUBZthJuNb6whY20WoU0pZTsf92W0AFthafW4CqPqGXK+QQe4fbtG7f+dAgMBAAECggEAHiUbph/WXldKz5TK/4wKWQ/XhXClrhXSq1/pSAQdreuttOEFzEinlLqz6LULSia2umh8B19td92A/V32c37rC55k+KQ9am1EdICH8yUgEH+R9LxT7JQUCdNZZ1b1+9+dh9Em/Zgidh/RbClOnVRGiGl0asyfQHIsuWx1rMd7pUrvBufy/bEFbcEio7r009qM8CxluS6zRmXO4tWNoZFu42JYjJcEnU+a1mRJ06n1GaVJ0dLd1IA5g4pvVF8wy6R3bQb9xRiwLQMBuG0vLhm69YeyJwHShE5S4WqsubtcxkiZrPak7MLlzwsxk1wU52PbTk1T3geqH5JIerwJ5g7CGQKBgQDq17z9e9OedERboUgXU/nZpSWOqZp5Ukw/dx2xWVtMKhGg6wWoIvadDEeke9RB6JbAlSmBcvT5A8D8P0/1OiatK0i2Yyj1MzuxACnJkYaPjp0Xl4wpZJH1t4QkWEpztPx/JdY6O08WYAbdl0GJhNbL5rqCOS3FZhLpj9OH42IRgwKBgQC7kA0nfifnRRoiL2NwVLeAuG2O4b8JfRPehLw63kl/C4VVbZt0P99EqZwUuKv/nvlrvAfBinwhXvtWhX3GvLUnAQm5eGEt1BMd7GJaIylHFpmlt4+qcoyD+jyd0Jbq4vwiNiupZSZctqRmbu9rLdkoEzSGkkvyY6GQbJxc/ZiAXwKBgAJSb8Px3X3LmIFvbs8MPYQxZdWrR6O7dJWMD/cY8xYltFbq+/tVnSqgXHT75HViX1s4HljxUgrERrw3xAqgsJE1xFpJULZb81MktUUQ80uoFVWOYgxmuiq7zcquNM5AE98N+LhKrdWCzY6TWEqLzbPmbCGtfw5cnANDMMw/K1ERAoGAaLrMvYqR2W8aYpA3ZBfJxxQ0CJ5Av5mZqJxRRkWsoEXck5D6RnULxBk4z9E2KSupdeCuLAGZwkB48xzi2D+yny7TMT7odGCAtCqz2ETd3ZXfAUt36uK/V0o44p4ARvOreabpxlJ2kzpgndm/0gbtxJTEtYem5JeBNVWQEdSAfN0CgYABJjSTY6fDJ1Cj18eiTWY4Tq2phUJQedhZr7/m6e/OZCi40MBiBZ8rdpqqIJzwIVqeei3CTvgZGPa8uKBjMxFZ1ikfNe2+/MA629Cq2FfSNEw1k/+w8uMUMet/Zb+mJ2a6HGZdNfck/5STVYkjKVoIx9jagBBaG1gTbkUcA/XA8Q==", + "rsa_pub_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArA+8eKc6r7pI1mMj0Ih+Ck4MVfnqeobFvt1d8+b6NDPOmuH13Lz51SdjeHaGM9wrAuGCVn7WSoi8mNYf3vGQfREXem5GsUQ+pV4Nuhq8v2DzvoBZYbM6JSz5xcWGgbU06uD7d+MbHg+Y0JIkzV0mDUm6cV/2TYgp/wueSHFFn+yy+gbrn69XJKs+BiBT3JVd/ltfqgm/NiN06DI5biiHAKR414b2ZsOxD6IAGsRR11iZosQBNBFSHrAUfgxpT2aqg2sojsNgSmZec5c75y9OABshV+/wVlAWbYSbjW+sIWNtFqFNKWU7H/dltABbYWn1uAqj6hlyvkEHuH27Ru3/nQIDAQAB", + "group_pub_key": "I8s5zyMT4UHmqR4a99qBnZWXcQt8l4sePztiSEudSeUZqfnUZaWvV8MmeI/Wo0QwKpFNoxYabxfoqXr26KgDLyRdJc5/pCH4o6v714iE+QqNWQDItrGff9l2UcSYI0QhDugl3PWLCswyqeQjLYZkCIk/Qo4iE5eOmW9cM5TsxQ8DYk2USWuMtz+h72xmfomQo28Fyb1GtdQU4LWxfXMLUR3ttXPoJ/IYTxeZNfFuDWGUa3MWdlAJLgdWNsWaDyPiBgzntlxiw5q5xdCTJkmNcNzbZlVRHeng+ZX41gxY4scIP0+pMVtPQFj4NFAFAxVjMl4Zlrvzuy6+Iut3LYLBxBQG1TiY2dANuXJlvZbOC8jUsgeZvKZnwCG/ljnaZQ0CGyF0/ydNadCsSLPFfTiecSJkXL1Pi5NLxzacG/4QBPIhy4VjU4Gg17HNzbYuKljCvS0jAbYsuDEHqi7Z4MXTpREafV3WTE9yGdB2zE/RMRklUNukcAC1ayAjb4tIw6yD", + "join_responses": { + "20240622": "BAh+HPSF0v2NgWIsqbaFlZokWS5+yuzg0WJAcT1AlAZwIz5/e7n2v7P79pa3LzKjklJ4xUM/WZiujbNZd3gfX5QEFS55KwibK5dEdYMTzDiHWMU4OSIV7p/ZWmjs61Apbx8gnckDONbUOrf4FR2Y46SMp4/VxaZju+S6qBVFdbgqdQQG85r4caFZa56KdERRvy19pfnCdYTDvKedWs7V8xc6LhXjMbFIMlRv0ExlONRTtD3NqOynppR9UCIZmVyaQ+FiBA+uiU9RkwWpAvg2oTAWyS0XbgV6xFe2YlY1mvbpX2ZyEPS+aCmbJo/8/X0piHvcd01D6ytLZfUKQ3qFcoh2mZUGRo7N4bow9J5+dHZkSDQN/r94XLA4uJrTM2dxeFeNAyNlEuSYnxzA9YBRF6YyPuRlWRHXxhSlqO0rbspV+fEX", + "20240623": "BAms4nX6G89VWTmPwwy+sMc9H+ymwfTeBGbtkle0XByTFtkhnki68EGUgfgrYzOQBffl7XlMvIfmtV9nTPr6GF4EFpldOPmsuo9qj6xcL5sSKt5UJh2GrKi2Xd1pXtsU3b0gRy3TCQfxrbLN9UtJTlgazIraaVLOaD9mNiG2A71s8gQk0AdA+70haTw/ATXuEn0nQwVxNs/ZQokykOJ2d3GUmh7VG7Hwdylc/kwQxBqx7G3qILydEifJ6rI9XBFiYjSUBAutyyC9rWtcFLspGoyeb26/8vDtR0xpB6EyNSRmuiYKGHzY4FJPI5LzWrbawR7EDMfQmX2d4UbH3s6tbAK7we8BdyNNdafqz7Lu4FMS8FzI6dVsO/kkxsgK8ihAgCYpBxOTcw5QkXxHzbyMfMv04hJ0GM0xpjAtqe32r197jvC7", + "20240624": "BCHYKUHSD/1fgTW15TKBdgdT15gxg3vVwycHfZfdwhT9DTLp6o/AoAq31yBKjCdS2YOyR+w1jU9XTIA5fhaeWxUEA97oWXnmVn1A+K6laHCRvi2XvBl8JpZHX329Z013j1oKVPhnAoAPpk3l365bQ5YTtl2xTzRtYRGjkGs+VtKvWwQSkXXdd7SotMuCuMmigtm35FYzv1Y6u3Xc5DbTAfL56BFXKGErbIfHXsoO6XsDgp0wDMMsDsLiMFqwSpzNZu48BBc1Cx4hYcLGAiJAHArLDAJhtQReTBQy0rxEjB+/ZIBCB+M54YCaL8w1Ug71oXxzHrMlxq9Ba1R8vSaXxtUhb1MWLRJM7HftXZsekyYF7XzCyjZCij56fl7Sm/N0NPoPnAOQJ3joU8LCUWJKTvaxBE5SpcVXauvY8jm3xUSoEtIz" + } +} diff --git a/test/data/web_discovery/hpn-config.json b/test/data/web_discovery/hpn-config.json new file mode 100644 index 000000000000..c4d335233ca9 --- /dev/null +++ b/test/data/web_discovery/hpn-config.json @@ -0,0 +1,146 @@ +{ + "minVersion": 1, + "groupPubKeys": { + "20240624": "ITmwC7dQpF5vcMFbe8U/k7hdg7ZSWXsOswfkcxm7nJoBsx1c6O+kJcyiXIZR08t5Nf64tUOwptEDqxWkIWLToQ0S2G21Yfh9Vif0+zAb45f1m3+9plm9sKncHFNuoeJZI/f/vQfeo8yd/jpqCKMXNY6VWWlEMESYIeIsLZtUxPUOx6lQG09iRtrvHUHVGlaN5VGAKNoTVFfiCgNl373iZR1k0ZvW0wdA7OkJiTVj3+osawlfpeYhZSmdWo7qsZ3UDkoy9faYFBGsTScQJsLsgQh72gQBa3VgIWPWt/lLn38QbqymAMc/OZsf11hjkXP59t9V/9Ugv7vYFsAxRT9SLQR8eB8sATtrQ1DmqCesvhdCpOIftZOfn70u4S8pYrXGHTyXNZBSYo6CG0nj0cJOfXK9wrm/PeSXB5wdLQSLzqoHrzR8cEuJEbWW1D/4lAVGMGQTMOLLOcxo0n9nH+QG6iNoT1aIaPjmn2QwAx/ohIEH53YKkxp0DNmTDQOETI/k", + "20240625": "DSoyIdA3VA6CjOhWEBh7ZCreAI2nOYD2JUUynaP+prUgM/GOl68av5u+3KFRhrrITKwGDOLzPV3wCM/FRkG1HxINxQRHFNOgcE9qKbc75LAmjIYuoAE+VqBdmvlYXzzsFyKCFhytVsiZ7DLAPX9fmRdkxJidA5KEF8k5EzOKtXkLp/PzWaFqDsif3zSsm/yXkQOtjfd7XhpJ0kmYEuhS0BY9FeuCGvkIhvW1zEf7WDVaty06yAr2QlpsPDHd604fGlrzNh/41PiXsiWtWLgKG1MLRvt6yV0jnfhF2Okawl4ecdl0CkSvxiusZyKgLSKVSiEP00fY2BR1fYyH/DL3fQXrAZo+MhY91V3M55oxsnliSxLRtcU559rNokQuTKFXAHiVTnDPI40Def11OLLPp/ZQgICtUa/azW4cu6sEkY0KE8ebJDplsqM5ehsMh5RZeVm7NJZEret4z6hFtm45jhgyEDoQMyv5Mn86X0q8QtAdM6wKpVRU2ymy9S2DdqKt", + "20240626": "FuqBHl3PCRXlhHSqFSrQBhCOzgmZQDCZLR27U/a+AlUj6H+XXoN5jroQTKJ091Rb7W360aIgS+I4CLCQgZS+ERCEikPh5WNlvLV1UpQ6pDtpCFZagrirsLQLx+CEqEojC436N9vC+OOM9Gg/gZWuBliAQRkDwzlwhJ+at/qFZeojH+NSvCDdgUVIgkw60BxDNFrOFoJknfxXf7B6m9pjYQ+ZDGppdvJMrhMiARX1jMce72QbzSlSxbzRE1PK951gJIKICdDjY51hP7aQM9WqSswd8LNEp741yAVyFd3n4aEVSaYUgzykpzzTIGf7pVOt8CQXqBzJZzTc6/+1WuUyfhDNFEnO3Ep4yAb3H03Mv9SXBSwGaoucaL8kGRZfSyMUC2idJUQKbg6VOPXAEE2LMfSQQzz5Zgl6GSROv45QgRIMhXqDLdiSknFfmUQUKFkMi7oBN2JYesemz8QIIyw0px90wMchsF23n7EdtVVBj0E7HVj/uMISTIxeQY7mv27n", + "20240627": "DTwg0yh8wrUSKWVXlhp879+E64ART4w5Drt47ezgCdIb0HgZeU3iE2pc8kMb+UarK0XWA5uC0jA6vrkXJlcNVAY1sEl7NO/V8eDI5EavwbXiTJzBlCJabHeeanE7yLMTGXofjgxYUvGjAiompViQsaQH+6hAh96LNxBmL8lnGscRjn6t12/YeaiYui/fvmGfjf+05bnz4gcKl8U+3k2lQg+t9jY8qzzNJZOwk4FutovPBWwDcu8Mms2Z2K1+MErJAkGjP2wrNDBqgQm2s0hdQDNhOPGKqLv1CnPsLf+5EZEF6FYvU3e71n0fX9y8pGBMVRvSW0zlqbF9l/W7+oM4+g3WHQrPirJq4v7D7OZ4pjgpLTHjxGms7qhOqJHrEbCzBBtgR1MjKvPj2lIjGld+TE2JznLavqJ9aKhxc5EfoZAczdWSIR+qbFpt7RcyeTpNIdZWQkDG2sEavlFXWfzb7gn5GZwg8YPL4XKoSyErqKl1TvIuGws/oehTzNe4Nz/B" + }, + "pubKeys": { + "20240624": "BECQDFoOR0DE3wLaDidGAC/2Mpgjasf9QgJDGGLTkTdll+pW2S/RgX0pkFyDjQZc6efyX3RGQKJ2cq8HOB8vZOo=", + "20240625": "BEURJ7UWSqV/OsxhCDyoQtAX2F5Vm/CKh6NS8SjIvz5RWtVOkyfEnR+XkdcldmyQk2AwOhLMEV/ZIwbThVmvmrQ=", + "20240626": "BDjEnHSmPhNywOIvDo2kWAzz7S/Cf2xDZEjZTTOp0FCb/Qk6uoSb8R7PF8Ne2RAMkCifeKIUtR1mF/Enmb+VFBM=", + "20240627": "BO7isKljTTltnCBtM4hRUV0fcxlH4kjvWVVummU2irqq6i/jLSgF+lR+/qOL4fqrzCsBJnIn8lTvsEOAfzg9XDM=" + }, + "sourceMap": { + "actions": { + "alive": { + "keys": [ + "t->url" + ] + }, + "page": { + "keys": [ + "url->url" + ] + }, + "query": { + "keys": [ + "q->url" + ] + }, + "query0": { + "keys": [ + "q->url" + ] + }, + "query-am": { + "keys": [ + "q->url" + ] + }, + "img": { + "keys": [ + "q->url" + ] + }, + "img-p": { + "keys": [ + "q->url" + ] + }, + "video-p": { + "keys": [ + "q->url" + ] + }, + "videos-p": { + "keys": [ + "q->url" + ] + }, + "place": { + "keys": [ + "->obj" + ] + }, + "places": { + "keys": [ + "->obj" + ] + }, + "places2": { + "keys": [ + "q->url" + ] + }, + "category-am": { + "keys": [ + "curl" + ] + }, + "ads_A": { + "keys": [ + "q->url" + ] + }, + "ads_B": { + "keys": [ + "q->url" + ] + }, + "ads_C": { + "keys": [ + "q->url" + ] + }, + "snippet": { + "keys": [ + "q->url" + ] + }, + "snippet2": { + "keys": [ + "q->url" + ] + }, + "snippet3": { + "keys": [ + "q->url" + ] + }, + "linkedin": { + "keys": [ + "profileLink->url" + ] + }, + "hw.telemetry.actionstats": {}, + "locdata": { + "keys": [ + "q->url" + ] + }, + "ads_D": { + "keys": [ + "q->url" + ] + }, + "usercontext": { + "limit": 0 + }, + "sq": { + "keys": [ + "oq->url" + ] + }, + "ad-ctr": { + "limit": 0 + }, + "top-stories": { + "keys": [ + "q->url" + ] + } + } + } +} diff --git a/test/data/web_discovery/page.html b/test/data/web_discovery/page.html new file mode 100644 index 000000000000..d987ae3ed875 --- /dev/null +++ b/test/data/web_discovery/page.html @@ -0,0 +1,21 @@ + + + + Test Page To Scrape + + +
+
+
A query
+ Foo1 +
+
+ Foo2 +
+
+ Foo3 + +
+
+ + diff --git a/test/data/web_discovery/patterns.gz b/test/data/web_discovery/patterns.gz new file mode 100644 index 0000000000000000000000000000000000000000..966ab484915258f96cb9ba8b620416ea617ff024 GIT binary patch literal 3762 zcmV;j4o&eNiwFP!000021Kk{JbK5rZ`~3=yd=uNTNF{a>C+;}DJepkEyj?d=6h;G* zkjR)X0gz<5&40Pya=+|$0a79>@M~JEcr(>2$@g3=FuO`A@kl6 zAt(!Y^QhDO{8{gZVc(;0q2D~|Y@OWr$=KN$9CyClF!pZjsGr@N!t{?_ZaKSu?j)D9 z#<=(HZnM?=;s_D(qfXnvzb|d$W=A&^3bV&vK}2DmDTU@wh8-e_}5nB{a%j2(Ti zbHms@K>+^*2ryn(jrxQ*VVItElZh0i_s9LnAnBN0VSxrJXn zP@E16J$n2(2K2dUV8Z~vTtWTjk?hoPgJ>u^0r9cx0DHBXVuGOJf$xTtP2eVt?~aH! zr0_q?>otsmJA>x8Z{LtM0BwhXc<2*hIS!@v@5 z81P3?wx1|(=d>P^3&G)FP}|%8=3e1UqA0HKQvvBY6^H`i}$QR z)dL*7}0*%E=@PvOy}m6)L+ zjHSSHW@UiGSg(oP8hW0(H3MXUhR{GXoP!2g1W(XQkpb8X5L`^+OR#QLAv%oqG7uw4 zpfzL+hJFZHA;d^W{RL}Emr?pe_0E8|#!_7@0ZazK$#23hK>{23JOdsMUsJBvQ)4VU2Q$R+s8PcTxOVF%8*R3&`^xOd4dtWmd=%>Yo zzRA0&v;G&pG zMtpQjC{-?<(Q1hRV<9@}EMQGbO&mMKT$MlQ(|}ypenp}PV8>BSvmd%L1j4J4421AX zMngx<37Wt=IOX8NmeJ8s6Jt16MoH>g21{Vca2ogJg%Um&%(cY-MVMVkahWi>6Ag?X zEt1|yl&sVo3}Aa2*C>m1)N>gZn&r5joe5{^S~96?gV+`%zl#mcTui8L6o!R9;TI4U z_K3%+`c{GACd``tJ`ksVPcDy?brlRuyFmM1KKK|eqMyU1cm}O)m?Wfq&DL$S3e(#+ zh#&pL$?=)I7@>W-mF)nHc+UWWS(;4!*#q}U+_3m}R#z{`W@cZpdS zphzW4TS|52!Vbp6Ri0S-eZnV!rOBWz7dH-8GFsaHBjmI3W2z-`36hPxT7uKp1BZOt zw<_snPRJ=J&Or?kLoh&tEW9Lpion~#2!hPYgj`wRgt!OyUI{+)6! zVh1qnZYCC6z&0<9Qm*=5)pg=6z=`0U^tz57UG6_O72m2NaTei7F%-pNo1D!l;CJNS zv*+F^v9F7T(ltE@&quwD@(*1!(LZ<-`wg8k?>le|r}O5Nou8XEy+t-Kl7 zBp*Aj{pEOj6nP}tHu8KdXY&$6ws_Ej2r@P;W++7Hmov+%IkJYvwXF|P6fDL+>(g?MO#wo+r9pwTYscLr|(f@BYG^YPpxc5~jLmyG#Yf zJicEV4PZ!nfF#+#%)$$;i`gD}tRue!;}1#r`t#)R`=Kc>-QKGQZ(_%UJGy}=&hlc- z#m%ILjiUg9c)5?Xb9=Wc%kh7C21M;@s17Qlk~lEV4|vWbUO>9hGc`+G>c2id_wUeF zLkBgA6O*Y0Awn?MHtIQU#5zp{A-~k0Pae?^lI>DjN)~4?PcJ9ERwLglUfK8G^k2}T zJ%Kn++hku18|rqF$qiSkRtQt`QVjB=M{)RM)TM?rTW#Kw}-zaDZKvSg55id z?`=kX@`(0SX0|tvuhm%Cm#iLLPXpzNHgV+!3eV&&f{Ytmy@+v7@4tNT?@3ko01beL z8JGwT=E5WDdEJn?{i;Ai2~>5Of%S%6pF}NO8(+B}+SEI?&%(=3meHAcX1Ml*5qH8= z3)ZwpnIX3^`kL$a@%+s1t8RE9*BOMa76cG&K)~DE3dq8ZBrj*e@%Ad!Zm5($5ky1( z{UUn)*vvZ=-8!eXW5w}w|5jwzawx>MS#G&LdHup1CH6PA{ZtUw<8W^-L)*h{(k94U zC?36AX}J2XY_yQ{v}5UO+(IGZnV3abIONom5|kmI4OiXykS~YJ6&}s|NM#sPu1INu z7m+?)0dLl^0H~60Y~#lswfj_)9+c?WcS8F2;L4Of(!u60$9T?Y=$Eesk!j~i zL^QvCB%innX+B+^9zHeeM}k#|lzJ9vV+U3sDx?;278TK#2`=>0x1X-=nGI(@9cDo2 zBUj3jP4r6Jz12`EbD_VudVPABY#+TFJimQvs|{MSs{&MMkQ5Ba1dQEkx;}3ibL3oD zM{QV~a{qUFafHJt=SZe>$>aNjN354P=$Gv3oYgaQu=+x4J~f1yP2CYBwH9qJPT7(H z*|iv2tB(eotvi`r(0|R=CUbaf)iI9usE;QsY@#e}K?$vWLG{ z-99apkwiUy`tj1S(gcD=p?0Hq>Wt+a$KvE87DlAE1JaDYI&@crAZvCH2A2I zoTQ9YRrex_QlwNDJi?xC)*GT*??w)&+L8Q5q7(!>uJN=FBBw^^6IHeijk1Du z!XcB~6H*D|0-+ff{-{Q&9F(d9w{4wx{cz4y9W|?( zBMsX;d8k13oKNf|oIZ^6G&cxT5}%7NoPAjzRy%$r>77r4g;yTI%BvA=z!cz+ zT$R-$Kxe&5r=s17Qghp6*%9d6oR?|?oa5=%@FGdYk1=YpEPP&4>+FzyzZtf!_jvYu z8iV{}C2Q8cS9^FwNjvq>NKM4@VMh>3JpDoP;7C%P4urtxYv=}r&yyq-*R!JGYSWN= z;XL>FOI%>Lh_nRRO49b@C?;0{TZzD=TZ+Cc#2gi?h|f{Ew1%%5r2wY;D96bKu-B$} zaRFU5nnf6CT!hS}#X2yDVN;Z+E7dkk@)Y#iwC1bAHbrO#?z&XY)m&BA+9z51vJlti z;);r_>f}Dnv2z)Mb*Z108&#sx3oRrgOQ!@?g&t$`bv@Ol!2>sns3=}$o1kc2>1?K? zrWRB73w+Xgl&rnbN|fc)n(9GL0aoGJkdH_*)`~O_a@5L#n-<=e#KixGg9Yn-`rt%0 zvlN7ZRuHNvaCwFlK6O-{J*onyI+5}OlkYDN_7Vn3(B=_W2e%kIF&OREvlFaRHCSi92eICUHfE~pkGCK z#bTrC8Cqq*lqabC+~(_pzUM@;*vnD9uxhAAj$DuOLJC$Azr5_D+Cm6=A#Q9|qCG>A z%=i})lo-RMUF6B?%3i}2U9PpB0^gP~6^c=(%%HlnLD>|fhGmh?SYK(-u+AVnw68HN zCHP9?rcScrI6R`RF)XS)Xx+nOBvsaF)(w*jkB`^gS(^gOS*ZAgwmfN-k1^NVdtHfQ zE+vT%#LCa;o|@5N>irh$o$*wc)%<&k)} Date: Tue, 6 Aug 2024 17:37:17 -0700 Subject: [PATCH 2/9] Address Web Discovery feedback --- android/BUILD.gn | 2 +- browser/brave_local_state_prefs.cc | 2 +- browser/brave_profile_prefs.cc | 2 +- ...browser_context_keyed_service_factories.cc | 2 +- .../api/settings_private/brave_prefs_util.cc | 2 +- browser/profiles/brave_profile_manager.cc | 2 +- browser/resources/settings/BUILD.gn | 2 +- .../search_engines/search_engine_tracker.cc | 2 +- .../search_engines/search_engine_tracker.h | 2 +- browser/sources.gni | 6 +- browser/ui/BUILD.gn | 8 +- browser/ui/webui/brave_settings_ui.cc | 14 +- browser/web_discovery/BUILD.gn | 4 +- .../web_discovery/web_discovery_cta_util.cc | 4 +- .../web_discovery_infobar_delegate.cc | 4 +- .../web_discovery_service_factory.cc | 2 +- .../web_discovery_service_factory_unittest.cc | 3 +- chromium_src/chrome/browser/DEPS | 2 +- .../flags/android/chrome_feature_list.cc | 15 +- components/web_discovery/browser/BUILD.gn | 4 +- components/web_discovery/browser/DEPS | 1 + .../browser/credential_manager.cc | 185 +++++++------ .../browser/credential_manager.h | 52 +++- .../browser/credential_manager_unittest.cc | 24 +- .../browser/credential_signer.cc | 12 - .../web_discovery/browser/credential_signer.h | 4 +- components/web_discovery/browser/patterns.cc | 38 ++- components/web_discovery/browser/patterns.h | 4 +- .../browser/server_config_loader.cc | 260 +++++++++++------- .../browser/server_config_loader.h | 28 +- .../browser/server_config_loader_unittest.cc | 39 +-- components/web_discovery/browser/util.cc | 4 +- .../browser/web_discovery_service.cc | 12 +- .../browser/web_discovery_service.h | 4 + .../{common => }/buildflags/BUILD.gn | 2 +- .../{common => }/buildflags/buildflags.gni | 0 components/web_discovery/common/BUILD.gn | 6 +- components/web_discovery/common/features.cc | 2 +- components/web_discovery/common/features.h | 2 +- test/BUILD.gn | 2 +- 40 files changed, 455 insertions(+), 310 deletions(-) delete mode 100644 components/web_discovery/browser/credential_signer.cc rename components/web_discovery/{common => }/buildflags/BUILD.gn (85%) rename components/web_discovery/{common => }/buildflags/buildflags.gni (100%) diff --git a/android/BUILD.gn b/android/BUILD.gn index 8fcd7824dfdb..e8c300338259 100644 --- a/android/BUILD.gn +++ b/android/BUILD.gn @@ -5,7 +5,7 @@ import("//brave/components/ai_chat/core/common/buildflags/buildflags.gni") import("//brave/components/p3a/buildflags.gni") -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//brave/components/webcompat_reporter/buildflags/buildflags.gni") import("//build/config/android/rules.gni") diff --git a/browser/brave_local_state_prefs.cc b/browser/brave_local_state_prefs.cc index c06b4a8e687a..2bd64b7a2d1c 100644 --- a/browser/brave_local_state_prefs.cc +++ b/browser/brave_local_state_prefs.cc @@ -37,7 +37,7 @@ #include "brave/components/p3a/star_randomness_meta.h" #include "brave/components/skus/browser/skus_utils.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/common/pref_names.h" #include "components/metrics/metrics_pref_names.h" diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index a87561c03f13..8aa1659e45d4 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -50,7 +50,7 @@ #include "brave/components/search_engines/brave_prepopulated_engines.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/browser/prefetch/pref_names.h" #include "chrome/browser/prefs/session_startup_pref.h" diff --git a/browser/browser_context_keyed_service_factories.cc b/browser/browser_context_keyed_service_factories.cc index dd05c70346b2..b83eb6e3e30a 100644 --- a/browser/browser_context_keyed_service_factories.cc +++ b/browser/browser_context_keyed_service_factories.cc @@ -42,7 +42,7 @@ #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index a5cebc019e78..119639f19050 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -23,7 +23,7 @@ #include "brave/components/request_otr/common/pref_names.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "chrome/browser/content_settings/cookie_settings_factory.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/extensions/api/settings_private/prefs_util.h" diff --git a/browser/profiles/brave_profile_manager.cc b/browser/profiles/brave_profile_manager.cc index c545038c542a..fed51ca57853 100644 --- a/browser/profiles/brave_profile_manager.cc +++ b/browser/profiles/brave_profile_manager.cc @@ -26,7 +26,7 @@ #include "brave/components/ntp_background_images/common/pref_names.h" #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile_attributes_entry.h" diff --git a/browser/resources/settings/BUILD.gn b/browser/resources/settings/BUILD.gn index 2905c82c9241..f04eebb60c44 100644 --- a/browser/resources/settings/BUILD.gn +++ b/browser/resources/settings/BUILD.gn @@ -8,7 +8,7 @@ import("//brave/build/config.gni") import("//brave/components/brave_vpn/common/buildflags/buildflags.gni") import("//brave/components/brave_wayback_machine/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//brave/resources/brave_grit.gni") import("//chrome/common/features.gni") import("//extensions/buildflags/buildflags.gni") diff --git a/browser/search_engines/search_engine_tracker.cc b/browser/search_engines/search_engine_tracker.cc index 95246343cb19..693db739a231 100644 --- a/browser/search_engines/search_engine_tracker.cc +++ b/browser/search_engines/search_engine_tracker.cc @@ -248,7 +248,7 @@ void SearchEngineTracker::RecordWebDiscoveryEnabledP3A() { #endif #if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) if (base::FeatureList::IsEnabled( - web_discovery::features::kWebDiscoveryNative)) { + web_discovery::features::kBraveWebDiscoveryNative)) { enabled = profile_prefs_->GetBoolean(web_discovery::kWebDiscoveryNativeEnabled); } diff --git a/browser/search_engines/search_engine_tracker.h b/browser/search_engines/search_engine_tracker.h index 25cc168e4572..cce08e4f513c 100644 --- a/browser/search_engines/search_engine_tracker.h +++ b/browser/search_engines/search_engine_tracker.h @@ -11,7 +11,7 @@ #include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "brave/components/time_period_storage/weekly_event_storage.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "components/keyed_service/content/browser_context_keyed_service_factory.h" #include "components/keyed_service/core/keyed_service.h" #include "components/prefs/pref_change_registrar.h" diff --git a/browser/sources.gni b/browser/sources.gni index ee58cb8034aa..b7fbdf0d1b77 100644 --- a/browser/sources.gni +++ b/browser/sources.gni @@ -52,7 +52,7 @@ import("//brave/components/brave_webtorrent/browser/buildflags/buildflags.gni") import("//brave/components/commander/common/buildflags/buildflags.gni") import("//brave/components/greaselion/browser/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//extensions/buildflags/buildflags.gni") brave_chrome_browser_visibility = [ @@ -215,8 +215,7 @@ brave_chrome_browser_deps = [ "//brave/components/speedreader/common/buildflags", "//brave/components/tor/buildflags", "//brave/components/version_info", - "//brave/components/web_discovery/common", - "//brave/components/web_discovery/common/buildflags", + "//brave/components/web_discovery/buildflags", "//brave/components/webcompat/content/browser", "//brave/components/webcompat/core/common", "//brave/services/network/public/cpp", @@ -365,6 +364,7 @@ if (enable_web_discovery_native) { brave_chrome_browser_deps += [ "//brave/browser/web_discovery", "//brave/components/web_discovery/browser", + "//brave/components/web_discovery/common", ] } diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index 36790fea8178..8d233f2a5a60 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -17,7 +17,7 @@ import("//brave/components/request_otr/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") import("//brave/components/text_recognition/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//build/config/features.gni") import("//chrome/common/features.gni") import("//components/gcm_driver/config.gni") @@ -835,6 +835,7 @@ source_set("ui") { "//brave/components/tor/buildflags", "//brave/components/url_sanitizer/browser", "//brave/components/vector_icons", + "//brave/components/web_discovery/buildflags", "//brave/components/webui", "//chrome/app:command_ids", "//chrome/app/vector_icons:vector_icons", @@ -903,10 +904,7 @@ source_set("ui") { } if (enable_web_discovery_native) { - deps += [ - "//brave/components/web_discovery/common", - "//brave/components/web_discovery/common/buildflags", - ] + deps += [ "//brave/components/web_discovery/common" ] } if (is_linux) { diff --git a/browser/ui/webui/brave_settings_ui.cc b/browser/ui/webui/brave_settings_ui.cc index 88883cbb3718..de31ce02cfb4 100644 --- a/browser/ui/webui/brave_settings_ui.cc +++ b/browser/ui/webui/brave_settings_ui.cc @@ -45,8 +45,7 @@ #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "brave/components/version_info/version_info.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" -#include "brave/components/web_discovery/common/features.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/settings/metrics_reporting_handler.h" @@ -93,6 +92,10 @@ #include "brave/components/playlist/common/features.h" #endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/common/features.h" +#endif + using ntp_background_images::ViewCounterServiceFactory; BraveSettingsUI::BraveSettingsUI(content::WebUI* web_ui) : SettingsUI(web_ui) { @@ -195,9 +198,10 @@ void BraveSettingsUI::AddResources(content::WebUIDataSource* html_source, html_source->AddBoolean("enable_extensions", BUILDFLAG(ENABLE_EXTENSIONS)); #if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - html_source->AddBoolean("isWebDiscoveryNativeEnabled", - base::FeatureList::IsEnabled( - web_discovery::features::kWebDiscoveryNative)); + html_source->AddBoolean( + "isWebDiscoveryNativeEnabled", + base::FeatureList::IsEnabled( + web_discovery::features::kBraveWebDiscoveryNative)); #endif html_source->AddBoolean("extensionsManifestV2Feature", diff --git a/browser/web_discovery/BUILD.gn b/browser/web_discovery/BUILD.gn index aa506028751e..1ec447b9c281 100644 --- a/browser/web_discovery/BUILD.gn +++ b/browser/web_discovery/BUILD.gn @@ -3,11 +3,11 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//extensions/buildflags/buildflags.gni") if (enable_web_discovery_native) { - source_set("web_discovery") { + static_library("web_discovery") { sources = [ "web_discovery_service_factory.cc", "web_discovery_service_factory.h", diff --git a/browser/web_discovery/web_discovery_cta_util.cc b/browser/web_discovery/web_discovery_cta_util.cc index 0a053023faeb..4bb473a53294 100644 --- a/browser/web_discovery/web_discovery_cta_util.cc +++ b/browser/web_discovery/web_discovery_cta_util.cc @@ -14,7 +14,7 @@ #include "brave/components/constants/pref_names.h" #include "brave/components/constants/url_constants.h" #include "brave/components/search_engines/brave_prepopulated_engines.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/search_engines/template_url.h" @@ -96,7 +96,7 @@ bool ShouldShowWebDiscoveryInfoBar(TemplateURLService* service, const char* enabled_pref_name = kWebDiscoveryExtensionEnabled; #if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) if (base::FeatureList::IsEnabled( - web_discovery::features::kWebDiscoveryNative)) { + web_discovery::features::kBraveWebDiscoveryNative)) { enabled_pref_name = web_discovery::kWebDiscoveryNativeEnabled; } #endif diff --git a/browser/web_discovery/web_discovery_infobar_delegate.cc b/browser/web_discovery/web_discovery_infobar_delegate.cc index b604262e05b1..6b8f934e6d31 100644 --- a/browser/web_discovery/web_discovery_infobar_delegate.cc +++ b/browser/web_discovery/web_discovery_infobar_delegate.cc @@ -7,7 +7,7 @@ #include "brave/browser/web_discovery/web_discovery_cta_util.h" #include "brave/components/constants/pref_names.h" -#include "brave/components/web_discovery/common/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "components/infobars/core/infobar.h" #include "components/prefs/pref_service.h" @@ -51,7 +51,7 @@ void WebDiscoveryInfoBarDelegate::EnableWebDiscovery() { const char* pref_name = kWebDiscoveryExtensionEnabled; #if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) if (base::FeatureList::IsEnabled( - web_discovery::features::kWebDiscoveryNative)) { + web_discovery::features::kBraveWebDiscoveryNative)) { pref_name = web_discovery::kWebDiscoveryNativeEnabled; } #endif diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc index 093226b23b23..87522d736a7f 100644 --- a/browser/web_discovery/web_discovery_service_factory.cc +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -49,7 +49,7 @@ KeyedService* WebDiscoveryServiceFactory::BuildServiceInstanceFor( content::BrowserContext* WebDiscoveryServiceFactory::GetBrowserContextToUse( content::BrowserContext* context) const { - if (!base::FeatureList::IsEnabled(features::kWebDiscoveryNative)) { + if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { return nullptr; } // Prevents creation of service instance for incognito/OTR profiles diff --git a/browser/web_discovery/web_discovery_service_factory_unittest.cc b/browser/web_discovery/web_discovery_service_factory_unittest.cc index b4717b1749e7..cbda07dfa63b 100644 --- a/browser/web_discovery/web_discovery_service_factory_unittest.cc +++ b/browser/web_discovery/web_discovery_service_factory_unittest.cc @@ -15,8 +15,9 @@ namespace web_discovery { TEST(WebDiscoveryServiceFactoryTest, PrivateNotCreated) { + base::test::ScopedFeatureList scoped_features( + features::kBraveWebDiscoveryNative); content::BrowserTaskEnvironment task_environment; - base::test::ScopedFeatureList scoped_features(features::kWebDiscoveryNative); auto* browser_process = TestingBrowserProcess::GetGlobal(); TestingProfileManager profile_manager(browser_process); ASSERT_TRUE(profile_manager.SetUp()); diff --git a/chromium_src/chrome/browser/DEPS b/chromium_src/chrome/browser/DEPS index a76c71b81b32..a4f2b0061486 100644 --- a/chromium_src/chrome/browser/DEPS +++ b/chromium_src/chrome/browser/DEPS @@ -48,7 +48,7 @@ include_rules = [ "+brave/components/url_sanitizer", "+brave/components/vector_icons", "+brave/components/version_info", - "+brave/components/web_discovery/common", + "+brave/components/web_discovery", "+brave/components/webcompat", "+brave/net", "+brave/services/network/public", diff --git a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc index c836c47732d8..8c79f36c76ef 100644 --- a/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc +++ b/chromium_src/chrome/browser/flags/android/chrome_feature_list.cc @@ -18,7 +18,7 @@ #include "brave/components/playlist/common/features.h" #include "brave/components/request_otr/common/features.h" #include "brave/components/speedreader/common/features.h" -#include "brave/components/web_discovery/common/features.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "brave/components/webcompat/core/common/features.h" #include "net/base/features.h" #include "third_party/blink/public/common/features.h" @@ -30,9 +30,18 @@ #define BRAVE_AI_CHAT_FLAG #endif +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "brave/components/web_discovery/common/features.h" +#define BRAVE_WEB_DISCOVERY_FLAG \ + &web_discovery::features::kBraveWebDiscoveryNative, +#else +#define BRAVE_WEB_DISCOVERY_FLAG +#endif + // clang-format off #define kForceWebContentsDarkMode kForceWebContentsDarkMode, \ BRAVE_AI_CHAT_FLAG \ + BRAVE_WEB_DISCOVERY_FLAG \ &brave_rewards::features::kBraveRewards, \ &brave_search_conversion::features::kOmniboxBanner, \ &brave_vpn::features::kBraveVPNLinkSubscriptionAndroidUI, \ @@ -50,14 +59,14 @@ &google_sign_in_permission::features::kBraveGoogleSignInPermission, \ &net::features::kBraveForgetFirstPartyStorage, \ &brave_shields::features::kBraveShowStrictFingerprintingMode, \ - &brave_shields::features::kBraveLocalhostAccessPermission, \ - &web_discovery::features::kWebDiscoveryNative + &brave_shields::features::kBraveLocalhostAccessPermission // clang-format on #include "src/chrome/browser/flags/android/chrome_feature_list.cc" #undef kForceWebContentsDarkMode #undef BRAVE_AI_CHAT_FLAG +#undef BRAVE_WEB_DISCOVERY_FLAG namespace chrome { namespace android { diff --git a/components/web_discovery/browser/BUILD.gn b/components/web_discovery/browser/BUILD.gn index aa3c8dc31878..dc19dce58ff2 100644 --- a/components/web_discovery/browser/BUILD.gn +++ b/components/web_discovery/browser/BUILD.gn @@ -3,7 +3,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at https://mozilla.org/MPL/2.0/. -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") assert(enable_web_discovery_native) @@ -11,7 +11,6 @@ static_library("browser") { sources = [ "credential_manager.cc", "credential_manager.h", - "credential_signer.cc", "credential_signer.h", "patterns.cc", "patterns.h", @@ -33,7 +32,6 @@ static_library("browser") { "//brave/components/web_discovery/common", "//components/keyed_service/core", "//components/prefs", - "//content/public/browser", "//crypto", "//extensions/buildflags", "//net", diff --git a/components/web_discovery/browser/DEPS b/components/web_discovery/browser/DEPS index 8a3c47daae89..290a8dab5740 100644 --- a/components/web_discovery/browser/DEPS +++ b/components/web_discovery/browser/DEPS @@ -1,4 +1,5 @@ include_rules = [ + "-content", "+services/network/public", "+extensions/buildflags/buildflags.h", "+services/service_manager/public/cpp", diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 562d3d18a90f..0c5ccca25dc1 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -8,6 +8,8 @@ #include #include "base/base64.h" +#include "base/containers/span.h" +#include "base/containers/span_rust.h" #include "base/functional/bind.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -17,6 +19,7 @@ #include "base/threading/thread_restrictions.h" #include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" #include "brave/components/web_discovery/browser/pref_names.h" +#include "brave/components/web_discovery/browser/rsa.h" #include "brave/components/web_discovery/browser/util.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" @@ -61,19 +64,43 @@ constexpr net::NetworkTrafficAnnotationTag kJoinNetworkTrafficAnnotation = "Users can opt-in or out via brave://settings/search" })"); -std::optional GenerateJoinRequest( - anonymous_credentials::CredentialManager* anonymous_credential_manager, - crypto::RSAPrivateKey* rsa_private_key, - std::string pre_challenge) { +} // namespace + +BackgroundCredentialHelper::BackgroundCredentialHelper() + : anonymous_credential_manager_( + anonymous_credentials::new_credential_manager()) {} + +BackgroundCredentialHelper::~BackgroundCredentialHelper() = default; + +void BackgroundCredentialHelper::UseFixedSeedForTesting() { + anonymous_credential_manager_ = + anonymous_credentials::new_credential_manager_with_fixed_seed(); +} + +std::unique_ptr BackgroundCredentialHelper::GenerateRSAKey() { + auto key_pair = GenerateRSAKeyPair(); + if (!key_pair) { + return nullptr; + } + rsa_private_key_ = std::move(key_pair->key_pair); + return key_pair; +} + +void BackgroundCredentialHelper::SetRSAKey( + std::unique_ptr rsa_private_key) { + rsa_private_key_ = std::move(rsa_private_key); +} + +std::optional +BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { base::AssertLongCPUWorkAllowed(); - base::span pre_challenge_span( - reinterpret_cast(pre_challenge.data()), pre_challenge.size()); - auto challenge = crypto::SHA256Hash(pre_challenge_span); + CHECK(rsa_private_key_); + auto challenge = crypto::SHA256Hash(base::as_byte_span(pre_challenge)); - auto join_request = anonymous_credential_manager->start_join( - rust::Slice(challenge.data(), challenge.size())); + auto join_request = anonymous_credential_manager_->start_join( + base::SpanToRustSlice(challenge)); - auto signature = RSASign(rsa_private_key, join_request.join_request); + auto signature = RSASign(rsa_private_key_.get(), join_request.join_request); if (!signature) { VLOG(1) << "RSA signature failed"; @@ -84,19 +111,18 @@ std::optional GenerateJoinRequest( .signature = *signature}; } -std::optional FinishJoin( - anonymous_credentials::CredentialManager* anonymous_credential_manager, +std::optional BackgroundCredentialHelper::FinishJoin( std::string date, std::vector group_pub_key, std::vector gsk, std::vector join_resp_bytes) { base::AssertLongCPUWorkAllowed(); auto pub_key_result = anonymous_credentials::load_group_public_key( - rust::Slice(group_pub_key.data(), group_pub_key.size())); - auto gsk_result = anonymous_credentials::load_credential_big( - rust::Slice(gsk.data(), gsk.size())); + base::SpanToRustSlice(group_pub_key)); + auto gsk_result = + anonymous_credentials::load_credential_big(base::SpanToRustSlice(gsk)); auto join_resp_result = anonymous_credentials::load_join_response( - rust::Slice(join_resp_bytes.data(), join_resp_bytes.size())); + base::SpanToRustSlice(join_resp_bytes)); if (!pub_key_result.error_message.empty() || !gsk_result.error_message.empty() || !join_resp_result.error_message.empty()) { @@ -107,7 +133,7 @@ std::optional FinishJoin( << join_resp_result.error_message.c_str(); return std::nullopt; } - auto finish_res = anonymous_credential_manager->finish_join( + auto finish_res = anonymous_credential_manager_->finish_join( *pub_key_result.value, *gsk_result.value, std::move(join_resp_result.value)); if (!finish_res.error_message.empty()) { @@ -118,8 +144,8 @@ std::optional FinishJoin( return base::Base64Encode(finish_res.data); } -std::optional> PerformSign( - anonymous_credentials::CredentialManager* anonymous_credential_manager, +std::optional> +BackgroundCredentialHelper::PerformSign( std::vector msg, std::vector basename, std::optional> gsk_bytes, @@ -127,11 +153,9 @@ std::optional> PerformSign( base::AssertLongCPUWorkAllowed(); if (gsk_bytes && credential_bytes) { auto gsk_result = anonymous_credentials::load_credential_big( - rust::Slice(reinterpret_cast(gsk_bytes->data()), - gsk_bytes->size())); + base::SpanToRustSlice(*gsk_bytes)); auto credential_result = anonymous_credentials::load_user_credentials( - rust::Slice(reinterpret_cast(credential_bytes->data()), - credential_bytes->size())); + base::SpanToRustSlice(*credential_bytes)); if (!gsk_result.error_message.empty() || !credential_result.error_message.empty()) { VLOG(1) << "Failed to sign due to deserialization error with gsk, or " @@ -140,12 +164,11 @@ std::optional> PerformSign( << credential_result.error_message.c_str(); return std::nullopt; } - anonymous_credential_manager->set_gsk_and_credentials( + anonymous_credential_manager_->set_gsk_and_credentials( std::move(gsk_result.value), std::move(credential_result.value)); } - auto sig_res = anonymous_credential_manager->sign( - rust::Slice(msg.data(), msg.size()), - rust::Slice(basename.data(), basename.size())); + auto sig_res = anonymous_credential_manager_->sign( + base::SpanToRustSlice(msg), base::SpanToRustSlice(basename)); if (!sig_res.error_message.empty()) { VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); return std::nullopt; @@ -153,8 +176,6 @@ std::optional> PerformSign( return std::vector(sig_res.data.begin(), sig_res.data.end()); } -} // namespace - CredentialManager::CredentialManager( PrefService* profile_prefs, network::SharedURLLoaderFactory* shared_url_loader_factory, @@ -164,12 +185,8 @@ CredentialManager::CredentialManager( server_config_loader_(server_config_loader), join_url_(GetDirectHPNHost() + kJoinPath), backoff_entry_(&kBackoffPolicy), - sequenced_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})), - anonymous_credential_manager_( - new rust::Box(anonymous_credentials::new_credential_manager()), - base::OnTaskRunnerDeleter(sequenced_task_runner_)), - rsa_private_key_(nullptr, - base::OnTaskRunnerDeleter(sequenced_task_runner_)) {} + background_task_runner_(base::ThreadPool::CreateSequencedTaskRunner({})), + background_credential_helper_(background_task_runner_) {} CredentialManager::~CredentialManager() = default; @@ -183,11 +200,15 @@ bool CredentialManager::LoadRSAKey() { return true; } - rsa_private_key_.reset(ImportRSAKeyPair(private_key_b64).release()); - if (!rsa_private_key_) { + auto key_pair = ImportRSAKeyPair(private_key_b64); + if (!key_pair) { VLOG(1) << "Failed to import stored RSA key"; + rsa_public_key_b64_ = std::nullopt; return false; } + background_credential_helper_ + .AsyncCall(&BackgroundCredentialHelper::SetRSAKey) + .WithArgs(std::move(key_pair)); return true; } @@ -198,7 +219,6 @@ void CredentialManager::OnNewRSAKey(std::unique_ptr key_info) { return; } - rsa_private_key_.reset(key_info->key_pair.release()); rsa_public_key_b64_ = key_info->public_key_b64; profile_prefs_->SetString(kCredentialRSAPrivateKey, @@ -213,53 +233,48 @@ void CredentialManager::JoinGroups() { auto today_date = FormatServerDate(base::Time::Now().UTCMidnight()); const auto& anon_creds_dict = profile_prefs_->GetDict(kAnonymousCredentialsDict); - for (const auto& [date, group_pub_key_b64] : server_config.group_pub_keys) { + for (const auto& [date, group_pub_key] : server_config.group_pub_keys) { if (date < today_date || join_url_loaders_.contains(date) || anon_creds_dict.contains(date)) { continue; } - if (rsa_private_key_ == nullptr) { + if (!rsa_public_key_b64_) { if (!LoadRSAKey()) { return; } - if (rsa_private_key_ == nullptr) { - sequenced_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&GenerateRSAKeyPair), - base::BindOnce(&CredentialManager::OnNewRSAKey, - weak_ptr_factory_.GetWeakPtr())); + if (!rsa_public_key_b64_) { + background_credential_helper_ + .AsyncCall(&BackgroundCredentialHelper::GenerateRSAKey) + .Then(base::BindOnce(&CredentialManager::OnNewRSAKey, + weak_ptr_factory_.GetWeakPtr())); return; } } - StartJoinGroup(date, group_pub_key_b64); + StartJoinGroup(date, group_pub_key); } } -void CredentialManager::StartJoinGroup(const std::string& date, - const std::string& group_pub_key_b64) { - auto group_pub_key = base::Base64Decode(group_pub_key_b64); - if (!group_pub_key) { - VLOG(1) << "Failed to decode group public key for " << date; - return; - } - std::vector group_pub_key_const(group_pub_key->begin(), - group_pub_key->end()); +void CredentialManager::StartJoinGroup( + const std::string& date, + const std::vector& group_pub_key) { + std::vector group_pub_key_const(group_pub_key.begin(), + group_pub_key.end()); auto challenge_elements = base::Value::List::with_capacity(2); challenge_elements.Append(*rsa_public_key_b64_); - challenge_elements.Append(group_pub_key_b64); + challenge_elements.Append(base::Base64Encode(group_pub_key)); std::string pre_challenge; base::JSONWriter::Write(challenge_elements, &pre_challenge); - sequenced_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&GenerateJoinRequest, &**anonymous_credential_manager_, - rsa_private_key_.get(), pre_challenge), - base::BindOnce(&CredentialManager::OnJoinRequestReady, - weak_ptr_factory_.GetWeakPtr(), date, - group_pub_key_const)); + background_credential_helper_ + .AsyncCall(&BackgroundCredentialHelper::GenerateJoinRequest) + .WithArgs(pre_challenge) + .Then(base::BindOnce(&CredentialManager::OnJoinRequestReady, + weak_ptr_factory_.GetWeakPtr(), date, + group_pub_key_const)); } void CredentialManager::OnJoinRequestReady( @@ -376,12 +391,11 @@ bool CredentialManager::ProcessJoinResponse( std::vector join_resp_bytes_const(join_resp_bytes->begin(), join_resp_bytes->end()); - sequenced_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&FinishJoin, &**anonymous_credential_manager_, date, - group_pub_key, gsk, join_resp_bytes_const), - base::BindOnce(&CredentialManager::OnCredentialsReady, - weak_ptr_factory_.GetWeakPtr(), date, gsk)); + background_credential_helper_ + .AsyncCall(&BackgroundCredentialHelper::FinishJoin) + .WithArgs(date, group_pub_key, gsk, join_resp_bytes_const) + .Then(base::BindOnce(&CredentialManager::OnCredentialsReady, + weak_ptr_factory_.GetWeakPtr(), date, gsk)); return true; } @@ -405,7 +419,7 @@ bool CredentialManager::CredentialExistsForToday() { .contains(FormatServerDate(base::Time::Now())); } -bool CredentialManager::Sign(std::vector msg, +void CredentialManager::Sign(std::vector msg, std::vector basename, SignCallback callback) { auto today_date = FormatServerDate(base::Time::Now().UTCMidnight()); @@ -417,30 +431,31 @@ bool CredentialManager::Sign(std::vector msg, auto* today_cred_dict = anon_creds_dict.FindDict(today_date); if (!today_cred_dict) { VLOG(1) << "Failed to sign due to unavailability of credentials"; - return false; + std::move(callback).Run(std::nullopt); + return; } auto* gsk_b64 = today_cred_dict->FindString(kGSKDictKey); auto* credential_b64 = today_cred_dict->FindString(kCredentialDictKey); if (!gsk_b64 || !credential_b64) { VLOG(1) << "Failed to sign due to unavailability of gsk/credential"; - return false; + std::move(callback).Run(std::nullopt); + return; } gsk_bytes = base::Base64Decode(*gsk_b64); credential_bytes = base::Base64Decode(*credential_b64); if (!gsk_bytes || !credential_bytes) { VLOG(1) << "Failed to sign due to bad gsk/credential base64"; - return false; + std::move(callback).Run(std::nullopt); + return; } } - sequenced_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&PerformSign, &**anonymous_credential_manager_, msg, - basename, gsk_bytes, credential_bytes), - base::BindOnce(&CredentialManager::OnSignResult, - weak_ptr_factory_.GetWeakPtr(), today_date, - std::move(callback))); - return true; + background_credential_helper_ + .AsyncCall(&BackgroundCredentialHelper::PerformSign) + .WithArgs(msg, basename, gsk_bytes, credential_bytes) + .Then(base::BindOnce(&CredentialManager::OnSignResult, + weak_ptr_factory_.GetWeakPtr(), today_date, + std::move(callback))); } void CredentialManager::OnSignResult( @@ -452,12 +467,8 @@ void CredentialManager::OnSignResult( } void CredentialManager::UseFixedSeedForTesting() { - anonymous_credential_manager_ = - std::unique_ptr, - base::OnTaskRunnerDeleter>( - new rust::Box( - anonymous_credentials::new_credential_manager_with_fixed_seed()), - base::OnTaskRunnerDeleter(sequenced_task_runner_)); + background_credential_helper_.AsyncCall( + &BackgroundCredentialHelper::UseFixedSeedForTesting); } } // namespace web_discovery diff --git a/components/web_discovery/browser/credential_manager.h b/components/web_discovery/browser/credential_manager.h index e5550a37ca0f..18841571de24 100644 --- a/components/web_discovery/browser/credential_manager.h +++ b/components/web_discovery/browser/credential_manager.h @@ -14,6 +14,7 @@ #include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/task/sequenced_task_runner.h" +#include "base/threading/sequence_bound.h" #include "base/timer/wall_clock_timer.h" #include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" #include "brave/components/web_discovery/browser/credential_signer.h" @@ -36,6 +37,38 @@ struct GenerateJoinRequestResult { std::string signature; }; +class BackgroundCredentialHelper { + public: + BackgroundCredentialHelper(); + ~BackgroundCredentialHelper(); + + BackgroundCredentialHelper(const BackgroundCredentialHelper&) = delete; + BackgroundCredentialHelper& operator=(const BackgroundCredentialHelper&) = + delete; + + void UseFixedSeedForTesting(); + + std::unique_ptr GenerateRSAKey(); + void SetRSAKey(std::unique_ptr rsa_private_key); + std::optional GenerateJoinRequest( + std::string pre_challenge); + std::optional FinishJoin( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes); + std::optional> PerformSign( + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes); + + private: + rust::Box + anonymous_credential_manager_; + std::unique_ptr rsa_private_key_; +}; + // Manages and utilizes anonymous credentials used for communicating // with Web Discovery servers. These Direct Anonymous Attestation credentials // are used to prevent Sybil attacks on the servers. @@ -63,7 +96,7 @@ class CredentialManager : public CredentialSigner { // CredentialSigner: bool CredentialExistsForToday() override; - bool Sign(std::vector msg, + void Sign(std::vector msg, std::vector basename, SignCallback callback) override; @@ -78,7 +111,7 @@ class CredentialManager : public CredentialSigner { void OnNewRSAKey(std::unique_ptr key_info); void StartJoinGroup(const std::string& date, - const std::string& group_pub_key_b64); + const std::vector& group_pub_key); void OnJoinRequestReady( std::string date, @@ -102,9 +135,9 @@ class CredentialManager : public CredentialSigner { SignCallback callback, std::optional> signed_message); - raw_ptr profile_prefs_; - raw_ptr shared_url_loader_factory_; - raw_ptr server_config_loader_; + const raw_ptr profile_prefs_; + const raw_ptr shared_url_loader_factory_; + const raw_ptr server_config_loader_; GURL join_url_; base::flat_map> @@ -112,14 +145,9 @@ class CredentialManager : public CredentialSigner { net::BackoffEntry backoff_entry_; base::WallClockTimer retry_timer_; - scoped_refptr sequenced_task_runner_; - - std::unique_ptr, - base::OnTaskRunnerDeleter> - anonymous_credential_manager_; + scoped_refptr background_task_runner_; - std::unique_ptr - rsa_private_key_; + base::SequenceBound background_credential_helper_; std::optional rsa_public_key_b64_; std::optional loaded_credential_date_; diff --git a/components/web_discovery/browser/credential_manager_unittest.cc b/components/web_discovery/browser/credential_manager_unittest.cc index 98e12761a8b1..b7237ced323a 100644 --- a/components/web_discovery/browser/credential_manager_unittest.cc +++ b/components/web_discovery/browser/credential_manager_unittest.cc @@ -9,6 +9,7 @@ #include #include +#include "base/base64.h" #include "base/files/file_util.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" @@ -69,7 +70,9 @@ class WebDiscoveryCredentialManagerTest : public testing::Test { auto server_config = std::make_unique(); for (const auto [date, join_response] : *join_responses) { - server_config->group_pub_keys[date] = *group_pub_key; + auto decoded_group_pub_key = base::Base64Decode(*group_pub_key); + ASSERT_TRUE(decoded_group_pub_key); + server_config->group_pub_keys[date] = *decoded_group_pub_key; join_responses_[date] = join_response.GetString(); } server_config_loader_->SetLastServerConfigForTesting( @@ -90,12 +93,6 @@ class WebDiscoveryCredentialManagerTest : public testing::Test { credential_manager_->UseFixedSeedForTesting(); } - base::test::TaskEnvironment task_environment_; - std::unique_ptr credential_manager_; - TestingPrefServiceSimple profile_prefs_; - size_t join_requests_made_ = 0; - - private: void HandleRequest(const network::ResourceRequest& request) { url_loader_factory_.ClearResponses(); std::string response; @@ -120,10 +117,19 @@ class WebDiscoveryCredentialManagerTest : public testing::Test { join_requests_made_++; } - base::flat_map join_responses_; - std::unique_ptr server_config_loader_; + base::test::TaskEnvironment task_environment_; + network::TestURLLoaderFactory url_loader_factory_; scoped_refptr shared_url_loader_factory_; + + TestingPrefServiceSimple profile_prefs_; + std::unique_ptr server_config_loader_; + + base::flat_map join_responses_; + + std::unique_ptr credential_manager_; + + size_t join_requests_made_ = 0; }; TEST_F(WebDiscoveryCredentialManagerTest, JoinGroups) { diff --git a/components/web_discovery/browser/credential_signer.cc b/components/web_discovery/browser/credential_signer.cc deleted file mode 100644 index ca8fce0c3b51..000000000000 --- a/components/web_discovery/browser/credential_signer.cc +++ /dev/null @@ -1,12 +0,0 @@ -/* Copyright (c) 2024 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#include "brave/components/web_discovery/browser/credential_signer.h" - -namespace web_discovery { - -CredentialSigner::~CredentialSigner() = default; - -} // namespace web_discovery diff --git a/components/web_discovery/browser/credential_signer.h b/components/web_discovery/browser/credential_signer.h index b7e167cf1e49..fd79c67a894c 100644 --- a/components/web_discovery/browser/credential_signer.h +++ b/components/web_discovery/browser/credential_signer.h @@ -17,7 +17,7 @@ class CredentialSigner { public: using SignCallback = base::OnceCallback>)>; - virtual ~CredentialSigner(); + virtual ~CredentialSigner() = default; // Returns true is a credential is available for the current date. // The caller can expect future calls to `Sign` to succeed, if made today. @@ -29,7 +29,7 @@ class CredentialSigner { // preventing Sybil attacks. // See signature_basename.h/cc for more information on how the basename // should be generated. - virtual bool Sign(std::vector msg, + virtual void Sign(std::vector msg, std::vector basename, SignCallback callback) = 0; }; diff --git a/components/web_discovery/browser/patterns.cc b/components/web_discovery/browser/patterns.cc index 34cce886417e..d0e7457dd5cd 100644 --- a/components/web_discovery/browser/patterns.cc +++ b/components/web_discovery/browser/patterns.cc @@ -7,10 +7,12 @@ #include +#include "base/containers/contains.h" #include "base/containers/fixed_flat_map.h" #include "base/json/json_reader.h" #include "base/logging.h" #include "base/strings/string_number_conversions.h" +#include "base/threading/thread_restrictions.h" #include "third_party/re2/src/re2/re2.h" namespace web_discovery { @@ -88,7 +90,6 @@ std::optional> ParsePayloadRules( return std::nullopt; } auto* action = rule_group_dict->FindString(kActionKey); - auto* fields = rule_group_dict->FindList(kFieldsKey); auto* rule_type_str = rule_group_dict->FindString(kRuleTypeKey); auto* result_type_str = rule_group_dict->FindString(kResultTypeKey); if (!action || !rule_type_str || !result_type_str) { @@ -106,7 +107,7 @@ std::optional> ParsePayloadRules( rule_group_it->key = key; rule_group_it->result_type = result_type_it->second; rule_group_it->rule_type = rule_type_it->second; - if (fields) { + if (auto* fields = rule_group_dict->FindList(kFieldsKey)) { rule_group_it->rules = std::vector(fields->size()); auto rule_it = rule_group_it->rules.begin(); @@ -124,15 +125,16 @@ std::optional> ParsePayloadRules( } RefineFunctionList ParseFunctionsApplied(const base::Value::List* list) { + CHECK(list); RefineFunctionList result; for (const auto& function_val : *list) { const auto* function_list = function_val.GetIfList(); if (!function_list || function_list->size() <= 1) { continue; } - std::vector function_vec; + base::Value::List function_vec; for (const auto& element : *function_list) { - function_vec.push_back(element.Clone()); + function_vec.Append(element.Clone()); } result.push_back(std::move(function_vec)); } @@ -212,6 +214,11 @@ std::optional> ParsePatternsURLDetails( auto& details = result[i]; details.url_regex = std::make_unique(*url_regex); + if (!details.url_regex->ok()) { + VLOG(1) << "URL pattern is not valid regex: " + << details.url_regex->error(); + return std::nullopt; + } std::string i_str = base::NumberToString(i); @@ -224,10 +231,7 @@ std::optional> ParsePatternsURLDetails( } details.id = *id; - details.is_search_engine = - base::ranges::find(search_engines_list->begin(), - search_engines_list->end(), - i_str) != search_engines_list->end(); + details.is_search_engine = base::Contains(*search_engines_list, i_str); auto scrape_rule_groups = ParseScrapeRules(scrape_url_dict); if (!scrape_rule_groups) { @@ -285,14 +289,22 @@ const PatternsURLDetails* PatternsGroup::GetMatchingURLPattern( return nullptr; } -std::unique_ptr ParsePatterns(const std::string& patterns_json) { - auto result = std::make_unique(); - auto patterns_value = base::JSONReader::Read(patterns_json); - if (!patterns_value || !patterns_value->is_dict()) { +std::unique_ptr ParsePatterns(std::string_view patterns_json) { + base::AssertLongCPUWorkAllowed(); + const auto patterns_parse_result = + base::JSONReader::ReadAndReturnValueWithError(patterns_json); + if (!patterns_parse_result.has_value()) { + VLOG(1) << "Failed to parse patterns JSON: " + << patterns_parse_result.error().ToString(); + return nullptr; + } + const auto& patterns_value = patterns_parse_result.value(); + if (!patterns_value.is_dict()) { VLOG(1) << "Patterns is not JSON or is not dict"; return nullptr; } - const auto& patterns_dict = patterns_value->GetDict(); + auto result = std::make_unique(); + const auto& patterns_dict = patterns_value.GetDict(); auto* normal_dict = patterns_dict.FindDict(kNormalPatternsKey); auto* strict_dict = patterns_dict.FindDict(kStrictPatternsKey); diff --git a/components/web_discovery/browser/patterns.h b/components/web_discovery/browser/patterns.h index b9e3b749e104..a87de2ceaa64 100644 --- a/components/web_discovery/browser/patterns.h +++ b/components/web_discovery/browser/patterns.h @@ -52,7 +52,7 @@ enum class PayloadResultType { // Contains functions for refining the scraped value. The inner vector // contains the function name and arguments for the function. -using RefineFunctionList = std::vector>; +using RefineFunctionList = std::vector; // Defines rule for scraping an attribute from a given selected element. struct ScrapeRule { @@ -167,7 +167,7 @@ struct PatternsGroup { }; // Returns nullptr if parsing fails. -std::unique_ptr ParsePatterns(const std::string& patterns_json); +std::unique_ptr ParsePatterns(std::string_view patterns_json); } // namespace web_discovery diff --git a/components/web_discovery/browser/server_config_loader.cc b/components/web_discovery/browser/server_config_loader.cc index 1f4d0c3a7fca..b9ecd4bbeac2 100644 --- a/components/web_discovery/browser/server_config_loader.cc +++ b/components/web_discovery/browser/server_config_loader.cc @@ -84,12 +84,11 @@ constexpr auto kAllowedReportLocations = KeyMap ParseKeys(const base::Value::Dict& encoded_keys) { KeyMap map; for (const auto [date, key_b64] : encoded_keys) { - std::vector decoded_data; - // Decode to check for valid base64 - if (!base::Base64Decode(key_b64.GetString())) { + auto decoded_data = base::Base64Decode(key_b64.GetString()); + if (!decoded_data) { continue; } - map[date] = key_b64.GetString(); + map[date] = *decoded_data; } return map; } @@ -145,6 +144,70 @@ std::optional ReadPatternsFile(base::FilePath patterns_path) { return contents; } +std::unique_ptr ProcessConfigResponses( + const std::string collector_response_body, + const std::string quorum_response_body) { + base::AssertLongCPUWorkAllowed(); + auto collector_parsed_json = base::JSONReader::ReadAndReturnValueWithError( + collector_response_body, base::JSON_PARSE_RFC); + auto quorum_parsed_json = base::JSONReader::ReadAndReturnValueWithError( + quorum_response_body, base::JSON_PARSE_RFC); + + if (!collector_parsed_json.has_value() || !quorum_parsed_json.has_value()) { + const auto& error = !collector_parsed_json.has_value() + ? collector_parsed_json.error() + : quorum_parsed_json.error(); + VLOG(1) << "Failed to parse server config json: " << error.ToString(); + return nullptr; + } + + const auto* collector_root = collector_parsed_json.value().GetIfDict(); + const auto* quorum_root = quorum_parsed_json.value().GetIfDict(); + if (!collector_root || !quorum_root) { + VLOG(1) << "Failed to parse server config: not a dict"; + return nullptr; + } + + const auto min_version = collector_root->FindInt(kMinVersionFieldName); + if (min_version && *min_version > kCurrentVersion) { + VLOG(1) << "Server minimum version is higher than current version, failing"; + return nullptr; + } + + auto config = std::make_unique(); + + const auto* group_pub_keys = collector_root->FindDict(kGroupPubKeysFieldName); + if (!group_pub_keys) { + VLOG(1) << "Failed to retrieve groupPubKeys from server config"; + return nullptr; + } + const auto* pub_keys = collector_root->FindDict(kPubKeysFieldName); + if (!pub_keys) { + VLOG(1) << "Failed to retrieve pubKeys from server config"; + return nullptr; + } + const auto* source_map = collector_root->FindDict(kSourceMapFieldName); + const auto* source_map_actions = + source_map ? source_map->FindDict(kSourceMapActionsFieldName) : nullptr; + if (!source_map_actions) { + VLOG(1) << "Failed to retrieve sourceMap from server config"; + return nullptr; + } + + const auto* location = quorum_root->FindString(kLocationFieldName); + if (location && kAllowedReportLocations.contains(*location)) { + config->location = *location; + } else { + config->location = kOmittedLocationValue; + } + + config->group_pub_keys = ParseKeys(*group_pub_keys); + config->pub_keys = ParseKeys(*pub_keys); + config->source_map_actions = ParseSourceMapActionConfigs(*source_map_actions); + + return config; +} + } // namespace SourceMapActionConfig::SourceMapActionConfig() = default; @@ -153,6 +216,14 @@ SourceMapActionConfig::~SourceMapActionConfig() = default; ServerConfig::ServerConfig() = default; ServerConfig::~ServerConfig() = default; +ServerConfigDownloadResult::ServerConfigDownloadResult( + bool is_collector_config, + std::optional response_body) + : is_collector_config(is_collector_config), response_body(response_body) {} +ServerConfigDownloadResult::~ServerConfigDownloadResult() = default; +ServerConfigDownloadResult::ServerConfigDownloadResult( + const ServerConfigDownloadResult&) = default; + ServerConfigLoader::ServerConfigLoader( PrefService* local_state, base::FilePath user_data_dir, @@ -160,7 +231,7 @@ ServerConfigLoader::ServerConfigLoader( base::RepeatingClosure config_callback, base::RepeatingClosure patterns_callback) : local_state_(local_state), - sequenced_task_runner_( + background_task_runner_( base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})), shared_url_loader_factory_(shared_url_loader_factory), config_callback_(config_callback), @@ -211,27 +282,75 @@ void ServerConfigLoader::LoadConfigs() { quorum_config_url_loader_ = network::SimpleURLLoader::Create( std::move(quorum_resource_request), kNetworkTrafficAnnotation); - auto callback = base::BarrierCallback>( - 2, base::BindOnce(&ServerConfigLoader::OnConfigResponses, + auto callback = base::BarrierCallback( + 2, base::BindOnce(&ServerConfigLoader::OnConfigResponsesDownloaded, base::Unretained(this))); + auto make_download_result = [](bool is_collector_config, + std::optional response_body) { + return ServerConfigDownloadResult(is_collector_config, response_body); + }; + + auto collector_callback = + base::BindOnce(make_download_result, true).Then(callback); + auto quorum_callback = + base::BindOnce(make_download_result, false).Then(callback); + collector_config_url_loader_->DownloadToString( - shared_url_loader_factory_.get(), callback, kMaxResponseSize); + shared_url_loader_factory_.get(), std::move(collector_callback), + kMaxResponseSize); quorum_config_url_loader_->DownloadToString(shared_url_loader_factory_.get(), - callback, kMaxResponseSize); + std::move(quorum_callback), + kMaxResponseSize); } -void ServerConfigLoader::OnConfigResponses( - std::vector> response_bodies) { - CHECK_EQ(response_bodies.size(), 2u); - base::Time update_time = base::Time::Now(); - bool result = ProcessConfigResponses(response_bodies[0], response_bodies[1]); +void ServerConfigLoader::OnConfigResponsesDownloaded( + std::vector results) { + CHECK_EQ(results.size(), 2u); + const std::optional* collector_response_body = nullptr; + const std::optional* quorum_response_body = nullptr; + for (const auto& result : results) { + if (result.is_collector_config) { + collector_response_body = &result.response_body; + } else { + quorum_response_body = &result.response_body; + } + } + CHECK(collector_response_body && quorum_response_body); + + auto* collector_response_info = collector_config_url_loader_->ResponseInfo(); + auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); + if (!*collector_response_body || !*quorum_response_body || + !collector_response_info || !quorum_response_info || + collector_response_info->headers->response_code() != 200 || + quorum_response_info->headers->response_code() != 200) { + VLOG(1) << "Failed to download one or more server configs"; + OnConfigResponsesProcessed(nullptr); + return; + } + + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, + base::BindOnce(&ProcessConfigResponses, **collector_response_body, + **quorum_response_body), + base::BindOnce(&ServerConfigLoader::OnConfigResponsesProcessed, + weak_ptr_factory_.GetWeakPtr())); +} + +void ServerConfigLoader::OnConfigResponsesProcessed( + std::unique_ptr config) { + bool result = config != nullptr; + if (result) { + last_loaded_server_config_ = std::move(config); + config_callback_.Run(); + } config_backoff_entry_.InformOfRequest(result); collector_config_url_loader_ = nullptr; quorum_config_url_loader_ = nullptr; + auto update_time = base::Time::Now(); if (!result) { update_time += config_backoff_entry_.GetTimeUntilRelease(); } else { @@ -245,80 +364,8 @@ void ServerConfigLoader::OnConfigResponses( base::BindOnce(&ServerConfigLoader::LoadConfigs, base::Unretained(this))); } -bool ServerConfigLoader::ProcessConfigResponses( - const std::optional& collector_response_body, - const std::optional& quorum_response_body) { - auto* collector_response_info = collector_config_url_loader_->ResponseInfo(); - auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); - if (!collector_response_body || !collector_response_info || - !quorum_response_body || !collector_response_info || - collector_response_info->headers->response_code() != 200 || - quorum_response_info->headers->response_code() != 200) { - VLOG(1) << "Failed to fetch server config"; - return false; - } - - auto collector_parsed_json = base::JSONReader::ReadAndReturnValueWithError( - *collector_response_body, base::JSON_PARSE_RFC); - auto quorum_parsed_json = base::JSONReader::ReadAndReturnValueWithError( - *quorum_response_body, base::JSON_PARSE_RFC); - - if (!collector_parsed_json.has_value() || !quorum_parsed_json.has_value()) { - VLOG(1) << "Failed to parse server config json"; - return false; - } - - const auto* collector_root = collector_parsed_json.value().GetIfDict(); - const auto* quorum_root = quorum_parsed_json.value().GetIfDict(); - if (!collector_root || !quorum_root) { - VLOG(1) << "Failed to parse server config: not a dict"; - return false; - } - - const auto min_version = collector_root->FindInt(kMinVersionFieldName); - if (min_version && *min_version > kCurrentVersion) { - VLOG(1) << "Server minimum version is higher than current version, failing"; - return false; - } - - auto config = std::make_unique(); - - const auto* group_pub_keys = collector_root->FindDict(kGroupPubKeysFieldName); - if (!group_pub_keys) { - VLOG(1) << "Failed to retrieve groupPubKeys from server config"; - return false; - } - const auto* pub_keys = collector_root->FindDict(kPubKeysFieldName); - if (!pub_keys) { - VLOG(1) << "Failed to retrieve pubKeys from server config"; - return false; - } - const auto* source_map = collector_root->FindDict(kSourceMapFieldName); - const auto* source_map_actions = - source_map ? source_map->FindDict(kSourceMapActionsFieldName) : nullptr; - if (!source_map_actions) { - VLOG(1) << "Failed to retrieve sourceMap from server config"; - return false; - } - - const auto* location = quorum_root->FindString(kLocationFieldName); - if (location && kAllowedReportLocations.contains(*location)) { - config->location = *location; - } else { - config->location = kOmittedLocationValue; - } - - config->group_pub_keys = ParseKeys(*group_pub_keys); - config->pub_keys = ParseKeys(*pub_keys); - config->source_map_actions = ParseSourceMapActionConfigs(*source_map_actions); - - last_loaded_server_config_ = std::move(config); - config_callback_.Run(); - return true; -} - void ServerConfigLoader::LoadStoredPatterns() { - sequenced_task_runner_->PostTaskAndReplyWithResult( + background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&ReadPatternsFile, patterns_path_), base::BindOnce(&ServerConfigLoader::OnPatternsFileLoaded, weak_ptr_factory_.GetWeakPtr())); @@ -332,14 +379,10 @@ void ServerConfigLoader::OnPatternsFileLoaded( SchedulePatternsRequest(); return; } - auto parsed_patterns = ParsePatterns(*patterns_json); - if (!parsed_patterns) { - local_state_->ClearPref(kPatternsRetrievalTime); - SchedulePatternsRequest(); - return; - } - last_loaded_patterns_ = std::move(parsed_patterns); - patterns_callback_.Run(); + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + base::BindOnce(&ServerConfigLoader::OnStoredPatternsParsed, + weak_ptr_factory_.GetWeakPtr())); } void ServerConfigLoader::SchedulePatternsRequest() { @@ -403,7 +446,7 @@ void ServerConfigLoader::OnPatternsResponse( HandlePatternsStatus(false); return; } - sequenced_task_runner_->PostTaskAndReplyWithResult( + background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, base::BindOnce(&GunzipContents, *response_body), base::BindOnce(&ServerConfigLoader::OnPatternsGunzip, weak_ptr_factory_.GetWeakPtr())); @@ -416,14 +459,33 @@ void ServerConfigLoader::OnPatternsGunzip( HandlePatternsStatus(false); return; } - auto parsed_patterns = ParsePatterns(*patterns_json); + background_task_runner_->PostTaskAndReplyWithResult( + FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + base::BindOnce(&ServerConfigLoader::OnNewPatternsParsed, + weak_ptr_factory_.GetWeakPtr(), *patterns_json)); +} + +void ServerConfigLoader::OnStoredPatternsParsed( + std::unique_ptr parsed_patterns) { + if (!parsed_patterns) { + local_state_->ClearPref(kPatternsRetrievalTime); + SchedulePatternsRequest(); + return; + } + last_loaded_patterns_ = std::move(parsed_patterns); + patterns_callback_.Run(); +} + +void ServerConfigLoader::OnNewPatternsParsed( + std::string new_patterns_json, + std::unique_ptr parsed_patterns) { if (!parsed_patterns) { HandlePatternsStatus(false); return; } - sequenced_task_runner_->PostTaskAndReplyWithResult( + background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, - base::BindOnce(&WritePatternsFile, patterns_path_, *patterns_json), + base::BindOnce(&WritePatternsFile, patterns_path_, new_patterns_json), base::BindOnce(&ServerConfigLoader::OnPatternsWritten, weak_ptr_factory_.GetWeakPtr(), std::move(parsed_patterns))); diff --git a/components/web_discovery/browser/server_config_loader.h b/components/web_discovery/browser/server_config_loader.h index 10ad1f55fa4d..552735976493 100644 --- a/components/web_discovery/browser/server_config_loader.h +++ b/components/web_discovery/browser/server_config_loader.h @@ -28,7 +28,7 @@ class SimpleURLLoader; namespace web_discovery { -using KeyMap = base::flat_map; +using KeyMap = base::flat_map>; struct SourceMapActionConfig { SourceMapActionConfig(); @@ -58,6 +58,17 @@ struct ServerConfig { std::string location; }; +struct ServerConfigDownloadResult { + ServerConfigDownloadResult(bool is_collector_config, + std::optional response_body); + ~ServerConfigDownloadResult(); + + ServerConfigDownloadResult(const ServerConfigDownloadResult&); + + bool is_collector_config; + std::optional response_body; +}; + // Handles retrieval, updating and caching of the following server // configurations: // - HPN server config: contains public keys, and "source maps" used @@ -93,11 +104,9 @@ class ServerConfigLoader { void SetLastPatternsForTesting(std::unique_ptr patterns); private: - void OnConfigResponses( - std::vector> response_bodies); - bool ProcessConfigResponses( - const std::optional& collector_response_body, - const std::optional& quorum_response_body); + void OnConfigResponsesDownloaded( + std::vector results); + void OnConfigResponsesProcessed(std::unique_ptr config); void LoadStoredPatterns(); void OnPatternsFileLoaded(std::optional patterns_json); @@ -105,13 +114,16 @@ class ServerConfigLoader { void RequestPatterns(); void OnPatternsResponse(std::optional response_body); void OnPatternsGunzip(std::optional patterns_json); + void OnStoredPatternsParsed(std::unique_ptr parsed_patterns); + void OnNewPatternsParsed(std::string new_patterns_json, + std::unique_ptr parsed_patterns); void OnPatternsWritten(std::unique_ptr parsed_group, bool result); void HandlePatternsStatus(bool result); - raw_ptr local_state_; + const raw_ptr local_state_; - scoped_refptr sequenced_task_runner_; + scoped_refptr background_task_runner_; GURL collector_config_url_; GURL quorum_config_url_; diff --git a/components/web_discovery/browser/server_config_loader_unittest.cc b/components/web_discovery/browser/server_config_loader_unittest.cc index fe69cbf7520e..2c7d1689bf6d 100644 --- a/components/web_discovery/browser/server_config_loader_unittest.cc +++ b/components/web_discovery/browser/server_config_loader_unittest.cc @@ -78,22 +78,6 @@ class WebDiscoveryServerConfigLoaderTest : public testing::Test { install_dir_.GetPath().AppendASCII("wdp_patterns.json")); } - base::test::TaskEnvironment task_environment_; - std::unique_ptr server_config_loader_; - base::ScopedTempDir install_dir_; - - size_t hpn_config_requests_made_ = 0; - size_t quorum_config_requests_made_ = 0; - size_t patterns_requests_made_ = 0; - - size_t config_ready_calls_made_ = 0; - size_t patterns_ready_calls_made_ = 0; - - net::HttpStatusCode hpn_config_status_code_ = net::HTTP_OK; - net::HttpStatusCode quorum_config_status_code_ = net::HTTP_OK; - net::HttpStatusCode patterns_status_code_ = net::HTTP_OK; - - private: void HandleRequest(const network::ResourceRequest& request) { url_loader_factory_.ClearResponses(); @@ -120,13 +104,30 @@ class WebDiscoveryServerConfigLoaderTest : public testing::Test { void HandlePatternsReady() { patterns_ready_calls_made_++; } + base::test::TaskEnvironment task_environment_; + TestingPrefServiceSimple local_state_; + + network::TestURLLoaderFactory url_loader_factory_; + scoped_refptr shared_url_loader_factory_; + + base::ScopedTempDir install_dir_; + + size_t hpn_config_requests_made_ = 0; + size_t quorum_config_requests_made_ = 0; + size_t patterns_requests_made_ = 0; + + size_t config_ready_calls_made_ = 0; + size_t patterns_ready_calls_made_ = 0; + + net::HttpStatusCode hpn_config_status_code_ = net::HTTP_OK; + net::HttpStatusCode quorum_config_status_code_ = net::HTTP_OK; + net::HttpStatusCode patterns_status_code_ = net::HTTP_OK; + std::string patterns_gz_contents_; std::string quorum_config_contents_; std::string hpn_config_contents_; - TestingPrefServiceSimple local_state_; - network::TestURLLoaderFactory url_loader_factory_; - scoped_refptr shared_url_loader_factory_; + std::unique_ptr server_config_loader_; }; TEST_F(WebDiscoveryServerConfigLoaderTest, LoadConfigs) { diff --git a/components/web_discovery/browser/util.cc b/components/web_discovery/browser/util.cc index acb38bdf63e2..bd124961518e 100644 --- a/components/web_discovery/browser/util.cc +++ b/components/web_discovery/browser/util.cc @@ -5,6 +5,8 @@ #include "brave/components/web_discovery/browser/util.h" +#include + #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "brave/brave_domains/service_domains.h" @@ -46,7 +48,7 @@ GURL GetPatternsEndpoint() { std::unique_ptr CreateResourceRequest(GURL url) { auto resource_request = std::make_unique(); - resource_request->url = url; + resource_request->url = std::move(url); resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; return resource_request; } diff --git a/components/web_discovery/browser/web_discovery_service.cc b/components/web_discovery/browser/web_discovery_service.cc index 6acb7c733565..848d89ec289b 100644 --- a/components/web_discovery/browser/web_discovery_service.cc +++ b/components/web_discovery/browser/web_discovery_service.cc @@ -18,7 +18,6 @@ #include "components/prefs/scoped_user_pref_update.h" #include "extensions/buildflags/buildflags.h" #include "services/network/public/cpp/shared_url_loader_factory.h" -#include "services/service_manager/public/cpp/interface_provider.h" namespace web_discovery { @@ -66,13 +65,18 @@ void WebDiscoveryService::RegisterProfilePrefs(PrefRegistrySimple* registry) { void WebDiscoveryService::SetExtensionPrefIfNativeDisabled( PrefService* profile_prefs) { #if BUILDFLAG(ENABLE_EXTENSIONS) - if (!base::FeatureList::IsEnabled(features::kWebDiscoveryNative) && + if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative) && profile_prefs->GetBoolean(kWebDiscoveryNativeEnabled)) { profile_prefs->SetBoolean(kWebDiscoveryExtensionEnabled, true); } #endif } +void WebDiscoveryService::Shutdown() { + Stop(); + pref_change_registrar_.RemoveAll(); +} + void WebDiscoveryService::Start() { if (!server_config_loader_) { server_config_loader_ = std::make_unique( @@ -93,8 +97,9 @@ void WebDiscoveryService::Start() { void WebDiscoveryService::Stop() { server_config_loader_ = nullptr; credential_manager_ = nullptr; +} - profile_prefs_->ClearPref(kWebDiscoveryNativeEnabled); +void WebDiscoveryService::ClearPrefs() { profile_prefs_->ClearPref(kAnonymousCredentialsDict); profile_prefs_->ClearPref(kCredentialRSAPrivateKey); profile_prefs_->ClearPref(kCredentialRSAPublicKey); @@ -105,6 +110,7 @@ void WebDiscoveryService::OnEnabledChange() { Start(); } else { Stop(); + ClearPrefs(); } } diff --git a/components/web_discovery/browser/web_discovery_service.h b/components/web_discovery/browser/web_discovery_service.h index 52cc9b74193c..c30f03917ed0 100644 --- a/components/web_discovery/browser/web_discovery_service.h +++ b/components/web_discovery/browser/web_discovery_service.h @@ -52,9 +52,13 @@ class WebDiscoveryService : public KeyedService { // Relevant for a Griffin/variations rollback. static void SetExtensionPrefIfNativeDisabled(PrefService* profile_prefs); + // KeyedService: + void Shutdown() override; + private: void Start(); void Stop(); + void ClearPrefs(); void OnEnabledChange(); diff --git a/components/web_discovery/common/buildflags/BUILD.gn b/components/web_discovery/buildflags/BUILD.gn similarity index 85% rename from components/web_discovery/common/buildflags/BUILD.gn rename to components/web_discovery/buildflags/BUILD.gn index 5d0fc823c167..2c23f6ce7b16 100644 --- a/components/web_discovery/common/buildflags/BUILD.gn +++ b/components/web_discovery/buildflags/BUILD.gn @@ -3,7 +3,7 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at https://mozilla.org/MPL/2.0/. -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//build/buildflag_header.gni") buildflag_header("buildflags") { diff --git a/components/web_discovery/common/buildflags/buildflags.gni b/components/web_discovery/buildflags/buildflags.gni similarity index 100% rename from components/web_discovery/common/buildflags/buildflags.gni rename to components/web_discovery/buildflags/buildflags.gni diff --git a/components/web_discovery/common/BUILD.gn b/components/web_discovery/common/BUILD.gn index a50ead3e6ff5..6ce00a413af9 100644 --- a/components/web_discovery/common/BUILD.gn +++ b/components/web_discovery/common/BUILD.gn @@ -3,7 +3,9 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at https://mozilla.org/MPL/2.0/. -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") + +assert(enable_web_discovery_native) static_library("common") { sources = [ @@ -13,6 +15,6 @@ static_library("common") { deps = [ "//base", - "//brave/components/web_discovery/common/buildflags", + "//brave/components/web_discovery/buildflags", ] } diff --git a/components/web_discovery/common/features.cc b/components/web_discovery/common/features.cc index 6eec658cfc8b..b22c1cf7a31c 100644 --- a/components/web_discovery/common/features.cc +++ b/components/web_discovery/common/features.cc @@ -7,7 +7,7 @@ namespace web_discovery::features { -BASE_FEATURE(kWebDiscoveryNative, +BASE_FEATURE(kBraveWebDiscoveryNative, "BraveWebDiscoveryNative", base::FEATURE_DISABLED_BY_DEFAULT); diff --git a/components/web_discovery/common/features.h b/components/web_discovery/common/features.h index 45db0afd2555..98349380cf5e 100644 --- a/components/web_discovery/common/features.h +++ b/components/web_discovery/common/features.h @@ -12,7 +12,7 @@ namespace web_discovery::features { // Enables the native re-implementation of the Web Discovery Project. // If enabled, the Web Discovery component of the extension should be disabled. -BASE_DECLARE_FEATURE(kWebDiscoveryNative); +BASE_DECLARE_FEATURE(kBraveWebDiscoveryNative); } // namespace web_discovery::features diff --git a/test/BUILD.gn b/test/BUILD.gn index 5c3f1fad0810..842b873e57e9 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -17,7 +17,7 @@ import("//brave/components/playlist/common/buildflags/buildflags.gni") import("//brave/components/request_otr/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") -import("//brave/components/web_discovery/common/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//brave/test/testing.gni") import("//brave/updater/config.gni") import("//chrome/common/features.gni") From a7d9315af2137478217f966cc8fa284997ed641c Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Thu, 8 Aug 2024 15:22:22 -0700 Subject: [PATCH 3/9] Address Web Discovery feedback --- .../webui/welcome_page/welcome_dom_handler.cc | 3 + browser/web_discovery/BUILD.gn | 9 +- .../web_discovery_service_factory.cc | 27 ++-- .../web_discovery_service_factory.h | 6 +- .../web_discovery_service_factory_unittest.cc | 33 ----- components/constants/BUILD.gn | 5 +- components/constants/DEPS | 1 + components/constants/pref_names.h | 3 + components/web_discovery/browser/BUILD.gn | 5 +- .../browser/background_credential_helper.cc | 128 +++++++++++++++++ .../browser/background_credential_helper.h | 59 ++++++++ .../browser/credential_manager.cc | 134 ++---------------- .../browser/credential_manager.h | 42 +----- .../browser/credential_manager_unittest.cc | 4 +- components/web_discovery/browser/patterns.cc | 26 ++-- components/web_discovery/browser/pref_names.h | 2 - components/web_discovery/browser/rsa.cc | 29 +++- components/web_discovery/browser/rsa.h | 4 +- .../browser/server_config_loader.cc | 117 ++++++--------- .../browser/server_config_loader.h | 7 +- .../browser/web_discovery_service.cc | 2 - components/web_discovery/common/BUILD.gn | 3 +- .../credential_keys_and_responses.json | 1 - 23 files changed, 312 insertions(+), 338 deletions(-) delete mode 100644 browser/web_discovery/web_discovery_service_factory_unittest.cc create mode 100644 components/web_discovery/browser/background_credential_helper.cc create mode 100644 components/web_discovery/browser/background_credential_helper.h diff --git a/browser/ui/webui/welcome_page/welcome_dom_handler.cc b/browser/ui/webui/welcome_page/welcome_dom_handler.cc index 0478aaaaab8a..b828846b04e9 100644 --- a/browser/ui/webui/welcome_page/welcome_dom_handler.cc +++ b/browser/ui/webui/welcome_page/welcome_dom_handler.cc @@ -21,6 +21,7 @@ #include "chrome/common/webui_url_constants.h" #include "chrome/grit/branded_strings.h" #include "components/prefs/pref_service.h" +#include "extensions/buildflags/buildflags.h" #include "ui/base/l10n/l10n_util.h" namespace { @@ -170,7 +171,9 @@ void WelcomeDOMHandler::HandleSetMetricsReportingEnabled( void WelcomeDOMHandler::HandleEnableWebDiscovery( const base::Value::List& args) { DCHECK(profile_); +#if BUILDFLAG(ENABLE_EXTENSIONS) profile_->GetPrefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); +#endif } void WelcomeDOMHandler::SetLocalStateBooleanEnabled( diff --git a/browser/web_discovery/BUILD.gn b/browser/web_discovery/BUILD.gn index 1ec447b9c281..991df6173997 100644 --- a/browser/web_discovery/BUILD.gn +++ b/browser/web_discovery/BUILD.gn @@ -17,6 +17,7 @@ if (enable_web_discovery_native) { "//brave/components/web_discovery/browser", "//brave/components/web_discovery/common", "//chrome/browser:browser_process", + "//chrome/browser/profiles:profile", "//chrome/common:constants", "//components/keyed_service/content", "//components/user_prefs", @@ -44,14 +45,6 @@ source_set("unit_tests") { "//content/test:test_support", "//testing/gtest", ] - - if (enable_web_discovery_native) { - sources += [ "web_discovery_service_factory_unittest.cc" ] - deps += [ - ":web_discovery", - "//brave/components/web_discovery/common", - ] - } } } diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc index 87522d736a7f..f6d0d29f2315 100644 --- a/browser/web_discovery/web_discovery_service_factory.cc +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -9,14 +9,25 @@ #include "brave/components/web_discovery/browser/web_discovery_service.h" #include "brave/components/web_discovery/common/features.h" #include "chrome/browser/browser_process.h" +#include "chrome/browser/profiles/profile_selections.h" #include "chrome/common/chrome_paths.h" -#include "components/keyed_service/content/browser_context_dependency_manager.h" #include "components/user_prefs/user_prefs.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/storage_partition.h" namespace web_discovery { +namespace { + +ProfileSelections GetProfileSelections() { + if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { + return ProfileSelections::BuildNoProfilesSelected(); + } + return ProfileSelections::BuildForRegularProfile(); +} + +} // namespace + WebDiscoveryService* WebDiscoveryServiceFactory::GetForBrowserContext( content::BrowserContext* context) { return static_cast( @@ -29,9 +40,8 @@ WebDiscoveryServiceFactory* WebDiscoveryServiceFactory::GetInstance() { } WebDiscoveryServiceFactory::WebDiscoveryServiceFactory() - : BrowserContextKeyedServiceFactory( - "WebDiscoveryService", - BrowserContextDependencyManager::GetInstance()) {} + : ProfileKeyedServiceFactory("WebDiscoveryService", + GetProfileSelections()) {} WebDiscoveryServiceFactory::~WebDiscoveryServiceFactory() = default; @@ -47,15 +57,6 @@ KeyedService* WebDiscoveryServiceFactory::BuildServiceInstanceFor( user_data_dir, shared_url_loader_factory); } -content::BrowserContext* WebDiscoveryServiceFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { - return nullptr; - } - // Prevents creation of service instance for incognito/OTR profiles - return context->IsOffTheRecord() ? nullptr : context; -} - bool WebDiscoveryServiceFactory::ServiceIsCreatedWithBrowserContext() const { return true; } diff --git a/browser/web_discovery/web_discovery_service_factory.h b/browser/web_discovery/web_discovery_service_factory.h index 48687f69b531..8e914b91b039 100644 --- a/browser/web_discovery/web_discovery_service_factory.h +++ b/browser/web_discovery/web_discovery_service_factory.h @@ -7,13 +7,13 @@ #define BRAVE_BROWSER_WEB_DISCOVERY_WEB_DISCOVERY_SERVICE_FACTORY_H_ #include "base/no_destructor.h" -#include "components/keyed_service/content/browser_context_keyed_service_factory.h" +#include "chrome/browser/profiles/profile_keyed_service_factory.h" namespace web_discovery { class WebDiscoveryService; -class WebDiscoveryServiceFactory : public BrowserContextKeyedServiceFactory { +class WebDiscoveryServiceFactory : public ProfileKeyedServiceFactory { public: static WebDiscoveryServiceFactory* GetInstance(); static WebDiscoveryService* GetForBrowserContext( @@ -31,8 +31,6 @@ class WebDiscoveryServiceFactory : public BrowserContextKeyedServiceFactory { KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; }; diff --git a/browser/web_discovery/web_discovery_service_factory_unittest.cc b/browser/web_discovery/web_discovery_service_factory_unittest.cc deleted file mode 100644 index cbda07dfa63b..000000000000 --- a/browser/web_discovery/web_discovery_service_factory_unittest.cc +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (c) 2024 The Brave Authors. All rights reserved. - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at https://mozilla.org/MPL/2.0/. */ - -#include "brave/browser/web_discovery/web_discovery_service_factory.h" - -#include "base/test/scoped_feature_list.h" -#include "brave/components/web_discovery/common/features.h" -#include "chrome/test/base/testing_browser_process.h" -#include "chrome/test/base/testing_profile_manager.h" -#include "content/public/test/browser_task_environment.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace web_discovery { - -TEST(WebDiscoveryServiceFactoryTest, PrivateNotCreated) { - base::test::ScopedFeatureList scoped_features( - features::kBraveWebDiscoveryNative); - content::BrowserTaskEnvironment task_environment; - auto* browser_process = TestingBrowserProcess::GetGlobal(); - TestingProfileManager profile_manager(browser_process); - ASSERT_TRUE(profile_manager.SetUp()); - - auto* profile = profile_manager.CreateTestingProfile("test"); - - EXPECT_TRUE(WebDiscoveryServiceFactory::GetForBrowserContext(profile)); - EXPECT_FALSE(WebDiscoveryServiceFactory::GetForBrowserContext( - profile->GetOffTheRecordProfile( - Profile::OTRProfileID::CreateUniqueForTesting(), true))); -} - -} // namespace web_discovery diff --git a/components/constants/BUILD.gn b/components/constants/BUILD.gn index e5aa1ab647ab..0c078e684f71 100644 --- a/components/constants/BUILD.gn +++ b/components/constants/BUILD.gn @@ -29,7 +29,10 @@ source_set("constants") { ] public_deps = [ ":brave_services_key" ] - deps = [ "//base" ] + deps = [ + "//base", + "//extensions/buildflags", + ] } source_set("brave_service_key_helper") { diff --git a/components/constants/DEPS b/components/constants/DEPS index 08b52cb8cbf3..055e4869975b 100644 --- a/components/constants/DEPS +++ b/components/constants/DEPS @@ -1,3 +1,4 @@ include_rules = [ + "+extensions/buildflags", "+extensions/common", ] diff --git a/components/constants/pref_names.h b/components/constants/pref_names.h index c0c832447925..8c459d21b0ed 100644 --- a/components/constants/pref_names.h +++ b/components/constants/pref_names.h @@ -7,6 +7,7 @@ #define BRAVE_COMPONENTS_CONSTANTS_PREF_NAMES_H_ #include "build/build_config.h" +#include "extensions/buildflags/buildflags.h" inline constexpr char kBraveAutofillPrivateWindows[] = "brave.autofill_private_windows"; @@ -85,8 +86,10 @@ inline constexpr char kBraveShieldsSettingsVersion[] = inline constexpr char kDefaultBrowserPromptEnabled[] = "brave.default_browser_prompt_enabled"; +#if BUILDFLAG(ENABLE_EXTENSIONS) inline constexpr char kWebDiscoveryExtensionEnabled[] = "brave.web_discovery_enabled"; +#endif inline constexpr char kWebDiscoveryCTAState[] = "brave.web_discovery.cta_state"; inline constexpr char kDontAskEnableWebDiscovery[] = "brave.dont_ask_enable_web_discovery"; diff --git a/components/web_discovery/browser/BUILD.gn b/components/web_discovery/browser/BUILD.gn index dc19dce58ff2..16e4467c5f15 100644 --- a/components/web_discovery/browser/BUILD.gn +++ b/components/web_discovery/browser/BUILD.gn @@ -7,8 +7,11 @@ import("//brave/components/web_discovery/buildflags/buildflags.gni") assert(enable_web_discovery_native) -static_library("browser") { +component("browser") { + output_name = "web_discovery_browser" sources = [ + "background_credential_helper.cc", + "background_credential_helper.h", "credential_manager.cc", "credential_manager.h", "credential_signer.h", diff --git a/components/web_discovery/browser/background_credential_helper.cc b/components/web_discovery/browser/background_credential_helper.cc new file mode 100644 index 000000000000..8eaf90135f13 --- /dev/null +++ b/components/web_discovery/browser/background_credential_helper.cc @@ -0,0 +1,128 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/components/web_discovery/browser/background_credential_helper.h" + +#include + +#include "base/base64.h" +#include "base/containers/span_rust.h" +#include "base/logging.h" +#include "base/threading/thread_restrictions.h" +#include "crypto/sha2.h" + +namespace web_discovery { + +BackgroundCredentialHelper::BackgroundCredentialHelper() + : anonymous_credential_manager_( + anonymous_credentials::new_credential_manager()) {} + +BackgroundCredentialHelper::~BackgroundCredentialHelper() = default; + +void BackgroundCredentialHelper::UseFixedSeedForTesting() { + anonymous_credential_manager_ = + anonymous_credentials::new_credential_manager_with_fixed_seed(); +} + +std::unique_ptr BackgroundCredentialHelper::GenerateRSAKey() { + auto key_pair = GenerateRSAKeyPair(); + if (!key_pair) { + return nullptr; + } + rsa_private_key_ = std::move(key_pair->key_pair); + return key_pair; +} + +void BackgroundCredentialHelper::SetRSAKey( + std::unique_ptr rsa_private_key) { + rsa_private_key_ = std::move(rsa_private_key); +} + +std::optional +BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { + base::AssertLongCPUWorkAllowed(); + CHECK(rsa_private_key_); + auto challenge = crypto::SHA256Hash(base::as_byte_span(pre_challenge)); + + auto join_request = anonymous_credential_manager_->start_join( + base::SpanToRustSlice(challenge)); + + auto signature = RSASign(rsa_private_key_.get(), join_request.join_request); + + if (!signature) { + VLOG(1) << "RSA signature failed"; + return std::nullopt; + } + + return GenerateJoinRequestResult{.start_join_result = join_request, + .signature = *signature}; +} + +std::optional BackgroundCredentialHelper::FinishJoin( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes) { + base::AssertLongCPUWorkAllowed(); + auto pub_key_result = anonymous_credentials::load_group_public_key( + base::SpanToRustSlice(group_pub_key)); + auto gsk_result = + anonymous_credentials::load_credential_big(base::SpanToRustSlice(gsk)); + auto join_resp_result = anonymous_credentials::load_join_response( + base::SpanToRustSlice(join_resp_bytes)); + if (!pub_key_result.error_message.empty() || + !gsk_result.error_message.empty() || + !join_resp_result.error_message.empty()) { + VLOG(1) << "Failed to finish credential join due to deserialization error " + "with group pub key, gsk, or join response: " + << pub_key_result.error_message.c_str() + << gsk_result.error_message.c_str() + << join_resp_result.error_message.c_str(); + return std::nullopt; + } + auto finish_res = anonymous_credential_manager_->finish_join( + *pub_key_result.value, *gsk_result.value, + std::move(join_resp_result.value)); + if (!finish_res.error_message.empty()) { + VLOG(1) << "Failed to finish credential join for " << date << ": " + << finish_res.error_message.c_str(); + return std::nullopt; + } + return base::Base64Encode(finish_res.data); +} + +std::optional> +BackgroundCredentialHelper::PerformSign( + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes) { + base::AssertLongCPUWorkAllowed(); + if (gsk_bytes && credential_bytes) { + auto gsk_result = anonymous_credentials::load_credential_big( + base::SpanToRustSlice(*gsk_bytes)); + auto credential_result = anonymous_credentials::load_user_credentials( + base::SpanToRustSlice(*credential_bytes)); + if (!gsk_result.error_message.empty() || + !credential_result.error_message.empty()) { + VLOG(1) << "Failed to sign due to deserialization error with gsk, or " + "user credential: " + << gsk_result.error_message.c_str() + << credential_result.error_message.c_str(); + return std::nullopt; + } + anonymous_credential_manager_->set_gsk_and_credentials( + std::move(gsk_result.value), std::move(credential_result.value)); + } + auto sig_res = anonymous_credential_manager_->sign( + base::SpanToRustSlice(msg), base::SpanToRustSlice(basename)); + if (!sig_res.error_message.empty()) { + VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); + return std::nullopt; + } + return std::vector(sig_res.data.begin(), sig_res.data.end()); +} + +} // namespace web_discovery diff --git a/components/web_discovery/browser/background_credential_helper.h b/components/web_discovery/browser/background_credential_helper.h new file mode 100644 index 000000000000..970efe823191 --- /dev/null +++ b/components/web_discovery/browser/background_credential_helper.h @@ -0,0 +1,59 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ +#define BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ + +#include +#include +#include +#include + +#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/rsa.h" +#include "crypto/rsa_private_key.h" + +namespace web_discovery { + +struct GenerateJoinRequestResult { + anonymous_credentials::StartJoinResult start_join_result; + std::string signature; +}; + +class BackgroundCredentialHelper { + public: + BackgroundCredentialHelper(); + ~BackgroundCredentialHelper(); + + BackgroundCredentialHelper(const BackgroundCredentialHelper&) = delete; + BackgroundCredentialHelper& operator=(const BackgroundCredentialHelper&) = + delete; + + void UseFixedSeedForTesting(); + + std::unique_ptr GenerateRSAKey(); + void SetRSAKey(std::unique_ptr rsa_private_key); + std::optional GenerateJoinRequest( + std::string pre_challenge); + std::optional FinishJoin( + std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes); + std::optional> PerformSign( + std::vector msg, + std::vector basename, + std::optional> gsk_bytes, + std::optional> credential_bytes); + + private: + rust::Box + anonymous_credential_manager_; + std::unique_ptr rsa_private_key_; +}; + +} // namespace web_discovery + +#endif // BRAVE_COMPONENTS_WEB_DISCOVERY_BROWSER_BACKGROUND_CREDENTIAL_HELPER_H_ diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 0c5ccca25dc1..495a314e35bf 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -9,22 +9,16 @@ #include "base/base64.h" #include "base/containers/span.h" -#include "base/containers/span_rust.h" #include "base/functional/bind.h" #include "base/json/json_reader.h" #include "base/json/json_writer.h" #include "base/logging.h" -#include "base/task/sequenced_task_runner.h" #include "base/task/thread_pool.h" -#include "base/threading/thread_restrictions.h" -#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" #include "brave/components/web_discovery/browser/pref_names.h" #include "brave/components/web_discovery/browser/rsa.h" #include "brave/components/web_discovery/browser/util.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" -#include "crypto/rsa_private_key.h" -#include "crypto/sha2.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" @@ -66,116 +60,6 @@ constexpr net::NetworkTrafficAnnotationTag kJoinNetworkTrafficAnnotation = } // namespace -BackgroundCredentialHelper::BackgroundCredentialHelper() - : anonymous_credential_manager_( - anonymous_credentials::new_credential_manager()) {} - -BackgroundCredentialHelper::~BackgroundCredentialHelper() = default; - -void BackgroundCredentialHelper::UseFixedSeedForTesting() { - anonymous_credential_manager_ = - anonymous_credentials::new_credential_manager_with_fixed_seed(); -} - -std::unique_ptr BackgroundCredentialHelper::GenerateRSAKey() { - auto key_pair = GenerateRSAKeyPair(); - if (!key_pair) { - return nullptr; - } - rsa_private_key_ = std::move(key_pair->key_pair); - return key_pair; -} - -void BackgroundCredentialHelper::SetRSAKey( - std::unique_ptr rsa_private_key) { - rsa_private_key_ = std::move(rsa_private_key); -} - -std::optional -BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { - base::AssertLongCPUWorkAllowed(); - CHECK(rsa_private_key_); - auto challenge = crypto::SHA256Hash(base::as_byte_span(pre_challenge)); - - auto join_request = anonymous_credential_manager_->start_join( - base::SpanToRustSlice(challenge)); - - auto signature = RSASign(rsa_private_key_.get(), join_request.join_request); - - if (!signature) { - VLOG(1) << "RSA signature failed"; - return std::nullopt; - } - - return GenerateJoinRequestResult{.start_join_result = join_request, - .signature = *signature}; -} - -std::optional BackgroundCredentialHelper::FinishJoin( - std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes) { - base::AssertLongCPUWorkAllowed(); - auto pub_key_result = anonymous_credentials::load_group_public_key( - base::SpanToRustSlice(group_pub_key)); - auto gsk_result = - anonymous_credentials::load_credential_big(base::SpanToRustSlice(gsk)); - auto join_resp_result = anonymous_credentials::load_join_response( - base::SpanToRustSlice(join_resp_bytes)); - if (!pub_key_result.error_message.empty() || - !gsk_result.error_message.empty() || - !join_resp_result.error_message.empty()) { - VLOG(1) << "Failed to finish credential join due to deserialization error " - "with group pub key, gsk, or join response: " - << pub_key_result.error_message.c_str() - << gsk_result.error_message.c_str() - << join_resp_result.error_message.c_str(); - return std::nullopt; - } - auto finish_res = anonymous_credential_manager_->finish_join( - *pub_key_result.value, *gsk_result.value, - std::move(join_resp_result.value)); - if (!finish_res.error_message.empty()) { - VLOG(1) << "Failed to finish credential join for " << date << ": " - << finish_res.error_message.c_str(); - return std::nullopt; - } - return base::Base64Encode(finish_res.data); -} - -std::optional> -BackgroundCredentialHelper::PerformSign( - std::vector msg, - std::vector basename, - std::optional> gsk_bytes, - std::optional> credential_bytes) { - base::AssertLongCPUWorkAllowed(); - if (gsk_bytes && credential_bytes) { - auto gsk_result = anonymous_credentials::load_credential_big( - base::SpanToRustSlice(*gsk_bytes)); - auto credential_result = anonymous_credentials::load_user_credentials( - base::SpanToRustSlice(*credential_bytes)); - if (!gsk_result.error_message.empty() || - !credential_result.error_message.empty()) { - VLOG(1) << "Failed to sign due to deserialization error with gsk, or " - "user credential: " - << gsk_result.error_message.c_str() - << credential_result.error_message.c_str(); - return std::nullopt; - } - anonymous_credential_manager_->set_gsk_and_credentials( - std::move(gsk_result.value), std::move(credential_result.value)); - } - auto sig_res = anonymous_credential_manager_->sign( - base::SpanToRustSlice(msg), base::SpanToRustSlice(basename)); - if (!sig_res.error_message.empty()) { - VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); - return std::nullopt; - } - return std::vector(sig_res.data.begin(), sig_res.data.end()); -} - CredentialManager::CredentialManager( PrefService* profile_prefs, network::SharedURLLoaderFactory* shared_url_loader_factory, @@ -193,22 +77,22 @@ CredentialManager::~CredentialManager() = default; bool CredentialManager::LoadRSAKey() { std::string private_key_b64 = profile_prefs_->GetString(kCredentialRSAPrivateKey); - rsa_public_key_b64_ = profile_prefs_->GetString(kCredentialRSAPublicKey); - if (private_key_b64.empty() || rsa_public_key_b64_->empty()) { - rsa_public_key_b64_ = std::nullopt; + if (private_key_b64.empty()) { return true; } - auto key_pair = ImportRSAKeyPair(private_key_b64); - if (!key_pair) { + auto key_info = ImportRSAKeyPair(private_key_b64); + if (!key_info) { VLOG(1) << "Failed to import stored RSA key"; - rsa_public_key_b64_ = std::nullopt; return false; } + + CHECK(key_info->key_pair); + rsa_public_key_b64_ = std::move(key_info->public_key_b64); background_credential_helper_ .AsyncCall(&BackgroundCredentialHelper::SetRSAKey) - .WithArgs(std::move(key_pair)); + .WithArgs(std::move(key_info->key_pair)); return true; } @@ -220,10 +104,10 @@ void CredentialManager::OnNewRSAKey(std::unique_ptr key_info) { } rsa_public_key_b64_ = key_info->public_key_b64; + CHECK(key_info->key_pair && key_info->private_key_b64); profile_prefs_->SetString(kCredentialRSAPrivateKey, - key_info->private_key_b64); - profile_prefs_->SetString(kCredentialRSAPublicKey, *rsa_public_key_b64_); + *key_info->private_key_b64); JoinGroups(); } diff --git a/components/web_discovery/browser/credential_manager.h b/components/web_discovery/browser/credential_manager.h index 18841571de24..1fa9d9eb4706 100644 --- a/components/web_discovery/browser/credential_manager.h +++ b/components/web_discovery/browser/credential_manager.h @@ -11,16 +11,13 @@ #include #include -#include "base/functional/callback.h" #include "base/memory/raw_ptr.h" #include "base/task/sequenced_task_runner.h" #include "base/threading/sequence_bound.h" #include "base/timer/wall_clock_timer.h" -#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/background_credential_helper.h" #include "brave/components/web_discovery/browser/credential_signer.h" -#include "brave/components/web_discovery/browser/rsa.h" #include "brave/components/web_discovery/browser/server_config_loader.h" -#include "crypto/rsa_private_key.h" #include "net/base/backoff_entry.h" class PrefService; @@ -32,43 +29,6 @@ class SimpleURLLoader; namespace web_discovery { -struct GenerateJoinRequestResult { - anonymous_credentials::StartJoinResult start_join_result; - std::string signature; -}; - -class BackgroundCredentialHelper { - public: - BackgroundCredentialHelper(); - ~BackgroundCredentialHelper(); - - BackgroundCredentialHelper(const BackgroundCredentialHelper&) = delete; - BackgroundCredentialHelper& operator=(const BackgroundCredentialHelper&) = - delete; - - void UseFixedSeedForTesting(); - - std::unique_ptr GenerateRSAKey(); - void SetRSAKey(std::unique_ptr rsa_private_key); - std::optional GenerateJoinRequest( - std::string pre_challenge); - std::optional FinishJoin( - std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes); - std::optional> PerformSign( - std::vector msg, - std::vector basename, - std::optional> gsk_bytes, - std::optional> credential_bytes); - - private: - rust::Box - anonymous_credential_manager_; - std::unique_ptr rsa_private_key_; -}; - // Manages and utilizes anonymous credentials used for communicating // with Web Discovery servers. These Direct Anonymous Attestation credentials // are used to prevent Sybil attacks on the servers. diff --git a/components/web_discovery/browser/credential_manager_unittest.cc b/components/web_discovery/browser/credential_manager_unittest.cc index b7237ced323a..d3efa10710f1 100644 --- a/components/web_discovery/browser/credential_manager_unittest.cc +++ b/components/web_discovery/browser/credential_manager_unittest.cc @@ -56,12 +56,10 @@ class WebDiscoveryCredentialManagerTest : public testing::Test { ASSERT_TRUE(test_data_value); const auto& test_data_dict = test_data_value->GetDict(); const auto* rsa_priv_key = test_data_dict.FindString("rsa_priv_key"); - const auto* rsa_pub_key = test_data_dict.FindString("rsa_pub_key"); const auto* group_pub_key = test_data_dict.FindString("group_pub_key"); const auto* join_responses = test_data_dict.FindDict("join_responses"); - ASSERT_TRUE(rsa_priv_key && rsa_pub_key && group_pub_key && join_responses); + ASSERT_TRUE(rsa_priv_key && group_pub_key && join_responses); - profile_prefs_.SetString(kCredentialRSAPublicKey, *rsa_pub_key); profile_prefs_.SetString(kCredentialRSAPrivateKey, *rsa_priv_key); server_config_loader_ = std::make_unique( diff --git a/components/web_discovery/browser/patterns.cc b/components/web_discovery/browser/patterns.cc index d0e7457dd5cd..dea62ea3b36d 100644 --- a/components/web_discovery/browser/patterns.cc +++ b/components/web_discovery/browser/patterns.cc @@ -67,8 +67,8 @@ bool ParsePayloadRule(const base::Value& rule_value, PayloadRule* rule_out) { VLOG(1) << "Selector or key missing from payload rule"; return false; } - rule_out->selector = *selector; - rule_out->key = *payload_key; + rule_out->selector = std::move(*selector); + rule_out->key = std::move(*payload_key); if (rule_list->size() > 2) { auto* field_action = (*rule_list)[2].GetIfString(); if (field_action && *field_action == kJoinFieldAction) { @@ -103,8 +103,8 @@ std::optional> ParsePayloadRules( VLOG(1) << "Payload rule or result types unknown"; return std::nullopt; } - rule_group_it->action = *action; - rule_group_it->key = key; + rule_group_it->action = std::move(*action); + rule_group_it->key = std::move(key); rule_group_it->result_type = result_type_it->second; rule_group_it->rule_type = rule_type_it->second; if (auto* fields = rule_group_dict->FindList(kFieldsKey)) { @@ -127,16 +127,12 @@ std::optional> ParsePayloadRules( RefineFunctionList ParseFunctionsApplied(const base::Value::List* list) { CHECK(list); RefineFunctionList result; - for (const auto& function_val : *list) { - const auto* function_list = function_val.GetIfList(); + for (auto& function_val : *list) { + auto* function_list = function_val.GetIfList(); if (!function_list || function_list->size() <= 1) { continue; } - base::Value::List function_vec; - for (const auto& element : *function_list) { - function_vec.Append(element.Clone()); - } - result.push_back(std::move(function_vec)); + result.push_back(function_list->Clone()); } return result; } @@ -175,9 +171,9 @@ std::optional> ParseScrapeRules( rule->rule_type = rule_type_it->second; } } - rule->attribute = *attribute; + rule->attribute = std::move(*attribute); if (sub_selector) { - rule->sub_selector = *sub_selector; + rule->sub_selector = std::move(*sub_selector); } if (functions_applied) { rule->functions_applied = ParseFunctionsApplied(functions_applied); @@ -229,7 +225,7 @@ std::optional> ParsePatternsURLDetails( VLOG(1) << "ID or scrape dict missing for pattern"; return std::nullopt; } - details.id = *id; + details.id = std::move(*id); details.is_search_engine = base::Contains(*search_engines_list, i_str); @@ -250,7 +246,7 @@ std::optional> ParsePatternsURLDetails( if (query_template_dict) { auto* prefix = query_template_dict->FindString(kQueryTemplatePrefixKey); if (prefix) { - details.search_template_prefix = *prefix; + details.search_template_prefix = std::move(*prefix); } } } diff --git a/components/web_discovery/browser/pref_names.h b/components/web_discovery/browser/pref_names.h index b2f39a2cb7f6..6f6e1ecb3e7f 100644 --- a/components/web_discovery/browser/pref_names.h +++ b/components/web_discovery/browser/pref_names.h @@ -18,8 +18,6 @@ inline constexpr char kWebDiscoveryNativeEnabled[] = // they do not require secure storage. inline constexpr char kCredentialRSAPrivateKey[] = "brave.web_discovery.rsa_priv_key"; -inline constexpr char kCredentialRSAPublicKey[] = - "brave.web_discovery.rsa_pub_key"; inline constexpr char kAnonymousCredentialsDict[] = "brave.web_discovery.anon_creds"; diff --git a/components/web_discovery/browser/rsa.cc b/components/web_discovery/browser/rsa.cc index 6b2b50cf16bd..1dc2ec67e253 100644 --- a/components/web_discovery/browser/rsa.cc +++ b/components/web_discovery/browser/rsa.cc @@ -26,37 +26,54 @@ RSAKeyInfo::~RSAKeyInfo() = default; std::unique_ptr GenerateRSAKeyPair() { base::AssertLongCPUWorkAllowed(); - auto info = std::make_unique(); auto private_key = crypto::RSAPrivateKey::Create(kRsaKeySize); if (!private_key) { return nullptr; } - info->key_pair = std::move(private_key); + auto key_pair = std::move(private_key); std::vector encoded_public_key; std::vector encoded_private_key; - if (!info->key_pair->ExportPrivateKey(&encoded_private_key) || - !info->key_pair->ExportPublicKey(&encoded_public_key)) { + if (!key_pair->ExportPrivateKey(&encoded_private_key) || + !key_pair->ExportPublicKey(&encoded_public_key)) { return nullptr; } + auto info = std::make_unique(); + + info->key_pair = std::move(key_pair); info->public_key_b64 = base::Base64Encode(encoded_public_key); info->private_key_b64 = base::Base64Encode(encoded_private_key); return info; } -std::unique_ptr ImportRSAKeyPair( +std::unique_ptr ImportRSAKeyPair( const std::string& private_key_b64) { auto decoded_key = base::Base64Decode(private_key_b64); if (!decoded_key) { return nullptr; } - return crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(*decoded_key); + auto key_pair = crypto::RSAPrivateKey::CreateFromPrivateKeyInfo(*decoded_key); + if (!key_pair) { + return nullptr; + } + + std::vector encoded_public_key; + if (!key_pair->ExportPublicKey(&encoded_public_key)) { + return nullptr; + } + + auto info = std::make_unique(); + + info->key_pair = std::move(key_pair); + info->public_key_b64 = base::Base64Encode(encoded_public_key); + + return info; } std::optional RSASign(crypto::RSAPrivateKey* key, diff --git a/components/web_discovery/browser/rsa.h b/components/web_discovery/browser/rsa.h index e940649fcdd1..35861bd8ec61 100644 --- a/components/web_discovery/browser/rsa.h +++ b/components/web_discovery/browser/rsa.h @@ -19,13 +19,13 @@ struct RSAKeyInfo { RSAKeyInfo(); ~RSAKeyInfo(); std::unique_ptr key_pair; - std::string private_key_b64; + std::optional private_key_b64; std::string public_key_b64; }; std::unique_ptr GenerateRSAKeyPair(); -std::unique_ptr ImportRSAKeyPair( +std::unique_ptr ImportRSAKeyPair( const std::string& private_key_b64); std::optional RSASign(crypto::RSAPrivateKey* key, diff --git a/components/web_discovery/browser/server_config_loader.cc b/components/web_discovery/browser/server_config_loader.cc index b9ecd4bbeac2..ae01a5770601 100644 --- a/components/web_discovery/browser/server_config_loader.cc +++ b/components/web_discovery/browser/server_config_loader.cc @@ -88,7 +88,7 @@ KeyMap ParseKeys(const base::Value::Dict& encoded_keys) { if (!decoded_data) { continue; } - map[date] = *decoded_data; + map[date] = std::move(*decoded_data); } return map; } @@ -107,9 +107,9 @@ ParseSourceMapActionConfigs(const base::Value::Dict& configs_dict) { } auto* keys_list = config_dict->FindList(kKeysFieldName); if (keys_list) { - for (const auto& key_val : *keys_list) { + for (auto& key_val : *keys_list) { if (key_val.is_string()) { - action_config->keys.push_back(key_val.GetString()); + action_config->keys.push_back(std::move(key_val.GetString())); } } } @@ -122,26 +122,35 @@ ParseSourceMapActionConfigs(const base::Value::Dict& configs_dict) { return map; } -std::optional GunzipContents(std::string gzipped_contents) { - base::AssertLongCPUWorkAllowed(); - std::string result; - if (!compression::GzipUncompress(gzipped_contents, &result)) { - return std::nullopt; +std::unique_ptr ParseAndWritePatternsFile( + base::FilePath patterns_path, + std::string gzipped_contents) { + std::string uncompressed_contents; + if (!compression::GzipUncompress(gzipped_contents, &uncompressed_contents)) { + VLOG(1) << "Failed to uncompress patterns"; + return nullptr; + } + auto patterns = ParsePatterns(uncompressed_contents); + if (!patterns) { + return nullptr; } - return result; -} -bool WritePatternsFile(base::FilePath patterns_path, std::string contents) { - return base::WriteFile(patterns_path, contents); + if (!base::WriteFile(patterns_path, uncompressed_contents)) { + VLOG(1) << "Failed to write patterns file"; + return nullptr; + } + return patterns; } -std::optional ReadPatternsFile(base::FilePath patterns_path) { +std::unique_ptr ReadAndParsePatternsFile( + base::FilePath patterns_path) { std::string contents; if (!base::ReadFileToStringWithMaxSize(patterns_path, &contents, kPatternsMaxFileSize)) { - return std::nullopt; + VLOG(1) << "Failed to read local patterns file"; + return nullptr; } - return contents; + return ParsePatterns(contents); } std::unique_ptr ProcessConfigResponses( @@ -196,7 +205,7 @@ std::unique_ptr ProcessConfigResponses( const auto* location = quorum_root->FindString(kLocationFieldName); if (location && kAllowedReportLocations.contains(*location)) { - config->location = *location; + config->location = std::move(*location); } else { config->location = kOmittedLocationValue; } @@ -219,7 +228,8 @@ ServerConfig::~ServerConfig() = default; ServerConfigDownloadResult::ServerConfigDownloadResult( bool is_collector_config, std::optional response_body) - : is_collector_config(is_collector_config), response_body(response_body) {} + : is_collector_config(is_collector_config), + response_body(std::move(response_body)) {} ServerConfigDownloadResult::~ServerConfigDownloadResult() = default; ServerConfigDownloadResult::ServerConfigDownloadResult( const ServerConfigDownloadResult&) = default; @@ -288,7 +298,8 @@ void ServerConfigLoader::LoadConfigs() { auto make_download_result = [](bool is_collector_config, std::optional response_body) { - return ServerConfigDownloadResult(is_collector_config, response_body); + return ServerConfigDownloadResult(is_collector_config, + std::move(response_body)); }; auto collector_callback = @@ -307,20 +318,19 @@ void ServerConfigLoader::LoadConfigs() { void ServerConfigLoader::OnConfigResponsesDownloaded( std::vector results) { CHECK_EQ(results.size(), 2u); - const std::optional* collector_response_body = nullptr; - const std::optional* quorum_response_body = nullptr; + std::optional collector_response_body; + std::optional quorum_response_body; for (const auto& result : results) { if (result.is_collector_config) { - collector_response_body = &result.response_body; + collector_response_body = std::move(result.response_body); } else { - quorum_response_body = &result.response_body; + quorum_response_body = std::move(result.response_body); } } - CHECK(collector_response_body && quorum_response_body); auto* collector_response_info = collector_config_url_loader_->ResponseInfo(); auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); - if (!*collector_response_body || !*quorum_response_body || + if (!collector_response_body || !quorum_response_body || !collector_response_info || !quorum_response_info || collector_response_info->headers->response_code() != 200 || quorum_response_info->headers->response_code() != 200) { @@ -331,8 +341,9 @@ void ServerConfigLoader::OnConfigResponsesDownloaded( background_task_runner_->PostTaskAndReplyWithResult( FROM_HERE, - base::BindOnce(&ProcessConfigResponses, **collector_response_body, - **quorum_response_body), + base::BindOnce(&ProcessConfigResponses, + std::move(*collector_response_body), + std::move(*quorum_response_body)), base::BindOnce(&ServerConfigLoader::OnConfigResponsesProcessed, weak_ptr_factory_.GetWeakPtr())); } @@ -366,21 +377,7 @@ void ServerConfigLoader::OnConfigResponsesProcessed( void ServerConfigLoader::LoadStoredPatterns() { background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ReadPatternsFile, patterns_path_), - base::BindOnce(&ServerConfigLoader::OnPatternsFileLoaded, - weak_ptr_factory_.GetWeakPtr())); -} - -void ServerConfigLoader::OnPatternsFileLoaded( - std::optional patterns_json) { - if (!patterns_json) { - VLOG(1) << "Failed to load local patterns file"; - local_state_->ClearPref(kPatternsRetrievalTime); - SchedulePatternsRequest(); - return; - } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + FROM_HERE, base::BindOnce(&ReadAndParsePatternsFile, patterns_path_), base::BindOnce(&ServerConfigLoader::OnStoredPatternsParsed, weak_ptr_factory_.GetWeakPtr())); } @@ -447,22 +444,11 @@ void ServerConfigLoader::OnPatternsResponse( return; } background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&GunzipContents, *response_body), - base::BindOnce(&ServerConfigLoader::OnPatternsGunzip, - weak_ptr_factory_.GetWeakPtr())); -} - -void ServerConfigLoader::OnPatternsGunzip( - std::optional patterns_json) { - if (!patterns_json) { - VLOG(1) << "Failed to decompress patterns file"; - HandlePatternsStatus(false); - return; - } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, base::BindOnce(&ParsePatterns, *patterns_json), + FROM_HERE, + base::BindOnce(&ParseAndWritePatternsFile, patterns_path_, + std::move(*response_body)), base::BindOnce(&ServerConfigLoader::OnNewPatternsParsed, - weak_ptr_factory_.GetWeakPtr(), *patterns_json)); + weak_ptr_factory_.GetWeakPtr())); } void ServerConfigLoader::OnStoredPatternsParsed( @@ -477,31 +463,14 @@ void ServerConfigLoader::OnStoredPatternsParsed( } void ServerConfigLoader::OnNewPatternsParsed( - std::string new_patterns_json, std::unique_ptr parsed_patterns) { if (!parsed_patterns) { HandlePatternsStatus(false); return; } - background_task_runner_->PostTaskAndReplyWithResult( - FROM_HERE, - base::BindOnce(&WritePatternsFile, patterns_path_, new_patterns_json), - base::BindOnce(&ServerConfigLoader::OnPatternsWritten, - weak_ptr_factory_.GetWeakPtr(), - std::move(parsed_patterns))); -} - -void ServerConfigLoader::OnPatternsWritten( - std::unique_ptr parsed_group, - bool result) { - if (!result) { - VLOG(1) << "Failed to write patterns file"; - HandlePatternsStatus(false); - return; - } local_state_->SetTime(kPatternsRetrievalTime, base::Time::Now()); HandlePatternsStatus(true); - last_loaded_patterns_ = std::move(parsed_group); + last_loaded_patterns_ = std::move(parsed_patterns); patterns_callback_.Run(); } diff --git a/components/web_discovery/browser/server_config_loader.h b/components/web_discovery/browser/server_config_loader.h index 552735976493..4331592db277 100644 --- a/components/web_discovery/browser/server_config_loader.h +++ b/components/web_discovery/browser/server_config_loader.h @@ -109,16 +109,11 @@ class ServerConfigLoader { void OnConfigResponsesProcessed(std::unique_ptr config); void LoadStoredPatterns(); - void OnPatternsFileLoaded(std::optional patterns_json); void SchedulePatternsRequest(); void RequestPatterns(); void OnPatternsResponse(std::optional response_body); - void OnPatternsGunzip(std::optional patterns_json); void OnStoredPatternsParsed(std::unique_ptr parsed_patterns); - void OnNewPatternsParsed(std::string new_patterns_json, - std::unique_ptr parsed_patterns); - void OnPatternsWritten(std::unique_ptr parsed_group, - bool result); + void OnNewPatternsParsed(std::unique_ptr parsed_patterns); void HandlePatternsStatus(bool result); const raw_ptr local_state_; diff --git a/components/web_discovery/browser/web_discovery_service.cc b/components/web_discovery/browser/web_discovery_service.cc index 848d89ec289b..2be6eee2c7c0 100644 --- a/components/web_discovery/browser/web_discovery_service.cc +++ b/components/web_discovery/browser/web_discovery_service.cc @@ -59,7 +59,6 @@ void WebDiscoveryService::RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(kWebDiscoveryNativeEnabled, false); registry->RegisterDictionaryPref(kAnonymousCredentialsDict); registry->RegisterStringPref(kCredentialRSAPrivateKey, {}); - registry->RegisterStringPref(kCredentialRSAPublicKey, {}); } void WebDiscoveryService::SetExtensionPrefIfNativeDisabled( @@ -102,7 +101,6 @@ void WebDiscoveryService::Stop() { void WebDiscoveryService::ClearPrefs() { profile_prefs_->ClearPref(kAnonymousCredentialsDict); profile_prefs_->ClearPref(kCredentialRSAPrivateKey); - profile_prefs_->ClearPref(kCredentialRSAPublicKey); } void WebDiscoveryService::OnEnabledChange() { diff --git a/components/web_discovery/common/BUILD.gn b/components/web_discovery/common/BUILD.gn index 6ce00a413af9..36b3ff34852e 100644 --- a/components/web_discovery/common/BUILD.gn +++ b/components/web_discovery/common/BUILD.gn @@ -7,7 +7,8 @@ import("//brave/components/web_discovery/buildflags/buildflags.gni") assert(enable_web_discovery_native) -static_library("common") { +component("common") { + output_name = "web_discovery_common" sources = [ "features.cc", "features.h", diff --git a/test/data/web_discovery/credential_keys_and_responses.json b/test/data/web_discovery/credential_keys_and_responses.json index 9fbc3cce99e8..46d9d85f1bc4 100644 --- a/test/data/web_discovery/credential_keys_and_responses.json +++ b/test/data/web_discovery/credential_keys_and_responses.json @@ -1,6 +1,5 @@ { "rsa_priv_key": "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCsD7x4pzqvukjWYyPQiH4KTgxV+ep6hsW+3V3z5vo0M86a4fXcvPnVJ2N4doYz3CsC4YJWftZKiLyY1h/e8ZB9ERd6bkaxRD6lXg26Gry/YPO+gFlhszolLPnFxYaBtTTq4Pt34xseD5jQkiTNXSYNSbpxX/ZNiCn/C55IcUWf7LL6Buufr1ckqz4GIFPclV3+W1+qCb82I3ToMjluKIcApHjXhvZmw7EPogAaxFHXWJmixAE0EVIesBR+DGlPZqqDayiOw2BKZl5zlzvnL04AGyFX7/BWUBZthJuNb6whY20WoU0pZTsf92W0AFthafW4CqPqGXK+QQe4fbtG7f+dAgMBAAECggEAHiUbph/WXldKz5TK/4wKWQ/XhXClrhXSq1/pSAQdreuttOEFzEinlLqz6LULSia2umh8B19td92A/V32c37rC55k+KQ9am1EdICH8yUgEH+R9LxT7JQUCdNZZ1b1+9+dh9Em/Zgidh/RbClOnVRGiGl0asyfQHIsuWx1rMd7pUrvBufy/bEFbcEio7r009qM8CxluS6zRmXO4tWNoZFu42JYjJcEnU+a1mRJ06n1GaVJ0dLd1IA5g4pvVF8wy6R3bQb9xRiwLQMBuG0vLhm69YeyJwHShE5S4WqsubtcxkiZrPak7MLlzwsxk1wU52PbTk1T3geqH5JIerwJ5g7CGQKBgQDq17z9e9OedERboUgXU/nZpSWOqZp5Ukw/dx2xWVtMKhGg6wWoIvadDEeke9RB6JbAlSmBcvT5A8D8P0/1OiatK0i2Yyj1MzuxACnJkYaPjp0Xl4wpZJH1t4QkWEpztPx/JdY6O08WYAbdl0GJhNbL5rqCOS3FZhLpj9OH42IRgwKBgQC7kA0nfifnRRoiL2NwVLeAuG2O4b8JfRPehLw63kl/C4VVbZt0P99EqZwUuKv/nvlrvAfBinwhXvtWhX3GvLUnAQm5eGEt1BMd7GJaIylHFpmlt4+qcoyD+jyd0Jbq4vwiNiupZSZctqRmbu9rLdkoEzSGkkvyY6GQbJxc/ZiAXwKBgAJSb8Px3X3LmIFvbs8MPYQxZdWrR6O7dJWMD/cY8xYltFbq+/tVnSqgXHT75HViX1s4HljxUgrERrw3xAqgsJE1xFpJULZb81MktUUQ80uoFVWOYgxmuiq7zcquNM5AE98N+LhKrdWCzY6TWEqLzbPmbCGtfw5cnANDMMw/K1ERAoGAaLrMvYqR2W8aYpA3ZBfJxxQ0CJ5Av5mZqJxRRkWsoEXck5D6RnULxBk4z9E2KSupdeCuLAGZwkB48xzi2D+yny7TMT7odGCAtCqz2ETd3ZXfAUt36uK/V0o44p4ARvOreabpxlJ2kzpgndm/0gbtxJTEtYem5JeBNVWQEdSAfN0CgYABJjSTY6fDJ1Cj18eiTWY4Tq2phUJQedhZr7/m6e/OZCi40MBiBZ8rdpqqIJzwIVqeei3CTvgZGPa8uKBjMxFZ1ikfNe2+/MA629Cq2FfSNEw1k/+w8uMUMet/Zb+mJ2a6HGZdNfck/5STVYkjKVoIx9jagBBaG1gTbkUcA/XA8Q==", - "rsa_pub_key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArA+8eKc6r7pI1mMj0Ih+Ck4MVfnqeobFvt1d8+b6NDPOmuH13Lz51SdjeHaGM9wrAuGCVn7WSoi8mNYf3vGQfREXem5GsUQ+pV4Nuhq8v2DzvoBZYbM6JSz5xcWGgbU06uD7d+MbHg+Y0JIkzV0mDUm6cV/2TYgp/wueSHFFn+yy+gbrn69XJKs+BiBT3JVd/ltfqgm/NiN06DI5biiHAKR414b2ZsOxD6IAGsRR11iZosQBNBFSHrAUfgxpT2aqg2sojsNgSmZec5c75y9OABshV+/wVlAWbYSbjW+sIWNtFqFNKWU7H/dltABbYWn1uAqj6hlyvkEHuH27Ru3/nQIDAQAB", "group_pub_key": "I8s5zyMT4UHmqR4a99qBnZWXcQt8l4sePztiSEudSeUZqfnUZaWvV8MmeI/Wo0QwKpFNoxYabxfoqXr26KgDLyRdJc5/pCH4o6v714iE+QqNWQDItrGff9l2UcSYI0QhDugl3PWLCswyqeQjLYZkCIk/Qo4iE5eOmW9cM5TsxQ8DYk2USWuMtz+h72xmfomQo28Fyb1GtdQU4LWxfXMLUR3ttXPoJ/IYTxeZNfFuDWGUa3MWdlAJLgdWNsWaDyPiBgzntlxiw5q5xdCTJkmNcNzbZlVRHeng+ZX41gxY4scIP0+pMVtPQFj4NFAFAxVjMl4Zlrvzuy6+Iut3LYLBxBQG1TiY2dANuXJlvZbOC8jUsgeZvKZnwCG/ljnaZQ0CGyF0/ydNadCsSLPFfTiecSJkXL1Pi5NLxzacG/4QBPIhy4VjU4Gg17HNzbYuKljCvS0jAbYsuDEHqi7Z4MXTpREafV3WTE9yGdB2zE/RMRklUNukcAC1ayAjb4tIw6yD", "join_responses": { "20240622": "BAh+HPSF0v2NgWIsqbaFlZokWS5+yuzg0WJAcT1AlAZwIz5/e7n2v7P79pa3LzKjklJ4xUM/WZiujbNZd3gfX5QEFS55KwibK5dEdYMTzDiHWMU4OSIV7p/ZWmjs61Apbx8gnckDONbUOrf4FR2Y46SMp4/VxaZju+S6qBVFdbgqdQQG85r4caFZa56KdERRvy19pfnCdYTDvKedWs7V8xc6LhXjMbFIMlRv0ExlONRTtD3NqOynppR9UCIZmVyaQ+FiBA+uiU9RkwWpAvg2oTAWyS0XbgV6xFe2YlY1mvbpX2ZyEPS+aCmbJo/8/X0piHvcd01D6ytLZfUKQ3qFcoh2mZUGRo7N4bow9J5+dHZkSDQN/r94XLA4uJrTM2dxeFeNAyNlEuSYnxzA9YBRF6YyPuRlWRHXxhSlqO0rbspV+fEX", From 4aaff0b8c3bd58df9c1f5864aee7bc156a326102 Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Wed, 21 Aug 2024 21:04:08 -0700 Subject: [PATCH 4/9] Address Web Discovery feedback --- .../web_discovery_service_factory.cc | 20 +++++------ .../web_discovery_service_factory.h | 2 ++ components/web_discovery/browser/BUILD.gn | 2 +- .../browser/background_credential_helper.cc | 15 ++++----- .../browser/background_credential_helper.h | 17 +++++----- .../browser/credential_manager.cc | 33 ++++++++----------- .../browser/credential_manager.h | 18 +++++----- .../browser/credential_manager_unittest.cc | 12 +++---- .../web_discovery/browser/credential_signer.h | 6 ++-- 9 files changed, 58 insertions(+), 67 deletions(-) diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc index f6d0d29f2315..7da05aa16e7e 100644 --- a/browser/web_discovery/web_discovery_service_factory.cc +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -17,17 +17,6 @@ namespace web_discovery { -namespace { - -ProfileSelections GetProfileSelections() { - if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { - return ProfileSelections::BuildNoProfilesSelected(); - } - return ProfileSelections::BuildForRegularProfile(); -} - -} // namespace - WebDiscoveryService* WebDiscoveryServiceFactory::GetForBrowserContext( content::BrowserContext* context) { return static_cast( @@ -41,10 +30,17 @@ WebDiscoveryServiceFactory* WebDiscoveryServiceFactory::GetInstance() { WebDiscoveryServiceFactory::WebDiscoveryServiceFactory() : ProfileKeyedServiceFactory("WebDiscoveryService", - GetProfileSelections()) {} + CreateProfileSelections()) {} WebDiscoveryServiceFactory::~WebDiscoveryServiceFactory() = default; +ProfileSelections WebDiscoveryServiceFactory::CreateProfileSelections() { + if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { + return ProfileSelections::BuildNoProfilesSelected(); + } + return ProfileSelections::BuildForRegularProfile(); +} + KeyedService* WebDiscoveryServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { auto* default_storage_partition = context->GetDefaultStoragePartition(); diff --git a/browser/web_discovery/web_discovery_service_factory.h b/browser/web_discovery/web_discovery_service_factory.h index 8e914b91b039..81bfeeb2a993 100644 --- a/browser/web_discovery/web_discovery_service_factory.h +++ b/browser/web_discovery/web_discovery_service_factory.h @@ -29,6 +29,8 @@ class WebDiscoveryServiceFactory : public ProfileKeyedServiceFactory { WebDiscoveryServiceFactory& operator=(const WebDiscoveryServiceFactory&) = delete; + static ProfileSelections CreateProfileSelections(); + KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; bool ServiceIsCreatedWithBrowserContext() const override; diff --git a/components/web_discovery/browser/BUILD.gn b/components/web_discovery/browser/BUILD.gn index 16e4467c5f15..e315bcd16884 100644 --- a/components/web_discovery/browser/BUILD.gn +++ b/components/web_discovery/browser/BUILD.gn @@ -28,7 +28,7 @@ component("browser") { "web_discovery_service.h", ] deps = [ - "anonymous_credentials/rs/cxx:rust_lib", + "anonymous_credentials/rust:rust_lib", "//base", "//brave/brave_domains", "//brave/components/constants", diff --git a/components/web_discovery/browser/background_credential_helper.cc b/components/web_discovery/browser/background_credential_helper.cc index 8eaf90135f13..28799eb972cb 100644 --- a/components/web_discovery/browser/background_credential_helper.cc +++ b/components/web_discovery/browser/background_credential_helper.cc @@ -62,9 +62,9 @@ BackgroundCredentialHelper::GenerateJoinRequest(std::string pre_challenge) { std::optional BackgroundCredentialHelper::FinishJoin( std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes) { + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes) { base::AssertLongCPUWorkAllowed(); auto pub_key_result = anonymous_credentials::load_group_public_key( base::SpanToRustSlice(group_pub_key)); @@ -93,10 +93,9 @@ std::optional BackgroundCredentialHelper::FinishJoin( return base::Base64Encode(finish_res.data); } -std::optional> -BackgroundCredentialHelper::PerformSign( - std::vector msg, - std::vector basename, +std::optional> BackgroundCredentialHelper::PerformSign( + std::vector msg, + std::vector basename, std::optional> gsk_bytes, std::optional> credential_bytes) { base::AssertLongCPUWorkAllowed(); @@ -122,7 +121,7 @@ BackgroundCredentialHelper::PerformSign( VLOG(1) << "Failed to sign: " << sig_res.error_message.c_str(); return std::nullopt; } - return std::vector(sig_res.data.begin(), sig_res.data.end()); + return std::vector(sig_res.data.begin(), sig_res.data.end()); } } // namespace web_discovery diff --git a/components/web_discovery/browser/background_credential_helper.h b/components/web_discovery/browser/background_credential_helper.h index 970efe823191..62bc71db81c9 100644 --- a/components/web_discovery/browser/background_credential_helper.h +++ b/components/web_discovery/browser/background_credential_helper.h @@ -11,7 +11,7 @@ #include #include -#include "brave/components/web_discovery/browser/anonymous_credentials/rs/cxx/src/lib.rs.h" +#include "brave/components/web_discovery/browser/anonymous_credentials/rust/src/lib.rs.h" #include "brave/components/web_discovery/browser/rsa.h" #include "crypto/rsa_private_key.h" @@ -37,14 +37,13 @@ class BackgroundCredentialHelper { void SetRSAKey(std::unique_ptr rsa_private_key); std::optional GenerateJoinRequest( std::string pre_challenge); - std::optional FinishJoin( - std::string date, - std::vector group_pub_key, - std::vector gsk, - std::vector join_resp_bytes); - std::optional> PerformSign( - std::vector msg, - std::vector basename, + std::optional FinishJoin(std::string date, + std::vector group_pub_key, + std::vector gsk, + std::vector join_resp_bytes); + std::optional> PerformSign( + std::vector msg, + std::vector basename, std::optional> gsk_bytes, std::optional> credential_bytes); diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 495a314e35bf..7165a55dd507 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -143,9 +143,6 @@ void CredentialManager::JoinGroups() { void CredentialManager::StartJoinGroup( const std::string& date, const std::vector& group_pub_key) { - std::vector group_pub_key_const(group_pub_key.begin(), - group_pub_key.end()); - auto challenge_elements = base::Value::List::with_capacity(2); challenge_elements.Append(*rsa_public_key_b64_); challenge_elements.Append(base::Base64Encode(group_pub_key)); @@ -158,12 +155,12 @@ void CredentialManager::StartJoinGroup( .WithArgs(pre_challenge) .Then(base::BindOnce(&CredentialManager::OnJoinRequestReady, weak_ptr_factory_.GetWeakPtr(), date, - group_pub_key_const)); + group_pub_key)); } void CredentialManager::OnJoinRequestReady( std::string date, - std::vector group_pub_key, + std::vector group_pub_key, std::optional generate_join_result) { if (!generate_join_result) { return; @@ -183,9 +180,9 @@ void CredentialManager::OnJoinRequestReady( return; } - auto gsk = std::vector( - generate_join_result->start_join_result.gsk.begin(), - generate_join_result->start_join_result.gsk.end()); + auto gsk = + std::vector(generate_join_result->start_join_result.gsk.begin(), + generate_join_result->start_join_result.gsk.end()); auto resource_request = CreateResourceRequest(join_url_); resource_request->headers.SetHeader(kVersionHeader, @@ -207,8 +204,8 @@ void CredentialManager::OnJoinRequestReady( void CredentialManager::OnJoinResponse( std::string date, - std::vector group_pub_key, - std::vector gsk, + std::vector group_pub_key, + std::vector gsk, std::optional response_body) { bool result = ProcessJoinResponse(date, group_pub_key, gsk, response_body); if (!result) { @@ -235,8 +232,8 @@ void CredentialManager::HandleJoinResponseStatus(const std::string& date, bool CredentialManager::ProcessJoinResponse( const std::string& date, - const std::vector& group_pub_key, - const std::vector& gsk, + const std::vector& group_pub_key, + const std::vector& gsk, const std::optional& response_body) { CHECK(join_url_loaders_[date]); auto& url_loader = join_url_loaders_[date]; @@ -272,12 +269,10 @@ bool CredentialManager::ProcessJoinResponse( VLOG(1) << "Failed to decode join response base64"; return false; } - std::vector join_resp_bytes_const(join_resp_bytes->begin(), - join_resp_bytes->end()); background_credential_helper_ .AsyncCall(&BackgroundCredentialHelper::FinishJoin) - .WithArgs(date, group_pub_key, gsk, join_resp_bytes_const) + .WithArgs(date, group_pub_key, gsk, *join_resp_bytes) .Then(base::BindOnce(&CredentialManager::OnCredentialsReady, weak_ptr_factory_.GetWeakPtr(), date, gsk)); return true; @@ -285,7 +280,7 @@ bool CredentialManager::ProcessJoinResponse( void CredentialManager::OnCredentialsReady( std::string date, - std::vector gsk, + std::vector gsk, std::optional credentials) { if (!credentials) { HandleJoinResponseStatus(date, false); @@ -303,8 +298,8 @@ bool CredentialManager::CredentialExistsForToday() { .contains(FormatServerDate(base::Time::Now())); } -void CredentialManager::Sign(std::vector msg, - std::vector basename, +void CredentialManager::Sign(std::vector msg, + std::vector basename, SignCallback callback) { auto today_date = FormatServerDate(base::Time::Now().UTCMidnight()); const auto& anon_creds_dict = @@ -345,7 +340,7 @@ void CredentialManager::Sign(std::vector msg, void CredentialManager::OnSignResult( std::string credential_date, SignCallback callback, - std::optional> signed_message) { + std::optional> signed_message) { loaded_credential_date_ = credential_date; std::move(callback).Run(signed_message); } diff --git a/components/web_discovery/browser/credential_manager.h b/components/web_discovery/browser/credential_manager.h index 1fa9d9eb4706..c54a7b3e8437 100644 --- a/components/web_discovery/browser/credential_manager.h +++ b/components/web_discovery/browser/credential_manager.h @@ -56,8 +56,8 @@ class CredentialManager : public CredentialSigner { // CredentialSigner: bool CredentialExistsForToday() override; - void Sign(std::vector msg, - std::vector basename, + void Sign(std::vector msg, + std::vector basename, SignCallback callback) override; // Uses a fixed seed in the anonymous credential manager @@ -75,25 +75,25 @@ class CredentialManager : public CredentialSigner { void OnJoinRequestReady( std::string date, - std::vector group_pub_key, + std::vector group_pub_key, std::optional generate_join_result); void OnJoinResponse(std::string date, - std::vector group_pub_key, - std::vector gsk, + std::vector group_pub_key, + std::vector gsk, std::optional response_body); void HandleJoinResponseStatus(const std::string& date, bool result); bool ProcessJoinResponse(const std::string& date, - const std::vector& group_pub_key, - const std::vector& gsk, + const std::vector& group_pub_key, + const std::vector& gsk, const std::optional& response_body); void OnCredentialsReady(std::string date, - std::vector gsk, + std::vector gsk, std::optional credentials); void OnSignResult(std::string credential_date, SignCallback callback, - std::optional> signed_message); + std::optional> signed_message); const raw_ptr profile_prefs_; const raw_ptr shared_url_loader_factory_; diff --git a/components/web_discovery/browser/credential_manager_unittest.cc b/components/web_discovery/browser/credential_manager_unittest.cc index d3efa10710f1..a185f59fdb37 100644 --- a/components/web_discovery/browser/credential_manager_unittest.cc +++ b/components/web_discovery/browser/credential_manager_unittest.cc @@ -162,12 +162,12 @@ TEST_F(WebDiscoveryCredentialManagerTest, LoadKeysFromStorage) { } TEST_F(WebDiscoveryCredentialManagerTest, Sign) { - std::vector message({0, 1, 2, 3, 4}); - std::vector basename({5, 6, 7, 8, 9}); + std::vector message({0, 1, 2, 3, 4}); + std::vector basename({5, 6, 7, 8, 9}); credential_manager_->Sign( message, basename, base::BindLambdaForTesting( - [&](const std::optional> signature) { + [&](const std::optional> signature) { EXPECT_FALSE(signature); })); task_environment_.RunUntilIdle(); @@ -175,12 +175,12 @@ TEST_F(WebDiscoveryCredentialManagerTest, Sign) { credential_manager_->JoinGroups(); task_environment_.RunUntilIdle(); - base::flat_set> signatures; + base::flat_set> signatures; for (size_t i = 0; i < 3; i++) { credential_manager_->Sign( message, basename, base::BindLambdaForTesting( - [&](const std::optional> signature) { + [&](const std::optional> signature) { ASSERT_TRUE(signature); EXPECT_FALSE(signature->empty()); EXPECT_FALSE(signatures.contains(*signature)); @@ -191,7 +191,7 @@ TEST_F(WebDiscoveryCredentialManagerTest, Sign) { credential_manager_->Sign( message, basename, base::BindLambdaForTesting( - [&](const std::optional> signature) { + [&](const std::optional> signature) { EXPECT_FALSE(signature); })); task_environment_.RunUntilIdle(); diff --git a/components/web_discovery/browser/credential_signer.h b/components/web_discovery/browser/credential_signer.h index fd79c67a894c..59b67d08a778 100644 --- a/components/web_discovery/browser/credential_signer.h +++ b/components/web_discovery/browser/credential_signer.h @@ -16,7 +16,7 @@ namespace web_discovery { class CredentialSigner { public: using SignCallback = - base::OnceCallback>)>; + base::OnceCallback>)>; virtual ~CredentialSigner() = default; // Returns true is a credential is available for the current date. @@ -29,8 +29,8 @@ class CredentialSigner { // preventing Sybil attacks. // See signature_basename.h/cc for more information on how the basename // should be generated. - virtual void Sign(std::vector msg, - std::vector basename, + virtual void Sign(std::vector msg, + std::vector basename, SignCallback callback) = 0; }; From fecbbfd27fce70a82e95e157614fe09bb7709271 Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Thu, 22 Aug 2024 17:54:38 -0700 Subject: [PATCH 5/9] Move Web Discovery Android prefs to separate target --- .../privacy/settings/BravePrivacySettings.java | 5 +++-- browser/android/preferences/BUILD.gn | 1 - build/android/config.gni | 1 + components/web_discovery/android/BUILD.gn | 12 ++++++++++++ .../java_templates/WebDiscoveryPrefs.java.tmpl | 15 +++++++++++++++ 5 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 components/web_discovery/android/BUILD.gn create mode 100644 components/web_discovery/android/java_templates/WebDiscoveryPrefs.java.tmpl diff --git a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java index 5cd65887f143..f545dd43e523 100644 --- a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java +++ b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java @@ -38,6 +38,7 @@ import org.chromium.components.browser_ui.settings.SettingsUtils; import org.chromium.components.prefs.PrefService; import org.chromium.components.user_prefs.UserPrefs; +import org.chromium.components.web_discovery.WebDiscoveryPrefs; import org.chromium.gms.ChromiumPlayServicesAvailability; import org.chromium.mojo.bindings.ConnectionErrorHandler; import org.chromium.mojo.system.MojoException; @@ -535,7 +536,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { BraveLocalState.commitPendingWrite(); } else if (PREF_SEND_WEB_DISCOVERY.equals(key)) { UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) - .setBoolean(BravePref.WEB_DISCOVERY_NATIVE_ENABLED, (boolean) newValue); + .setBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_NATIVE_ENABLED, (boolean) newValue); } else if (PREF_SEND_CRASH_REPORTS.equals(key)) { UmaSessionStats.changeMetricsReportingConsent( (boolean) newValue, ChangeMetricsReportingStateCalledFrom.UI_SETTINGS); @@ -703,7 +704,7 @@ private void updateBravePreferences() { getActivity().getResources().getString(R.string.send_web_discovery_summary)); mSendWebDiscovery.setChecked( UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) - .getBoolean(BravePref.WEB_DISCOVERY_NATIVE_ENABLED)); + .getBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_NATIVE_ENABLED)); } mSendCrashReports.setChecked(mPrivacyPrefManager.isUsageAndCrashReportingPermittedByUser()); diff --git a/browser/android/preferences/BUILD.gn b/browser/android/preferences/BUILD.gn index 8b17806592a2..8c2e03b5987a 100644 --- a/browser/android/preferences/BUILD.gn +++ b/browser/android/preferences/BUILD.gn @@ -51,7 +51,6 @@ java_cpp_strings("java_pref_names_srcjar") { "//brave/components/request_otr/common/pref_names.h", "//brave/components/search_engines/brave_search_engines_pref_names.h", "//brave/components/speedreader/speedreader_pref_names.h", - "//brave/components/web_discovery/browser/pref_names.h", "//components/translate/core/browser/translate_pref_names.h", ] diff --git a/build/android/config.gni b/build/android/config.gni index 4c9258611191..e153c4af0c1e 100644 --- a/build/android/config.gni +++ b/build/android/config.gni @@ -81,6 +81,7 @@ brave_chrome_java_srcjar_deps = [ "//brave/android:brave_android_java_enums_srcjar", "//brave/android:brave_config_java", "//brave/browser/android/preferences:java_pref_names_srcjar", + "//brave/components/web_discovery/android:java_pref_names_srcjar", ] brave_chrome_app_java_resources_deps = [ diff --git a/components/web_discovery/android/BUILD.gn b/components/web_discovery/android/BUILD.gn new file mode 100644 index 000000000000..27966758aebb --- /dev/null +++ b/components/web_discovery/android/BUILD.gn @@ -0,0 +1,12 @@ +# Copyright (c) 2024 The Brave Authors. All rights reserved. +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at https://mozilla.org/MPL/2.0/. + +import("//build/config/android/rules.gni") + +java_cpp_strings("java_pref_names_srcjar") { + sources = [ "//brave/components/web_discovery/browser/pref_names.h" ] + + template = "//brave/components/web_discovery/android/java_templates/WebDiscoveryPrefs.java.tmpl" +} diff --git a/components/web_discovery/android/java_templates/WebDiscoveryPrefs.java.tmpl b/components/web_discovery/android/java_templates/WebDiscoveryPrefs.java.tmpl new file mode 100644 index 000000000000..9bc52c2ae618 --- /dev/null +++ b/components/web_discovery/android/java_templates/WebDiscoveryPrefs.java.tmpl @@ -0,0 +1,15 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +package org.chromium.components.web_discovery; + +/** Contains Web Discovery preference names. */ +public final class WebDiscoveryPrefs {{ + +{NATIVE_STRINGS} + + // Prevents instantiation. + private WebDiscoveryPrefs() {{}} +}} From da9920d1d2fd72ff5f8a2e71fe5c54104d2204ab Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Thu, 22 Aug 2024 21:45:05 -0700 Subject: [PATCH 6/9] Add extension API for checking WDP native feature, use single pref for Web Discovery native + extension --- .../settings/BravePrivacySettings.java | 4 +-- browser/brave_profile_prefs.cc | 4 +-- browser/brave_tab_helpers.cc | 5 +-- browser/extensions/BUILD.gn | 6 ++++ .../api/settings_private/brave_prefs_util.cc | 13 ++------ browser/extensions/api/web_discovery_api.cc | 27 ++++++++++++++++ browser/extensions/api/web_discovery_api.h | 29 +++++++++++++++++ browser/profiles/brave_profile_manager.cc | 9 ------ .../brave_search_engines_page.html | 29 +++++------------ .../brave_search_engines_page.ts | 4 --- .../search_engines/search_engine_tracker.cc | 27 ++-------------- .../search_engine_tracker_browsertest.cc | 8 +++-- browser/ui/webui/brave_settings_ui.cc | 11 ------- .../webui/welcome_page/welcome_dom_handler.cc | 5 +-- browser/web_discovery/BUILD.gn | 4 +-- browser/web_discovery/sources.gni | 3 +- .../web_discovery/web_discovery_cta_util.cc | 17 +--------- .../web_discovery_infobar_delegate.cc | 15 +-------- .../web_discovery/web_discovery_unittest.cc | 6 ++-- common/extensions/api/_api_features.json | 10 +++++- common/extensions/api/api_sources.gni | 1 + common/extensions/api/web_discovery.json | 31 +++++++++++++++++++ .../background/webDiscoveryProject.ts | 17 ++++++---- .../extension/brave_extension/manifest.json | 1 + components/constants/BUILD.gn | 1 + components/constants/pref_names.h | 6 ++-- components/definitions/chromel.d.ts | 5 +++ components/web_discovery/browser/pref_names.h | 3 +- .../browser/web_discovery_service.cc | 30 ++---------------- .../browser/web_discovery_service.h | 5 --- test/BUILD.gn | 1 + 31 files changed, 166 insertions(+), 171 deletions(-) create mode 100644 browser/extensions/api/web_discovery_api.cc create mode 100644 browser/extensions/api/web_discovery_api.h create mode 100644 common/extensions/api/web_discovery.json diff --git a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java index f545dd43e523..366e6030c54c 100644 --- a/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java +++ b/android/java/org/chromium/chrome/browser/privacy/settings/BravePrivacySettings.java @@ -536,7 +536,7 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { BraveLocalState.commitPendingWrite(); } else if (PREF_SEND_WEB_DISCOVERY.equals(key)) { UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) - .setBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_NATIVE_ENABLED, (boolean) newValue); + .setBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_ENABLED, (boolean) newValue); } else if (PREF_SEND_CRASH_REPORTS.equals(key)) { UmaSessionStats.changeMetricsReportingConsent( (boolean) newValue, ChangeMetricsReportingStateCalledFrom.UI_SETTINGS); @@ -704,7 +704,7 @@ private void updateBravePreferences() { getActivity().getResources().getString(R.string.send_web_discovery_summary)); mSendWebDiscovery.setChecked( UserPrefs.get(ProfileManager.getLastUsedRegularProfile()) - .getBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_NATIVE_ENABLED)); + .getBoolean(WebDiscoveryPrefs.WEB_DISCOVERY_ENABLED)); } mSendCrashReports.setChecked(mPrivacyPrefManager.isUsageAndCrashReportingPermittedByUser()); diff --git a/browser/brave_profile_prefs.cc b/browser/brave_profile_prefs.cc index 8aa1659e45d4..7b6f5e146507 100644 --- a/browser/brave_profile_prefs.cc +++ b/browser/brave_profile_prefs.cc @@ -434,8 +434,8 @@ void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry) { prefs::kBraveDefaultSearchVersion, TemplateURLPrepopulateData::kBraveCurrentDataVersion); -#if BUILDFLAG(ENABLE_EXTENSIONS) - registry->RegisterBooleanPref(kWebDiscoveryExtensionEnabled, false); +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + registry->RegisterBooleanPref(kWebDiscoveryEnabled, false); registry->RegisterDictionaryPref(kWebDiscoveryCTAState); #endif diff --git a/browser/brave_tab_helpers.cc b/browser/brave_tab_helpers.cc index 924fa3cd0c85..f205805a2a57 100644 --- a/browser/brave_tab_helpers.cc +++ b/browser/brave_tab_helpers.cc @@ -30,6 +30,7 @@ #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/profiles/profile.h" @@ -87,7 +88,7 @@ #include "brave/components/tor/tor_tab_helper.h" #endif -#if BUILDFLAG(ENABLE_EXTENSIONS) +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) #include "brave/browser/web_discovery/web_discovery_tab_helper.h" #endif @@ -163,7 +164,7 @@ void AttachTabHelpers(content::WebContents* web_contents) { web_contents); psst::PsstTabHelper::MaybeCreateForWebContents( web_contents, ISOLATED_WORLD_ID_BRAVE_INTERNAL); -#if BUILDFLAG(ENABLE_EXTENSIONS) +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) WebDiscoveryTabHelper::MaybeCreateForWebContents(web_contents); #endif diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index 526823f77265..dd7841db2164 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -11,6 +11,7 @@ import("//brave/components/brave_webtorrent/browser/buildflags/buildflags.gni") import("//brave/components/playlist/common/buildflags/buildflags.gni") import("//brave/components/speedreader/common/buildflags/buildflags.gni") import("//brave/components/tor/buildflags/buildflags.gni") +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//build/config/features.gni") import("//build/config/ui.gni") import("//components/gcm_driver/config.gni") @@ -173,6 +174,7 @@ source_set("extensions") { "//brave/components/request_otr/common", "//brave/components/sidebar/browser", "//brave/components/tor/buildflags", + "//brave/components/web_discovery/buildflags", "//chrome/browser:browser_process", "//chrome/browser:browser_public_dependencies", "//chrome/browser/extensions", @@ -229,6 +231,10 @@ source_set("extensions") { ] } + if (enable_web_discovery_native) { + deps += [ "//brave/components/web_discovery/common" ] + } + # It seems like this brave_wallet_api should be renamed to ethereum_remote_client_api. # However this is not possible right now because the ethereum-remote-client extension # uses chrome.braveWallet, so the API is intentionally not being renamed now. diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index 119639f19050..c5efe7b0460d 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -63,10 +63,6 @@ #include "brave/components/playlist/browser/pref_names.h" #endif -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) -#include "brave/components/web_discovery/browser/pref_names.h" -#endif - namespace extensions { using ntp_background_images::prefs::kNewTabPageShowBackgroundImage; @@ -189,14 +185,9 @@ const PrefsUtil::TypedPrefMap& BravePrefsUtil::GetAllowlistedKeys() { settings_api::PrefType::kBoolean; (*s_brave_allowlist)[kNewTabPageShowsOptions] = settings_api::PrefType::kNumber; -#if BUILDFLAG(ENABLE_EXTENSIONS) +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) // Web discovery prefs - (*s_brave_allowlist)[kWebDiscoveryExtensionEnabled] = - settings_api::PrefType::kBoolean; -#endif -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - (*s_brave_allowlist)[web_discovery::kWebDiscoveryNativeEnabled] = - settings_api::PrefType::kBoolean; + (*s_brave_allowlist)[kWebDiscoveryEnabled] = settings_api::PrefType::kBoolean; #endif // Clear browsing data on exit prefs. (*s_brave_allowlist)[browsing_data::prefs::kDeleteBrowsingHistoryOnExit] = diff --git a/browser/extensions/api/web_discovery_api.cc b/browser/extensions/api/web_discovery_api.cc new file mode 100644 index 000000000000..7deec9539ee4 --- /dev/null +++ b/browser/extensions/api/web_discovery_api.cc @@ -0,0 +1,27 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/extensions/api/web_discovery_api.h" + +#include "brave/components/web_discovery/buildflags/buildflags.h" + +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +#include "base/feature_list.h" +#include "brave/components/web_discovery/common/features.h" +#endif + +namespace extensions::api { + +ExtensionFunction::ResponseAction +WebDiscoveryIsWebDiscoveryNativeEnabledFunction::Run() { + bool result = false; +#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + result = base::FeatureList::IsEnabled( + web_discovery::features::kBraveWebDiscoveryNative); +#endif + return RespondNow(WithArguments(result)); +} + +} // namespace extensions::api diff --git a/browser/extensions/api/web_discovery_api.h b/browser/extensions/api/web_discovery_api.h new file mode 100644 index 000000000000..26b584b630af --- /dev/null +++ b/browser/extensions/api/web_discovery_api.h @@ -0,0 +1,29 @@ +/* Copyright (c) 2024 The Brave Authors. All rights reserved. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_EXTENSIONS_API_WEB_DISCOVERY_API_H_ +#define BRAVE_BROWSER_EXTENSIONS_API_WEB_DISCOVERY_API_H_ + +#include "extensions/browser/extension_function.h" + +namespace extensions { +namespace api { + +class WebDiscoveryIsWebDiscoveryNativeEnabledFunction + : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("webDiscovery.isWebDiscoveryNativeEnabled", + UNKNOWN) + + protected: + ~WebDiscoveryIsWebDiscoveryNativeEnabledFunction() override {} + + ResponseAction Run() override; +}; + +} // namespace api +} // namespace extensions + +#endif // BRAVE_BROWSER_EXTENSIONS_API_WEB_DISCOVERY_API_H_ diff --git a/browser/profiles/brave_profile_manager.cc b/browser/profiles/brave_profile_manager.cc index fed51ca57853..9077bd1308ae 100644 --- a/browser/profiles/brave_profile_manager.cc +++ b/browser/profiles/brave_profile_manager.cc @@ -26,7 +26,6 @@ #include "brave/components/ntp_background_images/common/pref_names.h" #include "brave/components/request_otr/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" -#include "brave/components/web_discovery/buildflags/buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/profiles/profile_attributes_entry.h" @@ -47,10 +46,6 @@ #include "brave/components/tor/tor_constants.h" #endif -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) -#include "brave/components/web_discovery/browser/web_discovery_service.h" -#endif - using brave_shields::ControlType; using content::BrowserThread; using ntp_background_images::prefs::kNewTabPageShowBackgroundImage; @@ -143,10 +138,6 @@ void BraveProfileManager::InitProfileUserPrefs(Profile* profile) { brave::SetDefaultThirdPartyCookieBlockValue(profile); perf::MaybeEnableBraveFeatureForPerfTesting(profile); MigrateHttpsUpgradeSettings(profile); -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - web_discovery::WebDiscoveryService::SetExtensionPrefIfNativeDisabled( - profile->GetPrefs()); -#endif } void BraveProfileManager::DoFinalInitForServices(Profile* profile, diff --git a/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html b/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html index 7cb5c2619c64..bceeb9c5ee20 100644 --- a/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html +++ b/browser/resources/settings/brave_search_engines_page/brave_search_engines_page.html @@ -17,27 +17,14 @@ sub-label="$i18n{searchSuggestDesc}">
- - - - - + + + GetBoolean(kWebDiscoveryExtensionEnabled); -#endif -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - if (base::FeatureList::IsEnabled( - web_discovery::features::kBraveWebDiscoveryNative)) { - enabled = - profile_prefs_->GetBoolean(web_discovery::kWebDiscoveryNativeEnabled); - } -#endif + bool enabled = profile_prefs_->GetBoolean(kWebDiscoveryEnabled); UMA_HISTOGRAM_BOOLEAN(kWebDiscoveryEnabledMetric, enabled); UMA_HISTOGRAM_BOOLEAN( kWebDiscoveryAndAdsMetric, diff --git a/browser/search_engines/search_engine_tracker_browsertest.cc b/browser/search_engines/search_engine_tracker_browsertest.cc index 714aa69c61cf..30b2cd4c04df 100644 --- a/browser/search_engines/search_engine_tracker_browsertest.cc +++ b/browser/search_engines/search_engine_tracker_browsertest.cc @@ -13,6 +13,7 @@ #include "brave/components/constants/pref_names.h" #include "brave/components/search_engines/brave_prepopulated_engines.h" #include "brave/components/tor/buildflags/buildflags.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h" #include "chrome/browser/search_engines/template_url_service_factory.h" @@ -24,6 +25,7 @@ #include "components/search_engines/search_engine_choice/search_engine_choice_service.h" #include "components/search_engines/template_url_prepopulate_data.h" #include "content/public/test/browser_test.h" +#include "extensions/buildflags/buildflags.h" class SearchEngineProviderP3ATest : public InProcessBrowserTest { public: @@ -159,11 +161,12 @@ IN_PROC_BROWSER_TEST_F(SearchEngineProviderP3ATest, SwitchSearchEngineP3A) { histogram_tester_->ExpectTotalCount(kSwitchSearchEngineMetric, 8); } +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) IN_PROC_BROWSER_TEST_F(SearchEngineProviderP3ATest, WebDiscoveryEnabledP3A) { histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 0, 1); PrefService* prefs = browser()->profile()->GetPrefs(); - prefs->SetBoolean(kWebDiscoveryExtensionEnabled, true); + prefs->SetBoolean(kWebDiscoveryEnabled, true); histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 1, 1); @@ -171,9 +174,10 @@ IN_PROC_BROWSER_TEST_F(SearchEngineProviderP3ATest, WebDiscoveryEnabledP3A) { prefs->SetBoolean(brave_ads::prefs::kOptedInToNotificationAds, true); histogram_tester_->ExpectBucketCount(kWebDiscoveryAndAdsMetric, 1, 1); - prefs->SetBoolean(kWebDiscoveryExtensionEnabled, false); + prefs->SetBoolean(kWebDiscoveryEnabled, false); histogram_tester_->ExpectBucketCount(kWebDiscoveryEnabledMetric, 0, 2); histogram_tester_->ExpectBucketCount(kWebDiscoveryAndAdsMetric, 0, 3); histogram_tester_->ExpectTotalCount(kWebDiscoveryAndAdsMetric, 4); } +#endif diff --git a/browser/ui/webui/brave_settings_ui.cc b/browser/ui/webui/brave_settings_ui.cc index de31ce02cfb4..c81298d8861d 100644 --- a/browser/ui/webui/brave_settings_ui.cc +++ b/browser/ui/webui/brave_settings_ui.cc @@ -45,7 +45,6 @@ #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "brave/components/version_info/version_info.h" -#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ui/webui/settings/metrics_reporting_handler.h" @@ -92,10 +91,6 @@ #include "brave/components/playlist/common/features.h" #endif -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) -#include "brave/components/web_discovery/common/features.h" -#endif - using ntp_background_images::ViewCounterServiceFactory; BraveSettingsUI::BraveSettingsUI(content::WebUI* web_ui) : SettingsUI(web_ui) { @@ -197,12 +192,6 @@ void BraveSettingsUI::AddResources(content::WebUIDataSource* html_source, ShouldExposeElementsForTesting()); html_source->AddBoolean("enable_extensions", BUILDFLAG(ENABLE_EXTENSIONS)); -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - html_source->AddBoolean( - "isWebDiscoveryNativeEnabled", - base::FeatureList::IsEnabled( - web_discovery::features::kBraveWebDiscoveryNative)); -#endif html_source->AddBoolean("extensionsManifestV2Feature", base::FeatureList::IsEnabled(kExtensionsManifestV2)); diff --git a/browser/ui/webui/welcome_page/welcome_dom_handler.cc b/browser/ui/webui/welcome_page/welcome_dom_handler.cc index b828846b04e9..de6bbce226f9 100644 --- a/browser/ui/webui/welcome_page/welcome_dom_handler.cc +++ b/browser/ui/webui/welcome_page/welcome_dom_handler.cc @@ -12,6 +12,7 @@ #include "brave/common/importer/importer_constants.h" #include "brave/components/constants/pref_names.h" #include "brave/components/p3a/pref_names.h" +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/metrics/metrics_reporting_state.h" #include "chrome/browser/profiles/profile.h" @@ -171,8 +172,8 @@ void WelcomeDOMHandler::HandleSetMetricsReportingEnabled( void WelcomeDOMHandler::HandleEnableWebDiscovery( const base::Value::List& args) { DCHECK(profile_); -#if BUILDFLAG(ENABLE_EXTENSIONS) - profile_->GetPrefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) + profile_->GetPrefs()->SetBoolean(kWebDiscoveryEnabled, true); #endif } diff --git a/browser/web_discovery/BUILD.gn b/browser/web_discovery/BUILD.gn index 991df6173997..bcce68d20e25 100644 --- a/browser/web_discovery/BUILD.gn +++ b/browser/web_discovery/BUILD.gn @@ -28,7 +28,7 @@ if (enable_web_discovery_native) { } source_set("unit_tests") { - if (enable_extensions) { + if (enable_extensions || enable_web_discovery_native) { testonly = true sources = [ "web_discovery_unittest.cc" ] @@ -49,7 +49,7 @@ source_set("unit_tests") { } source_set("browser_tests") { - if (enable_extensions) { + if (enable_extensions || enable_web_discovery_native) { testonly = true defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ] diff --git a/browser/web_discovery/sources.gni b/browser/web_discovery/sources.gni index 4547374948cb..866ca2b7fd7b 100644 --- a/browser/web_discovery/sources.gni +++ b/browser/web_discovery/sources.gni @@ -3,12 +3,13 @@ # License, v. 2.0. If a copy of the MPL was not distributed with this file, # You can obtain one at http://mozilla.org/MPL/2.0/. +import("//brave/components/web_discovery/buildflags/buildflags.gni") import("//extensions/buildflags/buildflags.gni") brave_browser_web_discovery_sources = [] brave_browser_web_discovery_deps = [] -if (enable_extensions) { +if (enable_extensions || enable_web_discovery_native) { brave_browser_web_discovery_sources += [ "//brave/browser/web_discovery/web_discovery_cta_util.cc", "//brave/browser/web_discovery/web_discovery_cta_util.h", diff --git a/browser/web_discovery/web_discovery_cta_util.cc b/browser/web_discovery/web_discovery_cta_util.cc index 4bb473a53294..9251daac6c70 100644 --- a/browser/web_discovery/web_discovery_cta_util.cc +++ b/browser/web_discovery/web_discovery_cta_util.cc @@ -14,7 +14,6 @@ #include "brave/components/constants/pref_names.h" #include "brave/components/constants/url_constants.h" #include "brave/components/search_engines/brave_prepopulated_engines.h" -#include "brave/components/web_discovery/buildflags/buildflags.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" #include "components/search_engines/template_url.h" @@ -22,11 +21,6 @@ #include "net/base/url_util.h" #include "url/gurl.h" -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) -#include "brave/components/web_discovery/browser/pref_names.h" -#include "brave/components/web_discovery/common/features.h" -#endif - namespace { constexpr int kMaxDisplayCount = 5; @@ -93,17 +87,8 @@ bool ShouldShowWebDiscoveryInfoBar(TemplateURLService* service, PrefService* prefs, const WebDiscoveryCTAState& state, base::Clock* test_clock) { - const char* enabled_pref_name = kWebDiscoveryExtensionEnabled; -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - if (base::FeatureList::IsEnabled( - web_discovery::features::kBraveWebDiscoveryNative)) { - enabled_pref_name = web_discovery::kWebDiscoveryNativeEnabled; - } -#endif - - if (prefs->GetBoolean(enabled_pref_name)) { + if (prefs->GetBoolean(kWebDiscoveryEnabled)) return false; - } if (!service || !IsBraveSearchDefault(service)) return false; diff --git a/browser/web_discovery/web_discovery_infobar_delegate.cc b/browser/web_discovery/web_discovery_infobar_delegate.cc index 6b8f934e6d31..c9db24eaa35b 100644 --- a/browser/web_discovery/web_discovery_infobar_delegate.cc +++ b/browser/web_discovery/web_discovery_infobar_delegate.cc @@ -7,15 +7,9 @@ #include "brave/browser/web_discovery/web_discovery_cta_util.h" #include "brave/components/constants/pref_names.h" -#include "brave/components/web_discovery/buildflags/buildflags.h" #include "components/infobars/core/infobar.h" #include "components/prefs/pref_service.h" -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) -#include "brave/components/web_discovery/browser/pref_names.h" -#include "brave/components/web_discovery/common/features.h" -#endif - WebDiscoveryInfoBarDelegate::WebDiscoveryInfoBarDelegate(PrefService* prefs) : prefs_(prefs) {} @@ -48,13 +42,6 @@ void WebDiscoveryInfoBarDelegate::Close(bool dismiss) { } void WebDiscoveryInfoBarDelegate::EnableWebDiscovery() { - const char* pref_name = kWebDiscoveryExtensionEnabled; -#if BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) - if (base::FeatureList::IsEnabled( - web_discovery::features::kBraveWebDiscoveryNative)) { - pref_name = web_discovery::kWebDiscoveryNativeEnabled; - } -#endif - prefs_->SetBoolean(pref_name, true); + prefs_->SetBoolean(kWebDiscoveryEnabled, true); infobar()->RemoveSelf(); } diff --git a/browser/web_discovery/web_discovery_unittest.cc b/browser/web_discovery/web_discovery_unittest.cc index f0c01983bedf..a52a74702100 100644 --- a/browser/web_discovery/web_discovery_unittest.cc +++ b/browser/web_discovery/web_discovery_unittest.cc @@ -104,7 +104,7 @@ class WebDiscoveryCTATest : public testing::Test { }; TEST_F(WebDiscoveryCTATest, InitialDataTest) { - EXPECT_FALSE(prefs()->GetBoolean(kWebDiscoveryExtensionEnabled)); + EXPECT_FALSE(prefs()->GetBoolean(kWebDiscoveryEnabled)); const auto& info_value = prefs()->GetDict(kWebDiscoveryCTAState); EXPECT_TRUE(info_value.empty()); @@ -137,10 +137,10 @@ TEST_F(WebDiscoveryCTATest, ShouldShowInfoBarTest) { EXPECT_TRUE(ShouldShowWebDiscoveryInfoBar()); // Don't show if already enabled. - prefs()->SetBoolean(kWebDiscoveryExtensionEnabled, true); + prefs()->SetBoolean(kWebDiscoveryEnabled, true); EXPECT_FALSE(ShouldShowWebDiscoveryInfoBar()); - prefs()->SetBoolean(kWebDiscoveryExtensionEnabled, false); + prefs()->SetBoolean(kWebDiscoveryEnabled, false); EXPECT_TRUE(ShouldShowWebDiscoveryInfoBar()); WebDiscoveryCTAState state = GetCurrentCTAState(); diff --git a/common/extensions/api/_api_features.json b/common/extensions/api/_api_features.json index 4804af72f729..2245a7ef931d 100644 --- a/common/extensions/api/_api_features.json +++ b/common/extensions/api/_api_features.json @@ -87,5 +87,13 @@ "matches": [ "chrome://newtab/*" ] - }] + }], + "webDiscovery": { + "channel": "stable", + "component_extensions_auto_granted": false, + "contexts": ["privileged_extension"], + "allowlist": [ + "A321D47A2B4CA86898167A55CA8B2E02385EA7CD" + ] + } } diff --git a/common/extensions/api/api_sources.gni b/common/extensions/api/api_sources.gni index 8d5d8eada427..f6c300ed3f13 100644 --- a/common/extensions/api/api_sources.gni +++ b/common/extensions/api/api_sources.gni @@ -16,6 +16,7 @@ schema_sources_ = [ "brave_theme.json", "greaselion.json", "rewards_notifications.json", + "web_discovery.json", ] if (ethereum_remote_client_enabled) { diff --git a/common/extensions/api/web_discovery.json b/common/extensions/api/web_discovery.json new file mode 100644 index 000000000000..c5f692f31b8f --- /dev/null +++ b/common/extensions/api/web_discovery.json @@ -0,0 +1,31 @@ +// Copyright (c) 2024 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at https://mozilla.org/MPL/2.0/. + +[ + { + "namespace": "webDiscovery", + "description": "Includes Web Discovery functions for the Brave extension", + "compiler_options": { + "implemented_in": "brave/browser/extensions/api/web_discovery_api.h" + }, + "functions": [ + { + "name": "isWebDiscoveryNativeEnabled", + "type": "function", + "description": "Returns true if Web Discovery native feature is active", + "parameters": [], + "returns_async": { + "name": "callback", + "parameters": [ + { + "name": "enabled", + "type": "boolean" + } + ] + } + } + ] + } +] diff --git a/components/brave_extension/extension/brave_extension/background/webDiscoveryProject.ts b/components/brave_extension/extension/brave_extension/background/webDiscoveryProject.ts index fd91ce8eb61c..178c72b817a0 100644 --- a/components/brave_extension/extension/brave_extension/background/webDiscoveryProject.ts +++ b/components/brave_extension/extension/brave_extension/background/webDiscoveryProject.ts @@ -79,12 +79,17 @@ if (!chrome.extension.inIncognitoContext) { } } - chrome.settingsPrivate.onPrefsChanged.addListener((prefs: chrome.settingsPrivate.PrefObject[]) => { - const pref = prefs.find(p => p.key === WEB_DISCOVERY_PREF_KEY) - toggleWebDiscovery(pref) - }) + chrome.webDiscovery.isWebDiscoveryNativeEnabled((nativeEnabled) => { + if (nativeEnabled) { + return + } + chrome.settingsPrivate.onPrefsChanged.addListener((prefs: chrome.settingsPrivate.PrefObject[]) => { + const pref = prefs.find(p => p.key === WEB_DISCOVERY_PREF_KEY) + toggleWebDiscovery(pref) + }) - chrome.settingsPrivate.getPref(WEB_DISCOVERY_PREF_KEY, (pref: chrome.settingsPrivate.PrefObject) => { - toggleWebDiscovery(pref) + chrome.settingsPrivate.getPref(WEB_DISCOVERY_PREF_KEY, (pref: chrome.settingsPrivate.PrefObject) => { + toggleWebDiscovery(pref) + }) }) } diff --git a/components/brave_extension/extension/brave_extension/manifest.json b/components/brave_extension/extension/brave_extension/manifest.json index 15acb6c99c05..639e7a7b4000 100644 --- a/components/brave_extension/extension/brave_extension/manifest.json +++ b/components/brave_extension/extension/brave_extension/manifest.json @@ -46,6 +46,7 @@ "webRequest", "*://*/*", "chrome://favicon/*", + "webDiscovery", "webRequestBlocking", "unlimitedStorage", "" diff --git a/components/constants/BUILD.gn b/components/constants/BUILD.gn index 0c078e684f71..a3aea520e863 100644 --- a/components/constants/BUILD.gn +++ b/components/constants/BUILD.gn @@ -31,6 +31,7 @@ source_set("constants") { public_deps = [ ":brave_services_key" ] deps = [ "//base", + "//brave/components/web_discovery/buildflags", "//extensions/buildflags", ] } diff --git a/components/constants/pref_names.h b/components/constants/pref_names.h index 8c459d21b0ed..2e6138af42f0 100644 --- a/components/constants/pref_names.h +++ b/components/constants/pref_names.h @@ -6,6 +6,7 @@ #ifndef BRAVE_COMPONENTS_CONSTANTS_PREF_NAMES_H_ #define BRAVE_COMPONENTS_CONSTANTS_PREF_NAMES_H_ +#include "brave/components/web_discovery/buildflags/buildflags.h" #include "build/build_config.h" #include "extensions/buildflags/buildflags.h" @@ -86,9 +87,8 @@ inline constexpr char kBraveShieldsSettingsVersion[] = inline constexpr char kDefaultBrowserPromptEnabled[] = "brave.default_browser_prompt_enabled"; -#if BUILDFLAG(ENABLE_EXTENSIONS) -inline constexpr char kWebDiscoveryExtensionEnabled[] = - "brave.web_discovery_enabled"; +#if BUILDFLAG(ENABLE_EXTENSIONS) || BUILDFLAG(ENABLE_WEB_DISCOVERY_NATIVE) +inline constexpr char kWebDiscoveryEnabled[] = "brave.web_discovery_enabled"; #endif inline constexpr char kWebDiscoveryCTAState[] = "brave.web_discovery.cta_state"; inline constexpr char kDontAskEnableWebDiscovery[] = diff --git a/components/definitions/chromel.d.ts b/components/definitions/chromel.d.ts index 2c57f6d4302b..ac1050e574fe 100644 --- a/components/definitions/chromel.d.ts +++ b/components/definitions/chromel.d.ts @@ -248,3 +248,8 @@ declare namespace cf_worker { declare namespace chrome.test { const sendMessage: (message: string) => {} } + +declare namespace chrome.webDiscovery { + type WebDiscoveryNativeEnabledCallback = (enabled: boolean) => void + const isWebDiscoveryNativeEnabled: (callback: WebDiscoveryNativeEnabledCallback) => void +} diff --git a/components/web_discovery/browser/pref_names.h b/components/web_discovery/browser/pref_names.h index 6f6e1ecb3e7f..aba8c307d0a5 100644 --- a/components/web_discovery/browser/pref_names.h +++ b/components/web_discovery/browser/pref_names.h @@ -9,8 +9,7 @@ namespace web_discovery { // Profile prefs -inline constexpr char kWebDiscoveryNativeEnabled[] = - "brave.web_discovery.wdp_native_enabled"; +inline constexpr char kWebDiscoveryEnabled[] = "brave.web_discovery_enabled"; // The following pref values are used for generating // anonymous signatures for user submissions. diff --git a/components/web_discovery/browser/web_discovery_service.cc b/components/web_discovery/browser/web_discovery_service.cc index 2be6eee2c7c0..b2eb0821fbc5 100644 --- a/components/web_discovery/browser/web_discovery_service.cc +++ b/components/web_discovery/browser/web_discovery_service.cc @@ -5,18 +5,12 @@ #include "brave/components/web_discovery/browser/web_discovery_service.h" -#include - -#include "base/feature_list.h" #include "base/functional/bind.h" -#include "brave/components/constants/pref_names.h" #include "brave/components/web_discovery/browser/pref_names.h" #include "brave/components/web_discovery/browser/server_config_loader.h" -#include "brave/components/web_discovery/common/features.h" #include "components/prefs/pref_registry_simple.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" -#include "extensions/buildflags/buildflags.h" #include "services/network/public/cpp/shared_url_loader_factory.h" namespace web_discovery { @@ -30,20 +24,13 @@ WebDiscoveryService::WebDiscoveryService( profile_prefs_(profile_prefs), user_data_dir_(user_data_dir), shared_url_loader_factory_(shared_url_loader_factory) { -#if BUILDFLAG(ENABLE_EXTENSIONS) - if (profile_prefs_->GetBoolean(kWebDiscoveryExtensionEnabled)) { - profile_prefs_->ClearPref(kWebDiscoveryExtensionEnabled); - profile_prefs_->SetBoolean(kWebDiscoveryNativeEnabled, true); - } -#endif - pref_change_registrar_.Init(profile_prefs); pref_change_registrar_.Add( - kWebDiscoveryNativeEnabled, + kWebDiscoveryEnabled, base::BindRepeating(&WebDiscoveryService::OnEnabledChange, base::Unretained(this))); - if (profile_prefs_->GetBoolean(kWebDiscoveryNativeEnabled)) { + if (profile_prefs_->GetBoolean(kWebDiscoveryEnabled)) { Start(); } } @@ -56,21 +43,10 @@ void WebDiscoveryService::RegisterLocalStatePrefs( } void WebDiscoveryService::RegisterProfilePrefs(PrefRegistrySimple* registry) { - registry->RegisterBooleanPref(kWebDiscoveryNativeEnabled, false); registry->RegisterDictionaryPref(kAnonymousCredentialsDict); registry->RegisterStringPref(kCredentialRSAPrivateKey, {}); } -void WebDiscoveryService::SetExtensionPrefIfNativeDisabled( - PrefService* profile_prefs) { -#if BUILDFLAG(ENABLE_EXTENSIONS) - if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative) && - profile_prefs->GetBoolean(kWebDiscoveryNativeEnabled)) { - profile_prefs->SetBoolean(kWebDiscoveryExtensionEnabled, true); - } -#endif -} - void WebDiscoveryService::Shutdown() { Stop(); pref_change_registrar_.RemoveAll(); @@ -104,7 +80,7 @@ void WebDiscoveryService::ClearPrefs() { } void WebDiscoveryService::OnEnabledChange() { - if (profile_prefs_->GetBoolean(kWebDiscoveryNativeEnabled)) { + if (profile_prefs_->GetBoolean(kWebDiscoveryEnabled)) { Start(); } else { Stop(); diff --git a/components/web_discovery/browser/web_discovery_service.h b/components/web_discovery/browser/web_discovery_service.h index c30f03917ed0..88c6837d60bd 100644 --- a/components/web_discovery/browser/web_discovery_service.h +++ b/components/web_discovery/browser/web_discovery_service.h @@ -47,11 +47,6 @@ class WebDiscoveryService : public KeyedService { static void RegisterLocalStatePrefs(PrefRegistrySimple* registry); static void RegisterProfilePrefs(PrefRegistrySimple* registry); - // Sets the extension preference to true if the preference for the native - // implementation is set to true and the feature is disabled. - // Relevant for a Griffin/variations rollback. - static void SetExtensionPrefIfNativeDisabled(PrefService* profile_prefs); - // KeyedService: void Shutdown() override; diff --git a/test/BUILD.gn b/test/BUILD.gn index 842b873e57e9..6de317247535 100644 --- a/test/BUILD.gn +++ b/test/BUILD.gn @@ -930,6 +930,7 @@ test("brave_browser_tests") { "//brave/components/speedreader/common/buildflags", "//brave/components/tor/buildflags", "//brave/components/url_sanitizer/browser", + "//brave/components/web_discovery/buildflags", "//brave/components/webcompat/content/browser", "//brave/components/webcompat/core/common", "//brave/renderer/skus:browser_tests", From 2f7eded6d53397040472dc735bcd6484768f93fe Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Fri, 23 Aug 2024 16:36:27 -0700 Subject: [PATCH 7/9] Fix new RSA key check --- components/web_discovery/browser/credential_manager.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 7165a55dd507..792f809f08d3 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -104,7 +104,7 @@ void CredentialManager::OnNewRSAKey(std::unique_ptr key_info) { } rsa_public_key_b64_ = key_info->public_key_b64; - CHECK(key_info->key_pair && key_info->private_key_b64); + CHECK(key_info->private_key_b64); profile_prefs_->SetString(kCredentialRSAPrivateKey, *key_info->private_key_b64); From 5b90bd3a3e706d8c2134804b26f69f0f7d436241 Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Mon, 16 Sep 2024 21:20:40 -0700 Subject: [PATCH 8/9] Address Web Discovery feedback --- browser/extensions/BUILD.gn | 2 ++ browser/extensions/api/settings_private/brave_prefs_util.cc | 2 -- browser/web_discovery/web_discovery_service_factory.cc | 1 + components/constants/BUILD.gn | 6 +++--- components/web_discovery/browser/DEPS | 1 + 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/browser/extensions/BUILD.gn b/browser/extensions/BUILD.gn index dd7841db2164..90802e48461c 100644 --- a/browser/extensions/BUILD.gn +++ b/browser/extensions/BUILD.gn @@ -108,6 +108,8 @@ source_set("extensions") { "api/rewards_notifications_api.h", "api/settings_private/brave_prefs_util.cc", "api/settings_private/brave_prefs_util.h", + "api/web_discovery_api.cc", + "api/web_discovery_api.h", "brave_extension_management.cc", "brave_extension_management.h", "brave_extension_provider.cc", diff --git a/browser/extensions/api/settings_private/brave_prefs_util.cc b/browser/extensions/api/settings_private/brave_prefs_util.cc index c5efe7b0460d..3fd9613372e3 100644 --- a/browser/extensions/api/settings_private/brave_prefs_util.cc +++ b/browser/extensions/api/settings_private/brave_prefs_util.cc @@ -24,8 +24,6 @@ #include "brave/components/speedreader/common/buildflags/buildflags.h" #include "brave/components/tor/buildflags/buildflags.h" #include "brave/components/web_discovery/buildflags/buildflags.h" -#include "chrome/browser/content_settings/cookie_settings_factory.h" -#include "chrome/browser/content_settings/host_content_settings_map_factory.h" #include "chrome/browser/extensions/api/settings_private/prefs_util.h" #include "chrome/common/extensions/api/settings_private.h" #include "chrome/common/pref_names.h" diff --git a/browser/web_discovery/web_discovery_service_factory.cc b/browser/web_discovery/web_discovery_service_factory.cc index 7da05aa16e7e..3dceb835ee25 100644 --- a/browser/web_discovery/web_discovery_service_factory.cc +++ b/browser/web_discovery/web_discovery_service_factory.cc @@ -34,6 +34,7 @@ WebDiscoveryServiceFactory::WebDiscoveryServiceFactory() WebDiscoveryServiceFactory::~WebDiscoveryServiceFactory() = default; +// static ProfileSelections WebDiscoveryServiceFactory::CreateProfileSelections() { if (!base::FeatureList::IsEnabled(features::kBraveWebDiscoveryNative)) { return ProfileSelections::BuildNoProfilesSelected(); diff --git a/components/constants/BUILD.gn b/components/constants/BUILD.gn index a3aea520e863..aa683cb8af8b 100644 --- a/components/constants/BUILD.gn +++ b/components/constants/BUILD.gn @@ -28,12 +28,12 @@ source_set("constants") { "webui_url_constants.h", ] - public_deps = [ ":brave_services_key" ] - deps = [ - "//base", + public_deps = [ + ":brave_services_key", "//brave/components/web_discovery/buildflags", "//extensions/buildflags", ] + deps = [ "//base" ] } source_set("brave_service_key_helper") { diff --git a/components/web_discovery/browser/DEPS b/components/web_discovery/browser/DEPS index 290a8dab5740..1c0aaedc075e 100644 --- a/components/web_discovery/browser/DEPS +++ b/components/web_discovery/browser/DEPS @@ -2,6 +2,7 @@ include_rules = [ "-content", "+services/network/public", "+extensions/buildflags/buildflags.h", + "+services/network/test", "+services/service_manager/public/cpp", "+third_party/re2", "+third_party/zlib", From 8b00999534749ab9f1d407762b630c1cbb0d65d2 Mon Sep 17 00:00:00 2001 From: Darnell Andries Date: Mon, 28 Oct 2024 14:42:41 -0700 Subject: [PATCH 9/9] Address Web Discovery feedback --- components/web_discovery/browser/credential_manager.cc | 3 ++- components/web_discovery/browser/server_config_loader.cc | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/components/web_discovery/browser/credential_manager.cc b/components/web_discovery/browser/credential_manager.cc index 792f809f08d3..c17bd8a53646 100644 --- a/components/web_discovery/browser/credential_manager.cc +++ b/components/web_discovery/browser/credential_manager.cc @@ -19,6 +19,7 @@ #include "brave/components/web_discovery/browser/util.h" #include "components/prefs/pref_service.h" #include "components/prefs/scoped_user_pref_update.h" +#include "net/http/http_status_code.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/url_response_head.mojom.h" @@ -239,7 +240,7 @@ bool CredentialManager::ProcessJoinResponse( auto& url_loader = join_url_loaders_[date]; auto* response_info = url_loader->ResponseInfo(); if (!response_body || !response_info || - response_info->headers->response_code() != 200) { + response_info->headers->response_code() != net::HttpStatusCode::HTTP_OK) { VLOG(1) << "Failed to fetch credentials for " << date; return false; } diff --git a/components/web_discovery/browser/server_config_loader.cc b/components/web_discovery/browser/server_config_loader.cc index ae01a5770601..279e8ebf1294 100644 --- a/components/web_discovery/browser/server_config_loader.cc +++ b/components/web_discovery/browser/server_config_loader.cc @@ -21,6 +21,7 @@ #include "brave/components/web_discovery/browser/pref_names.h" #include "brave/components/web_discovery/browser/util.h" #include "components/prefs/pref_service.h" +#include "net/http/http_status_code.h" #include "net/traffic_annotation/network_traffic_annotation.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "services/network/public/cpp/simple_url_loader.h" @@ -332,8 +333,10 @@ void ServerConfigLoader::OnConfigResponsesDownloaded( auto* quorum_response_info = quorum_config_url_loader_->ResponseInfo(); if (!collector_response_body || !quorum_response_body || !collector_response_info || !quorum_response_info || - collector_response_info->headers->response_code() != 200 || - quorum_response_info->headers->response_code() != 200) { + collector_response_info->headers->response_code() != + net::HttpStatusCode::HTTP_OK || + quorum_response_info->headers->response_code() != + net::HttpStatusCode::HTTP_OK) { VLOG(1) << "Failed to download one or more server configs"; OnConfigResponsesProcessed(nullptr); return; @@ -438,7 +441,7 @@ void ServerConfigLoader::OnPatternsResponse( std::optional response_body) { auto* response_info = patterns_url_loader_->ResponseInfo(); if (!response_body || !response_info || - response_info->headers->response_code() != 200) { + response_info->headers->response_code() != net::HttpStatusCode::HTTP_OK) { VLOG(1) << "Failed to retrieve patterns file"; HandlePatternsStatus(false); return;