Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(41)

Side by Side Diff: chrome/browser/sync/engine/auth_watcher.cc

Issue 3305003: New authorization framework for sync. ... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: '' Created 10 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698