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