blob: 841847cd7ce3d7bbdc3389fddd9f295c64cc96f0 [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 "chrome/browser/permissions/permission_actions_history.h"
#include "base/containers/adapters.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "base/ranges/algorithm.h"
#include "base/util/values/values_util.h"
#include "build/build_config.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/keyed_service/content/browser_context_dependency_manager.h"
#include "components/permissions/permission_util.h"
#include "components/permissions/request_type.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include <vector>
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 },
// { "time": "1567957177000", "action": 3 },
// ],
// "geolocation": [...],
// ...
// }
constexpr char kPermissionActionEntryActionKey[] = "action";
constexpr char kPermissionActionEntryTimestampKey[] = "time";
// Entries in permission actions expire after they become this old.
constexpr base::TimeDelta kPermissionActionMaxAge =
base::TimeDelta::FromDays(90);
} // namespace
// static
PermissionActionsHistory* PermissionActionsHistory::GetForProfile(
Profile* profile) {
return Factory::GetForProfile(profile);
}
std::vector<PermissionActionsHistory::Entry>
PermissionActionsHistory::GetHistory(const base::Time& begin) {
const base::DictionaryValue* dictionary =
pref_service_->GetDictionary(prefs::kPermissionActions);
if (!dictionary)
return {};
std::vector<PermissionActionsHistory::Entry> matching_actions;
for (const auto& permission_entry : *dictionary) {
const auto permission_actions =
GetHistoryInternal(begin, permission_entry.first);
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,
permissions::RequestType type) {
return GetHistoryInternal(begin, PermissionKeyForRequestType(type));
}
void PermissionActionsHistory::RecordAction(
permissions::PermissionAction action,
permissions::RequestType type) {
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::ListValue());
}
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 base::Optional<base::Time> timestamp =
util::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
return !timestamp || *timestamp < cutoff;
});
// Record the new permission action.
base::DictionaryValue new_action_attributes;
new_action_attributes.SetKey(kPermissionActionEntryTimestampKey,
util::TimeToValue(base::Time::Now()));
new_action_attributes.SetIntKey(kPermissionActionEntryActionKey,
static_cast<int>(action));
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 (const auto& permission_entry : *update) {
permission_entry.second->EraseListValueIf([delete_begin,
delete_end](const auto& entry) {
const base::Optional<base::Time> timestamp =
util::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
return (!timestamp ||
(*timestamp >= delete_begin && *timestamp < delete_end));
});
}
}
PermissionActionsHistory::PermissionActionsHistory(Profile* profile)
: pref_service_(profile->GetPrefs()) {}
std::vector<PermissionActionsHistory::Entry>
PermissionActionsHistory::GetHistoryInternal(const base::Time& begin,
const std::string& key) {
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->GetList()) {
const base::Optional<base::Time> timestamp =
util::ValueToTime(entry.FindKey(kPermissionActionEntryTimestampKey));
if (timestamp >= begin) {
const permissions::PermissionAction past_action =
static_cast<permissions::PermissionAction>(
*(entry.FindIntKey(kPermissionActionEntryActionKey)));
matching_actions.emplace_back(
PermissionActionsHistory::Entry{past_action, timestamp.value()});
}
}
return matching_actions;
}
// PermissionActionsHistory::Factory------------------------------------
PermissionActionsHistory*
PermissionActionsHistory::Factory::GetForProfile(Profile* profile) {
return static_cast<PermissionActionsHistory*>(
GetInstance()->GetServiceForBrowserContext(profile, true));
}
// static
PermissionActionsHistory::Factory*
PermissionActionsHistory::Factory::GetInstance() {
return base::Singleton<PermissionActionsHistory::Factory>::get();
}
PermissionActionsHistory::Factory::Factory()
: BrowserContextKeyedServiceFactory(
"PermissionActionsHistory",
BrowserContextDependencyManager::GetInstance()) {}
PermissionActionsHistory::Factory::~Factory() = default;
KeyedService* PermissionActionsHistory::Factory::BuildServiceInstanceFor(
content::BrowserContext* context) const {
return new PermissionActionsHistory(static_cast<Profile*>(context));
}
content::BrowserContext*
PermissionActionsHistory::Factory::GetBrowserContextToUse(
content::BrowserContext* context) const {
return chrome::GetBrowserContextRedirectedInIncognito(context);
}