Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1089)

Side by Side Diff: components/ntp_snippets/ntp_snippets_fetcher.cc

Issue 1922083004: Allow fetching personalized snippets from ChromeReader. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Yet another unittest fix Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/ntp_snippets/ntp_snippets_fetcher.h" 5 #include "components/ntp_snippets/ntp_snippets_fetcher.h"
6 6
7 #include <stdlib.h>
8
7 #include "base/command_line.h" 9 #include "base/command_line.h"
8 #include "base/files/file_path.h" 10 #include "base/files/file_path.h"
9 #include "base/files/file_util.h" 11 #include "base/files/file_util.h"
10 #include "base/metrics/histogram_macros.h" 12 #include "base/metrics/histogram_macros.h"
11 #include "base/metrics/sparse_histogram.h" 13 #include "base/metrics/sparse_histogram.h"
12 #include "base/path_service.h" 14 #include "base/path_service.h"
13 #include "base/strings/string_number_conversions.h" 15 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h" 16 #include "base/strings/string_util.h"
15 #include "base/strings/stringprintf.h" 17 #include "base/strings/stringprintf.h"
16 #include "base/time/default_tick_clock.h" 18 #include "base/time/default_tick_clock.h"
17 #include "base/values.h" 19 #include "base/values.h"
18 #include "components/data_use_measurement/core/data_use_user_data.h" 20 #include "components/data_use_measurement/core/data_use_user_data.h"
21 #include "components/ntp_snippets/ntp_snippets_constants.h"
19 #include "components/ntp_snippets/switches.h" 22 #include "components/ntp_snippets/switches.h"
23 #include "components/signin/core/browser/profile_oauth2_token_service.h"
24 #include "components/signin/core/browser/signin_manager.h"
25 #include "components/signin/core/browser/signin_manager_base.h"
26 #include "components/variations/variations_associated_data.h"
20 #include "google_apis/google_api_keys.h" 27 #include "google_apis/google_api_keys.h"
21 #include "net/base/load_flags.h" 28 #include "net/base/load_flags.h"
22 #include "net/http/http_request_headers.h" 29 #include "net/http/http_request_headers.h"
23 #include "net/http/http_response_headers.h" 30 #include "net/http/http_response_headers.h"
24 #include "net/http/http_status_code.h" 31 #include "net/http/http_status_code.h"
25 #include "net/url_request/url_fetcher.h" 32 #include "net/url_request/url_fetcher.h"
33 #include "third_party/icu/source/common/unicode/uloc.h"
34 #include "third_party/icu/source/common/unicode/utypes.h"
26 35
27 using net::URLFetcher; 36 using net::URLFetcher;
28 using net::URLRequestContextGetter; 37 using net::URLRequestContextGetter;
29 using net::HttpRequestHeaders; 38 using net::HttpRequestHeaders;
30 using net::URLRequestStatus; 39 using net::URLRequestStatus;
31 40
32 namespace ntp_snippets { 41 namespace ntp_snippets {
33 42
34 namespace { 43 namespace {
35 44
36 const char kContentSnippetsServerFormat[] = 45 const char kApiScope[] = "https://www.googleapis.com/auth/webhistory";
37 "https://chromereader-pa.googleapis.com/v1/fetch?key=%s"; 46 const char kSnippetsServer[] =
47 "https://chromereader-pa.googleapis.com/v1/fetch";
48 const char kSnippetsServerNonAuthorizedFormat[] = "%s?key=%s";
49 const char kAuthorizationRequestHeaderFormat[] = "Bearer %s";
50
51 // Variation parameter for the variant of fetching to use.
52 const char kVariantName[] = "fetching_variant";
53
54 // Constants listing possible values of the "fetching_variant" parameter.
55 const char kVariantRestrictedString[] = "restricted";
56 const char kVariantPersonalizedString[] = "personalized";
57 const char kVariantRestrictedPersonalizedString[] = "restricted_personalized";
38 58
39 const char kRequestParameterFormat[] = 59 const char kRequestParameterFormat[] =
40 "{" 60 "{"
41 " \"response_detail_level\": \"STANDARD\"," 61 " \"response_detail_level\": \"STANDARD\","
62 "%s" // If authenticated - an obfuscated Gaia ID will be inserted here.
42 " \"advanced_options\": {" 63 " \"advanced_options\": {"
43 " \"local_scoring_params\": {" 64 " \"local_scoring_params\": {"
44 " \"content_params\": {" 65 " \"content_params\": {"
45 " \"only_return_personalized_results\": false" 66 " \"only_return_personalized_results\": false"
67 "%s" // If authenticated - user segment (lang code) will be inserted here.
46 " }," 68 " },"
47 " \"content_restricts\": {" 69 " \"content_restricts\": {"
48 " \"type\": \"METADATA\"," 70 " \"type\": \"METADATA\","
49 " \"value\": \"TITLE\"" 71 " \"value\": \"TITLE\""
50 " }," 72 " },"
51 " \"content_restricts\": {" 73 " \"content_restricts\": {"
52 " \"type\": \"METADATA\"," 74 " \"type\": \"METADATA\","
53 " \"value\": \"SNIPPET\"" 75 " \"value\": \"SNIPPET\""
54 " }," 76 " },"
55 " \"content_restricts\": {" 77 " \"content_restricts\": {"
56 " \"type\": \"METADATA\"," 78 " \"type\": \"METADATA\","
57 " \"value\": \"THUMBNAIL\"" 79 " \"value\": \"THUMBNAIL\""
58 " }" 80 " }"
59 "%s" 81 "%s" // If host restricted - host restrictions will be inserted here.
60 " }," 82 " },"
61 " \"global_scoring_params\": {" 83 " \"global_scoring_params\": {"
62 " \"num_to_return\": %i" 84 " \"num_to_return\": %i,"
85 " \"sort_type\": 1"
63 " }" 86 " }"
64 " }" 87 " }"
65 "}"; 88 "}";
66 89
90 const char kGaiaIdFormat[] = " \"obfuscated_gaia_id\": \"%s\",";
91 const char kUserSegmentFormat[] = " ,\"user_segment\": \"%s\"";
67 const char kHostRestrictFormat[] = 92 const char kHostRestrictFormat[] =
68 " ,\"content_selectors\": {" 93 " ,\"content_selectors\": {"
69 " \"type\": \"HOST_RESTRICT\"," 94 " \"type\": \"HOST_RESTRICT\","
70 " \"value\": \"%s\"" 95 " \"value\": \"%s\""
71 " }"; 96 " }";
72 97
73 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) { 98 std::string FetchResultToString(NTPSnippetsFetcher::FetchResult result) {
74 switch (result) { 99 switch (result) {
75 case NTPSnippetsFetcher::FetchResult::SUCCESS: 100 case NTPSnippetsFetcher::FetchResult::SUCCESS:
76 return "OK"; 101 return "OK";
(...skipping 10 matching lines...) Expand all
87 case NTPSnippetsFetcher::FetchResult::RESULT_MAX: 112 case NTPSnippetsFetcher::FetchResult::RESULT_MAX:
88 break; 113 break;
89 } 114 }
90 NOTREACHED(); 115 NOTREACHED();
91 return "Unknown error"; 116 return "Unknown error";
92 } 117 }
93 118
94 } // namespace 119 } // namespace
95 120
96 NTPSnippetsFetcher::NTPSnippetsFetcher( 121 NTPSnippetsFetcher::NTPSnippetsFetcher(
122 SigninManagerBase* signin_manager,
123 OAuth2TokenService* token_service,
97 scoped_refptr<URLRequestContextGetter> url_request_context_getter, 124 scoped_refptr<URLRequestContextGetter> url_request_context_getter,
98 const ParseJSONCallback& parse_json_callback, 125 const ParseJSONCallback& parse_json_callback,
99 bool is_stable_channel) 126 bool is_stable_channel)
100 : url_request_context_getter_(url_request_context_getter), 127 : OAuth2TokenService::Consumer("ntp_snippets"),
128 signin_manager_(signin_manager),
129 token_service_(token_service),
130 waiting_for_refresh_token_(false),
131 url_request_context_getter_(url_request_context_getter),
101 parse_json_callback_(parse_json_callback), 132 parse_json_callback_(parse_json_callback),
102 is_stable_channel_(is_stable_channel), 133 is_stable_channel_(is_stable_channel),
103 tick_clock_(new base::DefaultTickClock()), 134 tick_clock_(new base::DefaultTickClock()),
104 weak_ptr_factory_(this) {} 135 weak_ptr_factory_(this) {
136 // Parse the variation parameters and set the defaults if missing.
137 std::string variant = variations::GetVariationParamValue(
138 ntp_snippets::kStudyName, kVariantName);
139 if (variant == kVariantRestrictedString) {
140 variant_ = Variant::kRestricted;
141 } else if (variant == kVariantPersonalizedString) {
142 variant_ = Variant::kPersonalized;
143 } else {
144 variant_ = Variant::kRestrictedPersonalized;
145 LOG_IF(WARNING,
146 !variant.empty() && variant != kVariantRestrictedPersonalizedString)
147 << "Unknown fetching variant provided: " << variant;
148 }
149 }
105 150
106 NTPSnippetsFetcher::~NTPSnippetsFetcher() {} 151 NTPSnippetsFetcher::~NTPSnippetsFetcher() {
152 if (waiting_for_refresh_token_)
153 token_service_->RemoveObserver(this);
154 }
107 155
108 void NTPSnippetsFetcher::SetCallback( 156 void NTPSnippetsFetcher::SetCallback(
109 const SnippetsAvailableCallback& callback) { 157 const SnippetsAvailableCallback& callback) {
110 snippets_available_callback_ = callback; 158 snippets_available_callback_ = callback;
111 } 159 }
112 160
113 void NTPSnippetsFetcher::FetchSnippetsFromHosts( 161 void NTPSnippetsFetcher::FetchSnippetsFromHosts(
114 const std::set<std::string>& hosts, int count) { 162 const std::set<std::string>& hosts,
115 std::string host_restricts; 163 const std::string& language_code,
164 int count) {
165 hosts_ = hosts;
116 fetch_start_time_ = tick_clock_->NowTicks(); 166 fetch_start_time_ = tick_clock_->NowTicks();
117 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( 167
118 switches::kDontRestrict)) { 168 if (UseHostRestriction() && hosts_.empty()) {
119 if (hosts.empty()) { 169 FetchFinished(OptionalSnippets(), FetchResult::EMPTY_HOSTS,
120 FetchFinished(OptionalSnippets(), FetchResult::EMPTY_HOSTS, 170 /*extra_message=*/std::string());
121 /*extra_message=*/std::string()); 171 return;
122 return; 172 }
173
174 // Translate the BCP 47 |language_code| into a posix locale string.
175 char locale[ULOC_FULLNAME_CAPACITY];
176 UErrorCode error;
177 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY,
178 nullptr, &error);
179 DLOG_IF(WARNING, U_ZERO_ERROR != error)
180 << "Error in translating language code to a locale string: " << error;
181 locale_ = locale;
182
183 count_to_fetch_ = count;
184
185 bool use_authentication = UseAuthentication();
186
187 if (use_authentication && signin_manager_->IsAuthenticated()) {
188 // Signed-in: get OAuth token --> fetch snippets.
189 StartTokenRequest();
190 } else if (use_authentication && signin_manager_->AuthInProgress()) {
191 // Currently signing in: wait for auth to finish (the refresh token) -->
192 // get OAuth token --> fetch snippets.
193 if (!waiting_for_refresh_token_) {
194 // Wait until we get a refresh token.
195 waiting_for_refresh_token_ = true;
196 token_service_->AddObserver(this);
123 } 197 }
124 for (const std::string& host : hosts) 198 } else {
125 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str()); 199 // Not signed in: fetch snippets (without authentication).
200 FetchSnippetsNonAuthenticated();
126 } 201 }
127 const std::string& key = is_stable_channel_ 202 }
128 ? google_apis::GetAPIKey() 203
129 : google_apis::GetNonStableAPIKey(); 204 void NTPSnippetsFetcher::FetchSnippetsImpl(const GURL& url,
130 std::string url = 205 const std::string& auth_header,
131 base::StringPrintf(kContentSnippetsServerFormat, key.c_str()); 206 const std::string& request) {
132 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this); 207 url_fetcher_ = URLFetcher::Create(url, URLFetcher::POST, this);
208
133 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); 209 url_fetcher_->SetRequestContext(url_request_context_getter_.get());
134 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 210 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
135 net::LOAD_DO_NOT_SAVE_COOKIES); 211 net::LOAD_DO_NOT_SAVE_COOKIES);
212
136 data_use_measurement::DataUseUserData::AttachToFetcher( 213 data_use_measurement::DataUseUserData::AttachToFetcher(
137 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); 214 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS);
215
138 HttpRequestHeaders headers; 216 HttpRequestHeaders headers;
217 if (!auth_header.empty())
218 headers.SetHeader("Authorization", auth_header);
139 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); 219 headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
140 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); 220 url_fetcher_->SetExtraRequestHeaders(headers.ToString());
141 url_fetcher_->SetUploadData("application/json", 221 url_fetcher_->SetUploadData("application/json", request);
142 base::StringPrintf(kRequestParameterFormat,
143 host_restricts.c_str(),
144 count));
145
146 // Fetchers are sometimes cancelled because a network change was detected. 222 // Fetchers are sometimes cancelled because a network change was detected.
147 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 223 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
148 // Try to make fetching the files bit more robust even with poor connection. 224 // Try to make fetching the files bit more robust even with poor connection.
149 url_fetcher_->SetMaxRetriesOn5xx(3); 225 url_fetcher_->SetMaxRetriesOn5xx(3);
150 url_fetcher_->Start(); 226 url_fetcher_->Start();
151 } 227 }
152 228
229 std::string NTPSnippetsFetcher::GetHostRestricts() const {
230 std::string host_restricts;
231 if (UseHostRestriction()) {
232 for (const std::string& host : hosts_)
233 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
234 }
235 return host_restricts;
236 }
237
238 bool NTPSnippetsFetcher::UseHostRestriction() const {
239 return (variant_ == Variant::kRestricted ||
240 variant_ == Variant::kRestrictedPersonalized) &&
241 !base::CommandLine::ForCurrentProcess()->HasSwitch(
242 switches::kDontRestrict);
243 }
244
245 bool NTPSnippetsFetcher::UseAuthentication() const {
246 return (variant_ == Variant::kPersonalized ||
247 variant_ == Variant::kRestrictedPersonalized);
248 }
249
250 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() {
251 // When not providing OAuth token, we need to pass the Google API key.
252 const std::string& key = is_stable_channel_
253 ? google_apis::GetAPIKey()
254 : google_apis::GetNonStableAPIKey();
255 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat,
256 kSnippetsServer, key.c_str()));
257
258 FetchSnippetsImpl(
259 url, std::string(),
260 base::StringPrintf(kRequestParameterFormat, "", "",
261 GetHostRestricts().c_str(), count_to_fetch_));
262 }
263
264 void NTPSnippetsFetcher::FetchSnippetsAuthenticated(
265 const std::string& account_id,
266 const std::string& oauth_access_token) {
267 std::string auth = base::StringPrintf(kGaiaIdFormat, account_id.c_str());
268 std::string user_segment =
269 base::StringPrintf(kUserSegmentFormat, locale_.c_str());
270
271 FetchSnippetsImpl(
272 GURL(kSnippetsServer),
273 base::StringPrintf(kAuthorizationRequestHeaderFormat,
274 oauth_access_token.c_str()),
275 base::StringPrintf(kRequestParameterFormat, auth.c_str(),
276 user_segment.c_str(), GetHostRestricts().c_str(),
277 count_to_fetch_));
278 }
279
280 void NTPSnippetsFetcher::StartTokenRequest() {
281 OAuth2TokenService::ScopeSet scopes;
282 scopes.insert(kApiScope);
283 oauth_request_ = token_service_->StartRequest(
284 signin_manager_->GetAuthenticatedAccountId(), scopes, this);
285 }
286
287 ////////////////////////////////////////////////////////////////////////////////
288 // OAuth2TokenService::Consumer overrides
289 void NTPSnippetsFetcher::OnGetTokenSuccess(
290 const OAuth2TokenService::Request* request,
291 const std::string& access_token,
292 const base::Time& expiration_time) {
293 // Delete the request after we leave this method.
294 std::unique_ptr<OAuth2TokenService::Request> oauth_request(
295 std::move(oauth_request_));
296 DCHECK_EQ(oauth_request.get(), request)
297 << "Got tokens from some previous request";
298
299 FetchSnippetsAuthenticated(oauth_request->GetAccountId(), access_token);
300 }
301
302 void NTPSnippetsFetcher::OnGetTokenFailure(
303 const OAuth2TokenService::Request* request,
304 const GoogleServiceAuthError& error) {
305 oauth_request_.reset();
306 DLOG(ERROR) << "Unable to get token: " << error.ToString()
307 << " - fetching the snippets without authentication.";
308
309 // Fallback to fetching non-authenticated tokens.
310 FetchSnippetsNonAuthenticated();
311 }
312
313 ////////////////////////////////////////////////////////////////////////////////
314 // OAuth2TokenService::Observer overrides
315 void NTPSnippetsFetcher::OnRefreshTokenAvailable(
316 const std::string& account_id) {
317 // Only react on tokens for the account the user has signed in with.
318 if (account_id != signin_manager_->GetAuthenticatedAccountId())
319 return;
320
321 token_service_->RemoveObserver(this);
322 waiting_for_refresh_token_ = false;
323 StartTokenRequest();
324 }
325
153 //////////////////////////////////////////////////////////////////////////////// 326 ////////////////////////////////////////////////////////////////////////////////
154 // URLFetcherDelegate overrides 327 // URLFetcherDelegate overrides
155 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { 328 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
156 DCHECK_EQ(url_fetcher_.get(), source); 329 DCHECK_EQ(url_fetcher_.get(), source);
157 330
158 const URLRequestStatus& status = source->GetStatus(); 331 const URLRequestStatus& status = source->GetStatus();
159 332
160 UMA_HISTOGRAM_SPARSE_SLOWLY( 333 UMA_HISTOGRAM_SPARSE_SLOWLY(
161 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", 334 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode",
162 status.is_success() ? source->GetResponseCode() : status.error()); 335 status.is_success() ? source->GetResponseCode() : status.error());
163 336
164 if (!status.is_success()) { 337 if (!status.is_success()) {
165 FetchFinished(OptionalSnippets(), FetchResult::URL_REQUEST_STATUS_ERROR, 338 FetchFinished(OptionalSnippets(), FetchResult::URL_REQUEST_STATUS_ERROR,
166 /*extra_message=*/base::StringPrintf(" %d", status.error())); 339 /*extra_message=*/base::StringPrintf(" %d", status.error()));
167 } else if (source->GetResponseCode() != net::HTTP_OK) { 340 } else if (source->GetResponseCode() != net::HTTP_OK) {
341 // TODO(jkrcal): https://crbug.com/609084
342 // We need to deal with the edge case again where the auth
343 // token expires just before we send the request (in which case we need to
344 // fetch a new auth token). We should extract that into a common class
345 // instead of adding it to every single class that uses auth tokens.
168 FetchFinished( 346 FetchFinished(
169 OptionalSnippets(), FetchResult::HTTP_ERROR, 347 OptionalSnippets(), FetchResult::HTTP_ERROR,
170 /*extra_message=*/base::StringPrintf(" %d", source->GetResponseCode())); 348 /*extra_message=*/base::StringPrintf(" %d", source->GetResponseCode()));
171 } else { 349 } else {
172 bool stores_result_to_string = source->GetResponseAsString( 350 bool stores_result_to_string = source->GetResponseAsString(
173 &last_fetch_json_); 351 &last_fetch_json_);
174 DCHECK(stores_result_to_string); 352 DCHECK(stores_result_to_string);
175 353
176 parse_json_callback_.Run( 354 parse_json_callback_.Run(
177 last_fetch_json_, 355 last_fetch_json_,
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
217 tick_clock_->NowTicks() - fetch_start_time_); 395 tick_clock_->NowTicks() - fetch_start_time_);
218 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult", 396 UMA_HISTOGRAM_ENUMERATION("NewTabPage.Snippets.FetchResult",
219 static_cast<int>(result), 397 static_cast<int>(result),
220 static_cast<int>(FetchResult::RESULT_MAX)); 398 static_cast<int>(FetchResult::RESULT_MAX));
221 399
222 if (!snippets_available_callback_.is_null()) 400 if (!snippets_available_callback_.is_null())
223 snippets_available_callback_.Run(std::move(snippets)); 401 snippets_available_callback_.Run(std::move(snippets));
224 } 402 }
225 403
226 } // namespace ntp_snippets 404 } // namespace ntp_snippets
OLDNEW
« no previous file with comments | « components/ntp_snippets/ntp_snippets_fetcher.h ('k') | components/ntp_snippets/ntp_snippets_fetcher_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698