1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 #include "components/permissions/permission_actions_history.h"
6 #include "base/containers/adapters.h"
7 #include "base/json/values_util.h"
8 #include "base/ranges/algorithm.h"
9 #include "base/strings/string_piece.h"
10 #include "base/values.h"
11 #include "build/build_config.h"
12 #include "components/keyed_service/content/browser_context_dependency_manager.h"
13 #include "components/permissions/permission_util.h"
14 #include "components/permissions/pref_names.h"
15 #include "components/permissions/request_type.h"
16 #include "components/prefs/pref_service.h"
17 #include "components/prefs/scoped_user_pref_update.h"
18 #include "third_party/abseil-cpp/absl/types/optional.h"
22 namespace permissions {
25 // Inner structure of |prefs::kPermissionActions| containing a history of past
26 // permission actions. It is a dictionary of JSON lists keyed on the result of
27 // PermissionUtil::GetPermissionString (lower-cased for backwards compatibility)
28 // and has the following format:
30 // "profile.content_settings.permission_actions": {
32 // { "time": "1333333333337", "action": 1, "prompt_disposition": 2 },
33 // { "time": "1567957177000", "action": 3, "prompt_disposition": 4 },
35 // "geolocation": [...],
38 // The "prompt_disposition" key was added in M96. Any older entry will be
39 // missing that key. The value is backed by the PermissionPromptDisposition
41 constexpr char kPermissionActionEntryActionKey[] = "action";
42 constexpr char kPermissionActionEntryTimestampKey[] = "time";
43 constexpr char kPermissionActionEntryPromptDispositionKey[] =
46 // Entries in permission actions expire after they become this old.
47 constexpr base::TimeDelta kPermissionActionMaxAge = base::Days(90);
51 std::vector<PermissionActionsHistory::Entry>
52 PermissionActionsHistory::GetHistory(const base::Time& begin,
53 EntryFilter entry_filter) {
54 const base::Value::Dict& dictionary =
55 pref_service_->GetDict(prefs::kPermissionActions);
57 std::vector<PermissionActionsHistory::Entry> matching_actions;
58 for (auto permission_entry : dictionary) {
59 const auto permission_actions =
60 GetHistoryInternal(begin, permission_entry.first, entry_filter);
62 matching_actions.insert(matching_actions.end(), permission_actions.begin(),
63 permission_actions.end());
68 [](const PermissionActionsHistory::Entry& entry) { return entry.time; });
69 return matching_actions;
72 std::vector<PermissionActionsHistory::Entry>
73 PermissionActionsHistory::GetHistory(const base::Time& begin,
75 EntryFilter entry_filter) {
76 return GetHistoryInternal(begin, PermissionKeyForRequestType(type),
80 void PermissionActionsHistory::RecordAction(
81 PermissionAction action,
83 PermissionPromptDisposition prompt_disposition) {
84 ScopedDictPrefUpdate update(pref_service_, prefs::kPermissionActions);
85 base::Value::Dict& update_dict = update.Get();
87 const base::StringPiece permission_path(PermissionKeyForRequestType(type));
89 if (!update_dict.FindListByDottedPath(permission_path)) {
90 update_dict.SetByDottedPath(permission_path, base::Value::List());
93 base::Value::List* permission_actions =
94 update_dict.FindListByDottedPath(permission_path);
95 CHECK(permission_actions);
97 // Discard permission actions older than |kPermissionActionMaxAge|.
98 const base::Time cutoff = base::Time::Now() - kPermissionActionMaxAge;
99 permission_actions->EraseIf([cutoff](const base::Value& entry) {
100 const absl::optional<base::Time> timestamp = base::ValueToTime(
101 entry.GetDict().Find(kPermissionActionEntryTimestampKey));
102 return !timestamp || *timestamp < cutoff;
105 // Record the new permission action.
106 base::Value::Dict new_action_attributes;
107 new_action_attributes.Set(kPermissionActionEntryTimestampKey,
108 base::TimeToValue(base::Time::Now()));
109 new_action_attributes.Set(kPermissionActionEntryActionKey,
110 static_cast<int>(action));
111 new_action_attributes.Set(kPermissionActionEntryPromptDispositionKey,
112 static_cast<int>(prompt_disposition));
113 permission_actions->Append(std::move(new_action_attributes));
116 void PermissionActionsHistory::ClearHistory(const base::Time& delete_begin,
117 const base::Time& delete_end) {
118 DCHECK(!delete_end.is_null());
119 if (delete_begin.is_null() && delete_end.is_max()) {
120 pref_service_->ClearPref(prefs::kPermissionActions);
124 ScopedDictPrefUpdate update(pref_service_, prefs::kPermissionActions);
126 for (auto permission_entry : update.Get()) {
127 permission_entry.second.GetList().EraseIf([delete_begin,
128 delete_end](const auto& entry) {
129 const absl::optional<base::Time> timestamp = base::ValueToTime(
130 entry.GetDict().Find(kPermissionActionEntryTimestampKey));
131 return (!timestamp ||
132 (*timestamp >= delete_begin && *timestamp < delete_end));
137 PermissionActionsHistory::PermissionActionsHistory(PrefService* pref_service)
138 : pref_service_(pref_service) {}
140 std::vector<PermissionActionsHistory::Entry>
141 PermissionActionsHistory::GetHistoryInternal(const base::Time& begin,
142 const std::string& key,
143 EntryFilter entry_filter) {
144 const base::Value::List* permission_actions =
145 pref_service_->GetDict(prefs::kPermissionActions).FindList(key);
147 if (!permission_actions)
150 std::vector<Entry> matching_actions;
152 for (const auto& entry : *permission_actions) {
153 const base::Value::Dict& entry_dict = entry.GetDict();
154 const absl::optional<base::Time> timestamp =
155 base::ValueToTime(entry_dict.Find(kPermissionActionEntryTimestampKey));
157 if (timestamp < begin)
160 if (entry_filter != EntryFilter::WANT_ALL_PROMPTS) {
161 // If we want either the Loud or Quiet UI actions but don't have this
162 // info due to legacy reasons we ignore the entry.
163 const absl::optional<int> prompt_disposition_int =
164 entry_dict.FindInt(kPermissionActionEntryPromptDispositionKey);
165 if (!prompt_disposition_int)
168 const PermissionPromptDisposition prompt_disposition =
169 static_cast<PermissionPromptDisposition>(*prompt_disposition_int);
171 if (entry_filter == EntryFilter::WANT_LOUD_PROMPTS_ONLY &&
172 !PermissionUmaUtil::IsPromptDispositionLoud(prompt_disposition)) {
176 if (entry_filter == EntryFilter::WANT_QUIET_PROMPTS_ONLY &&
177 !PermissionUmaUtil::IsPromptDispositionQuiet(prompt_disposition)) {
181 const PermissionAction past_action = static_cast<PermissionAction>(
182 *(entry_dict.FindInt(kPermissionActionEntryActionKey)));
183 matching_actions.emplace_back(
184 PermissionActionsHistory::Entry{past_action, timestamp.value()});
186 return matching_actions;
189 PrefService* PermissionActionsHistory::GetPrefServiceForTesting() {
190 return pref_service_;
194 void PermissionActionsHistory::FillInActionCounts(
195 PredictionRequestFeatures::ActionCounts* counts,
196 const std::vector<PermissionActionsHistory::Entry>& actions) {
197 for (const auto& entry : actions) {
198 switch (entry.action) {
199 case PermissionAction::DENIED:
202 case PermissionAction::GRANTED:
203 case PermissionAction::GRANTED_ONCE:
206 case PermissionAction::DISMISSED:
207 counts->dismissals++;
209 case PermissionAction::IGNORED:
213 // Anything else is ignored.
220 void PermissionActionsHistory::RegisterProfilePrefs(
221 user_prefs::PrefRegistrySyncable* registry) {
222 registry->RegisterDictionaryPref(prefs::kPermissionActions,
223 PrefRegistry::LOSSY_PREF);
226 } // namespace permissions