| // 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 |