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 "components/password_manager/core/browser/password_manager_util.h"
20 #include "content/public/browser/browser_thread.h"
22 #include "dbus/message.h"
23 #include "dbus/object_path.h"
24 #include "dbus/object_proxy.h"
25 #include "ui/base/l10n/l10n_util.h"
27 using autofill::PasswordForm
;
28 using content::BrowserThread
;
32 // In case the fields in the pickle ever change, version them so we can try to
33 // read old pickles. (Note: do not eat old pickles past the expiration date.)
34 const int kPickleVersion
= 7;
36 // We could localize this string, but then changing your locale would cause
37 // you to lose access to all your stored passwords. Maybe best not to do that.
38 // Name of the folder to store passwords in.
39 const char kKWalletFolder
[] = "Chrome Form Data";
41 // DBus service, path, and interface names for klauncher and kwalletd.
42 const char kKWalletServiceName
[] = "org.kde.kwalletd";
43 const char kKWalletPath
[] = "/modules/kwalletd";
44 const char kKWalletInterface
[] = "org.kde.KWallet";
45 const char kKLauncherServiceName
[] = "org.kde.klauncher";
46 const char kKLauncherPath
[] = "/KLauncher";
47 const char kKLauncherInterface
[] = "org.kde.KLauncher";
49 // Checks a serialized list of PasswordForms for sanity. Returns true if OK.
50 // Note that |realm| is only used for generating a useful warning message.
51 bool CheckSerializedValue(const uint8_t* byte_array
,
53 const std::string
& realm
) {
54 const base::Pickle::Header
* header
=
55 reinterpret_cast<const base::Pickle::Header
*>(byte_array
);
56 if (length
< sizeof(*header
) ||
57 header
->payload_size
> length
- sizeof(*header
)) {
58 LOG(WARNING
) << "Invalid KWallet entry detected (realm: " << realm
<< ")";
64 // Convenience function to read a GURL from a Pickle. Assumes the URL has
65 // been written as a UTF-8 string. Returns true on success.
66 bool ReadGURL(base::PickleIterator
* iter
, bool warn_only
, GURL
* url
) {
67 std::string url_string
;
68 if (!iter
->ReadString(&url_string
)) {
70 LOG(ERROR
) << "Failed to deserialize URL.";
74 *url
= GURL(url_string
);
78 void LogDeserializationWarning(int version
,
79 std::string signon_realm
,
82 LOG(WARNING
) << "Failed to deserialize version " << version
83 << " KWallet entry (realm: " << signon_realm
84 << ") with native architecture size; will try alternate "
87 LOG(ERROR
) << "Failed to deserialize version " << version
88 << " KWallet entry (realm: " << signon_realm
<< ")";
92 // Deserializes a list of credentials from the wallet to |forms| (replacing
93 // the contents of |forms|). |size_32| controls reading the size field within
94 // the pickle as 32 bits. We used to use Pickle::WriteSize() to write the number
95 // of password forms, but that has a different size on 32- and 64-bit systems.
96 // So, now we always write a 64-bit quantity, but we support trying to read it
97 // as either size when reading old pickles that fail to deserialize using the
98 // native size. Returns true on success.
99 bool DeserializeValueSize(const std::string
& signon_realm
,
100 const base::PickleIterator
& init_iter
,
104 ScopedVector
<autofill::PasswordForm
>* forms
) {
105 base::PickleIterator iter
= init_iter
;
109 uint32_t count_32
= 0;
110 if (!iter
.ReadUInt32(&count_32
)) {
111 LOG(ERROR
) << "Failed to deserialize KWallet entry "
112 << "(realm: " << signon_realm
<< ")";
117 if (!iter
.ReadSizeT(&count
)) {
118 LOG(ERROR
) << "Failed to deserialize KWallet entry "
119 << "(realm: " << signon_realm
<< ")";
124 if (count
> 0xFFFF) {
125 // Trying to pin down the cause of http://crbug.com/80728 (or fix it).
126 // This is a very large number of passwords to be saved for a single realm.
127 // It is almost certainly a corrupt pickle and not real data. Ignore it.
128 // This very well might actually be http://crbug.com/107701, so if we're
129 // reading an old pickle, we don't even log this the first time we try to
130 // read it. (That is, when we're reading the native architecture size.)
132 LOG(ERROR
) << "Suspiciously large number of entries in KWallet entry "
133 << "(" << count
<< "; realm: " << signon_realm
<< ")";
138 // We'll swap |converted_forms| with |*forms| on success, to make sure we
139 // don't return partial results on failure.
140 ScopedVector
<autofill::PasswordForm
> converted_forms
;
141 converted_forms
.reserve(count
);
142 for (size_t i
= 0; i
< count
; ++i
) {
143 scoped_ptr
<PasswordForm
> form(new PasswordForm());
144 form
->signon_realm
.assign(signon_realm
);
147 int64 date_created
= 0;
149 int generation_upload_status
= 0;
150 // Note that these will be read back in the order listed due to
151 // short-circuit evaluation. This is important.
152 if (!iter
.ReadInt(&scheme
) ||
153 !ReadGURL(&iter
, warn_only
, &form
->origin
) ||
154 !ReadGURL(&iter
, warn_only
, &form
->action
) ||
155 !iter
.ReadString16(&form
->username_element
) ||
156 !iter
.ReadString16(&form
->username_value
) ||
157 !iter
.ReadString16(&form
->password_element
) ||
158 !iter
.ReadString16(&form
->password_value
) ||
159 !iter
.ReadString16(&form
->submit_element
) ||
160 !iter
.ReadBool(&form
->ssl_valid
) ||
161 !iter
.ReadBool(&form
->preferred
) ||
162 !iter
.ReadBool(&form
->blacklisted_by_user
) ||
163 !iter
.ReadInt64(&date_created
)) {
164 LogDeserializationWarning(version
, signon_realm
, warn_only
);
167 form
->scheme
= static_cast<PasswordForm::Scheme
>(scheme
);
170 if (!iter
.ReadInt(&type
) ||
171 !iter
.ReadInt(&form
->times_used
) ||
172 !autofill::DeserializeFormData(&iter
, &form
->form_data
)) {
173 LogDeserializationWarning(version
, signon_realm
, false);
176 form
->type
= static_cast<PasswordForm::Type
>(type
);
180 int64 date_synced
= 0;
181 if (!iter
.ReadInt64(&date_synced
)) {
182 LogDeserializationWarning(version
, signon_realm
, false);
185 form
->date_synced
= base::Time::FromInternalValue(date_synced
);
189 if (!iter
.ReadString16(&form
->display_name
) ||
190 !ReadGURL(&iter
, warn_only
, &form
->icon_url
) ||
191 !ReadGURL(&iter
, warn_only
, &form
->federation_url
) ||
192 !iter
.ReadBool(&form
->skip_zero_click
)) {
193 LogDeserializationWarning(version
, signon_realm
, false);
199 form
->date_created
= base::Time::FromInternalValue(date_created
);
201 form
->date_created
= base::Time::FromTimeT(date_created
);
205 bool read_success
= iter
.ReadInt(&generation_upload_status
);
206 if (!read_success
&& version
> 6) {
207 // Valid version 6 pickles might still lack the
208 // generation_upload_status, see http://crbug.com/494229#c11.
209 LogDeserializationWarning(version
, signon_realm
, false);
213 form
->generation_upload_status
=
214 static_cast<PasswordForm::GenerationUploadStatus
>(
215 generation_upload_status
);
219 converted_forms
.push_back(form
.Pass());
222 forms
->swap(converted_forms
);
226 // Serializes a list of PasswordForms to be stored in the wallet.
227 void SerializeValue(const std::vector
<autofill::PasswordForm
*>& forms
,
228 base::Pickle
* pickle
) {
229 pickle
->WriteInt(kPickleVersion
);
230 pickle
->WriteSizeT(forms
.size());
231 for (autofill::PasswordForm
* form
: forms
) {
232 pickle
->WriteInt(form
->scheme
);
233 pickle
->WriteString(form
->origin
.spec());
234 pickle
->WriteString(form
->action
.spec());
235 pickle
->WriteString16(form
->username_element
);
236 pickle
->WriteString16(form
->username_value
);
237 pickle
->WriteString16(form
->password_element
);
238 pickle
->WriteString16(form
->password_value
);
239 pickle
->WriteString16(form
->submit_element
);
240 pickle
->WriteBool(form
->ssl_valid
);
241 pickle
->WriteBool(form
->preferred
);
242 pickle
->WriteBool(form
->blacklisted_by_user
);
243 pickle
->WriteInt64(form
->date_created
.ToInternalValue());
244 pickle
->WriteInt(form
->type
);
245 pickle
->WriteInt(form
->times_used
);
246 autofill::SerializeFormData(form
->form_data
, pickle
);
247 pickle
->WriteInt64(form
->date_synced
.ToInternalValue());
248 pickle
->WriteString16(form
->display_name
);
249 pickle
->WriteString(form
->icon_url
.spec());
250 pickle
->WriteString(form
->federation_url
.spec());
251 pickle
->WriteBool(form
->skip_zero_click
);
252 pickle
->WriteInt(form
->generation_upload_status
);
256 // Moves the content of |second| to the end of |first|.
257 void AppendSecondToFirst(ScopedVector
<autofill::PasswordForm
>* first
,
258 ScopedVector
<autofill::PasswordForm
> second
) {
259 first
->reserve(first
->size() + second
.size());
260 first
->insert(first
->end(), second
.begin(), second
.end());
264 void UMALogDeserializationStatus(bool success
) {
265 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
271 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id
)
273 kwallet_proxy_(nullptr),
274 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME
)) {
275 folder_name_
= GetProfileSpecificFolderName();
278 NativeBackendKWallet::~NativeBackendKWallet() {
279 // This destructor is called on the thread that is destroying the Profile
280 // containing the PasswordStore that owns this NativeBackend. Generally that
281 // won't be the DB thread; it will be the UI thread. So we post a message to
282 // shut it down on the DB thread, and it will be destructed afterward when the
283 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
284 // destroyed before that occurs, but that's OK.
285 if (session_bus_
.get()) {
286 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
287 base::Bind(&dbus::Bus::ShutdownAndBlock
,
288 session_bus_
.get()));
292 bool NativeBackendKWallet::Init() {
293 // Without the |optional_bus| parameter, a real bus will be instantiated.
294 return InitWithBus(scoped_refptr
<dbus::Bus
>());
297 bool NativeBackendKWallet::InitWithBus(scoped_refptr
<dbus::Bus
> optional_bus
) {
298 // We must synchronously do a few DBus calls to figure out if initialization
299 // succeeds, but later, we'll want to do most work on the DB thread. So we
300 // have to do the initialization on the DB thread here too, and wait for it.
301 bool success
= false;
302 base::WaitableEvent
event(false, false);
303 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
304 // to finish, so we can safely use base::Unretained here.
305 BrowserThread::PostTask(BrowserThread::DB
, FROM_HERE
,
306 base::Bind(&NativeBackendKWallet::InitOnDBThread
,
307 base::Unretained(this),
308 optional_bus
, &event
, &success
));
310 // This ScopedAllowWait should not be here. http://crbug.com/125331
311 base::ThreadRestrictions::ScopedAllowWait allow_wait
;
316 void NativeBackendKWallet::InitOnDBThread(scoped_refptr
<dbus::Bus
> optional_bus
,
317 base::WaitableEvent
* event
,
319 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
320 DCHECK(!session_bus_
.get());
321 if (optional_bus
.get()) {
322 // The optional_bus parameter is given when this method is called in tests.
323 session_bus_
= optional_bus
;
325 // Get a (real) connection to the session bus.
326 dbus::Bus::Options options
;
327 options
.bus_type
= dbus::Bus::SESSION
;
328 options
.connection_type
= dbus::Bus::PRIVATE
;
329 session_bus_
= new dbus::Bus(options
);
332 session_bus_
->GetObjectProxy(kKWalletServiceName
,
333 dbus::ObjectPath(kKWalletPath
));
334 // kwalletd may not be running. If we get a temporary failure initializing it,
335 // try to start it and then try again. (Note the short-circuit evaluation.)
336 const InitResult result
= InitWallet();
337 *success
= (result
== INIT_SUCCESS
||
338 (result
== TEMPORARY_FAIL
&&
339 StartKWalletd() && InitWallet() == INIT_SUCCESS
));
343 bool NativeBackendKWallet::StartKWalletd() {
344 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
345 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
346 // klauncher to start it.
347 dbus::ObjectProxy
* klauncher
=
348 session_bus_
->GetObjectProxy(kKLauncherServiceName
,
349 dbus::ObjectPath(kKLauncherPath
));
351 dbus::MethodCall
method_call(kKLauncherInterface
,
352 "start_service_by_desktop_name");
353 dbus::MessageWriter
builder(&method_call
);
354 std::vector
<std::string
> empty
;
355 builder
.AppendString("kwalletd"); // serviceName
356 builder
.AppendArrayOfStrings(empty
); // urls
357 builder
.AppendArrayOfStrings(empty
); // envs
358 builder
.AppendString(std::string()); // startup_id
359 builder
.AppendBool(false); // blind
360 scoped_ptr
<dbus::Response
> response(
361 klauncher
->CallMethodAndBlock(
362 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
363 if (!response
.get()) {
364 LOG(ERROR
) << "Error contacting klauncher to start kwalletd";
367 dbus::MessageReader
reader(response
.get());
369 std::string dbus_name
;
372 if (!reader
.PopInt32(&ret
) || !reader
.PopString(&dbus_name
) ||
373 !reader
.PopString(&error
) || !reader
.PopInt32(&pid
)) {
374 LOG(ERROR
) << "Error reading response from klauncher to start kwalletd: "
375 << response
->ToString();
378 if (!error
.empty() || ret
) {
379 LOG(ERROR
) << "Error launching kwalletd: error '" << error
<< "' "
380 << " (code " << ret
<< ")";
387 NativeBackendKWallet::InitResult
NativeBackendKWallet::InitWallet() {
388 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
390 // Check that KWallet is enabled.
391 dbus::MethodCall
method_call(kKWalletInterface
, "isEnabled");
392 scoped_ptr
<dbus::Response
> response(
393 kwallet_proxy_
->CallMethodAndBlock(
394 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
395 if (!response
.get()) {
396 LOG(ERROR
) << "Error contacting kwalletd (isEnabled)";
397 return TEMPORARY_FAIL
;
399 dbus::MessageReader
reader(response
.get());
400 bool enabled
= false;
401 if (!reader
.PopBool(&enabled
)) {
402 LOG(ERROR
) << "Error reading response from kwalletd (isEnabled): "
403 << response
->ToString();
404 return PERMANENT_FAIL
;
406 // Not enabled? Don't use KWallet. But also don't warn here.
408 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
409 return PERMANENT_FAIL
;
414 // Get the wallet name.
415 dbus::MethodCall
method_call(kKWalletInterface
, "networkWallet");
416 scoped_ptr
<dbus::Response
> response(
417 kwallet_proxy_
->CallMethodAndBlock(
418 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
419 if (!response
.get()) {
420 LOG(ERROR
) << "Error contacting kwalletd (networkWallet)";
421 return TEMPORARY_FAIL
;
423 dbus::MessageReader
reader(response
.get());
424 if (!reader
.PopString(&wallet_name_
)) {
425 LOG(ERROR
) << "Error reading response from kwalletd (networkWallet): "
426 << response
->ToString();
427 return PERMANENT_FAIL
;
434 password_manager::PasswordStoreChangeList
NativeBackendKWallet::AddLogin(
435 const PasswordForm
& form
) {
436 int wallet_handle
= WalletHandle();
437 if (wallet_handle
== kInvalidKWalletHandle
)
438 return password_manager::PasswordStoreChangeList();
440 ScopedVector
<autofill::PasswordForm
> forms
;
441 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
442 return password_manager::PasswordStoreChangeList();
444 auto it
= std::partition(forms
.begin(), forms
.end(),
445 [&form
](const PasswordForm
* current_form
) {
446 return !ArePasswordFormUniqueKeyEqual(form
, *current_form
);
448 password_manager::PasswordStoreChangeList changes
;
449 if (it
!= forms
.end()) {
451 changes
.push_back(password_manager::PasswordStoreChange(
452 password_manager::PasswordStoreChange::REMOVE
, **it
));
453 forms
.erase(it
, forms
.end());
456 forms
.push_back(new PasswordForm(form
));
457 changes
.push_back(password_manager::PasswordStoreChange(
458 password_manager::PasswordStoreChange::ADD
, form
));
460 bool ok
= SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
);
467 bool NativeBackendKWallet::UpdateLogin(
468 const PasswordForm
& form
,
469 password_manager::PasswordStoreChangeList
* changes
) {
471 int wallet_handle
= WalletHandle();
472 if (wallet_handle
== kInvalidKWalletHandle
)
475 ScopedVector
<autofill::PasswordForm
> forms
;
476 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &forms
))
479 auto it
= std::partition(forms
.begin(), forms
.end(),
480 [&form
](const PasswordForm
* current_form
) {
481 return !ArePasswordFormUniqueKeyEqual(form
, *current_form
);
484 if (it
== forms
.end())
487 forms
.erase(it
, forms
.end());
488 forms
.push_back(new PasswordForm(form
));
489 if (SetLoginsList(forms
.get(), form
.signon_realm
, wallet_handle
)) {
490 changes
->push_back(password_manager::PasswordStoreChange(
491 password_manager::PasswordStoreChange::UPDATE
, form
));
498 bool NativeBackendKWallet::RemoveLogin(
499 const PasswordForm
& form
,
500 password_manager::PasswordStoreChangeList
* changes
) {
502 int wallet_handle
= WalletHandle();
503 if (wallet_handle
== kInvalidKWalletHandle
)
506 ScopedVector
<autofill::PasswordForm
> all_forms
;
507 if (!GetLoginsList(form
.signon_realm
, wallet_handle
, &all_forms
))
510 ScopedVector
<autofill::PasswordForm
> kept_forms
;
511 kept_forms
.reserve(all_forms
.size());
512 for (auto& saved_form
: all_forms
) {
513 if (!ArePasswordFormUniqueKeyEqual(form
, *saved_form
)) {
514 kept_forms
.push_back(saved_form
);
515 saved_form
= nullptr;
519 if (kept_forms
.size() != all_forms
.size()) {
520 changes
->push_back(password_manager::PasswordStoreChange(
521 password_manager::PasswordStoreChange::REMOVE
, form
));
522 return SetLoginsList(kept_forms
.get(), form
.signon_realm
, wallet_handle
);
528 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
529 base::Time delete_begin
,
530 base::Time delete_end
,
531 password_manager::PasswordStoreChangeList
* changes
) {
532 return RemoveLoginsBetween(
533 delete_begin
, delete_end
, CREATION_TIMESTAMP
, changes
);
536 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
537 base::Time delete_begin
,
538 base::Time delete_end
,
539 password_manager::PasswordStoreChangeList
* changes
) {
540 return RemoveLoginsBetween(delete_begin
, delete_end
, SYNC_TIMESTAMP
, changes
);
543 bool NativeBackendKWallet::GetLogins(
544 const PasswordForm
& form
,
545 ScopedVector
<autofill::PasswordForm
>* forms
) {
546 int wallet_handle
= WalletHandle();
547 if (wallet_handle
== kInvalidKWalletHandle
)
549 return GetLoginsList(form
.signon_realm
, wallet_handle
, forms
);
552 bool NativeBackendKWallet::GetAutofillableLogins(
553 ScopedVector
<autofill::PasswordForm
>* forms
) {
554 int wallet_handle
= WalletHandle();
555 if (wallet_handle
== kInvalidKWalletHandle
)
557 return GetLoginsList(BlacklistOptions::AUTOFILLABLE
, wallet_handle
, forms
);
560 bool NativeBackendKWallet::GetBlacklistLogins(
561 ScopedVector
<autofill::PasswordForm
>* forms
) {
562 int wallet_handle
= WalletHandle();
563 if (wallet_handle
== kInvalidKWalletHandle
)
565 return GetLoginsList(BlacklistOptions::BLACKLISTED
, wallet_handle
, forms
);
568 bool NativeBackendKWallet::GetLoginsList(
569 const std::string
& signon_realm
,
571 ScopedVector
<autofill::PasswordForm
>* forms
) {
573 // Is there an entry in the wallet?
575 dbus::MethodCall
method_call(kKWalletInterface
, "hasEntry");
576 dbus::MessageWriter
builder(&method_call
);
577 builder
.AppendInt32(wallet_handle
); // handle
578 builder
.AppendString(folder_name_
); // folder
579 builder
.AppendString(signon_realm
); // key
580 builder
.AppendString(app_name_
); // appid
581 scoped_ptr
<dbus::Response
> response(
582 kwallet_proxy_
->CallMethodAndBlock(
583 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
584 if (!response
.get()) {
585 LOG(ERROR
) << "Error contacting kwalletd (hasEntry)";
588 dbus::MessageReader
reader(response
.get());
589 bool has_entry
= false;
590 if (!reader
.PopBool(&has_entry
)) {
591 LOG(ERROR
) << "Error reading response from kwalletd (hasEntry): "
592 << response
->ToString();
596 // This is not an error. There just isn't a matching entry.
602 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
603 dbus::MessageWriter
builder(&method_call
);
604 builder
.AppendInt32(wallet_handle
); // handle
605 builder
.AppendString(folder_name_
); // folder
606 builder
.AppendString(signon_realm
); // key
607 builder
.AppendString(app_name_
); // appid
608 scoped_ptr
<dbus::Response
> response(
609 kwallet_proxy_
->CallMethodAndBlock(
610 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
611 if (!response
.get()) {
612 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
615 dbus::MessageReader
reader(response
.get());
616 const uint8_t* bytes
= nullptr;
618 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
619 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
620 << response
->ToString();
625 if (!CheckSerializedValue(bytes
, length
, signon_realm
)) {
626 // This is weird, but we choose not to call it an error. There is an
627 // invalid entry somehow, but by just ignoring it, we make it easier to
628 // repair without having to delete it using kwalletmanager (that is, by
629 // just saving a new password within this realm to overwrite it).
633 // Can't we all just agree on whether bytes are signed or not? Please?
634 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
635 *forms
= DeserializeValue(signon_realm
, pickle
);
641 bool NativeBackendKWallet::GetLoginsList(
642 BlacklistOptions options
,
644 ScopedVector
<autofill::PasswordForm
>* forms
) {
646 ScopedVector
<autofill::PasswordForm
> all_forms
;
647 if (!GetAllLogins(wallet_handle
, &all_forms
))
650 // Remove the duplicate sync tags.
651 ScopedVector
<autofill::PasswordForm
> duplicates
;
652 password_manager_util::FindDuplicates(&all_forms
, &duplicates
, nullptr);
653 if (!duplicates
.empty()) {
654 // Fill the signon realms to be updated.
655 std::map
<std::string
, std::vector
<autofill::PasswordForm
*>> update_forms
;
656 for (autofill::PasswordForm
* form
: duplicates
) {
657 update_forms
.insert(std::make_pair(
658 form
->signon_realm
, std::vector
<autofill::PasswordForm
*>()));
661 // Fill the actual forms to be saved.
662 for (autofill::PasswordForm
* form
: all_forms
) {
663 auto it
= update_forms
.find(form
->signon_realm
);
664 if (it
!= update_forms
.end())
665 it
->second
.push_back(form
);
668 // Update the backend.
669 for (const auto& forms
: update_forms
) {
670 if (!SetLoginsList(forms
.second
, forms
.first
, wallet_handle
))
674 // We have to read all the entries, and then filter them here.
675 forms
->reserve(all_forms
.size());
676 for (auto& saved_form
: all_forms
) {
677 if (saved_form
->blacklisted_by_user
==
678 (options
== BlacklistOptions::BLACKLISTED
)) {
679 forms
->push_back(saved_form
);
680 saved_form
= nullptr;
687 bool NativeBackendKWallet::GetAllLogins(
689 ScopedVector
<autofill::PasswordForm
>* forms
) {
690 // We could probably also use readEntryList here.
691 std::vector
<std::string
> realm_list
;
693 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
694 dbus::MessageWriter
builder(&method_call
);
695 builder
.AppendInt32(wallet_handle
); // handle
696 builder
.AppendString(folder_name_
); // folder
697 builder
.AppendString(app_name_
); // appid
698 scoped_ptr
<dbus::Response
> response(
699 kwallet_proxy_
->CallMethodAndBlock(
700 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
701 if (!response
.get()) {
702 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
705 dbus::MessageReader
reader(response
.get());
706 if (!reader
.PopArrayOfStrings(&realm_list
)) {
707 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
708 << response
->ToString();
714 for (const std::string
& signon_realm
: realm_list
) {
715 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
716 dbus::MessageWriter
builder(&method_call
);
717 builder
.AppendInt32(wallet_handle
); // handle
718 builder
.AppendString(folder_name_
); // folder
719 builder
.AppendString(signon_realm
); // key
720 builder
.AppendString(app_name_
); // appid
721 scoped_ptr
<dbus::Response
> response(
722 kwallet_proxy_
->CallMethodAndBlock(
723 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
724 if (!response
.get()) {
725 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
728 dbus::MessageReader
reader(response
.get());
729 const uint8_t* bytes
= nullptr;
731 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
732 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
733 << response
->ToString();
736 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
739 // Can't we all just agree on whether bytes are signed or not? Please?
740 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
741 AppendSecondToFirst(forms
, DeserializeValue(signon_realm
, pickle
));
746 bool NativeBackendKWallet::SetLoginsList(
747 const std::vector
<autofill::PasswordForm
*>& forms
,
748 const std::string
& signon_realm
,
751 // No items left? Remove the entry from the wallet.
752 dbus::MethodCall
method_call(kKWalletInterface
, "removeEntry");
753 dbus::MessageWriter
builder(&method_call
);
754 builder
.AppendInt32(wallet_handle
); // handle
755 builder
.AppendString(folder_name_
); // folder
756 builder
.AppendString(signon_realm
); // key
757 builder
.AppendString(app_name_
); // appid
758 scoped_ptr
<dbus::Response
> response(
759 kwallet_proxy_
->CallMethodAndBlock(
760 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
761 if (!response
.get()) {
762 LOG(ERROR
) << "Error contacting kwalletd (removeEntry)";
763 return kInvalidKWalletHandle
;
765 dbus::MessageReader
reader(response
.get());
767 if (!reader
.PopInt32(&ret
)) {
768 LOG(ERROR
) << "Error reading response from kwalletd (removeEntry): "
769 << response
->ToString();
773 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet removeEntry";
778 SerializeValue(forms
, &value
);
780 dbus::MethodCall
method_call(kKWalletInterface
, "writeEntry");
781 dbus::MessageWriter
builder(&method_call
);
782 builder
.AppendInt32(wallet_handle
); // handle
783 builder
.AppendString(folder_name_
); // folder
784 builder
.AppendString(signon_realm
); // key
785 builder
.AppendArrayOfBytes(static_cast<const uint8_t*>(value
.data()),
786 value
.size()); // value
787 builder
.AppendString(app_name_
); // appid
788 scoped_ptr
<dbus::Response
> response(
789 kwallet_proxy_
->CallMethodAndBlock(
790 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
791 if (!response
.get()) {
792 LOG(ERROR
) << "Error contacting kwalletd (writeEntry)";
793 return kInvalidKWalletHandle
;
795 dbus::MessageReader
reader(response
.get());
797 if (!reader
.PopInt32(&ret
)) {
798 LOG(ERROR
) << "Error reading response from kwalletd (writeEntry): "
799 << response
->ToString();
803 LOG(ERROR
) << "Bad return code " << ret
<< " from KWallet writeEntry";
807 bool NativeBackendKWallet::RemoveLoginsBetween(
808 base::Time delete_begin
,
809 base::Time delete_end
,
810 TimestampToCompare date_to_compare
,
811 password_manager::PasswordStoreChangeList
* changes
) {
814 int wallet_handle
= WalletHandle();
815 if (wallet_handle
== kInvalidKWalletHandle
)
818 // We could probably also use readEntryList here.
819 std::vector
<std::string
> realm_list
;
821 dbus::MethodCall
method_call(kKWalletInterface
, "entryList");
822 dbus::MessageWriter
builder(&method_call
);
823 builder
.AppendInt32(wallet_handle
); // handle
824 builder
.AppendString(folder_name_
); // folder
825 builder
.AppendString(app_name_
); // appid
826 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
827 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
828 if (!response
.get()) {
829 LOG(ERROR
) << "Error contacting kwalletd (entryList)";
832 dbus::MessageReader
reader(response
.get());
833 dbus::MessageReader
array(response
.get());
834 if (!reader
.PopArray(&array
)) {
835 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
836 << response
->ToString();
839 while (array
.HasMoreData()) {
841 if (!array
.PopString(&realm
)) {
842 LOG(ERROR
) << "Error reading response from kwalletd (entryList): "
843 << response
->ToString();
846 realm_list
.push_back(realm
);
851 for (size_t i
= 0; i
< realm_list
.size(); ++i
) {
852 const std::string
& signon_realm
= realm_list
[i
];
853 dbus::MethodCall
method_call(kKWalletInterface
, "readEntry");
854 dbus::MessageWriter
builder(&method_call
);
855 builder
.AppendInt32(wallet_handle
); // handle
856 builder
.AppendString(folder_name_
); // folder
857 builder
.AppendString(signon_realm
); // key
858 builder
.AppendString(app_name_
); // appid
859 scoped_ptr
<dbus::Response
> response(kwallet_proxy_
->CallMethodAndBlock(
860 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
861 if (!response
.get()) {
862 LOG(ERROR
) << "Error contacting kwalletd (readEntry)";
865 dbus::MessageReader
reader(response
.get());
866 const uint8_t* bytes
= nullptr;
868 if (!reader
.PopArrayOfBytes(&bytes
, &length
)) {
869 LOG(ERROR
) << "Error reading response from kwalletd (readEntry): "
870 << response
->ToString();
873 if (!bytes
|| !CheckSerializedValue(bytes
, length
, signon_realm
))
876 // Can't we all just agree on whether bytes are signed or not? Please?
877 base::Pickle
pickle(reinterpret_cast<const char*>(bytes
), length
);
878 ScopedVector
<autofill::PasswordForm
> all_forms
=
879 DeserializeValue(signon_realm
, pickle
);
881 ScopedVector
<autofill::PasswordForm
> kept_forms
;
882 kept_forms
.reserve(all_forms
.size());
883 base::Time
autofill::PasswordForm::*date_member
=
884 date_to_compare
== CREATION_TIMESTAMP
885 ? &autofill::PasswordForm::date_created
886 : &autofill::PasswordForm::date_synced
;
887 for (auto& saved_form
: all_forms
) {
888 if (delete_begin
<= saved_form
->*date_member
&&
889 (delete_end
.is_null() || saved_form
->*date_member
< delete_end
)) {
890 changes
->push_back(password_manager::PasswordStoreChange(
891 password_manager::PasswordStoreChange::REMOVE
, *saved_form
));
893 kept_forms
.push_back(saved_form
);
894 saved_form
= nullptr;
898 if (!SetLoginsList(kept_forms
.get(), signon_realm
, wallet_handle
)) {
907 ScopedVector
<autofill::PasswordForm
> NativeBackendKWallet::DeserializeValue(
908 const std::string
& signon_realm
,
909 const base::Pickle
& pickle
) {
910 base::PickleIterator
iter(pickle
);
913 if (!iter
.ReadInt(&version
) ||
914 version
< 0 || version
> kPickleVersion
) {
915 LOG(ERROR
) << "Failed to deserialize KWallet entry "
916 << "(realm: " << signon_realm
<< ")";
917 return ScopedVector
<autofill::PasswordForm
>();
920 ScopedVector
<autofill::PasswordForm
> forms
;
923 // In current pickles, we expect 64-bit sizes. Failure is an error.
924 success
= DeserializeValueSize(
925 signon_realm
, iter
, version
, false, false, &forms
);
926 UMALogDeserializationStatus(success
);
930 const bool size_32
= sizeof(size_t) == sizeof(uint32_t);
931 if (!DeserializeValueSize(
932 signon_realm
, iter
, version
, size_32
, true, &forms
)) {
933 // We failed to read the pickle using the native architecture of the system.
934 // Try again with the opposite architecture. Note that we do this even on
935 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
936 // since mostly we expect upgrades, not downgrades, but both are possible.)
937 success
= DeserializeValueSize(
938 signon_realm
, iter
, version
, !size_32
, false, &forms
);
940 UMALogDeserializationStatus(success
);
944 int NativeBackendKWallet::WalletHandle() {
945 DCHECK_CURRENTLY_ON(BrowserThread::DB
);
948 // TODO(mdm): Are we leaking these handles? Find out.
949 int32_t handle
= kInvalidKWalletHandle
;
951 dbus::MethodCall
method_call(kKWalletInterface
, "open");
952 dbus::MessageWriter
builder(&method_call
);
953 builder
.AppendString(wallet_name_
); // wallet
954 builder
.AppendInt64(0); // wid
955 builder
.AppendString(app_name_
); // appid
956 scoped_ptr
<dbus::Response
> response(
957 kwallet_proxy_
->CallMethodAndBlock(
958 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
959 if (!response
.get()) {
960 LOG(ERROR
) << "Error contacting kwalletd (open)";
961 return kInvalidKWalletHandle
;
963 dbus::MessageReader
reader(response
.get());
964 if (!reader
.PopInt32(&handle
)) {
965 LOG(ERROR
) << "Error reading response from kwalletd (open): "
966 << response
->ToString();
967 return kInvalidKWalletHandle
;
969 if (handle
== kInvalidKWalletHandle
) {
970 LOG(ERROR
) << "Error obtaining KWallet handle";
971 return kInvalidKWalletHandle
;
975 // Check if our folder exists.
976 bool has_folder
= false;
978 dbus::MethodCall
method_call(kKWalletInterface
, "hasFolder");
979 dbus::MessageWriter
builder(&method_call
);
980 builder
.AppendInt32(handle
); // handle
981 builder
.AppendString(folder_name_
); // folder
982 builder
.AppendString(app_name_
); // appid
983 scoped_ptr
<dbus::Response
> response(
984 kwallet_proxy_
->CallMethodAndBlock(
985 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
986 if (!response
.get()) {
987 LOG(ERROR
) << "Error contacting kwalletd (hasFolder)";
988 return kInvalidKWalletHandle
;
990 dbus::MessageReader
reader(response
.get());
991 if (!reader
.PopBool(&has_folder
)) {
992 LOG(ERROR
) << "Error reading response from kwalletd (hasFolder): "
993 << response
->ToString();
994 return kInvalidKWalletHandle
;
998 // Create it if it didn't.
1000 dbus::MethodCall
method_call(kKWalletInterface
, "createFolder");
1001 dbus::MessageWriter
builder(&method_call
);
1002 builder
.AppendInt32(handle
); // handle
1003 builder
.AppendString(folder_name_
); // folder
1004 builder
.AppendString(app_name_
); // appid
1005 scoped_ptr
<dbus::Response
> response(
1006 kwallet_proxy_
->CallMethodAndBlock(
1007 &method_call
, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT
));
1008 if (!response
.get()) {
1009 LOG(ERROR
) << "Error contacting kwalletd (createFolder)";
1010 return kInvalidKWalletHandle
;
1012 dbus::MessageReader
reader(response
.get());
1013 bool success
= false;
1014 if (!reader
.PopBool(&success
)) {
1015 LOG(ERROR
) << "Error reading response from kwalletd (createFolder): "
1016 << response
->ToString();
1017 return kInvalidKWalletHandle
;
1020 LOG(ERROR
) << "Error creating KWallet folder";
1021 return kInvalidKWalletHandle
;
1028 std::string
NativeBackendKWallet::GetProfileSpecificFolderName() const {
1029 // Originally, the folder name was always just "Chrome Form Data".
1030 // Now we use it to distinguish passwords for different profiles.
1031 return base::StringPrintf("%s (%d)", kKWalletFolder
, profile_id_
);