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