| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "components/ntp_snippets/breaking_news/subscription_manager_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/strings/stringprintf.h" |
| #include "components/ntp_snippets/breaking_news/breaking_news_metrics.h" |
| #include "components/ntp_snippets/breaking_news/subscription_json_request.h" |
| #include "components/ntp_snippets/features.h" |
| #include "components/ntp_snippets/ntp_snippets_constants.h" |
| #include "components/ntp_snippets/pref_names.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/variations/service/variations_service.h" |
| #include "net/base/url_util.h" |
| #include "services/identity/public/cpp/primary_account_access_token_fetcher.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| |
| namespace ntp_snippets { |
| |
| using internal::SubscriptionJsonRequest; |
| |
| namespace { |
| |
| const char kApiKeyParamName[] = "key"; |
| const char kAuthorizationRequestHeaderFormat[] = "Bearer %s"; |
| |
| } // namespace |
| |
| SubscriptionManagerImpl::SubscriptionManagerImpl( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| PrefService* pref_service, |
| variations::VariationsService* variations_service, |
| identity::IdentityManager* identity_manager, |
| const std::string& locale, |
| const std::string& api_key, |
| const GURL& subscribe_url, |
| const GURL& unsubscribe_url) |
| : url_loader_factory_(std::move(url_loader_factory)), |
| pref_service_(pref_service), |
| variations_service_(variations_service), |
| identity_manager_(identity_manager), |
| locale_(locale), |
| api_key_(api_key), |
| subscribe_url_(subscribe_url), |
| unsubscribe_url_(unsubscribe_url) { |
| identity_manager_->AddObserver(this); |
| } |
| |
| SubscriptionManagerImpl::~SubscriptionManagerImpl() { |
| identity_manager_->RemoveObserver(this); |
| } |
| |
| void SubscriptionManagerImpl::Subscribe(const std::string& subscription_token) { |
| // If there is a request in flight, cancel it. |
| if (request_) { |
| request_ = nullptr; |
| } |
| if (identity_manager_->HasPrimaryAccount()) { |
| StartAccessTokenRequest(subscription_token); |
| } else { |
| SubscribeInternal(subscription_token, /*access_token=*/std::string()); |
| } |
| } |
| |
| void SubscriptionManagerImpl::SubscribeInternal( |
| const std::string& subscription_token, |
| const std::string& access_token) { |
| SubscriptionJsonRequest::Builder builder; |
| builder.SetToken(subscription_token).SetUrlLoaderFactory(url_loader_factory_); |
| |
| if (!access_token.empty()) { |
| builder.SetUrl(subscribe_url_); |
| builder.SetAuthenticationHeader(base::StringPrintf( |
| kAuthorizationRequestHeaderFormat, access_token.c_str())); |
| } else { |
| // When not providing OAuth token, we need to pass the Google API key. |
| builder.SetUrl( |
| net::AppendQueryParameter(subscribe_url_, kApiKeyParamName, api_key_)); |
| } |
| |
| builder.SetLocale(locale_); |
| builder.SetCountryCode(variations_service_ |
| ? variations_service_->GetStoredPermanentCountry() |
| : ""); |
| |
| request_ = builder.Build(); |
| request_->Start(base::BindOnce(&SubscriptionManagerImpl::DidSubscribe, |
| base::Unretained(this), subscription_token, |
| /*is_authenticated=*/!access_token.empty())); |
| } |
| |
| void SubscriptionManagerImpl::StartAccessTokenRequest( |
| const std::string& subscription_token) { |
| // If there is already an ongoing token request, destroy it. |
| if (access_token_fetcher_) { |
| access_token_fetcher_ = nullptr; |
| } |
| |
| identity::ScopeSet scopes = {kContentSuggestionsApiScope}; |
| access_token_fetcher_ = std::make_unique< |
| identity::PrimaryAccountAccessTokenFetcher>( |
| "ntp_snippets", identity_manager_, scopes, |
| base::BindOnce(&SubscriptionManagerImpl::AccessTokenFetchFinished, |
| base::Unretained(this), subscription_token), |
| identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable); |
| } |
| |
| void SubscriptionManagerImpl::AccessTokenFetchFinished( |
| const std::string& subscription_token, |
| GoogleServiceAuthError error, |
| identity::AccessTokenInfo access_token_info) { |
| access_token_fetcher_.reset(); |
| |
| if (error.state() != GoogleServiceAuthError::NONE) { |
| // In case of error, we will retry on next Chrome restart. |
| return; |
| } |
| DCHECK(!access_token_info.token.empty()); |
| SubscribeInternal(subscription_token, access_token_info.token); |
| } |
| |
| void SubscriptionManagerImpl::DidSubscribe( |
| const std::string& subscription_token, |
| bool is_authenticated, |
| const Status& status) { |
| metrics::OnSubscriptionRequestCompleted(status); |
| |
| // Delete the request only after we leave this method (which is called from |
| // the request itself). |
| std::unique_ptr<internal::SubscriptionJsonRequest> request_deleter( |
| std::move(request_)); |
| |
| switch (status.code) { |
| case StatusCode::SUCCESS: |
| // In case of successful subscription, store the same data used for |
| // subscription in order to be able to resubscribe in case of data |
| // change. |
| // TODO(mamir): Store region and language. |
| pref_service_->SetString(prefs::kBreakingNewsSubscriptionDataToken, |
| subscription_token); |
| pref_service_->SetBoolean( |
| prefs::kBreakingNewsSubscriptionDataIsAuthenticated, |
| is_authenticated); |
| break; |
| default: |
| // TODO(mamir): Handle failure. |
| break; |
| } |
| } |
| |
| void SubscriptionManagerImpl::Unsubscribe() { |
| std::string token = |
| pref_service_->GetString(prefs::kBreakingNewsSubscriptionDataToken); |
| ResubscribeInternal(/*old_token=*/token, /*new_token=*/std::string()); |
| } |
| |
| void SubscriptionManagerImpl::ResubscribeInternal( |
| const std::string& old_token, |
| const std::string& new_token) { |
| // If there is an request in flight, cancel it. |
| if (request_) { |
| request_ = nullptr; |
| } |
| |
| SubscriptionJsonRequest::Builder builder; |
| builder.SetToken(old_token).SetUrlLoaderFactory(url_loader_factory_); |
| builder.SetUrl( |
| net::AppendQueryParameter(unsubscribe_url_, kApiKeyParamName, api_key_)); |
| |
| request_ = builder.Build(); |
| request_->Start(base::BindOnce(&SubscriptionManagerImpl::DidUnsubscribe, |
| base::Unretained(this), new_token)); |
| } |
| |
| bool SubscriptionManagerImpl::IsSubscribed() { |
| std::string subscription_token = |
| pref_service_->GetString(prefs::kBreakingNewsSubscriptionDataToken); |
| return !subscription_token.empty(); |
| } |
| |
| bool SubscriptionManagerImpl::NeedsToResubscribe() { |
| // Check if authentication state changed after subscription. |
| bool is_auth_subscribe = pref_service_->GetBoolean( |
| prefs::kBreakingNewsSubscriptionDataIsAuthenticated); |
| bool is_authenticated = identity_manager_->HasPrimaryAccount(); |
| return is_auth_subscribe != is_authenticated; |
| } |
| |
| void SubscriptionManagerImpl::Resubscribe(const std::string& new_token) { |
| std::string old_token = |
| pref_service_->GetString(prefs::kBreakingNewsSubscriptionDataToken); |
| if (old_token == new_token) { |
| // If the token didn't change, subscribe directly. The server handles the |
| // unsubscription if previous subscriptions exists. |
| Subscribe(old_token); |
| } else { |
| ResubscribeInternal(old_token, new_token); |
| } |
| } |
| |
| void SubscriptionManagerImpl::DidUnsubscribe(const std::string& new_token, |
| const Status& status) { |
| metrics::OnUnsubscriptionRequestCompleted(status); |
| |
| // Delete the request only after we leave this method (which is called from |
| // the request itself). |
| std::unique_ptr<internal::SubscriptionJsonRequest> request_deleter( |
| std::move(request_)); |
| |
| switch (status.code) { |
| case StatusCode::SUCCESS: |
| // In case of successful unsubscription, clear the previously stored data. |
| // TODO(mamir): Clear stored region and language. |
| pref_service_->ClearPref(prefs::kBreakingNewsSubscriptionDataToken); |
| pref_service_->ClearPref( |
| prefs::kBreakingNewsSubscriptionDataIsAuthenticated); |
| if (!new_token.empty()) { |
| Subscribe(new_token); |
| } |
| break; |
| default: |
| // TODO(mamir): Handle failure. |
| break; |
| } |
| } |
| |
| void SubscriptionManagerImpl::OnPrimaryAccountSet( |
| const CoreAccountInfo& account_info) { |
| SigninStatusChanged(); |
| } |
| |
| void SubscriptionManagerImpl::OnPrimaryAccountCleared( |
| const AccountInfo& account_info) { |
| SigninStatusChanged(); |
| } |
| |
| void SubscriptionManagerImpl::SigninStatusChanged() { |
| // If subscribed already, resubscribe. |
| if (IsSubscribed()) { |
| if (request_) { |
| request_ = nullptr; |
| } |
| std::string token = |
| pref_service_->GetString(prefs::kBreakingNewsSubscriptionDataToken); |
| Subscribe(token); |
| } |
| } |
| |
| // static |
| void SubscriptionManagerImpl::RegisterProfilePrefs( |
| PrefRegistrySimple* registry) { |
| registry->RegisterStringPref(prefs::kBreakingNewsSubscriptionDataToken, |
| std::string()); |
| registry->RegisterBooleanPref( |
| prefs::kBreakingNewsSubscriptionDataIsAuthenticated, false); |
| } |
| |
| // TODO(vitaliii): Add a test to ensure that this clears everything. |
| // static |
| void SubscriptionManagerImpl::ClearProfilePrefs(PrefService* pref_service) { |
| pref_service->ClearPref(prefs::kBreakingNewsSubscriptionDataToken); |
| pref_service->ClearPref(prefs::kBreakingNewsSubscriptionDataIsAuthenticated); |
| } |
| |
| } // namespace ntp_snippets |