blob: ae5be88c14ce06477c7a4a7efe71beda462bdad2 [file] [log] [blame]
// Copyright 2020 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/permissions/permission_actions_history.h"
#include "base/containers/adapters.h"
#include "base/json/values_util.h"
#include "base/ranges/algorithm.h"
#include "base/strings/string_piece.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/pref_names.h"
#include "components/permissions/request_type.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include <vector>
namespace permissions {
namespace {
// Inner structure of |prefs::kPermissionActions| containing a history of past
// permission actions. It is a dictionary of JSON lists keyed on the result of
// PermissionUtil::GetPermissionString (lower-cased for backwards compatibility)
// and has the following format:
//
// "profile.content_settings.permission_actions": {
// "notifications": [
// { "time": "1333333333337", "action": 1, "prompt_disposition": 2 },
// { "time": "1567957177000", "action": 3, "prompt_disposition": 4 },
// ],
// "geolocation": [...],
// ...
// }
// The "prompt_disposition" key was added in M96. Any older entry will be
// missing that key. The value is backed by the PermissionPromptDisposition
// enum.
constexpr char kPermissionActionEntryActionKey[] = "action";
constexpr char kPermissionActionEntryTimestampKey[] = "time";
constexpr char kPermissionActionEntryPromptDispositionKey[] =
"prompt_disposition";
// Entries in permission actions expire after they become this old.
constexpr base::TimeDelta kPermissionActionMaxAge = base::Days(90);
} // namespace
std::vector<PermissionActionsHistory::Entry>
PermissionActionsHistory::GetHistory(const base::Time& begin,
EntryFilter entry_filter) {
const base::Value* dictionary =
pref_service_->GetDictionary(prefs::kPermissionActions);
if (!dictionary)
return {};
std::vector<PermissionActionsHistory::Entry> matching_actions;
for (auto permission_entry : dictionary->DictItems()) {
const auto permission_actions =
GetHistoryInternal(begin, permission_entry.first, entry_filter);
matching_actions.insert(matching_actions.end(), permission_actions.begin(),
permission_actions.end());
}
base::ranges::sort(
matching_actions, {},
[](const PermissionActionsHistory::Entry& entry) { return entry.time; });
return matching_actions;
}
std::vector<PermissionActionsHistory::Entry>
PermissionActionsHistory::GetHistory(const base::Time& begin,
RequestType type,
EntryFilter entry_filter) {
return GetHistoryInternal(begin, PermissionKeyForRequestType(type),
entry_filter);
}
void PermissionActionsHistory::RecordAction(
PermissionAction action,
RequestType type,
PermissionPromptDisposition prompt_disposition) {
DictionaryPrefUpdate update(pref_service_, prefs::kPermissionActions);
const base::StringPiece permission_path(PermissionKeyForRequestType(type));
if (!update->FindPathOfType(permission_path, base::Value::Type::LIST)) {
update->SetPath(permission_path, base::Value(base::Value::Type::LIST));
}
base::Value* permission_actions =
update->FindPathOfType(permission_path, base::Value::Type::LIST);
CHECK(permission_actions);
// Discard permission actions older than |kPermissionActionMaxAge|.
const base::Time cutoff = base::Time::Now() - kPermissionActionMaxAge;
permission_actions->EraseListValueIf([cutoff](const base::Value& entry) {
const absl::optional<base::Time> timestamp =
base::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
return !timestamp || *timestamp < cutoff;
});
// Record the new permission action.
base::DictionaryValue new_action_attributes;
new_action_attributes.SetKey(kPermissionActionEntryTimestampKey,
base::TimeToValue(base::Time::Now()));
new_action_attributes.SetIntKey(kPermissionActionEntryActionKey,
static_cast<int>(action));
new_action_attributes.SetIntKey(kPermissionActionEntryPromptDispositionKey,
static_cast<int>(prompt_disposition));
permission_actions->Append(std::move(new_action_attributes));
}
void PermissionActionsHistory::ClearHistory(const base::Time& delete_begin,
const base::Time& delete_end) {
DCHECK(!delete_end.is_null());
if (delete_begin.is_null() && delete_end.is_max()) {
pref_service_->ClearPref(prefs::kPermissionActions);
return;
}
DictionaryPrefUpdate update(pref_service_, prefs::kPermissionActions);
for (auto permission_entry : update->DictItems()) {
permission_entry.second.EraseListValueIf([delete_begin,
delete_end](const auto& entry) {
const absl::optional<base::Time> timestamp =
base::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
return (!timestamp ||
(*timestamp >= delete_begin && *timestamp < delete_end));
});
}
}
PermissionActionsHistory::PermissionActionsHistory(PrefService* pref_service)
: pref_service_(pref_service) {}
std::vector<PermissionActionsHistory::Entry>
PermissionActionsHistory::GetHistoryInternal(const base::Time& begin,
const std::string& key,
EntryFilter entry_filter) {
const base::Value* permission_actions =
pref_service_->GetDictionary(prefs::kPermissionActions)->FindListKey(key);
if (!permission_actions)
return {};
std::vector<Entry> matching_actions;
for (const auto& entry : permission_actions->GetListDeprecated()) {
const absl::optional<base::Time> timestamp =
base::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
if (timestamp < begin)
continue;
if (entry_filter != EntryFilter::WANT_ALL_PROMPTS) {
// If we want either the Loud or Quiet UI actions but don't have this
// info due to legacy reasons we ignore the entry.
const absl::optional<int> prompt_disposition_int =
entry.FindIntKey(kPermissionActionEntryPromptDispositionKey);
if (!prompt_disposition_int)
continue;
const PermissionPromptDisposition prompt_disposition =
static_cast<PermissionPromptDisposition>(*prompt_disposition_int);
if (entry_filter == EntryFilter::WANT_LOUD_PROMPTS_ONLY &&
!PermissionUmaUtil::IsPromptDispositionLoud(prompt_disposition)) {
continue;
}
if (entry_filter == EntryFilter::WANT_QUIET_PROMPTS_ONLY &&
!PermissionUmaUtil::IsPromptDispositionQuiet(prompt_disposition)) {
continue;
}
}
const PermissionAction past_action = static_cast<PermissionAction>(
*(entry.FindIntKey(kPermissionActionEntryActionKey)));
matching_actions.emplace_back(
PermissionActionsHistory::Entry{past_action, timestamp.value()});
}
return matching_actions;
}
PrefService* PermissionActionsHistory::GetPrefServiceForTesting() {
return pref_service_;
}
// static
void PermissionActionsHistory::FillInActionCounts(
PredictionRequestFeatures::ActionCounts* counts,
const std::vector<PermissionActionsHistory::Entry>& actions) {
for (const auto& entry : actions) {
switch (entry.action) {
case PermissionAction::DENIED:
counts->denies++;
break;
case PermissionAction::GRANTED:
counts->grants++;
break;
case PermissionAction::DISMISSED:
counts->dismissals++;
break;
case PermissionAction::IGNORED:
counts->ignores++;
break;
default:
// Anything else is ignored.
break;
}
}
}
// static
void PermissionActionsHistory::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(prefs::kPermissionActions,
PrefRegistry::LOSSY_PREF);
}
} // namespace permissions