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

Side by Side Diff: chrome/browser/sync/credential_cache_service_win.cc

Issue 10656033: [sync] Automatic bootstrapping of Sync on Win 8 from cached credentials (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix unit tests Created 8 years, 5 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) 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 #include "chrome/browser/sync/credential_cache_service_win.h"
6
7 #include "base/bind.h"
8 #include "base/bind_helpers.h"
9 #include "base/base64.h"
10 #include "base/compiler_specific.h"
11 #include "base/file_util.h"
12 #include "base/values.h"
13 #include "chrome/browser/prefs/pref_service.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/profiles/profile_manager.h"
16 #include "chrome/browser/signin/signin_manager.h"
17 #include "chrome/browser/signin/token_service.h"
18 #include "chrome/browser/signin/token_service_factory.h"
19 #include "chrome/browser/sync/glue/chrome_encryptor.h"
20 #include "chrome/browser/sync/profile_sync_service.h"
21 #include "chrome/browser/sync/profile_sync_service_factory.h"
22 #include "chrome/browser/sync/sync_prefs.h"
23 #include "chrome/common/chrome_constants.h"
24 #include "chrome/common/chrome_notification_types.h"
25 #include "chrome/common/chrome_paths_internal.h"
26 #include "chrome/common/net/gaia/gaia_constants.h"
27 #include "chrome/common/pref_names.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/notification_details.h"
30 #include "content/public/browser/notification_source.h"
31 #include "sync/internal_api/public/base/model_type.h"
32
33 namespace syncer {
34
35 using content::BrowserThread;
36
37 const char kSyncServiceToken[] = "sync.sync_service_token";
38
39 CredentialCacheService::CredentialCacheService(Profile* profile)
40 : profile_(profile),
41 loaded_credentials_(false),
42 weak_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
43 if (profile_)
Andrew T Wilson (Slow) 2012/07/20 01:04:05 When is profile_ null? Is this just for unit tests
Raghu Simha 2012/07/20 23:35:22 Done.
44 InitializeLocalCredentialCacheWriter();
45 }
46
47 CredentialCacheService::~CredentialCacheService() {
48 Shutdown();
49 }
50
51 void CredentialCacheService::Shutdown() {
Andrew T Wilson (Slow) 2012/07/20 01:04:05 I thought the point of using a pref_registrar_, et
Raghu Simha 2012/07/20 23:35:22 Not really. Removed.
52 weak_factory_.InvalidateWeakPtrs();
53 token_service_registrar_.RemoveAll();
54 pref_registrar_.RemoveAll();
55 if (local_store_)
56 local_store_->RemoveObserver(this);
57 if (alternate_store_)
58 alternate_store_->RemoveObserver(this);
59 }
60
61 // static
62 bool CredentialCacheService::IsDefaultProfileDir(const FilePath& profile_dir) {
63 FilePath default_user_data_dir;
64 chrome::GetDefaultUserDataDirectory(&default_user_data_dir);
65 return profile_dir ==
66 ProfileManager::GetDefaultProfileDir(default_user_data_dir);
67 }
68
69 // static
70 base::StringValue* CredentialCacheService::PackCredential(
71 const std::string& credential) {
72 // Do nothing for empty credentials.
73 if (credential.empty())
74 return base::Value::CreateStringValue("");
75
76 browser_sync::ChromeEncryptor encryptor;
77 std::string encrypted;
78 if (!encryptor.EncryptString(credential, &encrypted)) {
79 NOTREACHED();
80 return NULL;
81 }
82
83 std::string encoded;
84 if (!base::Base64Encode(encrypted, &encoded)) {
85 NOTREACHED();
86 return NULL;
Andrew T Wilson (Slow) 2012/07/20 01:04:05 Do you really want to return NULL from this routin
Raghu Simha 2012/07/20 23:35:22 Good catch. Empty string is good.
87 }
88
89 return base::Value::CreateStringValue(encoded);
90 }
91
92 // static
93 std::string CredentialCacheService::UnpackCredential(
94 const base::Value& packed) {
95 std::string encoded;
96 if (!packed.GetAsString(&encoded)) {
97 NOTREACHED();
98 return std::string();
99 }
100
101 // Do nothing for empty credentials.
102 if (encoded.empty())
103 return "";
Andrew T Wilson (Slow) 2012/07/20 01:04:05 Why are we returning "" here instead of std::strin
Raghu Simha 2012/07/20 23:35:22 Good catch. Fixed.
104
105 std::string encrypted;
106 if (!base::Base64Decode(encoded, &encrypted)) {
107 NOTREACHED();
108 return std::string();
109 }
110
111 browser_sync::ChromeEncryptor encryptor;
112 std::string unpacked;
Andrew T Wilson (Slow) 2012/07/20 01:04:05 nit:Should this be called "unencrypted" not "unpac
Raghu Simha 2012/07/20 23:35:22 Done.
113 if (!encryptor.DecryptString(encrypted, &unpacked)) {
114 NOTREACHED();
115 return std::string();
116 }
117
118 return unpacked;
119 }
120
121 void CredentialCacheService::OnInitializationCompleted(bool succeeded) {
122 DCHECK(succeeded);
123 // We observe two JsonPrefStores -- one for the local profile and one for the
124 // alternate profile. When the alternate credential store becomes available,
125 // we begin consuming its cached credentials. There is nothing to do when the
126 // local credential store becomes available, since we automatically get
127 // notified when the user signs in / signs out.
128 if (alternate_store_ && alternate_store_->IsInitializationComplete()) {
129 ReadCachedCredentialsFromAlternateProfile();
130 }
131 }
132
133 void CredentialCacheService::OnPrefValueChanged(const std::string& key) {
134 // Nothing to do here, since credentials are cached silently.
135 }
136
137 namespace {
138
139 // Determines if credentials should be read from the alternate profile based
140 // on the existence of the local and alternate credential files. Returns
141 // true via |result| if there is a credential cache file in the alternate
142 // profile, but there isn't one in the local profile. Returns false otherwise.
143 void ShouldReadFromAlternateCache(
144 const FilePath& credential_path_in_current_profile,
145 const FilePath& credential_path_in_alternate_profile,
146 bool* result) {
147 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::FILE));
148 DCHECK(result);
149 *result = !file_util::PathExists(credential_path_in_current_profile) &&
150 file_util::PathExists(credential_path_in_alternate_profile);
151 }
152
153 } // namespace
154
155 void CredentialCacheService::LookForCachedCredentialsInAlternateProfile() {
156 bool* should_initialize = new bool(false);
157 content::BrowserThread::PostTaskAndReply(
158 content::BrowserThread::FILE,
159 FROM_HERE,
160 base::Bind(&ShouldReadFromAlternateCache,
161 GetCredentialPathInCurrentProfile(),
162 GetCredentialPathInAlternateProfile(),
163 should_initialize),
164 base::Bind(
165 &CredentialCacheService::InitializeAlternateCredentialCacheReader,
166 weak_factory_.GetWeakPtr(),
167 base::Owned(should_initialize)));
168 }
169
170 bool CredentialCacheService::SuccessfullyLoadedCredentials() const {
171 return loaded_credentials_;
172 }
173
174 FilePath CredentialCacheService::GetCredentialPathInCurrentProfile() const {
175 // The sync credential path in the default Desktop profile is
176 // "%Appdata%\Local\Google\Chrome\User Data\Default\Sync Credentials", while
177 // the sync credential path in the default Metro profile is
178 // "%Appdata%\Local\Google\Chrome\Metro\User Data\Default\Sync Credentials".
179 return profile_->GetPath().Append(chrome::kSyncCredentialsFilename);
180 }
181
182 FilePath CredentialCacheService::GetCredentialPathInAlternateProfile() const {
183 FilePath alternate_user_data_dir;
184 chrome::GetAlternateUserDataDirectory(&alternate_user_data_dir);
185 FilePath alternate_default_profile_dir =
186 ProfileManager::GetDefaultProfileDir(alternate_user_data_dir);
187 return alternate_default_profile_dir.Append(chrome::kSyncCredentialsFilename);
188 }
189
190 void CredentialCacheService::InitializeLocalCredentialCacheWriter() {
191 local_store_ = new JsonPrefStore(
192 GetCredentialPathInCurrentProfile(),
193 content::BrowserThread::GetMessageLoopProxyForThread(
194 content::BrowserThread::FILE));
195 local_store_->AddObserver(this);
196 local_store_->ReadPrefsAsync(NULL);
197
198 // Register for notifications for updates to the sync credentials, which are
199 // stored in the PrefStore.
200 pref_registrar_.Init(profile_->GetPrefs());
201 pref_registrar_.Add(prefs::kSyncEncryptionBootstrapToken, this);
202 pref_registrar_.Add(prefs::kGoogleServicesUsername, this);
203 pref_registrar_.Add(prefs::kSyncKeepEverythingSynced, this);
204 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
205 if (i == NIGORI) // The NIGORI preference is not persisted.
206 continue;
207 pref_registrar_.Add(
208 browser_sync::SyncPrefs::GetPrefNameForDataType(ModelTypeFromInt(i)),
209 this);
210 }
211
212 // Register for notifications for updates to the sync service token, which is
213 // stored in the TokenService.
214 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
215 token_service_registrar_.Add(
216 this,
217 chrome::NOTIFICATION_TOKEN_AVAILABLE,
218 content::Source<TokenService>(token_service));
219 token_service_registrar_.Add(
220 this,
221 chrome::NOTIFICATION_TOKENS_CLEARED,
222 content::Source<TokenService>(token_service));
223 }
224
225 void CredentialCacheService::InitializeAlternateCredentialCacheReader(
226 bool* should_initialize) {
Andrew T Wilson (Slow) 2012/07/20 01:04:05 Not clear why should_initialize is a pointer, sinc
Raghu Simha 2012/07/20 23:35:22 This is so that base::Owned can clean up the bool*
227 // If |should_initialize| is false, there was no credential cache in the
228 // alternate profile directory, and there is nothing to do.
229 // TODO(rsimha): Add a polling mechanism that periodically examines the
230 // credential file in the alternate profile directory so we can respond to the
231 // user signing in and signing out.
232 DCHECK(should_initialize);
233 if (!*should_initialize)
234 return;
235 alternate_store_ = new JsonPrefStore(
236 GetCredentialPathInAlternateProfile(),
237 content::BrowserThread::GetMessageLoopProxyForThread(
238 content::BrowserThread::FILE));
239 alternate_store_->AddObserver(this);
240 alternate_store_->ReadPrefsAsync(NULL);
241 }
242
243 bool CredentialCacheService::HasPref(scoped_refptr<JsonPrefStore> store,
244 const std::string& pref_name) {
245 return (store->GetValue(pref_name, NULL) == PrefStore::READ_OK);
246 }
247
248 std::string CredentialCacheService::GetStringPref(
249 scoped_refptr<JsonPrefStore> store,
250 const std::string& pref_name) {
251 const base::Value* pref_value = NULL;
252 store->GetValue(pref_name, &pref_value);
253 return UnpackCredential(*pref_value);
254 }
255
256 bool CredentialCacheService::GetBooleanPref(
257 scoped_refptr<JsonPrefStore> store,
258 const std::string& pref_name) {
259 const base::Value* pref_value = NULL;
260 store->GetValue(pref_name, &pref_value);
261 bool pref;
262 pref_value->GetAsBoolean(&pref);
263 return pref;
264 }
265
266 void CredentialCacheService::ReadCachedCredentialsFromAlternateProfile() {
267 if (!HasPref(alternate_store_, prefs::kGoogleServicesUsername) ||
268 !HasPref(alternate_store_, kSyncServiceToken) ||
269 !HasPref(alternate_store_, prefs::kSyncEncryptionBootstrapToken) ||
270 !HasPref(alternate_store_, prefs::kSyncKeepEverythingSynced)) {
271 VLOG(1) << "Could not find cached credentials.";
272 return;
273 }
274
275 std::string google_services_username =
276 GetStringPref(alternate_store_, prefs::kGoogleServicesUsername);
277 std::string sync_service_token =
278 GetStringPref(alternate_store_, kSyncServiceToken);
279 std::string encryption_bootstrap_token =
280 GetStringPref(alternate_store_, prefs::kSyncEncryptionBootstrapToken);
281 bool keep_everything_synced =
282 GetBooleanPref(alternate_store_, prefs::kSyncKeepEverythingSynced);
283
284 if (google_services_username.empty() ||
285 sync_service_token.empty() ||
286 encryption_bootstrap_token.empty()) {
287 VLOG(1) << "Found empty cached credentials.";
288 return;
289 }
290
291 bool datatype_prefs[MODEL_TYPE_COUNT] = { false };
292 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
293 if (i == NIGORI) // The NIGORI preference is not persisted.
294 continue;
295 std::string datatype_pref_name =
296 browser_sync::SyncPrefs::GetPrefNameForDataType(ModelTypeFromInt(i));
297 if (!HasPref(alternate_store_, datatype_pref_name)) {
298 VLOG(1) << "Could not find cached datatype prefs.";
299 return;
300 }
301 datatype_prefs[i] = GetBooleanPref(alternate_store_, datatype_pref_name);
302 }
303
304 ApplyCachedCredentials(google_services_username,
305 sync_service_token,
306 encryption_bootstrap_token,
307 keep_everything_synced,
308 datatype_prefs);
309 }
310
311 void CredentialCacheService::ApplyCachedCredentials(
312 const std::string& google_services_username,
313 const std::string& sync_service_token,
314 const std::string& encryption_bootstrap_token,
315 bool keep_everything_synced,
316 const bool datatype_prefs[]) {
317 loaded_credentials_ = true;
318 ProfileSyncService* service =
319 ProfileSyncServiceFactory::GetForProfile(profile_);
320 base::WeakPtr<browser_sync::SyncPrefs> sync_prefs = service->sync_prefs();
321 service->signin()->SetAuthenticatedUsername(google_services_username);
322 profile_->GetPrefs()->SetString(prefs::kGoogleServicesUsername,
323 google_services_username);
324 sync_prefs->SetStartSuppressed(false);
325 sync_prefs->SetEncryptionBootstrapToken(encryption_bootstrap_token);
326 sync_prefs->SetKeepEverythingSynced(keep_everything_synced);
327 syncer::ModelTypeSet registered_types;
328 syncer::ModelTypeSet preferred_types;
329 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) {
330 if (i == NIGORI) // The NIGORI preference is not persisted.
331 continue;
332 registered_types.Put(ModelTypeFromInt(i));
333 if (datatype_prefs[i])
334 preferred_types.Put(ModelTypeFromInt(i));
335 }
336 sync_prefs->SetPreferredDataTypes(registered_types, preferred_types);
337 TokenService* token_service = TokenServiceFactory::GetForProfile(profile_);
338 token_service->SaveCachedSyncTokenToDB(sync_service_token);
339 }
340
341 bool CredentialCacheService::HasUserSignedOut() {
342 // If HasPref() is false, the user never signed in, since there are no
343 // previously cached credentials. If the kGoogleServicesUsername pref is
344 // empty, it means that the user signed in and subsequently signed out.
345 return HasPref(local_store_, prefs::kGoogleServicesUsername) &&
346 GetStringPref(local_store_, prefs::kGoogleServicesUsername).empty();
347 }
348
349 void CredentialCacheService::UpdateStringPref(const std::string& pref_name,
350 const std::string& new_value) {
351 if (!HasUserSignedOut()) {
352 local_store_->SetValueSilently(pref_name, PackCredential(new_value));
353 } else {
354 // Write a blank value since we only use cached credentials for first-time
355 // sign-ins.
356 local_store_->SetValueSilently(pref_name, PackCredential(""));
357 }
358 }
359
360 void CredentialCacheService::UpdateBooleanPref(const std::string& pref_name,
361 bool new_value) {
362 if (!HasUserSignedOut()) {
363 local_store_->SetValueSilently(pref_name,
364 base::Value::CreateBooleanValue(new_value));
365 } else {
366 // Write a default value of false since we only use cached credentials for
367 // first-time sign-ins.
368 local_store_->SetValueSilently(pref_name,
369 base::Value::CreateBooleanValue(false));
370 }
371 }
372
373 void CredentialCacheService::Observe(
374 int type,
375 const content::NotificationSource& source,
376 const content::NotificationDetails& details) {
377 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
378 DCHECK(local_store_);
379 switch (type) {
380 case chrome::NOTIFICATION_PREF_CHANGED: {
381 const std::string pref_name =
382 *(content::Details<const std::string>(details).ptr());
383 if (pref_name == prefs::kGoogleServicesUsername ||
384 pref_name == prefs::kSyncEncryptionBootstrapToken) {
385 UpdateStringPref(pref_name,
386 profile_->GetPrefs()->GetString(pref_name.c_str()));
387 } else {
388 UpdateBooleanPref(pref_name,
389 profile_->GetPrefs()->GetBoolean(pref_name.c_str()));
390 }
391 break;
392 }
393
394 case chrome::NOTIFICATION_TOKEN_AVAILABLE: {
395 const TokenService::TokenAvailableDetails& token_details =
396 *(content::Details<const TokenService::TokenAvailableDetails>(
397 details).ptr());
398 if (token_details.service() == GaiaConstants::kSyncService) {
399 UpdateStringPref(kSyncServiceToken, token_details.token());
Andrew T Wilson (Slow) 2012/07/20 01:04:05 I'm still not clear on why we are only syncing the
Raghu Simha 2012/07/20 23:35:22 Good point re: tokens for other services. I've gon
400 }
401 break;
402 }
403
404 case chrome::NOTIFICATION_TOKENS_CLEARED: {
405 UpdateStringPref(kSyncServiceToken, "");
406 break;
407 }
408
409 default: {
410 NOTREACHED();
411 break;
412 }
413 }
414 }
415
416 } // namespace syncer
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698