blob: c97b19fdf60f2874d0ba67ab5b063cbe946d2a2d [file] [log] [blame]
// Copyright 2014 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/password_manager/core/browser/android_affiliation/affiliation_fetcher.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_api.pb.h"
#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
#include "components/password_manager/core/browser/android_affiliation/lookup_affiliation_response_parser.h"
#include "components/password_manager/core/browser/android_affiliation/test_affiliation_fetcher_factory.h"
#include "google_apis/google_api_keys.h"
#include "net/base/load_flags.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "url/gurl.h"
namespace password_manager {
// Enumeration listing the possible outcomes of fetching affiliation information
// from the Affiliation API. This is used in UMA histograms, so do not change
// existing values, only add new values at the end.
enum AffiliationFetchResult {
AFFILIATION_FETCH_RESULT_SUCCESS,
AFFILIATION_FETCH_RESULT_FAILURE,
AFFILIATION_FETCH_RESULT_MALFORMED,
AFFILIATION_FETCH_RESULT_MAX
};
static TestAffiliationFetcherFactory* g_testing_factory = nullptr;
AffiliationFetcher::AffiliationFetcher(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::vector<FacetURI>& facet_uris,
AffiliationFetcherDelegate* delegate)
: url_loader_factory_(std::move(url_loader_factory)),
requested_facet_uris_(facet_uris),
delegate_(delegate) {
for (const FacetURI& uri : requested_facet_uris_) {
DCHECK(uri.is_valid());
}
}
AffiliationFetcher::~AffiliationFetcher() = default;
// static
AffiliationFetcher* AffiliationFetcher::Create(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const std::vector<FacetURI>& facet_uris,
AffiliationFetcherDelegate* delegate) {
if (g_testing_factory) {
return g_testing_factory->CreateInstance(std::move(url_loader_factory),
facet_uris, delegate);
}
return new AffiliationFetcher(std::move(url_loader_factory), facet_uris,
delegate);
}
// static
void AffiliationFetcher::SetFactoryForTesting(
TestAffiliationFetcherFactory* factory) {
g_testing_factory = factory;
}
void AffiliationFetcher::StartRequest() {
DCHECK(!simple_url_loader_);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("affiliation_lookup", R"(
semantics {
sender: "Android Credentials Affiliation Fetcher"
description:
"Users syncing their passwords may have credentials stored for "
"Android apps. Unless synced data is encrypted with a custom "
"passphrase, this service downloads the associations between "
"Android apps and the corresponding websites. Thus, the Android "
"credentials can be used while browsing the web. "
trigger: "Periodically in the background."
data:
"List of Android apps the user has credentials for. The passwords "
"and usernames aren't sent."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Users can enable or disable this feature either by stoping "
"syncing passwords to Google (via unchecking 'Passwords' in "
"Chromium's settings under 'Sign In', 'Advanced sync settings') or "
"by introducing a custom passphrase to disable this service. The "
"feature is enabled by default."
chrome_policy {
SyncDisabled {
policy_options {mode: MANDATORY}
SyncDisabled: true
}
}
})");
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = BuildQueryURL();
resource_request->load_flags =
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE;
resource_request->allow_credentials = false;
resource_request->method = "POST";
simple_url_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
simple_url_loader_->AttachStringForUpload(PreparePayload(),
"application/x-protobuf");
simple_url_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&AffiliationFetcher::OnSimpleLoaderComplete,
base::Unretained(this)));
}
// static
GURL AffiliationFetcher::BuildQueryURL() {
return net::AppendQueryParameter(
GURL("https://www.googleapis.com/affiliation/v1/affiliation:lookup"),
"key", google_apis::GetAPIKey());
}
std::string AffiliationFetcher::PreparePayload() const {
affiliation_pb::LookupAffiliationRequest lookup_request;
for (const FacetURI& uri : requested_facet_uris_)
lookup_request.add_facet(uri.canonical_spec());
// Enable request for branding information.
auto mask = std::make_unique<affiliation_pb::LookupAffiliationMask>();
mask->set_branding_info(true);
lookup_request.set_allocated_mask(mask.release());
std::string serialized_request;
bool success = lookup_request.SerializeToString(&serialized_request);
DCHECK(success);
return serialized_request;
}
bool AffiliationFetcher::ParseResponse(
const std::string& serialized_response,
AffiliationFetcherDelegate::Result* result) const {
// This function parses the response protocol buffer message for a list of
// equivalence classes, and stores them into |results| after performing some
// validation and sanitization steps to make sure that the contract of
// AffiliationFetcherDelegate is fulfilled. Possible discrepancies are:
// * The server response will not have anything for facets that are not
// affiliated with any other facet, while |result| must have them.
// * The server response might contain future, unknown kinds of facet URIs,
// while |result| must contain only those that are FacetURI::is_valid().
// * The server response being ill-formed or self-inconsistent (in the sense
// that there are overlapping equivalence classes) is indicative of server
// side issues likely not remedied by re-fetching. Report failure in this
// case so the caller can be notified and it can act accordingly.
// * The |result| will be free of duplicate or empty equivalence classes.
affiliation_pb::LookupAffiliationResponse response;
if (!response.ParseFromString(serialized_response))
return false;
return ParseLookupAffiliationResponse(requested_facet_uris_, response,
result);
}
void AffiliationFetcher::OnSimpleLoaderComplete(
std::unique_ptr<std::string> response_body) {
// Note that invoking the |delegate_| may destroy |this| synchronously, so the
// invocation must happen last.
std::unique_ptr<AffiliationFetcherDelegate::Result> result_data(
new AffiliationFetcherDelegate::Result);
if (response_body) {
if (ParseResponse(*response_body, result_data.get())) {
UMA_HISTOGRAM_ENUMERATION(
"PasswordManager.AffiliationFetcher.FetchResult",
AFFILIATION_FETCH_RESULT_SUCCESS, AFFILIATION_FETCH_RESULT_MAX);
delegate_->OnFetchSucceeded(std::move(result_data));
} else {
UMA_HISTOGRAM_ENUMERATION(
"PasswordManager.AffiliationFetcher.FetchResult",
AFFILIATION_FETCH_RESULT_MALFORMED, AFFILIATION_FETCH_RESULT_MAX);
delegate_->OnMalformedResponse();
}
} else {
UMA_HISTOGRAM_ENUMERATION("PasswordManager.AffiliationFetcher.FetchResult",
AFFILIATION_FETCH_RESULT_FAILURE,
AFFILIATION_FETCH_RESULT_MAX);
int response_code = -1;
if (simple_url_loader_->ResponseInfo() &&
simple_url_loader_->ResponseInfo()->headers) {
response_code =
simple_url_loader_->ResponseInfo()->headers->response_code();
}
base::UmaHistogramSparse(
"PasswordManager.AffiliationFetcher.FetchHttpResponseCode",
response_code);
// Network error codes are negative. See: src/net/base/net_error_list.h.
base::UmaHistogramSparse(
"PasswordManager.AffiliationFetcher.FetchErrorCode",
-simple_url_loader_->NetError());
delegate_->OnFetchFailed();
}
}
} // namespace password_manager