chromium/chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.cc

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/policy/dlp/dlp_confidential_contents.h"

#include <memory>
#include <vector>

#include "base/ranges/algorithm.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/policy/dlp/dlp_rules_manager.h"
#include "chrome/browser/favicon/favicon_utils.h"
#include "components/enterprise/data_controls/core/browser/dlp_histogram_helper.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/window.h"

namespace policy {

namespace {

gfx::ImageSkia GetWindowIcon(aura::Window* window) {
  gfx::ImageSkia* image = window->GetProperty(aura::client::kWindowIconKey);
  if (!image)
    image = window->GetProperty(aura::client::kAppIconKey);
  return image ? *image : gfx::ImageSkia();
}

}  // namespace

// The maximum number of entries that can be kept in the
// DlpConfidentialContentsCache.
static constexpr size_t kDefaultCacheSizeLimit = 100;

// The default timeout after which the entries are evicted from the
// DlpConfidentialContentsCache.
static constexpr base::TimeDelta kDefaultCacheTimeout = base::Days(7);

DlpConfidentialContent::DlpConfidentialContent(
    content::WebContents* web_contents)
    : icon(favicon::TabFaviconFromWebContents(web_contents).AsImageSkia()),
      title(web_contents->GetTitle()),
      url(web_contents->GetLastCommittedURL().GetWithoutRef()) {}

DlpConfidentialContent::DlpConfidentialContent(aura::Window* window,
                                               const GURL& url)
    : icon(GetWindowIcon(window)),
      title(window->GetTitle()),
      url(url.GetWithoutRef()) {}

DlpConfidentialContent::DlpConfidentialContent(
    const DlpConfidentialContent& other) = default;
DlpConfidentialContent& DlpConfidentialContent::operator=(
    const DlpConfidentialContent& other) = default;

bool DlpConfidentialContent::operator==(
    const DlpConfidentialContent& other) const {
  return url == other.url;
}

bool DlpConfidentialContent::operator!=(
    const DlpConfidentialContent& other) const {
  return !(*this == other);
}

bool DlpConfidentialContent::operator<(
    const DlpConfidentialContent& other) const {
  return url < other.url;
}

bool DlpConfidentialContent::operator<=(
    const DlpConfidentialContent& other) const {
  return *this == other || *this < other;
}

bool DlpConfidentialContent::operator>(
    const DlpConfidentialContent& other) const {
  return !(*this <= other);
}

bool DlpConfidentialContent::operator>=(
    const DlpConfidentialContent& other) const {
  return !(*this < other);
}

DlpConfidentialContents::DlpConfidentialContents() = default;

DlpConfidentialContents::DlpConfidentialContents(
    const std::vector<content::WebContents*>& web_contents) {
  for (auto* content : web_contents)
    Add(content);
}

DlpConfidentialContents::DlpConfidentialContents(
    const DlpConfidentialContents& other) = default;

DlpConfidentialContents& DlpConfidentialContents::operator=(
    const DlpConfidentialContents& other) = default;

DlpConfidentialContents::~DlpConfidentialContents() = default;

const base::flat_set<DlpConfidentialContent>&
DlpConfidentialContents::GetContents() const {
  return contents_;
}

base::flat_set<DlpConfidentialContent>& DlpConfidentialContents::GetContents() {
  return contents_;
}

void DlpConfidentialContents::Add(content::WebContents* web_contents) {
  contents_.insert(DlpConfidentialContent(web_contents));
}

void DlpConfidentialContents::Add(aura::Window* window, const GURL& url) {
  contents_.insert(DlpConfidentialContent(window, url));
}

void DlpConfidentialContents::ClearAndAdd(content::WebContents* web_contents) {
  contents_.clear();
  Add(web_contents);
}

void DlpConfidentialContents::ClearAndAdd(aura::Window* window,
                                          const GURL& url) {
  contents_.clear();
  Add(window, url);
}

bool DlpConfidentialContents::IsEmpty() const {
  return contents_.empty();
}

void DlpConfidentialContents::InsertOrUpdate(
    const DlpConfidentialContents& other) {
  contents_.insert(other.contents_.begin(), other.contents_.end());
  for (auto other_content : other.contents_) {
    auto it = base::ranges::find_if(
        contents_, [&other_content](const DlpConfidentialContent& content) {
          return content == other_content &&
                 content.title != other_content.title;
        });
    if (it != contents_.end()) {
      *it = other_content;
    }
  }
}

DlpConfidentialContentsCache::Entry::Entry(
    const DlpConfidentialContent& content,
    DlpRulesManager::Restriction restriction,
    base::TimeTicks timestamp)
    : content(content), restriction(restriction), created_at(timestamp) {}

DlpConfidentialContentsCache::Entry::~Entry() = default;

DlpConfidentialContentsCache::DlpConfidentialContentsCache()
    : cache_size_limit_(kDefaultCacheSizeLimit),
      task_runner_(base::SequencedTaskRunner::GetCurrentDefault()) {}

DlpConfidentialContentsCache::~DlpConfidentialContentsCache() = default;

void DlpConfidentialContentsCache::Cache(
    const DlpConfidentialContent& content,
    DlpRulesManager::Restriction restriction) {
  if (Contains(content, restriction)) {
    return;
  }

  auto entry =
      std::make_unique<Entry>(content, restriction, base::TimeTicks::Now());
  StartEvictionTimer(entry.get());
  entries_.push_front(std::move(entry));
  if (entries_.size() > cache_size_limit_) {
    entries_.pop_back();
  }
  data_controls::DlpCountHistogram(
      data_controls::dlp::kConfidentialContentsCount, entries_.size(),
      cache_size_limit_);
}

bool DlpConfidentialContentsCache::Contains(
    content::WebContents* web_contents,
    DlpRulesManager::Restriction restriction) const {
  const GURL url = web_contents->GetLastCommittedURL();
  return base::ranges::any_of(
      entries_, [&](const std::unique_ptr<Entry>& entry) {
        return entry->restriction == restriction &&
               entry->content.url.EqualsIgnoringRef(url);
      });
}

bool DlpConfidentialContentsCache::Contains(
    const DlpConfidentialContent& content,
    DlpRulesManager::Restriction restriction) const {
  return base::ranges::any_of(
      entries_, [&](const std::unique_ptr<Entry>& entry) {
        return entry->restriction == restriction &&
               entry->content.url.EqualsIgnoringRef(content.url);
      });
}

size_t DlpConfidentialContentsCache::GetSizeForTesting() const {
  return entries_.size();
}

// static
base::TimeDelta DlpConfidentialContentsCache::GetCacheTimeout() {
  return kDefaultCacheTimeout;
}

void DlpConfidentialContentsCache::SetTaskRunnerForTesting(
    scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
  task_runner_ = task_runner;
}

void DlpConfidentialContentsCache::StartEvictionTimer(Entry* entry) {
  entry->eviction_timer.SetTaskRunner(task_runner_);
  entry->eviction_timer.Start(
      FROM_HERE, GetCacheTimeout(),
      base::BindOnce(&DlpConfidentialContentsCache::OnEvictionTimerUp,
                     base::Unretained(this), entry->content));
}

void DlpConfidentialContentsCache::OnEvictionTimerUp(
    const DlpConfidentialContent& content) {
  entries_.remove_if([&](const std::unique_ptr<Entry>& entry) {
    return entry.get()->content == content;
  });
}

}  // namespace policy