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 "chrome/browser/services/gcm/gcm_account_tracker.h" 6 7 #include <algorithm> 8 #include <vector> 9 10 #include "base/time/time.h" 11 #include "google_apis/gaia/google_service_auth_error.h" 12 13 namespace gcm { 14 15 namespace { 16 const char kGCMGroupServerScope[] = "https://www.googleapis.com/auth/gcm"; 17 const char kGCMCheckinServerScope[] = 18 "https://www.googleapis.com/auth/android_checkin"; 19 const char kGCMAccountTrackerName[] = "gcm_account_tracker"; 20 } // namespace 21 22 GCMAccountTracker::AccountInfo::AccountInfo(const std::string& email, 23 AccountState state) 24 : email(email), state(state) { 25 } 26 27 GCMAccountTracker::AccountInfo::~AccountInfo() { 28 } 29 30 GCMAccountTracker::GCMAccountTracker( 31 scoped_ptr<gaia::AccountTracker> account_tracker, 32 const UpdateAccountsCallback& callback) 33 : OAuth2TokenService::Consumer(kGCMAccountTrackerName), 34 account_tracker_(account_tracker.release()), 35 callback_(callback), 36 shutdown_called_(false) { 37 DCHECK(!callback_.is_null()); 38 } 39 40 GCMAccountTracker::~GCMAccountTracker() { 41 DCHECK(shutdown_called_); 42 } 43 44 void GCMAccountTracker::Shutdown() { 45 Stop(); 46 shutdown_called_ = true; 47 account_tracker_->Shutdown(); 48 } 49 50 void GCMAccountTracker::Start() { 51 DCHECK(!shutdown_called_); 52 account_tracker_->AddObserver(this); 53 54 std::vector<gaia::AccountIds> accounts = account_tracker_->GetAccounts(); 55 if (accounts.empty()) { 56 CompleteCollectingTokens(); 57 return; 58 } 59 60 for (std::vector<gaia::AccountIds>::const_iterator iter = accounts.begin(); 61 iter != accounts.end(); 62 ++iter) { 63 if (!iter->email.empty()) { 64 account_infos_.insert(std::make_pair( 65 iter->account_key, AccountInfo(iter->email, TOKEN_NEEDED))); 66 } 67 } 68 69 GetAllNeededTokens(); 70 } 71 72 void GCMAccountTracker::Stop() { 73 DCHECK(!shutdown_called_); 74 account_tracker_->RemoveObserver(this); 75 pending_token_requests_.clear(); 76 } 77 78 void GCMAccountTracker::OnAccountAdded(const gaia::AccountIds& ids) { 79 DVLOG(1) << "Account added: " << ids.email; 80 // We listen for the account signing in, which happens after account is added. 81 } 82 83 void GCMAccountTracker::OnAccountRemoved(const gaia::AccountIds& ids) { 84 DVLOG(1) << "Account removed: " << ids.email; 85 // We listen for the account signing out, which happens before account is 86 // removed. 87 } 88 89 void GCMAccountTracker::OnAccountSignInChanged(const gaia::AccountIds& ids, 90 bool is_signed_in) { 91 if (is_signed_in) 92 OnAccountSignedIn(ids); 93 else 94 OnAccountSignedOut(ids); 95 } 96 97 void GCMAccountTracker::OnGetTokenSuccess( 98 const OAuth2TokenService::Request* request, 99 const std::string& access_token, 100 const base::Time& expiration_time) { 101 DCHECK(request); 102 DCHECK(!request->GetAccountId().empty()); 103 DVLOG(1) << "Get token success: " << request->GetAccountId(); 104 105 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId()); 106 DCHECK(iter != account_infos_.end()); 107 if (iter != account_infos_.end()) { 108 DCHECK(iter->second.state == GETTING_TOKEN || 109 iter->second.state == ACCOUNT_REMOVED); 110 // If OnAccountSignedOut(..) was called most recently, account is kept in 111 // ACCOUNT_REMOVED state. 112 if (iter->second.state == GETTING_TOKEN) { 113 iter->second.state = TOKEN_PRESENT; 114 iter->second.access_token = access_token; 115 } 116 } 117 118 DeleteTokenRequest(request); 119 CompleteCollectingTokens(); 120 } 121 122 void GCMAccountTracker::OnGetTokenFailure( 123 const OAuth2TokenService::Request* request, 124 const GoogleServiceAuthError& error) { 125 DCHECK(request); 126 DCHECK(!request->GetAccountId().empty()); 127 DVLOG(1) << "Get token failure: " << request->GetAccountId(); 128 129 AccountInfos::iterator iter = account_infos_.find(request->GetAccountId()); 130 DCHECK(iter != account_infos_.end()); 131 if (iter != account_infos_.end()) { 132 DCHECK(iter->second.state == GETTING_TOKEN || 133 iter->second.state == ACCOUNT_REMOVED); 134 // If OnAccountSignedOut(..) was called most recently, account is kept in 135 // ACCOUNT_REMOVED state. 136 if (iter->second.state == GETTING_TOKEN) 137 iter->second.state = TOKEN_NEEDED; 138 } 139 140 DeleteTokenRequest(request); 141 CompleteCollectingTokens(); 142 } 143 144 void GCMAccountTracker::CompleteCollectingTokens() { 145 DCHECK(!callback_.is_null()); 146 // Wait for gaia::AccountTracker to be done with fetching the user info, as 147 // well as all of the pending token requests from GCMAccountTracker to be done 148 // before you report the results. 149 if (!account_tracker_->IsAllUserInfoFetched() || 150 !pending_token_requests_.empty()) { 151 return; 152 } 153 154 bool account_removed = false; 155 std::map<std::string, std::string> account_tokens; 156 for (AccountInfos::iterator iter = account_infos_.begin(); 157 iter != account_infos_.end();) { 158 switch (iter->second.state) { 159 case ACCOUNT_REMOVED: 160 // We only mark accounts as removed when there was an account that was 161 // explicitly signed out. 162 account_removed = true; 163 // We also stop tracking the account, now that it will be reported as 164 // removed. 165 account_infos_.erase(iter++); 166 break; 167 168 case TOKEN_PRESENT: 169 account_tokens[iter->second.email] = iter->second.access_token; 170 ++iter; 171 break; 172 173 case GETTING_TOKEN: 174 // This should not happen, as we are making a check that there are no 175 // pending requests above. 176 NOTREACHED(); 177 ++iter; 178 break; 179 180 case TOKEN_NEEDED: 181 // We failed to fetch an access token for the account, but it has not 182 // been signed out (perhaps there is a network issue). We don't report 183 // it, but next time there is a sign-in change we will update its state. 184 ++iter; 185 break; 186 } 187 } 188 189 // Make sure that there is something to report, otherwise bail out. 190 if (!account_tokens.empty() || account_removed) { 191 DVLOG(1) << "Calling callback: " << account_tokens.size(); 192 callback_.Run(account_tokens); 193 } else { 194 DVLOG(1) << "No tokens and nothing removed. Skipping callback."; 195 } 196 } 197 198 void GCMAccountTracker::DeleteTokenRequest( 199 const OAuth2TokenService::Request* request) { 200 ScopedVector<OAuth2TokenService::Request>::iterator iter = std::find( 201 pending_token_requests_.begin(), pending_token_requests_.end(), request); 202 if (iter != pending_token_requests_.end()) 203 pending_token_requests_.erase(iter); 204 } 205 206 void GCMAccountTracker::GetAllNeededTokens() { 207 for (AccountInfos::iterator iter = account_infos_.begin(); 208 iter != account_infos_.end(); 209 ++iter) { 210 if (iter->second.state == TOKEN_NEEDED) 211 GetToken(iter); 212 } 213 } 214 215 void GCMAccountTracker::GetToken(AccountInfos::iterator& account_iter) { 216 DCHECK(GetTokenService()); 217 DCHECK_EQ(account_iter->second.state, TOKEN_NEEDED); 218 219 OAuth2TokenService::ScopeSet scopes; 220 scopes.insert(kGCMGroupServerScope); 221 scopes.insert(kGCMCheckinServerScope); 222 scoped_ptr<OAuth2TokenService::Request> request = 223 GetTokenService()->StartRequest(account_iter->first, scopes, this); 224 225 pending_token_requests_.push_back(request.release()); 226 account_iter->second.state = GETTING_TOKEN; 227 } 228 229 void GCMAccountTracker::OnAccountSignedIn(const gaia::AccountIds& ids) { 230 DVLOG(1) << "Account signed in: " << ids.email; 231 AccountInfos::iterator iter = account_infos_.find(ids.account_key); 232 if (iter == account_infos_.end()) { 233 DCHECK(!ids.email.empty()); 234 account_infos_.insert( 235 std::make_pair(ids.account_key, AccountInfo(ids.email, TOKEN_NEEDED))); 236 } else if (iter->second.state == ACCOUNT_REMOVED) { 237 iter->second.state = TOKEN_NEEDED; 238 } 239 240 GetAllNeededTokens(); 241 } 242 243 void GCMAccountTracker::OnAccountSignedOut(const gaia::AccountIds& ids) { 244 DVLOG(1) << "Account signed out: " << ids.email; 245 AccountInfos::iterator iter = account_infos_.find(ids.account_key); 246 if (iter == account_infos_.end()) 247 return; 248 249 iter->second.access_token.clear(); 250 iter->second.state = ACCOUNT_REMOVED; 251 CompleteCollectingTokens(); 252 } 253 254 OAuth2TokenService* GCMAccountTracker::GetTokenService() { 255 DCHECK(account_tracker_->identity_provider()); 256 return account_tracker_->identity_provider()->GetTokenService(); 257 } 258 259 } // namespace gcm 260