Chromium Code Reviews| Index: chrome/browser/password_manager/password_manager_util.cc |
| diff --git a/chrome/browser/password_manager/password_manager_util.cc b/chrome/browser/password_manager/password_manager_util.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..815777af81410870c8d39781c0346e41c5b2b340 |
| --- /dev/null |
| +++ b/chrome/browser/password_manager/password_manager_util.cc |
| @@ -0,0 +1,232 @@ |
| +// 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 "chrome/browser/password_manager/password_manager_util.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/time/time.h" |
| +#include "chrome/browser/password_manager/password_store_factory.h" |
| +#include "chrome/browser/profiles/profile.h" |
| +#include "components/autofill/core/common/password_form.h" |
| +#include "components/keyed_service/core/service_access_type.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" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "net/http/transport_security_state.h" |
| +#include "net/url_request/url_request_context.h" |
| +#include "net/url_request/url_request_context_getter.h" |
| +#include "url/gurl.h" |
| + |
| +using autofill::PasswordForm; |
| + |
| +namespace password_manager_util { |
| + |
| +namespace { |
| + |
| +// 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. |
| +class ObsoleteHttpCleaner : public password_manager::PasswordStoreConsumer { |
| + public: |
| + // Constructing a ObsoleteHttpCleaner will result in issuing the clean up |
| + // tasks already. |
| + explicit ObsoleteHttpCleaner(Profile* profile); |
| + ~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<password_manager::InteractionsStats> stats) override; |
| + |
| + Profile* profile() { return profile_; } |
| + |
| + // Returns the PasswordStore associated with |profile_|. |
| + password_manager::PasswordStore* GetPasswordStore() const; |
| + |
| + private: |
| + Profile* profile_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ObsoleteHttpCleaner); |
| +}; |
| + |
| +// 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; |
| +} |
| + |
| +ObsoleteHttpCleaner::ObsoleteHttpCleaner(Profile* profile) : profile_(profile) { |
| + DCHECK(profile_); |
| + GetPasswordStore()->GetBlacklistLogins(this); |
| + GetPasswordStore()->GetAutofillableLogins(this); |
| + GetPasswordStore()->GetAllSiteStats(this); |
| +} |
| + |
| +ObsoleteHttpCleaner::~ObsoleteHttpCleaner() = default; |
| + |
| +void ObsoleteHttpCleaner::OnGetPasswordStoreResults( |
| + std::vector<std::unique_ptr<PasswordForm>> results) { |
| + // Non HTTP or HTTPS credentials are ignored. |
| + results.erase(std::remove_if(std::begin(results), std::end(results), |
| + [](const std::unique_ptr<PasswordForm>& form) { |
| + return !form->origin.SchemeIsHTTPOrHTTPS(); |
| + }), |
| + std::end(results)); |
| + |
| + // 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) { |
| + if (IsHSTSActiveForProfileAndHost(profile_, form->origin)) |
| + GetPasswordStore()->RemoveLogin(*form); |
| + } |
| + |
| + // Return early if there are no non-blacklisted HTTP forms. |
| + if (results.empty()) |
| + return; |
| + |
| + // Ignore non HSTS forms. |
| + https_forms.erase( |
| + std::remove_if(std::begin(https_forms), std::end(https_forms), |
| + [this](const std::unique_ptr<PasswordForm>& form) { |
| + return IsHSTSActiveForProfileAndHost(profile_, |
| + form->origin); |
| + }), |
| + std::end(https_forms)); |
| + |
| + // Sort HSTS 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 HSTS form. |
| + for (const auto& form : results) { |
| + if (std::binary_search(std::begin(https_forms), std::end(https_forms), form, |
| + form_cmp)) |
| + GetPasswordStore()->RemoveLogin(*form); |
| + } |
| +} |
| + |
| +void ObsoleteHttpCleaner::OnGetSiteStatistics( |
| + std::vector<password_manager::InteractionsStats> stats) { |
| + for (const auto& stat : stats) { |
| + if (stat.origin_domain.SchemeIs(url::kHttpScheme) && |
| + IsHSTSActiveForProfileAndHost(profile_, stat.origin_domain)) |
| + GetPasswordStore()->RemoveSiteStats(stat.origin_domain); |
| + } |
| +} |
| + |
| +password_manager::PasswordStore* ObsoleteHttpCleaner::GetPasswordStore() const { |
| + return PasswordStoreFactory::GetForProfile(profile_, |
| + ServiceAccessType::EXPLICIT_ACCESS) |
| + .get(); |
| +} |
| + |
| +void PostToDBThread(std::unique_ptr<ObsoleteHttpCleaner> cleaner) { |
|
vasilii
2017/02/24 17:27:04
It's not necessary a DB thread.
The function dese
jdoerrie
2017/02/27 10:53:21
I took the naming from |password_store_factory|: h
vasilii
2017/02/27 12:28:26
PostToPasswordStoreThread
jdoerrie
2017/03/23 15:42:12
Acknowledged.
|
| + if (cleaner->HasWeakPtrs()) { |
| + // The presence of weak ptrs implies that the cleaning tasks have not |
| + // completed yet. In this case we post a task to the background thread to |
| + // call this method again on the UI thread. Eventually there won't be any |
| + // weak pointers to cleaner left and this method returns, destroying the |
| + // cleaner. |
| + const auto post_to_ui_thread = |
| + [](std::unique_ptr<ObsoleteHttpCleaner> cleaner) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::UI, FROM_HERE, |
| + base::Bind(&PostToDBThread, base::Passed(std::move(cleaner)))); |
|
vasilii
2017/02/24 17:27:03
Doesn't it work without std::move?
jdoerrie
2017/02/27 10:53:21
Not sure, base::Passed's documentation mentions st
jdoerrie
2017/03/23 15:42:12
Removing std::move results in a compilation error
|
| + }; |
| + |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::DB, FROM_HERE, |
|
vasilii
2017/02/24 17:27:04
PasswordStore may run on another thread. It still
jdoerrie
2017/02/27 10:53:21
Under what circumstances does this actually happen
vasilii
2017/02/27 12:28:26
You can use PasswordStore::ScheduleTask. On Mac th
jdoerrie
2017/03/23 15:42:11
Done.
|
| + base::Bind(post_to_ui_thread, base::Passed(std::move(cleaner)))); |
| + } |
| + |
| + cleaner->profile()->GetPrefs()->SetBoolean( |
| + password_manager::prefs::kWasObsoleteHttpDataCleaned, true); |
| +} |
| + |
| +} // namespace |
| + |
| +bool IsHSTSActiveForProfileAndHost(Profile* profile, const GURL& origin) { |
| + if (!origin.is_valid()) |
| + return false; |
| + |
| + net::TransportSecurityState* security_state = |
| + profile->GetRequestContext() |
| + ->GetURLRequestContext() |
| + ->transport_security_state(); |
| + |
| + if (!security_state) |
| + return false; |
| + |
| + return security_state->ShouldUpgradeToSSL(origin.host()); |
| +} |
| + |
| +void CleanObsoleteHttpDataForProfile(Profile* profile) { |
| + if (!profile->GetPrefs()->GetBoolean( |
| + password_manager::prefs::kWasObsoleteHttpDataCleaned)) { |
| + PostToDBThread(base::MakeUnique<ObsoleteHttpCleaner>(profile)); |
| + } |
| +} |
| + |
| +void DelayCleanObsoleteHttpDataForProfile(Profile* profile) { |
| + content::BrowserThread::PostDelayedTask( |
| + content::BrowserThread::UI, FROM_HERE, |
| + base::Bind(&CleanObsoleteHttpDataForProfile, profile), |
| + base::TimeDelta::FromSeconds(40)); |
|
vasilii
2017/02/24 17:27:04
magic number to the constants in the begin of the
jdoerrie
2017/03/23 15:42:12
Done.
|
| +} |
| + |
| +} // namespace password_manager_util |