1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/omnibox/browser/document_suggestions_service.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/feature_list.h"
12 #include "base/i18n/rtl.h"
13 #include "base/json/json_writer.h"
14 #include "base/memory/scoped_refptr.h"
15 #include "base/metrics/field_trial_params.h"
16 #include "base/values.h"
17 #include "components/omnibox/browser/document_provider.h"
18 #include "components/omnibox/common/omnibox_features.h"
19 #include "components/signin/public/identity_manager/identity_manager.h"
20 #include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h"
21 #include "components/signin/public/identity_manager/scope_set.h"
22 #include "components/variations/net/variations_http_headers.h"
23 #include "components/variations/variations_associated_data.h"
24 #include "net/base/load_flags.h"
25 #include "net/traffic_annotation/network_traffic_annotation.h"
26 #include "services/network/public/cpp/resource_request.h"
27 #include "services/network/public/cpp/shared_url_loader_factory.h"
28 #include "services/network/public/cpp/simple_url_loader.h"
29
30 namespace {
31
32 // Builds a document search request body. Inputs that affect the request are:
33 // |query|: Current omnibox query text, passed as an argument.
34 // |locale|: Current browser locale as BCP-47, obtained inside the function.
35 // The format of the request is:
36 // {
37 // query: "|query|",
38 // start: 0,
39 // pageSize: 10,
40 // requestOptions: {
41 // searchApplicationId: "searchapplications/chrome",
42 // languageCode: "|locale|",
43 // }
44 // }
BuildDocumentSuggestionRequest(const base::string16 & query)45 std::string BuildDocumentSuggestionRequest(const base::string16& query) {
46 base::Value root(base::Value::Type::DICTIONARY);
47 root.SetKey("query", base::Value(query));
48 // The API supports pagination. We're always concerned with the first N
49 // results on the first page.
50 root.SetKey("start", base::Value(0));
51 root.SetKey("pageSize", base::Value(10));
52
53 base::Value request_options(base::Value::Type::DICTIONARY);
54 request_options.SetKey("searchApplicationId",
55 base::Value("searchapplications/chrome"));
56 request_options.SetKey("languageCode",
57 base::Value(base::i18n::GetConfiguredLocale()));
58 root.SetKey("requestOptions", std::move(request_options));
59
60 std::string result;
61 base::JSONWriter::Write(root, &result);
62 return result;
63 }
64
65 } // namespace
66
DocumentSuggestionsService(signin::IdentityManager * identity_manager,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)67 DocumentSuggestionsService::DocumentSuggestionsService(
68 signin::IdentityManager* identity_manager,
69 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
70 : url_loader_factory_(url_loader_factory),
71 identity_manager_(identity_manager),
72 token_fetcher_(nullptr) {
73 DCHECK(url_loader_factory);
74 }
75
~DocumentSuggestionsService()76 DocumentSuggestionsService::~DocumentSuggestionsService() {}
77
CreateDocumentSuggestionsRequest(const base::string16 & query,bool is_incognito,StartCallback start_callback,CompletionCallback completion_callback)78 void DocumentSuggestionsService::CreateDocumentSuggestionsRequest(
79 const base::string16& query,
80 bool is_incognito,
81 StartCallback start_callback,
82 CompletionCallback completion_callback) {
83 std::string endpoint = base::GetFieldTrialParamValueByFeature(
84 omnibox::kDocumentProvider, "DocumentProviderEndpoint");
85 if (endpoint.empty())
86 endpoint = "https://cloudsearch.googleapis.com/v1/query/search";
87 const GURL suggest_url = GURL(endpoint);
88 DCHECK(suggest_url.is_valid());
89
90 net::NetworkTrafficAnnotationTag traffic_annotation =
91 net::DefineNetworkTrafficAnnotation("omnibox_documentsuggest", R"(
92 semantics {
93 sender: "Omnibox"
94 description:
95 "Request for Google Drive document suggestions from the omnibox."
96 "User must be signed in and have default search provider set to "
97 "Google."
98 trigger: "Signed-in user enters text in the omnibox."
99 data: "The query string from the omnibox."
100 destination: GOOGLE_OWNED_SERVICE
101 }
102 policy {
103 cookies_allowed: YES
104 cookies_store: "user"
105 setting:
106 "Coupled to Google default search plus signed-in"
107 chrome_policy {
108 SearchSuggestEnabled {
109 policy_options {mode: MANDATORY}
110 SearchSuggestEnabled: false
111 }
112 }
113 })");
114 auto request = std::make_unique<network::ResourceRequest>();
115 request->url = suggest_url;
116 request->method = "POST";
117 std::string request_body = BuildDocumentSuggestionRequest(query);
118 request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES;
119 // It is expected that the user is signed in here. But we only care about
120 // experiment IDs from the variations server, which do not require the
121 // signed-in version of this method.
122 variations::AppendVariationsHeaderUnknownSignedIn(
123 request->url,
124 is_incognito ? variations::InIncognito::kYes
125 : variations::InIncognito::kNo,
126 request.get());
127
128 // Create and fetch an OAuth2 token.
129 std::string scope = "https://www.googleapis.com/auth/cloud_search.query";
130 signin::ScopeSet scopes;
131 scopes.insert(scope);
132 token_fetcher_ = std::make_unique<signin::PrimaryAccountAccessTokenFetcher>(
133 "document_suggestions_service", identity_manager_, scopes,
134 base::BindOnce(&DocumentSuggestionsService::AccessTokenAvailable,
135 base::Unretained(this), std::move(request),
136 std::move(request_body), traffic_annotation,
137 std::move(start_callback), std::move(completion_callback)),
138 signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable);
139 }
140
StopCreatingDocumentSuggestionsRequest()141 void DocumentSuggestionsService::StopCreatingDocumentSuggestionsRequest() {
142 std::unique_ptr<signin::PrimaryAccountAccessTokenFetcher>
143 token_fetcher_deleter(std::move(token_fetcher_));
144 }
145
AccessTokenAvailable(std::unique_ptr<network::ResourceRequest> request,std::string request_body,net::NetworkTrafficAnnotationTag traffic_annotation,StartCallback start_callback,CompletionCallback completion_callback,GoogleServiceAuthError error,signin::AccessTokenInfo access_token_info)146 void DocumentSuggestionsService::AccessTokenAvailable(
147 std::unique_ptr<network::ResourceRequest> request,
148 std::string request_body,
149 net::NetworkTrafficAnnotationTag traffic_annotation,
150 StartCallback start_callback,
151 CompletionCallback completion_callback,
152 GoogleServiceAuthError error,
153 signin::AccessTokenInfo access_token_info) {
154 DCHECK(token_fetcher_);
155 token_fetcher_.reset();
156
157 // If there were no errors obtaining the access token, append it to the
158 // request as a header.
159 if (error.state() == GoogleServiceAuthError::NONE) {
160 DCHECK(!access_token_info.token.empty());
161 request->headers.SetHeader(
162 "Authorization",
163 base::StringPrintf("Bearer %s", access_token_info.token.c_str()));
164 }
165
166 StartDownloadAndTransferLoader(std::move(request), std::move(request_body),
167 traffic_annotation, std::move(start_callback),
168 std::move(completion_callback));
169 }
170
StartDownloadAndTransferLoader(std::unique_ptr<network::ResourceRequest> request,std::string request_body,net::NetworkTrafficAnnotationTag traffic_annotation,StartCallback start_callback,CompletionCallback completion_callback)171 void DocumentSuggestionsService::StartDownloadAndTransferLoader(
172 std::unique_ptr<network::ResourceRequest> request,
173 std::string request_body,
174 net::NetworkTrafficAnnotationTag traffic_annotation,
175 StartCallback start_callback,
176 CompletionCallback completion_callback) {
177 std::unique_ptr<network::SimpleURLLoader> loader =
178 network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
179 if (!request_body.empty()) {
180 loader->AttachStringForUpload(request_body, "application/json");
181 }
182 loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
183 url_loader_factory_.get(),
184 base::BindOnce(std::move(completion_callback), loader.get()));
185
186 std::move(start_callback).Run(std::move(loader));
187 }
188