Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / password_manager / native_backend_kwallet_x.cc
blob50b84b52a44fe3b19367249477199a7cb12bb4d9
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 "components/password_manager/core/browser/password_manager_util.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "dbus/bus.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;
30 namespace {
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,
52 size_t length,
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 << ")";
59 return false;
61 return true;
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)) {
69 if (!warn_only)
70 LOG(ERROR) << "Failed to deserialize URL.";
71 *url = GURL();
72 return false;
74 *url = GURL(url_string);
75 return true;
78 void LogDeserializationWarning(int version,
79 std::string signon_realm,
80 bool warn_only) {
81 if (warn_only) {
82 LOG(WARNING) << "Failed to deserialize version " << version
83 << " KWallet entry (realm: " << signon_realm
84 << ") with native architecture size; will try alternate "
85 << "size.";
86 } else {
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,
101 int version,
102 bool size_32,
103 bool warn_only,
104 ScopedVector<autofill::PasswordForm>* forms) {
105 base::PickleIterator iter = init_iter;
107 size_t count = 0;
108 if (size_32) {
109 uint32_t count_32 = 0;
110 if (!iter.ReadUInt32(&count_32)) {
111 LOG(ERROR) << "Failed to deserialize KWallet entry "
112 << "(realm: " << signon_realm << ")";
113 return false;
115 count = count_32;
116 } else {
117 if (!iter.ReadSizeT(&count)) {
118 LOG(ERROR) << "Failed to deserialize KWallet entry "
119 << "(realm: " << signon_realm << ")";
120 return false;
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.)
131 if (!warn_only) {
132 LOG(ERROR) << "Suspiciously large number of entries in KWallet entry "
133 << "(" << count << "; realm: " << signon_realm << ")";
135 return false;
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);
146 int scheme = 0;
147 int64 date_created = 0;
148 int type = 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);
165 return false;
167 form->scheme = static_cast<PasswordForm::Scheme>(scheme);
169 if (version > 1) {
170 if (!iter.ReadInt(&type) ||
171 !iter.ReadInt(&form->times_used) ||
172 !autofill::DeserializeFormData(&iter, &form->form_data)) {
173 LogDeserializationWarning(version, signon_realm, false);
174 return false;
176 form->type = static_cast<PasswordForm::Type>(type);
179 if (version > 2) {
180 int64 date_synced = 0;
181 if (!iter.ReadInt64(&date_synced)) {
182 LogDeserializationWarning(version, signon_realm, false);
183 return false;
185 form->date_synced = base::Time::FromInternalValue(date_synced);
188 if (version > 3) {
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);
194 return false;
198 if (version > 4) {
199 form->date_created = base::Time::FromInternalValue(date_created);
200 } else {
201 form->date_created = base::Time::FromTimeT(date_created);
204 if (version > 5) {
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);
210 return false;
212 if (read_success) {
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);
223 return true;
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());
261 second.weak_clear();
264 void UMALogDeserializationStatus(bool success) {
265 UMA_HISTOGRAM_BOOLEAN("PasswordManager.KWalletDeserializationStatus",
266 success);
269 } // namespace
271 NativeBackendKWallet::NativeBackendKWallet(LocalProfileId id)
272 : profile_id_(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;
312 event.Wait();
313 return success;
316 void NativeBackendKWallet::InitOnDBThread(scoped_refptr<dbus::Bus> optional_bus,
317 base::WaitableEvent* event,
318 bool* success) {
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;
324 } else {
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);
331 kwallet_proxy_ =
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));
340 event->Signal();
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";
365 return false;
367 dbus::MessageReader reader(response.get());
368 int32_t ret = -1;
369 std::string dbus_name;
370 std::string error;
371 int32_t pid = -1;
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();
376 return false;
378 if (!error.empty() || ret) {
379 LOG(ERROR) << "Error launching kwalletd: error '" << error << "' "
380 << " (code " << ret << ")";
381 return false;
384 return true;
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.
407 if (!enabled) {
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;
431 return INIT_SUCCESS;
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()) {
450 // It's an update.
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);
461 if (!ok)
462 changes.clear();
464 return changes;
467 bool NativeBackendKWallet::UpdateLogin(
468 const PasswordForm& form,
469 password_manager::PasswordStoreChangeList* changes) {
470 DCHECK(changes);
471 int wallet_handle = WalletHandle();
472 if (wallet_handle == kInvalidKWalletHandle)
473 return false;
475 ScopedVector<autofill::PasswordForm> forms;
476 if (!GetLoginsList(form.signon_realm, wallet_handle, &forms))
477 return false;
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())
485 return true;
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));
492 return true;
495 return false;
498 bool NativeBackendKWallet::RemoveLogin(
499 const PasswordForm& form,
500 password_manager::PasswordStoreChangeList* changes) {
501 DCHECK(changes);
502 int wallet_handle = WalletHandle();
503 if (wallet_handle == kInvalidKWalletHandle)
504 return false;
506 ScopedVector<autofill::PasswordForm> all_forms;
507 if (!GetLoginsList(form.signon_realm, wallet_handle, &all_forms))
508 return false;
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);
525 return true;
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)
548 return false;
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)
556 return false;
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)
564 return false;
565 return GetLoginsList(BlacklistOptions::BLACKLISTED, wallet_handle, forms);
568 bool NativeBackendKWallet::GetLoginsList(
569 const std::string& signon_realm,
570 int wallet_handle,
571 ScopedVector<autofill::PasswordForm>* forms) {
572 forms->clear();
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)";
586 return false;
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();
593 return false;
595 if (!has_entry) {
596 // This is not an error. There just isn't a matching entry.
597 return true;
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)";
613 return false;
615 dbus::MessageReader reader(response.get());
616 const uint8_t* bytes = nullptr;
617 size_t length = 0;
618 if (!reader.PopArrayOfBytes(&bytes, &length)) {
619 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
620 << response->ToString();
621 return false;
623 if (!bytes)
624 return false;
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).
630 return true;
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);
638 return true;
641 bool NativeBackendKWallet::GetLoginsList(
642 BlacklistOptions options,
643 int wallet_handle,
644 ScopedVector<autofill::PasswordForm>* forms) {
645 forms->clear();
646 ScopedVector<autofill::PasswordForm> all_forms;
647 if (!GetAllLogins(wallet_handle, &all_forms))
648 return false;
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))
671 return false;
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;
684 return true;
687 bool NativeBackendKWallet::GetAllLogins(
688 int wallet_handle,
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)";
703 return false;
705 dbus::MessageReader reader(response.get());
706 if (!reader.PopArrayOfStrings(&realm_list)) {
707 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
708 << response->ToString();
709 return false;
713 forms->clear();
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)";
726 return false;
728 dbus::MessageReader reader(response.get());
729 const uint8_t* bytes = nullptr;
730 size_t length = 0;
731 if (!reader.PopArrayOfBytes(&bytes, &length)) {
732 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
733 << response->ToString();
734 return false;
736 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
737 continue;
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));
743 return true;
746 bool NativeBackendKWallet::SetLoginsList(
747 const std::vector<autofill::PasswordForm*>& forms,
748 const std::string& signon_realm,
749 int wallet_handle) {
750 if (forms.empty()) {
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());
766 int ret = 0;
767 if (!reader.PopInt32(&ret)) {
768 LOG(ERROR) << "Error reading response from kwalletd (removeEntry): "
769 << response->ToString();
770 return false;
772 if (ret != 0)
773 LOG(ERROR) << "Bad return code " << ret << " from KWallet removeEntry";
774 return ret == 0;
777 base::Pickle value;
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());
796 int ret = 0;
797 if (!reader.PopInt32(&ret)) {
798 LOG(ERROR) << "Error reading response from kwalletd (writeEntry): "
799 << response->ToString();
800 return false;
802 if (ret != 0)
803 LOG(ERROR) << "Bad return code " << ret << " from KWallet writeEntry";
804 return ret == 0;
807 bool NativeBackendKWallet::RemoveLoginsBetween(
808 base::Time delete_begin,
809 base::Time delete_end,
810 TimestampToCompare date_to_compare,
811 password_manager::PasswordStoreChangeList* changes) {
812 DCHECK(changes);
813 changes->clear();
814 int wallet_handle = WalletHandle();
815 if (wallet_handle == kInvalidKWalletHandle)
816 return false;
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)";
830 return false;
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();
837 return false;
839 while (array.HasMoreData()) {
840 std::string realm;
841 if (!array.PopString(&realm)) {
842 LOG(ERROR) << "Error reading response from kwalletd (entryList): "
843 << response->ToString();
844 return false;
846 realm_list.push_back(realm);
850 bool ok = true;
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)";
863 continue;
865 dbus::MessageReader reader(response.get());
866 const uint8_t* bytes = nullptr;
867 size_t length = 0;
868 if (!reader.PopArrayOfBytes(&bytes, &length)) {
869 LOG(ERROR) << "Error reading response from kwalletd (readEntry): "
870 << response->ToString();
871 continue;
873 if (!bytes || !CheckSerializedValue(bytes, length, signon_realm))
874 continue;
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));
892 } else {
893 kept_forms.push_back(saved_form);
894 saved_form = nullptr;
898 if (!SetLoginsList(kept_forms.get(), signon_realm, wallet_handle)) {
899 ok = false;
900 changes->clear();
903 return ok;
906 // static
907 ScopedVector<autofill::PasswordForm> NativeBackendKWallet::DeserializeValue(
908 const std::string& signon_realm,
909 const base::Pickle& pickle) {
910 base::PickleIterator iter(pickle);
912 int version = -1;
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;
921 bool success = true;
922 if (version > 0) {
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);
927 return forms.Pass();
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);
941 return forms.Pass();
944 int NativeBackendKWallet::WalletHandle() {
945 DCHECK_CURRENTLY_ON(BrowserThread::DB);
947 // Open the wallet.
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.
999 if (!has_folder) {
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;
1019 if (!success) {
1020 LOG(ERROR) << "Error creating KWallet folder";
1021 return kInvalidKWalletHandle;
1025 return handle;
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_);