Chromium Code Reviews| Index: components/password_manager/core/browser/http_data_cleaner.cc |
| diff --git a/components/password_manager/core/browser/http_data_cleaner.cc b/components/password_manager/core/browser/http_data_cleaner.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..10078ab0c03be8df7595e7e311f5b6cce0664123 |
| --- /dev/null |
| +++ b/components/password_manager/core/browser/http_data_cleaner.cc |
| @@ -0,0 +1,255 @@ |
| +// Copyright 2017 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "components/password_manager/core/browser/http_data_cleaner.h" |
| + |
| +#include <algorithm> |
| +#include <iterator> |
| +#include <memory> |
| +#include <tuple> |
| +#include <vector> |
| + |
| +#include "base/bind.h" |
| +#include "base/bind_helpers.h" |
| +#include "base/logging.h" |
| +#include "base/macros.h" |
| +#include "base/stl_util.h" |
| +#include "components/autofill/core/common/password_form.h" |
| +#include "components/keyed_service/core/service_access_type.h" |
| +#include "components/password_manager/core/browser/hsts_query.h" |
| +#include "components/password_manager/core/browser/password_store.h" |
| +#include "components/password_manager/core/browser/password_store_consumer.h" |
| +#include "components/password_manager/core/browser/statistics_table.h" |
| +#include "components/password_manager/core/common/password_manager_pref_names.h" |
| +#include "components/prefs/pref_service.h" |
| + |
| +using autofill::PasswordForm; |
| + |
| +namespace password_manager { |
| + |
| +constexpr int kDefaultDelay = 40; |
|
vabr (Chromium)
2017/03/28 16:48:03
nit: You don't export this constant, so you might
vasilii
2017/03/28 17:27:45
Any reason not to put it in namespace {} ?
jdoerrie
2017/03/29 11:27:56
Done.
|
| + |
| +namespace { |
| + |
| +// Utility function that moves all elements from a specified iterator into a new |
| +// vector and returns it. The moved elements are deleted from the original |
| +// vector. |
| +std::vector<std::unique_ptr<PasswordForm>> SplitFormsFrom( |
| + std::vector<std::unique_ptr<PasswordForm>>::iterator from, |
| + std::vector<std::unique_ptr<PasswordForm>>* forms) { |
| + const auto end_forms = std::end(*forms); |
| + std::vector<std::unique_ptr<PasswordForm>> result; |
| + result.reserve(std::distance(from, end_forms)); |
| + std::move(from, end_forms, std::back_inserter(result)); |
| + forms->erase(from, end_forms); |
| + return result; |
| +} |
| + |
| +void RemoveLoginIfHSTS(const scoped_refptr<PasswordStore>& store, |
| + const PasswordForm& form, |
| + bool is_hsts) { |
| + if (is_hsts) |
| + store->RemoveLogin(form); |
| +} |
| + |
| +void RemoveSiteStatsIfHSTS(const scoped_refptr<PasswordStore>& store, |
| + const InteractionsStats& stats, |
| + bool is_hsts) { |
| + if (is_hsts) |
| + store->RemoveSiteStats(stats.origin_domain); |
| +} |
| + |
| +// This class removes obsolete HTTP data from a password store. HTTP data is |
| +// obsolete, if the corresponding host migrated to HTTPS and has HSTS enabled. |
|
vasilii
2017/03/28 17:27:44
no comma.
jdoerrie
2017/03/29 11:27:56
Done.
|
| +class ObsoleteHttpCleaner : public password_manager::PasswordStoreConsumer { |
| + public: |
| + // Constructing a ObsoleteHttpCleaner will result in issuing the clean up |
| + // tasks already. |
| + ObsoleteHttpCleaner( |
| + PasswordStore* store, |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context); |
| + ~ObsoleteHttpCleaner() override; |
| + |
| + // PasswordStoreConsumer: |
| + // This will be called for both autofillable logins as well as blacklisted |
| + // logins. Blacklisted logins are removed iff the scheme is HTTP and HSTS is |
| + // enabled for the host. |
| + // Autofillable logins are removed iff the scheme is HTTP and there exists |
| + // another HTTPS login with active HSTS that has the same host as well as the |
| + // same username and password. |
| + void OnGetPasswordStoreResults( |
| + std::vector<std::unique_ptr<PasswordForm>> results) override; |
| + |
| + // This will remove all stats for HTTP sites for which HSTS is active. |
| + void OnGetSiteStatistics(std::vector<InteractionsStats> stats) override; |
| + |
| + PasswordStore* store() { return store_; } |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context() { |
|
vabr (Chromium)
2017/03/28 16:48:03
nit: I recommend separating the (unrelated) access
jdoerrie
2017/03/29 11:27:56
Done.
|
| + return request_context_; |
| + } |
| + bool finished_cleaning() const { return remaining_cleaning_tasks_ == 0; } |
| + |
| + private: |
| + PasswordStore* store_; |
| + scoped_refptr<net::URLRequestContextGetter> request_context_; |
| + // There are 3 cleaning tasks initiated in the constructor. |
| + int remaining_cleaning_tasks_ = 3; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ObsoleteHttpCleaner); |
| +}; |
| + |
| +ObsoleteHttpCleaner::ObsoleteHttpCleaner( |
| + PasswordStore* password_store, |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context) |
| + : store_(password_store), request_context_(request_context) { |
| + DCHECK(store_); |
| + DCHECK(request_context_.get()); |
| + store()->GetBlacklistLogins(this); |
| + store()->GetAutofillableLogins(this); |
| + store()->GetAllSiteStats(this); |
| +} |
| + |
| +ObsoleteHttpCleaner::~ObsoleteHttpCleaner() = default; |
| + |
| +void ObsoleteHttpCleaner::OnGetPasswordStoreResults( |
| + std::vector<std::unique_ptr<PasswordForm>> results) { |
| + --remaining_cleaning_tasks_; |
| + // Non HTTP or HTTPS credentials are ignored. |
| + base::EraseIf(results, [](const std::unique_ptr<PasswordForm>& form) { |
| + return !form->origin.SchemeIsHTTPOrHTTPS(); |
| + }); |
| + |
| + // Move HTTPS forms into their own container. |
| + auto https_forms = SplitFormsFrom( |
| + std::partition(std::begin(results), std::end(results), |
| + [](const std::unique_ptr<PasswordForm>& form) { |
| + return form->origin.SchemeIs(url::kHttpScheme); |
| + }), |
| + &results); |
| + |
| + // Move blacklisted HTTP forms into their own container. |
| + const auto blacklisted_http_forms = SplitFormsFrom( |
| + std::partition(std::begin(results), std::end(results), |
| + [](const std::unique_ptr<PasswordForm>& form) { |
| + return !form->blacklisted_by_user; |
| + }), |
| + &results); |
| + |
| + // Remove blacklisted HTTP forms from the password store when HSTS is active |
| + // for the given host. |
| + for (const auto& form : blacklisted_http_forms) { |
| + PostHSTSQueryForHostAndRequestContext( |
| + form->origin, request_context(), |
| + base::Bind(&RemoveLoginIfHSTS, make_scoped_refptr(store()), *form)); |
| + } |
| + |
| + // Return early if there are no non-blacklisted HTTP forms. |
| + if (results.empty()) |
| + return; |
| + |
| + // Sort HTTPS forms according to custom comparison function. Consider two |
| + // forms equivalent if they have the same host, as well as the same username |
| + // and password. |
| + const auto form_cmp = [](const std::unique_ptr<PasswordForm>& lhs, |
| + const std::unique_ptr<PasswordForm>& rhs) { |
| + return std::forward_as_tuple(lhs->origin.host_piece(), lhs->username_value, |
| + lhs->password_value) < |
| + std::forward_as_tuple(rhs->origin.host_piece(), rhs->username_value, |
| + rhs->password_value); |
| + }; |
| + |
| + std::sort(std::begin(https_forms), std::end(https_forms), form_cmp); |
| + |
| + // Iterate through HTTP forms and remove them from the password store if there |
| + // exists an equivalent HTTPS form that has HSTS enabled. |
| + for (const auto& form : results) { |
| + if (std::binary_search(std::begin(https_forms), std::end(https_forms), form, |
| + form_cmp)) { |
| + PostHSTSQueryForHostAndRequestContext( |
| + form->origin, request_context(), |
| + base::Bind(&RemoveLoginIfHSTS, make_scoped_refptr(store()), *form)); |
| + } |
| + } |
| +} |
| + |
| +void ObsoleteHttpCleaner::OnGetSiteStatistics( |
| + std::vector<InteractionsStats> stats) { |
| + --remaining_cleaning_tasks_; |
| + for (const auto& stat : stats) { |
| + if (stat.origin_domain.SchemeIs(url::kHttpScheme)) { |
| + PostHSTSQueryForHostAndRequestContext( |
| + stat.origin_domain, request_context(), |
| + base::Bind(&RemoveSiteStatsIfHSTS, make_scoped_refptr(store()), |
| + stat)); |
| + } |
| + } |
| +} |
| + |
| +void WaitUntilCleaningIsDone(std::unique_ptr<ObsoleteHttpCleaner> cleaner, |
| + PrefService* prefs) { |
| + // Given the async nature of the cleaning tasks it is non-trivial to determine |
| + // when they are all done. In this method we make use of the fact that as long |
| + // the cleaning is not completed, weak pointers to the cleaner object exist |
| + // (the scheduled, but not yet executed tasks hold them). If the cleaning is |
| + // not done yet, this method schedules a task on the password store, which |
| + // will be behind the cleaning tasks in the task queue. When the scheduled |
| + // task gets executed, this method is called again. Now it is guaranteed that |
| + // the initial scheduled cleaning tasks will have been executed, but it might |
| + // be the case that they wait for the result of other scheduled tasks (e.g. on |
| + // Windows there could be async calls to GetIE7Login). In this case another |
| + // round trip of tasks will be scheduled. Finally, when no weak ptrs remain, |
| + // this method sets a boolean preference flag and returns. |
| + if (cleaner->HasWeakPtrs()) { |
| + const auto post_to_main_thread = |
| + [](std::unique_ptr<ObsoleteHttpCleaner> cleaner, PrefService* prefs) { |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
|
vasilii
2017/03/28 17:27:45
It returns a thread runner for the current thread.
jdoerrie
2017/03/29 11:27:56
Done.
|
| + FROM_HERE, base::Bind(&WaitUntilCleaningIsDone, |
| + base::Passed(std::move(cleaner)), prefs)); |
| + }; |
| + |
| + cleaner->store()->ScheduleTask(base::Bind( |
| + post_to_main_thread, base::Passed(std::move(cleaner)), prefs)); |
| + return; |
| + } |
| + |
| + DCHECK(cleaner->finished_cleaning()); |
| + prefs->SetBoolean(password_manager::prefs::kWasObsoleteHttpDataCleaned, true); |
| +} |
| + |
| +void DelayCleanObsoleteHttpDataForPasswordStoreAndPrefsImpl( |
| + PasswordStore* store, |
| + PrefService* prefs, |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context, |
| + int delay_in_seconds) { |
| + if (!prefs->GetBoolean( |
| + password_manager::prefs::kWasObsoleteHttpDataCleaned)) { |
| + base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| + FROM_HERE, |
| + base::Bind(&WaitUntilCleaningIsDone, |
| + base::Passed(base::MakeUnique<ObsoleteHttpCleaner>( |
| + store, request_context)), |
| + prefs), |
| + base::TimeDelta::FromSeconds(delay_in_seconds)); |
|
vasilii
2017/03/28 17:27:44
Only cleaning is delayed here. Everything else is
jdoerrie
2017/03/29 11:27:56
Done.
|
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +void DelayCleanObsoleteHttpDataForPasswordStoreAndPrefs( |
| + PasswordStore* store, |
| + PrefService* prefs, |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context) { |
| + DelayCleanObsoleteHttpDataForPasswordStoreAndPrefsImpl( |
| + store, prefs, request_context, kDefaultDelay); |
| +} |
| + |
| +void CleanObsoleteHttpDataForPasswordStoreAndPrefs( |
| + PasswordStore* store, |
| + PrefService* prefs, |
| + const scoped_refptr<net::URLRequestContextGetter>& request_context) { |
| + DelayCleanObsoleteHttpDataForPasswordStoreAndPrefsImpl(store, prefs, |
| + request_context, 0); |
| +} |
| + |
| +} // namespace password_manager |