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

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

Powered by Google App Engine
This is Rietveld 408576698