Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(216)

Side by Side Diff: components/password_manager/core/browser/affiliation_fetcher.cc

Issue 767163005: Add AffiliationFetcher to fetch authoritative affiliation information regarding facets. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Minor improvements to phrasing. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698