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

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 #4 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 if (!variant.empty() && variant != kVariantRestrictedPersonalizedString)
128 LOG(WARNING) << "Unknown fetching variant provided: " << variant;
129 }
130 }
87 131
88 NTPSnippetsFetcher::~NTPSnippetsFetcher() {} 132 NTPSnippetsFetcher::~NTPSnippetsFetcher() {
133 if (waiting_for_refresh_token_)
134 token_service_->RemoveObserver(this);
135 }
89 136
90 void NTPSnippetsFetcher::SetCallback( 137 void NTPSnippetsFetcher::SetCallback(
91 const SnippetsAvailableCallback& callback) { 138 const SnippetsAvailableCallback& callback) {
92 snippets_available_callback_ = callback; 139 snippets_available_callback_ = callback;
93 } 140 }
94 141
95 void NTPSnippetsFetcher::FetchSnippetsFromHosts( 142 void NTPSnippetsFetcher::FetchSnippetsFromHosts(
96 const std::set<std::string>& hosts, int count) { 143 const std::set<std::string>& hosts,
97 std::string host_restricts; 144 const std::string& language_code,
98 if (!base::CommandLine::ForCurrentProcess()->HasSwitch( 145 int count) {
99 switches::kDontRestrict)) { 146 hosts_ = hosts;
100 if (hosts.empty()) { 147
101 if (!snippets_available_callback_.is_null()) { 148 if (UseHostRestriction() && hosts_.empty()) {
102 snippets_available_callback_.Run(NTPSnippet::PtrVector(), 149 if (!snippets_available_callback_.is_null()) {
103 kStatusMessageEmptyHosts); 150 snippets_available_callback_.Run(NTPSnippet::PtrVector(),
104 } 151 kStatusMessageEmptyHosts);
105 return;
106 } 152 }
107 for (const std::string& host : hosts) 153 return;
108 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
109 } 154 }
110 const std::string& key = is_stable_channel_ 155
111 ? google_apis::GetAPIKey() 156 // Translate the BCP 47 |language_code| into a posix locale string.
112 : google_apis::GetNonStableAPIKey(); 157 char locale[ULOC_FULLNAME_CAPACITY];
113 std::string url = 158 UErrorCode error;
114 base::StringPrintf(kContentSnippetsServerFormat, key.c_str()); 159 uloc_forLanguageTag(language_code.c_str(), locale, ULOC_FULLNAME_CAPACITY,
115 url_fetcher_ = URLFetcher::Create(GURL(url), URLFetcher::POST, this); 160 nullptr, &error);
161 DLOG_IF(WARNING, U_ZERO_ERROR != error) <<
162 "Error in translating language code to a locale string: " << error;
163 locale_ = locale;
164
165 count_to_fetch_ = count;
166
167 bool use_authentication = UseAuthentication();
168
169 if (use_authentication && signin_manager_->IsAuthenticated()) {
170 // Signed-in: get OAuth token --> fetch snippets.
171 StartTokenRequest();
172 } else if (use_authentication && signin_manager_->AuthInProgress()) {
173 // Currently signing in: wait for auth to finish (the refresh token) -->
174 // get OAuth token --> fetch snippets.
175 if (!waiting_for_refresh_token_) {
176 // Wait until we get a refresh token.
177 waiting_for_refresh_token_ = true;
178 token_service_->AddObserver(this);
179 }
180 } else {
181 // Not signed in: fetch snippets (without authentication).
182 FetchSnippetsNonAuthenticated();
183 }
184 }
185
186 void NTPSnippetsFetcher::FetchSnippetsImpl(const GURL& url,
187 const std::string& auth_header,
188 const std::string& request) {
189 url_fetcher_ = URLFetcher::Create(url, URLFetcher::POST, this);
190
116 url_fetcher_->SetRequestContext(url_request_context_getter_.get()); 191 url_fetcher_->SetRequestContext(url_request_context_getter_.get());
117 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 192 url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
118 net::LOAD_DO_NOT_SAVE_COOKIES); 193 net::LOAD_DO_NOT_SAVE_COOKIES);
194
119 data_use_measurement::DataUseUserData::AttachToFetcher( 195 data_use_measurement::DataUseUserData::AttachToFetcher(
120 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS); 196 url_fetcher_.get(), data_use_measurement::DataUseUserData::NTP_SNIPPETS);
197
121 HttpRequestHeaders headers; 198 HttpRequestHeaders headers;
199 if (!auth_header.empty())
200 headers.SetHeader("Authorization", auth_header);
122 headers.SetHeader("Content-Type", "application/json; charset=UTF-8"); 201 headers.SetHeader("Content-Type", "application/json; charset=UTF-8");
123 url_fetcher_->SetExtraRequestHeaders(headers.ToString()); 202 url_fetcher_->SetExtraRequestHeaders(headers.ToString());
124 url_fetcher_->SetUploadData("application/json", 203 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. 204 // Fetchers are sometimes cancelled because a network change was detected.
130 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3); 205 url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
131 // Try to make fetching the files bit more robust even with poor connection. 206 // Try to make fetching the files bit more robust even with poor connection.
132 url_fetcher_->SetMaxRetriesOn5xx(3); 207 url_fetcher_->SetMaxRetriesOn5xx(3);
133 url_fetcher_->Start(); 208 url_fetcher_->Start();
134 } 209 }
135 210
211 std::string NTPSnippetsFetcher::GetHostRestricts() const {
212 std::string host_restricts;
213 if (UseHostRestriction()) {
214 for (const std::string& host : hosts_)
215 host_restricts += base::StringPrintf(kHostRestrictFormat, host.c_str());
216 }
217 return host_restricts;
218 }
219
220 bool NTPSnippetsFetcher::UseHostRestriction() const {
221 return (variant_ == Variant::kRestricted ||
222 variant_ == Variant::kRestrictedPersonalized) &&
223 !base::CommandLine::ForCurrentProcess()->HasSwitch(
224 switches::kDontRestrict);
225 }
226
227 bool NTPSnippetsFetcher::UseAuthentication() const {
228 return (variant_ == Variant::kPersonalized ||
229 variant_ == Variant::kRestrictedPersonalized);
230 }
231
232 void NTPSnippetsFetcher::FetchSnippetsNonAuthenticated() {
233 // When not providing OAuth token, we need to pass the Google API key.
234 const std::string& key = is_stable_channel_
235 ? google_apis::GetAPIKey()
236 : google_apis::GetNonStableAPIKey();
237 GURL url(base::StringPrintf(kSnippetsServerNonAuthorizedFormat,
238 kSnippetsServer,
239 key.c_str()));
240
241 FetchSnippetsImpl(url, std::string(),
242 base::StringPrintf(kRequestParameterFormat, "", "",
243 GetHostRestricts().c_str(),
244 count_to_fetch_));
245 }
246
247 void NTPSnippetsFetcher::FetchSnippetsAuthenticated(
248 const std::string& account_id,
249 const std::string& oauth_access_token) {
250 std::string auth =
251 base::StringPrintf(kGaiaIdFormat, account_id.c_str());
Marc Treib 2016/05/09 16:48:24 I think this fits on the previous line?
jkrcal 2016/05/09 19:04:16 Done.
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 std::unique_ptr<OAuth2TokenService::Request> oauth_request(
Marc Treib 2016/05/09 16:48:24 nit: Might be worth a comment ("delete after we le
jkrcal 2016/05/09 19:04:16 Done.
279 std::move(oauth_request_));
280
281 FetchSnippetsAuthenticated(oauth_request->GetAccountId(), access_token);
Marc Treib 2016/05/09 16:48:24 Have you checked that this actually returns an obf
jkrcal 2016/05/09 19:04:16 Yes, I've checked with Roger and it also returns m
Marc Treib 2016/05/10 08:42:19 Okay, great! In that case, could you send a CL to
282 }
283
284 void NTPSnippetsFetcher::OnGetTokenFailure(
285 const OAuth2TokenService::Request* request,
286 const GoogleServiceAuthError& error) {
287 oauth_request_.reset();
288 DLOG(ERROR) << "Unable to get token: " << error.ToString()
289 << " - fetching the snippets without authentication.";
290
291 // Fallback to fetching non-authenticated tokens.
292 FetchSnippetsNonAuthenticated();
293 }
294
295 ////////////////////////////////////////////////////////////////////////////////
296 // OAuth2TokenService::Observer overrides
297 void NTPSnippetsFetcher::OnRefreshTokenAvailable(
298 const std::string& account_id) {
299 token_service_->RemoveObserver(this);
300 waiting_for_refresh_token_ = false;
301 StartTokenRequest();
302 }
303
136 //////////////////////////////////////////////////////////////////////////////// 304 ////////////////////////////////////////////////////////////////////////////////
137 // URLFetcherDelegate overrides 305 // URLFetcherDelegate overrides
138 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) { 306 void NTPSnippetsFetcher::OnURLFetchComplete(const URLFetcher* source) {
139 DCHECK_EQ(url_fetcher_.get(), source); 307 DCHECK_EQ(url_fetcher_.get(), source);
140 308
141 std::string message; 309 std::string message;
142 const URLRequestStatus& status = source->GetStatus(); 310 const URLRequestStatus& status = source->GetStatus();
143 311
144 UMA_HISTOGRAM_SPARSE_SLOWLY( 312 UMA_HISTOGRAM_SPARSE_SLOWLY(
145 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode", 313 "NewTabPage.Snippets.FetchHttpResponseOrErrorCode",
146 status.is_success() ? source->GetResponseCode() : status.error()); 314 status.is_success() ? source->GetResponseCode() : status.error());
147 315
148 if (!status.is_success()) { 316 if (!status.is_success()) {
149 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat, 317 message = base::StringPrintf(kStatusMessageURLRequestErrorFormat,
150 status.error()); 318 status.error());
151 } else if (source->GetResponseCode() != net::HTTP_OK) { 319 } else if (source->GetResponseCode() != net::HTTP_OK) {
320 // TODO(jkrcal): https://crbug.com/609084
321 // We need to deal with the edge case again where the auth
322 // token expires just before we send the request (in which case we need to
323 // fetch a new auth token). We should extract that into a common class
324 // instead of adding it to every single class that uses auth tokens.
152 message = base::StringPrintf(kStatusMessageHTTPErrorFormat, 325 message = base::StringPrintf(kStatusMessageHTTPErrorFormat,
153 source->GetResponseCode()); 326 source->GetResponseCode());
154 } 327 }
155 328
156 if (!message.empty()) { 329 if (!message.empty()) {
330 std::string error_response;
331 source->GetResponseAsString(&error_response);
157 DLOG(WARNING) << message << " while trying to download " 332 DLOG(WARNING) << message << " while trying to download "
158 << source->GetURL().spec(); 333 << source->GetURL().spec() << ": " << error_response;
159 if (!snippets_available_callback_.is_null()) 334 if (!snippets_available_callback_.is_null())
160 snippets_available_callback_.Run(NTPSnippet::PtrVector(), message); 335 snippets_available_callback_.Run(NTPSnippet::PtrVector(), message);
161 } else { 336 } else {
162 bool stores_result_to_string = source->GetResponseAsString( 337 bool stores_result_to_string = source->GetResponseAsString(
163 &last_fetch_json_); 338 &last_fetch_json_);
164 DCHECK(stores_result_to_string); 339 DCHECK(stores_result_to_string);
165 340
166 parse_json_callback_.Run( 341 parse_json_callback_.Run(
167 last_fetch_json_, 342 last_fetch_json_,
168 base::Bind(&NTPSnippetsFetcher::OnJsonParsed, 343 base::Bind(&NTPSnippetsFetcher::OnJsonParsed,
(...skipping 25 matching lines...) Expand all
194 LOG(WARNING) << "Received invalid JSON (" << error << "): " 369 LOG(WARNING) << "Received invalid JSON (" << error << "): "
195 << last_fetch_json_; 370 << last_fetch_json_;
196 if (!snippets_available_callback_.is_null()) { 371 if (!snippets_available_callback_.is_null()) {
197 snippets_available_callback_.Run( 372 snippets_available_callback_.Run(
198 NTPSnippet::PtrVector(), 373 NTPSnippet::PtrVector(),
199 base::StringPrintf(kStatusMessageJsonErrorFormat, error.c_str())); 374 base::StringPrintf(kStatusMessageJsonErrorFormat, error.c_str()));
200 } 375 }
201 } 376 }
202 377
203 } // namespace ntp_snippets 378 } // 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