| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2006-2009 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/sync/engine/auth_watcher.h" | |
| 6 | |
| 7 #include "base/file_util.h" | |
| 8 #include "base/string_util.h" | |
| 9 #include "chrome/browser/sync/engine/all_status.h" | |
| 10 #include "chrome/browser/sync/engine/authenticator.h" | |
| 11 #include "chrome/browser/sync/engine/net/server_connection_manager.h" | |
| 12 #include "chrome/browser/sync/syncable/directory_manager.h" | |
| 13 #include "chrome/browser/sync/syncable/syncable.h" | |
| 14 #include "chrome/browser/sync/util/user_settings.h" | |
| 15 #include "chrome/common/deprecated/event_sys-inl.h" | |
| 16 #include "chrome/common/net/gaia/gaia_authenticator.h" | |
| 17 | |
| 18 // How authentication happens: | |
| 19 // | |
| 20 // Kick Off: | |
| 21 // The sync API looks to see if the user's name and | |
| 22 // password are stored. If so, it calls authwatcher.Authenticate() with | |
| 23 // them. Otherwise it fires an error event. | |
| 24 // | |
| 25 // On failed Gaia Auth: | |
| 26 // The AuthWatcher attempts to use saved hashes to authenticate | |
| 27 // locally, and on success opens the share. | |
| 28 // On failure, fires an error event. | |
| 29 // | |
| 30 // On successful Gaia Auth: | |
| 31 // AuthWatcher launches a thread to open the share and to get the | |
| 32 // authentication token from the sync server. | |
| 33 | |
| 34 using std::pair; | |
| 35 using std::string; | |
| 36 using std::vector; | |
| 37 | |
| 38 namespace browser_sync { | |
| 39 | |
| 40 AuthWatcher::AuthWatcher(DirectoryManager* dirman, | |
| 41 ServerConnectionManager* scm, | |
| 42 const string& user_agent, | |
| 43 const string& service_id, | |
| 44 const string& gaia_url, | |
| 45 UserSettings* user_settings, | |
| 46 gaia::GaiaAuthenticator* gaia_auth) | |
| 47 : gaia_(gaia_auth), | |
| 48 dirman_(dirman), | |
| 49 scm_(scm), | |
| 50 status_(NOT_AUTHENTICATED), | |
| 51 user_settings_(user_settings), | |
| 52 auth_backend_thread_("SyncEngine_AuthWatcherThread"), | |
| 53 current_attempt_trigger_(AuthWatcherEvent::USER_INITIATED) { | |
| 54 | |
| 55 if (!auth_backend_thread_.Start()) | |
| 56 NOTREACHED() << "Couldn't start SyncEngine_AuthWatcherThread"; | |
| 57 | |
| 58 gaia_->set_message_loop(message_loop()); | |
| 59 loop_proxy_ = auth_backend_thread_.message_loop_proxy(); | |
| 60 | |
| 61 connmgr_hookup_.reset( | |
| 62 NewEventListenerHookup(scm->channel(), this, | |
| 63 &AuthWatcher::HandleServerConnectionEvent)); | |
| 64 AuthWatcherEvent done = { AuthWatcherEvent::AUTHWATCHER_DESTROYED }; | |
| 65 channel_.reset(new Channel(done)); | |
| 66 } | |
| 67 | |
| 68 void AuthWatcher::PersistCredentials() { | |
| 69 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 70 gaia::GaiaAuthenticator::AuthResults results = gaia_->results(); | |
| 71 | |
| 72 // We just successfully signed in again, let's clear out any residual cached | |
| 73 // login data from earlier sessions. | |
| 74 ClearAuthenticationData(); | |
| 75 | |
| 76 user_settings_->StoreEmailForSignin(results.email, results.primary_email); | |
| 77 results.email = results.primary_email; | |
| 78 gaia_->SetUsernamePassword(results.primary_email, results.password); | |
| 79 if (!user_settings_->VerifyAgainstStoredHash(results.email, results.password)) | |
| 80 user_settings_->StoreHashedPassword(results.email, results.password); | |
| 81 | |
| 82 user_settings_->SetAuthTokenForService(results.email, | |
| 83 SYNC_SERVICE_NAME, | |
| 84 gaia_->auth_token()); | |
| 85 } | |
| 86 | |
| 87 // TODO(chron): Full integration test suite needed. http://crbug.com/35429 | |
| 88 void AuthWatcher::RenewAuthToken(const std::string& updated_token) { | |
| 89 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 90 &AuthWatcher::DoRenewAuthToken, updated_token)); | |
| 91 } | |
| 92 | |
| 93 void AuthWatcher::DoRenewAuthToken(const std::string& updated_token) { | |
| 94 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 95 // TODO(chron): We should probably only store auth token in one place. | |
| 96 if (scm_->auth_token() == updated_token) { | |
| 97 return; // This thread is the only one writing to the SCM's auth token. | |
| 98 } | |
| 99 LOG(INFO) << "Updating auth token:" << updated_token; | |
| 100 scm_->set_auth_token(updated_token); | |
| 101 gaia_->RenewAuthToken(updated_token); // Must be on AuthWatcher thread | |
| 102 user_settings_->SetAuthTokenForService(user_settings_->email(), | |
| 103 SYNC_SERVICE_NAME, | |
| 104 updated_token); | |
| 105 | |
| 106 NotifyAuthChanged(user_settings_->email(), updated_token, true); | |
| 107 } | |
| 108 | |
| 109 void AuthWatcher::AuthenticateWithLsid(const std::string& lsid) { | |
| 110 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 111 &AuthWatcher::DoAuthenticateWithLsid, lsid)); | |
| 112 } | |
| 113 | |
| 114 void AuthWatcher::DoAuthenticateWithLsid(const std::string& lsid) { | |
| 115 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 116 | |
| 117 AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START }; | |
| 118 NotifyListeners(&event); | |
| 119 | |
| 120 if (gaia_->AuthenticateWithLsid(lsid)) { | |
| 121 PersistCredentials(); | |
| 122 DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token()); | |
| 123 } else { | |
| 124 ProcessGaiaAuthFailure(); | |
| 125 } | |
| 126 } | |
| 127 | |
| 128 const char kAuthWatcher[] = "AuthWatcher"; | |
| 129 | |
| 130 void AuthWatcher::AuthenticateWithToken(const std::string& gaia_email, | |
| 131 const std::string& auth_token) { | |
| 132 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 133 &AuthWatcher::DoAuthenticateWithToken, gaia_email, auth_token)); | |
| 134 } | |
| 135 | |
| 136 void AuthWatcher::DoAuthenticateWithToken(const std::string& gaia_email, | |
| 137 const std::string& auth_token) { | |
| 138 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 139 | |
| 140 Authenticator auth(scm_, user_settings_); | |
| 141 Authenticator::AuthenticationResult result = | |
| 142 auth.AuthenticateToken(auth_token); | |
| 143 string email = gaia_email; | |
| 144 if (auth.display_email() && *auth.display_email()) { | |
| 145 email = auth.display_email(); | |
| 146 LOG(INFO) << "Auth returned email " << email << " for gaia email " << | |
| 147 gaia_email; | |
| 148 } | |
| 149 | |
| 150 AuthWatcherEvent event = {AuthWatcherEvent::ILLEGAL_VALUE , 0}; | |
| 151 gaia_->SetUsername(email); | |
| 152 gaia_->SetAuthToken(auth_token); | |
| 153 const bool was_authenticated = NOT_AUTHENTICATED != status_; | |
| 154 switch (result) { | |
| 155 case Authenticator::SUCCESS: | |
| 156 { | |
| 157 status_ = GAIA_AUTHENTICATED; | |
| 158 const std::string& share_name = email; | |
| 159 user_settings_->SwitchUser(email); | |
| 160 scm_->set_auth_token(auth_token); | |
| 161 | |
| 162 if (!was_authenticated) { | |
| 163 LOG(INFO) << "Opening DB for AuthenticateWithToken (" | |
| 164 << share_name << ")"; | |
| 165 dirman_->Open(share_name); | |
| 166 } | |
| 167 NotifyAuthChanged(email, auth_token, false); | |
| 168 return; | |
| 169 } | |
| 170 case Authenticator::BAD_AUTH_TOKEN: | |
| 171 event.what_happened = AuthWatcherEvent::SERVICE_AUTH_FAILED; | |
| 172 break; | |
| 173 case Authenticator::CORRUPT_SERVER_RESPONSE: | |
| 174 case Authenticator::SERVICE_DOWN: | |
| 175 event.what_happened = AuthWatcherEvent::SERVICE_CONNECTION_FAILED; | |
| 176 break; | |
| 177 case Authenticator::USER_NOT_ACTIVATED: | |
| 178 event.what_happened = AuthWatcherEvent::SERVICE_USER_NOT_SIGNED_UP; | |
| 179 break; | |
| 180 default: | |
| 181 LOG(FATAL) << "Illegal return from AuthenticateToken"; | |
| 182 return; | |
| 183 } | |
| 184 // Always fall back to local authentication. | |
| 185 if (was_authenticated || AuthenticateLocally(email)) { | |
| 186 if (AuthWatcherEvent::SERVICE_CONNECTION_FAILED == event.what_happened) | |
| 187 return; | |
| 188 } | |
| 189 DCHECK_NE(event.what_happened, AuthWatcherEvent::ILLEGAL_VALUE); | |
| 190 NotifyListeners(&event); | |
| 191 } | |
| 192 | |
| 193 bool AuthWatcher::AuthenticateLocally(string email) { | |
| 194 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 195 user_settings_->GetEmailForSignin(&email); | |
| 196 if (file_util::PathExists(FilePath(dirman_->GetSyncDataDatabasePath()))) { | |
| 197 gaia_->SetUsername(email); | |
| 198 status_ = LOCALLY_AUTHENTICATED; | |
| 199 user_settings_->SwitchUser(email); | |
| 200 LOG(INFO) << "Opening DB for AuthenticateLocally (" << email << ")"; | |
| 201 dirman_->Open(email); | |
| 202 NotifyAuthChanged(email, "", false); | |
| 203 return true; | |
| 204 } else { | |
| 205 return false; | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 bool AuthWatcher::AuthenticateLocally(string email, const string& password) { | |
| 210 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 211 user_settings_->GetEmailForSignin(&email); | |
| 212 return user_settings_->VerifyAgainstStoredHash(email, password) | |
| 213 && AuthenticateLocally(email); | |
| 214 } | |
| 215 | |
| 216 void AuthWatcher::ProcessGaiaAuthFailure() { | |
| 217 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 218 gaia::GaiaAuthenticator::AuthResults results = gaia_->results(); | |
| 219 if (LOCALLY_AUTHENTICATED != status_ && | |
| 220 AuthenticateLocally(results.email, results.password)) { | |
| 221 // TODO(chron): Do we really want a bogus token? | |
| 222 const string auth_token("bogus"); | |
| 223 user_settings_->SetAuthTokenForService(results.email, | |
| 224 SYNC_SERVICE_NAME, | |
| 225 auth_token); | |
| 226 } | |
| 227 AuthWatcherEvent myevent = { AuthWatcherEvent::GAIA_AUTH_FAILED, &results }; | |
| 228 NotifyListeners(&myevent); | |
| 229 } | |
| 230 | |
| 231 void AuthWatcher::DoAuthenticate(const AuthRequest& request) { | |
| 232 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 233 | |
| 234 AuthWatcherEvent event = { AuthWatcherEvent::AUTHENTICATION_ATTEMPT_START }; | |
| 235 NotifyListeners(&event); | |
| 236 | |
| 237 current_attempt_trigger_ = request.trigger; | |
| 238 | |
| 239 // We let the caller be lazy and try using the last captcha token seen by | |
| 240 // the gaia authenticator if they haven't provided a token but have sent | |
| 241 // a challenge response. Of course, if the captcha token is specified, | |
| 242 // we use that one instead. | |
| 243 std::string captcha_token(request.captcha_token); | |
| 244 if (!request.captcha_value.empty() && captcha_token.empty()) | |
| 245 captcha_token = gaia_->captcha_token(); | |
| 246 | |
| 247 if (!request.password.empty()) { | |
| 248 bool authenticated = false; | |
| 249 if (!captcha_token.empty()) { | |
| 250 authenticated = gaia_->Authenticate(request.email, request.password, | |
| 251 captcha_token, | |
| 252 request.captcha_value); | |
| 253 } else { | |
| 254 authenticated = gaia_->Authenticate(request.email, request.password); | |
| 255 } | |
| 256 if (authenticated) { | |
| 257 PersistCredentials(); | |
| 258 DoAuthenticateWithToken(gaia_->email(), gaia_->auth_token()); | |
| 259 } else { | |
| 260 ProcessGaiaAuthFailure(); | |
| 261 } | |
| 262 } else if (!request.auth_token.empty()) { | |
| 263 DoAuthenticateWithToken(request.email, request.auth_token); | |
| 264 } else { | |
| 265 LOG(ERROR) << "Attempt to authenticate with no credentials."; | |
| 266 } | |
| 267 } | |
| 268 | |
| 269 void AuthWatcher::NotifyAuthChanged(const string& email, | |
| 270 const string& auth_token, | |
| 271 bool renewed) { | |
| 272 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 273 LOG(INFO) << "NotifyAuthSucceeded"; | |
| 274 AuthWatcherEvent event = { | |
| 275 renewed ? | |
| 276 AuthWatcherEvent::AUTH_RENEWED : | |
| 277 AuthWatcherEvent::AUTH_SUCCEEDED | |
| 278 }; | |
| 279 event.user_email = email; | |
| 280 event.auth_token = auth_token; | |
| 281 | |
| 282 NotifyListeners(&event); | |
| 283 } | |
| 284 | |
| 285 void AuthWatcher::HandleServerConnectionEvent( | |
| 286 const ServerConnectionEvent& event) { | |
| 287 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 288 &AuthWatcher::DoHandleServerConnectionEvent, event, | |
| 289 scm_->auth_token())); | |
| 290 } | |
| 291 | |
| 292 void AuthWatcher::DoHandleServerConnectionEvent( | |
| 293 const ServerConnectionEvent& event, | |
| 294 const std::string& auth_token_snapshot) { | |
| 295 DCHECK_EQ(MessageLoop::current(), message_loop()); | |
| 296 if (event.server_reachable && | |
| 297 // If the auth_token at the time of the event differs from the current | |
| 298 // one, we have authenticated since then and don't need to re-try. | |
| 299 (auth_token_snapshot == gaia_->auth_token()) && | |
| 300 (event.connection_code == HttpResponse::SYNC_AUTH_ERROR || | |
| 301 status_ == LOCALLY_AUTHENTICATED)) { | |
| 302 // We're either online or just got reconnected and want to try to | |
| 303 // authenticate. If we've got a saved token this should just work. If not | |
| 304 // the auth failure should trigger UI indications that we're not logged in. | |
| 305 | |
| 306 // METRIC: If we get a SYNC_AUTH_ERROR, our token expired. | |
| 307 gaia::GaiaAuthenticator::AuthResults authresults = gaia_->results(); | |
| 308 AuthRequest request = { authresults.email, authresults.password, | |
| 309 authresults.auth_token, std::string(), | |
| 310 std::string(), | |
| 311 AuthWatcherEvent::EXPIRED_CREDENTIALS }; | |
| 312 DoAuthenticate(request); | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 AuthWatcher::~AuthWatcher() { | |
| 317 auth_backend_thread_.Stop(); | |
| 318 // The gaia authenticator takes a const MessageLoop* because it only uses it | |
| 319 // to ensure all methods are invoked on the given loop. Once our thread has | |
| 320 // stopped, the current message loop will be NULL, and no methods should be | |
| 321 // invoked on |gaia_| after this point. We could set it to NULL, but | |
| 322 // abstaining allows for even more sanity checking that nothing is invoked on | |
| 323 // it from now on. | |
| 324 } | |
| 325 | |
| 326 void AuthWatcher::Authenticate(const string& email, const string& password, | |
| 327 const string& captcha_token, const string& captcha_value) { | |
| 328 LOG(INFO) << "AuthWatcher::Authenticate called"; | |
| 329 | |
| 330 string empty; | |
| 331 AuthRequest request = { FormatAsEmailAddress(email), password, empty, | |
| 332 captcha_token, captcha_value, | |
| 333 AuthWatcherEvent::USER_INITIATED }; | |
| 334 message_loop_proxy()->PostTask(FROM_HERE, NewRunnableMethod(this, | |
| 335 &AuthWatcher::DoAuthenticate, request)); | |
| 336 } | |
| 337 | |
| 338 void AuthWatcher::ClearAuthenticationData() { | |
| 339 scm_->set_auth_token(std::string()); | |
| 340 user_settings_->ClearAllServiceTokens(); | |
| 341 } | |
| 342 | |
| 343 string AuthWatcher::email() const { | |
| 344 return gaia_->email(); | |
| 345 } | |
| 346 | |
| 347 void AuthWatcher::NotifyListeners(AuthWatcherEvent* event) { | |
| 348 event->trigger = current_attempt_trigger_; | |
| 349 channel_->NotifyListeners(*event); | |
| 350 } | |
| 351 | |
| 352 } // namespace browser_sync | |
| OLD | NEW |