diff --git a/components/brave_vpn/brave_vpn_api_request.cc b/components/brave_vpn/brave_vpn_api_request.cc index 771356af73d7..6cf028523b3d 100644 --- a/components/brave_vpn/brave_vpn_api_request.cc +++ b/components/brave_vpn/brave_vpn_api_request.cc @@ -213,18 +213,20 @@ void BraveVpnAPIRequest::GetSubscriberCredentialV12( {{"Brave-Payments-Environment", environment}}); } -void BraveVpnAPIRequest::CreateSupportTicket(ResponseCallback callback, - const std::string& email, - const std::string& subject, - const std::string& body) { +void BraveVpnAPIRequest::CreateSupportTicket( + ResponseCallback callback, + const std::string& email, + const std::string& subject, + const std::string& body, + const std::string& subscriber_credential) { auto internal_callback = base::BindOnce(&BraveVpnAPIRequest::OnCreateSupportTicket, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - OAuthRequest( - GetURLWithPath(kVpnHost, kCreateSupportTicket), "POST", - CreateJSONRequestBody(GetValueWithTicketInfos(email, subject, body)), - std::move(internal_callback)); + OAuthRequest(GetURLWithPath(kVpnHost, kCreateSupportTicket), "POST", + CreateJSONRequestBody(GetValueWithTicketInfos( + email, subject, body, subscriber_credential)), + std::move(internal_callback)); } void BraveVpnAPIRequest::OAuthRequest( diff --git a/components/brave_vpn/brave_vpn_api_request.h b/components/brave_vpn/brave_vpn_api_request.h index 8cd7bac5740e..a61a760cf86d 100644 --- a/components/brave_vpn/brave_vpn_api_request.h +++ b/components/brave_vpn/brave_vpn_api_request.h @@ -71,7 +71,8 @@ class BraveVpnAPIRequest { void CreateSupportTicket(ResponseCallback callback, const std::string& email, const std::string& subject, - const std::string& body); + const std::string& body, + const std::string& subscriber_credential); private: using URLRequestCallback = base::OnceCallback; diff --git a/components/brave_vpn/brave_vpn_constants.h b/components/brave_vpn/brave_vpn_constants.h index 6e6e4ab6ce3f..2932f141905a 100644 --- a/components/brave_vpn/brave_vpn_constants.h +++ b/components/brave_vpn/brave_vpn_constants.h @@ -100,6 +100,9 @@ constexpr char kCreateSubscriberCredentialV12[] = "api/v1.2/subscriber-credential/create"; constexpr int kP3AIntervalHours = 24; +constexpr char kSubscriberCredentialKey[] = "credential"; +constexpr char kSubscriberCredentialExpirationKey[] = "expiration"; + #if !BUILDFLAG(IS_ANDROID) constexpr char kTokenNoLongerValid[] = "Token No Longer Valid"; #endif // !BUILDFLAG(IS_ANDROID) diff --git a/components/brave_vpn/brave_vpn_os_connection_api.cc b/components/brave_vpn/brave_vpn_os_connection_api.cc index 7fe5ffe6080d..2f51c600ed8c 100644 --- a/components/brave_vpn/brave_vpn_os_connection_api.cc +++ b/components/brave_vpn/brave_vpn_os_connection_api.cc @@ -151,10 +151,6 @@ void BraveVPNOSConnectionAPI::CheckConnection() { CheckConnectionImpl(target_vpn_entry_name_); } -void BraveVPNOSConnectionAPI::SetSkusCredential(const std::string& credential) { - skus_credential_ = credential; -} - void BraveVPNOSConnectionAPI::ResetConnectionInfo() { VLOG(2) << __func__; connection_info_.Reset(); @@ -333,7 +329,7 @@ std::string BraveVPNOSConnectionAPI::GetSelectedRegion() const { } std::string BraveVPNOSConnectionAPI::GetCurrentEnvironment() const { - return local_prefs_->GetString(prefs::kBraveVPNEEnvironment); + return local_prefs_->GetString(prefs::kBraveVPNEnvironment); } void BraveVPNOSConnectionAPI::FetchHostnamesForRegion(const std::string& name) { @@ -402,59 +398,19 @@ void BraveVPNOSConnectionAPI::ParseAndCacheHostnames( << 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; - } - if (!GetAPIRequest()) { CHECK_IS_TEST(); return; } - // Get subscriber credentials and then get EAP credentials with it to create - // OS VPN entry. - VLOG(2) << __func__ << " : request subscriber credential:" + // Get profile credentials it to create OS VPN entry. + VLOG(2) << __func__ << " : request profile credential:" << GetBraveVPNPaymentsEnv(GetCurrentEnvironment()); - GetAPIRequest()->GetSubscriberCredentialV12( - base::BindOnce(&BraveVPNOSConnectionAPI::OnGetSubscriberCredentialV12, - base::Unretained(this)), - skus_credential_, GetBraveVPNPaymentsEnv(GetCurrentEnvironment())); -} - -void BraveVPNOSConnectionAPI::OnGetSubscriberCredentialV12( - const std::string& subscriber_credential, - bool success) { - if (cancel_connecting_) { - UpdateAndNotifyConnectionStateChange(ConnectionState::DISCONNECTED); - cancel_connecting_ = false; - return; - } - - if (!success) { - VLOG(2) << __func__ << " : failed to get subscriber credential"; - if (subscriber_credential == kTokenNoLongerValid) { - for (auto& obs : observers_) - obs.OnGetInvalidToken(); - } - UpdateAndNotifyConnectionStateChange(ConnectionState::CONNECT_FAILED); - return; - } - - VLOG(2) << __func__ << " : received subscriber credential"; - - if (!GetAPIRequest()) { - CHECK_IS_TEST(); - return; - } - // TODO(bsclifton): consider storing `subscriber_credential` for - // support ticket use-case (see `CreateSupportTicket`). GetAPIRequest()->GetProfileCredentials( base::BindOnce(&BraveVPNOSConnectionAPI::OnGetProfileCredentials, base::Unretained(this)), - subscriber_credential, hostname_->hostname); + GetSubscriberCredential(local_prefs_), hostname_->hostname); } void BraveVPNOSConnectionAPI::OnGetProfileCredentials( diff --git a/components/brave_vpn/brave_vpn_os_connection_api.h b/components/brave_vpn/brave_vpn_os_connection_api.h index f7e6ca9446dc..1e26da4ca9c7 100644 --- a/components/brave_vpn/brave_vpn_os_connection_api.h +++ b/components/brave_vpn/brave_vpn_os_connection_api.h @@ -35,7 +35,6 @@ class BraveVPNOSConnectionAPI : public base::PowerSuspendObserver, public: class Observer : public base::CheckedObserver { public: - virtual void OnGetInvalidToken() = 0; virtual void OnConnectionStateChanged(mojom::ConnectionState state) = 0; protected: @@ -74,7 +73,6 @@ class BraveVPNOSConnectionAPI : public base::PowerSuspendObserver, void ToggleConnection(); void RemoveVPNConnection(); void CheckConnection(); - void SetSkusCredential(const std::string& credential); void ResetConnectionInfo(); std::string GetHostname() const; @@ -121,8 +119,6 @@ class BraveVPNOSConnectionAPI : public base::PowerSuspendObserver, bool success); void ParseAndCacheHostnames(const std::string& region, const base::Value::List& hostnames_value); - void OnGetSubscriberCredentialV12(const std::string& subscriber_credential, - bool success); void OnGetProfileCredentials(const std::string& profile_credential, bool success); @@ -134,7 +130,6 @@ class BraveVPNOSConnectionAPI : public base::PowerSuspendObserver, bool reconnect_on_resume_ = false; bool prevent_creation_ = false; std::string target_vpn_entry_name_; - std::string skus_credential_; mojom::ConnectionState connection_state_ = mojom::ConnectionState::DISCONNECTED; BraveVPNConnectionInfo connection_info_; diff --git a/components/brave_vpn/brave_vpn_service.cc b/components/brave_vpn/brave_vpn_service.cc index 97ce6249dd3c..dd076e8ced32 100644 --- a/components/brave_vpn/brave_vpn_service.cc +++ b/components/brave_vpn/brave_vpn_service.cc @@ -22,6 +22,7 @@ #include "brave/components/skus/browser/skus_utils.h" #include "components/prefs/pref_service.h" #include "net/cookies/cookie_inclusion_status.h" +#include "net/cookies/cookie_util.h" #include "net/cookies/parsed_cookie.h" #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/url_util.h" @@ -59,24 +60,10 @@ BraveVpnService::BraveVpnService( #if !BUILDFLAG(IS_ANDROID) auto* cmd = base::CommandLine::ForCurrentProcess(); is_simulation_ = cmd->HasSwitch(switches::kBraveVPNSimulation); - // is_simulation_ = true; observed_.Observe(GetBraveVPNConnectionAPI()); GetBraveVPNConnectionAPI()->set_target_vpn_entry_name(kBraveVPNEntryName); - // To get proper connection state, we need to load purchased state. - // Connection state will be checked after we confirm that this profile - // is purchased user. - // However, purchased state loading makes additional network request. - // We should not make this network request for fresh user. - // To prevent this, we load purchased state at startup only - // when profile has cached region list because region list is fetched - // and cached only when user purchased at least once. - auto* preference = local_prefs_->FindPreference(prefs::kBraveVPNRegionList); - if (preference && !preference->IsDefaultValue()) { - ReloadPurchasedState(); - } - pref_change_registrar_.Init(local_prefs_); pref_change_registrar_.Add( prefs::kBraveVPNSelectedRegion, @@ -85,13 +72,41 @@ BraveVpnService::BraveVpnService( #endif // !BUILDFLAG(IS_ANDROID) + CheckInitialState(); InitP3A(); } BraveVpnService::~BraveVpnService() = default; +void BraveVpnService::CheckInitialState() { + if (HasValidSubscriberCredential(local_prefs_)) { + ScheduleSubscriberCredentialRefresh(); + +#if BUILDFLAG(IS_ANDROID) + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); + // Android has its own region data managing logic. +#else + LoadCachedRegionData(); + if (regions_.empty()) { + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::LOADING); + // Not sure this can happen when user is already purchased user. + // To make sure before set as purchased user, however, trying region fetch + // and then set as a purchased user when we get valid region data. + FetchRegionData(false); + } else { + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); + } + + ScheduleBackgroundRegionDataFetch(); + GetBraveVPNConnectionAPI()->CheckConnection(); +#endif + } else { + ClearSubscriberCredential(local_prefs_); + } +} + std::string BraveVpnService::GetCurrentEnvironment() const { - return local_prefs_->GetString(prefs::kBraveVPNEEnvironment); + return local_prefs_->GetString(prefs::kBraveVPNEnvironment); } void BraveVpnService::ReloadPurchasedState() { @@ -118,10 +133,6 @@ void BraveVpnService::ScheduleBackgroundRegionDataFetch() { base::Unretained(this), true)); } -void BraveVpnService::OnGetInvalidToken() { - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::EXPIRED); -} - void BraveVpnService::OnConnectionStateChanged(mojom::ConnectionState state) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); VLOG(2) << __func__; @@ -336,6 +347,10 @@ void BraveVpnService::SetDeviceRegionWithTimezone( if (current_time_zone == timezone.GetString()) { VLOG(2) << "Found default region: " << *region_name; SetDeviceRegion(*region_name); + // Use device region as a default selected region. + if (GetSelectedRegion().empty()) { + SetSelectedRegion(*region_name); + } return; } } @@ -391,15 +406,6 @@ void BraveVpnService::GetAllRegions(GetAllRegionsCallback callback) { 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__; @@ -423,6 +429,11 @@ void BraveVpnService::SetSelectedRegion(mojom::RegionPtr region_ptr) { VLOG(2) << __func__ << ": Current state: " << connection_state << " : prevent changing selected region while previous operation " "is in-progress"; + // This is workaround to prevent UI changes seleted region. + for (const auto& obs : observers_) { + obs->OnSelectedRegionChanged( + GetRegionPtrWithNameFromRegionList(GetSelectedRegion(), regions_)); + } return; } @@ -448,8 +459,9 @@ void BraveVpnService::CreateSupportTicket( auto internal_callback = base::BindOnce(&BraveVpnService::OnCreateSupportTicket, weak_ptr_factory_.GetWeakPtr(), std::move(callback)); - api_request_.CreateSupportTicket(std::move(internal_callback), email, subject, - body); + api_request_.CreateSupportTicket( + std::move(internal_callback), email, subject, body, + ::brave_vpn::GetSubscriberCredential(local_prefs_)); } void BraveVpnService::GetSupportData(GetSupportDataCallback callback) { @@ -554,8 +566,11 @@ void BraveVpnService::LoadPurchasedState(const std::string& domain) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); auto requested_env = skus::GetEnvironmentForDomain(domain); if (GetCurrentEnvironment() == requested_env && - purchased_state_ == PurchasedState::LOADING) + purchased_state_ == PurchasedState::LOADING) { + VLOG(2) << __func__ << ": Loading in-progress"; return; + } + #if !BUILDFLAG(IS_ANDROID) if (!IsNetworkAvailable()) { VLOG(2) << __func__ << ": Network is not available, failed to connect"; @@ -567,23 +582,28 @@ void BraveVpnService::LoadPurchasedState(const std::string& domain) { if (!purchased_state_.has_value()) SetPurchasedState(requested_env, 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(); - SetCurrentEnvironment(requested_env); - if (!regions_.empty()) { - SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); + if (HasValidSubscriberCredential(local_prefs_)) { +#if BUILDFLAG(IS_ANDROID) + SetPurchasedState(requested_env, PurchasedState::PURCHASED); +#else + // Need to wait more till region data is fetched. + if (regions_.empty()) { + VLOG(2) << __func__ << ": Wait till we get valid region data."; + SetPurchasedState(requested_env, PurchasedState::LOADING); } else { - FetchRegionData(false); + VLOG(2) << __func__ + << ": Set as a purchased user as we have valid subscriber " + "credentials & region data"; + SetPurchasedState(requested_env, PurchasedState::PURCHASED); } - - GetBraveVPNConnectionAPI()->CheckConnection(); +#endif return; } -#endif // !BUILDFLAG(IS_ANDROID) && !defined(OFFICIAL_BUILD) + + VLOG(2) << __func__ + << ": Checking purchased state as we doesn't have valid subscriber " + "credentials"; + ClearSubscriberCredential(local_prefs_); EnsureMojoConnected(); skus_service_->CredentialSummary( @@ -606,10 +626,8 @@ void BraveVpnService::OnCredentialSummary(const std::string& domain, absl::optional records_v = base::JSONReader::Read( summary_string, base::JSONParserOptions::JSON_PARSE_RFC); - if (records_v && records_v->is_dict()) { - auto active = records_v->GetDict().FindBool("active").value_or(false); - if (active) { + if (IsValidCredentialSummary(*records_v)) { VLOG(1) << __func__ << " : Active credential found!"; // if a credential is ready, we can present it EnsureMojoConnected(); @@ -640,6 +658,8 @@ void BraveVpnService::OnPrepareCredentialsPresentation( // or maybe it can be considered FAILED status? if (!credential_cookie.IsValid()) { VLOG(1) << __func__ << " : FAILED credential_cookie.IsValid"; + // TODO(simonhong): Set as NOT_PURCHASED. + // It seems we're not using this state. SetPurchasedState(env, PurchasedState::FAILED); return; } @@ -649,9 +669,17 @@ void BraveVpnService::OnPrepareCredentialsPresentation( return; } + if (!credential_cookie.HasExpires()) { + VLOG(1) << __func__ << " : FAILED cookie doesn't have expired date."; + SetPurchasedState(env, 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(); + const std::string encoded_credential = credential_cookie.Value(); + const auto time = + net::cookie_util::ParseCookieExpirationTime(credential_cookie.Expires()); url::RawCanonOutputT unescaped; url::DecodeURLEscapeSequences( encoded_credential.data(), encoded_credential.size(), @@ -662,23 +690,56 @@ void BraveVpnService::OnPrepareCredentialsPresentation( SetPurchasedState(env, PurchasedState::NOT_PURCHASED); return; } + + // Early return when it's already expired. + if (time < base::Time::Now()) { + SetPurchasedState(env, PurchasedState::EXPIRED); + return; + } + if (GetCurrentEnvironment() != env) { // Change environment because we have successfully authorized with new one. SetCurrentEnvironment(env); } - skus_credential_ = credential; + api_request_.GetSubscriberCredentialV12( + base::BindOnce(&BraveVpnService::OnGetSubscriberCredentialV12, + base::Unretained(this), time), + credential, GetBraveVPNPaymentsEnv(GetCurrentEnvironment())); +} +void BraveVpnService::OnGetSubscriberCredentialV12( + const base::Time& expiration_time, + const std::string& subscriber_credential, + bool success) { + if (!success) { + VLOG(2) << __func__ << " : failed to get subscriber credential"; #if BUILDFLAG(IS_ANDROID) - SetPurchasedState(env, PurchasedState::PURCHASED); + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::NOT_PURCHASED); #else - GetBraveVPNConnectionAPI()->SetSkusCredential(skus_credential_); + if (subscriber_credential == kTokenNoLongerValid) { + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::EXPIRED); + } else { + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::NOT_PURCHASED); + } +#endif + return; + } + SetSubscriberCredential(local_prefs_, subscriber_credential, expiration_time); + + // Launch one-shot timer for refreshing subscriber_credential before it's + // expired. + ScheduleSubscriberCredentialRefresh(); + +#if BUILDFLAG(IS_ANDROID) + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); +#else LoadCachedRegionData(); // Only fetch when we don't have cache. if (!regions_.empty()) { - SetPurchasedState(env, PurchasedState::PURCHASED); + SetPurchasedState(GetCurrentEnvironment(), PurchasedState::PURCHASED); } else { FetchRegionData(false); } @@ -688,6 +749,31 @@ void BraveVpnService::OnPrepareCredentialsPresentation( #endif } +void BraveVpnService::ScheduleSubscriberCredentialRefresh() { + if (subs_cred_refresh_timer_.IsRunning()) + subs_cred_refresh_timer_.Stop(); + + const auto expiration_time = GetExpirationTime(local_prefs_); + if (!expiration_time) + return; + + auto expiration_time_delta = *expiration_time - base::Time::Now(); + VLOG(2) << "Schedule subscriber credential fetching after " + << expiration_time_delta; + subs_cred_refresh_timer_.Start( + FROM_HERE, expiration_time_delta, + base::BindOnce(&BraveVpnService::RefreshSubscriberCredential, + base::Unretained(this))); +} + +void BraveVpnService::RefreshSubscriberCredential() { + VLOG(2) << "Refresh subscriber credential"; + + // Clear current credentials to get newer one. + ClearSubscriberCredential(local_prefs_); + ReloadPurchasedState(); +} + // TODO(simonhong): Should move p3a to BraveVPNOSConnectionAPI? void BraveVpnService::InitP3A() { p3a_timer_.Start(FROM_HERE, base::Hours(kP3AIntervalHours), this, @@ -762,7 +848,7 @@ void BraveVpnService::SetPurchasedState(const std::string& env, } void BraveVpnService::SetCurrentEnvironment(const std::string& env) { - local_prefs_->SetString(prefs::kBraveVPNEEnvironment, env); + local_prefs_->SetString(prefs::kBraveVPNEnvironment, env); purchased_state_.reset(); } @@ -865,9 +951,10 @@ void BraveVpnService::GetSubscriberCredential( } void BraveVpnService::GetSubscriberCredentialV12(ResponseCallback callback) { - api_request_.GetSubscriberCredentialV12( - std::move(callback), skus_credential_, - GetBraveVPNPaymentsEnv(GetCurrentEnvironment())); + // Caller can get valid subscriber credential only in purchased state. + // Otherwise, false is passed to |callback| as success param. + std::move(callback).Run(::brave_vpn::GetSubscriberCredential(local_prefs_), + HasValidSubscriberCredential(local_prefs_)); } } // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_service.h b/components/brave_vpn/brave_vpn_service.h index 3603b865f505..e4b4728929e1 100644 --- a/components/brave_vpn/brave_vpn_service.h +++ b/components/brave_vpn/brave_vpn_service.h @@ -95,7 +95,6 @@ class BraveVpnService : void Connect() override; void Disconnect() 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; @@ -168,7 +167,6 @@ class BraveVpnService : friend class ::BraveBrowserCommandControllerTest; // BraveVPNOSConnectionAPI::Observer overrides: - void OnGetInvalidToken() override; void OnConnectionStateChanged(mojom::ConnectionState state) override; void LoadCachedRegionData(); @@ -216,6 +214,14 @@ class BraveVpnService : void OnPrepareCredentialsPresentation( const std::string& domain, const std::string& credential_as_cookie); + void OnGetSubscriberCredentialV12(const base::Time& expiration_time, + const std::string& subscriber_credential, + bool success); + void ScheduleSubscriberCredentialRefresh(); + void RefreshSubscriberCredential(); + + // Check initial purchased/connected state. + void CheckInitialState(); #if !BUILDFLAG(IS_ANDROID) std::vector regions_; @@ -242,8 +248,8 @@ class BraveVpnService : absl::optional purchased_state_; mojo::RemoteSet observers_; BraveVpnAPIRequest api_request_; - std::string skus_credential_; base::RepeatingTimer p3a_timer_; + base::OneShotTimer subs_cred_refresh_timer_; base::WeakPtrFactory weak_ptr_factory_{this}; }; diff --git a/components/brave_vpn/brave_vpn_service_helper.cc b/components/brave_vpn/brave_vpn_service_helper.cc index 0c6fdfc5d3b5..f96c33664ee8 100644 --- a/components/brave_vpn/brave_vpn_service_helper.cc +++ b/components/brave_vpn/brave_vpn_service_helper.cc @@ -6,14 +6,19 @@ #include "brave/components/brave_vpn/brave_vpn_service_helper.h" #include +#include #include "base/base64.h" +#include "base/json/values_util.h" #include "base/notreached.h" #include "base/ranges/algorithm.h" +#include "base/time/time.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/brave_vpn/pref_names.h" #include "brave/components/skus/browser/skus_utils.h" -#include "third_party/abseil-cpp/absl/types/optional.h" +#include "components/prefs/pref_service.h" namespace brave_vpn { @@ -150,9 +155,11 @@ std::vector ParseRegionList( return regions; } -base::Value::Dict GetValueWithTicketInfos(const std::string& email, - const std::string& subject, - const std::string& body) { +base::Value::Dict GetValueWithTicketInfos( + const std::string& email, + const std::string& subject, + const std::string& body, + const std::string& subscriber_credential) { base::Value::Dict dict; std::string email_trimmed, subject_trimmed, body_trimmed, body_encoded; @@ -168,7 +175,7 @@ base::Value::Dict GetValueWithTicketInfos(const std::string& email, dict.Set("partner-client-id", "com.brave.browser"); // optional (but encouraged) fields - dict.Set("subscriber-credential", ""); + dict.Set("subscriber-credential", subscriber_credential); dict.Set("payment-validation-method", "brave-premium"); dict.Set("payment-validation-data", ""); @@ -184,4 +191,76 @@ mojom::RegionPtr GetRegionPtrWithNameFromRegionList( return mojom::RegionPtr(); } +bool IsValidCredentialSummary(const base::Value& summary) { + DCHECK(summary.is_dict()); + const bool active = summary.GetDict().FindBool("active").value_or(false); + const int remaining_credential_count = + summary.GetDict().FindInt("remaining_credential_count").value_or(0); + return active && remaining_credential_count > 0; +} + +bool HasValidSubscriberCredential(PrefService* local_prefs) { + const base::Value::Dict& sub_cred_dict = + local_prefs->GetDict(prefs::kBraveVPNSubscriberCredential); + if (sub_cred_dict.empty()) + return false; + + const std::string* cred = sub_cred_dict.FindString(kSubscriberCredentialKey); + const base::Value* expiration_time_value = + sub_cred_dict.Find(kSubscriberCredentialExpirationKey); + + if (!cred || !expiration_time_value) + return false; + + if (cred->empty()) + return false; + + auto expiration_time = base::ValueToTime(expiration_time_value); + if (!expiration_time || expiration_time < base::Time::Now()) + return false; + + return true; +} + +std::string GetSubscriberCredential(PrefService* local_prefs) { + if (!HasValidSubscriberCredential(local_prefs)) + return ""; + const base::Value::Dict& sub_cred_dict = + local_prefs->GetDict(prefs::kBraveVPNSubscriberCredential); + const std::string* cred = sub_cred_dict.FindString(kSubscriberCredentialKey); + DCHECK(cred); + return *cred; +} + +absl::optional GetExpirationTime(PrefService* local_prefs) { + if (!HasValidSubscriberCredential(local_prefs)) + return absl::nullopt; + + const base::Value::Dict& sub_cred_dict = + local_prefs->GetDict(prefs::kBraveVPNSubscriberCredential); + + const base::Value* expiration_time_value = + sub_cred_dict.Find(kSubscriberCredentialExpirationKey); + + if (!expiration_time_value) + return absl::nullopt; + + return base::ValueToTime(expiration_time_value); +} + +void SetSubscriberCredential(PrefService* local_prefs, + const std::string& subscriber_credential, + const base::Time& expiration_time) { + base::Value::Dict cred_dict; + cred_dict.Set(kSubscriberCredentialKey, subscriber_credential); + cred_dict.Set(kSubscriberCredentialExpirationKey, + base::TimeToValue(expiration_time)); + local_prefs->SetDict(prefs::kBraveVPNSubscriberCredential, + std::move(cred_dict)); +} + +void ClearSubscriberCredential(PrefService* local_prefs) { + local_prefs->ClearPref(prefs::kBraveVPNSubscriberCredential); +} + } // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_service_helper.h b/components/brave_vpn/brave_vpn_service_helper.h index 4b4115f0021e..ac63b259f390 100644 --- a/components/brave_vpn/brave_vpn_service_helper.h +++ b/components/brave_vpn/brave_vpn_service_helper.h @@ -12,6 +12,14 @@ #include "base/values.h" #include "brave/components/brave_vpn/mojom/brave_vpn.mojom.h" +#include "third_party/abseil-cpp/absl/types/optional.h" + +class PrefService; + +namespace base { +class Time; +class Value; +} // namespace base namespace brave_vpn { @@ -26,12 +34,22 @@ std::unique_ptr PickBestHostname( std::vector ParseHostnames(const base::Value::List& hostnames); std::vector ParseRegionList( const base::Value::List& region_list); -base::Value::Dict GetValueWithTicketInfos(const std::string& email, - const std::string& subject, - const std::string& body); +base::Value::Dict GetValueWithTicketInfos( + const std::string& email, + const std::string& subject, + const std::string& body, + const std::string& subscriber_credential); mojom::RegionPtr GetRegionPtrWithNameFromRegionList( const std::string& name, const std::vector region_list); +bool IsValidCredentialSummary(const base::Value& summary); +bool HasValidSubscriberCredential(PrefService* local_prefs); +std::string GetSubscriberCredential(PrefService* local_prefs); +absl::optional GetExpirationTime(PrefService* local_prefs); +void SetSubscriberCredential(PrefService* local_prefs, + const std::string& subscriber_credential, + const base::Time& expiration_time); +void ClearSubscriberCredential(PrefService* local_prefs); } // namespace brave_vpn diff --git a/components/brave_vpn/brave_vpn_unittest.cc b/components/brave_vpn/brave_vpn_unittest.cc index d50548391f28..69715792f532 100644 --- a/components/brave_vpn/brave_vpn_unittest.cc +++ b/components/brave_vpn/brave_vpn_unittest.cc @@ -306,13 +306,6 @@ class BraveVPNServiceTest : public testing::Test { GetBraveVPNConnectionAPI()->OnFetchHostnames(region, hostnames, success); } - std::string& skus_credential() { return service_->skus_credential_; } - - void SetSkusCredential(const std::string& credential) { - service_->skus_credential_ = credential; - GetBraveVPNConnectionAPI()->SetSkusCredential(credential); - } - bool& prevent_creation() { return GetBraveVPNConnectionAPI()->prevent_creation_; } @@ -341,8 +334,8 @@ class BraveVPNServiceTest : public testing::Test { } void OnGetSubscriberCredentialV12(const std::string& subscriber_credential, bool success) { - GetBraveVPNConnectionAPI()->OnGetSubscriberCredentialV12( - subscriber_credential, success); + service_->OnGetSubscriberCredentialV12(base::Time::Now(), + subscriber_credential, success); } void OnGetProfileCredentials(const std::string& profile_credential, bool success) { @@ -534,6 +527,11 @@ class BraveVPNServiceTest : public testing::Test { )"; } + void SetValidSubscriberCredential() { + SetSubscriberCredential(&local_pref_service_, "subscriber_credential", + base::Time::Now() + base::Seconds(10)); + } + std::string SetupTestingStoreForEnv(const std::string& env, bool active_subscription = true) { std::string domain = skus::GetDomain("vpn", env); @@ -662,10 +660,12 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { // Reached to purchased state when valid credential, region data // and timezone info. SetPurchasedState(env, PurchasedState::LOADING); - OnCredentialSummary(domain, R"({ "active": true } )"); + OnCredentialSummary( + domain, R"({ "active": true, "remaining_credential_count": 1 } )"); EXPECT_TRUE(regions().empty()); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); - OnPrepareCredentialsPresentation(domain, "credential=abcdefghijk"); + OnPrepareCredentialsPresentation( + domain, "credential=abcdefghijk; Expires=Wed, 21 Oct 2050 07:28:00 GMT"); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); OnFetchRegionList(false, GetRegionsData(), true); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); @@ -680,7 +680,8 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { // Treat not purchased when empty. SetPurchasedState(env, PurchasedState::LOADING); - OnPrepareCredentialsPresentation(domain, "credential="); + OnPrepareCredentialsPresentation( + domain, "credential=; Expires=Wed, 21 Oct 2050 07:28:00 GMT"); EXPECT_EQ(PurchasedState::NOT_PURCHASED, GetPurchasedStateSync()); // Treat failed when invalid. @@ -688,11 +689,10 @@ TEST_F(BraveVPNServiceTest, LoadPurchasedStateTest) { OnPrepareCredentialsPresentation(domain, ""); EXPECT_EQ(PurchasedState::FAILED, GetPurchasedStateSync()); - // Treat as purchased state early when service has region data already. - EXPECT_FALSE(regions().empty()); + // Treat failed when cookie doesn't have expired date. SetPurchasedState(env, PurchasedState::LOADING); OnPrepareCredentialsPresentation(domain, "credential=abcdefghijk"); - EXPECT_EQ(PurchasedState::PURCHASED, GetPurchasedStateSync()); + EXPECT_EQ(PurchasedState::FAILED, GetPurchasedStateSync()); } TEST_F(BraveVPNServiceTest, CancelConnectingTest) { @@ -734,12 +734,6 @@ TEST_F(BraveVPNServiceTest, CancelConnectingTest) { 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); @@ -774,9 +768,21 @@ TEST_F(BraveVPNServiceTest, SelectedRegionChangedUpdateTest) { loop.Run(); } +// Check SetSelectedRegion is called when default device region is set. +// We use default device region as an initial selected region. +TEST_F(BraveVPNServiceTest, SelectedRegionChangedUpdateWithDeviceRegionTest) { + TestBraveVPNServiceObserver observer; + AddObserver(observer.GetReceiver()); + + OnFetchRegionList(false, GetRegionsData(), true); + SetTestTimezone("Asia/Seoul"); + OnFetchTimezones(GetTimeZonesData(), true); + base::RunLoop loop; + observer.WaitSelectedRegionStateChange(loop.QuitClosure()); + loop.Run(); +} + TEST_F(BraveVPNServiceTest, ConnectionInfoTest) { - // Having skus_credential is pre-requisite before try connecting. - SetSkusCredential("test_credentials"); // Check valid connection info is set when valid hostname and profile // credential are fetched. connection_state() = ConnectionState::CONNECTING; @@ -1039,8 +1045,8 @@ TEST_F(BraveVPNServiceTest, CheckInitialPurchasedStateTest) { // Purchased state is not checked for fresh user. EXPECT_EQ(PurchasedState::NOT_PURCHASED, GetPurchasedStateSync()); - // Dirty region list prefs to pretend it's already cached. - local_pref_service_.SetList(prefs::kBraveVPNRegionList, {}); + // Set valid subscriber credential to pretend it's purchased user. + SetValidSubscriberCredential(); ResetVpnService(); EXPECT_EQ(PurchasedState::LOADING, GetPurchasedStateSync()); } @@ -1048,13 +1054,9 @@ TEST_F(BraveVPNServiceTest, CheckInitialPurchasedStateTest) { TEST_F(BraveVPNServiceTest, SubscribedCredentials) { std::string env = skus::GetDefaultEnvironment(); SetPurchasedState(env, PurchasedState::PURCHASED); - cancel_connecting() = false; - connection_state() = ConnectionState::CONNECTING; EXPECT_EQ(PurchasedState::PURCHASED, GetPurchasedStateSync()); OnGetSubscriberCredentialV12("Token No Longer Valid", false); EXPECT_EQ(PurchasedState::EXPIRED, GetPurchasedStateSync()); - EXPECT_FALSE(cancel_connecting()); - EXPECT_EQ(ConnectionState::CONNECT_FAILED, connection_state()); } #endif diff --git a/components/brave_vpn/brave_vpn_utils.cc b/components/brave_vpn/brave_vpn_utils.cc index 565bbe042b56..40d107b57d11 100644 --- a/components/brave_vpn/brave_vpn_utils.cc +++ b/components/brave_vpn/brave_vpn_utils.cc @@ -35,10 +35,10 @@ void RegisterVPNLocalStatePrefs(PrefRegistrySimple* registry) { registry->RegisterBooleanPref(prefs::kBraveVPNShowDNSPolicyWarningDialog, true); #endif - registry->RegisterStringPref(prefs::kBraveVPNEEnvironment, + registry->RegisterStringPref(prefs::kBraveVPNEnvironment, skus::GetDefaultEnvironment()); registry->RegisterDictionaryPref(prefs::kBraveVPNRootPref); - + registry->RegisterDictionaryPref(prefs::kBraveVPNSubscriberCredential); registry->RegisterBooleanPref(prefs::kBraveVPNLocalStateMigrated, false); } diff --git a/components/brave_vpn/mojom/brave_vpn.mojom b/components/brave_vpn/mojom/brave_vpn.mojom index 5f285948e752..2a9666f9c4ca 100644 --- a/components/brave_vpn/mojom/brave_vpn.mojom +++ b/components/brave_vpn/mojom/brave_vpn.mojom @@ -54,10 +54,6 @@ interface ServiceHandler { [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); diff --git a/components/brave_vpn/pref_names.h b/components/brave_vpn/pref_names.h index 64d8bea949d8..39bf2c15a9e4 100644 --- a/components/brave_vpn/pref_names.h +++ b/components/brave_vpn/pref_names.h @@ -20,7 +20,10 @@ constexpr char kBraveVPNSelectedRegion[] = "brave.brave_vpn.selected_region_name"; constexpr char kBraveVPNShowDNSPolicyWarningDialog[] = "brave.brave_vpn.show_dns_policy_warning_dialog"; -constexpr char kBraveVPNEEnvironment[] = "brave.brave_vpn.env"; +constexpr char kBraveVPNEnvironment[] = "brave.brave_vpn.env"; +// Dict that has subscriber credential its expiration date. +constexpr char kBraveVPNSubscriberCredential[] = + "brave.brave_vpn.subscriber_credential"; #if BUILDFLAG(IS_ANDROID) extern const char kBraveVPNPurchaseTokenAndroid[]; diff --git a/components/brave_vpn/resources/panel/stories/mock-data/api.ts b/components/brave_vpn/resources/panel/stories/mock-data/api.ts index 6d13f803b74c..169a7199f62d 100644 --- a/components/brave_vpn/resources/panel/stories/mock-data/api.ts +++ b/components/brave_vpn/resources/panel/stories/mock-data/api.ts @@ -19,7 +19,6 @@ BraveVPN.setPanelBrowserApiForTesting({ disconnect: doNothing, loadPurchasedState: doNothing, getAllRegions: () => Promise.resolve({ regions: mockRegionList }), - getDeviceRegion: () => Promise.resolve({ deviceRegion: mockRegionList[0] }), getSelectedRegion: () => Promise.resolve({ currentRegion: mockRegionList[1] }), setSelectedRegion: doNothing, getProductUrls: () => Promise.resolve({ diff --git a/components/brave_vpn/switches.h b/components/brave_vpn/switches.h index cc4dda133eb0..7c41d26e8351 100644 --- a/components/brave_vpn/switches.h +++ b/components/brave_vpn/switches.h @@ -12,7 +12,6 @@ namespace switches { // Use for simulation instead of calling os platform apis. constexpr char kBraveVPNSimulation[] = "brave-vpn-simulate"; -constexpr char kBraveVPNTestMonthlyPass[] = "brave-vpn-test-monthly-pass"; } // namespace switches