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_kwallet_x.h"
10 #include "base/logging.h"
11 #include "base/metrics/histogram.h"
12 #include "base/pickle.h"
13 #include "base/stl_util.h"
14 #include "base/strings/stringprintf.h"
15 #include "base/synchronization/waitable_event.h"
16 #include "base/threading/thread_restrictions.h"
17 #include "chrome/grit/chromium_strings.h"
18 #include "components/autofill/core/common/password_form.h"
19 #include "content/public/browser/browser_thread.h"
21 #include "dbus/message.h"
22 #include "dbus/object_path.h"
23 #include "dbus/object_proxy.h"
24 #include "ui/base/l10n/l10n_util.h"
26 using autofill::PasswordForm
;
27 using content::BrowserThread
;
31 // In case the fields in the pickle ever change, version them so we can try to
32 // read old pickles. (Note: do not eat old pickles past the expiration date.)
33 const int kPickleVersion
= 7;
35 // We could localize this string, but then changing your locale would cause
36 // you to lose access to all your stored passwords. Maybe best not to do that.
37 // Name of the folder to store passwords in.
38 const char kKWalletFolder
[] = "Chrome Form Data";
40 // DBus service, path, and interface names for klauncher and kwalletd.
41 const char kKWalletServiceName
[] = "org.kde.kwalletd";
42 const char kKWalletPath
[] = "/modules/kwalletd";
43 const char kKWalletInterface
[] = "org.kde.KWallet";
44 const char kKLauncherServiceName
[] = "org.kde.klauncher";
45 const char kKLauncherPath
[] = "/KLauncher";
46 const char kKLauncherInterface
[] = "org.kde.KLauncher";
48 // Compares two PasswordForms and returns true if they are the same.
49 // If |update_check| is false, we only check the fields that are checked by
50 // LoginDatabase::UpdateLogin() when updating logins; otherwise, we check the
51 // fields that are checked by LoginDatabase::RemoveLogin() for removing them.
52 bool CompareForms(const autofill::PasswordForm
& a
,
53 const autofill::PasswordForm
& b
,
55 // An update check doesn't care about the submit element.
56 if (!update_check
&& a
.submit_element
!= b
.submit_element
)
58 return a
.origin
== b
.origin
&&
59 a
.password_element
== b
.password_element
&&
60 a
.signon_realm
== b
.signon_realm
&&
61 a
.username_element
== b
.username_element
&&
62 a
.username_value
== b
.username_value
;
65 // Checks a serialized list of PasswordForms for sanity. Returns true if OK.
66 // Note that |realm| is only used for generating a useful warning message.
67 bool CheckSerializedValue(const uint8_t* byte_array
,
69 const std::string
& realm
) {
70 const base::Pickle::Header
* header
=
71 reinterpret_cast<const base::Pickle::Header
*>(byte_array
);
72 if (length
< sizeof(*header
) ||
73 header
->payload_size
> length
- sizeof(*header
)) {
74 LOG(WARNING
) << "Invalid KWallet entry detected (realm: " << realm
<< ")";
80 // Convenience function to read a GURL from a Pickle. Assumes the URL has
81 // been written as a UTF-8 string. Returns true on success.
82 bool ReadGURL(base::PickleIterator
* iter
, bool warn_only
, GURL
* url
) {
83 std::string url_string
;
84 if (!iter
->ReadString(&url_string
)) {
86 LOG(ERROR
) << "Failed to deserialize URL.";
90 *url
= GURL(url_string
);
94 void LogDeserializationWarning(int version
,
95 std::string signon_realm
,
98 LOG(WARNING
) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm
100 << ") with native architecture size; will try alternate "
103 LOG(ERROR
) << "Failed to deserialize version " << version
104 << " KWallet entry (realm: " << signon_realm
<< ")";
108 // Deserializes a list of credentials from the wallet to |forms| (replacing
109 // the contents of |forms|). |size_32| controls reading the size field within
110 // the pickle as 32 bits. We used to use Pickle::WriteSize() to write the number
111 // of password forms, but that has a different size on 32- and 64-bit systems.
112 // So, now we always write a 64-bit quantity, but we support trying to read it
113 // as either size when reading old pickles that fail to deserialize using the
114 // native size. Returns true on success.
115 bool DeserializeValueSize(const std::string
& signon_realm
,
116 const base::PickleIterator
& init_iter
,
120 ScopedVector
<autofill::PasswordForm
>* forms
) {
121 base::PickleIterator iter
= init_iter
;
125 uint32_t count_32
= 0;
126 if (!iter
.ReadUInt32(&count_32
)) {
127 LOG(ERROR
) << "Failed to deserialize KWallet entry "
128 << "(realm: " << signon_realm
<< ")";
133 if (!iter
.ReadSizeT(&count
)) {
134 LOG(ERROR
) << "Failed to deserialize KWallet entry "
135 << "(realm: " << signon_realm
<< ")";
140 if (count
> 0xFFFF) {
141 // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
142 // This is a very large number of passwords to be saved for a single realm.
143 // It is almost certainly a corrupt pickle and not real data. Ignore it.
144 // This very well might actually be http://crbug.com/107701, so if we're
145 // reading an old pickle, we don't even log this the first time we try to
146 // read it. (That is, when we're reading the native architecture size.)
148 LOG(ERROR
) << "Suspiciously large number of entries in KWallet entry "
149 << "(" << count
<< "; realm: " << signon_realm
<< ")";
154 // We'll swap |converted_forms| with |*forms| on success, to make sure we
155 // don't return partial results on failure.
156 ScopedVector
<autofill::PasswordForm
> converted_forms
;
157 converted_forms
.reserve(count
);
158 for (size_t i
= 0; i
< count
; ++i
) {
159 scoped_ptr
<PasswordForm
> form(new PasswordForm());
160 form
->signon_realm
.assign(signon_realm
);
163 int64 date_created
= 0;
165 int generation_upload_status
= 0;
166 // Note that these will be read back in the order listed due to
167 // short-circuit evaluation. This is important.
168 if (!iter
.ReadInt(&scheme
) ||
169 !ReadGURL(&iter
, warn_only
, &form
->origin
) ||
170 !ReadGURL(&iter
, warn_only
, &form
->action
) ||
171 !iter
.ReadString16(&form
->username_element
) ||
172 !iter
.ReadString16(&form
->username_value
) ||
173 !iter
.ReadString16(&form
->password_element
) ||
174 !iter
.ReadString16(&form
->password_value
) ||
175 !iter
.ReadString16(&form
->submit_element
) ||
176 !iter
.ReadBool(&form
->ssl_valid
) ||
177 !iter
.ReadBool(&form
->preferred
) ||
178 !iter
.ReadBool(&form
->blacklisted_by_user
) ||
179 !iter
.ReadInt64(&date_created
)) {
180 LogDeserializationWarning(version
, signon_realm
, warn_only
);
183 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme
);
186 if (!iter
.ReadInt(&type
) ||
187 !iter
.ReadInt(&form
->times_used
) ||
188 !autofill::DeserializeFormData(&iter
, &form
->form_data
)) {
189 LogDeserializationWarning(version
, signon_realm
, false);
192 form
->type
= static_cast<PasswordForm::Type
>(type
);
196 int64 date_synced
= 0;
197 if (!iter
.ReadInt64(&date_synced
)) {
198 LogDeserializationWarning(version
, signon_realm
, false);
201 form
->date_synced
= base::Time::FromInternalValue(date_synced
);
205 if (!iter
.ReadString16(&form
->display_name
) ||
206 !ReadGURL(&iter
, warn_only
, &form
->icon_url
) ||
207 !ReadGURL(&iter
, warn_only
, &form
->federation_url
) ||
208 !iter
.ReadBool(&form
->skip_zero_click
)) {
209 LogDeserializationWarning(version
, signon_realm
, false);
215 form
->date_created
= base::Time::FromInternalValue(date_created
);
217 form
->date_created
= base::Time::FromTimeT(date_created
);
221 bool read_success
= iter
.ReadInt(&generation_upload_status
);
222 if (!read_success
&& version
> 6) {
223 // Valid version 6 pickles might still lack the
224 // generation_upload_status, see http://crbug.com/494229#c11.
225 LogDeserializationWarning(version
, signon_realm
, false);
229 form
->generation_upload_status
=
230 static_cast<PasswordForm::GenerationUploadStatus
>(
231 generation_upload_status
);
235 converted_forms
.push_back(form
.Pass());
238 forms
->swap(converted_forms
);
242 // Serializes a list of PasswordForms to be stored in the wallet.
243 void SerializeValue(const std::vector
<autofill::PasswordForm
*>& forms
,
244 base::Pickle
* pickle
) {
245 pickle
->WriteInt(kPickleVersion
);
246 pickle
->WriteSizeT(forms
.size());
247 for (autofill::PasswordForm
* form
: forms
) {
248 pickle
->WriteInt(form
->scheme
);
249 pickle
->WriteString(form
->origin
.spec());
250 pickle
->WriteString(form
->action
.spec());
251 pickle
->WriteString16(form
->username_element
);
252 pickle
->WriteString16(form
->username_value
);
253 pickle
->WriteString16(form
->password_element
);
254 pickle
->WriteString16(form
->password_value
);
255 pickle
->WriteString16(form
->submit_element
);
256 pickle
->WriteBool(form
->ssl_valid
);
257 pickle
->WriteBool(form
->preferred
);
258 pickle
->WriteBool(form
->blacklisted_by_user
);
259 pickle
->WriteInt64(form
->date_created
.ToInternalValue());
260 pickle
->WriteInt(form
->type
);
261 pickle
->WriteInt(form
->times_used
);
262 autofill::SerializeFormData(form
->form_data
, pickle
);
263 pickle
->WriteInt64(form
->date_synced
.ToInternalValue());
264 pickle
->WriteString16(form
->display_name
);
265 pickle
->WriteString(form
->icon_url
.spec());
266 pickle
->WriteString(form
->federation_url
.spec());
267 pickle
->WriteBool(form
->skip_zero_click
);
268 pickle
->WriteInt(form
->generation_upload_status
);
272 // Moves the content of |second| to the end of |first|.
273 void AppendSecondToFirst(ScopedVector
<autofill::PasswordForm
>* first
,
274 ScopedVector
<autofill::PasswordForm
> second
) {
275 first
->reserve(first
->size() + second
.size());
276 first
->insert(first
->end(), second
.begin(), second
.end());
280 void UMALogDeserializationStatus(bool success
) {
281 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
287 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id
)
289 kwallet_proxy_(nullptr),
290 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME
)) {
291 folder_name_
= GetProfileSpecificFolderName();
294 NativeBackendKWallet::~NativeBackendKWallet() {
295 // This destructor is called on the thread that is destroying the Profile
296 // containing the PasswordStore that owns this NativeBackend. Generally that
297 // won't be the DB thread; it will be the UI thread. So we post a message to
298 // shut it down on the DB thread, and it will be destructed afterward when the
299 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
300 // destroyed before that occurs, but that's OK.
301 if (session_bus_
.get()) {
302 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
303 base::Bind(&dbus::Bus::ShutdownAndBlock
,
304 session_bus_
.get()));
308 bool NativeBackendKWallet::Init() {
309 // Without the |optional_bus| parameter, a real bus will be instantiated.
310 return InitWithBus(scoped_refptr
<dbus::Bus
>());
313 bool NativeBackendKWallet::InitWithBus(scoped_refptr
<dbus::Bus
> optional_bus
) {
314 // We must synchronously do a few DBus calls to figure out if initialization
315 // succeeds, but later, we'll want to do most work on the DB thread. So we
316 // have to do the initialization on the DB thread here too, and wait for it.
317 bool success
= false;
318 base::WaitableEvent
event(false, false);
319 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
320 // to finish, so we can safely use base::Unretained here.
321 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
322 base::Bind(&NativeBackendKWallet::InitOnDBThread
,
323 base::Unretained(this),
324 optional_bus
, &event
, &success
));
326 // This ScopedAllowWait should not be here. http://crbug.com/125331
327 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
332 void NativeBackendKWallet::InitOnDBThread(scoped_refptr
<dbus::Bus
> optional_bus
,
333 base::WaitableEvent
* event
,
335 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
336 DCHECK(!session_bus_
.get());
337 if (optional_bus
.get()) {
338 // The optional_bus parameter is given when this method is called in tests.
339 session_bus_
= optional_bus
;
341 // Get a (real) connection to the session bus.
342 dbus::Bus::Options options
;
343 options
.bus_type
= dbus::Bus::SESSION
;
344 options
.connection_type
= dbus::Bus::PRIVATE
;
345 session_bus_
= new dbus::Bus(options
);
348 session_bus_
->GetObjectProxy(kKWalletServiceName
,
349 dbus::ObjectPath(kKWalletPath
));
350 // kwalletd may not be running. If we get a temporary failure initializing it,
351 // try to start it and then try again. (Note the short-circuit evaluation.)
352 const InitResult result
= InitWallet();
353 *success
= (result
== INIT_SUCCESS
||
354 (result
== TEMPORARY_FAIL
&&
355 StartKWalletd() && InitWallet() == INIT_SUCCESS
));
359 bool NativeBackendKWallet::StartKWalletd() {
360 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
361 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
362 // klauncher to start it.
363 dbus::ObjectProxy
* klauncher
=
364 session_bus_
->GetObjectProxy(kKLauncherServiceName
,
365 dbus::ObjectPath(kKLauncherPath
));
367 dbus::MethodCall
method_call(kKLauncherInterface
,
368 "start_service_by_desktop_name");
369 dbus::MessageWriter
builder(&method_call
);
370 std::vector
<std::string
> empty
;
371 builder
.AppendString("kwalletd"); // serviceName
372 builder
.AppendArrayOfStrings(empty
); // urls
373 builder
.AppendArrayOfStrings(empty
); // envs
374 builder
.AppendString(std::string()); // startup_id
375 builder
.AppendBool(false); // blind
376 scoped_ptr
<dbus::Response
> response(
377 klauncher
->CallMethodAndBlock(
378 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
379 if (!response
.get()) {
380 LOG(ERROR
) << "Error contacting klauncher to start kwalletd";
383 dbus::MessageReader
reader(response
.get());
385 std::string dbus_name
;
388 if (!reader
.PopInt32(&ret
) || !reader
.PopString(&dbus_name
) ||
389 !reader
.PopString(&error
) || !reader
.PopInt32(&pid
)) {
390 LOG(ERROR
) << "Error reading response from klauncher to start kwalletd: "
391 << response
->ToString();
394 if (!error
.empty() || ret
) {
395 LOG(ERROR
) << "Error launching kwalletd: error '" << error
<< "' "
396 << " (code " << ret
<< ")";
403 NativeBackendKWallet::InitResult
NativeBackendKWallet::InitWallet() {
404 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
406 // Check that KWallet is enabled.
407 dbus::MethodCall
method_call(kKWalletInterface
, "isEnabled");
408 scoped_ptr
<dbus::Response
> response(
409 kwallet_proxy_
->CallMethodAndBlock(
410 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
411 if (!response
.get()) {
412 LOG(ERROR
) << "Error contacting kwalletd (isEnabled)";
413 return TEMPORARY_FAIL
;
415 dbus::MessageReader
reader(response
.get());
416 bool enabled
= false;
417 if (!reader
.PopBool(&enabled
)) {
418 LOG(ERROR
) << "Error reading response from kwalletd (isEnabled): "
419 << response
->ToString();
420 return PERMANENT_FAIL
;
422 // Not enabled? Don't use KWallet. But also don't warn here.
424 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
425 return PERMANENT_FAIL
;
430 // Get the wallet name.
431 dbus::MethodCall
method_call(kKWalletInterface
, "networkWallet");
432 scoped_ptr
<dbus::Response
> response(
433 kwallet_proxy_
->CallMethodAndBlock(
434 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
435 if (!response
.get()) {
436 LOG(ERROR
) << "Error contacting kwalletd (networkWallet)";
437 return TEMPORARY_FAIL
;
439 dbus::MessageReader
reader(response
.get());
440 if (!reader
.PopString(&wallet_name_
)) {
441 LOG(ERROR
) << "Error reading response from kwalletd (networkWallet): "
442 << response
->ToString();
443 return PERMANENT_FAIL
;
450 password_manager::PasswordStoreChangeList
NativeBackendKWallet::AddLogin(
451 const PasswordForm
& form
) {
452 int wallet_handle
= WalletHandle();
453 if (wallet_handle
== kInvalidKWalletHandle
)
454 return password_manager::PasswordStoreChangeList();
456 ScopedVector
<autofill::PasswordForm
> forms
;
457 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
458 return password_manager::PasswordStoreChangeList();
460 // We search for a login to update, rather than unconditionally appending the
461 // login, because in some cases (especially involving sync) we can be asked to
462 // add a login that already exists. In these cases we want to just update.
463 bool updated
= false;
464 password_manager::PasswordStoreChangeList changes
;
465 for (size_t i
= 0; i
< forms
.size(); ++i
) {
466 // Use the more restrictive removal comparison, so that we never have
467 // duplicate logins that would all be removed together by RemoveLogin().
468 if (CompareForms(form
, *forms
[i
], false)) {
469 changes
.push_back(password_manager::PasswordStoreChange(
470 password_manager::PasswordStoreChange::REMOVE
, *forms
[i
]));
476 forms
.push_back(new PasswordForm(form
));
477 changes
.push_back(password_manager::PasswordStoreChange(
478 password_manager::PasswordStoreChange::ADD
, form
));
480 bool ok
= SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
);
487 bool NativeBackendKWallet::UpdateLogin(
488 const PasswordForm
& form
,
489 password_manager::PasswordStoreChangeList
* changes
) {
492 int wallet_handle
= WalletHandle();
493 if (wallet_handle
== kInvalidKWalletHandle
)
496 ScopedVector
<autofill::PasswordForm
> forms
;
497 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
500 bool updated
= false;
501 for (size_t i
= 0; i
< forms
.size(); ++i
) {
502 if (CompareForms(form
, *forms
[i
], true)) {
510 if (SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
)) {
511 changes
->push_back(password_manager::PasswordStoreChange(
512 password_manager::PasswordStoreChange::UPDATE
, form
));
519 bool NativeBackendKWallet::RemoveLogin(const PasswordForm
& form
) {
520 int wallet_handle
= WalletHandle();
521 if (wallet_handle
== kInvalidKWalletHandle
)
524 ScopedVector
<autofill::PasswordForm
> all_forms
;
525 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &all_forms
))
528 ScopedVector
<autofill::PasswordForm
> kept_forms
;
529 kept_forms
.reserve(all_forms
.size());
530 for (auto& saved_form
: all_forms
) {
531 if (!CompareForms(form
, *saved_form
, false)) {
532 kept_forms
.push_back(saved_form
);
533 saved_form
= nullptr;
537 // Update the entry in the wallet, possibly deleting it.
538 return SetLoginsList(kept_forms
.get(), form
.signon_realm
, wallet_handle
);
541 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
542 base::Time delete_begin
,
543 base::Time delete_end
,
544 password_manager::PasswordStoreChangeList
* changes
) {
545 return RemoveLoginsBetween(
546 delete_begin
, delete_end
, CREATION_TIMESTAMP
, changes
);
549 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
550 base::Time delete_begin
,
551 base::Time delete_end
,
552 password_manager::PasswordStoreChangeList
* changes
) {
553 return RemoveLoginsBetween(delete_begin
, delete_end
, SYNC_TIMESTAMP
, changes
);
556 bool NativeBackendKWallet::GetLogins(
557 const PasswordForm
& form
,
558 ScopedVector
<autofill::PasswordForm
>* forms
) {
559 int wallet_handle
= WalletHandle();
560 if (wallet_handle
== kInvalidKWalletHandle
)
562 return GetLoginsList(form
.signon_realm
, wallet_handle
, forms
);
565 bool NativeBackendKWallet::GetAutofillableLogins(
566 ScopedVector
<autofill::PasswordForm
>* forms
) {
567 int wallet_handle
= WalletHandle();
568 if (wallet_handle
== kInvalidKWalletHandle
)
570 return GetLoginsList(BlacklistOptions::AUTOFILLABLE
, wallet_handle
, forms
);
573 bool NativeBackendKWallet::GetBlacklistLogins(
574 ScopedVector
<autofill::PasswordForm
>* forms
) {
575 int wallet_handle
= WalletHandle();
576 if (wallet_handle
== kInvalidKWalletHandle
)
578 return GetLoginsList(BlacklistOptions::BLACKLISTED
, wallet_handle
, forms
);
581 bool NativeBackendKWallet::GetLoginsList(
582 const std::string
& signon_realm
,
584 ScopedVector
<autofill::PasswordForm
>* forms
) {
586 // Is there an entry in the wallet?
588 dbus::MethodCall
method_call(kKWalletInterface
, "hasEntry");
589 dbus::MessageWriter
builder(&method_call
);
590 builder
.AppendInt32(wallet_handle
); // handle
591 builder
.AppendString(folder_name_
); // folder
592 builder
.AppendString(signon_realm
); // key
593 builder
.AppendString(app_name_
); // appid
594 scoped_ptr
<dbus::Response
> response(
595 kwallet_proxy_
->CallMethodAndBlock(
596 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
597 if (!response
.get()) {
598 LOG(ERROR
) << "Error contacting kwalletd (hasEntry)";
601 dbus::MessageReader
reader(response
.get());
602 bool has_entry
= false;
603 if (!reader
.PopBool(&has_entry
)) {
604 LOG(ERROR
) << "Error reading response from kwalletd (hasEntry): "
605 << response
->ToString();
609 // This is not an error. There just isn't a matching entry.
615 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
616 dbus::MessageWriter
builder(&method_call
);
617 builder
.AppendInt32(wallet_handle
); // handle
618 builder
.AppendString(folder_name_
); // folder
619 builder
.AppendString(signon_realm
); // key
620 builder
.AppendString(app_name_
); // appid
621 scoped_ptr
<dbus::Response
> response(
622 kwallet_proxy_
->CallMethodAndBlock(
623 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
624 if (!response
.get()) {
625 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
628 dbus::MessageReader
reader(response
.get());
629 const uint8_t* bytes
= nullptr;
631 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
632 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
633 << response
->ToString();
638 if (!CheckSerializedValue(bytes
, length
, signon_realm
)) {
639 // This is weird, but we choose not to call it an error. There is an
640 // invalid entry somehow, but by just ignoring it, we make it easier to
641 // repair without having to delete it using kwalletmanager (that is, by
642 // just saving a new password within this realm to overwrite it).
646 // Can't we all just agree on whether bytes are signed or not? Please?
647 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
648 *forms
= DeserializeValue(signon_realm
, pickle
);
654 bool NativeBackendKWallet::GetLoginsList(
655 BlacklistOptions options
,
657 ScopedVector
<autofill::PasswordForm
>* forms
) {
659 ScopedVector
<autofill::PasswordForm
> all_forms
;
660 if (!GetAllLogins(wallet_handle
, &all_forms
))
663 // We have to read all the entries, and then filter them here.
664 forms
->reserve(all_forms
.size());
665 for (auto& saved_form
: all_forms
) {
666 if (saved_form
->blacklisted_by_user
==
667 (options
== BlacklistOptions::BLACKLISTED
)) {
668 forms
->push_back(saved_form
);
669 saved_form
= nullptr;
676 bool NativeBackendKWallet::GetAllLogins(
678 ScopedVector
<autofill::PasswordForm
>* forms
) {
679 // We could probably also use readEntryList here.
680 std::vector
<std::string
> realm_list
;
682 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
683 dbus::MessageWriter
builder(&method_call
);
684 builder
.AppendInt32(wallet_handle
); // handle
685 builder
.AppendString(folder_name_
); // folder
686 builder
.AppendString(app_name_
); // appid
687 scoped_ptr
<dbus::Response
> response(
688 kwallet_proxy_
->CallMethodAndBlock(
689 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
690 if (!response
.get()) {
691 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
694 dbus::MessageReader
reader(response
.get());
695 if (!reader
.PopArrayOfStrings(&realm_list
)) {
696 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
697 << response
->ToString();
703 for (const std::string
& signon_realm
: realm_list
) {
704 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
705 dbus::MessageWriter
builder(&method_call
);
706 builder
.AppendInt32(wallet_handle
); // handle
707 builder
.AppendString(folder_name_
); // folder
708 builder
.AppendString(signon_realm
); // key
709 builder
.AppendString(app_name_
); // appid
710 scoped_ptr
<dbus::Response
> response(
711 kwallet_proxy_
->CallMethodAndBlock(
712 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
713 if (!response
.get()) {
714 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
717 dbus::MessageReader
reader(response
.get());
718 const uint8_t* bytes
= nullptr;
720 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
721 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
722 << response
->ToString();
725 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
728 // Can't we all just agree on whether bytes are signed or not? Please?
729 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
730 AppendSecondToFirst(forms
, DeserializeValue(signon_realm
, pickle
));
735 bool NativeBackendKWallet::SetLoginsList(
736 const std::vector
<autofill::PasswordForm
*>& forms
,
737 const std::string
& signon_realm
,
740 // No items left? Remove the entry from the wallet.
741 dbus::MethodCall
method_call(kKWalletInterface
, "removeEntry");
742 dbus::MessageWriter
builder(&method_call
);
743 builder
.AppendInt32(wallet_handle
); // handle
744 builder
.AppendString(folder_name_
); // folder
745 builder
.AppendString(signon_realm
); // key
746 builder
.AppendString(app_name_
); // appid
747 scoped_ptr
<dbus::Response
> response(
748 kwallet_proxy_
->CallMethodAndBlock(
749 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
750 if (!response
.get()) {
751 LOG(ERROR
) << "Error contacting kwalletd (removeEntry)";
752 return kInvalidKWalletHandle
;
754 dbus::MessageReader
reader(response
.get());
756 if (!reader
.PopInt32(&ret
)) {
757 LOG(ERROR
) << "Error reading response from kwalletd (removeEntry): "
758 << response
->ToString();
762 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet removeEntry";
767 SerializeValue(forms
, &value
);
769 dbus::MethodCall
method_call(kKWalletInterface
, "writeEntry");
770 dbus::MessageWriter
builder(&method_call
);
771 builder
.AppendInt32(wallet_handle
); // handle
772 builder
.AppendString(folder_name_
); // folder
773 builder
.AppendString(signon_realm
); // key
774 builder
.AppendArrayOfBytes(static_cast<const uint8_t*>(value
.data()),
775 value
.size()); // value
776 builder
.AppendString(app_name_
); // appid
777 scoped_ptr
<dbus::Response
> response(
778 kwallet_proxy_
->CallMethodAndBlock(
779 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
780 if (!response
.get()) {
781 LOG(ERROR
) << "Error contacting kwalletd (writeEntry)";
782 return kInvalidKWalletHandle
;
784 dbus::MessageReader
reader(response
.get());
786 if (!reader
.PopInt32(&ret
)) {
787 LOG(ERROR
) << "Error reading response from kwalletd (writeEntry): "
788 << response
->ToString();
792 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet writeEntry";
796 bool NativeBackendKWallet::RemoveLoginsBetween(
797 base::Time delete_begin
,
798 base::Time delete_end
,
799 TimestampToCompare date_to_compare
,
800 password_manager::PasswordStoreChangeList
* changes
) {
803 int wallet_handle
= WalletHandle();
804 if (wallet_handle
== kInvalidKWalletHandle
)
807 // We could probably also use readEntryList here.
808 std::vector
<std::string
> realm_list
;
810 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
811 dbus::MessageWriter
builder(&method_call
);
812 builder
.AppendInt32(wallet_handle
); // handle
813 builder
.AppendString(folder_name_
); // folder
814 builder
.AppendString(app_name_
); // appid
815 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
816 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
817 if (!response
.get()) {
818 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
821 dbus::MessageReader
reader(response
.get());
822 dbus::MessageReader
array(response
.get());
823 if (!reader
.PopArray(&array
)) {
824 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
825 << response
->ToString();
828 while (array
.HasMoreData()) {
830 if (!array
.PopString(&realm
)) {
831 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
832 << response
->ToString();
835 realm_list
.push_back(realm
);
840 for (size_t i
= 0; i
< realm_list
.size(); ++i
) {
841 const std::string
& signon_realm
= realm_list
[i
];
842 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
843 dbus::MessageWriter
builder(&method_call
);
844 builder
.AppendInt32(wallet_handle
); // handle
845 builder
.AppendString(folder_name_
); // folder
846 builder
.AppendString(signon_realm
); // key
847 builder
.AppendString(app_name_
); // appid
848 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
849 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
850 if (!response
.get()) {
851 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
854 dbus::MessageReader
reader(response
.get());
855 const uint8_t* bytes
= nullptr;
857 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
858 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
859 << response
->ToString();
862 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
865 // Can't we all just agree on whether bytes are signed or not? Please?
866 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
867 ScopedVector
<autofill::PasswordForm
> all_forms
=
868 DeserializeValue(signon_realm
, pickle
);
870 ScopedVector
<autofill::PasswordForm
> kept_forms
;
871 kept_forms
.reserve(all_forms
.size());
872 base::Time
autofill::PasswordForm::*date_member
=
873 date_to_compare
== CREATION_TIMESTAMP
874 ? &autofill::PasswordForm::date_created
875 : &autofill::PasswordForm::date_synced
;
876 for (auto& saved_form
: all_forms
) {
877 if (delete_begin
<= saved_form
->*date_member
&&
878 (delete_end
.is_null() || saved_form
->*date_member
< delete_end
)) {
879 changes
->push_back(password_manager::PasswordStoreChange(
880 password_manager::PasswordStoreChange::REMOVE
, *saved_form
));
882 kept_forms
.push_back(saved_form
);
883 saved_form
= nullptr;
887 if (!SetLoginsList(kept_forms
.get(), signon_realm
, wallet_handle
)) {
896 ScopedVector
<autofill::PasswordForm
> NativeBackendKWallet::DeserializeValue(
897 const std::string
& signon_realm
,
898 const base::Pickle
& pickle
) {
899 base::PickleIterator
iter(pickle
);
902 if (!iter
.ReadInt(&version
) ||
903 version
< 0 || version
> kPickleVersion
) {
904 LOG(ERROR
) << "Failed to deserialize KWallet entry "
905 << "(realm: " << signon_realm
<< ")";
906 return ScopedVector
<autofill::PasswordForm
>();
909 ScopedVector
<autofill::PasswordForm
> forms
;
912 // In current pickles, we expect 64-bit sizes. Failure is an error.
913 success
= DeserializeValueSize(
914 signon_realm
, iter
, version
, false, false, &forms
);
915 UMALogDeserializationStatus(success
);
919 const bool size_32
= sizeof(size_t) == sizeof(uint32_t);
920 if (!DeserializeValueSize(
921 signon_realm
, iter
, version
, size_32
, true, &forms
)) {
922 // We failed to read the pickle using the native architecture of the system.
923 // Try again with the opposite architecture. Note that we do this even on
924 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
925 // since mostly we expect upgrades, not downgrades, but both are possible.)
926 success
= DeserializeValueSize(
927 signon_realm
, iter
, version
, !size_32
, false, &forms
);
929 UMALogDeserializationStatus(success
);
933 int NativeBackendKWallet::WalletHandle() {
934 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
937 // TODO(mdm): Are we leaking these handles? Find out.
938 int32_t handle
= kInvalidKWalletHandle
;
940 dbus::MethodCall
method_call(kKWalletInterface
, "open");
941 dbus::MessageWriter
builder(&method_call
);
942 builder
.AppendString(wallet_name_
); // wallet
943 builder
.AppendInt64(0); // wid
944 builder
.AppendString(app_name_
); // appid
945 scoped_ptr
<dbus::Response
> response(
946 kwallet_proxy_
->CallMethodAndBlock(
947 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
948 if (!response
.get()) {
949 LOG(ERROR
) << "Error contacting kwalletd (open)";
950 return kInvalidKWalletHandle
;
952 dbus::MessageReader
reader(response
.get());
953 if (!reader
.PopInt32(&handle
)) {
954 LOG(ERROR
) << "Error reading response from kwalletd (open): "
955 << response
->ToString();
956 return kInvalidKWalletHandle
;
958 if (handle
== kInvalidKWalletHandle
) {
959 LOG(ERROR
) << "Error obtaining KWallet handle";
960 return kInvalidKWalletHandle
;
964 // Check if our folder exists.
965 bool has_folder
= false;
967 dbus::MethodCall
method_call(kKWalletInterface
, "hasFolder");
968 dbus::MessageWriter
builder(&method_call
);
969 builder
.AppendInt32(handle
); // handle
970 builder
.AppendString(folder_name_
); // folder
971 builder
.AppendString(app_name_
); // appid
972 scoped_ptr
<dbus::Response
> response(
973 kwallet_proxy_
->CallMethodAndBlock(
974 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
975 if (!response
.get()) {
976 LOG(ERROR
) << "Error contacting kwalletd (hasFolder)";
977 return kInvalidKWalletHandle
;
979 dbus::MessageReader
reader(response
.get());
980 if (!reader
.PopBool(&has_folder
)) {
981 LOG(ERROR
) << "Error reading response from kwalletd (hasFolder): "
982 << response
->ToString();
983 return kInvalidKWalletHandle
;
987 // Create it if it didn't.
989 dbus::MethodCall
method_call(kKWalletInterface
, "createFolder");
990 dbus::MessageWriter
builder(&method_call
);
991 builder
.AppendInt32(handle
); // handle
992 builder
.AppendString(folder_name_
); // folder
993 builder
.AppendString(app_name_
); // appid
994 scoped_ptr
<dbus::Response
> response(
995 kwallet_proxy_
->CallMethodAndBlock(
996 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
997 if (!response
.get()) {
998 LOG(ERROR
) << "Error contacting kwalletd (createFolder)";
999 return kInvalidKWalletHandle
;
1001 dbus::MessageReader
reader(response
.get());
1002 bool success
= false;
1003 if (!reader
.PopBool(&success
)) {
1004 LOG(ERROR
) << "Error reading response from kwalletd (createFolder): "
1005 << response
->ToString();
1006 return kInvalidKWalletHandle
;
1009 LOG(ERROR
) << "Error creating KWallet folder";
1010 return kInvalidKWalletHandle
;
1017 std::string
NativeBackendKWallet::GetProfileSpecificFolderName() const {
1018 // Originally, the folder name was always just "Chrome Form Data".
1019 // Now we use it to distinguish passwords for different profiles.
1020 return base::StringPrintf("%s (%d)", kKWalletFolder
, profile_id_
);