OLD | NEW |
---|---|
(Empty) | |
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. | |
4 | |
5 #include "components/password_manager/core/browser/affiliation_fetcher.h" | |
6 | |
7 #include "base/json/json_reader.h" | |
8 #include "base/json/json_writer.h" | |
9 #include "base/values.h" | |
10 #include "components/password_manager/core/browser/affiliation_utils.h" | |
11 #include "google_apis/google_api_keys.h" | |
12 #include "net/base/load_flags.h" | |
13 #include "net/base/url_util.h" | |
14 #include "net/http/http_status_code.h" | |
15 #include "net/url_request/url_fetcher.h" | |
16 #include "net/url_request/url_request_context_getter.h" | |
17 #include "url/gurl.h" | |
18 | |
19 namespace password_manager { | |
20 | |
21 AffiliationFetcher::AffiliationFetcher( | |
22 net::URLRequestContextGetter* request_context_getter, | |
23 const std::vector<FacetURI>& facet_uris, | |
24 AffiliationFetcherDelegate* delegate) | |
25 : request_context_getter_(request_context_getter), | |
26 requested_facet_uris_(facet_uris), | |
27 delegate_(delegate) { | |
28 for (const FacetURI& uri : requested_facet_uris_) | |
29 DCHECK(uri.is_valid()); | |
30 } | |
31 | |
32 AffiliationFetcher::~AffiliationFetcher() { | |
33 } | |
34 | |
35 // static | |
36 AffiliationFetcher* AffiliationFetcher::Create( | |
37 net::URLRequestContextGetter* context_getter, | |
38 const std::vector<FacetURI>& facet_uris, | |
39 AffiliationFetcherDelegate* delegate) { | |
40 return new AffiliationFetcher(context_getter, facet_uris, delegate); | |
41 } | |
42 | |
43 void AffiliationFetcher::StartRequest() { | |
44 DCHECK(!fetcher_); | |
45 | |
46 fetcher_.reset( | |
47 net::URLFetcher::Create(BuildQueryURL(), net::URLFetcher::POST, this)); | |
48 fetcher_->SetRequestContext(request_context_getter_.get()); | |
49 fetcher_->SetUploadData("application/json", PreparePayload()); | |
50 fetcher_->SetLoadFlags( | |
51 net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES | | |
52 net::LOAD_DO_NOT_SEND_AUTH_DATA | net::LOAD_BYPASS_CACHE | | |
53 net::LOAD_DISABLE_CACHE | net::LOAD_DO_NOT_PROMPT_FOR_LOGIN); | |
Ryan Sleevi
2014/12/10 19:44:36
Why LOAD_DISABLE_CACHE? Seems emminently sensible
engedy
2014/12/11 20:49:23
Most of the time the lookups will be performed in
| |
54 fetcher_->SetAutomaticallyRetryOn5xx(false); | |
55 fetcher_->SetAutomaticallyRetryOnNetworkChanges(0); | |
56 fetcher_->Start(); | |
57 } | |
58 | |
59 GURL AffiliationFetcher::BuildQueryURL() const { | |
60 return net::AppendQueryParameter( | |
61 GURL("https://www.googleapis.com/affiliation/v1/affiliation:lookup"), | |
62 "key", google_apis::GetAPIKey()); | |
63 } | |
64 | |
65 std::string AffiliationFetcher::PreparePayload() const { | |
66 // The request payload should be a JSON dictionary with a single key named | |
67 // "facet" that maps to the list of facet URIs to query: | |
68 // | |
69 // { | |
70 // "facet": [<requested_facet_uri_1>, ..., <requested_facet_uri_N>] | |
71 // } | |
72 | |
73 scoped_ptr<base::ListValue> requested_facet_uris_list(new base::ListValue); | |
74 for (const FacetURI& uri : requested_facet_uris_) | |
75 requested_facet_uris_list->AppendString(uri.canonical_spec()); | |
76 | |
77 base::DictionaryValue payload_dictionary; | |
78 payload_dictionary.Set("facet", requested_facet_uris_list.release()); | |
79 | |
80 std::string payload_json; | |
81 bool serialization_suceeded = | |
82 base::JSONWriter::Write(&payload_dictionary, &payload_json); | |
83 DCHECK(serialization_suceeded); | |
84 return payload_json; | |
85 } | |
86 | |
87 bool AffiliationFetcher::ParseResponse( | |
88 AffiliationFetcherDelegate::Result* result) const { | |
89 // The response payload will be a JSON dictionary containing the key named | |
90 // "affiliation" that maps to the list of equivalence classes, each of which | |
91 // will be a dictionary itself with a key named "facet" that maps to the list | |
92 // of facet URIs in that class: | |
93 // | |
94 // { | |
95 // "affiliation": [ | |
96 // { | |
97 // "facet": [<eq_class_1_facet_1>, ..., <eq_class_1_facet_N1>] | |
98 // }, | |
99 // ... | |
100 // { | |
101 // "facet": [<eq_class_M_facet_1>, ..., <eq_class_M_facet_Nm>] | |
102 // } | |
103 // } | |
104 | |
105 std::string response_json; | |
106 if (!fetcher_->GetResponseAsString(&response_json)) | |
107 NOTREACHED(); | |
108 | |
109 scoped_ptr<const base::Value> response_value( | |
110 base::JSONReader::Read(response_json)); | |
Ryan Sleevi
2014/12/10 19:44:36
SECURITY: Parsing JSON inside the Browser process,
Garrett Casto
2014/12/11 08:18:39
Protocol buffers also look like they would save yo
engedy
2014/12/11 20:49:23
Thanks for the heads-up! The server side support p
| |
111 if (!response_value) { | |
112 VLOG(1) << "Response could not be parsed as JSON."; | |
113 VLOG(1) << response_json; | |
Ryan Sleevi
2014/12/10 19:44:36
These are fairly chatty VLOGs. I want to make sure
Garrett Casto
2014/12/11 08:18:38
+1 to DVLOG. If you're actually worried about thes
engedy
2014/12/11 20:49:24
Removed logging statements -- Given that the Fetch
| |
114 return false; | |
115 } | |
116 | |
117 const base::DictionaryValue* response_dict; | |
118 if (!response_value->GetAsDictionary(&response_dict)) { | |
119 VLOG(1) << "Response is not a JSON object."; | |
120 VLOG(1) << *response_value; | |
121 return false; | |
122 } | |
123 | |
124 const base::ListValue* equivalence_classes_list; | |
125 if (!response_dict->GetList("affiliation", &equivalence_classes_list)) { | |
126 VLOG(1) << "Response contains no 'affiliation' list."; | |
127 VLOG(1) << *response_value; | |
128 return false; | |
129 } | |
130 | |
131 // Reserve enough space to avoid costly reallocations. We will return at most | |
132 // one equivalence class per requested facet, so use that as an upper bound. | |
133 // Note that the size of |equivalence_classes_list| is not necessarily enough, | |
134 // as it may be missing classes artificially added by this function later. | |
Ryan Sleevi
2014/12/10 19:44:36
This is a pretty lengthy comment for what is impli
engedy
2014/12/11 20:49:23
Done (removed comment).
| |
135 result->reserve(requested_facet_uris_.size()); | |
136 | |
137 std::map<FacetURI, size_t> facet_uri_to_class_index; | |
138 for (size_t i = 0; i < equivalence_classes_list->GetSize(); ++i) { | |
139 const base::DictionaryValue* equivalence_class_dict; | |
140 if (!equivalence_classes_list->GetDictionary(i, &equivalence_class_dict)) { | |
141 VLOG(1) << "An element of the 'affiliation' list is not a JSON object."; | |
142 VLOG(1) << *equivalence_classes_list; | |
143 return false; | |
144 } | |
145 | |
146 const base::ListValue* equivalence_class_members_list; | |
147 if (!equivalence_class_dict->GetList("facet", | |
148 &equivalence_class_members_list)) { | |
149 VLOG(1) << "Equivalence class JSON object contains no 'facet' list."; | |
150 VLOG(1) << *equivalence_class_dict; | |
151 return false; | |
152 } | |
153 | |
154 AffiliatedFacets affiliated_uris; | |
155 for (size_t j = 0; j < equivalence_class_members_list->GetSize(); ++j) { | |
156 std::string uri_spec; | |
157 if (!equivalence_class_members_list->GetString(j, &uri_spec)) { | |
158 VLOG(1) << "An element of a 'facet' list is not string."; | |
159 VLOG(1) << *equivalence_class_members_list; | |
160 return false; | |
161 } | |
162 | |
163 // Ignore potential future kinds of facet URIs (e.g. for new platforms). | |
164 FacetURI uri = FacetURI::FromPotentiallyInvalidSpec(uri_spec); | |
165 if (!uri.is_valid()){ | |
166 VLOG(2) << "Ignored facet URI of unknown/invalid format: " << uri_spec; | |
167 continue; | |
168 } | |
169 | |
170 affiliated_uris.push_back(uri); | |
171 } | |
172 | |
173 // Be lenient and ignore empty (after filtering) equivalence classes. | |
174 if (affiliated_uris.empty()) | |
175 continue; | |
176 | |
177 // Ignore equivalence classes if they are a duplicate of an earlier one. | |
178 // However, bail out if we discover partial overlapping, in which case the | |
179 // response cannot be part of an equivalence relation. | |
Ryan Sleevi
2014/12/10 19:44:36
Please consider rewriting this without the pronoun
engedy
2014/12/11 20:49:23
I have rephrased it, hopefully, for the better.
| |
180 for (const FacetURI& uri : affiliated_uris) { | |
181 if (!facet_uri_to_class_index.count(uri)) | |
182 facet_uri_to_class_index[uri] = result->size(); | |
183 if (facet_uri_to_class_index[uri] != | |
184 facet_uri_to_class_index[affiliated_uris[0]]) { | |
185 VLOG(1) << "Response cannot be part of an equivalence relation."; | |
186 VLOG(1) << *response_value; | |
187 return false; | |
188 } | |
189 } | |
190 | |
191 // Filter out duplicate equivalence classes in the response. | |
192 if (facet_uri_to_class_index[affiliated_uris[0]] == result->size()) | |
193 result->push_back(affiliated_uris); | |
194 } | |
195 | |
196 // The server does not return at all facet URIs that are not affiliated with | |
197 // anything, or facet URIs that it does not know about. However, this class | |
Ryan Sleevi
2014/12/10 19:44:36
This first comment reads weird, in particular, the
engedy
2014/12/11 20:49:23
That was a typo, sorry. I have used you comment su
| |
198 // promises to return an equivalence class for each requested facet, so create | |
199 // one for each each of these missing facets. | |
Ryan Sleevi
2014/12/10 19:44:36
// The API contract of this class is to return an
engedy
2014/12/11 20:49:23
Thanks for the suggestion! I have rephrased along
| |
200 for (const FacetURI& uri : requested_facet_uris_) { | |
201 if (!facet_uri_to_class_index.count(uri)) { | |
202 result->resize(result->size() + 1); | |
203 result->back().push_back(uri); | |
204 } | |
205 } | |
206 | |
207 return true; | |
208 } | |
209 | |
210 void AffiliationFetcher::OnURLFetchComplete(const net::URLFetcher* source) { | |
211 DCHECK_EQ(source, fetcher_.get()); | |
212 | |
213 scoped_ptr<AffiliationFetcherDelegate::Result> result( | |
214 new AffiliationFetcherDelegate::Result); | |
215 if (fetcher_->GetResponseCode() == net::HTTP_OK) { | |
Randy Smith (Not in Mondays)
2014/12/10 22:13:24
I'm a little uncomfortable with testing only this,
engedy
2014/12/11 20:49:24
Done. Thanks for pointing that out!
| |
216 if (ParseResponse(result.get())) | |
217 delegate_->OnFetchSucceeded(result.Pass()); | |
218 else | |
219 delegate_->OnMalformedResponse(); | |
220 } else { | |
221 delegate_->OnFetchFailed(); | |
222 } | |
223 } | |
224 | |
225 } // namespace password_manager | |
OLD | NEW |