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