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.
5 #include "chrome/browser/password_manager/native_backend_gnome_x.h"
8 #include <gnome-keyring.h>
14 #include "base/basictypes.h"
15 #include "base/logging.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/metrics/histogram.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_piece.h"
20 #include "base/strings/string_util.h"
21 #include "base/strings/stringprintf.h"
22 #include "base/strings/utf_string_conversions.h"
23 #include "base/synchronization/waitable_event.h"
24 #include "base/time/time.h"
25 #include "chrome/browser/password_manager/psl_matching_helper.h"
26 #include "components/autofill/core/common/password_form.h"
27 #include "content/public/browser/browser_thread.h"
29 using autofill::PasswordForm
;
30 using base::UTF8ToUTF16
;
31 using base::UTF16ToUTF8
;
32 using content::BrowserThread
;
34 #define GNOME_KEYRING_DEFINE_POINTER(name) \
35 typeof(&::gnome_keyring_##name) GnomeKeyringLoader::gnome_keyring_##name;
36 GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_DEFINE_POINTER
)
37 #undef GNOME_KEYRING_DEFINE_POINTER
39 bool GnomeKeyringLoader::keyring_loaded
= false;
41 #if defined(DLOPEN_GNOME_KEYRING)
43 #define GNOME_KEYRING_FUNCTION_INFO(name) \
44 {"gnome_keyring_"#name, reinterpret_cast<void**>(&gnome_keyring_##name)},
45 const GnomeKeyringLoader::FunctionInfo
GnomeKeyringLoader::functions
[] = {
46 GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_FUNCTION_INFO
)
49 #undef GNOME_KEYRING_FUNCTION_INFO
51 /* Load the library and initialize the function pointers. */
52 bool GnomeKeyringLoader::LoadGnomeKeyring() {
56 void* handle
= dlopen("libgnome-keyring.so.0", RTLD_NOW
| RTLD_GLOBAL
);
58 // We wanted to use GNOME Keyring, but we couldn't load it. Warn, because
59 // either the user asked for this, or we autodetected it incorrectly. (Or
60 // the system has broken libraries, which is also good to warn about.)
61 LOG(WARNING
) << "Could not load libgnome-keyring.so.0: " << dlerror();
65 for (size_t i
= 0; functions
[i
].name
; ++i
) {
67 *functions
[i
].pointer
= dlsym(handle
, functions
[i
].name
);
68 const char* error
= dlerror();
70 LOG(ERROR
) << "Unable to load symbol "
71 << functions
[i
].name
<< ": " << error
;
77 keyring_loaded
= true;
78 // We leak the library handle. That's OK: this function is called only once.
82 #else // defined(DLOPEN_GNOME_KEYRING)
84 bool GnomeKeyringLoader::LoadGnomeKeyring() {
87 #define GNOME_KEYRING_ASSIGN_POINTER(name) \
88 gnome_keyring_##name = &::gnome_keyring_##name;
89 GNOME_KEYRING_FOR_EACH_FUNC(GNOME_KEYRING_ASSIGN_POINTER
)
90 #undef GNOME_KEYRING_ASSIGN_POINTER
91 keyring_loaded
= true;
95 #endif // defined(DLOPEN_GNOME_KEYRING)
99 const char kGnomeKeyringAppString
[] = "chrome";
101 // Convert the attributes of a given keyring entry into a new PasswordForm.
102 // Note: does *not* get the actual password, as that is not a key attribute!
103 // Returns NULL if the attributes are for the wrong application.
104 scoped_ptr
<PasswordForm
> FormFromAttributes(GnomeKeyringAttributeList
* attrs
) {
105 // Read the string and int attributes into the appropriate map.
106 std::map
<std::string
, std::string
> string_attr_map
;
107 std::map
<std::string
, uint32_t> uint_attr_map
;
108 for (guint i
= 0; i
< attrs
->len
; ++i
) {
109 GnomeKeyringAttribute attr
= gnome_keyring_attribute_list_index(attrs
, i
);
110 if (attr
.type
== GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
)
111 string_attr_map
[attr
.name
] = attr
.value
.string
;
112 else if (attr
.type
== GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32
)
113 uint_attr_map
[attr
.name
] = attr
.value
.integer
;
115 // Check to make sure this is a password we care about.
116 const std::string
& app_value
= string_attr_map
["application"];
117 if (!base::StringPiece(app_value
).starts_with(kGnomeKeyringAppString
))
118 return scoped_ptr
<PasswordForm
>();
120 scoped_ptr
<PasswordForm
> form(new PasswordForm());
121 form
->origin
= GURL(string_attr_map
["origin_url"]);
122 form
->action
= GURL(string_attr_map
["action_url"]);
123 form
->username_element
= UTF8ToUTF16(string_attr_map
["username_element"]);
124 form
->username_value
= UTF8ToUTF16(string_attr_map
["username_value"]);
125 form
->password_element
= UTF8ToUTF16(string_attr_map
["password_element"]);
126 form
->submit_element
= UTF8ToUTF16(string_attr_map
["submit_element"]);
127 form
->signon_realm
= string_attr_map
["signon_realm"];
128 form
->ssl_valid
= uint_attr_map
["ssl_valid"];
129 form
->preferred
= uint_attr_map
["preferred"];
130 int64 date_created
= 0;
131 bool date_ok
= base::StringToInt64(string_attr_map
["date_created"],
134 form
->date_created
= base::Time::FromTimeT(date_created
);
135 form
->blacklisted_by_user
= uint_attr_map
["blacklisted_by_user"];
136 form
->scheme
= static_cast<PasswordForm::Scheme
>(uint_attr_map
["scheme"]);
141 // Parse all the results from the given GList into a PasswordFormList, and free
142 // the GList. PasswordForms are allocated on the heap, and should be deleted by
143 // the consumer. If not empty, |filter_by_signon_realm| is used to filter out
144 // results -- only credentials with signon realms passing the PSL matching
145 // (done by |helper|) against |filter_by_signon_realm| will be kept.
146 void ConvertFormList(GList
* found
,
147 const std::string
& filter_by_signon_realm
,
148 const PSLMatchingHelper
& helper
,
149 NativeBackendGnome::PasswordFormList
* forms
) {
150 PSLMatchingHelper::PSLDomainMatchMetric psl_domain_match_metric
=
151 PSLMatchingHelper::PSL_DOMAIN_MATCH_NONE
;
152 for (GList
* element
= g_list_first(found
); element
!= NULL
;
153 element
= g_list_next(element
)) {
154 GnomeKeyringFound
* data
= static_cast<GnomeKeyringFound
*>(element
->data
);
155 GnomeKeyringAttributeList
* attrs
= data
->attributes
;
157 scoped_ptr
<PasswordForm
> form(FormFromAttributes(attrs
));
159 if (!filter_by_signon_realm
.empty() &&
160 form
->signon_realm
!= filter_by_signon_realm
) {
161 // This is not an exact match, we try PSL matching.
162 if (!(PSLMatchingHelper::IsPublicSuffixDomainMatch(
163 filter_by_signon_realm
, form
->signon_realm
))) {
166 psl_domain_match_metric
= PSLMatchingHelper::PSL_DOMAIN_MATCH_FOUND
;
167 form
->original_signon_realm
= form
->signon_realm
;
170 form
->password_value
= UTF8ToUTF16(data
->secret
);
172 LOG(WARNING
) << "Unable to access password from list element!";
174 forms
->push_back(form
.release());
176 LOG(WARNING
) << "Could not initialize PasswordForm from attributes!";
179 if (!filter_by_signon_realm
.empty()) {
180 UMA_HISTOGRAM_ENUMERATION(
181 "PasswordManager.PslDomainMatchTriggering",
182 helper
.IsMatchingEnabled()
183 ? psl_domain_match_metric
184 : PSLMatchingHelper::PSL_DOMAIN_MATCH_DISABLED
,
185 PSLMatchingHelper::PSL_DOMAIN_MATCH_COUNT
);
189 // Schema is analagous to the fields in PasswordForm.
190 const GnomeKeyringPasswordSchema kGnomeSchema
= {
191 GNOME_KEYRING_ITEM_GENERIC_SECRET
, {
192 { "origin_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
193 { "action_url", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
194 { "username_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
195 { "username_value", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
196 { "password_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
197 { "submit_element", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
198 { "signon_realm", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
199 { "ssl_valid", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32
},
200 { "preferred", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32
},
201 { "date_created", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
202 { "blacklisted_by_user", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32
},
203 { "scheme", GNOME_KEYRING_ATTRIBUTE_TYPE_UINT32
},
204 // This field is always "chrome" so that we can search for it.
205 { "application", GNOME_KEYRING_ATTRIBUTE_TYPE_STRING
},
210 // Sadly, PasswordStore goes to great lengths to switch from the originally
211 // calling thread to the DB thread, and to provide an asynchronous API to
212 // callers while using a synchronous (virtual) API provided by subclasses like
213 // PasswordStoreX -- but GNOME Keyring really wants to be on the GLib main
214 // thread, which is the UI thread to us. So we end up having to switch threads
215 // again, possibly back to the very same thread (in case the UI thread is the
216 // caller, e.g. in the password management UI), and *block* the DB thread
217 // waiting for a response from the UI thread to provide the synchronous API
218 // PasswordStore expects of us. (It will then in turn switch back to the
219 // original caller to send the asynchronous reply to the original request.)
221 // This class represents a call to a GNOME Keyring method. A RunnableMethod
222 // should be posted to the UI thread to call one of its action methods, and then
223 // a WaitResult() method should be called to wait for the result. Each instance
224 // supports only one outstanding method at a time, though multiple instances may
225 // be used in parallel.
226 class GKRMethod
: public GnomeKeyringLoader
{
228 typedef NativeBackendGnome::PasswordFormList PasswordFormList
;
230 GKRMethod() : event_(false, false), result_(GNOME_KEYRING_RESULT_CANCELLED
) {}
232 // Action methods. These call gnome_keyring_* functions. Call from UI thread.
233 // See GetProfileSpecificAppString() for more information on the app string.
234 void AddLogin(const PasswordForm
& form
, const char* app_string
);
235 void AddLoginSearch(const PasswordForm
& form
, const char* app_string
);
236 void UpdateLoginSearch(const PasswordForm
& form
, const char* app_string
);
237 void RemoveLogin(const PasswordForm
& form
, const char* app_string
);
238 void GetLogins(const PasswordForm
& form
, const char* app_string
);
239 void GetLoginsList(uint32_t blacklisted_by_user
, const char* app_string
);
240 void GetAllLogins(const char* app_string
);
242 // Use after AddLogin, RemoveLogin.
243 GnomeKeyringResult
WaitResult();
245 // Use after AddLoginSearch, UpdateLoginSearch, GetLogins, GetLoginsList,
247 GnomeKeyringResult
WaitResult(PasswordFormList
* forms
);
250 struct GnomeKeyringAttributeListFreeDeleter
{
251 inline void operator()(void* list
) const {
252 gnome_keyring_attribute_list_free(
253 static_cast<GnomeKeyringAttributeList
*>(list
));
257 typedef scoped_ptr
<GnomeKeyringAttributeList
,
258 GnomeKeyringAttributeListFreeDeleter
> ScopedAttributeList
;
260 // Helper methods to abbreviate Gnome Keyring long API names.
261 static void AppendString(ScopedAttributeList
* list
,
264 static void AppendString(ScopedAttributeList
* list
,
266 const std::string
& value
);
267 static void AppendUint32(ScopedAttributeList
* list
,
271 // All these callbacks are called on UI thread.
272 static void OnOperationDone(GnomeKeyringResult result
, gpointer data
);
274 static void OnOperationGetList(GnomeKeyringResult result
, GList
* list
,
277 base::WaitableEvent event_
;
278 GnomeKeyringResult result_
;
279 NativeBackendGnome::PasswordFormList forms_
;
280 // Two additional arguments to OnOperationGetList:
281 // If the credential search is related to a particular form,
282 // |original_signon_realm_| contains the signon realm of that form. It is used
283 // to filter the relevant results out of all the found ones.
284 std::string original_signon_realm_
;
285 const PSLMatchingHelper helper_
;
288 void GKRMethod::AddLogin(const PasswordForm
& form
, const char* app_string
) {
289 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
290 time_t date_created
= form
.date_created
.ToTimeT();
291 // If we are asked to save a password with 0 date, use the current time.
292 // We don't want to actually save passwords as though on January 1, 1970.
294 date_created
= time(NULL
);
295 gnome_keyring_store_password(
297 NULL
, // Default keyring.
298 form
.origin
.spec().c_str(), // Display name.
299 UTF16ToUTF8(form
.password_value
).c_str(),
302 NULL
, // destroy_data
303 "origin_url", form
.origin
.spec().c_str(),
304 "action_url", form
.action
.spec().c_str(),
305 "username_element", UTF16ToUTF8(form
.username_element
).c_str(),
306 "username_value", UTF16ToUTF8(form
.username_value
).c_str(),
307 "password_element", UTF16ToUTF8(form
.password_element
).c_str(),
308 "submit_element", UTF16ToUTF8(form
.submit_element
).c_str(),
309 "signon_realm", form
.signon_realm
.c_str(),
310 "ssl_valid", form
.ssl_valid
,
311 "preferred", form
.preferred
,
312 "date_created", base::Int64ToString(date_created
).c_str(),
313 "blacklisted_by_user", form
.blacklisted_by_user
,
314 "scheme", form
.scheme
,
315 "application", app_string
,
319 void GKRMethod::AddLoginSearch(const PasswordForm
& form
,
320 const char* app_string
) {
321 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
322 original_signon_realm_
= form
.signon_realm
;
323 // Search GNOME Keyring for matching passwords to update.
324 ScopedAttributeList
attrs(gnome_keyring_attribute_list_new());
325 AppendString(&attrs
, "origin_url", form
.origin
.spec());
326 AppendString(&attrs
, "username_element", UTF16ToUTF8(form
.username_element
));
327 AppendString(&attrs
, "username_value", UTF16ToUTF8(form
.username_value
));
328 AppendString(&attrs
, "password_element", UTF16ToUTF8(form
.password_element
));
329 AppendString(&attrs
, "submit_element", UTF16ToUTF8(form
.submit_element
));
330 AppendString(&attrs
, "signon_realm", form
.signon_realm
);
331 AppendString(&attrs
, "application", app_string
);
332 gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET
,
336 /*destroy_data=*/NULL
);
339 void GKRMethod::UpdateLoginSearch(const PasswordForm
& form
,
340 const char* app_string
) {
341 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
342 original_signon_realm_
= form
.signon_realm
;
343 // Search GNOME Keyring for matching passwords to update.
344 ScopedAttributeList
attrs(gnome_keyring_attribute_list_new());
345 AppendString(&attrs
, "origin_url", form
.origin
.spec());
346 AppendString(&attrs
, "username_element", UTF16ToUTF8(form
.username_element
));
347 AppendString(&attrs
, "username_value", UTF16ToUTF8(form
.username_value
));
348 AppendString(&attrs
, "password_element", UTF16ToUTF8(form
.password_element
));
349 AppendString(&attrs
, "signon_realm", form
.signon_realm
);
350 AppendString(&attrs
, "application", app_string
);
351 gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET
,
355 /*destroy_data=*/NULL
);
358 void GKRMethod::RemoveLogin(const PasswordForm
& form
, const char* app_string
) {
359 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
360 // We find forms using the same fields as LoginDatabase::RemoveLogin().
361 gnome_keyring_delete_password(
365 NULL
, // destroy_data
366 "origin_url", form
.origin
.spec().c_str(),
367 "action_url", form
.action
.spec().c_str(),
368 "username_element", UTF16ToUTF8(form
.username_element
).c_str(),
369 "username_value", UTF16ToUTF8(form
.username_value
).c_str(),
370 "password_element", UTF16ToUTF8(form
.password_element
).c_str(),
371 "submit_element", UTF16ToUTF8(form
.submit_element
).c_str(),
372 "signon_realm", form
.signon_realm
.c_str(),
373 "application", app_string
,
377 void GKRMethod::GetLogins(const PasswordForm
& form
, const char* app_string
) {
378 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
379 original_signon_realm_
= form
.signon_realm
;
380 // Search GNOME Keyring for matching passwords.
381 ScopedAttributeList
attrs(gnome_keyring_attribute_list_new());
382 if (!helper_
.ShouldPSLDomainMatchingApply(
383 PSLMatchingHelper::GetRegistryControlledDomain(
384 GURL(form
.signon_realm
)))) {
385 AppendString(&attrs
, "signon_realm", form
.signon_realm
);
387 AppendString(&attrs
, "application", app_string
);
388 gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET
,
392 /*destroy_data=*/NULL
);
395 void GKRMethod::GetLoginsList(uint32_t blacklisted_by_user
,
396 const char* app_string
) {
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
398 original_signon_realm_
.clear();
399 // Search GNOME Keyring for matching passwords.
400 ScopedAttributeList
attrs(gnome_keyring_attribute_list_new());
401 AppendUint32(&attrs
, "blacklisted_by_user", blacklisted_by_user
);
402 AppendString(&attrs
, "application", app_string
);
403 gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET
,
407 /*destroy_data=*/NULL
);
410 void GKRMethod::GetAllLogins(const char* app_string
) {
411 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
412 original_signon_realm_
.clear();
413 // We need to search for something, otherwise we get no results - so
414 // we search for the fixed application string.
415 ScopedAttributeList
attrs(gnome_keyring_attribute_list_new());
416 AppendString(&attrs
, "application", app_string
);
417 gnome_keyring_find_items(GNOME_KEYRING_ITEM_GENERIC_SECRET
,
421 /*destroy_data=*/NULL
);
424 GnomeKeyringResult
GKRMethod::WaitResult() {
425 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
430 GnomeKeyringResult
GKRMethod::WaitResult(PasswordFormList
* forms
) {
431 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
433 if (forms
->empty()) {
434 // Normal case. Avoid extra allocation by swapping.
437 // Rare case. Append forms_ to *forms.
438 forms
->insert(forms
->end(), forms_
.begin(), forms_
.end());
445 void GKRMethod::AppendString(GKRMethod::ScopedAttributeList
* list
,
448 gnome_keyring_attribute_list_append_string(list
->get(), name
, value
);
452 void GKRMethod::AppendString(GKRMethod::ScopedAttributeList
* list
,
454 const std::string
& value
) {
455 AppendString(list
, name
, value
.c_str());
459 void GKRMethod::AppendUint32(GKRMethod::ScopedAttributeList
* list
,
462 gnome_keyring_attribute_list_append_uint32(list
->get(), name
, value
);
466 void GKRMethod::OnOperationDone(GnomeKeyringResult result
, gpointer data
) {
467 GKRMethod
* method
= static_cast<GKRMethod
*>(data
);
468 method
->result_
= result
;
469 method
->event_
.Signal();
473 void GKRMethod::OnOperationGetList(GnomeKeyringResult result
, GList
* list
,
475 GKRMethod
* method
= static_cast<GKRMethod
*>(data
);
476 method
->result_
= result
;
477 method
->forms_
.clear();
478 // |list| will be freed after this callback returns, so convert it now.
480 list
, method
->original_signon_realm_
, method
->helper_
, &method
->forms_
);
481 method
->original_signon_realm_
.clear();
482 method
->event_
.Signal();
487 NativeBackendGnome::NativeBackendGnome(LocalProfileId id
, PrefService
* prefs
)
488 : profile_id_(id
), prefs_(prefs
) {
489 // TODO(mdm): after a few more releases, remove the code which is now dead due
490 // to the true || here, and simplify this code. We don't do it yet to make it
491 // easier to revert if necessary.
492 if (true || PasswordStoreX::PasswordsUseLocalProfileId(prefs
)) {
493 app_string_
= GetProfileSpecificAppString();
494 // We already did the migration previously. Don't try again.
495 migrate_tried_
= true;
497 app_string_
= kGnomeKeyringAppString
;
498 migrate_tried_
= false;
502 NativeBackendGnome::~NativeBackendGnome() {
505 bool NativeBackendGnome::Init() {
506 return LoadGnomeKeyring() && gnome_keyring_is_available();
509 bool NativeBackendGnome::RawAddLogin(const PasswordForm
& form
) {
510 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
512 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
513 base::Bind(&GKRMethod::AddLogin
,
514 base::Unretained(&method
),
515 form
, app_string_
.c_str()));
516 GnomeKeyringResult result
= method
.WaitResult();
517 if (result
!= GNOME_KEYRING_RESULT_OK
) {
518 LOG(ERROR
) << "Keyring save failed: "
519 << gnome_keyring_result_to_message(result
);
522 // Successful write. Try migration if necessary.
524 MigrateToProfileSpecificLogins();
528 bool NativeBackendGnome::AddLogin(const PasswordForm
& form
) {
529 // Based on LoginDatabase::AddLogin(), we search for an existing match based
530 // on origin_url, username_element, username_value, password_element, submit
531 // element, and signon_realm first, remove that, and then add the new entry.
532 // We'd add the new one first, and then delete the original, but then the
533 // delete might actually delete the newly-added entry!
534 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
536 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
537 base::Bind(&GKRMethod::AddLoginSearch
,
538 base::Unretained(&method
),
539 form
, app_string_
.c_str()));
540 PasswordFormList forms
;
541 GnomeKeyringResult result
= method
.WaitResult(&forms
);
542 if (result
!= GNOME_KEYRING_RESULT_OK
&&
543 result
!= GNOME_KEYRING_RESULT_NO_MATCH
) {
544 LOG(ERROR
) << "Keyring find failed: "
545 << gnome_keyring_result_to_message(result
);
548 if (forms
.size() > 0) {
549 if (forms
.size() > 1) {
550 LOG(WARNING
) << "Adding login when there are " << forms
.size()
551 << " matching logins already! Will replace only the first.";
554 // We try migration before updating the existing logins, since otherwise
555 // we'd do it after making some but not all of the changes below.
556 if (forms
.size() > 0 && !migrate_tried_
)
557 MigrateToProfileSpecificLogins();
559 RemoveLogin(*forms
[0]);
560 for (size_t i
= 0; i
< forms
.size(); ++i
)
563 return RawAddLogin(form
);
566 bool NativeBackendGnome::UpdateLogin(const PasswordForm
& form
) {
567 // Based on LoginDatabase::UpdateLogin(), we search for forms to update by
568 // origin_url, username_element, username_value, password_element, and
569 // signon_realm. We then compare the result to the updated form. If they
570 // differ in any of the action, password_value, ssl_valid, or preferred
571 // fields, then we remove the original, and then add the new entry. We'd add
572 // the new one first, and then delete the original, but then the delete might
573 // actually delete the newly-added entry!
574 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
576 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
577 base::Bind(&GKRMethod::UpdateLoginSearch
,
578 base::Unretained(&method
),
579 form
, app_string_
.c_str()));
580 PasswordFormList forms
;
581 GnomeKeyringResult result
= method
.WaitResult(&forms
);
582 if (result
!= GNOME_KEYRING_RESULT_OK
) {
583 LOG(ERROR
) << "Keyring find failed: "
584 << gnome_keyring_result_to_message(result
);
588 // We try migration before updating the existing logins, since otherwise
589 // we'd do it after making some but not all of the changes below.
590 if (forms
.size() > 0 && !migrate_tried_
)
591 MigrateToProfileSpecificLogins();
594 for (size_t i
= 0; i
< forms
.size(); ++i
) {
595 if (forms
[i
]->action
!= form
.action
||
596 forms
[i
]->password_value
!= form
.password_value
||
597 forms
[i
]->ssl_valid
!= form
.ssl_valid
||
598 forms
[i
]->preferred
!= form
.preferred
) {
599 RemoveLogin(*forms
[i
]);
602 for (size_t i
= 0; i
< forms
.size(); ++i
) {
603 if (forms
[i
]->action
!= form
.action
||
604 forms
[i
]->password_value
!= form
.password_value
||
605 forms
[i
]->ssl_valid
!= form
.ssl_valid
||
606 forms
[i
]->preferred
!= form
.preferred
) {
607 forms
[i
]->action
= form
.action
;
608 forms
[i
]->password_value
= form
.password_value
;
609 forms
[i
]->ssl_valid
= form
.ssl_valid
;
610 forms
[i
]->preferred
= form
.preferred
;
611 if (!RawAddLogin(*forms
[i
]))
619 bool NativeBackendGnome::RemoveLogin(const PasswordForm
& form
) {
620 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
622 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
623 base::Bind(&GKRMethod::RemoveLogin
,
624 base::Unretained(&method
),
625 form
, app_string_
.c_str()));
626 GnomeKeyringResult result
= method
.WaitResult();
627 if (result
!= GNOME_KEYRING_RESULT_OK
) {
628 // Warning, not error, because this can sometimes happen due to the user
629 // racing with the daemon to delete the password a second time.
630 LOG(WARNING
) << "Keyring delete failed: "
631 << gnome_keyring_result_to_message(result
);
634 // Successful write. Try migration if necessary. Note that presumably if we've
635 // been asked to delete a login, it's because we returned it previously; thus,
636 // this will probably never happen since we'd have already tried migration.
638 MigrateToProfileSpecificLogins();
642 bool NativeBackendGnome::RemoveLoginsCreatedBetween(
643 const base::Time
& delete_begin
,
644 const base::Time
& delete_end
) {
645 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
647 // We could walk the list and delete items as we find them, but it is much
648 // easier to build the list and use RemoveLogin() to delete them.
649 PasswordFormList forms
;
650 if (!GetAllLogins(&forms
))
652 // No need to try migration here: GetAllLogins() does it.
654 for (size_t i
= 0; i
< forms
.size(); ++i
) {
655 if (delete_begin
<= forms
[i
]->date_created
&&
656 (delete_end
.is_null() || forms
[i
]->date_created
< delete_end
)) {
657 if (!RemoveLogin(*forms
[i
]))
665 bool NativeBackendGnome::GetLogins(const PasswordForm
& form
,
666 PasswordFormList
* forms
) {
667 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
669 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
670 base::Bind(&GKRMethod::GetLogins
,
671 base::Unretained(&method
),
672 form
, app_string_
.c_str()));
673 GnomeKeyringResult result
= method
.WaitResult(forms
);
674 if (result
== GNOME_KEYRING_RESULT_NO_MATCH
)
676 if (result
!= GNOME_KEYRING_RESULT_OK
) {
677 LOG(ERROR
) << "Keyring find failed: "
678 << gnome_keyring_result_to_message(result
);
681 // Successful read of actual data. Try migration if necessary.
683 MigrateToProfileSpecificLogins();
687 bool NativeBackendGnome::GetLoginsCreatedBetween(const base::Time
& get_begin
,
688 const base::Time
& get_end
,
689 PasswordFormList
* forms
) {
690 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
691 // We could walk the list and add items as we find them, but it is much
692 // easier to build the list and then filter the results.
693 PasswordFormList all_forms
;
694 if (!GetAllLogins(&all_forms
))
696 // No need to try migration here: GetAllLogins() does it.
698 forms
->reserve(forms
->size() + all_forms
.size());
699 for (size_t i
= 0; i
< all_forms
.size(); ++i
) {
700 if (get_begin
<= all_forms
[i
]->date_created
&&
701 (get_end
.is_null() || all_forms
[i
]->date_created
< get_end
)) {
702 forms
->push_back(all_forms
[i
]);
711 bool NativeBackendGnome::GetAutofillableLogins(PasswordFormList
* forms
) {
712 return GetLoginsList(forms
, true);
715 bool NativeBackendGnome::GetBlacklistLogins(PasswordFormList
* forms
) {
716 return GetLoginsList(forms
, false);
719 bool NativeBackendGnome::GetLoginsList(PasswordFormList
* forms
,
721 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
723 uint32_t blacklisted_by_user
= !autofillable
;
726 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
727 base::Bind(&GKRMethod::GetLoginsList
,
728 base::Unretained(&method
),
729 blacklisted_by_user
, app_string_
.c_str()));
730 GnomeKeyringResult result
= method
.WaitResult(forms
);
731 if (result
== GNOME_KEYRING_RESULT_NO_MATCH
)
733 if (result
!= GNOME_KEYRING_RESULT_OK
) {
734 LOG(ERROR
) << "Keyring find failed: "
735 << gnome_keyring_result_to_message(result
);
738 // Successful read of actual data. Try migration if necessary.
740 MigrateToProfileSpecificLogins();
744 bool NativeBackendGnome::GetAllLogins(PasswordFormList
* forms
) {
746 BrowserThread::PostTask(BrowserThread::UI
, FROM_HERE
,
747 base::Bind(&GKRMethod::GetAllLogins
,
748 base::Unretained(&method
),
749 app_string_
.c_str()));
750 GnomeKeyringResult result
= method
.WaitResult(forms
);
751 if (result
== GNOME_KEYRING_RESULT_NO_MATCH
)
753 if (result
!= GNOME_KEYRING_RESULT_OK
) {
754 LOG(ERROR
) << "Keyring find failed: "
755 << gnome_keyring_result_to_message(result
);
758 // Successful read of actual data. Try migration if necessary.
760 MigrateToProfileSpecificLogins();
764 std::string
NativeBackendGnome::GetProfileSpecificAppString() const {
765 // Originally, the application string was always just "chrome" and used only
766 // so that we had *something* to search for since GNOME Keyring won't search
767 // for nothing. Now we use it to distinguish passwords for different profiles.
768 return base::StringPrintf("%s-%d", kGnomeKeyringAppString
, profile_id_
);
771 void NativeBackendGnome::MigrateToProfileSpecificLogins() {
772 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB
));
774 DCHECK(!migrate_tried_
);
775 DCHECK_EQ(app_string_
, kGnomeKeyringAppString
);
777 // Record the fact that we've attempted migration already right away, so that
778 // we don't get recursive calls back to MigrateToProfileSpecificLogins().
779 migrate_tried_
= true;
781 // First get all the logins, using the old app string.
782 PasswordFormList forms
;
783 if (!GetAllLogins(&forms
))
786 // Now switch to a profile-specific app string.
787 app_string_
= GetProfileSpecificAppString();
789 // Try to add all the logins with the new app string.
791 for (size_t i
= 0; i
< forms
.size(); ++i
) {
792 if (!RawAddLogin(*forms
[i
]))
798 // All good! Keep the new app string and set a persistent pref.
799 // NOTE: We explicitly don't delete the old passwords yet. They are
800 // potentially shared with other profiles and other user data dirs!
801 // Each other profile must be able to migrate the shared data as well,
802 // so we must leave it alone. After a few releases, we'll add code to
803 // delete them, and eventually remove this migration code.
804 // TODO(mdm): follow through with the plan above.
805 PasswordStoreX::SetPasswordsUseLocalProfileId(prefs_
);
807 // We failed to migrate for some reason. Use the old app string.
808 app_string_
= kGnomeKeyringAppString
;