1 // Copyright (c) 2015 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 "chrome/browser/chromeos/policy/upload_job.h"
6
7 #include <stddef.h>
8
9 #include <memory>
10 #include <set>
11 #include <utility>
12
13 #include "base/bind.h"
14 #include "base/callback_helpers.h"
15 #include "base/containers/queue.h"
16 #include "base/location.h"
17 #include "base/macros.h"
18 #include "base/memory/ptr_util.h"
19 #include "base/run_loop.h"
20 #include "base/threading/thread_task_runner_handle.h"
21 #include "base/time/time.h"
22 #include "chrome/browser/chromeos/policy/upload_job_impl.h"
23 #include "content/public/test/browser_task_environment.h"
24 #include "google_apis/gaia/core_account_id.h"
25 #include "google_apis/gaia/fake_oauth2_access_token_manager.h"
26 #include "google_apis/gaia/google_service_auth_error.h"
27 #include "google_apis/gaia/oauth2_access_token_fetcher_impl.h"
28 #include "google_apis/gaia/oauth2_access_token_manager.h"
29 #include "net/http/http_status_code.h"
30 #include "net/test/embedded_test_server/embedded_test_server.h"
31 #include "net/test/embedded_test_server/http_request.h"
32 #include "net/test/embedded_test_server/http_response.h"
33 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
34 #include "services/network/test/test_shared_url_loader_factory.h"
35 #include "testing/gtest/include/gtest/gtest.h"
36
37 namespace policy {
38
39 namespace {
40
41 const char kUploadPath[] = "/upload";
42 const char kRobotAccountId[] = "robot@gmail.com";
43 const char kCustomField1[] = "customfield1";
44 const char kCustomField2[] = "customfield2";
45 const char kTestPayload1[] = "**||--||PAYLOAD1||--||**";
46 const char kTestPayload2[] = "**||--||PAYLOAD2||--||**";
47 const char kTokenExpired[] = "EXPIRED_TOKEN";
48 const char kTokenInvalid[] = "INVALID_TOKEN";
49 const char kTokenValid[] = "VALID_TOKEN";
50
51 class RepeatingMimeBoundaryGenerator
52 : public UploadJobImpl::MimeBoundaryGenerator {
53 public:
RepeatingMimeBoundaryGenerator(char character)54 explicit RepeatingMimeBoundaryGenerator(char character)
55 : character_(character) {}
~RepeatingMimeBoundaryGenerator()56 ~RepeatingMimeBoundaryGenerator() override {}
57
58 // MimeBoundaryGenerator:
GenerateBoundary() const59 std::string GenerateBoundary() const override {
60 const int kMimeBoundarySize = 32;
61 return std::string(kMimeBoundarySize, character_);
62 }
63
64 private:
65 const char character_;
66
67 DISALLOW_COPY_AND_ASSIGN(RepeatingMimeBoundaryGenerator);
68 };
69
70 class FakeOAuth2AccessTokenManagerWithCaching
71 : public FakeOAuth2AccessTokenManager {
72 public:
73 explicit FakeOAuth2AccessTokenManagerWithCaching(
74 OAuth2AccessTokenManager::Delegate* delegate);
75 ~FakeOAuth2AccessTokenManagerWithCaching() override;
76
77 // FakeOAuth2AccessTokenManager:
78 void FetchOAuth2Token(
79 OAuth2AccessTokenManager::RequestImpl* request,
80 const CoreAccountId& account_id,
81 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
82 const std::string& client_id,
83 const std::string& client_secret,
84 const OAuth2AccessTokenManager::ScopeSet& scopes) override;
85 void InvalidateAccessTokenImpl(
86 const CoreAccountId& account_id,
87 const std::string& client_id,
88 const OAuth2AccessTokenManager::ScopeSet& scopes,
89 const std::string& access_token) override;
90
91 void AddTokenToQueue(const std::string& token);
92 bool IsTokenValid(const std::string& token) const;
93 void SetTokenValid(const std::string& token);
94 void SetTokenInvalid(const std::string& token);
95
96 private:
97 base::queue<std::string> token_replies_;
98 std::set<std::string> valid_tokens_;
99
100 DISALLOW_COPY_AND_ASSIGN(FakeOAuth2AccessTokenManagerWithCaching);
101 };
102
103 FakeOAuth2AccessTokenManagerWithCaching::
FakeOAuth2AccessTokenManagerWithCaching(OAuth2AccessTokenManager::Delegate * delegate)104 FakeOAuth2AccessTokenManagerWithCaching(
105 OAuth2AccessTokenManager::Delegate* delegate)
106 : FakeOAuth2AccessTokenManager(delegate) {}
107
108 FakeOAuth2AccessTokenManagerWithCaching::
109 ~FakeOAuth2AccessTokenManagerWithCaching() = default;
110
FetchOAuth2Token(OAuth2AccessTokenManager::RequestImpl * request,const CoreAccountId & account_id,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,const std::string & client_id,const std::string & client_secret,const OAuth2AccessTokenManager::ScopeSet & scopes)111 void FakeOAuth2AccessTokenManagerWithCaching::FetchOAuth2Token(
112 OAuth2AccessTokenManager::RequestImpl* request,
113 const CoreAccountId& account_id,
114 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
115 const std::string& client_id,
116 const std::string& client_secret,
117 const OAuth2AccessTokenManager::ScopeSet& scopes) {
118 GoogleServiceAuthError response_error =
119 GoogleServiceAuthError::AuthErrorNone();
120 OAuth2AccessTokenConsumer::TokenResponse token_response;
121 if (token_replies_.empty()) {
122 response_error =
123 GoogleServiceAuthError::FromServiceError("Service unavailable.");
124 } else {
125 token_response.access_token = token_replies_.front();
126 token_response.expiration_time = base::Time::Now();
127 token_replies_.pop();
128 }
129 base::ThreadTaskRunnerHandle::Get()->PostTask(
130 FROM_HERE,
131 base::BindOnce(&OAuth2AccessTokenManager::RequestImpl::InformConsumer,
132 request->AsWeakPtr(), response_error, token_response));
133 }
134
AddTokenToQueue(const std::string & token)135 void FakeOAuth2AccessTokenManagerWithCaching::AddTokenToQueue(
136 const std::string& token) {
137 token_replies_.push(token);
138 }
139
IsTokenValid(const std::string & token) const140 bool FakeOAuth2AccessTokenManagerWithCaching::IsTokenValid(
141 const std::string& token) const {
142 return valid_tokens_.find(token) != valid_tokens_.end();
143 }
144
SetTokenValid(const std::string & token)145 void FakeOAuth2AccessTokenManagerWithCaching::SetTokenValid(
146 const std::string& token) {
147 valid_tokens_.insert(token);
148 }
149
SetTokenInvalid(const std::string & token)150 void FakeOAuth2AccessTokenManagerWithCaching::SetTokenInvalid(
151 const std::string& token) {
152 valid_tokens_.erase(token);
153 }
154
InvalidateAccessTokenImpl(const CoreAccountId & account_id,const std::string & client_id,const OAuth2AccessTokenManager::ScopeSet & scopes,const std::string & access_token)155 void FakeOAuth2AccessTokenManagerWithCaching::InvalidateAccessTokenImpl(
156 const CoreAccountId& account_id,
157 const std::string& client_id,
158 const OAuth2AccessTokenManager::ScopeSet& scopes,
159 const std::string& access_token) {
160 SetTokenInvalid(access_token);
161 }
162
163 class FakeOAuth2AccessTokenManagerDelegate
164 : public OAuth2AccessTokenManager::Delegate {
165 public:
166 FakeOAuth2AccessTokenManagerDelegate() = default;
167 ~FakeOAuth2AccessTokenManagerDelegate() override = default;
168
169 // OAuth2AccessTokenManager::Delegate:
CreateAccessTokenFetcher(const CoreAccountId & account_id,scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,OAuth2AccessTokenConsumer * consumer)170 std::unique_ptr<OAuth2AccessTokenFetcher> CreateAccessTokenFetcher(
171 const CoreAccountId& account_id,
172 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
173 OAuth2AccessTokenConsumer* consumer) override {
174 EXPECT_EQ(CoreAccountId(kRobotAccountId), account_id);
175 return std::make_unique<OAuth2AccessTokenFetcherImpl>(
176 consumer, url_loader_factory, "fake_refresh_token");
177 }
178
HasRefreshToken(const CoreAccountId & account_id) const179 bool HasRefreshToken(const CoreAccountId& account_id) const override {
180 return CoreAccountId(kRobotAccountId) == account_id;
181 }
182 };
183
184 } // namespace
185
186 class UploadJobTestBase : public testing::Test, public UploadJob::Delegate {
187 public:
UploadJobTestBase()188 UploadJobTestBase()
189 : task_environment_(content::BrowserTaskEnvironment::IO_MAINLOOP),
190 access_token_manager_(&token_manager_delegate_) {}
191
192 // policy::UploadJob::Delegate:
OnSuccess()193 void OnSuccess() override {
194 if (!expected_error_)
195 run_loop_.Quit();
196 else
197 FAIL();
198 }
199
200 // policy::UploadJob::Delegate:
OnFailure(UploadJob::ErrorCode error_code)201 void OnFailure(UploadJob::ErrorCode error_code) override {
202 if (expected_error_ && *expected_error_.get() == error_code)
203 run_loop_.Quit();
204 else
205 FAIL();
206 }
207
GetServerURL() const208 const GURL GetServerURL() const { return test_server_.GetURL(kUploadPath); }
209
SetExpectedError(std::unique_ptr<UploadJob::ErrorCode> expected_error)210 void SetExpectedError(std::unique_ptr<UploadJob::ErrorCode> expected_error) {
211 expected_error_ = std::move(expected_error);
212 }
213
214 // testing::Test:
SetUp()215 void SetUp() override {
216 url_loader_factory_ =
217 base::MakeRefCounted<network::TestSharedURLLoaderFactory>();
218 ASSERT_TRUE(test_server_.Start());
219 // Set retry delay to prevent timeouts
220 policy::UploadJobImpl::SetRetryDelayForTesting(0);
221 }
222
223 // testing::Test:
TearDown()224 void TearDown() override {
225 ASSERT_TRUE(test_server_.ShutdownAndWaitUntilComplete());
226 }
227
228 protected:
PrepareUploadJob(std::unique_ptr<UploadJobImpl::MimeBoundaryGenerator> mime_boundary_generator)229 std::unique_ptr<UploadJob> PrepareUploadJob(
230 std::unique_ptr<UploadJobImpl::MimeBoundaryGenerator>
231 mime_boundary_generator) {
232 std::unique_ptr<UploadJob> upload_job(new UploadJobImpl(
233 GetServerURL(), CoreAccountId(kRobotAccountId), &access_token_manager_,
234 url_loader_factory_, this, std::move(mime_boundary_generator),
235 TRAFFIC_ANNOTATION_FOR_TESTS, base::ThreadTaskRunnerHandle::Get()));
236
237 std::map<std::string, std::string> header_entries;
238 header_entries.insert(std::make_pair(kCustomField1, "CUSTOM1"));
239 std::unique_ptr<std::string> data(new std::string(kTestPayload1));
240 upload_job->AddDataSegment("Name1", "file1.ext", header_entries,
241 std::move(data));
242
243 header_entries.insert(std::make_pair(kCustomField2, "CUSTOM2"));
244 std::unique_ptr<std::string> data2(new std::string(kTestPayload2));
245 upload_job->AddDataSegment("Name2", "", header_entries, std::move(data2));
246 return upload_job;
247 }
248
249 content::BrowserTaskEnvironment task_environment_;
250 base::RunLoop run_loop_;
251 net::EmbeddedTestServer test_server_;
252 scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
253 FakeOAuth2AccessTokenManagerDelegate token_manager_delegate_;
254 FakeOAuth2AccessTokenManagerWithCaching access_token_manager_;
255
256 std::unique_ptr<UploadJob::ErrorCode> expected_error_;
257 };
258
259 class UploadFlowTest : public UploadJobTestBase {
260 public:
UploadFlowTest()261 UploadFlowTest() {}
262
263 // UploadJobTestBase:
SetUp()264 void SetUp() override {
265 test_server_.RegisterRequestHandler(
266 base::Bind(&UploadFlowTest::HandlePostRequest, base::Unretained(this)));
267 UploadJobTestBase::SetUp();
268 upload_attempt_count_ = 0;
269 }
270
271 // Sets the response code which will be returned when no other problems occur.
272 // Default is |net::HTTP_OK|
SetResponseDefaultStatusCode(net::HttpStatusCode code)273 void SetResponseDefaultStatusCode(net::HttpStatusCode code) {
274 default_status_code_ = code;
275 }
276
HandlePostRequest(const net::test_server::HttpRequest & request)277 std::unique_ptr<net::test_server::HttpResponse> HandlePostRequest(
278 const net::test_server::HttpRequest& request) {
279 upload_attempt_count_++;
280 EXPECT_TRUE(request.headers.find("Authorization") != request.headers.end());
281 const std::string authorization_header =
282 request.headers.at("Authorization");
283 std::unique_ptr<net::test_server::BasicHttpResponse> response(
284 new net::test_server::BasicHttpResponse);
285 const size_t pos = authorization_header.find(" ");
286 if (pos == std::string::npos) {
287 response->set_code(net::HTTP_UNAUTHORIZED);
288 return std::move(response);
289 }
290
291 const std::string token = authorization_header.substr(pos + 1);
292 response->set_code(access_token_manager_.IsTokenValid(token)
293 ? default_status_code_
294 : net::HTTP_UNAUTHORIZED);
295 return std::move(response);
296 }
297
298 protected:
299 int upload_attempt_count_;
300 net::HttpStatusCode default_status_code_ = net::HTTP_OK;
301 };
302
TEST_F(UploadFlowTest,SuccessfulUpload)303 TEST_F(UploadFlowTest, SuccessfulUpload) {
304 access_token_manager_.SetTokenValid(kTokenValid);
305 access_token_manager_.AddTokenToQueue(kTokenValid);
306 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
307 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
308 upload_job->Start();
309 run_loop_.Run();
310 ASSERT_EQ(1, upload_attempt_count_);
311 }
312
TEST_F(UploadFlowTest,TokenExpired)313 TEST_F(UploadFlowTest, TokenExpired) {
314 access_token_manager_.SetTokenValid(kTokenValid);
315 access_token_manager_.AddTokenToQueue(kTokenExpired);
316 access_token_manager_.AddTokenToQueue(kTokenValid);
317 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
318 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
319 upload_job->Start();
320 run_loop_.Run();
321 ASSERT_EQ(2, upload_attempt_count_);
322 }
323
TEST_F(UploadFlowTest,TokenInvalid)324 TEST_F(UploadFlowTest, TokenInvalid) {
325 access_token_manager_.AddTokenToQueue(kTokenInvalid);
326 access_token_manager_.AddTokenToQueue(kTokenInvalid);
327 access_token_manager_.AddTokenToQueue(kTokenInvalid);
328 access_token_manager_.AddTokenToQueue(kTokenInvalid);
329 SetExpectedError(std::unique_ptr<UploadJob::ErrorCode>(
330 new UploadJob::ErrorCode(UploadJob::AUTHENTICATION_ERROR)));
331
332 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
333 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
334 upload_job->Start();
335 run_loop_.Run();
336 ASSERT_EQ(4, upload_attempt_count_);
337 }
338
TEST_F(UploadFlowTest,TokenMultipleTries)339 TEST_F(UploadFlowTest, TokenMultipleTries) {
340 access_token_manager_.SetTokenValid(kTokenValid);
341 access_token_manager_.AddTokenToQueue(kTokenInvalid);
342 access_token_manager_.AddTokenToQueue(kTokenInvalid);
343 access_token_manager_.AddTokenToQueue(kTokenValid);
344
345 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
346 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
347 upload_job->Start();
348 run_loop_.Run();
349 ASSERT_EQ(3, upload_attempt_count_);
350 }
351
TEST_F(UploadFlowTest,TokenFetchFailure)352 TEST_F(UploadFlowTest, TokenFetchFailure) {
353 SetExpectedError(std::unique_ptr<UploadJob::ErrorCode>(
354 new UploadJob::ErrorCode(UploadJob::AUTHENTICATION_ERROR)));
355
356 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
357 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
358 upload_job->Start();
359 run_loop_.Run();
360 // Without a token we don't try to upload
361 ASSERT_EQ(0, upload_attempt_count_);
362 }
363
TEST_F(UploadFlowTest,InternalServerError)364 TEST_F(UploadFlowTest, InternalServerError) {
365 SetResponseDefaultStatusCode(net::HTTP_INTERNAL_SERVER_ERROR);
366 access_token_manager_.SetTokenValid(kTokenValid);
367 access_token_manager_.AddTokenToQueue(kTokenValid);
368
369 SetExpectedError(std::unique_ptr<UploadJob::ErrorCode>(
370 new UploadJob::ErrorCode(UploadJob::SERVER_ERROR)));
371
372 std::unique_ptr<UploadJob> upload_job = PrepareUploadJob(
373 base::WrapUnique(new UploadJobImpl::RandomMimeBoundaryGenerator));
374 upload_job->Start();
375 run_loop_.Run();
376 // kMaxAttempts
377 ASSERT_EQ(4, upload_attempt_count_);
378 }
379
380 class UploadRequestTest : public UploadJobTestBase {
381 public:
UploadRequestTest()382 UploadRequestTest() {}
383
384 // UploadJobTestBase:
SetUp()385 void SetUp() override {
386 test_server_.RegisterRequestHandler(base::Bind(
387 &UploadRequestTest::HandlePostRequest, base::Unretained(this)));
388 UploadJobTestBase::SetUp();
389 }
390
HandlePostRequest(const net::test_server::HttpRequest & request)391 std::unique_ptr<net::test_server::HttpResponse> HandlePostRequest(
392 const net::test_server::HttpRequest& request) {
393 std::unique_ptr<net::test_server::BasicHttpResponse> response(
394 new net::test_server::BasicHttpResponse);
395 response->set_code(net::HTTP_OK);
396 EXPECT_EQ(expected_content_, request.content);
397 return std::move(response);
398 }
399
SetExpectedRequestContent(const std::string & expected_content)400 void SetExpectedRequestContent(const std::string& expected_content) {
401 expected_content_ = expected_content;
402 }
403
404 protected:
405 std::string expected_content_;
406 };
407
TEST_F(UploadRequestTest,TestRequestStructure)408 TEST_F(UploadRequestTest, TestRequestStructure) {
409 access_token_manager_.SetTokenValid(kTokenValid);
410 access_token_manager_.AddTokenToQueue(kTokenValid);
411 std::unique_ptr<UploadJob> upload_job =
412 PrepareUploadJob(std::make_unique<RepeatingMimeBoundaryGenerator>('A'));
413 SetExpectedRequestContent(
414 "--AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n"
415 "Content-Disposition: form-data; "
416 "name=\"Name1\"; filename=\"file1.ext\"\r\n"
417 "customfield1: CUSTOM1\r\n"
418 "\r\n"
419 "**||--||PAYLOAD1||--||**\r\n"
420 "--AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\r\n"
421 "Content-Disposition: form-data; name=\"Name2\"\r\n"
422 "customfield1: CUSTOM1\r\n"
423 "customfield2: CUSTOM2\r\n"
424 "\r\n"
425 "**||--||PAYLOAD2||--||**\r\n--"
426 "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA--\r\n");
427
428 upload_job->Start();
429 run_loop_.Run();
430 }
431
432 } // namespace policy
433