1// Copyright (c) 2012 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// This file defines a unit test for the profile's token service.
6
7#include "chrome/browser/signin/token_service_unittest.h"
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/command_line.h"
12#include "base/run_loop.h"
13#include "chrome/browser/chrome_notification_types.h"
14#include "chrome/browser/signin/token_service_factory.h"
15#include "chrome/browser/webdata/token_web_data.h"
16#include "chrome/common/chrome_switches.h"
17#include "components/webdata/encryptor/encryptor.h"
18#include "google_apis/gaia/gaia_constants.h"
19#include "google_apis/gaia/mock_url_fetcher_factory.h"
20#include "net/url_request/test_url_fetcher_factory.h"
21
22TokenAvailableTracker::TokenAvailableTracker() {}
23
24TokenAvailableTracker::~TokenAvailableTracker() {}
25
26void TokenAvailableTracker::Observe(
27    int type,
28    const content::NotificationSource& source,
29    const content::NotificationDetails& details) {
30  TestNotificationTracker::Observe(type, source, details);
31  if (type == chrome::NOTIFICATION_TOKEN_AVAILABLE) {
32    content::Details<const TokenService::TokenAvailableDetails> full = details;
33    details_ = *full.ptr();
34  }
35}
36
37TokenFailedTracker::TokenFailedTracker() {}
38
39TokenFailedTracker::~TokenFailedTracker() {}
40
41void TokenFailedTracker::Observe(int type,
42                                 const content::NotificationSource& source,
43                                 const content::NotificationDetails& details) {
44  TestNotificationTracker::Observe(type, source, details);
45  if (type == chrome::NOTIFICATION_TOKEN_REQUEST_FAILED) {
46    content::Details<const TokenService::TokenRequestFailedDetails> full =
47        details;
48    details_ = *full.ptr();
49  }
50}
51
52TokenServiceTestHarness::TokenServiceTestHarness() {}
53
54TokenServiceTestHarness::~TokenServiceTestHarness() {}
55
56void TokenServiceTestHarness::SetUp() {
57#if defined(OS_MACOSX)
58  Encryptor::UseMockKeychain(true);
59#endif
60  credentials_.sid = "sid";
61  credentials_.lsid = "lsid";
62  credentials_.token = "token";
63  credentials_.data = "data";
64  oauth_token_ = "oauth";
65  oauth_secret_ = "secret";
66
67  profile_.reset(new TestingProfile());
68  profile_->CreateWebDataService();
69
70  // Force the loading of the WebDataService.
71  TokenWebData::FromBrowserContext(profile_.get());
72  base::RunLoop().RunUntilIdle();
73
74  service_ = TokenServiceFactory::GetForProfile(profile_.get());
75
76  success_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_AVAILABLE,
77                             content::Source<TokenService>(service_));
78  failure_tracker_.ListenFor(chrome::NOTIFICATION_TOKEN_REQUEST_FAILED,
79                             content::Source<TokenService>(service_));
80
81  service()->Initialize("test", profile_.get());
82}
83
84void TokenServiceTestHarness::TearDown() {
85  // You have to destroy the profile before the threads are shut down.
86  profile_.reset();
87}
88
89void TokenServiceTestHarness::UpdateCredentialsOnService() {
90  service()->UpdateCredentials(credentials_);
91}
92
93class TokenServiceTest : public TokenServiceTestHarness {
94 public:
95  virtual void SetUp() {
96    TokenServiceTestHarness::SetUp();
97    UpdateCredentialsOnService();
98  }
99 protected:
100  void TestLoadSingleToken(
101      std::map<std::string, std::string>* db_tokens,
102      std::map<std::string, std::string>* memory_tokens,
103      const std::string& service_name) {
104    std::string token = service_name + "_token";
105    (*db_tokens)[service_name] = token;
106    size_t prev_success_size = success_tracker()->size();
107    service()->LoadTokensIntoMemory(*db_tokens, memory_tokens);
108
109    // Check notification.
110    EXPECT_EQ(prev_success_size + 1, success_tracker()->size());
111    TokenService::TokenAvailableDetails details = success_tracker()->details();
112    EXPECT_EQ(details.service(), service_name);
113    EXPECT_EQ(details.token(), token);
114    // Check memory tokens.
115    EXPECT_EQ(1U, memory_tokens->count(service_name));
116    EXPECT_EQ((*memory_tokens)[service_name], token);
117  }
118};
119
120TEST_F(TokenServiceTest, SanityCheck) {
121  EXPECT_FALSE(service()->HasTokenForService("nonexistent service"));
122  EXPECT_FALSE(service()->TokensLoadedFromDB());
123}
124
125TEST_F(TokenServiceTest, NoToken) {
126  EXPECT_FALSE(service()->HasTokenForService("nonexistent service"));
127  EXPECT_EQ(service()->GetTokenForService("nonexistent service"),
128            std::string());
129}
130
131TEST_F(TokenServiceTest, NotificationSuccess) {
132  EXPECT_EQ(0U, success_tracker()->size());
133  EXPECT_EQ(0U, failure_tracker()->size());
134  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
135  EXPECT_EQ(1U, success_tracker()->size());
136  EXPECT_EQ(0U, failure_tracker()->size());
137
138  TokenService::TokenAvailableDetails details = success_tracker()->details();
139  // MSVC doesn't like this comparison as EQ.
140  EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
141  EXPECT_EQ(details.token(), "token");
142}
143
144TEST_F(TokenServiceTest, NotificationOAuthLoginTokenSuccess) {
145  EXPECT_EQ(0U, success_tracker()->size());
146  EXPECT_EQ(0U, failure_tracker()->size());
147  service()->OnClientOAuthSuccess(
148      GaiaAuthConsumer::ClientOAuthResult("rt1", "at1", 3600));
149  EXPECT_EQ(1U, success_tracker()->size());
150  EXPECT_EQ(0U, failure_tracker()->size());
151
152  TokenService::TokenAvailableDetails details = success_tracker()->details();
153  // MSVC doesn't like this comparison as EQ.
154  EXPECT_TRUE(details.service() ==
155      GaiaConstants::kGaiaOAuth2LoginRefreshToken);
156  EXPECT_EQ(details.token(), "rt1");
157}
158
159TEST_F(TokenServiceTest, NotificationFailed) {
160  EXPECT_EQ(0U, success_tracker()->size());
161  EXPECT_EQ(0U, failure_tracker()->size());
162  GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
163  service()->OnIssueAuthTokenFailure(GaiaConstants::kSyncService, error);
164  EXPECT_EQ(0U, success_tracker()->size());
165  EXPECT_EQ(1U, failure_tracker()->size());
166
167  TokenService::TokenRequestFailedDetails details =
168      failure_tracker()->details();
169  // MSVC doesn't like this comparison as EQ.
170  EXPECT_TRUE(details.service() == GaiaConstants::kSyncService);
171  EXPECT_TRUE(details.error() == error);  // Struct has no print function.
172}
173
174TEST_F(TokenServiceTest, NotificationOAuthLoginTokenFailed) {
175  EXPECT_EQ(0U, success_tracker()->size());
176  EXPECT_EQ(0U, failure_tracker()->size());
177  GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED);
178  service()->OnClientOAuthFailure(error);
179  EXPECT_EQ(0U, success_tracker()->size());
180  EXPECT_EQ(1U, failure_tracker()->size());
181
182  TokenService::TokenRequestFailedDetails details =
183      failure_tracker()->details();
184
185  // MSVC doesn't like this comparison as EQ.
186  EXPECT_TRUE(details.service() ==
187      GaiaConstants::kGaiaOAuth2LoginRefreshToken);
188  EXPECT_TRUE(details.error() == error);  // Struct has no print function.
189}
190
191TEST_F(TokenServiceTest, OnTokenSuccessUpdate) {
192  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
193  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
194  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
195            "token");
196
197  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token2");
198  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
199  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
200            "token2");
201
202  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService,
203                                     std::string());
204  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
205  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService), "");
206}
207
208TEST_F(TokenServiceTest, OnOAuth2LoginTokenSuccessUpdate) {
209  EXPECT_FALSE(service()->HasOAuthLoginToken());
210
211  service()->OnClientOAuthSuccess(
212      GaiaAuthConsumer::ClientOAuthResult("rt1", "at1", 3600));
213  EXPECT_TRUE(service()->HasOAuthLoginToken());
214  EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt1");
215
216  service()->OnClientOAuthSuccess(
217      GaiaAuthConsumer::ClientOAuthResult("rt2", "at2", 3600));
218  EXPECT_TRUE(service()->HasOAuthLoginToken());
219  EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt2");
220
221  service()->OnClientOAuthSuccess(
222      GaiaAuthConsumer::ClientOAuthResult("rt3", "at3", 3600));
223  EXPECT_TRUE(service()->HasOAuthLoginToken());
224  EXPECT_EQ(service()->GetOAuth2LoginRefreshToken(), "rt3");
225}
226
227TEST_F(TokenServiceTest, OnTokenSuccess) {
228  // Don't "start fetching", just go ahead and issue the callback.
229  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
230  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
231  // Gaia returns the entire result as the token so while this is a shared
232  // result with ClientLogin, it doesn't matter, we should still get it back.
233  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
234            "token");
235}
236
237TEST_F(TokenServiceTest, Reset) {
238  net::TestURLFetcherFactory factory;
239  service()->StartFetchingTokens();
240  // You have to call delegates by hand with the test fetcher,
241  // Let's pretend only one returned.
242
243  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "eraseme");
244  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
245  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
246            "eraseme");
247
248  service()->ResetCredentialsInMemory();
249  EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
250
251  // Now start using it again.
252  UpdateCredentialsOnService();
253  service()->StartFetchingTokens();
254
255  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
256
257  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService),
258            "token");
259}
260
261TEST_F(TokenServiceTest, FullIntegration) {
262  std::string result = "SID=sid\nLSID=lsid\nAuth=auth\n";
263
264  {
265    MockURLFetcherFactory<MockFetcher> factory;
266    factory.set_results(result);
267    EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
268    service()->StartFetchingTokens();
269  }
270
271  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
272  // Gaia returns the entire result as the token so while this is a shared
273  // result with ClientLogin, it doesn't matter, we should still get it back.
274  EXPECT_EQ(service()->GetTokenForService(GaiaConstants::kSyncService), result);
275
276  service()->ResetCredentialsInMemory();
277  EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
278}
279
280TEST_F(TokenServiceTest, LoadTokensIntoMemoryBasic) {
281  // Validate that the method sets proper data in notifications and map.
282  std::map<std::string, std::string> db_tokens;
283  std::map<std::string, std::string> memory_tokens;
284
285  EXPECT_FALSE(service()->TokensLoadedFromDB());
286  service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
287  EXPECT_TRUE(db_tokens.empty());
288  EXPECT_TRUE(memory_tokens.empty());
289  EXPECT_EQ(0U, success_tracker()->size());
290
291  std::vector<std::string> services;
292  TokenService::GetServiceNames(&services);
293  for (std::vector<std::string>::const_iterator i = services.begin();
294       i != services.end(); ++i) {
295    const std::string& service = *i;
296    TestLoadSingleToken(&db_tokens, &memory_tokens, service);
297  }
298  std::string service = GaiaConstants::kGaiaOAuth2LoginRefreshToken;
299  TestLoadSingleToken(&db_tokens, &memory_tokens, service);
300}
301
302TEST_F(TokenServiceTest, LoadTokensIntoMemoryAdvanced) {
303  // LoadTokensIntoMemory should avoid setting tokens already in the
304  // token map.
305  std::map<std::string, std::string> db_tokens;
306  std::map<std::string, std::string> memory_tokens;
307
308  db_tokens["ignore"] = "token";
309
310  service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
311  EXPECT_TRUE(memory_tokens.empty());
312  db_tokens[GaiaConstants::kSyncService] = "pepper";
313
314  service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
315  EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
316  EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
317  EXPECT_EQ(1U, success_tracker()->size());
318  success_tracker()->Reset();
319
320  // SyncService token is already in memory. Pretend we got it off
321  // the disk as well, but an older token.
322  db_tokens[GaiaConstants::kSyncService] = "ignoreme";
323  service()->LoadTokensIntoMemory(db_tokens, &memory_tokens);
324
325  EXPECT_EQ(1U, memory_tokens.size());
326  EXPECT_EQ(0U, success_tracker()->size());
327  EXPECT_EQ(1U, memory_tokens.count(GaiaConstants::kSyncService));
328  EXPECT_EQ(memory_tokens[GaiaConstants::kSyncService], "pepper");
329}
330
331TEST_F(TokenServiceTest, WebDBLoadIntegration) {
332  service()->LoadTokensFromDB();
333  base::RunLoop().RunUntilIdle();
334  EXPECT_TRUE(service()->TokensLoadedFromDB());
335  EXPECT_EQ(0U, success_tracker()->size());
336
337  // Should result in DB write.
338  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
339  EXPECT_EQ(1U, success_tracker()->size());
340
341  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
342  // Clean slate.
343  service()->ResetCredentialsInMemory();
344  success_tracker()->Reset();
345  EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
346
347  service()->LoadTokensFromDB();
348  base::RunLoop().RunUntilIdle();
349
350  EXPECT_EQ(1U, success_tracker()->size());
351  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
352}
353
354TEST_F(TokenServiceTest, MultipleLoadResetIntegration) {
355  // Should result in DB write.
356  service()->OnIssueAuthTokenSuccess(GaiaConstants::kSyncService, "token");
357  service()->ResetCredentialsInMemory();
358  success_tracker()->Reset();
359  EXPECT_FALSE(service()->HasTokenForService(GaiaConstants::kSyncService));
360
361  EXPECT_FALSE(service()->TokensLoadedFromDB());
362  service()->LoadTokensFromDB();
363  base::RunLoop().RunUntilIdle();
364  EXPECT_TRUE(service()->TokensLoadedFromDB());
365
366  service()->LoadTokensFromDB();  // Should do nothing.
367  base::RunLoop().RunUntilIdle();
368  EXPECT_TRUE(service()->TokensLoadedFromDB());
369
370  EXPECT_EQ(1U, success_tracker()->size());
371  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
372
373  // Reset it one more time so there's no surprises.
374  service()->ResetCredentialsInMemory();
375  EXPECT_FALSE(service()->TokensLoadedFromDB());
376  success_tracker()->Reset();
377
378  service()->LoadTokensFromDB();
379  base::RunLoop().RunUntilIdle();
380  EXPECT_TRUE(service()->TokensLoadedFromDB());
381
382  EXPECT_EQ(1U, success_tracker()->size());
383  EXPECT_TRUE(service()->HasTokenForService(GaiaConstants::kSyncService));
384}
385
386#ifndef NDEBUG
387class TokenServiceCommandLineTest : public TokenServiceTestHarness {
388 public:
389  virtual void SetUp() {
390    CommandLine original_cl(*CommandLine::ForCurrentProcess());
391    CommandLine::ForCurrentProcess()->AppendSwitchASCII(
392        switches::kSetToken, "my_service:my_value");
393    TokenServiceTestHarness::SetUp();
394    UpdateCredentialsOnService();
395
396    *CommandLine::ForCurrentProcess() = original_cl;
397  }
398};
399
400TEST_F(TokenServiceCommandLineTest, TestValueOverride) {
401  EXPECT_TRUE(service()->HasTokenForService("my_service"));
402  EXPECT_EQ("my_value", service()->GetTokenForService("my_service"));
403}
404#endif   // ifndef NDEBUG
405