Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_kwallet_x.cc
blobae506175bbff1bbaf5fdc99df108609e3e6712db
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"
7 #include <vector>
9 #include "base/bind.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"
20 #include "dbus/bus.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;
29 namespace {
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 = 6;
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,
54 bool update_check) {
55 // An update check doesn't care about the submit element.
56 if (!update_check && a.submit_element != b.submit_element)
57 return false;
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,
68 size_t length,
69 const std::string& realm) {
70 const Pickle::Header* header =
71 reinterpret_cast<const 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 << ")";
75 return false;
77 return true;
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(PickleIterator* iter, bool warn_only, GURL* url) {
83 std::string url_string;
84 if (!iter->ReadString(&url_string)) {
85 if (!warn_only)
86 LOG(ERROR) << "Failed to deserialize URL.";
87 *url = GURL();
88 return false;
90 *url = GURL(url_string);
91 return true;
94 void LogDeserializationWarning(int version,
95 std::string signon_realm,
96 bool warn_only) {
97 if (warn_only) {
98 LOG(WARNING) << "Failed to deserialize version " << version
99 << " KWallet entry (realm: " << signon_realm
100 << ") with native architecture size; will try alternate "
101 << "size.";
102 } else {
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 PickleIterator& init_iter,
117 int version,
118 bool size_32,
119 bool warn_only,
120 ScopedVector<autofill::PasswordForm>* forms) {
121 PickleIterator iter = init_iter;
123 size_t count = 0;
124 if (size_32) {
125 uint32_t count_32 = 0;
126 if (!iter.ReadUInt32(&count_32)) {
127 LOG(ERROR) << "Failed to deserialize KWallet entry "
128 << "(realm: " << signon_realm << ")";
129 return false;
131 count = count_32;
132 } else {
133 if (!iter.ReadSizeT(&count)) {
134 LOG(ERROR) << "Failed to deserialize KWallet entry "
135 << "(realm: " << signon_realm << ")";
136 return false;
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.)
147 if (!warn_only) {
148 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
149 << "(" << count << "; realm: " << signon_realm << ")";
151 return false;
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);
162 int scheme = 0;
163 int64 date_created = 0;
164 int type = 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);
181 return false;
183 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
185 if (version > 1) {
186 if (!iter.ReadInt(&type) ||
187 !iter.ReadInt(&form->times_used) ||
188 !autofill::DeserializeFormData(&iter, &form->form_data)) {
189 LogDeserializationWarning(version, signon_realm, false);
190 return false;
192 form->type = static_cast<PasswordForm::Type>(type);
195 if (version > 2) {
196 int64 date_synced = 0;
197 if (!iter.ReadInt64(&date_synced)) {
198 LogDeserializationWarning(version, signon_realm, false);
199 return false;
201 form->date_synced = base::Time::FromInternalValue(date_synced);
204 if (version > 3) {
205 if (!iter.ReadString16(&form->display_name) ||
206 !ReadGURL(&iter, warn_only, &form->avatar_url) ||
207 !ReadGURL(&iter, warn_only, &form->federation_url) ||
208 !iter.ReadBool(&form->skip_zero_click)) {
209 LogDeserializationWarning(version, signon_realm, false);
210 return false;
214 if (version > 4) {
215 form->date_created = base::Time::FromInternalValue(date_created);
216 } else {
217 form->date_created = base::Time::FromTimeT(date_created);
220 if (version > 5) {
221 if (!iter.ReadInt(&generation_upload_status)) {
222 LogDeserializationWarning(version, signon_realm, false);
224 form->generation_upload_status =
225 static_cast<PasswordForm::GenerationUploadStatus>(
226 generation_upload_status);
229 converted_forms.push_back(form.release());
232 forms->swap(converted_forms);
233 return true;
236 // Serializes a list of PasswordForms to be stored in the wallet.
237 void SerializeValue(const std::vector<autofill::PasswordForm*>& forms,
238 Pickle* pickle) {
239 pickle->WriteInt(kPickleVersion);
240 pickle->WriteSizeT(forms.size());
241 for (autofill::PasswordForm* form : forms) {
242 pickle->WriteInt(form->scheme);
243 pickle->WriteString(form->origin.spec());
244 pickle->WriteString(form->action.spec());
245 pickle->WriteString16(form->username_element);
246 pickle->WriteString16(form->username_value);
247 pickle->WriteString16(form->password_element);
248 pickle->WriteString16(form->password_value);
249 pickle->WriteString16(form->submit_element);
250 pickle->WriteBool(form->ssl_valid);
251 pickle->WriteBool(form->preferred);
252 pickle->WriteBool(form->blacklisted_by_user);
253 pickle->WriteInt64(form->date_created.ToInternalValue());
254 pickle->WriteInt(form->type);
255 pickle->WriteInt(form->times_used);
256 autofill::SerializeFormData(form->form_data, pickle);
257 pickle->WriteInt64(form->date_synced.ToInternalValue());
258 pickle->WriteString16(form->display_name);
259 pickle->WriteString(form->avatar_url.spec());
260 pickle->WriteString(form->federation_url.spec());
261 pickle->WriteBool(form->skip_zero_click);
265 // Moves the content of |second| to the end of |first|.
266 void AppendSecondToFirst(ScopedVector<autofill::PasswordForm>* first,
267 ScopedVector<autofill::PasswordForm> second) {
268 first->reserve(first->size() + second.size());
269 first->insert(first->end(), second.begin(), second.end());
270 second.weak_clear();
273 void UMALogDeserializationStatus(bool success) {
274 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
275 success);
278 } // namespace
280 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
281 : profile_id_(id),
282 kwallet_proxy_(nullptr),
283 app_name_(l10n_util::GetStringUTF8(IDS_PRODUCT_NAME)) {
284 folder_name_ = GetProfileSpecificFolderName();
287 NativeBackendKWallet::~NativeBackendKWallet() {
288 // This destructor is called on the thread that is destroying the Profile
289 // containing the PasswordStore that owns this NativeBackend. Generally that
290 // won't be the DB thread; it will be the UI thread. So we post a message to
291 // shut it down on the DB thread, and it will be destructed afterward when the
292 // scoped_refptr<dbus::Bus> goes out of scope. The NativeBackend will be
293 // destroyed before that occurs, but that's OK.
294 if (session_bus_.get()) {
295 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
296 base::Bind(&dbus::Bus::ShutdownAndBlock,
297 session_bus_.get()));
301 bool NativeBackendKWallet::Init() {
302 // Without the |optional_bus| parameter, a real bus will be instantiated.
303 return InitWithBus(scoped_refptr<dbus::Bus>());
306 bool NativeBackendKWallet::InitWithBus(scoped_refptr<dbus::Bus> optional_bus) {
307 // We must synchronously do a few DBus calls to figure out if initialization
308 // succeeds, but later, we'll want to do most work on the DB thread. So we
309 // have to do the initialization on the DB thread here too, and wait for it.
310 bool success = false;
311 base::WaitableEvent event(false, false);
312 // NativeBackendKWallet isn't reference counted, but we wait for InitWithBus
313 // to finish, so we can safely use base::Unretained here.
314 BrowserThread::PostTask(BrowserThread::DB, FROM_HERE,
315 base::Bind(&NativeBackendKWallet::InitOnDBThread,
316 base::Unretained(this),
317 optional_bus, &event, &success));
319 // This ScopedAllowWait should not be here. http://crbug.com/125331
320 base::ThreadRestrictions::ScopedAllowWait allow_wait;
321 event.Wait();
322 return success;
325 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
326 base::WaitableEvent* event,
327 bool* success) {
328 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
329 DCHECK(!session_bus_.get());
330 if (optional_bus.get()) {
331 // The optional_bus parameter is given when this method is called in tests.
332 session_bus_ = optional_bus;
333 } else {
334 // Get a (real) connection to the session bus.
335 dbus::Bus::Options options;
336 options.bus_type = dbus::Bus::SESSION;
337 options.connection_type = dbus::Bus::PRIVATE;
338 session_bus_ = new dbus::Bus(options);
340 kwallet_proxy_ =
341 session_bus_->GetObjectProxy(kKWalletServiceName,
342 dbus::ObjectPath(kKWalletPath));
343 // kwalletd may not be running. If we get a temporary failure initializing it,
344 // try to start it and then try again. (Note the short-circuit evaluation.)
345 const InitResult result = InitWallet();
346 *success = (result == INIT_SUCCESS ||
347 (result == TEMPORARY_FAIL &&
348 StartKWalletd() && InitWallet() == INIT_SUCCESS));
349 event->Signal();
352 bool NativeBackendKWallet::StartKWalletd() {
353 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
354 // Sadly kwalletd doesn't use DBus activation, so we have to make a call to
355 // klauncher to start it.
356 dbus::ObjectProxy* klauncher =
357 session_bus_->GetObjectProxy(kKLauncherServiceName,
358 dbus::ObjectPath(kKLauncherPath));
360 dbus::MethodCall method_call(kKLauncherInterface,
361 "start_service_by_desktop_name");
362 dbus::MessageWriter builder(&method_call);
363 std::vector<std::string> empty;
364 builder.AppendString("kwalletd"); // serviceName
365 builder.AppendArrayOfStrings(empty); // urls
366 builder.AppendArrayOfStrings(empty); // envs
367 builder.AppendString(std::string()); // startup_id
368 builder.AppendBool(false); // blind
369 scoped_ptr<dbus::Response> response(
370 klauncher->CallMethodAndBlock(
371 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
372 if (!response.get()) {
373 LOG(ERROR) << "Error contacting klauncher to start kwalletd";
374 return false;
376 dbus::MessageReader reader(response.get());
377 int32_t ret = -1;
378 std::string dbus_name;
379 std::string error;
380 int32_t pid = -1;
381 if (!reader.PopInt32(&ret) || !reader.PopString(&dbus_name) ||
382 !reader.PopString(&error) || !reader.PopInt32(&pid)) {
383 LOG(ERROR) << "Error reading response from klauncher to start kwalletd: "
384 << response->ToString();
385 return false;
387 if (!error.empty() || ret) {
388 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
389 << " (code " << ret << ")";
390 return false;
393 return true;
396 NativeBackendKWallet::InitResult NativeBackendKWallet::InitWallet() {
397 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
399 // Check that KWallet is enabled.
400 dbus::MethodCall method_call(kKWalletInterface, "isEnabled");
401 scoped_ptr<dbus::Response> response(
402 kwallet_proxy_->CallMethodAndBlock(
403 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
404 if (!response.get()) {
405 LOG(ERROR) << "Error contacting kwalletd (isEnabled)";
406 return TEMPORARY_FAIL;
408 dbus::MessageReader reader(response.get());
409 bool enabled = false;
410 if (!reader.PopBool(&enabled)) {
411 LOG(ERROR) << "Error reading response from kwalletd (isEnabled): "
412 << response->ToString();
413 return PERMANENT_FAIL;
415 // Not enabled? Don't use KWallet. But also don't warn here.
416 if (!enabled) {
417 VLOG(1) << "kwalletd reports that KWallet is not enabled.";
418 return PERMANENT_FAIL;
423 // Get the wallet name.
424 dbus::MethodCall method_call(kKWalletInterface, "networkWallet");
425 scoped_ptr<dbus::Response> response(
426 kwallet_proxy_->CallMethodAndBlock(
427 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
428 if (!response.get()) {
429 LOG(ERROR) << "Error contacting kwalletd (networkWallet)";
430 return TEMPORARY_FAIL;
432 dbus::MessageReader reader(response.get());
433 if (!reader.PopString(&wallet_name_)) {
434 LOG(ERROR) << "Error reading response from kwalletd (networkWallet): "
435 << response->ToString();
436 return PERMANENT_FAIL;
440 return INIT_SUCCESS;
443 password_manager::PasswordStoreChangeList NativeBackendKWallet::AddLogin(
444 const PasswordForm& form) {
445 int wallet_handle = WalletHandle();
446 if (wallet_handle == kInvalidKWalletHandle)
447 return password_manager::PasswordStoreChangeList();
449 ScopedVector<autofill::PasswordForm> forms;
450 if (!GetLoginsList(form.signon_realm, wallet_handle, &forms))
451 return password_manager::PasswordStoreChangeList();
453 // We search for a login to update, rather than unconditionally appending the
454 // login, because in some cases (especially involving sync) we can be asked to
455 // add a login that already exists. In these cases we want to just update.
456 bool updated = false;
457 password_manager::PasswordStoreChangeList changes;
458 for (size_t i = 0; i < forms.size(); ++i) {
459 // Use the more restrictive removal comparison, so that we never have
460 // duplicate logins that would all be removed together by RemoveLogin().
461 if (CompareForms(form, *forms[i], false)) {
462 changes.push_back(password_manager::PasswordStoreChange(
463 password_manager::PasswordStoreChange::REMOVE, *forms[i]));
464 *forms[i] = form;
465 updated = true;
468 if (!updated)
469 forms.push_back(new PasswordForm(form));
470 changes.push_back(password_manager::PasswordStoreChange(
471 password_manager::PasswordStoreChange::ADD, form));
473 bool ok = SetLoginsList(forms.get(), form.signon_realm, wallet_handle);
474 if (!ok)
475 changes.clear();
477 return changes;
480 bool NativeBackendKWallet::UpdateLogin(
481 const PasswordForm& form,
482 password_manager::PasswordStoreChangeList* changes) {
483 DCHECK(changes);
484 changes->clear();
485 int wallet_handle = WalletHandle();
486 if (wallet_handle == kInvalidKWalletHandle)
487 return false;
489 ScopedVector<autofill::PasswordForm> forms;
490 if (!GetLoginsList(form.signon_realm, wallet_handle, &forms))
491 return false;
493 bool updated = false;
494 for (size_t i = 0; i < forms.size(); ++i) {
495 if (CompareForms(form, *forms[i], true)) {
496 *forms[i] = form;
497 updated = true;
500 if (!updated)
501 return true;
503 if (SetLoginsList(forms.get(), form.signon_realm, wallet_handle)) {
504 changes->push_back(password_manager::PasswordStoreChange(
505 password_manager::PasswordStoreChange::UPDATE, form));
506 return true;
509 return false;
512 bool NativeBackendKWallet::RemoveLogin(const PasswordForm& form) {
513 int wallet_handle = WalletHandle();
514 if (wallet_handle == kInvalidKWalletHandle)
515 return false;
517 ScopedVector<autofill::PasswordForm> all_forms;
518 if (!GetLoginsList(form.signon_realm, wallet_handle, &all_forms))
519 return false;
521 ScopedVector<autofill::PasswordForm> kept_forms;
522 kept_forms.reserve(all_forms.size());
523 for (auto& saved_form : all_forms) {
524 if (!CompareForms(form, *saved_form, false)) {
525 kept_forms.push_back(saved_form);
526 saved_form = nullptr;
530 // Update the entry in the wallet, possibly deleting it.
531 return SetLoginsList(kept_forms.get(), form.signon_realm, wallet_handle);
534 bool NativeBackendKWallet::RemoveLoginsCreatedBetween(
535 base::Time delete_begin,
536 base::Time delete_end,
537 password_manager::PasswordStoreChangeList* changes) {
538 return RemoveLoginsBetween(
539 delete_begin, delete_end, CREATION_TIMESTAMP, changes);
542 bool NativeBackendKWallet::RemoveLoginsSyncedBetween(
543 base::Time delete_begin,
544 base::Time delete_end,
545 password_manager::PasswordStoreChangeList* changes) {
546 return RemoveLoginsBetween(delete_begin, delete_end, SYNC_TIMESTAMP, changes);
549 bool NativeBackendKWallet::GetLogins(
550 const PasswordForm& form,
551 ScopedVector<autofill::PasswordForm>* forms) {
552 int wallet_handle = WalletHandle();
553 if (wallet_handle == kInvalidKWalletHandle)
554 return false;
555 return GetLoginsList(form.signon_realm, wallet_handle, forms);
558 bool NativeBackendKWallet::GetAutofillableLogins(
559 ScopedVector<autofill::PasswordForm>* forms) {
560 int wallet_handle = WalletHandle();
561 if (wallet_handle == kInvalidKWalletHandle)
562 return false;
563 return GetLoginsList(BlacklistOptions::AUTOFILLABLE, wallet_handle, forms);
566 bool NativeBackendKWallet::GetBlacklistLogins(
567 ScopedVector<autofill::PasswordForm>* forms) {
568 int wallet_handle = WalletHandle();
569 if (wallet_handle == kInvalidKWalletHandle)
570 return false;
571 return GetLoginsList(BlacklistOptions::BLACKLISTED, wallet_handle, forms);
574 bool NativeBackendKWallet::GetLoginsList(
575 const std::string& signon_realm,
576 int wallet_handle,
577 ScopedVector<autofill::PasswordForm>* forms) {
578 forms->clear();
579 // Is there an entry in the wallet?
581 dbus::MethodCall method_call(kKWalletInterface, "hasEntry");
582 dbus::MessageWriter builder(&method_call);
583 builder.AppendInt32(wallet_handle); // handle
584 builder.AppendString(folder_name_); // folder
585 builder.AppendString(signon_realm); // key
586 builder.AppendString(app_name_); // appid
587 scoped_ptr<dbus::Response> response(
588 kwallet_proxy_->CallMethodAndBlock(
589 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
590 if (!response.get()) {
591 LOG(ERROR) << "Error contacting kwalletd (hasEntry)";
592 return false;
594 dbus::MessageReader reader(response.get());
595 bool has_entry = false;
596 if (!reader.PopBool(&has_entry)) {
597 LOG(ERROR) << "Error reading response from kwalletd (hasEntry): "
598 << response->ToString();
599 return false;
601 if (!has_entry) {
602 // This is not an error. There just isn't a matching entry.
603 return true;
608 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
609 dbus::MessageWriter builder(&method_call);
610 builder.AppendInt32(wallet_handle); // handle
611 builder.AppendString(folder_name_); // folder
612 builder.AppendString(signon_realm); // key
613 builder.AppendString(app_name_); // appid
614 scoped_ptr<dbus::Response> response(
615 kwallet_proxy_->CallMethodAndBlock(
616 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
617 if (!response.get()) {
618 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
619 return false;
621 dbus::MessageReader reader(response.get());
622 const uint8_t* bytes = nullptr;
623 size_t length = 0;
624 if (!reader.PopArrayOfBytes(&bytes, &length)) {
625 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
626 << response->ToString();
627 return false;
629 if (!bytes)
630 return false;
631 if (!CheckSerializedValue(bytes, length, signon_realm)) {
632 // This is weird, but we choose not to call it an error. There is an
633 // invalid entry somehow, but by just ignoring it, we make it easier to
634 // repair without having to delete it using kwalletmanager (that is, by
635 // just saving a new password within this realm to overwrite it).
636 return true;
639 // Can't we all just agree on whether bytes are signed or not? Please?
640 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
641 *forms = DeserializeValue(signon_realm, pickle);
644 return true;
647 bool NativeBackendKWallet::GetLoginsList(
648 BlacklistOptions options,
649 int wallet_handle,
650 ScopedVector<autofill::PasswordForm>* forms) {
651 forms->clear();
652 ScopedVector<autofill::PasswordForm> all_forms;
653 if (!GetAllLogins(wallet_handle, &all_forms))
654 return false;
656 // We have to read all the entries, and then filter them here.
657 forms->reserve(all_forms.size());
658 for (auto& saved_form : all_forms) {
659 if (saved_form->blacklisted_by_user ==
660 (options == BlacklistOptions::BLACKLISTED)) {
661 forms->push_back(saved_form);
662 saved_form = nullptr;
666 return true;
669 bool NativeBackendKWallet::GetAllLogins(
670 int wallet_handle,
671 ScopedVector<autofill::PasswordForm>* forms) {
672 // We could probably also use readEntryList here.
673 std::vector<std::string> realm_list;
675 dbus::MethodCall method_call(kKWalletInterface, "entryList");
676 dbus::MessageWriter builder(&method_call);
677 builder.AppendInt32(wallet_handle); // handle
678 builder.AppendString(folder_name_); // folder
679 builder.AppendString(app_name_); // appid
680 scoped_ptr<dbus::Response> response(
681 kwallet_proxy_->CallMethodAndBlock(
682 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
683 if (!response.get()) {
684 LOG(ERROR) << "Error contacting kwalletd (entryList)";
685 return false;
687 dbus::MessageReader reader(response.get());
688 if (!reader.PopArrayOfStrings(&realm_list)) {
689 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
690 << response->ToString();
691 return false;
695 forms->clear();
696 for (const std::string& signon_realm : realm_list) {
697 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
698 dbus::MessageWriter builder(&method_call);
699 builder.AppendInt32(wallet_handle); // handle
700 builder.AppendString(folder_name_); // folder
701 builder.AppendString(signon_realm); // key
702 builder.AppendString(app_name_); // appid
703 scoped_ptr<dbus::Response> response(
704 kwallet_proxy_->CallMethodAndBlock(
705 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
706 if (!response.get()) {
707 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
708 return false;
710 dbus::MessageReader reader(response.get());
711 const uint8_t* bytes = nullptr;
712 size_t length = 0;
713 if (!reader.PopArrayOfBytes(&bytes, &length)) {
714 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
715 << response->ToString();
716 return false;
718 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
719 continue;
721 // Can't we all just agree on whether bytes are signed or not? Please?
722 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
723 AppendSecondToFirst(forms, DeserializeValue(signon_realm, pickle));
725 return true;
728 bool NativeBackendKWallet::SetLoginsList(
729 const std::vector<autofill::PasswordForm*>& forms,
730 const std::string& signon_realm,
731 int wallet_handle) {
732 if (forms.empty()) {
733 // No items left? Remove the entry from the wallet.
734 dbus::MethodCall method_call(kKWalletInterface, "removeEntry");
735 dbus::MessageWriter builder(&method_call);
736 builder.AppendInt32(wallet_handle); // handle
737 builder.AppendString(folder_name_); // folder
738 builder.AppendString(signon_realm); // key
739 builder.AppendString(app_name_); // appid
740 scoped_ptr<dbus::Response> response(
741 kwallet_proxy_->CallMethodAndBlock(
742 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
743 if (!response.get()) {
744 LOG(ERROR) << "Error contacting kwalletd (removeEntry)";
745 return kInvalidKWalletHandle;
747 dbus::MessageReader reader(response.get());
748 int ret = 0;
749 if (!reader.PopInt32(&ret)) {
750 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
751 << response->ToString();
752 return false;
754 if (ret != 0)
755 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
756 return ret == 0;
759 Pickle value;
760 SerializeValue(forms, &value);
762 dbus::MethodCall method_call(kKWalletInterface, "writeEntry");
763 dbus::MessageWriter builder(&method_call);
764 builder.AppendInt32(wallet_handle); // handle
765 builder.AppendString(folder_name_); // folder
766 builder.AppendString(signon_realm); // key
767 builder.AppendArrayOfBytes(static_cast<const uint8_t*>(value.data()),
768 value.size()); // value
769 builder.AppendString(app_name_); // appid
770 scoped_ptr<dbus::Response> response(
771 kwallet_proxy_->CallMethodAndBlock(
772 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
773 if (!response.get()) {
774 LOG(ERROR) << "Error contacting kwalletd (writeEntry)";
775 return kInvalidKWalletHandle;
777 dbus::MessageReader reader(response.get());
778 int ret = 0;
779 if (!reader.PopInt32(&ret)) {
780 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
781 << response->ToString();
782 return false;
784 if (ret != 0)
785 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
786 return ret == 0;
789 bool NativeBackendKWallet::RemoveLoginsBetween(
790 base::Time delete_begin,
791 base::Time delete_end,
792 TimestampToCompare date_to_compare,
793 password_manager::PasswordStoreChangeList* changes) {
794 DCHECK(changes);
795 changes->clear();
796 int wallet_handle = WalletHandle();
797 if (wallet_handle == kInvalidKWalletHandle)
798 return false;
800 // We could probably also use readEntryList here.
801 std::vector<std::string> realm_list;
803 dbus::MethodCall method_call(kKWalletInterface, "entryList");
804 dbus::MessageWriter builder(&method_call);
805 builder.AppendInt32(wallet_handle); // handle
806 builder.AppendString(folder_name_); // folder
807 builder.AppendString(app_name_); // appid
808 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
809 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
810 if (!response.get()) {
811 LOG(ERROR) << "Error contacting kwalletd (entryList)";
812 return false;
814 dbus::MessageReader reader(response.get());
815 dbus::MessageReader array(response.get());
816 if (!reader.PopArray(&array)) {
817 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
818 << response->ToString();
819 return false;
821 while (array.HasMoreData()) {
822 std::string realm;
823 if (!array.PopString(&realm)) {
824 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
825 << response->ToString();
826 return false;
828 realm_list.push_back(realm);
832 bool ok = true;
833 for (size_t i = 0; i < realm_list.size(); ++i) {
834 const std::string& signon_realm = realm_list[i];
835 dbus::MethodCall method_call(kKWalletInterface, "readEntry");
836 dbus::MessageWriter builder(&method_call);
837 builder.AppendInt32(wallet_handle); // handle
838 builder.AppendString(folder_name_); // folder
839 builder.AppendString(signon_realm); // key
840 builder.AppendString(app_name_); // appid
841 scoped_ptr<dbus::Response> response(kwallet_proxy_->CallMethodAndBlock(
842 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
843 if (!response.get()) {
844 LOG(ERROR) << "Error contacting kwalletd (readEntry)";
845 continue;
847 dbus::MessageReader reader(response.get());
848 const uint8_t* bytes = nullptr;
849 size_t length = 0;
850 if (!reader.PopArrayOfBytes(&bytes, &length)) {
851 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
852 << response->ToString();
853 continue;
855 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
856 continue;
858 // Can't we all just agree on whether bytes are signed or not? Please?
859 Pickle pickle(reinterpret_cast<const char*>(bytes), length);
860 ScopedVector<autofill::PasswordForm> all_forms =
861 DeserializeValue(signon_realm, pickle);
863 ScopedVector<autofill::PasswordForm> kept_forms;
864 kept_forms.reserve(all_forms.size());
865 base::Time autofill::PasswordForm::*date_member =
866 date_to_compare == CREATION_TIMESTAMP
867 ? &autofill::PasswordForm::date_created
868 : &autofill::PasswordForm::date_synced;
869 for (auto& saved_form : all_forms) {
870 if (delete_begin <= saved_form->*date_member &&
871 (delete_end.is_null() || saved_form->*date_member < delete_end)) {
872 changes->push_back(password_manager::PasswordStoreChange(
873 password_manager::PasswordStoreChange::REMOVE, *saved_form));
874 } else {
875 kept_forms.push_back(saved_form);
876 saved_form = nullptr;
880 if (!SetLoginsList(kept_forms.get(), signon_realm, wallet_handle)) {
881 ok = false;
882 changes->clear();
885 return ok;
888 // static
889 ScopedVector<autofill::PasswordForm> NativeBackendKWallet::DeserializeValue(
890 const std::string& signon_realm,
891 const Pickle& pickle) {
892 PickleIterator iter(pickle);
894 int version = -1;
895 if (!iter.ReadInt(&version) ||
896 version < 0 || version > kPickleVersion) {
897 LOG(ERROR) << "Failed to deserialize KWallet entry "
898 << "(realm: " << signon_realm << ")";
899 return ScopedVector<autofill::PasswordForm>();
902 ScopedVector<autofill::PasswordForm> forms;
903 bool success = true;
904 if (version > 0) {
905 // In current pickles, we expect 64-bit sizes. Failure is an error.
906 success = DeserializeValueSize(
907 signon_realm, iter, version, false, false, &forms);
908 UMALogDeserializationStatus(success);
909 return forms.Pass();
912 const bool size_32 = sizeof(size_t) == sizeof(uint32_t);
913 if (!DeserializeValueSize(
914 signon_realm, iter, version, size_32, true, &forms)) {
915 // We failed to read the pickle using the native architecture of the system.
916 // Try again with the opposite architecture. Note that we do this even on
917 // 32-bit machines, in case we're reading a 64-bit pickle. (Probably rare,
918 // since mostly we expect upgrades, not downgrades, but both are possible.)
919 success = DeserializeValueSize(
920 signon_realm, iter, version, !size_32, false, &forms);
922 UMALogDeserializationStatus(success);
923 return forms.Pass();
926 int NativeBackendKWallet::WalletHandle() {
927 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB));
929 // Open the wallet.
930 // TODO(mdm): Are we leaking these handles? Find out.
931 int32_t handle = kInvalidKWalletHandle;
933 dbus::MethodCall method_call(kKWalletInterface, "open");
934 dbus::MessageWriter builder(&method_call);
935 builder.AppendString(wallet_name_); // wallet
936 builder.AppendInt64(0); // wid
937 builder.AppendString(app_name_); // appid
938 scoped_ptr<dbus::Response> response(
939 kwallet_proxy_->CallMethodAndBlock(
940 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
941 if (!response.get()) {
942 LOG(ERROR) << "Error contacting kwalletd (open)";
943 return kInvalidKWalletHandle;
945 dbus::MessageReader reader(response.get());
946 if (!reader.PopInt32(&handle)) {
947 LOG(ERROR) << "Error reading response from kwalletd (open): "
948 << response->ToString();
949 return kInvalidKWalletHandle;
951 if (handle == kInvalidKWalletHandle) {
952 LOG(ERROR) << "Error obtaining KWallet handle";
953 return kInvalidKWalletHandle;
957 // Check if our folder exists.
958 bool has_folder = false;
960 dbus::MethodCall method_call(kKWalletInterface, "hasFolder");
961 dbus::MessageWriter builder(&method_call);
962 builder.AppendInt32(handle); // handle
963 builder.AppendString(folder_name_); // folder
964 builder.AppendString(app_name_); // appid
965 scoped_ptr<dbus::Response> response(
966 kwallet_proxy_->CallMethodAndBlock(
967 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
968 if (!response.get()) {
969 LOG(ERROR) << "Error contacting kwalletd (hasFolder)";
970 return kInvalidKWalletHandle;
972 dbus::MessageReader reader(response.get());
973 if (!reader.PopBool(&has_folder)) {
974 LOG(ERROR) << "Error reading response from kwalletd (hasFolder): "
975 << response->ToString();
976 return kInvalidKWalletHandle;
980 // Create it if it didn't.
981 if (!has_folder) {
982 dbus::MethodCall method_call(kKWalletInterface, "createFolder");
983 dbus::MessageWriter builder(&method_call);
984 builder.AppendInt32(handle); // handle
985 builder.AppendString(folder_name_); // folder
986 builder.AppendString(app_name_); // appid
987 scoped_ptr<dbus::Response> response(
988 kwallet_proxy_->CallMethodAndBlock(
989 &method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT));
990 if (!response.get()) {
991 LOG(ERROR) << "Error contacting kwalletd (createFolder)";
992 return kInvalidKWalletHandle;
994 dbus::MessageReader reader(response.get());
995 bool success = false;
996 if (!reader.PopBool(&success)) {
997 LOG(ERROR) << "Error reading response from kwalletd (createFolder): "
998 << response->ToString();
999 return kInvalidKWalletHandle;
1001 if (!success) {
1002 LOG(ERROR) << "Error creating KWallet folder";
1003 return kInvalidKWalletHandle;
1007 return handle;
1010 std::string NativeBackendKWallet::GetProfileSpecificFolderName() const {
1011 // Originally, the folder name was always just "Chrome Form Data".
1012 // Now we use it to distinguish passwords for different profiles.
1013 return base::StringPrintf("%s (%d)", kKWalletFolder, profile_id_);