Chromium Code Reviews| 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 |