blob: 8bfd600a86c650013435be14287c94b810c79e46 [file] [log] [blame]
// Copyright 2019 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/password_generation_state.h"
#include <map>
#include <utility>
#include "base/callback.h"
#include "base/time/default_clock.h"
#include "components/password_manager/core/browser/form_fetcher.h"
#include "components/password_manager/core/browser/form_saver.h"
#include "components/password_manager/core/browser/password_form_manager_for_ui.h"
#include "components/password_manager/core/browser/password_manager_client.h"
#include "components/password_manager/core/browser/password_manager_driver.h"
#include "components/password_manager/core/common/password_manager_features.h"
namespace password_manager {
namespace {
using autofill::PasswordForm;
using metrics_util::GenerationPresaveConflict;
using metrics_util::LogGenerationPresaveConflict;
std::vector<PasswordForm> DeepCopyVector(
const std::vector<const PasswordForm*>& forms) {
std::vector<PasswordForm> result;
result.reserve(forms.size());
for (const PasswordForm* form : forms)
result.emplace_back(*form);
return result;
}
// Implementation of the UI model for "Update password?" bubble in case there is
// a conflict in generation.
class PasswordDataForUI : public PasswordFormManagerForUI {
public:
PasswordDataForUI(PasswordForm pending_form,
const std::vector<const PasswordForm*>& matches,
const std::vector<const PasswordForm*>& federated,
base::RepeatingCallback<void(bool, const PasswordForm&)>
bubble_interaction);
~PasswordDataForUI() override = default;
PasswordDataForUI(const PasswordDataForUI&) = delete;
PasswordDataForUI& operator=(const PasswordDataForUI&) = delete;
// PasswordFormManagerForUI:
const GURL& GetOrigin() const override;
std::map<base::string16, const PasswordForm*> GetBestMatches() const override;
std::vector<const PasswordForm*> GetFederatedMatches() const override;
const PasswordForm& GetPendingCredentials() const override;
metrics_util::CredentialSourceType GetCredentialSource() const override;
PasswordFormMetricsRecorder* GetMetricsRecorder() override;
base::span<const InteractionsStats> GetInteractionsStats() const override;
bool IsBlacklisted() const override;
void Save() override;
void Update(const PasswordForm& credentials_to_update) override;
void UpdateUsername(const base::string16& new_username) override;
void UpdatePasswordValue(const base::string16& new_password) override;
void OnNopeUpdateClicked() override;
void OnNeverClicked() override;
void OnNoInteraction(bool is_update) override;
void PermanentlyBlacklist() override;
void OnPasswordsRevealed() override;
private:
PasswordForm pending_form_;
// TODO(https://crbug.com/831123): switch to vector.
std::map<base::string16, const PasswordForm*> matches_;
const std::vector<PasswordForm> federated_matches_;
const std::vector<PasswordForm> non_federated_matches_;
// Observer that waits for bubble interaction.
// The first parameter is true iff the bubble was accepted.
// The second parameter is the pending form.
base::RepeatingCallback<void(bool, const PasswordForm&)>
bubble_interaction_cb_;
};
PasswordDataForUI::PasswordDataForUI(
PasswordForm pending_form,
const std::vector<const PasswordForm*>& matches,
const std::vector<const PasswordForm*>& federated,
base::RepeatingCallback<void(bool, const PasswordForm&)> bubble_interaction)
: pending_form_(std::move(pending_form)),
federated_matches_(DeepCopyVector(federated)),
non_federated_matches_(DeepCopyVector(matches)),
bubble_interaction_cb_(std::move(bubble_interaction)) {
for (const PasswordForm& form : non_federated_matches_)
matches_[form.username_value] = &form;
}
const GURL& PasswordDataForUI::GetOrigin() const {
return pending_form_.origin;
}
std::map<base::string16, const PasswordForm*>
PasswordDataForUI::GetBestMatches() const {
return matches_;
}
std::vector<const PasswordForm*> PasswordDataForUI::GetFederatedMatches()
const {
std::vector<const PasswordForm*> result(federated_matches_.size());
std::transform(federated_matches_.begin(), federated_matches_.end(),
result.begin(),
[](const PasswordForm& form) { return &form; });
return result;
}
const PasswordForm& PasswordDataForUI::GetPendingCredentials() const {
return pending_form_;
}
metrics_util::CredentialSourceType PasswordDataForUI::GetCredentialSource()
const {
return metrics_util::CredentialSourceType::kPasswordManager;
}
PasswordFormMetricsRecorder* PasswordDataForUI::GetMetricsRecorder() {
return nullptr;
}
base::span<const InteractionsStats> PasswordDataForUI::GetInteractionsStats()
const {
return {};
}
bool PasswordDataForUI::IsBlacklisted() const {
// 'true' would suppress the bubble.
return false;
}
void PasswordDataForUI::Save() {
LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_SAVE);
bubble_interaction_cb_.Run(true, pending_form_);
}
void PasswordDataForUI::Update(const PasswordForm&) {
// The method is obsolete.
NOTREACHED();
}
void PasswordDataForUI::UpdateUsername(const base::string16& new_username) {
pending_form_.username_value = new_username;
}
void PasswordDataForUI::UpdatePasswordValue(
const base::string16& new_password) {
// Ignore. The generated password can be edited in-place.
}
void PasswordDataForUI::OnNopeUpdateClicked() {
LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_CANCEL);
bubble_interaction_cb_.Run(false, pending_form_);
}
void PasswordDataForUI::OnNeverClicked() {
LogPresavedUpdateUIDismissalReason(metrics_util::CLICKED_NEVER);
bubble_interaction_cb_.Run(false, pending_form_);
}
void PasswordDataForUI::OnNoInteraction(bool is_update) {
LogPresavedUpdateUIDismissalReason(metrics_util::NO_DIRECT_INTERACTION);
bubble_interaction_cb_.Run(false, pending_form_);
}
void PasswordDataForUI::PermanentlyBlacklist() {}
void PasswordDataForUI::OnPasswordsRevealed() {}
// Returns a form from |matches| that causes a name conflict with |generated|.
const PasswordForm* FindUsernameConflict(
const PasswordForm& generated,
const std::vector<const PasswordForm*>& matches) {
for (const auto* form : matches) {
if (form->username_value == generated.username_value)
return form;
}
return nullptr;
}
} // namespace
PasswordGenerationState::PasswordGenerationState(FormSaver* form_saver,
PasswordManagerClient* client)
: form_saver_(form_saver),
client_(client),
clock_(new base::DefaultClock) {}
PasswordGenerationState::~PasswordGenerationState() = default;
std::unique_ptr<PasswordGenerationState> PasswordGenerationState::Clone(
FormSaver* form_saver) const {
auto clone = std::make_unique<PasswordGenerationState>(form_saver, client_);
clone->presaved_ = presaved_;
return clone;
}
void PasswordGenerationState::GeneratedPasswordAccepted(
PasswordForm generated,
const FormFetcher& fetcher,
base::WeakPtr<PasswordManagerDriver> driver) {
if (!base::FeatureList::IsEnabled(features::kGenerationNoOverwrites)) {
// If the feature not enabled, just proceed with the generation.
driver->GeneratedPasswordAccepted(generated.password_value);
return;
}
// Clear the username value if there are already saved credentials with
// the same username in order to prevent overwriting.
std::vector<const PasswordForm*> matches = fetcher.GetNonFederatedMatches();
if (FindUsernameConflict(generated, matches)) {
generated.username_value.clear();
const PasswordForm* conflict = FindUsernameConflict(generated, matches);
if (conflict) {
LogGenerationPresaveConflict(
GenerationPresaveConflict::kConflictWithEmptyUsername);
auto bubble_launcher = std::make_unique<PasswordDataForUI>(
std::move(generated), matches, fetcher.GetFederatedMatches(),
base::BindRepeating(&PasswordGenerationState::OnPresaveBubbleResult,
weak_factory_.GetWeakPtr(), std::move(driver)));
client_->PromptUserToSaveOrUpdatePassword(std::move(bubble_launcher),
true);
return;
} else {
LogGenerationPresaveConflict(
GenerationPresaveConflict::kNoConflictWithEmptyUsername);
}
} else {
LogGenerationPresaveConflict(
GenerationPresaveConflict::kNoUsernameConflict);
}
driver->GeneratedPasswordAccepted(generated.password_value);
}
void PasswordGenerationState::PresaveGeneratedPassword(
PasswordForm generated,
const std::vector<const PasswordForm*>& matches) {
DCHECK(!generated.password_value.empty());
// Clear the username value if there are already saved credentials with
// the same username in order to prevent overwriting.
if (FindUsernameConflict(generated, matches))
generated.username_value.clear();
generated.date_created = clock_->Now();
if (presaved_) {
form_saver_->UpdateReplace(generated, {} /* matches */,
base::string16() /* old_password */,
presaved_.value() /* old_primary_key */);
} else {
form_saver_->Save(generated, {} /* matches */,
base::string16() /* old_password */);
}
presaved_ = std::move(generated);
}
void PasswordGenerationState::PasswordNoLongerGenerated() {
DCHECK(presaved_);
form_saver_->Remove(*presaved_);
presaved_.reset();
}
void PasswordGenerationState::CommitGeneratedPassword(
PasswordForm generated,
const std::vector<const PasswordForm*>& matches,
const base::string16& old_password) {
DCHECK(presaved_);
generated.preferred = true;
generated.date_last_used = clock_->Now();
generated.date_created = clock_->Now();
form_saver_->UpdateReplace(generated, matches, old_password,
presaved_.value() /* old_primary_key */);
}
void PasswordGenerationState::OnPresaveBubbleResult(
const base::WeakPtr<PasswordManagerDriver>& driver,
bool accepted,
const PasswordForm& pending) {
weak_factory_.InvalidateWeakPtrs();
if (accepted) {
driver->GeneratedPasswordAccepted(pending.password_value);
}
}
} // namespace password_manager