diff --git a/.storybook/BUILD.gn b/.storybook/BUILD.gn index c329324e9a6b..99a81a53497d 100644 --- a/.storybook/BUILD.gn +++ b/.storybook/BUILD.gn @@ -11,7 +11,7 @@ group("storybook") { # are disabled in a regular brave build due to build flags, # they will be generated before storybook is compiled. deps = [ - "//brave/components/brave_vpn:mojom_js", + "//brave/components/brave_vpn/mojom:mojom_js", "//brave/components/brave_wallet/common:mojom_js", "//ui/webui/resources/js:cr.m", ] diff --git a/browser/brave_content_browser_client.cc b/browser/brave_content_browser_client.cc index 6450b7627862..81cbbb68d7d9 100644 --- a/browser/brave_content_browser_client.cc +++ b/browser/brave_content_browser_client.cc @@ -174,8 +174,8 @@ using extensions::ChromeContentBrowserClientExtensionsPart; #if BUILDFLAG(ENABLE_BRAVE_VPN) && !BUILDFLAG(IS_ANDROID) #include "brave/browser/ui/webui/brave_vpn/vpn_panel_ui.h" -#include "brave/components/brave_vpn/brave_vpn.mojom.h" #include "brave/components/brave_vpn/brave_vpn_utils.h" +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" #endif #if BUILDFLAG(ETHEREUM_REMOTE_CLIENT_ENABLED) diff --git a/browser/brave_vpn/android/brave_vpn_native_worker.cc b/browser/brave_vpn/android/brave_vpn_native_worker.cc index df0d471c5a23..3626fd584438 100644 --- a/browser/brave_vpn/android/brave_vpn_native_worker.cc +++ b/browser/brave_vpn/android/brave_vpn_native_worker.cc @@ -17,10 +17,12 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_manager.h" +using brave_vpn::BraveVpnService; + namespace { BraveVpnService* GetBraveVpnService() { - return BraveVpnServiceFactory::GetForProfile( + return brave_vpn::BraveVpnServiceFactory::GetForProfile( ProfileManager::GetActiveUserProfile()->GetOriginalProfile()); } diff --git a/browser/brave_vpn/brave_vpn_service_factory.cc b/browser/brave_vpn/brave_vpn_service_factory.cc index 7b9f74c0c1c3..6b8bba8d129d 100644 --- a/browser/brave_vpn/brave_vpn_service_factory.cc +++ b/browser/brave_vpn/brave_vpn_service_factory.cc @@ -6,9 +6,11 @@ #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" #include "base/feature_list.h" +#include "brave/browser/profiles/profile_util.h" #include "brave/browser/skus/skus_service_factory.h" #include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/components/skus/common/features.h" +#include "build/build_config.h" #include "chrome/browser/profiles/incognito_helpers.h" #include "chrome/browser/profiles/profile.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" @@ -16,58 +18,39 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/storage_partition.h" -// TODO(bsclifton) or TODO(shong): -// We should be able to consolidate this integration into one implementation -// which we can share between Android and Desktop. -// -// As seen below, Desktop returns BraveVpnServiceDesktop and Android -// returns BraveVpnService. -// -// See https://github.com/brave/brave-browser/issues/20374 for more info. #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" #include "brave/components/brave_vpn/brave_vpn_utils.h" #endif +namespace brave_vpn { + // static BraveVpnServiceFactory* BraveVpnServiceFactory::GetInstance() { return base::Singleton::get(); } // static -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) -BraveVpnServiceDesktop* BraveVpnServiceFactory::GetForProfile( - Profile* profile) { - return static_cast( - GetInstance()->GetServiceForBrowserContext(profile, true)); -} -#elif BUILDFLAG(IS_ANDROID) BraveVpnService* BraveVpnServiceFactory::GetForProfile(Profile* profile) { return static_cast( GetInstance()->GetServiceForBrowserContext(profile, true)); } -#endif -// TODO(bsclifton) or TODO(shong): -// BraveVpnServiceDesktop is currently only used on Desktop, -// which is why there are only OS guards for Windows and macOS. -// Consolidating the Android/Desktop behaviors is captured with: -// https://github.com/brave/brave-browser/issues/20374 BraveVpnServiceFactory::BraveVpnServiceFactory() : BrowserContextKeyedServiceFactory( "BraveVpnService", BrowserContextDependencyManager::GetInstance()) { -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) DependsOn(skus::SkusServiceFactory::GetInstance()); -#endif } BraveVpnServiceFactory::~BraveVpnServiceFactory() = default; KeyedService* BraveVpnServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { + // TODO(simonhong): Can we use this check for android? + // For now, vpn is disabled by default on desktop but not sure on + // android. #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) - if (!brave_vpn::IsBraveVPNEnabled()) + if (!IsBraveVPNEnabled()) return nullptr; #endif @@ -75,22 +58,17 @@ KeyedService* BraveVpnServiceFactory::BuildServiceInstanceFor( auto shared_url_loader_factory = default_storage_partition->GetURLLoaderFactoryForBrowserProcess(); -// TODO(bsclifton) or TODO(shong): -// see same notes above -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) auto callback = base::BindRepeating( [](content::BrowserContext* context) { return skus::SkusServiceFactory::GetForContext(context); }, context); - return new BraveVpnServiceDesktop( - shared_url_loader_factory, user_prefs::UserPrefs::Get(context), callback); +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + return new BraveVpnService(shared_url_loader_factory, + user_prefs::UserPrefs::Get(context), callback); #elif BUILDFLAG(IS_ANDROID) - return new BraveVpnService(shared_url_loader_factory); + return new BraveVpnService(shared_url_loader_factory, callback); #endif } -content::BrowserContext* BraveVpnServiceFactory::GetBrowserContextToUse( - content::BrowserContext* context) const { - return chrome::GetBrowserContextRedirectedInIncognito(context); -} +} // namespace brave_vpn diff --git a/browser/brave_vpn/brave_vpn_service_factory.h b/browser/brave_vpn/brave_vpn_service_factory.h index 38919d5b78b8..d71798a53efa 100644 --- a/browser/brave_vpn/brave_vpn_service_factory.h +++ b/browser/brave_vpn/brave_vpn_service_factory.h @@ -14,23 +14,13 @@ class Profile; -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) -class BraveVpnServiceDesktop; -#endif +namespace brave_vpn { -#if BUILDFLAG(IS_ANDROID) class BraveVpnService; -#endif class BraveVpnServiceFactory : public BrowserContextKeyedServiceFactory { public: -#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) - static BraveVpnServiceDesktop* GetForProfile(Profile* profile); -#endif - -#if BUILDFLAG(IS_ANDROID) static BraveVpnService* GetForProfile(Profile* profile); -#endif static BraveVpnServiceFactory* GetInstance(); BraveVpnServiceFactory(const BraveVpnServiceFactory&) = delete; @@ -45,8 +35,8 @@ class BraveVpnServiceFactory : public BrowserContextKeyedServiceFactory { // BrowserContextKeyedServiceFactory overrides: KeyedService* BuildServiceInstanceFor( content::BrowserContext* context) const override; - content::BrowserContext* GetBrowserContextToUse( - content::BrowserContext* context) const override; }; +} // namespace brave_vpn + #endif // BRAVE_BROWSER_BRAVE_VPN_BRAVE_VPN_SERVICE_FACTORY_H_ diff --git a/browser/brave_vpn/sources.gni b/browser/brave_vpn/sources.gni index 76dfeec5a4be..091bcce4d4f2 100644 --- a/browser/brave_vpn/sources.gni +++ b/browser/brave_vpn/sources.gni @@ -12,16 +12,18 @@ if (enable_brave_vpn) { brave_browser_brave_vpn_sources += [ "//brave/browser/brave_vpn/brave_vpn_service_factory.cc", "//brave/browser/brave_vpn/brave_vpn_service_factory.h", + "//brave/browser/brave_vpn/vpn_utils.cc", + "//brave/browser/brave_vpn/vpn_utils.h", ] brave_browser_brave_vpn_deps += [ "//base", + "//brave/browser/skus", "//brave/components/brave_vpn", "//chrome/browser/profiles:profile", "//components/keyed_service/content", "//components/user_prefs", "//content/public/browser", - "//brave/browser/skus" ] if (is_android) { diff --git a/browser/brave_vpn/vpn_utils.cc b/browser/brave_vpn/vpn_utils.cc new file mode 100644 index 000000000000..96ad5ba4d4e0 --- /dev/null +++ b/browser/brave_vpn/vpn_utils.cc @@ -0,0 +1,25 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/browser/brave_vpn/vpn_utils.h" + +#include "brave/browser/profiles/profile_util.h" +#include "brave/components/brave_vpn/brave_vpn_utils.h" +#include "build/build_config.h" + +namespace brave_vpn { + +bool IsBraveVPNEnabled(content::BrowserContext* context) { + // TODO(simonhong): Can we use this check for android? + // For now, vpn is disabled by default on desktop but not sure on + // android. +#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) + return brave_vpn::IsBraveVPNEnabled() && brave::IsRegularProfile(context); +#else + return brave::IsRegularProfile(context); +#endif +} + +} // namespace brave_vpn diff --git a/browser/brave_vpn/vpn_utils.h b/browser/brave_vpn/vpn_utils.h new file mode 100644 index 000000000000..9c861b5ea21e --- /dev/null +++ b/browser/brave_vpn/vpn_utils.h @@ -0,0 +1,19 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_BROWSER_BRAVE_VPN_VPN_UTILS_H_ +#define BRAVE_BROWSER_BRAVE_VPN_VPN_UTILS_H_ + +namespace content { +class BrowserContext; +} // namespace content + +namespace brave_vpn { + +bool IsBraveVPNEnabled(content::BrowserContext* context); + +} // namespace brave_vpn + +#endif // BRAVE_BROWSER_BRAVE_VPN_VPN_UTILS_H_ diff --git a/browser/ui/BUILD.gn b/browser/ui/BUILD.gn index eb2db2e9e7a2..79f6bb1dc732 100644 --- a/browser/ui/BUILD.gn +++ b/browser/ui/BUILD.gn @@ -548,7 +548,7 @@ source_set("ui") { ] deps += [ "//brave/components/brave_vpn", - "//brave/components/brave_vpn:mojom", + "//brave/components/brave_vpn/mojom", "//brave/components/brave_vpn/resources/panel:brave_vpn_panel_generated", "//mojo/public/cpp/bindings", "//ui/webui:webui", diff --git a/browser/ui/brave_browser_command_controller.cc b/browser/ui/brave_browser_command_controller.cc index abf57578211f..d31652bae036 100644 --- a/browser/ui/brave_browser_command_controller.cc +++ b/browser/ui/brave_browser_command_controller.cc @@ -31,8 +31,8 @@ #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" -#include "brave/components/brave_vpn/brave_vpn_utils.h" +#include "brave/browser/brave_vpn/vpn_utils.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #endif #if BUILDFLAG(ENABLE_SIDEBAR) @@ -64,7 +64,10 @@ BraveBrowserCommandController::BraveBrowserCommandController(Browser* browser) brave_command_updater_(nullptr) { InitBraveCommandState(); #if BUILDFLAG(ENABLE_BRAVE_VPN) - Observe(BraveVpnServiceFactory::GetForProfile(browser_->profile())); + if (auto* vpn_service = brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { + Observe(vpn_service); + } #endif } @@ -189,7 +192,7 @@ void BraveBrowserCommandController::UpdateCommandForSidebar() { void BraveBrowserCommandController::UpdateCommandForBraveVPN() { #if BUILDFLAG(ENABLE_BRAVE_VPN) - if (!brave_vpn::IsBraveVPNEnabled()) { + if (!brave_vpn::IsBraveVPNEnabled(browser_->profile())) { UpdateCommandEnabled(IDC_SHOW_BRAVE_VPN_PANEL, false); UpdateCommandEnabled(IDC_BRAVE_VPN_MENU, false); UpdateCommandEnabled(IDC_TOGGLE_BRAVE_VPN_TOOLBAR_BUTTON, false); @@ -206,8 +209,8 @@ void BraveBrowserCommandController::UpdateCommandForBraveVPN() { UpdateCommandEnabled(IDC_ABOUT_BRAVE_VPN, true); UpdateCommandEnabled(IDC_MANAGE_BRAVE_VPN_PLAN, true); - if (auto* vpn_service = - BraveVpnServiceFactory::GetForProfile(browser_->profile())) { + if (auto* vpn_service = brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { // Only show vpn sub menu for purchased user. UpdateCommandEnabled(IDC_BRAVE_VPN_MENU, vpn_service->is_purchased_user()); UpdateCommandEnabled(IDC_TOGGLE_BRAVE_VPN, diff --git a/browser/ui/brave_browser_command_controller.h b/browser/ui/brave_browser_command_controller.h index c66cd5610514..3200e531c9dd 100644 --- a/browser/ui/brave_browser_command_controller.h +++ b/browser/ui/brave_browser_command_controller.h @@ -28,7 +28,7 @@ namespace chrome { class BraveBrowserCommandController : public chrome::BrowserCommandController #if BUILDFLAG(ENABLE_BRAVE_VPN) , - public BraveVPNServiceObserver + public brave_vpn::BraveVPNServiceObserver #endif { public: @@ -59,7 +59,7 @@ class BraveBrowserCommandController : public chrome::BrowserCommandController bool UpdateCommandEnabled(int id, bool state) override; #if BUILDFLAG(ENABLE_BRAVE_VPN) - // BraveVPNServiceObserver overrides: + // brave_vpn::BraveVPNServiceObserver overrides: void OnPurchasedStateChanged(brave_vpn::mojom::PurchasedState state) override; #endif diff --git a/browser/ui/brave_browser_command_controller_browsertest.cc b/browser/ui/brave_browser_command_controller_browsertest.cc index 0b3e5531c71b..ed3feb357304 100644 --- a/browser/ui/brave_browser_command_controller_browsertest.cc +++ b/browser/ui/brave_browser_command_controller_browsertest.cc @@ -32,7 +32,7 @@ #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/components/brave_vpn/features.h" #endif @@ -47,9 +47,12 @@ class BraveBrowserCommandControllerTest : public InProcessBrowserTest { #if BUILDFLAG(ENABLE_BRAVE_VPN) void SetPurchasedUserForBraveVPN(Browser* browser, bool purchased) { - auto* service = BraveVpnServiceFactory::GetForProfile(browser->profile()); - auto target_state = - purchased ? PurchasedState::PURCHASED : PurchasedState::NOT_PURCHASED; + auto* service = + brave_vpn::BraveVpnServiceFactory::GetForProfile(browser->profile()); + ASSERT_TRUE(!!service); + auto target_state = purchased + ? brave_vpn::mojom::PurchasedState::PURCHASED + : brave_vpn::mojom::PurchasedState::NOT_PURCHASED; service->SetPurchasedState(target_state); // Call explicitely to update vpn commands status because mojo works in // async way. @@ -144,10 +147,6 @@ IN_PROC_BROWSER_TEST_F(BraveBrowserCommandControllerTest, command_controller->IsCommandEnabled(IDC_NEW_OFFTHERECORD_WINDOW_TOR)); #endif -#if BUILDFLAG(ENABLE_BRAVE_VPN) - CheckBraveVPNCommands(private_browser); -#endif - if (syncer::IsSyncAllowedByFlag()) EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SHOW_BRAVE_SYNC)); else @@ -182,10 +181,6 @@ IN_PROC_BROWSER_TEST_F(BraveBrowserCommandControllerTest, command_controller->IsCommandEnabled(IDC_NEW_OFFTHERECORD_WINDOW_TOR)); #endif -#if BUILDFLAG(ENABLE_BRAVE_VPN) - CheckBraveVPNCommands(guest_browser); -#endif - EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SHOW_BRAVE_SYNC)); EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SHOW_BRAVE_WALLET)); @@ -220,10 +215,6 @@ IN_PROC_BROWSER_TEST_F(BraveBrowserCommandControllerTest, else EXPECT_FALSE(command_controller->IsCommandEnabled(IDC_SHOW_BRAVE_SYNC)); -#if BUILDFLAG(ENABLE_BRAVE_VPN) - CheckBraveVPNCommands(tor_browser); -#endif - EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_SHOW_BRAVE_WALLET)); EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_ADD_NEW_PROFILE)); EXPECT_TRUE(command_controller->IsCommandEnabled(IDC_OPEN_GUEST_PROFILE)); diff --git a/browser/ui/toolbar/brave_app_menu_model_browsertest.cc b/browser/ui/toolbar/brave_app_menu_model_browsertest.cc index 79ab8a9ba807..bf4d52247037 100644 --- a/browser/ui/toolbar/brave_app_menu_model_browsertest.cc +++ b/browser/ui/toolbar/brave_app_menu_model_browsertest.cc @@ -34,7 +34,7 @@ #if BUILDFLAG(ENABLE_BRAVE_VPN) #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/components/brave_vpn/features.h" #endif @@ -55,9 +55,12 @@ class BraveAppMenuBrowserTest : public InProcessBrowserTest { #if BUILDFLAG(ENABLE_BRAVE_VPN) void SetPurchasedUserForBraveVPN(Browser* browser, bool purchased) { - auto* service = BraveVpnServiceFactory::GetForProfile(browser->profile()); - auto target_state = - purchased ? PurchasedState::PURCHASED : PurchasedState::NOT_PURCHASED; + auto* service = + brave_vpn::BraveVpnServiceFactory::GetForProfile(browser->profile()); + ASSERT_TRUE(!!service); + auto target_state = purchased + ? brave_vpn::mojom::PurchasedState::PURCHASED + : brave_vpn::mojom::PurchasedState::NOT_PURCHASED; service->SetPurchasedState(target_state); // Call explicitely to update vpn commands status because mojo works in // async way. @@ -190,17 +193,17 @@ IN_PROC_BROWSER_TEST_F(BraveAppMenuBrowserTest, MenuOrderTest) { IDC_SHOW_BRAVE_WALLET, IDC_MANAGE_EXTENSIONS, IDC_SHOW_BRAVE_SYNC, -#if BUILDFLAG(ENABLE_BRAVE_VPN) - IDC_SHOW_BRAVE_VPN_PANEL, -#endif IDC_SHOW_BRAVE_ADBLOCK, IDC_ADD_NEW_PROFILE, IDC_OPEN_GUEST_PROFILE, IDC_SHOW_BRAVE_WEBCOMPAT_REPORTER }; std::vector commands_disabled_for_private_profile = { - IDC_NEW_TOR_CONNECTION_FOR_SITE, - IDC_RECENT_TABS_MENU, + IDC_NEW_TOR_CONNECTION_FOR_SITE, + IDC_RECENT_TABS_MENU, +#if BUILDFLAG(ENABLE_BRAVE_VPN) + IDC_SHOW_BRAVE_VPN_PANEL, +#endif }; if (!syncer::IsSyncAllowedByFlag()) { commands_in_order_for_private_profile.erase( @@ -223,21 +226,17 @@ IN_PROC_BROWSER_TEST_F(BraveAppMenuBrowserTest, MenuOrderTest) { DCHECK(guest_browser); EXPECT_TRUE(guest_browser->profile()->IsGuestSession()); std::vector commands_in_order_for_guest_profile = { - IDC_NEW_TAB, - IDC_NEW_WINDOW, - IDC_SHOW_DOWNLOADS, -#if BUILDFLAG(ENABLE_BRAVE_VPN) - IDC_SHOW_BRAVE_VPN_PANEL, -#endif - IDC_SHOW_BRAVE_ADBLOCK, - IDC_SHOW_BRAVE_WEBCOMPAT_REPORTER - }; + IDC_NEW_TAB, IDC_NEW_WINDOW, IDC_SHOW_DOWNLOADS, IDC_SHOW_BRAVE_ADBLOCK, + IDC_SHOW_BRAVE_WEBCOMPAT_REPORTER}; CheckCommandsAreInOrderInMenuModel(guest_browser, commands_in_order_for_guest_profile); std::vector commands_disabled_for_guest_profile = { IDC_NEW_INCOGNITO_WINDOW, #if BUILDFLAG(ENABLE_TOR) IDC_NEW_OFFTHERECORD_WINDOW_TOR, +#endif +#if BUILDFLAG(ENABLE_BRAVE_VPN) + IDC_SHOW_BRAVE_VPN_PANEL, #endif IDC_SHOW_BRAVE_REWARDS, IDC_RECENT_TABS_MENU, @@ -258,26 +257,25 @@ IN_PROC_BROWSER_TEST_F(BraveAppMenuBrowserTest, MenuOrderTest) { DCHECK(tor_browser); EXPECT_TRUE(tor_browser->profile()->IsTor()); std::vector commands_in_order_for_tor_profile = { - IDC_NEW_TAB, - IDC_NEW_TOR_CONNECTION_FOR_SITE, - IDC_NEW_WINDOW, - IDC_NEW_INCOGNITO_WINDOW, - IDC_NEW_OFFTHERECORD_WINDOW_TOR, - IDC_SHOW_BRAVE_REWARDS, - IDC_BOOKMARKS_MENU, - IDC_SHOW_DOWNLOADS, - IDC_SHOW_BRAVE_WALLET, - IDC_SHOW_BRAVE_SYNC, + IDC_NEW_TAB, + IDC_NEW_TOR_CONNECTION_FOR_SITE, + IDC_NEW_WINDOW, + IDC_NEW_INCOGNITO_WINDOW, + IDC_NEW_OFFTHERECORD_WINDOW_TOR, + IDC_SHOW_BRAVE_REWARDS, + IDC_BOOKMARKS_MENU, + IDC_SHOW_DOWNLOADS, + IDC_SHOW_BRAVE_WALLET, + IDC_SHOW_BRAVE_SYNC, + IDC_SHOW_BRAVE_ADBLOCK, + IDC_ADD_NEW_PROFILE, + IDC_OPEN_GUEST_PROFILE, + IDC_SHOW_BRAVE_WEBCOMPAT_REPORTER}; + std::vector commands_disabled_for_tor_profile = { + IDC_RECENT_TABS_MENU, #if BUILDFLAG(ENABLE_BRAVE_VPN) IDC_SHOW_BRAVE_VPN_PANEL, #endif - IDC_SHOW_BRAVE_ADBLOCK, - IDC_ADD_NEW_PROFILE, - IDC_OPEN_GUEST_PROFILE, - IDC_SHOW_BRAVE_WEBCOMPAT_REPORTER - }; - std::vector commands_disabled_for_tor_profile = { - IDC_RECENT_TABS_MENU, }; if (!syncer::IsSyncAllowedByFlag()) { commands_in_order_for_tor_profile.erase( diff --git a/browser/ui/toolbar/brave_vpn_menu_model.h b/browser/ui/toolbar/brave_vpn_menu_model.h index ccb30dce0914..37b51e7e057a 100644 --- a/browser/ui/toolbar/brave_vpn_menu_model.h +++ b/browser/ui/toolbar/brave_vpn_menu_model.h @@ -6,6 +6,7 @@ #ifndef BRAVE_BROWSER_UI_TOOLBAR_BRAVE_VPN_MENU_MODEL_H_ #define BRAVE_BROWSER_UI_TOOLBAR_BRAVE_VPN_MENU_MODEL_H_ +#include "base/memory/raw_ptr.h" #include "ui/base/models/simple_menu_model.h" class Browser; @@ -26,7 +27,7 @@ class BraveVPNMenuModel : public ui::SimpleMenuModel, void Build(); bool IsBraveVPNButtonVisible() const; - Browser* browser_ = nullptr; + raw_ptr browser_ = nullptr; }; #endif // BRAVE_BROWSER_UI_TOOLBAR_BRAVE_VPN_MENU_MODEL_H_ diff --git a/browser/ui/views/toolbar/brave_toolbar_view.cc b/browser/ui/views/toolbar/brave_toolbar_view.cc index 6606949bbb08..4e8ebd4ac137 100644 --- a/browser/ui/views/toolbar/brave_toolbar_view.cc +++ b/browser/ui/views/toolbar/brave_toolbar_view.cc @@ -32,8 +32,8 @@ #include "brave/components/brave_wallet/browser/brave_wallet_utils.h" #if BUILDFLAG(ENABLE_BRAVE_VPN) +#include "brave/browser/brave_vpn/vpn_utils.h" #include "brave/browser/ui/views/toolbar/brave_vpn_button.h" -#include "brave/components/brave_vpn/brave_vpn_utils.h" #include "brave/components/brave_vpn/pref_names.h" #endif @@ -145,28 +145,27 @@ void BraveToolbarView::Init() { browser, command, ui::DispositionFromEventFlags(event.flags())); }; - bookmark_ = new BookmarkButton( - base::BindRepeating(callback, browser_, IDC_BOOKMARK_THIS_TAB)); + DCHECK(location_bar_); + bookmark_ = + AddChildViewAt(std::make_unique(base::BindRepeating( + callback, browser_, IDC_BOOKMARK_THIS_TAB)), + GetIndexOf(location_bar_)); bookmark_->SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON); - DCHECK(location_bar_); - AddChildViewAt(bookmark_, GetIndexOf(location_bar_)); bookmark_->UpdateImageAndText(); if (brave_wallet::IsNativeWalletEnabled() && brave_wallet::IsAllowedForContext(profile)) { - wallet_ = new WalletButton(GetAppMenuButton(), profile->GetPrefs()); + wallet_ = AddChildViewAt( + std::make_unique(GetAppMenuButton(), profile->GetPrefs()), + GetIndexOf(GetAppMenuButton()) - 1); wallet_->SetTriggerableEventFlags(ui::EF_LEFT_MOUSE_BUTTON | ui::EF_MIDDLE_MOUSE_BUTTON); - } - - if (wallet_) { - AddChildViewAt(wallet_, GetIndexOf(GetAppMenuButton()) - 1); wallet_->UpdateImageAndText(); } #if BUILDFLAG(ENABLE_BRAVE_VPN) - if (brave_vpn::IsBraveVPNEnabled()) { + if (brave_vpn::IsBraveVPNEnabled(profile)) { show_brave_vpn_button_.Init( brave_vpn::prefs::kBraveVPNShowButton, profile->GetPrefs(), base::BindRepeating(&BraveToolbarView::OnVPNButtonVisibilityChanged, @@ -182,6 +181,7 @@ void BraveToolbarView::Init() { #if BUILDFLAG(ENABLE_BRAVE_VPN) void BraveToolbarView::OnVPNButtonVisibilityChanged() { + DCHECK(brave_vpn_); brave_vpn_->SetVisible(show_brave_vpn_button_.GetValue()); } #endif diff --git a/browser/ui/views/toolbar/brave_toolbar_view.h b/browser/ui/views/toolbar/brave_toolbar_view.h index 1d98afca10a2..5d76b2d0bf70 100644 --- a/browser/ui/views/toolbar/brave_toolbar_view.h +++ b/browser/ui/views/toolbar/brave_toolbar_view.h @@ -6,6 +6,7 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_TOOLBAR_VIEW_H_ #define BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_TOOLBAR_VIEW_H_ +#include "base/memory/raw_ptr.h" #include "base/scoped_observation.h" #include "brave/components/brave_vpn/buildflags/buildflags.h" #include "chrome/browser/profiles/profile_attributes_storage.h" @@ -53,14 +54,14 @@ class BraveToolbarView : public ToolbarView, void OnProfileWasRemoved(const base::FilePath& profile_path, const std::u16string& profile_name) override; - BookmarkButton* bookmark_ = nullptr; + raw_ptr bookmark_ = nullptr; // Tracks the preference to determine whether bookmark editing is allowed. BooleanPrefMember edit_bookmarks_enabled_; - WalletButton* wallet_ = nullptr; + raw_ptr wallet_ = nullptr; #if BUILDFLAG(ENABLE_BRAVE_VPN) - BraveVPNButton* brave_vpn_ = nullptr; + raw_ptr brave_vpn_ = nullptr; BooleanPrefMember show_brave_vpn_button_; #endif diff --git a/browser/ui/views/toolbar/brave_vpn_button.cc b/browser/ui/views/toolbar/brave_vpn_button.cc index 0a8f947bd754..9db6a52e95ac 100644 --- a/browser/ui/views/toolbar/brave_vpn_button.cc +++ b/browser/ui/views/toolbar/brave_vpn_button.cc @@ -14,7 +14,7 @@ #include "brave/app/vector_icons/vector_icons.h" #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" #include "brave/browser/themes/theme_properties.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/grit/brave_generated_resources.h" #include "chrome/browser/themes/theme_properties.h" #include "chrome/browser/ui/browser.h" @@ -31,6 +31,9 @@ #include "ui/views/border.h" #include "ui/views/controls/highlight_path_generator.h" +using ConnectionState = brave_vpn::mojom::ConnectionState; +using PurchasedState = brave_vpn::mojom::PurchasedState; + namespace { constexpr int kButtonRadius = 47; @@ -54,12 +57,13 @@ class BraveVPNButtonHighlightPathGenerator class VPNButtonMenuModel : public ui::SimpleMenuModel, public ui::SimpleMenuModel::Delegate, - public BraveVPNServiceObserver { + public brave_vpn::BraveVPNServiceObserver { public: explicit VPNButtonMenuModel(Browser* browser) : SimpleMenuModel(this), browser_(browser), - service_(BraveVpnServiceFactory::GetForProfile(browser_->profile())) { + service_(brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { DCHECK(service_); Observe(service_); Build(service_->is_purchased_user()); @@ -99,7 +103,7 @@ class VPNButtonMenuModel : public ui::SimpleMenuModel, } raw_ptr browser_ = nullptr; - raw_ptr service_ = nullptr; + raw_ptr service_ = nullptr; }; } // namespace @@ -110,7 +114,8 @@ BraveVPNButton::BraveVPNButton(Browser* browser) std::make_unique(browser), nullptr), browser_(browser), - service_(BraveVpnServiceFactory::GetForProfile(browser_->profile())) { + service_(brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { DCHECK(service_); Observe(service_); diff --git a/browser/ui/views/toolbar/brave_vpn_button.h b/browser/ui/views/toolbar/brave_vpn_button.h index 742e08db700c..41c8ffb11cfb 100644 --- a/browser/ui/views/toolbar/brave_vpn_button.h +++ b/browser/ui/views/toolbar/brave_vpn_button.h @@ -6,14 +6,19 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_BUTTON_H_ #define BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_BUTTON_H_ +#include "base/memory/raw_ptr.h" #include "brave/components/brave_vpn/brave_vpn_service_observer.h" #include "chrome/browser/ui/views/toolbar/toolbar_button.h" #include "ui/base/metadata/metadata_header_macros.h" -class BraveVpnServiceDesktop; +namespace brave_vpn { +class BraveVpnService; +} // namespace brave_vpn + class Browser; -class BraveVPNButton : public ToolbarButton, public BraveVPNServiceObserver { +class BraveVPNButton : public ToolbarButton, + public brave_vpn::BraveVPNServiceObserver { public: METADATA_HEADER(BraveVPNButton); @@ -36,8 +41,8 @@ class BraveVPNButton : public ToolbarButton, public BraveVPNServiceObserver { void OnButtonPressed(const ui::Event& event); - Browser* browser_ = nullptr; - BraveVpnServiceDesktop* service_ = nullptr; + raw_ptr browser_ = nullptr; + raw_ptr service_ = nullptr; }; #endif // BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_BUTTON_H_ diff --git a/browser/ui/views/toolbar/brave_vpn_status_label.cc b/browser/ui/views/toolbar/brave_vpn_status_label.cc index ea8fc15bceb7..dc9cdcbb12ea 100644 --- a/browser/ui/views/toolbar/brave_vpn_status_label.cc +++ b/browser/ui/views/toolbar/brave_vpn_status_label.cc @@ -9,16 +9,20 @@ #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" #include "brave/browser/themes/theme_properties.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/grit/brave_generated_resources.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/theme_provider.h" +using ConnectionState = brave_vpn::mojom::ConnectionState; +using PurchasedState = brave_vpn::mojom::PurchasedState; + BraveVPNStatusLabel::BraveVPNStatusLabel(Browser* browser) : browser_(browser), - service_(BraveVpnServiceFactory::GetForProfile(browser_->profile())) { + service_(brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { DCHECK(service_); Observe(service_); diff --git a/browser/ui/views/toolbar/brave_vpn_status_label.h b/browser/ui/views/toolbar/brave_vpn_status_label.h index ae3c74066cba..1c7c45c4ad5f 100644 --- a/browser/ui/views/toolbar/brave_vpn_status_label.h +++ b/browser/ui/views/toolbar/brave_vpn_status_label.h @@ -6,14 +6,18 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_STATUS_LABEL_H_ #define BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_STATUS_LABEL_H_ +#include "base/memory/raw_ptr.h" #include "brave/components/brave_vpn/brave_vpn_service_observer.h" #include "ui/views/controls/label.h" -class BraveVpnServiceDesktop; +namespace brave_vpn { +class BraveVpnService; +} // namespace brave_vpn + class Browser; class BraveVPNStatusLabel : public views::Label, - public BraveVPNServiceObserver { + public brave_vpn::BraveVPNServiceObserver { public: explicit BraveVPNStatusLabel(Browser* browser); ~BraveVPNStatusLabel() override; @@ -22,14 +26,14 @@ class BraveVPNStatusLabel : public views::Label, BraveVPNStatusLabel& operator=(const BraveVPNStatusLabel&) = delete; private: - // BraveVPNServiceObserver overrides: + // brave_vpn::BraveVPNServiceObserver overrides: void OnConnectionStateChanged( brave_vpn::mojom::ConnectionState state) override; void UpdateState(); - Browser* browser_ = nullptr; - BraveVpnServiceDesktop* service_ = nullptr; + raw_ptr browser_ = nullptr; + raw_ptr service_ = nullptr; }; #endif // BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_STATUS_LABEL_H_ diff --git a/browser/ui/views/toolbar/brave_vpn_toggle_button.cc b/browser/ui/views/toolbar/brave_vpn_toggle_button.cc index 87728079157b..3814ee6e00f5 100644 --- a/browser/ui/views/toolbar/brave_vpn_toggle_button.cc +++ b/browser/ui/views/toolbar/brave_vpn_toggle_button.cc @@ -10,16 +10,20 @@ #include "base/bind.h" #include "brave/browser/brave_vpn/brave_vpn_service_factory.h" #include "brave/browser/themes/theme_properties.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/grit/brave_generated_resources.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/views/frame/browser_view.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/theme_provider.h" +using ConnectionState = brave_vpn::mojom::ConnectionState; +using PurchasedState = brave_vpn::mojom::PurchasedState; + BraveVPNToggleButton::BraveVPNToggleButton(Browser* browser) : browser_(browser), - service_(BraveVpnServiceFactory::GetForProfile(browser_->profile())) { + service_(brave_vpn::BraveVpnServiceFactory::GetForProfile( + browser_->profile())) { DCHECK(service_); Observe(service_); diff --git a/browser/ui/views/toolbar/brave_vpn_toggle_button.h b/browser/ui/views/toolbar/brave_vpn_toggle_button.h index edd45f6d9e19..31c444bea199 100644 --- a/browser/ui/views/toolbar/brave_vpn_toggle_button.h +++ b/browser/ui/views/toolbar/brave_vpn_toggle_button.h @@ -6,14 +6,18 @@ #ifndef BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_TOGGLE_BUTTON_H_ #define BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_TOGGLE_BUTTON_H_ +#include "base/memory/raw_ptr.h" #include "brave/components/brave_vpn/brave_vpn_service_observer.h" #include "ui/views/controls/button/toggle_button.h" -class BraveVpnServiceDesktop; +namespace brave_vpn { +class BraveVpnService; +} // namespace brave_vpn + class Browser; class BraveVPNToggleButton : public views::ToggleButton, - public BraveVPNServiceObserver { + public brave_vpn::BraveVPNServiceObserver { public: explicit BraveVPNToggleButton(Browser* browser); ~BraveVPNToggleButton() override; @@ -29,8 +33,8 @@ class BraveVPNToggleButton : public views::ToggleButton, void OnButtonPressed(const ui::Event& event); void UpdateState(); - Browser* browser_ = nullptr; - BraveVpnServiceDesktop* service_ = nullptr; + raw_ptr browser_ = nullptr; + raw_ptr service_ = nullptr; }; #endif // BRAVE_BROWSER_UI_VIEWS_TOOLBAR_BRAVE_VPN_TOGGLE_BUTTON_H_ diff --git a/browser/ui/webui/brave_settings_ui.cc b/browser/ui/webui/brave_settings_ui.cc index fb44ec5bedc2..3a466e4b15c3 100644 --- a/browser/ui/webui/brave_settings_ui.cc +++ b/browser/ui/webui/brave_settings_ui.cc @@ -36,7 +36,7 @@ #endif #if BUILDFLAG(ENABLE_BRAVE_VPN) -#include "brave/components/brave_vpn/brave_vpn_utils.h" +#include "brave/browser/brave_vpn/vpn_utils.h" #endif using ntp_background_images::ViewCounterServiceFactory; @@ -75,7 +75,8 @@ void BraveSettingsUI::AddResources(content::WebUIDataSource* html_source, "isIdleDetectionFeatureEnabled", base::FeatureList::IsEnabled(features::kIdleDetection)); #if BUILDFLAG(ENABLE_BRAVE_VPN) - html_source->AddBoolean("isBraveVPNEnabled", brave_vpn::IsBraveVPNEnabled()); + html_source->AddBoolean("isBraveVPNEnabled", + brave_vpn::IsBraveVPNEnabled(profile)); #endif #if BUILDFLAG(ENABLE_SPEEDREADER) html_source->AddBoolean( diff --git a/browser/ui/webui/brave_vpn/vpn_panel_handler.cc b/browser/ui/webui/brave_vpn/vpn_panel_handler.cc index 0ed0e4d63010..378cf6230918 100644 --- a/browser/ui/webui/brave_vpn/vpn_panel_handler.cc +++ b/browser/ui/webui/brave_vpn/vpn_panel_handler.cc @@ -7,18 +7,27 @@ #include +#include "brave/browser/brave_vpn/brave_vpn_service_factory.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" + VPNPanelHandler::VPNPanelHandler( mojo::PendingReceiver receiver, - ui::MojoBubbleWebUIController* webui_controller) + ui::MojoBubbleWebUIController* webui_controller, + Profile* profile) : receiver_(this, std::move(receiver)), - webui_controller_(webui_controller) {} + webui_controller_(webui_controller), + profile_(profile) {} VPNPanelHandler::~VPNPanelHandler() = default; void VPNPanelHandler::ShowUI() { auto embedder = webui_controller_->embedder(); + brave_vpn::BraveVpnService* vpn_service = + brave_vpn::BraveVpnServiceFactory::GetForProfile(profile_); + DCHECK(vpn_service); if (embedder) { embedder->ShowUI(); + vpn_service->LoadPurchasedState(); } } diff --git a/browser/ui/webui/brave_vpn/vpn_panel_handler.h b/browser/ui/webui/brave_vpn/vpn_panel_handler.h index e61bc44af86c..bed77f8dadd3 100644 --- a/browser/ui/webui/brave_vpn/vpn_panel_handler.h +++ b/browser/ui/webui/brave_vpn/vpn_panel_handler.h @@ -9,7 +9,9 @@ #include #include -#include "brave/components/brave_vpn/brave_vpn.mojom.h" +#include "base/memory/raw_ptr.h" +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" +#include "chrome/browser/profiles/profile.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/receiver.h" #include "ui/webui/mojo_bubble_web_ui_controller.h" @@ -18,8 +20,6 @@ namespace content { class WebUI; } // namespace content -class BraveVpnServiceDesktop; - class VPNPanelHandler : public brave_vpn::mojom::PanelHandler { public: using GetWebContentsForTabCallback = @@ -27,7 +27,8 @@ class VPNPanelHandler : public brave_vpn::mojom::PanelHandler { VPNPanelHandler( mojo::PendingReceiver receiver, - ui::MojoBubbleWebUIController* webui_controller); + ui::MojoBubbleWebUIController* webui_controller, + Profile* profile); VPNPanelHandler(const VPNPanelHandler&) = delete; VPNPanelHandler& operator=(const VPNPanelHandler&) = delete; @@ -40,6 +41,7 @@ class VPNPanelHandler : public brave_vpn::mojom::PanelHandler { private: mojo::Receiver receiver_; ui::MojoBubbleWebUIController* const webui_controller_; + raw_ptr profile_; }; #endif // BRAVE_BROWSER_UI_WEBUI_BRAVE_VPN_VPN_PANEL_HANDLER_H_ diff --git a/browser/ui/webui/brave_vpn/vpn_panel_ui.cc b/browser/ui/webui/brave_vpn/vpn_panel_ui.cc index 90f4e688c1ce..587ba90b58ad 100644 --- a/browser/ui/webui/brave_vpn/vpn_panel_ui.cc +++ b/browser/ui/webui/brave_vpn/vpn_panel_ui.cc @@ -64,12 +64,12 @@ void VPNPanelUI::CreatePanelHandler( auto* profile = Profile::FromWebUI(web_ui()); DCHECK(profile); - panel_handler_ = - std::make_unique(std::move(panel_receiver), this); + panel_handler_ = std::make_unique(std::move(panel_receiver), + this, profile); - BraveVpnServiceDesktop* vpn_service_desktop = - BraveVpnServiceFactory::GetForProfile(profile); - if (vpn_service_desktop) { - vpn_service_desktop->BindInterface(std::move(vpn_service_receiver)); + brave_vpn::BraveVpnService* vpn_service = + brave_vpn::BraveVpnServiceFactory::GetForProfile(profile); + if (vpn_service) { + vpn_service->BindInterface(std::move(vpn_service_receiver)); } } diff --git a/browser/ui/webui/brave_vpn/vpn_panel_ui.h b/browser/ui/webui/brave_vpn/vpn_panel_ui.h index 2d9029e2ac5d..94ab76cb47d3 100644 --- a/browser/ui/webui/brave_vpn/vpn_panel_ui.h +++ b/browser/ui/webui/brave_vpn/vpn_panel_ui.h @@ -9,8 +9,8 @@ #include #include "brave/browser/ui/webui/brave_vpn/vpn_panel_handler.h" -#include "brave/components/brave_vpn/brave_vpn.mojom.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" #include "content/public/browser/web_ui_message_handler.h" #include "mojo/public/cpp/bindings/pending_receiver.h" #include "mojo/public/cpp/bindings/pending_remote.h" diff --git a/browser/ui/webui/brave_web_ui_controller_factory.cc b/browser/ui/webui/brave_web_ui_controller_factory.cc index 42e952503468..0b762f785643 100644 --- a/browser/ui/webui/brave_web_ui_controller_factory.cc +++ b/browser/ui/webui/brave_web_ui_controller_factory.cc @@ -44,6 +44,7 @@ #endif #if BUILDFLAG(ENABLE_BRAVE_VPN) && !BUILDFLAG(IS_ANDROID) +#include "brave/browser/brave_vpn/vpn_utils.h" #include "brave/browser/ui/webui/brave_vpn/vpn_panel_ui.h" #include "brave/components/brave_vpn/brave_vpn_utils.h" #endif @@ -107,7 +108,7 @@ WebUIController* NewWebUI(WebUI* web_ui, const GURL& url) { #endif // BUILDFLAG(OS_ANDROID) #if BUILDFLAG(ENABLE_BRAVE_VPN) && !BUILDFLAG(IS_ANDROID) } else if (host == kVPNPanelHost) { - if (brave_vpn::IsBraveVPNEnabled()) { + if (brave_vpn::IsBraveVPNEnabled(profile)) { return new VPNPanelUI(web_ui); } #endif // BUILDFLAG(ENABLE_BRAVE_VPN) diff --git a/chromium_src/chrome/browser/unexpire_flags.cc b/chromium_src/chrome/browser/unexpire_flags.cc index de176a7310d4..f0cccaa585e2 100644 --- a/chromium_src/chrome/browser/unexpire_flags.cc +++ b/chromium_src/chrome/browser/unexpire_flags.cc @@ -21,8 +21,7 @@ bool IsFlagExpired(const flags_ui::FlagsStorage* storage, version_info::Channel channel = chrome::GetChannel(); // Enable VPN feature only for nightly/development. if (base::LowerCaseEqualsASCII(kBraveVPNFeatureInternalName, internal_name) && - (channel == version_info::Channel::STABLE || - channel == version_info::Channel::BETA)) { + channel == version_info::Channel::STABLE) { return true; } #endif diff --git a/chromium_src/net/tools/transport_security_state_generator/input_file_parsers.cc b/chromium_src/net/tools/transport_security_state_generator/input_file_parsers.cc index 79f22eedcd72..900c01efc687 100644 --- a/chromium_src/net/tools/transport_security_state_generator/input_file_parsers.cc +++ b/chromium_src/net/tools/transport_security_state_generator/input_file_parsers.cc @@ -472,6 +472,11 @@ bool ParseJSON(base::StringPiece json, { "name": "payment.rewards.brave.software", "policy": "custom", "mode": "force-https", "pins": "brave"}, { "name": "rewards.brave.com", "mode": "force-https", "policy": "custom", "pins": "brave"}, + // Premium + { "name": "account.brave.com", "mode": "force-https", "policy": "custom", "pins": "brave"}, + { "name": "account.bravesoftware.com", "mode": "force-https", "policy": "custom", "pins": "brave"}, + { "name": "account.brave.software", "mode": "force-https", "policy": "custom", "pins": "brave"}, + // Test page using a CA outside of the pinset (expected to be blocked) { "name": "ssl-pinning.someblog.org", "policy": "custom", "mode": "force-https", "pins": "brave"} // =====END BRAVE HOSTS JSON===== diff --git a/common/extensions/api/_api_features.json b/common/extensions/api/_api_features.json index 7e72b1dc3dba..952519bf9bcf 100644 --- a/common/extensions/api/_api_features.json +++ b/common/extensions/api/_api_features.json @@ -73,7 +73,8 @@ "chrome://tab-strip/*", "chrome://wallet-panel.top-chrome/*", "chrome://wallet/*", - "chrome://brave-shields.top-chrome/*" + "chrome://brave-shields.top-chrome/*", + "chrome://vpn-panel.top-chrome/*" ] }, { "channel": "stable", diff --git a/components/brave_vpn/BUILD.gn b/components/brave_vpn/BUILD.gn index ce870044fd59..ed7689fbf91f 100644 --- a/components/brave_vpn/BUILD.gn +++ b/components/brave_vpn/BUILD.gn @@ -4,24 +4,38 @@ # You can obtain one at http://mozilla.org/MPL/2.0/. import("//brave/components/brave_vpn/buildflags/buildflags.gni") -import("//mojo/public/tools/bindings/mojom.gni") -import("//tools/grit/preprocess_if_expr.gni") -preprocess_folder = "preprocessed" -preprocess_mojo_manifest = "preprocessed_mojo_manifest.json" +assert(enable_brave_vpn) static_library("brave_vpn") { sources = [ + "brave_vpn_constants.h", "brave_vpn_service.cc", "brave_vpn_service.h", + "brave_vpn_service_observer.cc", + "brave_vpn_service_observer.h", + "brave_vpn_utils.cc", + "brave_vpn_utils.h", + "features.cc", + "features.h", ] deps = [ + "mojom", "//base", "//brave/components/api_request_helper:api_request_helper", + "//brave/components/resources:strings", + "//brave/components/skus/browser", + "//brave/components/skus/common", + "//brave/components/skus/common:mojom", + "//brave/components/version_info", "//components/keyed_service/core", + "//components/prefs", + "//components/version_info", + "//services/data_decoder/public/cpp", "//services/network/public/cpp", "//third_party/abseil-cpp:absl", + "//ui/base", "//url", ] @@ -31,20 +45,13 @@ static_library("brave_vpn") { sources += [ "brave_vpn_connection_info.cc", "brave_vpn_connection_info.h", - "brave_vpn_constants.h", "brave_vpn_data_types.h", "brave_vpn_os_connection_api.cc", "brave_vpn_os_connection_api.h", "brave_vpn_os_connection_api_sim.cc", "brave_vpn_os_connection_api_sim.h", - "brave_vpn_service_desktop.cc", - "brave_vpn_service_desktop.h", - "brave_vpn_service_observer.cc", - "brave_vpn_service_observer.h", - "brave_vpn_utils.cc", - "brave_vpn_utils.h", - "features.cc", - "features.h", + "brave_vpn_service_helper.cc", + "brave_vpn_service_helper.h", "pref_names.cc", "pref_names.h", "switches.h", @@ -71,14 +78,7 @@ static_library("brave_vpn") { deps += [ ":brave_vpn_internal", - ":mojom", - "//brave/components/resources:strings", - "//brave/components/skus/browser", - "//brave/components/skus/common", - "//brave/components/skus/common:mojom", - "//components/prefs", "//third_party/icu", - "//ui/base", ] } } @@ -138,20 +138,3 @@ executable("vpntool") { libs = [ "rasapi32.lib" ] } } - -preprocess_if_expr("preprocess_mojo") { - deps = [ "//brave/components/brave_vpn:mojom_js" ] - in_folder = "$target_gen_dir" - out_folder = "$target_gen_dir/$preprocess_folder" - out_manifest = "$target_gen_dir/$preprocess_mojo_manifest" - in_files = [ "brave_vpn.mojom-lite.js" ] -} - -mojom("mojom") { - sources = [ "brave_vpn.mojom" ] - - deps = [ - "//mojo/public/mojom/base", - "//url/mojom:url_mojom_gurl", - ] -} diff --git a/components/brave_vpn/DEPS b/components/brave_vpn/DEPS index fb9ef90d6215..ac11e42f72a5 100644 --- a/components/brave_vpn/DEPS +++ b/components/brave_vpn/DEPS @@ -1,8 +1,9 @@ include_rules = [ + "+services/data_decoder/public/cpp", "+services/network/public/cpp", "+base", "+components/keyed_service/core", "+net", "+url", "+brave/components/api_request_helper", -] \ No newline at end of file +] diff --git a/components/brave_vpn/brave_vpn_constants.h b/components/brave_vpn/brave_vpn_constants.h index 095bf5791325..94d2ccf3bc77 100644 --- a/components/brave_vpn/brave_vpn_constants.h +++ b/components/brave_vpn/brave_vpn_constants.h @@ -26,7 +26,7 @@ constexpr webui::LocalizedString kLocalizedStrings[] = { {"braveVpnBuy", IDS_BRAVE_VPN_BUY}, {"braveVpnPurchased", IDS_BRAVE_VPN_HAS_PURCHASED}, {"braveVpnPoweredBy", IDS_BRAVE_VPN_POWERED_BY}, - {"braveVpnSettings", IDS_BRAVE_VPN_SETTINGS}, + {"braveVpnSettingsPanelHeader", IDS_BRAVE_VPN_SETTINGS_PANEL_HEADER}, {"braveVpnStatus", IDS_BRAVE_VPN_STATUS}, {"braveVpnExpires", IDS_BRAVE_VPN_EXPIRES}, {"braveVpnManageSubscription", IDS_BRAVE_VPN_MANAGE_SUBSCRIPTION}, @@ -38,6 +38,35 @@ constexpr webui::LocalizedString kLocalizedStrings[] = { {"braveVpnFeature4", IDS_BRAVE_VPN_FEATURE_4}, {"braveVpnFeature5", IDS_BRAVE_VPN_FEATURE_5}, {"braveVpnLoading", IDS_BRAVE_VPN_LOADING}, + {"braveVpnEditPaymentMethod", IDS_BRAVE_VPN_EDIT_PAYMENT}, + {"braveVpnPaymentFailure", IDS_BRAVE_VPN_PAYMENT_FAILURE}, + {"braveVpnPaymentFailureReason", IDS_BRAVE_VPN_PAYMENT_FAILURE_REASON}, + {"braveVpnSupportEmail", IDS_BRAVE_VPN_SUPPORT_EMAIL}, + {"braveVpnSupportSubject", IDS_BRAVE_VPN_SUPPORT_SUBJECT}, + {"braveVpnSupportSubjectNotSet", IDS_BRAVE_VPN_SUPPORT_SUBJECT_NOTSET}, + {"braveVpnSupportSubjectOtherConnectionProblem", + IDS_BRAVE_VPN_SUPPORT_SUBJECT_OTHER_CONNECTION_PROBLEM}, + {"braveVpnSupportSubjectNoInternet", + IDS_BRAVE_VPN_SUPPORT_SUBJECT_NO_INTERNET}, + {"braveVpnSupportSubjectSlowConnection", + IDS_BRAVE_VPN_SUPPORT_SUBJECT_SLOW_CONNECTION}, + {"braveVpnSupportSubjectWebsiteDoesntWork", + IDS_BRAVE_VPN_SUPPORT_SUBJECT_WEBSITE_DOESNT_WORK}, + {"braveVpnSupportSubjectOther", IDS_BRAVE_VPN_SUPPORT_SUBJECT_OTHER}, + {"braveVpnSupportBody", IDS_BRAVE_VPN_SUPPORT_BODY}, + {"braveVpnSupportOptionalHeader", IDS_BRAVE_VPN_SUPPORT_OPTIONAL_HEADER}, + {"braveVpnSupportOptionalNotes", IDS_BRAVE_VPN_SUPPORT_OPTIONAL_NOTES}, + {"braveVpnSupportOptionalNotesPrivacyPolicy", + IDS_BRAVE_VPN_SUPPORT_OPTIONAL_NOTES_PRIVACY_POLICY}, + {"braveVpnSupportOptionalVpnHostname", + IDS_BRAVE_VPN_SUPPORT_OPTIONAL_VPN_HOSTNAME}, + {"braveVpnSupportOptionalAppVersion", + IDS_BRAVE_VPN_SUPPORT_OPTIONAL_APP_VERSION}, + {"braveVpnSupportOptionalOsVersion", + IDS_BRAVE_VPN_SUPPORT_OPTIONAL_OS_VERSION}, + {"braveVpnSupportNotes", IDS_BRAVE_VPN_SUPPORT_NOTES}, + {"braveVpnSupportSubmit", IDS_BRAVE_VPN_SUPPORT_SUBMIT}, + {"braveVpnConnectNotAllowed", IDS_BRAVE_VPN_CONNECT_NOT_ALLOWED}, }; constexpr char kManageUrlProd[] = "http://account.brave.com/"; @@ -47,6 +76,13 @@ constexpr char kManageUrlDev[] = "https://account.brave.software/"; // TODO(simonhong): Update when vpn feedback url is ready. constexpr char kFeedbackUrl[] = "http://support.brave.com/"; constexpr char kAboutUrl[] = "https://brave.com/firewall-vpn/"; + +constexpr char kBraveVPNEntryName[] = "BraveVPN"; +constexpr char kRegionContinentKey[] = "continent"; +constexpr char kRegionNameKey[] = "name"; +constexpr char kRegionNamePrettyKey[] = "name-pretty"; +constexpr char kCreateSupportTicket[] = "api/v1.2/partners/support-ticket"; + } // namespace brave_vpn #endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_CONSTANTS_H_ diff --git a/components/brave_vpn/brave_vpn_os_connection_api_mac.mm b/components/brave_vpn/brave_vpn_os_connection_api_mac.mm index d15828d2cc30..743e921092e3 100644 --- a/components/brave_vpn/brave_vpn_os_connection_api_mac.mm +++ b/components/brave_vpn/brave_vpn_os_connection_api_mac.mm @@ -172,14 +172,20 @@ OSStatus StorePassword(const NSString* password) { const BraveVPNConnectionInfo& info) { info_ = info; - if (StorePassword(base::SysUTF8ToNSString(info_.password())) != errSecSuccess) + if (StorePassword(base::SysUTF8ToNSString(info_.password())) != + errSecSuccess) { + for (Observer& obs : observers_) + obs.OnCreateFailed(); return; + } NEVPNManager* vpn_manager = [NEVPNManager sharedManager]; [vpn_manager loadFromPreferencesWithCompletionHandler:^(NSError* error) { if (error) { LOG(ERROR) << "Create - loadFromPrefs error: " << base::SysNSStringToUTF8([error localizedDescription]); + for (Observer& obs : observers_) + obs.OnCreateFailed(); return; } @@ -237,6 +243,8 @@ OSStatus StorePassword(const NSString* password) { if (error) { LOG(ERROR) << "Connect - loadFromPrefs error: " << base::SysNSStringToUTF8([error localizedDescription]); + for (Observer& obs : observers_) + obs.OnConnectFailed(); return; } @@ -244,6 +252,8 @@ OSStatus StorePassword(const NSString* password) { // Early return if already connected. if (current_status == NEVPNStatusConnected) { VLOG(2) << "Connect - Already connected"; + for (Observer& obs : observers_) + obs.OnConnected(); return; } @@ -252,6 +262,8 @@ OSStatus StorePassword(const NSString* password) { if (start_error != nil) { LOG(ERROR) << "Connect - startVPNTunnel error: " << base::SysNSStringToUTF8([start_error localizedDescription]); + for (Observer& obs : observers_) + obs.OnConnectFailed(); return; } }]; diff --git a/components/brave_vpn/brave_vpn_service.cc b/components/brave_vpn/brave_vpn_service.cc index 935b69c031ad..ca76eb7a557e 100644 --- a/components/brave_vpn/brave_vpn_service.cc +++ b/components/brave_vpn/brave_vpn_service.cc @@ -5,13 +5,40 @@ #include "brave/components/brave_vpn/brave_vpn_service.h" +#include #include #include "base/json/json_reader.h" #include "base/json/json_writer.h" +#include "base/strings/utf_string_conversions.h" +#include "brave/components/skus/browser/skus_utils.h" +#include "net/cookies/cookie_inclusion_status.h" +#include "net/cookies/parsed_cookie.h" #include "third_party/abseil-cpp/absl/types/optional.h" +#include "url/url_util.h" + +#if !BUILDFLAG(IS_ANDROID) +#include "base/bind.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/notreached.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/values.h" +#include "brave/components/brave_vpn/brave_vpn_constants.h" +#include "brave/components/brave_vpn/brave_vpn_service_helper.h" +#include "brave/components/brave_vpn/brave_vpn_utils.h" +#include "brave/components/brave_vpn/pref_names.h" +#include "brave/components/brave_vpn/switches.h" +#include "brave/components/version_info/version_info.h" +#include "components/prefs/pref_service.h" +#include "components/prefs/scoped_user_pref_update.h" +#include "components/version_info/version_info.h" +#include "third_party/icu/source/i18n/unicode/timezone.h" +#endif // !BUILDFLAG(IS_ANDROID) namespace { + constexpr char kVpnHost[] = "connect-api.guardianapp.com"; constexpr char kAllServerRegions[] = "api/v1/servers/all-server-regions"; @@ -38,6 +65,11 @@ net::NetworkTrafficAnnotationTag GetNetworkTrafficAnnotationTag() { "Servers, hosts and credentials for Brave VPN" destination: WEBSITE } + policy { + cookies_allowed: NO + policy_exception_justification: + "Not implemented." + } )"); } @@ -58,8 +90,7 @@ std::string GetSubscriberCredentialFromJson(const std::string& json) { base::JSONParserOptions::JSON_PARSE_RFC); absl::optional& records_v = value_with_error.value; if (!records_v) { - VLOG(1) << __func__ - << "Invalid response, could not parse JSON, JSON is: " << json; + VLOG(1) << __func__ << "Invalid response, could not parse JSON."; return ""; } @@ -76,14 +107,924 @@ std::string GetSubscriberCredentialFromJson(const std::string& json) { } // namespace +namespace brave_vpn { + +using ConnectionState = mojom::ConnectionState; +using PurchasedState = mojom::PurchasedState; + +#if BUILDFLAG(IS_ANDROID) BraveVpnService::BraveVpnService( - scoped_refptr url_loader_factory) - : api_request_helper_(GetNetworkTrafficAnnotationTag(), url_loader_factory), + scoped_refptr url_loader_factory, + base::RepeatingCallback()> + skus_service_getter) + : skus_service_getter_(skus_service_getter), + api_request_helper_(GetNetworkTrafficAnnotationTag(), url_loader_factory), weak_ptr_factory_(this) {} +#else +BraveVpnService::BraveVpnService( + scoped_refptr url_loader_factory, + PrefService* prefs, + base::RepeatingCallback()> + skus_service_getter) + : prefs_(prefs), + skus_service_getter_(skus_service_getter), + api_request_helper_(GetNetworkTrafficAnnotationTag(), + url_loader_factory) { + DCHECK(IsBraveVPNEnabled()); + + auto* cmd = base::CommandLine::ForCurrentProcess(); + is_simulation_ = cmd->HasSwitch(switches::kBraveVPNSimulation); + observed_.Observe(GetBraveVPNConnectionAPI()); + + GetBraveVPNConnectionAPI()->set_target_vpn_entry_name(kBraveVPNEntryName); +} +#endif + +BraveVpnService::~BraveVpnService() { +#if !BUILDFLAG(IS_ANDROID) + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); +#endif // !BUILDFLAG(IS_ANDROID) +} + +#if !BUILDFLAG(IS_ANDROID) +void BraveVpnService::ScheduleBackgroundRegionDataFetch() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (region_data_update_timer_.IsRunning()) + return; + + // Try to update region list every 5h. + constexpr int kRegionDataUpdateIntervalInHours = 5; + region_data_update_timer_.Start( + FROM_HERE, base::Hours(kRegionDataUpdateIntervalInHours), + base::BindRepeating(&BraveVpnService::FetchRegionData, + base::Unretained(this), true)); +} + +void BraveVpnService::OnCreated() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + if (cancel_connecting_) { + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + cancel_connecting_ = false; + return; + } + + for (const auto& obs : observers_) + obs->OnConnectionCreated(); + + // It's time to ask connecting to os after vpn entry is created. + GetBraveVPNConnectionAPI()->Connect(GetConnectionInfo().connection_name()); +} + +void BraveVpnService::OnCreateFailed() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + // Clear connecting cancel request. + if (cancel_connecting_) + cancel_connecting_ = false; + + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_NOT_ALLOWED); +} + +void BraveVpnService::OnRemoved() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + for (const auto& obs : observers_) + obs->OnConnectionRemoved(); +} + +void BraveVpnService::UpdateAndNotifyConnectionStateChange( + ConnectionState state, + bool force) { + // this is a simple state machine for handling connection state + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (connection_state_ == state) + return; + + // On Windows, we get disconnected status update twice. + // When user connects to different region while connected, + // we disconnect current connection and connect to newly selected + // region. To do that we monitor |DISCONNECTED| state and start + // connect when we get that state. But, Windows sends disconnected state + // noti again. So, ignore second one. + // On exception - we allow from connecting to disconnected in canceling + // scenario. + if (connection_state_ == ConnectionState::CONNECTING && + state == ConnectionState::DISCONNECTED && !cancel_connecting_) { + VLOG(2) << __func__ << ": Ignore disconnected state while connecting"; + return; + } + + // On Windows, we could get disconnected state after connect failed. + // To make connect failed state as a last state, ignore disconnected state. + if (!force && connection_state_ == ConnectionState::CONNECT_FAILED && + state == ConnectionState::DISCONNECTED) { + VLOG(2) << __func__ << ": Ignore disconnected state after connect failed"; + return; + } + + VLOG(2) << __func__ << " : changing from " << connection_state_ << " to " + << state; + + connection_state_ = state; + for (const auto& obs : observers_) + obs->OnConnectionStateChanged(connection_state_); +} + +void BraveVpnService::OnConnected() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + if (cancel_connecting_) { + // As connect is done, we don't need more for cancelling. + // Just start normal Disconenct() process. + cancel_connecting_ = false; + GetBraveVPNConnectionAPI()->Disconnect(kBraveVPNEntryName); + return; + } + + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTED); +} + +void BraveVpnService::OnIsConnecting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + if (!cancel_connecting_) + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTING); +} + +void BraveVpnService::OnConnectFailed() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + cancel_connecting_ = false; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); +} + +void BraveVpnService::OnDisconnected() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + + if (needs_connect_) { + needs_connect_ = false; + Connect(); + } +} + +void BraveVpnService::OnIsDisconnecting() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); +} + +void BraveVpnService::CreateVPNConnection() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (cancel_connecting_) { + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + cancel_connecting_ = false; + return; + } + + VLOG(2) << __func__; + GetBraveVPNConnectionAPI()->CreateVPNConnection(GetConnectionInfo()); +} + +void BraveVpnService::RemoveVPNConnnection() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + GetBraveVPNConnectionAPI()->RemoveVPNConnection(kBraveVPNEntryName); +} + +void BraveVpnService::Connect() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (connection_state_ == ConnectionState::DISCONNECTING || + connection_state_ == ConnectionState::CONNECTING) { + VLOG(2) << __func__ << ": Current state: " << connection_state_ + << " : prevent connecting while previous operation is in-progress"; + return; + } + + DCHECK(!cancel_connecting_); + + // User can ask connect again when user want to change region. + if (connection_state_ == ConnectionState::CONNECTED) { + // Disconnect first and then create again to setup for new region. + needs_connect_ = true; + Disconnect(); + return; + } + + VLOG(2) << __func__ << " : start connecting!"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTING); + + if (is_simulation_ || connection_info_.IsValid()) { + VLOG(2) << __func__ + << " : direct connect as we already have valid connection info."; + GetBraveVPNConnectionAPI()->Connect(GetConnectionInfo().connection_name()); + return; + } + + // If user doesn't select region explicitely, use default device region. + std::string target_region_name = GetSelectedRegion(); + if (target_region_name.empty()) { + target_region_name = GetDeviceRegion(); + VLOG(2) << __func__ << " : start connecting with valid default_region: " + << target_region_name; + } + DCHECK(!target_region_name.empty()); + FetchHostnamesForRegion(target_region_name); +} + +void BraveVpnService::Disconnect() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (connection_state_ == ConnectionState::DISCONNECTED) { + VLOG(2) << __func__ << " : already disconnected"; + return; + } + + if (connection_state_ == ConnectionState::DISCONNECTING) { + VLOG(2) << __func__ << " : disconnecting in progress"; + return; + } + + if (is_simulation_ || connection_state_ != ConnectionState::CONNECTING) { + VLOG(2) << __func__ << " : start disconnecting!"; + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); + GetBraveVPNConnectionAPI()->Disconnect(kBraveVPNEntryName); + return; + } + + cancel_connecting_ = true; + VLOG(2) << __func__ << " : Start cancelling connect request"; + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); +} + +void BraveVpnService::ToggleConnection() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + const bool can_disconnect = + (connection_state_ == ConnectionState::CONNECTED || + connection_state_ == ConnectionState::CONNECTING); + can_disconnect ? Disconnect() : Connect(); +} + +const BraveVPNConnectionInfo& BraveVpnService::GetConnectionInfo() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return connection_info_; +} + +void BraveVpnService::BindInterface( + mojo::PendingReceiver receiver) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + receivers_.Add(this, std::move(receiver)); +} + +void BraveVpnService::GetConnectionState(GetConnectionStateCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__ << " : " << connection_state_; + std::move(callback).Run(connection_state_); +} + +void BraveVpnService::ResetConnectionState() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Reset is allowed only when it has failed state. + if (connection_state_ != ConnectionState::CONNECT_NOT_ALLOWED && + connection_state_ != ConnectionState::CONNECT_FAILED) + return; + + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED, true); +} -BraveVpnService::~BraveVpnService() = default; +void BraveVpnService::FetchRegionData(bool background_fetch) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Only do background fetching for purchased user. + if (background_fetch && !is_purchased_user()) + return; + + VLOG(2) << __func__ + << (background_fetch ? " : Start fetching region data in background" + : " : Start fetching region data"); + + // Unretained is safe here becasue this class owns request helper. + GetAllServerRegions(base::BindOnce(&BraveVpnService::OnFetchRegionList, + base::Unretained(this), background_fetch)); +} + +void BraveVpnService::LoadCachedRegionData() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK_EQ(PurchasedState::LOADING, purchased_state_); + + // Already loaded from cache. + if (!regions_.empty()) + return; + + // Empty device region means it's initial state. + if (GetDeviceRegion().empty()) + return; + + auto* preference = prefs_->FindPreference(prefs::kBraveVPNRegionList); + DCHECK(preference); + // Early return when we don't have any cached region data. + if (preference->IsDefaultValue()) + return; + + // If cached one is outdated, don't use it. + if (!ValidateCachedRegionData(*preference->GetValue())) { + VLOG(2) << __func__ << " : Cached data is outdate. Will get fetch latest."; + return; + } + + if (ParseAndCacheRegionList(*preference->GetValue())) { + VLOG(2) << __func__ << " : Loaded cached region list"; + return; + } + + VLOG(2) << __func__ << " : Failed to load cached region list"; +} + +void BraveVpnService::SetRegionListToPrefs() { + DCHECK(!regions_.empty()); + + base::Value regions_list(base::Value::Type::LIST); + for (const auto& region : regions_) { + regions_list.Append(GetValueFromRegion(region)); + } + prefs_->Set(prefs::kBraveVPNRegionList, std::move(regions_list)); +} -void BraveVpnService::Shutdown() {} +void BraveVpnService::OnFetchRegionList(bool background_fetch, + const std::string& region_list, + bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Don't update purchased state during the background fetching. + + if (!background_fetch && !success) { + VLOG(2) << "Failed to get region list"; + SetPurchasedState(PurchasedState::FAILED); + return; + } + + absl::optional value = base::JSONReader::Read(region_list); + if (value && value->is_list()) { + if (background_fetch) { + ParseAndCacheRegionList(*value, true); + return; + } + + if (ParseAndCacheRegionList(*value, true)) { + VLOG(2) << "Got valid region list"; + // Set default device region and it'll be updated when received valid + // timezone info. + SetFallbackDeviceRegion(); + // Fetch timezones list to determine default region of this device. + GetTimezonesForRegions(base::BindOnce(&BraveVpnService::OnFetchTimezones, + base::Unretained(this))); + return; + } + } + + // Don't update purchased state during the background fetching. + if (!background_fetch) { + VLOG(2) << "Got invalid region list"; + SetPurchasedState(PurchasedState::FAILED); + } +} + +bool BraveVpnService::ParseAndCacheRegionList(const base::Value& region_value, + bool save_to_prefs) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(region_value.is_list()); + if (!region_value.is_list()) + return false; + + regions_ = ParseRegionList(region_value); + VLOG(2) << __func__ << " : has regionlist: " << !regions_.empty(); + + // If we can't get region list, we can't determine device region. + if (regions_.empty()) + return false; + + if (save_to_prefs) + SetRegionListToPrefs(); + return true; +} + +void BraveVpnService::OnFetchTimezones(const std::string& timezones_list, + bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + absl::optional value = base::JSONReader::Read(timezones_list); + if (success && value && value->is_list()) { + VLOG(2) << "Got valid timezones list"; + SetDeviceRegionWithTimezone(*value); + } else { + VLOG(2) << "Failed to get invalid timezones list"; + } + + // Can set as purchased state now regardless of timezone fetching result. + // We use default one picked from region list as a device region on failure. + SetPurchasedState(PurchasedState::PURCHASED); +} + +void BraveVpnService::SetDeviceRegionWithTimezone( + const base::Value& timezones_value) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(timezones_value.is_list()); + + const std::string current_time_zone = GetCurrentTimeZone(); + if (current_time_zone.empty()) + return; + + for (const auto& timezones : timezones_value.GetList()) { + DCHECK(timezones.is_dict()); + if (!timezones.is_dict()) + continue; + + const std::string* region_name = timezones.FindStringKey("name"); + if (!region_name) + continue; + const base::Value* timezone_list_value = timezones.FindKey("timezones"); + if (!timezone_list_value || !timezone_list_value->is_list()) + continue; + + for (const auto& timezone : timezone_list_value->GetList()) { + DCHECK(timezone.is_string()); + if (!timezone.is_string()) + continue; + if (current_time_zone == timezone.GetString()) { + VLOG(2) << "Found default region: " << *region_name; + SetDeviceRegion(*region_name); + return; + } + } + } +} + +void BraveVpnService::SetDeviceRegion(const std::string& name) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + prefs_->SetString(prefs::kBraveVPNDeviceRegion, name); +} + +void BraveVpnService::SetSelectedRegion(const std::string& name) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + prefs_->SetString(prefs::kBraveVPNSelectedRegion, name); +} + +std::string BraveVpnService::GetDeviceRegion() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return prefs_->GetString(prefs::kBraveVPNDeviceRegion); +} + +std::string BraveVpnService::GetSelectedRegion() const { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + return prefs_->GetString(prefs::kBraveVPNSelectedRegion); +} + +void BraveVpnService::SetFallbackDeviceRegion() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Set first item in the region list as a |device_region_| as a fallback. + DCHECK(!regions_.empty()); + SetDeviceRegion(regions_[0].name); +} + +std::string BraveVpnService::GetCurrentTimeZone() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (!test_timezone_.empty()) + return test_timezone_; + + std::unique_ptr zone(icu::TimeZone::createDefault()); + icu::UnicodeString id; + zone->getID(id); + std::string current_time_zone; + id.toUTF8String(current_time_zone); + return current_time_zone; +} + +void BraveVpnService::GetAllRegions(GetAllRegionsCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::vector regions; + for (const auto& region : regions_) { + regions.push_back(region.Clone()); + } + std::move(callback).Run(std::move(regions)); +} + +void BraveVpnService::GetDeviceRegion(GetDeviceRegionCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + auto region_name = GetDeviceRegion(); + DCHECK(!region_name.empty()); + std::move(callback).Run( + GetRegionPtrWithNameFromRegionList(region_name, regions_)); +} + +void BraveVpnService::GetSelectedRegion(GetSelectedRegionCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + + auto region_name = GetSelectedRegion(); + if (region_name.empty()) { + // Gives device region if there is no cached selected region. + VLOG(2) << __func__ << " : give device region instead."; + region_name = GetDeviceRegion(); + } + DCHECK(!region_name.empty()); + std::move(callback).Run( + GetRegionPtrWithNameFromRegionList(region_name, regions_)); +} + +void BraveVpnService::SetSelectedRegion(mojom::RegionPtr region_ptr) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (connection_state_ == ConnectionState::DISCONNECTING || + connection_state_ == ConnectionState::CONNECTING) { + VLOG(2) << __func__ << ": Current state: " << connection_state_ + << " : prevent changing selected region while previous operation " + "is in-progress"; + return; + } + + VLOG(2) << __func__ << " : " << region_ptr->name_pretty; + SetSelectedRegion(region_ptr->name); + + // As new selected region is used, |connection_info_| for previous selected + // should be cleared. + connection_info_.Reset(); +} + +void BraveVpnService::GetProductUrls(GetProductUrlsCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + std::move(callback).Run( + mojom::ProductUrls::New(kFeedbackUrl, kAboutUrl, GetManageUrl())); +} + +void BraveVpnService::CreateSupportTicket( + const std::string& email, + const std::string& subject, + const std::string& body, + CreateSupportTicketCallback callback) { + auto internal_callback = + base::BindOnce(&BraveVpnService::OnCreateSupportTicket, + weak_ptr_factory_.GetWeakPtr(), std::move(callback)); + + OAuthRequest( + GetURLWithPath(kVpnHost, kCreateSupportTicket), "POST", + CreateJSONRequestBody(GetValueWithTicketInfos(email, subject, body)), + std::move(internal_callback)); +} + +void BraveVpnService::GetSupportData(GetSupportDataCallback callback) { + std::string brave_version = + version_info::GetBraveVersionWithoutChromiumMajorVersion(); + std::string os_version = version_info::GetOSType(); + std::string vpn_hostname = hostname_ ? hostname_->display_name : ""; + std::move(callback).Run(brave_version, os_version, vpn_hostname); +} + +void BraveVpnService::FetchHostnamesForRegion(const std::string& name) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + // Hostname will be replaced with latest one. + hostname_.reset(); + + // Unretained is safe here becasue this class owns request helper. + GetHostnamesForRegion(base::BindOnce(&BraveVpnService::OnFetchHostnames, + base::Unretained(this), name), + name); +} + +void BraveVpnService::OnFetchHostnames(const std::string& region, + const std::string& hostnames, + bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__; + if (cancel_connecting_) { + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + cancel_connecting_ = false; + return; + } + + if (!success) { + VLOG(2) << __func__ << " : failed to fetch hostnames for " << region; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + absl::optional value = base::JSONReader::Read(hostnames); + if (value && value->is_list()) { + ParseAndCacheHostnames(region, *value); + return; + } + + VLOG(2) << __func__ << " : failed to fetch hostnames for " << region; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); +} + +void BraveVpnService::ParseAndCacheHostnames( + const std::string& region, + const base::Value& hostnames_value) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(hostnames_value.is_list()); + if (!hostnames_value.is_list()) { + VLOG(2) << __func__ << " : failed to parse hostnames for " << region; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + std::vector hostnames = ParseHostnames(hostnames_value); + + if (hostnames.empty()) { + VLOG(2) << __func__ << " : got empty hostnames list for " << region; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + hostname_ = PickBestHostname(hostnames); + if (hostname_->hostname.empty()) { + VLOG(2) << __func__ << " : got empty hostnames list for " << region; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + VLOG(2) << __func__ << " : Picked " << hostname_->hostname << ", " + << hostname_->display_name << ", " << hostname_->is_offline << ", " + << hostname_->capacity_score; + + if (skus_credential_.empty()) { + VLOG(2) << __func__ << " : skus_credential is empty"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + // Get subscriber credentials and then get EAP credentials with it to create + // OS VPN entry. + VLOG(2) << __func__ << " : request subscriber credential"; + GetSubscriberCredentialV12( + base::BindOnce(&BraveVpnService::OnGetSubscriberCredentialV12, + base::Unretained(this)), + GetBraveVPNPaymentsEnv(), skus_credential_); +} + +void BraveVpnService::OnGetSubscriberCredentialV12( + const std::string& subscriber_credential, + bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (cancel_connecting_) { + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + cancel_connecting_ = false; + return; + } + + if (!success) { + VLOG(2) << __func__ << " : failed to get subscriber credential"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + VLOG(2) << __func__ << " : received subscriber credential"; + // TODO(bsclifton): consider storing `subscriber_credential` for + // support ticket use-case (see `CreateSupportTicket`). + GetProfileCredentials( + base::BindOnce(&BraveVpnService::OnGetProfileCredentials, + base::Unretained(this)), + subscriber_credential, hostname_->hostname); +} + +void BraveVpnService::OnGetProfileCredentials( + const std::string& profile_credential, + bool success) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (cancel_connecting_) { + UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); + cancel_connecting_ = false; + return; + } + + if (!success) { + VLOG(2) << __func__ << " : failed to get profile credential"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + VLOG(2) << __func__ << " : received profile credential"; + + absl::optional value = + base::JSONReader::Read(profile_credential); + if (value && value->is_dict()) { + constexpr char kUsernameKey[] = "eap-username"; + constexpr char kPasswordKey[] = "eap-password"; + const std::string* username = value->FindStringKey(kUsernameKey); + const std::string* password = value->FindStringKey(kPasswordKey); + if (!username || !password) { + VLOG(2) << __func__ << " : it's invalid profile credential"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); + return; + } + + connection_info_.SetConnectionInfo(kBraveVPNEntryName, hostname_->hostname, + *username, *password); + // Let's create os vpn entry with |connection_info_|. + CreateVPNConnection(); + return; + } + + VLOG(2) << __func__ << " : it's invalid profile credential"; + UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); +} + +BraveVPNOSConnectionAPI* BraveVpnService::GetBraveVPNConnectionAPI() { + if (is_simulation_) + return BraveVPNOSConnectionAPI::GetInstanceForTest(); + return BraveVPNOSConnectionAPI::GetInstance(); +} + +void BraveVpnService::OnCreateSupportTicket( + CreateSupportTicketCallback callback, + int status, + const std::string& body, + const base::flat_map& headers) { + bool success = status == 200; + VLOG(2) << "OnCreateSupportTicket success=" << success + << "\nresponse_code=" << status; + std::move(callback).Run(success, body); +} +#endif // !BUILDFLAG(IS_ANDROID) + +void BraveVpnService::AddObserver( + mojo::PendingRemote observer) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + observers_.Add(std::move(observer)); +} + +void BraveVpnService::GetPurchasedState(GetPurchasedStateCallback callback) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + VLOG(2) << __func__ << " : " << static_cast(purchased_state_); + std::move(callback).Run(purchased_state_); +} + +void BraveVpnService::LoadPurchasedState() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (purchased_state_ == PurchasedState::LOADING) + return; + + SetPurchasedState(PurchasedState::LOADING); + +#if !BUILDFLAG(IS_ANDROID) && !defined(OFFICIAL_BUILD) + auto* cmd = base::CommandLine::ForCurrentProcess(); + if (cmd->HasSwitch(switches::kBraveVPNTestMonthlyPass)) { + skus_credential_ = + cmd->GetSwitchValueASCII(switches::kBraveVPNTestMonthlyPass); + LoadCachedRegionData(); + if (!regions_.empty()) { + SetPurchasedState(PurchasedState::PURCHASED); + } else { + FetchRegionData(false); + } + + GetBraveVPNConnectionAPI()->CheckConnection(kBraveVPNEntryName); + return; + } +#endif // !BUILDFLAG(IS_ANDROID) && !defined(OFFICIAL_BUILD) + + EnsureMojoConnected(); + skus_service_->CredentialSummary( + skus::GetDomain("vpn"), + base::BindOnce(&BraveVpnService::OnCredentialSummary, + base::Unretained(this))); +} + +void BraveVpnService::OnCredentialSummary(const std::string& summary_string) { + std::string summary_string_trimmed; + base::TrimWhitespaceASCII(summary_string, base::TrimPositions::TRIM_ALL, + &summary_string_trimmed); + if (summary_string_trimmed.length() == 0) { + // no credential found; person needs to login + VLOG(1) << __func__ << " : No credential found; user needs to login!"; + SetPurchasedState(PurchasedState::NOT_PURCHASED); + return; + } + + base::JSONReader::ValueWithError value_with_error = + base::JSONReader::ReadAndReturnValueWithError( + summary_string, base::JSONParserOptions::JSON_PARSE_RFC); + absl::optional& records_v = value_with_error.value; + + if (records_v && records_v->is_dict()) { + const base::Value* active = records_v->FindKey("active"); + bool has_credential = active && active->is_bool() && active->GetBool(); + if (has_credential) { + VLOG(1) << __func__ << " : Active credential found!"; + // if a credential is ready, we can present it + EnsureMojoConnected(); + skus_service_->PrepareCredentialsPresentation( + skus::GetDomain("vpn"), "*", + base::BindOnce(&BraveVpnService::OnPrepareCredentialsPresentation, + base::Unretained(this))); + } else { + VLOG(1) << __func__ << " : Credential appears to be expired."; + SetPurchasedState(PurchasedState::EXPIRED); + } + } else { + VLOG(1) << __func__ << " : Got invalid credential summary!"; + SetPurchasedState(PurchasedState::FAILED); + } +} + +void BraveVpnService::OnPrepareCredentialsPresentation( + const std::string& credential_as_cookie) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + // Credential is returned in cookie format. + net::CookieInclusionStatus status; + net::ParsedCookie credential_cookie(credential_as_cookie, &status); + // TODO(bsclifton): have a better check / logging. + // should these failed states be considered NOT_PURCHASED? + // or maybe it can be considered FAILED status? + if (!credential_cookie.IsValid()) { + VLOG(1) << __func__ << " : FAILED credential_cookie.IsValid"; + SetPurchasedState(PurchasedState::FAILED); + return; + } + if (!status.IsInclude()) { + VLOG(1) << __func__ << " : FAILED status.IsInclude"; + SetPurchasedState(PurchasedState::FAILED); + return; + } + + // Credential value received needs to be URL decoded. + // That leaves us with a Base64 encoded JSON blob which is the credential. + std::string encoded_credential = credential_cookie.Value(); + url::RawCanonOutputT unescaped; + url::DecodeURLEscapeSequences( + encoded_credential.data(), encoded_credential.size(), + url::DecodeURLMode::kUTF8OrIsomorphic, &unescaped); + std::string credential; + base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &credential); + + skus_credential_ = credential; + + if (skus_credential_.empty()) { + VLOG(2) << __func__ << " : Got empty skus credentials"; + SetPurchasedState(PurchasedState::NOT_PURCHASED); + return; + } + +#if BUILDFLAG(IS_ANDROID) + SetPurchasedState(PurchasedState::PURCHASED); +#else + LoadCachedRegionData(); + + // Only fetch when we don't have cache. + if (!regions_.empty()) { + SetPurchasedState(PurchasedState::PURCHASED); + } else { + FetchRegionData(false); + } + + ScheduleBackgroundRegionDataFetch(); + GetBraveVPNConnectionAPI()->CheckConnection(kBraveVPNEntryName); +#endif +} + +void BraveVpnService::SetPurchasedState(PurchasedState state) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + if (purchased_state_ == state) + return; + + purchased_state_ = state; + VLOG(1) << "SetPurchasedState: " << purchased_state_; + + for (const auto& obs : observers_) + obs->OnPurchasedStateChanged(purchased_state_); +} + +void BraveVpnService::EnsureMojoConnected() { + if (!skus_service_) { + auto pending = skus_service_getter_.Run(); + skus_service_.Bind(std::move(pending)); + } + DCHECK(skus_service_); + skus_service_.set_disconnect_handler(base::BindOnce( + &BraveVpnService::OnMojoConnectionError, base::Unretained(this))); +} + +void BraveVpnService::OnMojoConnectionError() { + skus_service_.reset(); + EnsureMojoConnected(); +} + +void BraveVpnService::Shutdown() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + skus_service_.reset(); + observers_.Clear(); + +#if !BUILDFLAG(IS_ANDROID) + observed_.Reset(); + receivers_.Clear(); +#endif // !BUILDFLAG(IS_ANDROID) +} void BraveVpnService::OAuthRequest( const GURL& url, @@ -163,8 +1104,38 @@ void BraveVpnService::OnGetResponse( std::string json_response; bool success = status == 200; if (success) { + // Give sanitized json response on success. json_response = body; + data_decoder::JsonSanitizer::Sanitize( + json_response, + base::BindOnce(&BraveVpnService::OnGetSanitizedJsonResponse, + weak_ptr_factory_.GetWeakPtr(), std::move(callback))); + } else { + // Give empty response on failure. + std::move(callback).Run(json_response, success); } +} + +void BraveVpnService::OnGetSanitizedJsonResponse( + ResponseCallback callback, + data_decoder::JsonSanitizer::Result sanitized_json_response) { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + + std::string json_response; + bool success = true; + if (sanitized_json_response.error) { + VLOG(1) << "Response validation error: " << *sanitized_json_response.error; + success = false; + } + + if (success && !sanitized_json_response.value.has_value()) { + VLOG(1) << "Empty response"; + success = false; + } + + if (success) + json_response = sanitized_json_response.value.value(); + std::move(callback).Run(json_response, success); } @@ -222,3 +1193,5 @@ void BraveVpnService::GetSubscriberCredentialV12( OAuthRequest(base_url, "POST", request_body, std::move(internal_callback), {{"Brave-Payments-Environment", payments_environment}}); } + +} // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_service.h b/components/brave_vpn/brave_vpn_service.h index 05c6fbce4203..e7977267dec5 100644 --- a/components/brave_vpn/brave_vpn_service.h +++ b/components/brave_vpn/brave_vpn_service.h @@ -10,31 +10,118 @@ #include "base/bind.h" #include "base/callback_forward.h" +#include "base/memory/raw_ptr.h" #include "base/memory/scoped_refptr.h" +#include "base/sequence_checker.h" #include "brave/components/api_request_helper/api_request_helper.h" +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" +#include "brave/components/skus/common/skus_sdk.mojom.h" +#include "build/build_config.h" #include "components/keyed_service/core/keyed_service.h" +#include "mojo/public/cpp/bindings/pending_remote.h" +#include "mojo/public/cpp/bindings/remote_set.h" +#include "services/data_decoder/public/cpp/json_sanitizer.h" #include "services/network/public/cpp/shared_url_loader_factory.h" #include "url/gurl.h" +#if !BUILDFLAG(IS_ANDROID) +#include +#include + +#include "base/scoped_observation.h" +#include "base/timer/timer.h" +#include "brave/components/brave_vpn/brave_vpn_connection_info.h" +#include "brave/components/brave_vpn/brave_vpn_data_types.h" +#include "brave/components/brave_vpn/brave_vpn_os_connection_api.h" +#include "mojo/public/cpp/bindings/receiver_set.h" +#endif // !BUILDFLAG(IS_ANDROID) + namespace network { class SharedURLLoaderFactory; } // namespace network -class BraveVpnService : public KeyedService { +#if !BUILDFLAG(IS_ANDROID) +namespace base { +class Value; +} // namespace base + +class PrefService; +class BraveAppMenuBrowserTest; +class BraveBrowserCommandControllerTest; + +#endif // !BUILDFLAG(IS_ANDROID) + +namespace brave_vpn { + +// This class is used by desktop and android. +// However, it includes desktop specific impls and it's hidden +// by IS_ANDROID ifdef. +class BraveVpnService : +#if !BUILDFLAG(IS_ANDROID) + public BraveVPNOSConnectionAPI::Observer, +#endif + public mojom::ServiceHandler, + public KeyedService { public: - explicit BraveVpnService( - scoped_refptr url_loader_factory); +#if BUILDFLAG(IS_ANDROID) + BraveVpnService( + scoped_refptr url_loader_factory, + base::RepeatingCallback()> + skus_service_getter); +#else + BraveVpnService( + scoped_refptr url_loader_factory, + PrefService* prefs, + base::RepeatingCallback()> + skus_service_getter); +#endif ~BraveVpnService() override; BraveVpnService(const BraveVpnService&) = delete; BraveVpnService& operator=(const BraveVpnService&) = delete; - // KeyedService overrides: - void Shutdown() override; +#if !BUILDFLAG(IS_ANDROID) + void ToggleConnection(); + void RemoveVPNConnnection(); + + bool is_connected() const { + return connection_state_ == mojom::ConnectionState::CONNECTED; + } + bool is_purchased_user() const { + return purchased_state_ == mojom::PurchasedState::PURCHASED; + } + mojom::ConnectionState connection_state() const { return connection_state_; } + + void BindInterface(mojo::PendingReceiver receiver); + + // mojom::vpn::ServiceHandler + void GetConnectionState(GetConnectionStateCallback callback) override; + void ResetConnectionState() override; + void Connect() override; + void Disconnect() override; + void CreateVPNConnection() override; + void GetAllRegions(GetAllRegionsCallback callback) override; + void GetDeviceRegion(GetDeviceRegionCallback callback) override; + void GetSelectedRegion(GetSelectedRegionCallback callback) override; + void SetSelectedRegion(mojom::RegionPtr region) override; + void GetProductUrls(GetProductUrlsCallback callback) override; + void CreateSupportTicket(const std::string& email, + const std::string& subject, + const std::string& body, + CreateSupportTicketCallback callback) override; + void GetSupportData(GetSupportDataCallback callback) override; +#endif // !BUILDFLAG(IS_ANDROID) + + void LoadPurchasedState(); using ResponseCallback = base::OnceCallback; + // mojom::vpn::ServiceHandler + void AddObserver( + mojo::PendingRemote observer) override; + void GetPurchasedState(GetPurchasedStateCallback callback) override; + void GetAllServerRegions(ResponseCallback callback); void GetTimezonesForRegions(ResponseCallback callback); void GetHostnamesForRegion(ResponseCallback callback, @@ -58,11 +145,72 @@ class BraveVpnService : public KeyedService { const std::string& monthly_pass); private: +#if !BUILDFLAG(IS_ANDROID) + friend class ::BraveAppMenuBrowserTest; + friend class ::BraveBrowserCommandControllerTest; + friend class BraveVPNServiceTest; + + // BraveVPNOSConnectionAPI::Observer overrides: + void OnCreated() override; + void OnCreateFailed() override; + void OnRemoved() override; + void OnConnected() override; + void OnIsConnecting() override; + void OnConnectFailed() override; + void OnDisconnected() override; + void OnIsDisconnecting() override; + + const BraveVPNConnectionInfo& GetConnectionInfo(); + void LoadCachedRegionData(); + void UpdateAndNotifyConnectionStateChange(mojom::ConnectionState state, + bool force = false); + + void FetchRegionData(bool background_fetch); + void OnFetchRegionList(bool background_fetch, + const std::string& region_list, + bool success); + bool ParseAndCacheRegionList(const base::Value& region_value, + bool save_to_prefs = false); + void OnFetchTimezones(const std::string& timezones_list, bool success); + void SetDeviceRegionWithTimezone(const base::Value& timezons_value); + void FetchHostnamesForRegion(const std::string& name); + void OnFetchHostnames(const std::string& region, + const std::string& hostnames, + bool success); + void ParseAndCacheHostnames(const std::string& region, + const base::Value& hostnames_value); + void SetDeviceRegion(const std::string& name); + void SetSelectedRegion(const std::string& name); + std::string GetDeviceRegion() const; + std::string GetSelectedRegion() const; + void SetFallbackDeviceRegion(); + void SetRegionListToPrefs(); + + std::string GetCurrentTimeZone(); + void ScheduleBackgroundRegionDataFetch(); + void ScheduleFetchRegionDataIfNeeded(); + + void OnGetSubscriberCredentialV12(const std::string& subscriber_credential, + bool success); + void OnGetProfileCredentials(const std::string& profile_credential, + bool success); + void OnCreateSupportTicket( + CreateSupportTicketCallback callback, + int status, + const std::string& body, + const base::flat_map& headers); + + BraveVPNOSConnectionAPI* GetBraveVPNConnectionAPI(); +#endif // !BUILDFLAG(IS_ANDROID) + using URLRequestCallback = base::OnceCallback&)>; + // KeyedService overrides: + void Shutdown() override; + void OAuthRequest( const GURL& url, const std::string& method, @@ -74,6 +222,9 @@ class BraveVpnService : public KeyedService { int status, const std::string& body, const base::flat_map& headers); + void OnGetSanitizedJsonResponse( + ResponseCallback callback, + data_decoder::JsonSanitizer::Result sanitized_json_response); void OnGetSubscriberCredential( ResponseCallback callback, @@ -81,8 +232,45 @@ class BraveVpnService : public KeyedService { const std::string& body, const base::flat_map& headers); + void SetPurchasedState(mojom::PurchasedState state); + void EnsureMojoConnected(); + void OnMojoConnectionError(); + void OnCredentialSummary(const std::string& summary_string); + void OnPrepareCredentialsPresentation( + const std::string& credential_as_cookie); + +#if !BUILDFLAG(IS_ANDROID) + raw_ptr prefs_ = nullptr; + std::vector regions_; + std::unique_ptr hostname_; + BraveVPNConnectionInfo connection_info_; + bool cancel_connecting_ = false; + mojom::ConnectionState connection_state_ = + mojom::ConnectionState::DISCONNECTED; + bool needs_connect_ = false; + base::ScopedObservation + observed_{this}; + mojo::ReceiverSet receivers_; + base::RepeatingTimer region_data_update_timer_; + + // Only for testing. + std::string test_timezone_; + bool is_simulation_ = false; +#endif // !BUILDFLAG(IS_ANDROID) + + SEQUENCE_CHECKER(sequence_checker_); + + base::RepeatingCallback()> + skus_service_getter_; + mojo::Remote skus_service_; + mojom::PurchasedState purchased_state_ = mojom::PurchasedState::NOT_PURCHASED; + mojo::RemoteSet observers_; api_request_helper::APIRequestHelper api_request_helper_; - base::WeakPtrFactory weak_ptr_factory_; + std::string skus_credential_; + base::WeakPtrFactory weak_ptr_factory_{this}; }; +} // namespace brave_vpn + #endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_H_ diff --git a/components/brave_vpn/brave_vpn_service_desktop.cc b/components/brave_vpn/brave_vpn_service_desktop.cc deleted file mode 100644 index 6c2e7e28f51d..000000000000 --- a/components/brave_vpn/brave_vpn_service_desktop.cc +++ /dev/null @@ -1,992 +0,0 @@ -/* Copyright (c) 2021 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 http://mozilla.org/MPL/2.0/. */ - -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" - -#include -#include - -#include "base/base64.h" -#include "base/bind.h" -#include "base/command_line.h" -#include "base/json/json_reader.h" -#include "base/logging.h" -#include "base/notreached.h" -#include "base/strings/string_split.h" -#include "base/strings/utf_string_conversions.h" -#include "base/values.h" -#include "brave/components/brave_vpn/brave_vpn_constants.h" -#include "brave/components/brave_vpn/brave_vpn_utils.h" -#include "brave/components/brave_vpn/pref_names.h" -#include "brave/components/brave_vpn/switches.h" -#include "brave/components/skus/browser/pref_names.h" -#include "brave/components/skus/browser/skus_utils.h" -#include "components/prefs/pref_service.h" -#include "components/prefs/scoped_user_pref_update.h" -#include "net/cookies/cookie_inclusion_status.h" -#include "net/cookies/parsed_cookie.h" -#include "third_party/icu/source/i18n/unicode/timezone.h" -#include "url/url_util.h" - -namespace { - -constexpr char kBraveVPNEntryName[] = "BraveVPN"; - -constexpr char kRegionContinentKey[] = "continent"; -constexpr char kRegionNameKey[] = "name"; -constexpr char kRegionNamePrettyKey[] = "name-pretty"; - -std::string GetStringFor(ConnectionState state) { - switch (state) { - case ConnectionState::CONNECTED: - return "Connected"; - case ConnectionState::CONNECTING: - return "Connecting"; - case ConnectionState::DISCONNECTED: - return "Disconnected"; - case ConnectionState::DISCONNECTING: - return "Disconnecting"; - case ConnectionState::CONNECT_FAILED: - return "Connect failed"; - default: - NOTREACHED(); - } - - return std::string(); -} - -bool IsValidRegion(const brave_vpn::mojom::Region& region) { - if (region.continent.empty() || region.name.empty() || - region.name_pretty.empty()) - return false; - - return true; -} - -// On desktop, the environment is tied to SKUs because you would purchase it -// from `account.brave.com` (or similar, based on env). The credentials for VPN -// will always be in the same environment as the SKU environment. -// -// When the vendor receives a credential from us during auth, it also includes -// the environment. The vendor then can do a lookup using Payment Service. -std::string GetBraveVPNPaymentsEnv() { - const std::string env = skus::GetEnvironment(); - if (env == skus::kEnvProduction) - return ""; - // Use same value. - if (env == skus::kEnvStaging || env == skus::kEnvDevelopment) - return env; - - NOTREACHED(); - -#if defined(OFFICIAL_BUILD) - return ""; -#else - return "development"; -#endif -} - -} // namespace - -BraveVpnServiceDesktop::BraveVpnServiceDesktop( - scoped_refptr url_loader_factory, - PrefService* prefs, - base::RepeatingCallback()> - skus_service_getter) - : BraveVpnService(url_loader_factory), - prefs_(prefs), - skus_service_getter_(skus_service_getter) { - DCHECK(brave_vpn::IsBraveVPNEnabled()); - - auto* cmd = base::CommandLine::ForCurrentProcess(); - is_simulation_ = cmd->HasSwitch(brave_vpn::switches::kBraveVPNSimulation); - observed_.Observe(GetBraveVPNConnectionAPI()); - - GetBraveVPNConnectionAPI()->set_target_vpn_entry_name(kBraveVPNEntryName); - - // Load cached data. - LoadCachedRegionData(); - LoadPurchasedState(); - LoadSelectedRegion(); - - // If already in purchased state, there is a high probability that - // the user has previously connected. So, try connection checking. - if (is_purchased_user()) - GetBraveVPNConnectionAPI()->CheckConnection(kBraveVPNEntryName); - - pref_change_registrar_.Init(prefs_); - pref_change_registrar_.Add( - skus::prefs::kSkusVPNHasCredential, - base::BindRepeating(&BraveVpnServiceDesktop::OnSkusVPNCredentialUpdated, - base::Unretained(this))); -} - -BraveVpnServiceDesktop::~BraveVpnServiceDesktop() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -} - -void BraveVpnServiceDesktop::ScheduleFetchRegionDataIfNeeded() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - if (!is_purchased_user()) - return; - - if (region_data_update_timer_.IsRunning()) - return; - - // Try to update region list every 5h. - FetchRegionData(); - constexpr int kRegionDataUpdateIntervalInHours = 5; - region_data_update_timer_.Start( - FROM_HERE, base::Hours(kRegionDataUpdateIntervalInHours), this, - &BraveVpnServiceDesktop::FetchRegionData); -} - -void BraveVpnServiceDesktop::Shutdown() { - BraveVpnService::Shutdown(); - - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - - skus_service_.reset(); - observed_.Reset(); - receivers_.Clear(); - observers_.Clear(); - pref_change_registrar_.RemoveAll(); -} - -void BraveVpnServiceDesktop::OnCreated() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - for (const auto& obs : observers_) - obs->OnConnectionCreated(); - - // It's time to ask connecting to os after vpn entry is created. - GetBraveVPNConnectionAPI()->Connect(GetConnectionInfo().connection_name()); -} - -void BraveVpnServiceDesktop::OnCreateFailed() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); -} - -void BraveVpnServiceDesktop::OnRemoved() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - for (const auto& obs : observers_) - obs->OnConnectionRemoved(); -} - -void BraveVpnServiceDesktop::UpdateAndNotifyConnectionStateChange( - ConnectionState state) { - // this is a simple state machine for handling connection state - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (connection_state_ == state) - return; - - // On Windows, we get disconnected status update twice. - // When user connects to different region while connected, - // we disconnect current connection and connect to newly selected - // region. To do that we monitor |DISCONNECTED| state and start - // connect when we get that state. But, Windows sends disconnected state - // noti again. So, ignore second one. - // On exception - we allow from connecting to disconnected in canceling - // scenario. - if (connection_state_ == ConnectionState::CONNECTING && - state == ConnectionState::DISCONNECTED && !cancel_connecting_) { - VLOG(2) << __func__ << ": Ignore disconnected state while connecting"; - return; - } - - // On Windows, we could get disconnected state after connect failed. - // To make connect failed state as a last state, ignore disconnected state. - if (connection_state_ == ConnectionState::CONNECT_FAILED && - state == ConnectionState::DISCONNECTED) { - VLOG(2) << __func__ << ": Ignore disconnected state after connect failed"; - return; - } - - VLOG(2) << __func__ << " : changing from " << GetStringFor(connection_state_) - << " to " << GetStringFor(state); - - connection_state_ = state; - for (const auto& obs : observers_) - obs->OnConnectionStateChanged(connection_state_); -} - -void BraveVpnServiceDesktop::OnConnected() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - - if (cancel_connecting_) { - // As connect is done, we don't need more for cancelling. - // Just start normal Disconenct() process. - cancel_connecting_ = false; - GetBraveVPNConnectionAPI()->Disconnect(kBraveVPNEntryName); - return; - } - - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTED); -} - -void BraveVpnServiceDesktop::OnIsConnecting() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - - if (!cancel_connecting_) - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTING); -} - -void BraveVpnServiceDesktop::OnConnectFailed() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - - cancel_connecting_ = false; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); -} - -void BraveVpnServiceDesktop::OnDisconnected() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - - if (needs_connect_) { - needs_connect_ = false; - Connect(); - } -} - -void BraveVpnServiceDesktop::OnIsDisconnecting() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); -} - -void BraveVpnServiceDesktop::CreateVPNConnection() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - VLOG(2) << __func__; - GetBraveVPNConnectionAPI()->CreateVPNConnection(GetConnectionInfo()); -} - -void BraveVpnServiceDesktop::RemoveVPNConnnection() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - GetBraveVPNConnectionAPI()->RemoveVPNConnection(kBraveVPNEntryName); -} - -void BraveVpnServiceDesktop::Connect() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (connection_state_ == ConnectionState::DISCONNECTING || - connection_state_ == ConnectionState::CONNECTING) { - VLOG(2) << __func__ - << ": Current state: " << GetStringFor(connection_state_) - << " : prevent connecting while previous operation is in-progress"; - return; - } - - DCHECK(!cancel_connecting_); - - // User can ask connect again when user want to change region. - if (connection_state_ == ConnectionState::CONNECTED) { - // Disconnect first and then create again to setup for new region. - needs_connect_ = true; - Disconnect(); - return; - } - - VLOG(2) << __func__ << " : start connecting!"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECTING); - - if (is_simulation_ || connection_info_.IsValid()) { - VLOG(2) << __func__ - << " : direct connect as we already have valid connection info."; - GetBraveVPNConnectionAPI()->Connect(GetConnectionInfo().connection_name()); - return; - } - - // If user doesn't select region explicitely, use default device region. - std::string target_region_name = device_region_.name; - if (IsValidRegion(selected_region_)) { - target_region_name = selected_region_.name; - VLOG(2) << __func__ << " : start connecting with valid selected_region: " - << target_region_name; - } - - FetchHostnamesForRegion(target_region_name); -} - -void BraveVpnServiceDesktop::Disconnect() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (connection_state_ == ConnectionState::DISCONNECTED) { - VLOG(2) << __func__ << " : already disconnected"; - return; - } - - if (connection_state_ == ConnectionState::DISCONNECTING) { - VLOG(2) << __func__ << " : disconnecting in progress"; - return; - } - - if (is_simulation_ || connection_state_ != ConnectionState::CONNECTING) { - VLOG(2) << __func__ << " : start disconnecting!"; - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); - GetBraveVPNConnectionAPI()->Disconnect(kBraveVPNEntryName); - return; - } - - cancel_connecting_ = true; - VLOG(2) << __func__ << " : Start cancelling connect request"; - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTING); -} - -void BraveVpnServiceDesktop::ToggleConnection() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - const bool can_disconnect = - (connection_state_ == ConnectionState::CONNECTED || - connection_state_ == ConnectionState::CONNECTING); - can_disconnect ? Disconnect() : Connect(); -} - -void BraveVpnServiceDesktop::AddObserver( - mojo::PendingRemote observer) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - observers_.Add(std::move(observer)); -} - -brave_vpn::BraveVPNConnectionInfo BraveVpnServiceDesktop::GetConnectionInfo() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - return connection_info_; -} - -void BraveVpnServiceDesktop::BindInterface( - mojo::PendingReceiver receiver) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - receivers_.Add(this, std::move(receiver)); -} - -void BraveVpnServiceDesktop::GetConnectionState( - GetConnectionStateCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__ << " : " << static_cast(connection_state_); - std::move(callback).Run(connection_state_); -} - -void BraveVpnServiceDesktop::GetPurchasedState( - GetPurchasedStateCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__ << " : " << static_cast(purchased_state_); - std::move(callback).Run(purchased_state_); -} - -void BraveVpnServiceDesktop::FetchRegionData() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__ << " : Start fetching region data"; - // Unretained is safe here becasue this class owns request helper. - GetAllServerRegions(base::BindOnce(&BraveVpnServiceDesktop::OnFetchRegionList, - base::Unretained(this))); -} - -void BraveVpnServiceDesktop::LoadCachedRegionData() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - auto* preference = - prefs_->FindPreference(brave_vpn::prefs::kBraveVPNRegionList); - if (preference && !preference->IsDefaultValue()) { - ParseAndCacheRegionList(preference->GetValue()->Clone()); - VLOG(2) << __func__ << " : " - << "Loaded cached region list"; - } - - preference = prefs_->FindPreference(brave_vpn::prefs::kBraveVPNDeviceRegion); - if (preference && !preference->IsDefaultValue()) { - auto* region_value = preference->GetValue(); - const std::string* continent = - region_value->FindStringKey(kRegionContinentKey); - const std::string* name = region_value->FindStringKey(kRegionNameKey); - const std::string* name_pretty = - region_value->FindStringKey(kRegionNamePrettyKey); - if (continent && name && name_pretty) { - device_region_.continent = *continent; - device_region_.name = *name; - device_region_.name_pretty = *name_pretty; - VLOG(2) << __func__ << " : " - << "Loaded cached device region"; - } - } -} - -void BraveVpnServiceDesktop::OnPrepareCredentialsPresentation( - const std::string& credential_as_cookie) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Credential is returned in cookie format. - net::CookieInclusionStatus status; - net::ParsedCookie credential_cookie(credential_as_cookie, &status); - // TODO(bsclifton): have a better check / logging. - if (!credential_cookie.IsValid()) { - VLOG(2) << __func__ << " : FAILED credential_cookie.IsValid"; - return; - } - if (!status.IsInclude()) { - VLOG(2) << __func__ << " : FAILED status.IsInclude"; - return; - } - - // Credential value received needs to be URL decoded. - // That leaves us with a Base64 encoded JSON blob which is the credential. - std::string encoded_credential = credential_cookie.Value(); - url::RawCanonOutputT unescaped; - url::DecodeURLEscapeSequences( - encoded_credential.data(), encoded_credential.size(), - url::DecodeURLMode::kUTF8OrIsomorphic, &unescaped); - std::string credential; - base::UTF16ToUTF8(unescaped.data(), unescaped.length(), &credential); - - // Only update credential if different - if (skus_credential_ == credential) - return; - - skus_credential_ = credential; - if (!skus_credential_.empty()) { - VLOG(2) << __func__ << " : " - << "Loaded cached skus credentials"; - SetPurchasedState(PurchasedState::PURCHASED); - } -} - -void BraveVpnServiceDesktop::LoadPurchasedState() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); -#if !defined(OFFICIAL_BUILD) - auto* cmd = base::CommandLine::ForCurrentProcess(); - if (cmd->HasSwitch(brave_vpn::switches::kBraveVPNTestMonthlyPass)) { - skus_credential_ = - cmd->GetSwitchValueASCII(brave_vpn::switches::kBraveVPNTestMonthlyPass); - SetPurchasedState(PurchasedState::PURCHASED); - return; - } -#endif - - const bool has_credential = - prefs_->GetBoolean(skus::prefs::kSkusVPNHasCredential); - - if (!has_credential) { - // TODO(bsclifton): we can show logic for person to login - // NOTE: we might save (to profile) if person EVER had a valid - // credential. If so, we may want to show an expired dialog - // instead of the "purchase" dialog. - skus_credential_ = ""; - return; - } - - EnsureMojoConnected(); - - // if a credential is ready, we can present it - skus_service_->PrepareCredentialsPresentation( - skus::GetDomain("vpn"), "*", - base::BindOnce(&BraveVpnServiceDesktop::OnPrepareCredentialsPresentation, - base::Unretained(this))); -} - -void BraveVpnServiceDesktop::LoadSelectedRegion() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - auto* preference = - prefs_->FindPreference(brave_vpn::prefs::kBraveVPNSelectedRegion); - if (preference && !preference->IsDefaultValue()) { - auto* region_value = preference->GetValue(); - const std::string* continent = - region_value->FindStringKey(kRegionContinentKey); - const std::string* name = region_value->FindStringKey(kRegionNameKey); - const std::string* name_pretty = - region_value->FindStringKey(kRegionNamePrettyKey); - if (continent && name && name_pretty) { - selected_region_.continent = *continent; - selected_region_.name = *name; - selected_region_.name_pretty = *name_pretty; - VLOG(2) << __func__ << " : " - << "Loaded selected region"; - } - } -} - -void BraveVpnServiceDesktop::OnFetchRegionList(const std::string& region_list, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (!success) { - // TODO(simonhong): Re-try? - VLOG(2) << "Failed to get region list"; - return; - } - - absl::optional value = base::JSONReader::Read(region_list); - if (value && value->is_list()) { - prefs_->Set(brave_vpn::prefs::kBraveVPNRegionList, *value); - - if (ParseAndCacheRegionList(std::move(*value))) { - // Fetch timezones list to determine default region of this device. - GetTimezonesForRegions(base::BindOnce( - &BraveVpnServiceDesktop::OnFetchTimezones, base::Unretained(this))); - return; - } - } - - // TODO(simonhong): Re-try? -} - -bool BraveVpnServiceDesktop::ParseAndCacheRegionList(base::Value region_value) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(region_value.is_list()); - if (!region_value.is_list()) - return false; - - regions_.clear(); - for (const auto& value : region_value.GetList()) { - DCHECK(value.is_dict()); - if (!value.is_dict()) - continue; - - brave_vpn::mojom::Region region; - if (auto* continent = value.FindStringKey(kRegionContinentKey)) - region.continent = *continent; - if (auto* name = value.FindStringKey(kRegionNameKey)) - region.name = *name; - if (auto* name_pretty = value.FindStringKey(kRegionNamePrettyKey)) - region.name_pretty = *name_pretty; - - regions_.push_back(region); - } - - // Sort region list alphabetically - std::sort(regions_.begin(), regions_.end(), - [](brave_vpn::mojom::Region& a, brave_vpn::mojom::Region& b) { - return (a.name_pretty < b.name_pretty); - }); - - VLOG(2) << __func__ << " : has regionlist: " << !regions_.empty(); - - // If we can't get region list, we can't determine device region. - if (regions_.empty()) - return false; - return true; -} - -void BraveVpnServiceDesktop::OnFetchTimezones(const std::string& timezones_list, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (!success) { - VLOG(2) << "Failed to get timezones list"; - SetFallbackDeviceRegion(); - return; - } - - absl::optional value = base::JSONReader::Read(timezones_list); - if (value && value->is_list()) { - ParseAndCacheDeviceRegionName(std::move(*value)); - return; - } - - SetFallbackDeviceRegion(); -} - -void BraveVpnServiceDesktop::ParseAndCacheDeviceRegionName( - base::Value timezones_value) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(timezones_value.is_list()); - - if (!timezones_value.is_list()) { - SetFallbackDeviceRegion(); - return; - } - - const std::string current_time_zone = GetCurrentTimeZone(); - if (current_time_zone.empty()) { - SetFallbackDeviceRegion(); - return; - } - - for (const auto& timezones : timezones_value.GetList()) { - DCHECK(timezones.is_dict()); - if (!timezones.is_dict()) - continue; - - const std::string* region_name = timezones.FindStringKey("name"); - if (!region_name) - continue; - std::string default_region_name_candidate = *region_name; - const base::Value* timezone_list_value = timezones.FindKey("timezones"); - if (!timezone_list_value || !timezone_list_value->is_list()) - continue; - - for (const auto& timezone : timezone_list_value->GetList()) { - DCHECK(timezone.is_string()); - if (!timezone.is_string()) - continue; - if (current_time_zone == timezone.GetString()) { - SetDeviceRegion(*region_name); - VLOG(2) << "Found default region: " << device_region_.name; - return; - } - } - } - - SetFallbackDeviceRegion(); -} - -void BraveVpnServiceDesktop::SetDeviceRegion(const std::string& name) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - auto it = - std::find_if(regions_.begin(), regions_.end(), - [&name](const auto& region) { return region.name == name; }); - if (it != regions_.end()) { - SetDeviceRegion(*it); - } -} - -void BraveVpnServiceDesktop::SetFallbackDeviceRegion() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - // Set first item in the region list as a |device_region_| as a fallback. - DCHECK(!regions_.empty()); - if (regions_.empty()) - return; - - SetDeviceRegion(regions_[0]); -} - -void BraveVpnServiceDesktop::SetDeviceRegion( - const brave_vpn::mojom::Region& region) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - device_region_ = region; - - DictionaryPrefUpdate update(prefs_, brave_vpn::prefs::kBraveVPNDeviceRegion); - base::Value* dict = update.Get(); - dict->SetStringKey(kRegionContinentKey, device_region_.continent); - dict->SetStringKey(kRegionNameKey, device_region_.name); - dict->SetStringKey(kRegionNamePrettyKey, device_region_.name_pretty); -} - -std::string BraveVpnServiceDesktop::GetCurrentTimeZone() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (!test_timezone_.empty()) - return test_timezone_; - - std::unique_ptr zone(icu::TimeZone::createDefault()); - icu::UnicodeString id; - zone->getID(id); - std::string current_time_zone; - id.toUTF8String(current_time_zone); - return current_time_zone; -} - -void BraveVpnServiceDesktop::GetAllRegions(GetAllRegionsCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - std::vector regions; - for (const auto& region : regions_) { - regions.push_back(region.Clone()); - } - std::move(callback).Run(std::move(regions)); -} - -void BraveVpnServiceDesktop::GetDeviceRegion(GetDeviceRegionCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - std::move(callback).Run(device_region_.Clone()); -} - -void BraveVpnServiceDesktop::GetSelectedRegion( - GetSelectedRegionCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - - if (!IsValidRegion(selected_region_)) { - // Gives device region if there is no cached selected region. - VLOG(2) << __func__ << " : give device region instead."; - std::move(callback).Run(device_region_.Clone()); - return; - } - - VLOG(2) << __func__ << " : Give " << selected_region_.name_pretty; - std::move(callback).Run(selected_region_.Clone()); -} - -void BraveVpnServiceDesktop::SetSelectedRegion( - brave_vpn::mojom::RegionPtr region_ptr) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (connection_state_ == ConnectionState::DISCONNECTING || - connection_state_ == ConnectionState::CONNECTING) { - VLOG(2) << __func__ - << ": Current state: " << GetStringFor(connection_state_) - << " : prevent changing selected region while previous operation " - "is in-progress"; - return; - } - - VLOG(2) << __func__ << " : " << region_ptr->name_pretty; - DictionaryPrefUpdate update(prefs_, - brave_vpn::prefs::kBraveVPNSelectedRegion); - base::Value* dict = update.Get(); - dict->SetStringKey(kRegionContinentKey, region_ptr->continent); - dict->SetStringKey(kRegionNameKey, region_ptr->name); - dict->SetStringKey(kRegionNamePrettyKey, region_ptr->name_pretty); - - selected_region_.continent = region_ptr->continent; - selected_region_.name = region_ptr->name; - selected_region_.name_pretty = region_ptr->name_pretty; - - connection_info_.Reset(); -} - -void BraveVpnServiceDesktop::GetProductUrls(GetProductUrlsCallback callback) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - brave_vpn::mojom::ProductUrls urls; - urls.feedback = brave_vpn::kFeedbackUrl; - urls.about = brave_vpn::kAboutUrl; - urls.manage = brave_vpn::GetManageUrl(); - std::move(callback).Run(urls.Clone()); -} - -void BraveVpnServiceDesktop::FetchHostnamesForRegion(const std::string& name) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - // Hostname will be replaced with latest one. - hostname_.reset(); - - // Unretained is safe here becasue this class owns request helper. - GetHostnamesForRegion( - base::BindOnce(&BraveVpnServiceDesktop::OnFetchHostnames, - base::Unretained(this), name), - name); -} - -void BraveVpnServiceDesktop::OnFetchHostnames(const std::string& region, - const std::string& hostnames, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - VLOG(2) << __func__; - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - if (!success) { - VLOG(2) << __func__ << " : failed to fetch hostnames for " << region; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - absl::optional value = base::JSONReader::Read(hostnames); - if (value && value->is_list()) { - ParseAndCacheHostnames(region, std::move(*value)); - return; - } - - VLOG(2) << __func__ << " : failed to fetch hostnames for " << region; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); -} - -void BraveVpnServiceDesktop::ParseAndCacheHostnames( - const std::string& region, - base::Value hostnames_value) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - DCHECK(hostnames_value.is_list()); - if (!hostnames_value.is_list()) { - VLOG(2) << __func__ << " : failed to parse hostnames for " << region; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - constexpr char kHostnameKey[] = "hostname"; - constexpr char kDisplayNameKey[] = "display-name"; - constexpr char kOfflineKey[] = "offline"; - constexpr char kCapacityScoreKey[] = "capacity-score"; - - std::vector hostnames; - for (const auto& value : hostnames_value.GetList()) { - DCHECK(value.is_dict()); - if (!value.is_dict()) - continue; - - const std::string* hostname_str = value.FindStringKey(kHostnameKey); - const std::string* display_name_str = value.FindStringKey(kDisplayNameKey); - absl::optional offline = value.FindBoolKey(kOfflineKey); - absl::optional capacity_score = value.FindIntKey(kCapacityScoreKey); - - if (!hostname_str || !display_name_str || !offline || !capacity_score) - continue; - - hostnames.push_back(brave_vpn::Hostname{*hostname_str, *display_name_str, - *offline, *capacity_score}); - } - - VLOG(2) << __func__ << " : has hostname: " << !hostnames.empty(); - - if (hostnames.empty()) { - VLOG(2) << __func__ << " : got empty hostnames list for " << region; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - hostname_ = PickBestHostname(hostnames); - if (hostname_->hostname.empty()) { - VLOG(2) << __func__ << " : got empty hostnames list for " << region; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - VLOG(2) << __func__ << " : Picked " << hostname_->hostname << ", " - << hostname_->display_name << ", " << hostname_->is_offline << ", " - << hostname_->capacity_score; - - if (skus_credential_.empty()) { - VLOG(2) << __func__ << " : skus_credential is empty"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - // Get subscriber credentials and then get EAP credentials with it to create - // OS VPN entry. - VLOG(2) << __func__ << " : request subsriber credential"; - GetSubscriberCredentialV12( - base::BindOnce(&BraveVpnServiceDesktop::OnGetSubscriberCredential, - base::Unretained(this)), - GetBraveVPNPaymentsEnv(), skus_credential_); -} - -void BraveVpnServiceDesktop::SetPurchasedState(PurchasedState state) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (purchased_state_ == state) - return; - - purchased_state_ = state; - - for (const auto& obs : observers_) - obs->OnPurchasedStateChanged(purchased_state_); - - ScheduleFetchRegionDataIfNeeded(); -} - -void BraveVpnServiceDesktop::EnsureMojoConnected() { - if (!skus_service_) { - auto pending = skus_service_getter_.Run(); - skus_service_.Bind(std::move(pending)); - } - DCHECK(skus_service_); - skus_service_.set_disconnect_handler(base::BindOnce( - &BraveVpnServiceDesktop::OnMojoConnectionError, base::Unretained(this))); -} - -void BraveVpnServiceDesktop::OnMojoConnectionError() { - skus_service_.reset(); - EnsureMojoConnected(); -} - -void BraveVpnServiceDesktop::OnSkusVPNCredentialUpdated() { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - LoadPurchasedState(); -} - -void BraveVpnServiceDesktop::OnGetSubscriberCredential( - const std::string& subscriber_credential, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - if (!success) { - VLOG(2) << __func__ << " : failed to get subscriber credential"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - VLOG(2) << __func__ << " : received subscriber credential"; - - GetProfileCredentials( - base::BindOnce(&BraveVpnServiceDesktop::OnGetProfileCredentials, - base::Unretained(this)), - subscriber_credential, hostname_->hostname); -} - -void BraveVpnServiceDesktop::OnGetProfileCredentials( - const std::string& profile_credential, - bool success) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - if (!success) { - VLOG(2) << __func__ << " : failed to get profile credential"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - VLOG(2) << __func__ << " : received profile credential"; - - absl::optional value = - base::JSONReader::Read(profile_credential); - if (value && value->is_dict()) { - constexpr char kUsernameKey[] = "eap-username"; - constexpr char kPasswordKey[] = "eap-password"; - const std::string* username = value->FindStringKey(kUsernameKey); - const std::string* password = value->FindStringKey(kPasswordKey); - if (!username || !password) { - VLOG(2) << __func__ << " : it's invalid profile credential"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - connection_info_.SetConnectionInfo(kBraveVPNEntryName, hostname_->hostname, - *username, *password); - // Let's create os vpn entry with |connection_info_|. - CreateVPNConnection(); - return; - } - - VLOG(2) << __func__ << " : it's invalid profile credential"; - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); -} - -std::unique_ptr BraveVpnServiceDesktop::PickBestHostname( - const std::vector& hostnames) { - DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); - std::vector filtered_hostnames; - std::copy_if( - hostnames.begin(), hostnames.end(), - std::back_inserter(filtered_hostnames), - [](const brave_vpn::Hostname& hostname) { return !hostname.is_offline; }); - - std::sort(filtered_hostnames.begin(), filtered_hostnames.end(), - [](const brave_vpn::Hostname& a, const brave_vpn::Hostname& b) { - return a.capacity_score > b.capacity_score; - }); - - if (filtered_hostnames.empty()) - return std::make_unique(); - - // Pick highest capacity score. - return std::make_unique(filtered_hostnames[0]); -} - -brave_vpn::BraveVPNOSConnectionAPI* -BraveVpnServiceDesktop::GetBraveVPNConnectionAPI() { - if (is_simulation_) - return brave_vpn::BraveVPNOSConnectionAPI::GetInstanceForTest(); - return brave_vpn::BraveVPNOSConnectionAPI::GetInstance(); -} diff --git a/components/brave_vpn/brave_vpn_service_desktop.h b/components/brave_vpn/brave_vpn_service_desktop.h deleted file mode 100644 index 4565a6255473..000000000000 --- a/components/brave_vpn/brave_vpn_service_desktop.h +++ /dev/null @@ -1,175 +0,0 @@ -/* Copyright (c) 2021 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 http://mozilla.org/MPL/2.0/. */ - -#ifndef BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_DESKTOP_H_ -#define BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_DESKTOP_H_ - -#include -#include -#include - -#include "base/scoped_observation.h" -#include "base/sequence_checker.h" -#include "base/timer/timer.h" -#include "brave/components/brave_vpn/brave_vpn.mojom.h" -#include "brave/components/brave_vpn/brave_vpn_connection_info.h" -#include "brave/components/brave_vpn/brave_vpn_data_types.h" -#include "brave/components/brave_vpn/brave_vpn_os_connection_api.h" -#include "brave/components/brave_vpn/brave_vpn_service.h" -#include "brave/components/skus/common/skus_sdk.mojom.h" -#include "components/prefs/pref_change_registrar.h" -#include "mojo/public/cpp/bindings/pending_remote.h" -#include "mojo/public/cpp/bindings/receiver_set.h" -#include "mojo/public/cpp/bindings/remote_set.h" - -namespace base { -class Value; -} // namespace base - -class PrefService; - -using ConnectionState = brave_vpn::mojom::ConnectionState; -using PurchasedState = brave_vpn::mojom::PurchasedState; - -class BraveVpnServiceDesktop - : public BraveVpnService, - public brave_vpn::BraveVPNOSConnectionAPI::Observer, - public brave_vpn::mojom::ServiceHandler { - public: - BraveVpnServiceDesktop( - scoped_refptr url_loader_factory, - PrefService* prefs, - base::RepeatingCallback()> - skus_service_getter); - ~BraveVpnServiceDesktop() override; - - BraveVpnServiceDesktop(const BraveVpnServiceDesktop&) = delete; - BraveVpnServiceDesktop& operator=(const BraveVpnServiceDesktop&) = delete; - - void ToggleConnection(); - void RemoveVPNConnnection(); - - bool is_connected() const { - return connection_state_ == ConnectionState::CONNECTED; - } - bool is_purchased_user() const { - return purchased_state_ == PurchasedState::PURCHASED; - } - ConnectionState connection_state() const { return connection_state_; } - - void BindInterface( - mojo::PendingReceiver receiver); - - // mojom::vpn::ServiceHandler - void AddObserver( - mojo::PendingRemote observer) override; - void GetConnectionState(GetConnectionStateCallback callback) override; - void GetPurchasedState(GetPurchasedStateCallback callback) override; - void Connect() override; - void Disconnect() override; - void CreateVPNConnection() override; - void GetAllRegions(GetAllRegionsCallback callback) override; - void GetDeviceRegion(GetDeviceRegionCallback callback) override; - void GetSelectedRegion(GetSelectedRegionCallback callback) override; - void SetSelectedRegion(brave_vpn::mojom::RegionPtr region) override; - void GetProductUrls(GetProductUrlsCallback callback) override; - - private: - friend class BraveAppMenuBrowserTest; - friend class BraveBrowserCommandControllerTest; - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, RegionDataTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, HostnamesTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, CancelConnectingTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, ConnectionInfoTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, LoadPurchasedStateTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, LoadRegionDataFromPrefsTest); - FRIEND_TEST_ALL_PREFIXES(BraveVPNServiceTest, NeedsConnectTest); - - // BraveVpnService overrides: - void Shutdown() override; - - // brave_vpn::BraveVPNOSConnectionAPI::Observer overrides: - void OnCreated() override; - void OnCreateFailed() override; - void OnRemoved() override; - void OnConnected() override; - void OnIsConnecting() override; - void OnConnectFailed() override; - void OnDisconnected() override; - void OnIsDisconnecting() override; - - brave_vpn::BraveVPNConnectionInfo GetConnectionInfo(); - void LoadCachedRegionData(); - void LoadPurchasedState(); - void LoadSelectedRegion(); - void UpdateAndNotifyConnectionStateChange(ConnectionState state); - - void FetchRegionData(); - void OnFetchRegionList(const std::string& region_list, bool success); - bool ParseAndCacheRegionList(base::Value region_value); - void OnFetchTimezones(const std::string& timezones_list, bool success); - void ParseAndCacheDeviceRegionName(base::Value timezons_value); - void FetchHostnamesForRegion(const std::string& name); - void OnFetchHostnames(const std::string& region, - const std::string& hostnames, - bool success); - void ParseAndCacheHostnames(const std::string& region, - base::Value hostnames_value); - void SetDeviceRegion(const std::string& name); - void SetFallbackDeviceRegion(); - void SetDeviceRegion(const brave_vpn::mojom::Region& region); - - std::string GetCurrentTimeZone(); - void SetPurchasedState(PurchasedState state); - void ScheduleFetchRegionDataIfNeeded(); - std::unique_ptr PickBestHostname( - const std::vector& hostnames); - - void EnsureMojoConnected(); - void OnMojoConnectionError(); - void OnSkusVPNCredentialUpdated(); - void OnGetSubscriberCredential(const std::string& subscriber_credential, - bool success); - void OnGetProfileCredentials(const std::string& profile_credential, - bool success); - void OnPrepareCredentialsPresentation( - const std::string& credential_as_cookie); - - brave_vpn::BraveVPNOSConnectionAPI* GetBraveVPNConnectionAPI(); - - void set_test_timezone(const std::string& timezone) { - test_timezone_ = timezone; - } - - PrefService* prefs_ = nullptr; - base::RepeatingCallback()> - skus_service_getter_; - PrefChangeRegistrar pref_change_registrar_; - std::string skus_credential_; - mojo::Remote skus_service_; - std::vector regions_; - brave_vpn::mojom::Region device_region_; - brave_vpn::mojom::Region selected_region_; - std::unique_ptr hostname_; - brave_vpn::BraveVPNConnectionInfo connection_info_; - bool cancel_connecting_ = false; - PurchasedState purchased_state_ = PurchasedState::NOT_PURCHASED; - ConnectionState connection_state_ = ConnectionState::DISCONNECTED; - bool needs_connect_ = false; - base::ScopedObservation - observed_{this}; - mojo::ReceiverSet receivers_; - mojo::RemoteSet observers_; - base::RepeatingTimer region_data_update_timer_; - - // Only for testing. - std::string test_timezone_; - bool is_simulation_ = false; - - SEQUENCE_CHECKER(sequence_checker_); -}; - -#endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_DESKTOP_H_ diff --git a/components/brave_vpn/brave_vpn_service_helper.cc b/components/brave_vpn/brave_vpn_service_helper.cc new file mode 100644 index 000000000000..2f0c5bd44497 --- /dev/null +++ b/components/brave_vpn/brave_vpn_service_helper.cc @@ -0,0 +1,184 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#include "brave/components/brave_vpn/brave_vpn_service_helper.h" + +#include + +#include "base/base64.h" +#include "base/notreached.h" +#include "base/values.h" +#include "brave/components/brave_vpn/brave_vpn_constants.h" +#include "brave/components/brave_vpn/brave_vpn_data_types.h" +#include "brave/components/skus/browser/skus_utils.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +namespace brave_vpn { + +namespace { + +bool IsValidRegionValue(const base::Value& value) { + if (!value.FindStringKey(kRegionContinentKey) || + !value.FindStringKey(kRegionNameKey) || + !value.FindStringKey(kRegionNamePrettyKey)) { + return false; + } + + return true; +} + +mojom::Region GetRegionFromValue(const base::Value& value) { + mojom::Region region; + if (auto* continent = value.FindStringKey(kRegionContinentKey)) + region.continent = *continent; + if (auto* name = value.FindStringKey(kRegionNameKey)) + region.name = *name; + if (auto* name_pretty = value.FindStringKey(kRegionNamePrettyKey)) + region.name_pretty = *name_pretty; + return region; +} + +} // namespace + +// On desktop, the environment is tied to SKUs because you would purchase it +// from `account.brave.com` (or similar, based on env). The credentials for VPN +// will always be in the same environment as the SKU environment. +// +// When the vendor receives a credential from us during auth, it also includes +// the environment. The vendor then can do a lookup using Payment Service. +std::string GetBraveVPNPaymentsEnv() { + const std::string env = skus::GetEnvironment(); + if (env == skus::kEnvProduction) + return ""; + // Use same value. + if (env == skus::kEnvStaging || env == skus::kEnvDevelopment) + return env; + + NOTREACHED(); + +#if defined(OFFICIAL_BUILD) + return ""; +#else + return "development"; +#endif +} + +bool ValidateCachedRegionData(const base::Value& region_value) { + for (const auto& value : region_value.GetList()) { + // Make sure cached one has all latest properties. + if (!IsValidRegionValue(value)) + return false; + } + + return true; +} + +base::Value GetValueFromRegion(const mojom::Region& region) { + base::Value region_dict(base::Value::Type::DICTIONARY); + region_dict.SetStringKey(kRegionContinentKey, region.continent); + region_dict.SetStringKey(kRegionNameKey, region.name); + region_dict.SetStringKey(kRegionNamePrettyKey, region.name_pretty); + return region_dict; +} + +std::unique_ptr PickBestHostname( + const std::vector& hostnames) { + std::vector filtered_hostnames; + std::copy_if(hostnames.begin(), hostnames.end(), + std::back_inserter(filtered_hostnames), + [](const Hostname& hostname) { return !hostname.is_offline; }); + + std::sort(filtered_hostnames.begin(), filtered_hostnames.end(), + [](const Hostname& a, const Hostname& b) { + return a.capacity_score > b.capacity_score; + }); + + if (filtered_hostnames.empty()) + return std::make_unique(); + + // Pick highest capacity score. + return std::make_unique(filtered_hostnames[0]); +} + +std::vector ParseHostnames(const base::Value& hostnames_value) { + std::vector hostnames; + for (const auto& value : hostnames_value.GetList()) { + DCHECK(value.is_dict()); + if (!value.is_dict()) + continue; + + constexpr char kHostnameKey[] = "hostname"; + constexpr char kDisplayNameKey[] = "display-name"; + constexpr char kOfflineKey[] = "offline"; + constexpr char kCapacityScoreKey[] = "capacity-score"; + const std::string* hostname_str = value.FindStringKey(kHostnameKey); + const std::string* display_name_str = value.FindStringKey(kDisplayNameKey); + absl::optional offline = value.FindBoolKey(kOfflineKey); + absl::optional capacity_score = value.FindIntKey(kCapacityScoreKey); + + if (!hostname_str || !display_name_str || !offline || !capacity_score) + continue; + + hostnames.push_back( + Hostname{*hostname_str, *display_name_str, *offline, *capacity_score}); + } + + return hostnames; +} + +std::vector ParseRegionList(const base::Value& region_list) { + std::vector regions; + for (const auto& value : region_list.GetList()) { + DCHECK(value.is_dict()); + if (!value.is_dict()) + continue; + regions.push_back(GetRegionFromValue(value)); + } + + // Sort region list alphabetically + std::sort(regions.begin(), regions.end(), + [](mojom::Region& a, mojom::Region& b) { + return (a.name_pretty < b.name_pretty); + }); + return regions; +} + +base::Value GetValueWithTicketInfos(const std::string& email, + const std::string& subject, + const std::string& body) { + base::Value dict(base::Value::Type::DICTIONARY); + + std::string email_trimmed, subject_trimmed, body_trimmed, body_encoded; + base::TrimWhitespaceASCII(email, base::TRIM_ALL, &email_trimmed); + base::TrimWhitespaceASCII(subject, base::TRIM_ALL, &subject_trimmed); + base::TrimWhitespaceASCII(body, base::TRIM_ALL, &body_trimmed); + base::Base64Encode(body_trimmed, &body_encoded); + + // required fields + dict.SetStringKey("email", email_trimmed); + dict.SetStringKey("subject", subject_trimmed); + dict.SetStringKey("support-ticket", body_encoded); + dict.SetStringKey("partner-client-id", "com.brave.browser"); + + // optional (but encouraged) fields + dict.SetStringKey("subscriber-credential", ""); + dict.SetStringKey("payment-validation-method", "brave-premium"); + dict.SetStringKey("payment-validation-data", ""); + + return dict; +} + +mojom::RegionPtr GetRegionPtrWithNameFromRegionList( + const std::string& name, + const std::vector region_list) { + auto it = + std::find_if(region_list.begin(), region_list.end(), + [&name](const auto& region) { return region.name == name; }); + if (it != region_list.end()) + return it->Clone(); + return mojom::RegionPtr(); +} + +} // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_service_helper.h b/components/brave_vpn/brave_vpn_service_helper.h new file mode 100644 index 000000000000..38764c5164ac --- /dev/null +++ b/components/brave_vpn/brave_vpn_service_helper.h @@ -0,0 +1,40 @@ +/* Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. */ + +#ifndef BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_HELPER_H_ +#define BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_HELPER_H_ + +#include +#include +#include + +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" + +namespace base { +class Value; +} // namespace base + +namespace brave_vpn { + +struct Hostname; + +bool ValidateCachedRegionData(const base::Value& region_value); +std::string GetBraveVPNPaymentsEnv(); + +base::Value GetValueFromRegion(const mojom::Region& region); +std::unique_ptr PickBestHostname( + const std::vector& hostnames); +std::vector ParseHostnames(const base::Value& hostnames); +std::vector ParseRegionList(const base::Value& region_list); +base::Value GetValueWithTicketInfos(const std::string& email, + const std::string& subject, + const std::string& body); +mojom::RegionPtr GetRegionPtrWithNameFromRegionList( + const std::string& name, + const std::vector region_list); + +} // namespace brave_vpn + +#endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_HELPER_H_ diff --git a/components/brave_vpn/brave_vpn_service_observer.cc b/components/brave_vpn/brave_vpn_service_observer.cc index 372158bd84b3..0c30ee6692a3 100644 --- a/components/brave_vpn/brave_vpn_service_observer.cc +++ b/components/brave_vpn/brave_vpn_service_observer.cc @@ -7,20 +7,24 @@ #include -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" #include "brave/components/brave_vpn/brave_vpn_utils.h" +namespace brave_vpn { + BraveVPNServiceObserver::BraveVPNServiceObserver() = default; BraveVPNServiceObserver::~BraveVPNServiceObserver() = default; -void BraveVPNServiceObserver::Observe(BraveVpnServiceDesktop* service) { +void BraveVPNServiceObserver::Observe(BraveVpnService* service) { if (!service) return; - if (brave_vpn::IsBraveVPNEnabled()) { - mojo::PendingRemote listener; + if (IsBraveVPNEnabled()) { + mojo::PendingRemote listener; receiver_.Bind(listener.InitWithNewPipeAndPassReceiver()); service->AddObserver(std::move(listener)); } } + +} // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_service_observer.h b/components/brave_vpn/brave_vpn_service_observer.h index 85f9e7107c9a..de4f7d31aa12 100644 --- a/components/brave_vpn/brave_vpn_service_observer.h +++ b/components/brave_vpn/brave_vpn_service_observer.h @@ -6,30 +6,35 @@ #ifndef BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_OBSERVER_H_ #define BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_OBSERVER_H_ -#include "brave/components/brave_vpn/brave_vpn.mojom.h" +#include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" +#include "build/build_config.h" #include "mojo/public/cpp/bindings/receiver.h" -class BraveVpnServiceDesktop; +namespace brave_vpn { -class BraveVPNServiceObserver : public brave_vpn::mojom::ServiceObserver { +class BraveVpnService; + +class BraveVPNServiceObserver : public mojom::ServiceObserver { public: BraveVPNServiceObserver(); ~BraveVPNServiceObserver() override; BraveVPNServiceObserver(const BraveVPNServiceObserver&) = delete; BraveVPNServiceObserver& operator=(const BraveVPNServiceObserver&) = delete; - void Observe(BraveVpnServiceDesktop* service); + void Observe(BraveVpnService* service); - // brave_vpn::mojom::ServiceObserver overrides: - void OnConnectionStateChanged( - brave_vpn::mojom::ConnectionState state) override {} - void OnPurchasedStateChanged( - brave_vpn::mojom::PurchasedState state) override {} + // mojom::ServiceObserver overrides: + void OnPurchasedStateChanged(mojom::PurchasedState state) override {} +#if !BUILDFLAG(IS_ANDROID) + void OnConnectionStateChanged(mojom::ConnectionState state) override {} void OnConnectionCreated() override {} void OnConnectionRemoved() override {} +#endif // !BUILDFLAG(IS_ANDROID) private: - mojo::Receiver receiver_{this}; + mojo::Receiver receiver_{this}; }; +} // namespace brave_vpn + #endif // BRAVE_COMPONENTS_BRAVE_VPN_BRAVE_VPN_SERVICE_OBSERVER_H_ diff --git a/components/brave_vpn/brave_vpn_unittest.cc b/components/brave_vpn/brave_vpn_unittest.cc index 1de5af8966ec..33eb8b7c9c46 100644 --- a/components/brave_vpn/brave_vpn_unittest.cc +++ b/components/brave_vpn/brave_vpn_unittest.cc @@ -8,7 +8,8 @@ #include "base/feature_list.h" #include "base/memory/scoped_refptr.h" #include "base/test/scoped_feature_list.h" -#include "brave/components/brave_vpn/brave_vpn_service_desktop.h" +#include "brave/components/brave_vpn/brave_vpn_service.h" +#include "brave/components/brave_vpn/brave_vpn_service_helper.h" #include "brave/components/brave_vpn/brave_vpn_utils.h" #include "brave/components/brave_vpn/features.h" #include "brave/components/brave_vpn/pref_names.h" @@ -26,37 +27,126 @@ #include "services/network/test/test_shared_url_loader_factory.h" #include "testing/gtest/include/gtest/gtest.h" -namespace { +namespace brave_vpn { -std::unique_ptr skus_service_; -mojo::PendingRemote GetSkusService() { - if (!skus_service_) { - return mojo::PendingRemote(); - } - return static_cast(skus_service_.get())->MakeRemote(); -} - -} // namespace +using ConnectionState = mojom::ConnectionState; +using PurchasedState = mojom::PurchasedState; class BraveVPNServiceTest : public testing::Test { public: BraveVPNServiceTest() { scoped_feature_list_.InitWithFeatures( - {skus::features::kSkusFeature, brave_vpn::features::kBraveVPN}, {}); + {skus::features::kSkusFeature, features::kBraveVPN}, {}); } void SetUp() override { skus::RegisterProfilePrefs(pref_service_.registry()); - brave_vpn::prefs::RegisterProfilePrefs(pref_service_.registry()); + prefs::RegisterProfilePrefs(pref_service_.registry()); // Setup required for SKU (dependency of VPN) auto url_loader_factory = base::MakeRefCounted(); skus_service_ = std::make_unique(&pref_service_, url_loader_factory); - auto callback = base::BindRepeating(GetSkusService); - service_ = std::make_unique( - url_loader_factory, &pref_service_, callback); + service_ = std::make_unique( + url_loader_factory, &pref_service_, + base::BindRepeating(&BraveVPNServiceTest::GetSkusService, + base::Unretained(this))); + } + + mojo::PendingRemote GetSkusService() { + if (!skus_service_) { + return mojo::PendingRemote(); + } + return static_cast(skus_service_.get()) + ->MakeRemote(); + } + + void OnFetchRegionList(bool background_fetch, + const std::string& region_list, + bool success) { + service_->OnFetchRegionList(background_fetch, region_list, success); + } + + void OnFetchTimezones(const std::string& timezones_list, bool success) { + service_->OnFetchTimezones(timezones_list, success); + } + + void OnFetchHostnames(const std::string& region, + const std::string& hostnames, + bool success) { + service_->OnFetchHostnames(region, hostnames, success); + } + + void OnCredentialSummary(const std::string& summary) { + service_->OnCredentialSummary(summary); + } + + std::vector& regions() const { return service_->regions_; } + + mojom::Region device_region() const { + if (auto region_ptr = GetRegionPtrWithNameFromRegionList( + service_->GetDeviceRegion(), regions())) { + return *region_ptr; + } + return mojom::Region(); + } + + std::unique_ptr& hostname() { return service_->hostname_; } + + bool& cancel_connecting() { return service_->cancel_connecting_; } + + ConnectionState& connection_state() { return service_->connection_state_; } + + PurchasedState& purchased_state() { return service_->purchased_state_; } + + std::string& skus_credential() { return service_->skus_credential_; } + + bool& is_simulation() { return service_->is_simulation_; } + + bool& needs_connect() { return service_->needs_connect_; } + + void Connect() { service_->Connect(); } + + void Disconnect() { service_->Disconnect(); } + + void CreateVPNConnection() { service_->CreateVPNConnection(); } + + void LoadCachedRegionData() { service_->LoadCachedRegionData(); } + + void OnCreated() { service_->OnCreated(); } + + void OnGetSubscriberCredentialV12(const std::string& subscriber_credential, + bool success) { + service_->OnGetSubscriberCredentialV12(subscriber_credential, success); + } + + void OnGetProfileCredentials(const std::string& profile_credential, + bool success) { + service_->OnGetProfileCredentials(profile_credential, success); + } + + void OnPrepareCredentialsPresentation( + const std::string& credential_as_cookie) { + service_->OnPrepareCredentialsPresentation(credential_as_cookie); + } + + void OnConnected() { service_->OnConnected(); } + + void OnDisconnected() { service_->OnDisconnected(); } + + const BraveVPNConnectionInfo& GetConnectionInfo() { + return service_->GetConnectionInfo(); + } + + void SetDeviceRegion(const std::string& name) { + service_->SetDeviceRegion(name); + } + + void SetFallbackDeviceRegion() { service_->SetFallbackDeviceRegion(); } + + void SetTestTimezone(const std::string& timezone) { + service_->test_timezone_ = timezone; } std::string GetRegionsData() { @@ -219,179 +309,227 @@ class BraveVPNServiceTest : public testing::Test { base::test::ScopedFeatureList scoped_feature_list_; content::BrowserTaskEnvironment task_environment_; sync_preferences::TestingPrefServiceSyncable pref_service_; - std::unique_ptr service_; + std::unique_ptr skus_service_; + std::unique_ptr service_; }; -// TODO(bsclifton): re-enable test after figuring out why crash is happening -TEST(BraveVPNFeatureTest, DISABLED_FeatureTest) { - EXPECT_FALSE(brave_vpn::IsBraveVPNEnabled()); +TEST(BraveVPNFeatureTest, FeatureTest) { + EXPECT_FALSE(IsBraveVPNEnabled()); } -// TODO(bsclifton): re-enable test after figuring out why crash is happening -TEST_F(BraveVPNServiceTest, DISABLED_RegionDataTest) { +TEST_F(BraveVPNServiceTest, RegionDataTest) { // Test invalid region data. - service_->OnFetchRegionList(std::string(), true); - EXPECT_TRUE(service_->regions_.empty()); + OnFetchRegionList(false, std::string(), true); + EXPECT_TRUE(regions().empty()); // Test valid region data parsing. - service_->OnFetchRegionList(GetRegionsData(), true); + OnFetchRegionList(false, GetRegionsData(), true); const size_t kRegionCount = 11; - EXPECT_EQ(kRegionCount, service_->regions_.size()); + EXPECT_EQ(kRegionCount, regions().size()); // First region in region list is set as a device region when fetch is failed. - service_->OnFetchTimezones(std::string(), false); - EXPECT_EQ(service_->regions_[0], service_->device_region_); + OnFetchTimezones(std::string(), false); + EXPECT_EQ(regions()[0], device_region()); - // Test proper device region is set when valid timezone is used. + // Test fallback region is replaced with proper device region + // when valid timezone is used. // "asia-sg" region is used for "Asia/Seoul" tz. - service_->device_region_ = brave_vpn::mojom::Region(); - service_->set_test_timezone("Asia/Seoul"); - service_->OnFetchTimezones(GetTimeZonesData(), true); - EXPECT_EQ("asia-sg", service_->device_region_.name); - - // Test first region is set as a device region when invalid timezone is set. - service_->device_region_ = brave_vpn::mojom::Region(); - service_->set_test_timezone("Invalid"); - service_->OnFetchTimezones(GetTimeZonesData(), true); - EXPECT_EQ(service_->regions_[0], service_->device_region_); + SetFallbackDeviceRegion(); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ("asia-sg", device_region().name); + + // Test device region is not changed when invalid timezone is set. + SetFallbackDeviceRegion(); + SetTestTimezone("Invalid"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ(regions()[0], device_region()); + + // Test device region is not changed when invalid timezone is set. + SetFallbackDeviceRegion(); + SetTestTimezone("Invalid"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ(regions()[0], device_region()); } -TEST_F(BraveVPNServiceTest, DISABLED_HostnamesTest) { +TEST_F(BraveVPNServiceTest, HostnamesTest) { // Set valid hostnames list - service_->hostname_.reset(); - service_->OnFetchHostnames("region-a", GetHostnamesData(), true); + hostname().reset(); + OnFetchHostnames("region-a", GetHostnamesData(), true); // Check best one is picked from fetched hostname list. - EXPECT_EQ("host-2.brave.com", service_->hostname_->hostname); + EXPECT_EQ("host-2.brave.com", hostname()->hostname); // Can't get hostname from invalid hostnames list - service_->hostname_.reset(); - service_->OnFetchHostnames("invalid-region-b", "", false); - EXPECT_FALSE(service_->hostname_); + hostname().reset(); + OnFetchHostnames("invalid-region-b", "", false); + EXPECT_FALSE(hostname()); } -// TODO(bsclifton): fix after flow is decided -TEST_F(BraveVPNServiceTest, DISABLED_LoadPurchasedStateTest) { - EXPECT_EQ(PurchasedState::NOT_PURCHASED, service_->purchased_state_); - pref_service_.SetBoolean(skus::prefs::kSkusVPNHasCredential, true); - EXPECT_EQ(PurchasedState::PURCHASED, service_->purchased_state_); +TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { + // Service try loading + purchased_state() = PurchasedState::LOADING; + // Treat not purchased When empty credential string received. + OnCredentialSummary(""); + EXPECT_EQ(PurchasedState::NOT_PURCHASED, purchased_state()); + + // Treat expired when credential with non active received. + purchased_state() = PurchasedState::LOADING; + OnCredentialSummary(R"({ "active": false } )"); + EXPECT_EQ(PurchasedState::EXPIRED, purchased_state()); + + // Treat failed when invalid string received. + purchased_state() = PurchasedState::LOADING; + OnCredentialSummary(R"( "invalid" )"); + EXPECT_EQ(PurchasedState::FAILED, purchased_state()); + + // Reached to purchased state when valid credential, region data + // and timezone info. + purchased_state() = PurchasedState::LOADING; + OnCredentialSummary(R"({ "active": true } )"); + EXPECT_TRUE(regions().empty()); + EXPECT_EQ(PurchasedState::LOADING, purchased_state()); + OnPrepareCredentialsPresentation("credential=abcdefghijk"); + EXPECT_EQ(PurchasedState::LOADING, purchased_state()); + OnFetchRegionList(false, GetRegionsData(), true); + EXPECT_EQ(PurchasedState::LOADING, purchased_state()); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); + EXPECT_EQ(PurchasedState::PURCHASED, purchased_state()); + + // Check purchased is set when fetching timezone is failed. + purchased_state() = PurchasedState::LOADING; + OnFetchTimezones("", false); + EXPECT_EQ(PurchasedState::PURCHASED, purchased_state()); + + // Treat not purchased when empty. + purchased_state() = PurchasedState::LOADING; + OnPrepareCredentialsPresentation("credential="); + EXPECT_EQ(PurchasedState::NOT_PURCHASED, purchased_state()); + + // Treat failed when invalid. + purchased_state() = PurchasedState::LOADING; + OnPrepareCredentialsPresentation(""); + EXPECT_EQ(PurchasedState::FAILED, purchased_state()); + + // Treat as purchased state early when service has region data already. + EXPECT_FALSE(regions().empty()); + purchased_state() = PurchasedState::LOADING; + OnPrepareCredentialsPresentation("credential=abcdefghijk"); + EXPECT_EQ(PurchasedState::PURCHASED, purchased_state()); } -// TODO(bsclifton): re-enable test after figuring out why crash is happening -TEST_F(BraveVPNServiceTest, DISABLED_CancelConnectingTest) { - service_->connection_state_ = ConnectionState::CONNECTING; - service_->cancel_connecting_ = true; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->OnCreated(); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTED, service_->connection_state_); +TEST_F(BraveVPNServiceTest, CancelConnectingTest) { + cancel_connecting() = true; + connection_state() = ConnectionState::CONNECTING; + OnCreated(); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTED, connection_state()); // Start disconnect() when connect is done for cancelling. - service_->cancel_connecting_ = false; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->Disconnect(); - EXPECT_TRUE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTING, service_->connection_state_); - service_->OnConnected(); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTING, service_->connection_state_); - - service_->cancel_connecting_ = false; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->Disconnect(); - EXPECT_TRUE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTING, service_->connection_state_); - - service_->cancel_connecting_ = true; - service_->CreateVPNConnection(); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTED, service_->connection_state_); - - service_->cancel_connecting_ = true; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->OnFetchHostnames("", "", true); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTED, service_->connection_state_); - - service_->cancel_connecting_ = true; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->OnGetSubscriberCredential("", true); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTED, service_->connection_state_); - - service_->cancel_connecting_ = true; - service_->connection_state_ = ConnectionState::CONNECTING; - service_->OnGetProfileCredentials("", true); - EXPECT_FALSE(service_->cancel_connecting_); - EXPECT_EQ(ConnectionState::DISCONNECTED, service_->connection_state_); + cancel_connecting() = false; + connection_state() = ConnectionState::CONNECTING; + Disconnect(); + EXPECT_TRUE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTING, connection_state()); + OnConnected(); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTING, connection_state()); + + cancel_connecting() = false; + connection_state() = ConnectionState::CONNECTING; + Disconnect(); + EXPECT_TRUE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTING, connection_state()); + + cancel_connecting() = true; + CreateVPNConnection(); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTED, connection_state()); + + cancel_connecting() = true; + connection_state() = ConnectionState::CONNECTING; + OnFetchHostnames("", "", true); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTED, connection_state()); + + cancel_connecting() = true; + connection_state() = ConnectionState::CONNECTING; + OnGetSubscriberCredentialV12("", true); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTED, connection_state()); + + cancel_connecting() = true; + connection_state() = ConnectionState::CONNECTING; + OnGetProfileCredentials("", true); + EXPECT_FALSE(cancel_connecting()); + EXPECT_EQ(ConnectionState::DISCONNECTED, connection_state()); } -// TODO(bsclifton): fix after flow is decided -TEST_F(BraveVPNServiceTest, DISABLED_ConnectionInfoTest) { +TEST_F(BraveVPNServiceTest, ConnectionInfoTest) { + // Having skus_credential is pre-requisite before try connecting. + skus_credential() = "test_credentials"; // Check valid connection info is set when valid hostname and profile // credential are fetched. - service_->connection_state_ = ConnectionState::CONNECTING; - pref_service_.SetBoolean(skus::prefs::kSkusVPNHasCredential, true); - service_->OnFetchHostnames("region-a", GetHostnamesData(), true); - EXPECT_EQ(ConnectionState::CONNECTING, service_->connection_state_); + connection_state() = ConnectionState::CONNECTING; + OnFetchHostnames("region-a", GetHostnamesData(), true); + EXPECT_EQ(ConnectionState::CONNECTING, connection_state()); // To prevent real os vpn entry creation. - service_->is_simulation_ = true; - service_->OnGetProfileCredentials(GetProfileCredentialData(), true); - EXPECT_EQ(ConnectionState::CONNECTING, service_->connection_state_); - EXPECT_TRUE(service_->connection_info_.IsValid()); + is_simulation() = true; + OnGetProfileCredentials(GetProfileCredentialData(), true); + EXPECT_EQ(ConnectionState::CONNECTING, connection_state()); + EXPECT_TRUE(GetConnectionInfo().IsValid()); // Check cached connection info is cleared when user set new selected region. - service_->connection_state_ = ConnectionState::DISCONNECTED; - brave_vpn::mojom::Region region; - service_->SetSelectedRegion(region.Clone()); - EXPECT_FALSE(service_->connection_info_.IsValid()); + connection_state() = ConnectionState::DISCONNECTED; + service_->SetSelectedRegion(mojom::Region().Clone()); + EXPECT_FALSE(GetConnectionInfo().IsValid()); } -// TODO(bsclifton): re-enable test after figuring out why crash is happening -TEST_F(BraveVPNServiceTest, DISABLED_NeedsConnectTest) { +TEST_F(BraveVPNServiceTest, NeedsConnectTest) { // Check ignore Connect() request while connecting or disconnecting is // in-progress. - service_->connection_state_ = ConnectionState::CONNECTING; - service_->Connect(); - EXPECT_EQ(ConnectionState::CONNECTING, service_->connection_state_); + SetDeviceRegion("eu-es"); + connection_state() = ConnectionState::CONNECTING; + Connect(); + EXPECT_EQ(ConnectionState::CONNECTING, connection_state()); - service_->connection_state_ = ConnectionState::DISCONNECTING; - service_->Connect(); - EXPECT_EQ(ConnectionState::DISCONNECTING, service_->connection_state_); + connection_state() = ConnectionState::DISCONNECTING; + Connect(); + EXPECT_EQ(ConnectionState::DISCONNECTING, connection_state()); // Handle connect after disconnect current connection. - service_->connection_state_ = ConnectionState::CONNECTED; - service_->Connect(); - EXPECT_TRUE(service_->needs_connect_); - EXPECT_EQ(ConnectionState::DISCONNECTING, service_->connection_state_); - service_->OnDisconnected(); - EXPECT_FALSE(service_->needs_connect_); - EXPECT_EQ(ConnectionState::CONNECTING, service_->connection_state_); + connection_state() = ConnectionState::CONNECTED; + Connect(); + EXPECT_TRUE(needs_connect()); + EXPECT_EQ(ConnectionState::DISCONNECTING, connection_state()); + OnDisconnected(); + EXPECT_FALSE(needs_connect()); + EXPECT_EQ(ConnectionState::CONNECTING, connection_state()); } -// TODO(bsclifton): re-enable test after figuring out why crash is happening -TEST_F(BraveVPNServiceTest, DISABLED_LoadRegionDataFromPrefsTest) { +TEST_F(BraveVPNServiceTest, LoadRegionDataFromPrefsTest) { // Initially, prefs doesn't have region data. - EXPECT_EQ(brave_vpn::mojom::Region(), service_->device_region_); - EXPECT_TRUE(service_->regions_.empty()); + EXPECT_EQ(mojom::Region(), device_region()); + EXPECT_TRUE(regions().empty()); // Set proper data to store them in prefs. - service_->OnFetchRegionList(GetRegionsData(), true); - service_->set_test_timezone("Asia/Seoul"); - service_->OnFetchTimezones(GetTimeZonesData(), true); + OnFetchRegionList(false, GetRegionsData(), true); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); // Check region data is set with above data. - EXPECT_FALSE(brave_vpn::mojom::Region() == service_->device_region_); - EXPECT_FALSE(service_->regions_.empty()); + EXPECT_FALSE(mojom::Region() == device_region()); + EXPECT_FALSE(regions().empty()); // Clear region data. - service_->device_region_ = brave_vpn::mojom::Region(); - service_->regions_.clear(); - EXPECT_EQ(brave_vpn::mojom::Region(), service_->device_region_); - EXPECT_TRUE(service_->regions_.empty()); + regions().clear(); + EXPECT_TRUE(regions().empty()); // Check region data is loaded from prefs. - service_->LoadCachedRegionData(); - EXPECT_FALSE(brave_vpn::mojom::Region() == service_->device_region_); - EXPECT_FALSE(service_->regions_.empty()); + purchased_state() = PurchasedState::LOADING; + LoadCachedRegionData(); + EXPECT_FALSE(regions().empty()); } + +} // namespace brave_vpn diff --git a/components/brave_vpn/mojom/BUILD.gn b/components/brave_vpn/mojom/BUILD.gn new file mode 100644 index 000000000000..c52360996e69 --- /dev/null +++ b/components/brave_vpn/mojom/BUILD.gn @@ -0,0 +1,27 @@ +# Copyright (c) 2022 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 http://mozilla.org/MPL/2.0/. + +import("//mojo/public/tools/bindings/mojom.gni") +import("//tools/grit/preprocess_if_expr.gni") + +preprocess_folder = "preprocessed" +preprocess_mojo_manifest = "preprocessed_mojo_manifest.json" + +preprocess_if_expr("preprocess_mojo") { + deps = [ ":mojom_js" ] + in_folder = "$target_gen_dir" + out_folder = "$target_gen_dir/$preprocess_folder" + out_manifest = "$target_gen_dir/$preprocess_mojo_manifest" + in_files = [ "brave_vpn.mojom-lite.js" ] +} + +mojom("mojom") { + sources = [ "brave_vpn.mojom" ] + + deps = [ + "//mojo/public/mojom/base", + "//url/mojom:url_mojom_gurl", + ] +} diff --git a/components/brave_vpn/brave_vpn.mojom b/components/brave_vpn/mojom/brave_vpn.mojom similarity index 71% rename from components/brave_vpn/brave_vpn.mojom rename to components/brave_vpn/mojom/brave_vpn.mojom index 6a8cb8ea9aec..058f7b1cd936 100644 --- a/components/brave_vpn/brave_vpn.mojom +++ b/components/brave_vpn/mojom/brave_vpn.mojom @@ -23,27 +23,60 @@ interface PanelHandler { }; interface ServiceObserver { + [EnableIfNot=is_android] OnConnectionCreated(); + + [EnableIfNot=is_android] OnConnectionRemoved(); + + [EnableIfNot=is_android] OnConnectionStateChanged(ConnectionState state); + OnPurchasedStateChanged(PurchasedState state); }; // Browser-side handler for vpn service things interface ServiceHandler { - GetConnectionState() => (ConnectionState state); - GetPurchasedState() => (PurchasedState state); AddObserver(pending_remote observer); + GetPurchasedState() => (PurchasedState state); + + [EnableIfNot=is_android] + GetConnectionState() => (ConnectionState state); + + [EnableIfNot=is_android] + ResetConnectionState(); + + [EnableIfNot=is_android] CreateVPNConnection(); + + [EnableIfNot=is_android] Connect(); + + [EnableIfNot=is_android] Disconnect(); + // Gets all region from internal cache which is fetched from Guardian API + [EnableIfNot=is_android] GetAllRegions() => (array regions); + // Gets region based on user's device timezone + [EnableIfNot=is_android] GetDeviceRegion() => (Region device_region); + + [EnableIfNot=is_android] GetSelectedRegion() => (Region current_region); + + [EnableIfNot=is_android] SetSelectedRegion(Region region); + + [EnableIfNot=is_android] GetProductUrls() => (ProductUrls urls); + + [EnableIfNot=is_android] + GetSupportData() => (string app_version, string os_version, string hostname); + + [EnableIfNot=is_android] + CreateSupportTicket(string email, string subject, string body) => (bool success, string response); }; // WebUI-side handler for requests from the browser. @@ -61,6 +94,7 @@ enum ConnectionState { DISCONNECTED, CONNECTING, DISCONNECTING, + CONNECT_NOT_ALLOWED, CONNECT_FAILED, }; @@ -68,8 +102,12 @@ enum PurchasedState { NOT_PURCHASED, PURCHASED, EXPIRED, + LOADING, + FAILED, }; +// Make sure updating BraveVpnService properly when member is added to Region struct. +// Ex, IsValidRegion() or IsValidRegionValue(). It should also check new member. struct Region { string continent; string name; diff --git a/components/brave_vpn/pref_names.cc b/components/brave_vpn/pref_names.cc index c25418a45187..04f9428fec49 100644 --- a/components/brave_vpn/pref_names.cc +++ b/components/brave_vpn/pref_names.cc @@ -12,9 +12,9 @@ namespace prefs { void RegisterProfilePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(kBraveVPNShowButton, true); - registry->RegisterDictionaryPref(kBraveVPNSelectedRegion); registry->RegisterListPref(kBraveVPNRegionList); - registry->RegisterDictionaryPref(kBraveVPNDeviceRegion); + registry->RegisterStringPref(kBraveVPNDeviceRegion, ""); + registry->RegisterStringPref(kBraveVPNSelectedRegion, ""); } } // namespace prefs diff --git a/components/brave_vpn/pref_names.h b/components/brave_vpn/pref_names.h index 6a5437c9d5c8..4314f65cab5a 100644 --- a/components/brave_vpn/pref_names.h +++ b/components/brave_vpn/pref_names.h @@ -11,10 +11,11 @@ class PrefRegistrySimple; namespace brave_vpn { namespace prefs { -constexpr char kBraveVPNSelectedRegion[] = "brave.brave_vpn.selected_region"; constexpr char kBraveVPNShowButton[] = "brave.brave_vpn.show_button"; constexpr char kBraveVPNRegionList[] = "brave.brave_vpn.region_list"; -constexpr char kBraveVPNDeviceRegion[] = "brave.brave_vpn.device_region"; +constexpr char kBraveVPNDeviceRegion[] = "brave.brave_vpn.device_region_name"; +constexpr char kBraveVPNSelectedRegion[] = + "brave.brave_vpn.selected_region_name"; void RegisterProfilePrefs(PrefRegistrySimple* registry); diff --git a/components/brave_vpn/resources/panel/BUILD.gn b/components/brave_vpn/resources/panel/BUILD.gn index c20ed011d866..773b30105a22 100644 --- a/components/brave_vpn/resources/panel/BUILD.gn +++ b/components/brave_vpn/resources/panel/BUILD.gn @@ -13,8 +13,8 @@ transpile_web_ui("brave_vpn_panel_ui") { ] ] resource_name = "brave_vpn_panel" deps = [ - "//brave/components/brave_vpn:mojom_js", - "//brave/components/brave_vpn:preprocess_mojo", + "//brave/components/brave_vpn/mojom:mojom_js", + "//brave/components/brave_vpn/mojom:preprocess_mojo", ] } diff --git a/components/brave_vpn/resources/panel/api/panel_browser_api.ts b/components/brave_vpn/resources/panel/api/panel_browser_api.ts index 59d6a553b979..393a397b5e1c 100644 --- a/components/brave_vpn/resources/panel/api/panel_browser_api.ts +++ b/components/brave_vpn/resources/panel/api/panel_browser_api.ts @@ -3,14 +3,16 @@ // 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 * as BraveVPN from 'gen/brave/components/brave_vpn/brave_vpn.mojom.m.js' +import * as BraveVPN from 'gen/brave/components/brave_vpn/mojom/brave_vpn.mojom.m.js' // Provide access to all the generated types -export * from 'gen/brave/components/brave_vpn/brave_vpn.mojom.m.js' +export * from 'gen/brave/components/brave_vpn/mojom/brave_vpn.mojom.m.js' + +export type SupportData = BraveVPN.ServiceHandler_GetSupportData_ResponseParams interface API { - pageCallbackRouter: BraveVPN.PageCallbackRouter - panelHandler: BraveVPN.PanelHandlerRemote - serviceHandler: BraveVPN.ServiceHandlerRemote + pageCallbackRouter: BraveVPN.PageInterface + panelHandler: BraveVPN.PanelHandlerInterface + serviceHandler: BraveVPN.ServiceHandlerInterface } let panelBrowserAPIInstance: API @@ -35,3 +37,7 @@ export default function getPanelBrowserAPI () { } return panelBrowserAPIInstance } + +export function setPanelBrowserApiForTesting (api: API) { + panelBrowserAPIInstance = api +} diff --git a/components/brave_vpn/resources/panel/components/contact-support/index.tsx b/components/brave_vpn/resources/panel/components/contact-support/index.tsx new file mode 100644 index 000000000000..1dea6a1f0615 --- /dev/null +++ b/components/brave_vpn/resources/panel/components/contact-support/index.tsx @@ -0,0 +1,234 @@ +import * as React from 'react' +import Button from '$web-components/button' +import Select from '$web-components/select' +import TextInput, { Textarea } from '$web-components/input' +import Toggle from '$web-components/toggle' +import { getLocale } from '../../../../../common/locale' +import * as S from './style' +import getPanelBrowserAPI, * as BraveVPN from '../../api/panel_browser_api' +import { CaratStrongLeftIcon } from 'brave-ui/components/icons' + +interface Props { + onCloseContactSupport: React.MouseEventHandler +} + +interface ContactSupportInputFields { + contactEmail: string + problemSubject: string + problemBody: string +} + +interface ContactSupportToggleFields { + shareHostname: boolean + shareAppVersion: boolean + shareOsVersion: boolean +} + +type ContactSupportState = ContactSupportInputFields & + ContactSupportToggleFields + +const defaultSupportState: ContactSupportState = { + contactEmail: '', + problemSubject: '', + problemBody: '', + shareHostname: true, + shareAppVersion: true, + shareOsVersion: true +} + +type FormElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement +type BaseType = string | number | React.FormEvent + +function ContactSupport (props: Props) { + const [supportData, setSupportData] = React.useState() + const [formData, setFormData] = React.useState(defaultSupportState) + const [showErrors, setShowErrors] = React.useState(false) + // Undefined for never sent, true for is sending, false for has completed + const [isSubmitting, setIsSubmitting] = React.useState() + const [, setRemoteSubmissionError] = React.useState() + + // Get possible values to submit + React.useEffect(() => { + getPanelBrowserAPI().serviceHandler.getSupportData() + .then(setSupportData) + }, []) + + function getOnChangeField (key: keyof ContactSupportInputFields) { + return function (e: T) { + console.log(setFormData) + const value = (typeof e === 'string' || typeof e === 'number') ? e : e.currentTarget.value + if (formData[key] === value) { + return + } + setFormData(data => ({ + ...data, + [key]: value + })) + } + } + + function getOnChangeToggle (key: keyof ContactSupportToggleFields) { + return function (isOn: boolean) { + if (formData[key] === isOn) { + return + } + setFormData(data => ({ + ...data, + [key]: isOn + })) + } + } + + const isValid = React.useMemo(() => { + return !supportData || + !!formData.problemBody || + !!formData.contactEmail || + !!formData.problemSubject + }, [formData, supportData]) + + const handleSubmit = async () => { + // Clear error about last submission + setRemoteSubmissionError(undefined) + // Handle submission when not valid, show user + // which fields are required + if (!isValid) { + setShowErrors(true) + return + } + // Handle is valid, submit data + setIsSubmitting(true) + const fullIssueBody = formData.problemBody + '\n' + + (formData.shareOsVersion ? `OS: ${supportData?.osVersion}\n` : '') + + (formData.shareAppVersion ? `App version: ${supportData?.appVersion}\n` : '') + + (formData.shareHostname ? `Hostname: ${supportData?.hostname}\n` : '') + + // TODO: this will return a bool (success) and string (message) + await getPanelBrowserAPI().serviceHandler.createSupportTicket( + formData.contactEmail, + formData.problemSubject, + fullIssueBody + ) + + // TODO: handle error case, if any? + setIsSubmitting(false) + } + + const handlePrivacyPolicyClick = () => { + chrome.tabs.create({ url: 'https://brave.com/privacy/browser/#vpn' }) + } + + return ( + + + + + + {getLocale('braveVpnContactSupport')} + + + + { e.preventDefault() }}> + + + + + + ) +} diff --git a/components/web-components/input/input.module.scss b/components/web-components/input/input.module.scss new file mode 100644 index 000000000000..72a38a2417af --- /dev/null +++ b/components/web-components/input/input.module.scss @@ -0,0 +1,45 @@ +.textInput { + max-width: 720px; + display: flex; + flex-direction: column; + gap: 5px; + font-size: 14px; + font-weight: 400; + line-height: 20px; + text-align: start; + color: var(--text2); + + > input, > textarea { + --focus-border-color: var(--highlight-color); + box-sizing: border-box; + border: none; + width: 100%; + border-radius: 4px; + outline: 1px solid var(--interactive8); + transition: outline .09s ease-in-out; + background-color: var(--background1); + padding: 10px 18px; + font-weight: 400; + font-size: 13px; + line-height: 20px; + color: var(--text1); + &:hover, &:focus-visible, &:active { + outline: 4px solid var(--focus-border); + } + &:disabled { + color: var(--disabled); + outline: 1px solid var(--disabled); + } + } + + .hasError & input { + background-color: #FFEBEB; + --focus-border-color: #FF0000; + } +} + + +.errorMessage { + margin-left: 4px; + color: #BD1531; +} \ No newline at end of file diff --git a/components/web-components/input/input.stories.tsx b/components/web-components/input/input.stories.tsx new file mode 100644 index 000000000000..a6b4f3d32255 --- /dev/null +++ b/components/web-components/input/input.stories.tsx @@ -0,0 +1,41 @@ +import React from 'react' +import { ComponentStory, ComponentMeta } from '@storybook/react' +import Component from './index' + +export default { + title: 'Input', + component: Component, + argTypes: { + isRequired: { + type: 'boolean' + }, + isErrorAlwaysShown: { + type: 'boolean' + }, + errorMessage: { + type: 'string' + }, + disabled: { + type: 'boolean' + }, + label: { + type: 'string' + } + // scale: { + // options: ['regular', 'tiny', 'small', 'large', 'jumbo'], + // control: { type: 'select' } + // } + } +} as ComponentMeta + +const Template: ComponentStory = function (args, o) { + const [value, setValue] = React.useState('I am an input') + const handleChange: React.FormEventHandler = (e) => { + setValue(e.currentTarget.value) + } + return
+ +
+} + +export const Everything = Template.bind({})