chromium/content/browser/lock_screen/lock_screen_storage_impl.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 "content/browser/lock_screen/lock_screen_storage_impl.h"

#include <map>
#include <memory>

#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/singleton.h"
#include "base/path_service.h"
#include "base/sequence_checker.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "components/value_store/value_store.h"
#include "components/value_store/value_store_factory.h"
#include "components/value_store/value_store_factory_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "crypto/sha2.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "url/origin.h"

using value_store::ValueStore;

namespace content {

namespace {

// See Extensions.Database.Open in histograms.xml.
const char kValueStoreDatabaseUMAClientName[] = "WebAppsLockScreen";

}  // namespace

// Helper class for running blocking tasks on a thread pool.
class LockScreenStorageHelper {
 public:
  LockScreenStorageHelper();
  ~LockScreenStorageHelper() = default;

  void Init(const base::FilePath& base_path);
  std::vector<std::string> GetKeys(const url::Origin& origin);
  bool SetData(const url::Origin& origin,
               const std::string& key,
               const std::string& data);

 private:
  ValueStore* GetValueStoreForOrigin(const url::Origin& origin);

  scoped_refptr<value_store::ValueStoreFactory> value_store_factory_;
  // Maps storage directory filename to ValueStore for a particular origin.
  // TODO(crbug.com/40204655): If there can only be one lock screen app at a
  // time, this does not need to be a map. Otherwise, there should be a clean
  // way of evicting value stores databases from this cache.
  std::map<std::string, std::unique_ptr<ValueStore>> storage_map_;
};

LockScreenStorageHelper::LockScreenStorageHelper() {}

void LockScreenStorageHelper::Init(const base::FilePath& base_path) {
  value_store_factory_ =
      base::MakeRefCounted<value_store::ValueStoreFactoryImpl>(base_path);
}

std::vector<std::string> LockScreenStorageHelper::GetKeys(
    const url::Origin& origin) {
  ValueStore* value_store = GetValueStoreForOrigin(origin);
  ValueStore::ReadResult read = value_store->Get();
  std::vector<std::string> result;
  if (!read.status().ok())
    return result;
  for (auto kv : read.settings()) {
    result.push_back(kv.first);
  }

  return result;
}

bool LockScreenStorageHelper::SetData(const url::Origin& origin,
                                      const std::string& key,
                                      const std::string& data) {
  ValueStore* value_store = GetValueStoreForOrigin(origin);
  ValueStore::WriteResult write =
      value_store->Set(ValueStore::DEFAULTS, key, base::Value(data));
  return write.status().ok();
}

ValueStore* LockScreenStorageHelper::GetValueStoreForOrigin(
    const url::Origin& origin) {
  DCHECK(!origin.opaque());

  // ValueStore will create a directory for storing its data. The directory name
  // is passed in. We want to key data by origin, so we use a hash of the origin
  // as the directory name under which to store the data. Origin.Serialize()
  // should just concatenate the scheme/host/port, which are the components that
  // need to appear identical if the two origins need to compare equal. Hence
  // if two origins are equal, the serialized origins should also be equal.
  std::string serialized_origin = origin.Serialize();
  uint8_t hash[crypto::kSHA256Length];
  crypto::SHA256HashString(serialized_origin, hash, sizeof(hash));
  std::string filename = base::HexEncode(hash);

  auto iter = storage_map_.find(filename);
  if (iter != storage_map_.end())
    return iter->second.get();

  base::FilePath value_store_path(filename);
  std::unique_ptr<ValueStore> value_store =
      value_store_factory_->CreateValueStore(value_store_path,
                                             kValueStoreDatabaseUMAClientName);
  ValueStore* result = value_store.get();
  storage_map_.emplace(filename, std::move(value_store));
  return result;
}

// static
LockScreenStorage* LockScreenStorage::GetInstance() {
  return LockScreenStorageImpl::GetInstance();
}

// static
LockScreenStorageImpl* LockScreenStorageImpl::GetInstance() {
  return base::Singleton<
      LockScreenStorageImpl,
      base::LeakySingletonTraits<LockScreenStorageImpl>>::get();
}

LockScreenStorageImpl::LockScreenStorageImpl()
    : helper_(base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})) {
}

LockScreenStorageImpl::~LockScreenStorageImpl() = default;

void LockScreenStorageImpl::Init(content::BrowserContext* browser_context,
                                 const base::FilePath& base_path) {
  DCHECK(!browser_context_);
  DCHECK(!browser_context->IsOffTheRecord());
  browser_context_ = browser_context;
  helper_.AsyncCall(&LockScreenStorageHelper::Init).WithArgs(base_path);
}

void LockScreenStorageImpl::GetKeys(
    const url::Origin& origin,
    blink::mojom::LockScreenService::GetKeysCallback callback) {
  helper_.AsyncCall(&LockScreenStorageHelper::GetKeys)
      .WithArgs(origin)
      .Then(base::BindOnce(&LockScreenStorageImpl::OnGetKeys,
                           weak_factory_.GetWeakPtr(), std::move(callback)));
}

void LockScreenStorageImpl::SetData(
    const url::Origin& origin,
    const std::string& key,
    const std::string& data,
    blink::mojom::LockScreenService::SetDataCallback callback) {
  helper_.AsyncCall(&LockScreenStorageHelper::SetData)
      .WithArgs(origin, key, data)
      .Then(base::BindOnce(&LockScreenStorageImpl::OnSetData,
                           weak_factory_.GetWeakPtr(), std::move(callback)));
}

bool LockScreenStorageImpl::IsAllowedBrowserContext(
    content::BrowserContext* browser_context) {
  return browser_context == browser_context_;
}

void LockScreenStorageImpl::OnGetKeys(
    blink::mojom::LockScreenService::GetKeysCallback callback,
    const std::vector<std::string>& result) {
  std::move(callback).Run(result);
}

void LockScreenStorageImpl::OnSetData(
    blink::mojom::LockScreenService::SetDataCallback callback,
    bool success) {
  if (success) {
    std::move(callback).Run(blink::mojom::LockScreenServiceStatus::kSuccess);
  } else {
    std::move(callback).Run(blink::mojom::LockScreenServiceStatus::kWriteError);
  }
}

void LockScreenStorageImpl::InitForTesting(
    content::BrowserContext* browser_context,
    const base::FilePath& base_path) {
  browser_context_ = nullptr;
  Init(browser_context, base_path);
}

}  // namespace content