| // Copyright 2018 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/omnibox/browser/document_suggestions_service.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/feature_list.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "components/data_use_measurement/core/data_use_user_data.h" |
| #include "components/omnibox/browser/document_provider.h" |
| #include "components/omnibox/browser/omnibox_field_trial.h" |
| #include "components/search_engines/template_url_service.h" |
| #include "components/variations/variations_associated_data.h" |
| #include "net/base/load_flags.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/identity/public/cpp/identity_manager.h" |
| #include "services/identity/public/cpp/primary_account_access_token_fetcher.h" |
| #include "services/network/public/cpp/resource_request.h" |
| #include "services/network/public/cpp/resource_response.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| |
| namespace { |
| |
| // Builds a document search request body. Inputs are: |
| // |query|: Current query text. |
| // The format of the request is: |
| // { |
| // query: "the search text", |
| // start: 0, |
| // pageSize: 10, |
| // sourceOptions: [{source: {predefinedSource: "GOOGLE_DRIVE"}}] |
| // } |
| std::string BuildDocumentSuggestionRequest(const base::string16& query) { |
| base::Value root(base::Value::Type::DICTIONARY); |
| root.SetKey("query", base::Value(query)); |
| root.SetKey("start", base::Value(0)); |
| root.SetKey("pageSize", base::Value(10)); |
| |
| base::Value::ListStorage storage_options_list; |
| base::Value source_definition(base::Value::Type::DICTIONARY); |
| source_definition.SetPath({"source", "predefinedSource"}, |
| base::Value("GOOGLE_DRIVE")); |
| storage_options_list.emplace_back(std::move(source_definition)); |
| root.SetKey("dataSourceRestrictions", |
| base::Value(std::move(storage_options_list))); |
| |
| std::string result; |
| base::JSONWriter::Write(root, &result); |
| return result; |
| } |
| |
| } // namespace |
| |
| DocumentSuggestionsService::DocumentSuggestionsService( |
| identity::IdentityManager* identity_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) |
| : url_loader_factory_(url_loader_factory), |
| identity_manager_(identity_manager), |
| token_fetcher_(nullptr) { |
| DCHECK(url_loader_factory); |
| } |
| |
| DocumentSuggestionsService::~DocumentSuggestionsService() {} |
| |
| void DocumentSuggestionsService::CreateDocumentSuggestionsRequest( |
| const base::string16& query, |
| const TemplateURLService* template_url_service, |
| StartCallback start_callback, |
| CompletionCallback completion_callback) { |
| std::string endpoint = base::GetFieldTrialParamValueByFeature( |
| omnibox::kDocumentProvider, "DocumentProviderEndpoint"); |
| if (endpoint.empty()) |
| endpoint = "https://cloudsearch.googleapis.com/v1/query/search"; |
| const GURL suggest_url = GURL(endpoint); |
| DCHECK(suggest_url.is_valid()); |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("omnibox_documentsuggest", R"( |
| semantics { |
| sender: "Omnibox" |
| description: |
| "Request for Google Drive document suggestions from the omnibox." |
| "User must be signed in and have default search provider set to " |
| "Google." |
| trigger: "Signed-in user enters text in the omnibox." |
| data: "The query string from the omnibox." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: YES |
| cookies_store: "user" |
| setting: |
| "Coupled to Google default search plus signed-in" |
| chrome_policy { |
| SearchSuggestEnabled { |
| policy_options {mode: MANDATORY} |
| SearchSuggestEnabled: false |
| } |
| } |
| })"); |
| auto request = std::make_unique<network::ResourceRequest>(); |
| request->url = suggest_url; |
| request->method = "POST"; |
| std::string request_body = BuildDocumentSuggestionRequest(query); |
| request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES; |
| // TODO(https://crbug.com/808498) re-add data use measurement once |
| // SimpleURLLoader supports it. |
| // We should attach data_use_measurement::DataUseUserData::OMNIBOX. |
| |
| // Create and fetch an OAuth2 token. |
| std::string scope = "https://www.googleapis.com/auth/cloud_search.query"; |
| OAuth2TokenService::ScopeSet scopes; |
| scopes.insert(scope); |
| token_fetcher_ = std::make_unique<identity::PrimaryAccountAccessTokenFetcher>( |
| "document_suggestions_service", identity_manager_, scopes, |
| base::BindOnce(&DocumentSuggestionsService::AccessTokenAvailable, |
| base::Unretained(this), std::move(request), |
| std::move(request_body), traffic_annotation, |
| std::move(start_callback), std::move(completion_callback)), |
| identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable); |
| } |
| |
| void DocumentSuggestionsService::StopCreatingDocumentSuggestionsRequest() { |
| std::unique_ptr<identity::PrimaryAccountAccessTokenFetcher> |
| token_fetcher_deleter(std::move(token_fetcher_)); |
| } |
| |
| void DocumentSuggestionsService::AccessTokenAvailable( |
| std::unique_ptr<network::ResourceRequest> request, |
| std::string request_body, |
| net::NetworkTrafficAnnotationTag traffic_annotation, |
| StartCallback start_callback, |
| CompletionCallback completion_callback, |
| GoogleServiceAuthError error, |
| identity::AccessTokenInfo access_token_info) { |
| DCHECK(token_fetcher_); |
| token_fetcher_.reset(); |
| |
| // If there were no errors obtaining the access token, append it to the |
| // request as a header. |
| if (error.state() == GoogleServiceAuthError::NONE) { |
| DCHECK(!access_token_info.token.empty()); |
| request->headers.SetHeader( |
| "Authorization", |
| base::StringPrintf("Bearer %s", access_token_info.token.c_str())); |
| } |
| |
| StartDownloadAndTransferLoader(std::move(request), std::move(request_body), |
| traffic_annotation, std::move(start_callback), |
| std::move(completion_callback)); |
| } |
| |
| void DocumentSuggestionsService::StartDownloadAndTransferLoader( |
| std::unique_ptr<network::ResourceRequest> request, |
| std::string request_body, |
| net::NetworkTrafficAnnotationTag traffic_annotation, |
| StartCallback start_callback, |
| CompletionCallback completion_callback) { |
| std::unique_ptr<network::SimpleURLLoader> loader = |
| network::SimpleURLLoader::Create(std::move(request), traffic_annotation); |
| if (!request_body.empty()) { |
| loader->AttachStringForUpload(request_body, "application/json"); |
| } |
| loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| url_loader_factory_.get(), |
| base::BindOnce(std::move(completion_callback), loader.get())); |
| |
| std::move(start_callback).Run(std::move(loader)); |
| } |