1 // Copyright 2014 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.
5 #include "components/password_manager/core/browser/affiliation_fetcher.h"
7 #include "base/metrics/histogram_macros.h"
8 #include "base/metrics/sparse_histogram.h"
9 #include "components/password_manager/core/browser/affiliation_api.pb.h"
10 #include "components/password_manager/core/browser/affiliation_utils.h"
11 #include "components/password_manager/core/browser/test_affiliation_fetcher_factory.h"
12 #include "google_apis/google_api_keys.h"
13 #include "net/base/load_flags.h"
14 #include "net/base/url_util.h"
15 #include "net/http/http_status_code.h"
16 #include "net/url_request/url_fetcher.h"
17 #include "net/url_request/url_request_context_getter.h"
20 namespace password_manager
{
24 // Enumeration listing the possible outcomes of fetching affiliation information
25 // from the Affiliation API. This is used in UMA histograms, so do not change
26 // existing values, only add new values at the end.
27 enum AffiliationFetchResult
{
28 AFFILIATION_FETCH_RESULT_SUCCESS
,
29 AFFILIATION_FETCH_RESULT_FAILURE
,
30 AFFILIATION_FETCH_RESULT_MALFORMED
,
31 AFFILIATION_FETCH_RESULT_MAX
34 // Records the given fetch |result| into the respective UMA histogram, as well
35 // as the response and error codes of |fetcher| if it is non-null.
36 void ReportStatistics(AffiliationFetchResult result
,
37 const net::URLFetcher
* fetcher
) {
38 UMA_HISTOGRAM_ENUMERATION("PasswordManager.AffiliationFetcher.FetchResult",
39 result
, AFFILIATION_FETCH_RESULT_MAX
);
41 UMA_HISTOGRAM_SPARSE_SLOWLY(
42 "PasswordManager.AffiliationFetcher.FetchHttpResponseCode",
43 fetcher
->GetResponseCode());
44 // Network error codes are negative. See: src/net/base/net_error_list.h.
45 UMA_HISTOGRAM_SPARSE_SLOWLY(
46 "PasswordManager.AffiliationFetcher.FetchErrorCode",
47 -fetcher
->GetStatus().error());
53 static TestAffiliationFetcherFactory
* g_testing_factory
= nullptr;
55 AffiliationFetcher::AffiliationFetcher(
56 net::URLRequestContextGetter
* request_context_getter
,
57 const std::vector
<FacetURI
>& facet_uris
,
58 AffiliationFetcherDelegate
* delegate
)
59 : request_context_getter_(request_context_getter
),
60 requested_facet_uris_(facet_uris
),
62 for (const FacetURI
& uri
: requested_facet_uris_
) {
63 DCHECK(uri
.is_valid());
67 AffiliationFetcher::~AffiliationFetcher() {
71 AffiliationFetcher
* AffiliationFetcher::Create(
72 net::URLRequestContextGetter
* context_getter
,
73 const std::vector
<FacetURI
>& facet_uris
,
74 AffiliationFetcherDelegate
* delegate
) {
75 if (g_testing_factory
) {
76 return g_testing_factory
->CreateInstance(context_getter
, facet_uris
,
79 return new AffiliationFetcher(context_getter
, facet_uris
, delegate
);
83 void AffiliationFetcher::SetFactoryForTesting(
84 TestAffiliationFetcherFactory
* factory
) {
85 g_testing_factory
= factory
;
88 void AffiliationFetcher::StartRequest() {
92 net::URLFetcher::Create(BuildQueryURL(), net::URLFetcher::POST
, this);
93 fetcher_
->SetRequestContext(request_context_getter_
.get());
94 fetcher_
->SetUploadData("application/x-protobuf", PreparePayload());
95 fetcher_
->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES
|
96 net::LOAD_DO_NOT_SEND_COOKIES
|
97 net::LOAD_DO_NOT_SEND_AUTH_DATA
|
98 net::LOAD_BYPASS_CACHE
| net::LOAD_DISABLE_CACHE
);
99 fetcher_
->SetAutomaticallyRetryOn5xx(false);
100 fetcher_
->SetAutomaticallyRetryOnNetworkChanges(0);
104 GURL
AffiliationFetcher::BuildQueryURL() const {
105 return net::AppendQueryParameter(
106 GURL("https://www.googleapis.com/affiliation/v1/affiliation:lookup"),
107 "key", google_apis::GetAPIKey());
110 std::string
AffiliationFetcher::PreparePayload() const {
111 affiliation_pb::LookupAffiliationRequest lookup_request
;
112 for (const FacetURI
& uri
: requested_facet_uris_
)
113 lookup_request
.add_facet(uri
.canonical_spec());
115 std::string serialized_request
;
116 bool success
= lookup_request
.SerializeToString(&serialized_request
);
118 return serialized_request
;
121 bool AffiliationFetcher::ParseResponse(
122 AffiliationFetcherDelegate::Result
* result
) const {
123 // This function parses the response protocol buffer message for a list of
124 // equivalence classes, and stores them into |results| after performing some
125 // validation and sanitization steps to make sure that the contract of
126 // AffiliationFetcherDelegate is fulfilled. Possible discrepancies are:
127 // * The server response will not have anything for facets that are not
128 // affiliated with any other facet, while |result| must have them.
129 // * The server response might contain future, unknown kinds of facet URIs,
130 // while |result| must contain only those that are FacetURI::is_valid().
131 // * The server response being ill-formed or self-inconsistent (in the sense
132 // that there are overlapping equivalence classes) is indicative of server
133 // side issues likely not remedied by re-fetching. Report failure in this
134 // case so the caller can be notified and it can act accordingly.
135 // * The |result| will be free of duplicate or empty equivalence classes.
137 std::string serialized_response
;
138 if (!fetcher_
->GetResponseAsString(&serialized_response
)) {
142 affiliation_pb::LookupAffiliationResponse response
;
143 if (!response
.ParseFromString(serialized_response
))
146 result
->reserve(requested_facet_uris_
.size());
148 std::map
<FacetURI
, size_t> facet_uri_to_class_index
;
149 for (int i
= 0; i
< response
.affiliation_size(); ++i
) {
150 const affiliation_pb::Affiliation
& equivalence_class(
151 response
.affiliation(i
));
153 AffiliatedFacets affiliated_uris
;
154 for (int j
= 0; j
< equivalence_class
.facet_size(); ++j
) {
155 const std::string
& uri_spec(equivalence_class
.facet(j
).id());
156 FacetURI uri
= FacetURI::FromPotentiallyInvalidSpec(uri_spec
);
157 // Ignore potential future kinds of facet URIs (e.g. for new platforms).
160 affiliated_uris
.push_back(uri
);
163 // Be lenient and ignore empty (after filtering) equivalence classes.
164 if (affiliated_uris
.empty())
167 // Ignore equivalence classes that are duplicates of earlier ones. However,
168 // fail in the case of a partial overlap, which violates the invariant that
169 // affiliations must form an equivalence relation.
170 for (const FacetURI
& uri
: affiliated_uris
) {
171 if (!facet_uri_to_class_index
.count(uri
))
172 facet_uri_to_class_index
[uri
] = result
->size();
173 if (facet_uri_to_class_index
[uri
] !=
174 facet_uri_to_class_index
[affiliated_uris
[0]]) {
179 // Filter out duplicate equivalence classes in the response.
180 if (facet_uri_to_class_index
[affiliated_uris
[0]] == result
->size())
181 result
->push_back(affiliated_uris
);
184 // Synthesize an equivalence class (of size one) for each facet that did not
185 // appear in the server response due to not being affiliated with any others.
186 for (const FacetURI
& uri
: requested_facet_uris_
) {
187 if (!facet_uri_to_class_index
.count(uri
))
188 result
->push_back(AffiliatedFacets(1, uri
));
194 void AffiliationFetcher::OnURLFetchComplete(const net::URLFetcher
* source
) {
195 DCHECK_EQ(source
, fetcher_
.get());
197 // Note that invoking the |delegate_| may destroy |this| synchronously, so the
198 // invocation must happen last.
199 scoped_ptr
<AffiliationFetcherDelegate::Result
> result_data(
200 new AffiliationFetcherDelegate::Result
);
201 if (fetcher_
->GetStatus().status() == net::URLRequestStatus::SUCCESS
&&
202 fetcher_
->GetResponseCode() == net::HTTP_OK
) {
203 if (ParseResponse(result_data
.get())) {
204 ReportStatistics(AFFILIATION_FETCH_RESULT_SUCCESS
, nullptr);
205 delegate_
->OnFetchSucceeded(result_data
.Pass());
207 ReportStatistics(AFFILIATION_FETCH_RESULT_MALFORMED
, nullptr);
208 delegate_
->OnMalformedResponse();
211 ReportStatistics(AFFILIATION_FETCH_RESULT_FAILURE
, fetcher_
.get());
212 delegate_
->OnFetchFailed();
216 } // namespace password_manager