1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
|
// 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/affiliation_fetcher.h"
#include <stddef.h>
#include <utility>
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "components/password_manager/core/browser/affiliation_api.pb.h"
#include "components/password_manager/core/browser/affiliation_utils.h"
#include "components/password_manager/core/browser/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/url_request/url_fetcher.h"
#include "net/url_request/url_request_context_getter.h"
#include "url/gurl.h"
namespace password_manager {
namespace {
// 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
};
// Records the given fetch |result| into the respective UMA histogram, as well
// as the response and error codes of |fetcher| if it is non-null.
void ReportStatistics(AffiliationFetchResult result,
const net::URLFetcher* fetcher) {
UMA_HISTOGRAM_ENUMERATION("PasswordManager.AffiliationFetcher.FetchResult",
result, AFFILIATION_FETCH_RESULT_MAX);
if (fetcher) {
UMA_HISTOGRAM_SPARSE_SLOWLY(
"PasswordManager.AffiliationFetcher.FetchHttpResponseCode",
fetcher->GetResponseCode());
// Network error codes are negative. See: src/net/base/net_error_list.h.
UMA_HISTOGRAM_SPARSE_SLOWLY(
"PasswordManager.AffiliationFetcher.FetchErrorCode",
-fetcher->GetStatus().error());
}
}
} // namespace
static TestAffiliationFetcherFactory* g_testing_factory = nullptr;
AffiliationFetcher::AffiliationFetcher(
net::URLRequestContextGetter* request_context_getter,
const std::vector<FacetURI>& facet_uris,
AffiliationFetcherDelegate* delegate)
: request_context_getter_(request_context_getter),
requested_facet_uris_(facet_uris),
delegate_(delegate) {
for (const FacetURI& uri : requested_facet_uris_) {
DCHECK(uri.is_valid());
}
}
AffiliationFetcher::~AffiliationFetcher() {
}
// static
AffiliationFetcher* AffiliationFetcher::Create(
net::URLRequestContextGetter* context_getter,
const std::vector<FacetURI>& facet_uris,
AffiliationFetcherDelegate* delegate) {
if (g_testing_factory) {
return g_testing_factory->CreateInstance(context_getter, facet_uris,
delegate);
}
return new AffiliationFetcher(context_getter, facet_uris, delegate);
}
// static
void AffiliationFetcher::SetFactoryForTesting(
TestAffiliationFetcherFactory* factory) {
g_testing_factory = factory;
}
void AffiliationFetcher::StartRequest() {
DCHECK(!fetcher_);
fetcher_ =
net::URLFetcher::Create(BuildQueryURL(), net::URLFetcher::POST, this);
fetcher_->SetRequestContext(request_context_getter_.get());
fetcher_->SetUploadData("application/x-protobuf", PreparePayload());
fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES |
net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SEND_AUTH_DATA |
net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE);
fetcher_->SetAutomaticallyRetryOn5xx(false);
fetcher_->SetAutomaticallyRetryOnNetworkChanges(0);
fetcher_->Start();
}
GURL AffiliationFetcher::BuildQueryURL() const {
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());
std::string serialized_request;
bool success = lookup_request.SerializeToString(&serialized_request);
DCHECK(success);
return serialized_request;
}
bool AffiliationFetcher::ParseResponse(
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.
std::string serialized_response;
if (!fetcher_->GetResponseAsString(&serialized_response)) {
NOTREACHED();
}
affiliation_pb::LookupAffiliationResponse response;
if (!response.ParseFromString(serialized_response))
return false;
result->reserve(requested_facet_uris_.size());
std::map<FacetURI, size_t> facet_uri_to_class_index;
for (int i = 0; i < response.affiliation_size(); ++i) {
const affiliation_pb::Affiliation& equivalence_class(
response.affiliation(i));
AffiliatedFacets affiliated_uris;
for (int j = 0; j < equivalence_class.facet_size(); ++j) {
const std::string& uri_spec(equivalence_class.facet(j).id());
FacetURI uri = FacetURI::FromPotentiallyInvalidSpec(uri_spec);
// Ignore potential future kinds of facet URIs (e.g. for new platforms).
if (!uri.is_valid())
continue;
affiliated_uris.push_back(uri);
}
// Be lenient and ignore empty (after filtering) equivalence classes.
if (affiliated_uris.empty())
continue;
// Ignore equivalence classes that are duplicates of earlier ones. However,
// fail in the case of a partial overlap, which violates the invariant that
// affiliations must form an equivalence relation.
for (const FacetURI& uri : affiliated_uris) {
if (!facet_uri_to_class_index.count(uri))
facet_uri_to_class_index[uri] = result->size();
if (facet_uri_to_class_index[uri] !=
facet_uri_to_class_index[affiliated_uris[0]]) {
return false;
}
}
// Filter out duplicate equivalence classes in the response.
if (facet_uri_to_class_index[affiliated_uris[0]] == result->size())
result->push_back(affiliated_uris);
}
// Synthesize an equivalence class (of size one) for each facet that did not
// appear in the server response due to not being affiliated with any others.
for (const FacetURI& uri : requested_facet_uris_) {
if (!facet_uri_to_class_index.count(uri))
result->push_back(AffiliatedFacets(1, uri));
}
return true;
}
void AffiliationFetcher::OnURLFetchComplete(const net::URLFetcher* source) {
DCHECK_EQ(source, fetcher_.get());
// 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 (fetcher_->GetStatus().status() == net::URLRequestStatus::SUCCESS &&
fetcher_->GetResponseCode() == net::HTTP_OK) {
if (ParseResponse(result_data.get())) {
ReportStatistics(AFFILIATION_FETCH_RESULT_SUCCESS, nullptr);
delegate_->OnFetchSucceeded(std::move(result_data));
} else {
ReportStatistics(AFFILIATION_FETCH_RESULT_MALFORMED, nullptr);
delegate_->OnMalformedResponse();
}
} else {
ReportStatistics(AFFILIATION_FETCH_RESULT_FAILURE, fetcher_.get());
delegate_->OnFetchFailed();
}
}
} // namespace password_manager
|