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 "chromeos/services/device_sync/cryptauth_api_call_flow.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10 #include <vector>
11
12 #include "base/bind.h"
13 #include "base/macros.h"
14 #include "base/no_destructor.h"
15 #include "base/optional.h"
16 #include "base/test/task_environment.h"
17 #include "chromeos/services/device_sync/network_request_error.h"
18 #include "net/base/net_errors.h"
19 #include "net/base/url_util.h"
20 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
21 #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
22 #include "services/network/test/test_url_loader_factory.h"
23 #include "services/network/test/test_utils.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 namespace chromeos {
27
28 namespace device_sync {
29
30 namespace {
31
32 const char kSerializedRequestProto[] = "serialized_request_proto";
33 const char kSerializedResponseProto[] = "result_proto";
34 const char kRequestUrl[] = "https://googleapis.com/cryptauth/test";
35 const char kAccessToken[] = "access_token";
36 const char kQueryParameterAlternateOutputKey[] = "alt";
37 const char kQueryParameterAlternateOutputProto[] = "proto";
38
39 const std::vector<std::pair<std::string, std::string>>&
GetTestRequestProtoAsQueryParameters()40 GetTestRequestProtoAsQueryParameters() {
41 static const base::NoDestructor<
42 std::vector<std::pair<std::string, std::string>>>
43 request_as_query_parameters([] {
44 std::vector<std::pair<std::string, std::string>>
45 request_as_query_parameters = {{"field1", "value1a"},
46 {"field1", "value1b"},
47 {"field2", "value2"}};
48 return request_as_query_parameters;
49 }());
50 return *request_as_query_parameters;
51 }
52
53 // Adds the "alt=proto" query parameters which specifies that the response
54 // should be formatted as a serialized proto. Adds the key-value pairs of
55 // |request_as_query_parameters| as query parameters.
56 // |request_as_query_parameters| is only non-null for GET requests.
UrlWithQueryParameters(const std::string & url,const base::Optional<std::vector<std::pair<std::string,std::string>>> & request_as_query_parameters)57 GURL UrlWithQueryParameters(
58 const std::string& url,
59 const base::Optional<std::vector<std::pair<std::string, std::string>>>&
60 request_as_query_parameters) {
61 GURL url_with_qp(url);
62
63 url_with_qp =
64 net::AppendQueryParameter(url_with_qp, kQueryParameterAlternateOutputKey,
65 kQueryParameterAlternateOutputProto);
66
67 if (request_as_query_parameters) {
68 for (const auto& key_value : *request_as_query_parameters) {
69 url_with_qp = net::AppendQueryParameter(url_with_qp, key_value.first,
70 key_value.second);
71 }
72 }
73
74 return url_with_qp;
75 }
76
77 } // namespace
78
79 class DeviceSyncCryptAuthApiCallFlowTest : public testing::Test {
80 protected:
DeviceSyncCryptAuthApiCallFlowTest()81 DeviceSyncCryptAuthApiCallFlowTest()
82 : shared_factory_(
83 base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
84 &test_url_loader_factory_)) {
85 flow_.SetPartialNetworkTrafficAnnotation(
86 PARTIAL_TRAFFIC_ANNOTATION_FOR_TESTS);
87 }
88
StartPostRequestApiCallFlow()89 void StartPostRequestApiCallFlow() {
90 StartPostRequestApiCallFlowWithSerializedRequest(kSerializedRequestProto);
91 }
92
StartPostRequestApiCallFlowWithSerializedRequest(const std::string & serialized_request)93 void StartPostRequestApiCallFlowWithSerializedRequest(
94 const std::string& serialized_request) {
95 flow_.StartPostRequest(
96 GURL(kRequestUrl), serialized_request, shared_factory_, kAccessToken,
97 base::BindOnce(&DeviceSyncCryptAuthApiCallFlowTest::OnResult,
98 base::Unretained(this)),
99 base::BindOnce(&DeviceSyncCryptAuthApiCallFlowTest::OnError,
100 base::Unretained(this)));
101 // A pending fetch for the API request should be created.
102 CheckCryptAuthHttpPostRequest(serialized_request);
103 }
104
StartGetRequestApiCallFlow()105 void StartGetRequestApiCallFlow() {
106 StartGetRequestApiCallFlowWithRequestAsQueryParameters(
107 GetTestRequestProtoAsQueryParameters());
108 }
109
StartGetRequestApiCallFlowWithRequestAsQueryParameters(const std::vector<std::pair<std::string,std::string>> & request_as_query_parameters)110 void StartGetRequestApiCallFlowWithRequestAsQueryParameters(
111 const std::vector<std::pair<std::string, std::string>>&
112 request_as_query_parameters) {
113 flow_.StartGetRequest(
114 GURL(kRequestUrl), request_as_query_parameters, shared_factory_,
115 kAccessToken,
116 base::BindOnce(&DeviceSyncCryptAuthApiCallFlowTest::OnResult,
117 base::Unretained(this)),
118 base::BindOnce(&DeviceSyncCryptAuthApiCallFlowTest::OnError,
119 base::Unretained(this)));
120 // A pending fetch for the API request should be created.
121 CheckCryptAuthHttpGetRequest(request_as_query_parameters);
122 }
123
OnResult(const std::string & result)124 void OnResult(const std::string& result) {
125 EXPECT_FALSE(result_ || network_error_);
126 result_.reset(new std::string(result));
127 }
128
OnError(NetworkRequestError network_error)129 void OnError(NetworkRequestError network_error) {
130 EXPECT_FALSE(result_ || network_error_);
131 network_error_.reset(new NetworkRequestError(network_error));
132 }
133
CheckCryptAuthHttpPostRequest(const std::string & serialized_request)134 void CheckCryptAuthHttpPostRequest(const std::string& serialized_request) {
135 const std::vector<network::TestURLLoaderFactory::PendingRequest>& pending =
136 *test_url_loader_factory_.pending_requests();
137 ASSERT_EQ(1u, pending.size());
138 const network::ResourceRequest& request = pending[0].request;
139
140 EXPECT_EQ(UrlWithQueryParameters(
141 kRequestUrl, base::nullopt /* request_as_query_parameters */),
142 request.url);
143
144 EXPECT_EQ(serialized_request, network::GetUploadData(request));
145
146 std::string content_type;
147 EXPECT_TRUE(request.headers.GetHeader(net::HttpRequestHeaders::kContentType,
148 &content_type));
149 EXPECT_EQ("application/x-protobuf", content_type);
150 }
151
CheckCryptAuthHttpGetRequest(const std::vector<std::pair<std::string,std::string>> & request_as_query_parameters)152 void CheckCryptAuthHttpGetRequest(
153 const std::vector<std::pair<std::string, std::string>>&
154 request_as_query_parameters) {
155 const std::vector<network::TestURLLoaderFactory::PendingRequest>& pending =
156 *test_url_loader_factory_.pending_requests();
157 ASSERT_EQ(1u, pending.size());
158 const network::ResourceRequest& request = pending[0].request;
159
160 EXPECT_EQ(UrlWithQueryParameters(kRequestUrl, request_as_query_parameters),
161 request.url);
162
163 // Expect no body.
164 EXPECT_TRUE(network::GetUploadData(request).empty());
165 EXPECT_FALSE(
166 request.headers.HasHeader(net::HttpRequestHeaders::kContentType));
167 }
168
169 // Responds to the current HTTP POST request. If the |error| is not net::OK,
170 // then the |response_code| and |response_string| are null.
CompleteCurrentPostRequest(net::Error error,base::Optional<int> response_code=base::nullopt,const base::Optional<std::string> & response_string=base::nullopt)171 void CompleteCurrentPostRequest(
172 net::Error error,
173 base::Optional<int> response_code = base::nullopt,
174 const base::Optional<std::string>& response_string = base::nullopt) {
175 network::URLLoaderCompletionStatus completion_status(error);
176 auto response_head = network::mojom::URLResponseHead::New();
177 std::string content;
178 if (error == net::OK) {
179 response_head = network::CreateURLResponseHead(
180 static_cast<net::HttpStatusCode>(*response_code));
181 content = *response_string;
182 }
183
184 // Use kUrlMatchPrefix flag to match URL without query parameters.
185 EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
186 GURL(kRequestUrl), completion_status, std::move(response_head), content,
187 network::TestURLLoaderFactory::ResponseMatchFlags::kUrlMatchPrefix));
188
189 task_environment_.RunUntilIdle();
190 EXPECT_TRUE(result_ || network_error_);
191 }
192
193 // Responds to the current HTTP GET request. If the |error| is not net::OK,
194 // then the |response_code| and |response_string| are null.
CompleteCurrentGetRequest(net::Error error,base::Optional<int> response_code=base::nullopt,const base::Optional<std::string> & response_string=base::nullopt)195 void CompleteCurrentGetRequest(
196 net::Error error,
197 base::Optional<int> response_code = base::nullopt,
198 const base::Optional<std::string>& response_string = base::nullopt) {
199 network::URLLoaderCompletionStatus completion_status(error);
200 auto response_head = network::mojom::URLResponseHead::New();
201 std::string content;
202 if (error == net::OK) {
203 response_head = network::CreateURLResponseHead(
204 static_cast<net::HttpStatusCode>(*response_code));
205 content = *response_string;
206 }
207
208 // Use kUrlMatchPrefix flag to match URL without query parameters.
209 EXPECT_TRUE(test_url_loader_factory_.SimulateResponseForPendingRequest(
210 GURL(kRequestUrl), completion_status, std::move(response_head), content,
211 network::TestURLLoaderFactory::ResponseMatchFlags::kUrlMatchPrefix));
212
213 task_environment_.RunUntilIdle();
214 EXPECT_TRUE(result_ || network_error_);
215 }
216
217 std::unique_ptr<std::string> result_;
218 std::unique_ptr<NetworkRequestError> network_error_;
219
220 private:
221 base::test::TaskEnvironment task_environment_;
222 network::TestURLLoaderFactory test_url_loader_factory_;
223 scoped_refptr<network::SharedURLLoaderFactory> shared_factory_;
224
225 CryptAuthApiCallFlow flow_;
226
227 DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthApiCallFlowTest);
228 };
229
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,PostRequestSuccess)230 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, PostRequestSuccess) {
231 StartPostRequestApiCallFlow();
232 CompleteCurrentPostRequest(net::OK, net::HTTP_OK, kSerializedResponseProto);
233 EXPECT_EQ(kSerializedResponseProto, *result_);
234 EXPECT_FALSE(network_error_);
235 }
236
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,GetRequestSuccess)237 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, GetRequestSuccess) {
238 StartGetRequestApiCallFlow();
239 CompleteCurrentGetRequest(net::OK, net::HTTP_OK, kSerializedResponseProto);
240 EXPECT_EQ(kSerializedResponseProto, *result_);
241 EXPECT_FALSE(network_error_);
242 }
243
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,PostRequestFailure)244 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, PostRequestFailure) {
245 StartPostRequestApiCallFlow();
246 CompleteCurrentPostRequest(net::ERR_FAILED);
247 EXPECT_FALSE(result_);
248 EXPECT_EQ(NetworkRequestError::kOffline, *network_error_);
249 }
250
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,GetRequestFailure)251 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, GetRequestFailure) {
252 StartGetRequestApiCallFlow();
253 CompleteCurrentPostRequest(net::ERR_FAILED);
254 EXPECT_FALSE(result_);
255 EXPECT_EQ(NetworkRequestError::kOffline, *network_error_);
256 }
257
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,RequestStatus500)258 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, RequestStatus500) {
259 StartPostRequestApiCallFlow();
260 CompleteCurrentPostRequest(net::OK, net::HTTP_INTERNAL_SERVER_ERROR,
261 "CryptAuth Meltdown.");
262 EXPECT_FALSE(result_);
263 EXPECT_EQ(NetworkRequestError::kInternalServerError, *network_error_);
264 }
265
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,GetRequestStatus500)266 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, GetRequestStatus500) {
267 StartGetRequestApiCallFlow();
268 CompleteCurrentPostRequest(net::OK, net::HTTP_INTERNAL_SERVER_ERROR,
269 "CryptAuth Meltdown.");
270 EXPECT_FALSE(result_);
271 EXPECT_EQ(NetworkRequestError::kInternalServerError, *network_error_);
272 }
273
274 // The empty string is a valid protocol buffer message serialization.
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,PostRequestWithNoBody)275 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, PostRequestWithNoBody) {
276 StartPostRequestApiCallFlowWithSerializedRequest(std::string());
277 CompleteCurrentPostRequest(net::OK, net::HTTP_OK, kSerializedResponseProto);
278 EXPECT_EQ(kSerializedResponseProto, *result_);
279 EXPECT_FALSE(network_error_);
280 }
281
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,GetRequestWithNoQueryParameters)282 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, GetRequestWithNoQueryParameters) {
283 StartGetRequestApiCallFlowWithRequestAsQueryParameters(
284 {} /* request_as_query_parameters */);
285 CompleteCurrentPostRequest(net::OK, net::HTTP_OK, kSerializedResponseProto);
286 EXPECT_EQ(kSerializedResponseProto, *result_);
287 EXPECT_FALSE(network_error_);
288 }
289
290 // The empty string is a valid protocol buffer message serialization.
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,PostResponseWithNoBody)291 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, PostResponseWithNoBody) {
292 StartPostRequestApiCallFlow();
293 CompleteCurrentPostRequest(net::OK, net::HTTP_OK, std::string());
294 EXPECT_EQ(std::string(), *result_);
295 EXPECT_FALSE(network_error_);
296 }
297
298 // The empty string is a valid protocol buffer message serialization.
TEST_F(DeviceSyncCryptAuthApiCallFlowTest,GetResponseWithNoBody)299 TEST_F(DeviceSyncCryptAuthApiCallFlowTest, GetResponseWithNoBody) {
300 StartGetRequestApiCallFlow();
301 CompleteCurrentPostRequest(net::OK, net::HTTP_OK, std::string());
302 EXPECT_EQ(std::string(), *result_);
303 EXPECT_FALSE(network_error_);
304 }
305
306 } // namespace device_sync
307
308 } // namespace chromeos
309