1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
|
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sensitive_content/sensitive_content_manager.h"
#include "base/check_deref.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/integrators/password_form_classification.h"
#include "components/password_manager/content/browser/password_form_classification_util.h"
#include "components/sensitive_content/features.h"
#include "components/sensitive_content/sensitive_content_client.h"
#include "content/public/browser/web_contents.h"
namespace sensitive_content {
namespace {
using LifecycleState = autofill::AutofillDriver::LifecycleState;
using autofill::AutofillField;
using autofill::AutofillManager;
using autofill::FieldType;
using autofill::FieldTypeGroup;
using autofill::FormGlobalId;
bool IsSensitiveAutofillType(FieldType type) {
static constexpr autofill::FieldTypeSet kNonSensitivePasswordTypes = {
FieldType::NOT_ACCOUNT_CREATION_PASSWORD, FieldType::NOT_NEW_PASSWORD,
FieldType::NOT_PASSWORD, FieldType::NOT_USERNAME};
FieldTypeGroup field_type_group = autofill::GroupTypeOfFieldType(type);
// Return true if the field is a credit card or password form field.
// A field is a password form field if it's part of
// `FieldTypeGroup::kPasswordField`, but it's not prefixed with "NOT_".
return field_type_group == FieldTypeGroup::kCreditCard ||
field_type_group == FieldTypeGroup::kStandaloneCvcField ||
(field_type_group == FieldTypeGroup::kPasswordField &&
!kNonSensitivePasswordTypes.contains(type));
}
} // namespace
SensitiveContentManager::SensitiveContentManager(
content::WebContents* web_contents,
SensitiveContentClient* client)
: client_(CHECK_DEREF(client)) {
autofill_managers_observation_.Observe(
web_contents, autofill::ScopedAutofillManagersObservation::
InitializationPolicy::kObservePreexistingManagers);
}
SensitiveContentManager::~SensitiveContentManager() = default;
bool SensitiveContentManager::UpdateContentSensitivity() {
const bool content_is_sensitive = !sensitive_fields_.empty();
// Prevent unnecessary calls to the client.
if (last_content_was_sensitive_ != content_is_sensitive) {
client_->SetContentSensitivity(!sensitive_fields_.empty());
last_content_was_sensitive_ = content_is_sensitive;
base::UmaHistogramBoolean(
base::StrCat({client_->GetHistogramPrefix(), "SensitivityChanged"}),
content_is_sensitive);
if (content_is_sensitive) {
content_became_sensitive_timestamp_ = base::TimeTicks::Now();
} else if (content_became_sensitive_timestamp_.has_value()) {
base::UmaHistogramLongTimes(
base::StrCat({client_->GetHistogramPrefix(), "SensitiveTime"}),
base::TimeTicks::Now() - content_became_sensitive_timestamp_.value());
content_became_sensitive_timestamp_.reset();
}
return true;
}
return false;
}
void SensitiveContentManager::OnFieldTypesDetermined(AutofillManager& manager,
FormGlobalId form_id,
FieldTypeSource) {
if (const autofill::FormStructure* form =
manager.FindCachedFormById(form_id)) {
for (const std::unique_ptr<AutofillField>& field : form->fields()) {
const bool field_is_sensitive =
IsSensitiveAutofillType(field->Type().GetStorableType());
// The feature param check is done first because reparsing by password
// manager (calling `ClassifyAsPasswordForm`) can take long. Moreover,
// this feature param exists only to check whether reparsing has a
// negative performance impact or not. Otherwise, it is known that
// reparsing by password manager is more accurate for password forms.
const bool field_is_sensitive_after_password_manager_reparsing =
features::kSensitiveContentUsePwmHeuristicsParam.Get() &&
password_manager::ClassifyAsPasswordForm(manager, form_id,
field->global_id())
.type !=
autofill::PasswordFormClassification::Type::kNoPasswordForm;
if (field_is_sensitive ||
field_is_sensitive_after_password_manager_reparsing) {
sensitive_fields_.insert(field->global_id());
} else {
sensitive_fields_.erase(field->global_id());
}
}
if (UpdateContentSensitivity() && last_content_was_sensitive_) {
const auto& element = latency_until_sensitive_timer_.find(form_id);
if (element != latency_until_sensitive_timer_.end()) {
base::UmaHistogramLongTimes(base::StrCat({client_->GetHistogramPrefix(),
"LatencyUntilSensitive"}),
base::TimeTicks::Now() - element->second);
}
}
}
}
void SensitiveContentManager::OnBeforeFormsSeen(
AutofillManager& manager,
base::span<const FormGlobalId> updated_forms,
base::span<const FormGlobalId> removed_forms) {
for (const FormGlobalId& form_id : updated_forms) {
latency_until_sensitive_timer_[form_id] = base::TimeTicks::Now();
}
for (const FormGlobalId& form_id : removed_forms) {
latency_until_sensitive_timer_.erase(form_id);
if (const autofill::FormStructure* form =
manager.FindCachedFormById(form_id)) {
for (const std::unique_ptr<AutofillField>& field : form->fields()) {
sensitive_fields_.erase(field->global_id());
}
}
}
UpdateContentSensitivity();
}
void SensitiveContentManager::OnAutofillManagerStateChanged(
AutofillManager& manager,
LifecycleState previous,
LifecycleState current) {
autofill::LocalFrameToken local_frame_token =
manager.driver().GetFrameToken();
if (previous == LifecycleState::kActive &&
current != LifecycleState::kActive) {
// The frame is not active anymore, so its fields are not anymore in the
// DOM.
// If needed, the time complexity can be further improved here by exploiting
// that fields from the same frame are next to each other in the set.
std::erase_if(sensitive_fields_,
[local_frame_token](const autofill::FieldGlobalId& field_id) {
return field_id.frame_token == local_frame_token;
});
std::erase_if(latency_until_sensitive_timer_,
[local_frame_token](const auto& item) {
FormGlobalId form_id = item.first;
return form_id.frame_token == local_frame_token;
});
} else if (previous != LifecycleState::kActive &&
current == LifecycleState::kActive) {
// The frame became active, so its fields are present in the DOM again.
const std::map<FormGlobalId, std::unique_ptr<autofill::FormStructure>>&
forms = manager.form_structures();
for (const auto& [form_id, form_structure] : forms) {
const std::vector<std::unique_ptr<AutofillField>>& fields =
form_structure->fields();
for (const std::unique_ptr<AutofillField>& field : fields) {
if (IsSensitiveAutofillType(field->Type().GetStorableType())) {
sensitive_fields_.insert(field->global_id());
}
}
}
}
UpdateContentSensitivity();
}
} // namespace sensitive_content
|