chromium/chrome/browser/metrics/structured/key_data_provider_ash.cc

// Copyright 2023 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/metrics/structured/key_data_provider_ash.h"

#include "chrome/browser/profiles/profile_manager.h"
#include "components/metrics/structured/key_data_provider_file.h"
#include "components/metrics/structured/structured_metrics_validator.h"

namespace metrics::structured {
namespace {

// Default delay period for the PersistentProto. This is the delay before a file
// write is triggered after a change has been made.
constexpr base::TimeDelta kSaveDelay = base::Milliseconds(1000);

// The path used to store per-profile keys. Relative to the user's
// cryptohome. This file is created by chromium.
constexpr char kProfileKeyPath[] = "structured_metrics/keys";

// The path used to store per-device keys. This file is created by tmpfiles.d
// on start and has its permissions and ownership set such that it is writable
// by chronos.
constexpr char kDeviceKeyPath[] = "/var/lib/metrics/structured/chromium/keys";

}  // namespace

KeyDataProviderAsh::KeyDataProviderAsh()
    : KeyDataProviderAsh(base::FilePath(kDeviceKeyPath), kSaveDelay) {}

KeyDataProviderAsh::KeyDataProviderAsh(const base::FilePath& device_key_path,
                                       base::TimeDelta write_delay)
    : device_key_path_(device_key_path), write_delay_(write_delay) {
  device_key_ =
      std::make_unique<KeyDataProviderFile>(device_key_path_, write_delay_);
  device_key_->AddObserver(this);
}

KeyDataProviderAsh::~KeyDataProviderAsh() {
  device_key_->RemoveObserver(this);
  if (profile_key_) {
    profile_key_->RemoveObserver(this);
  }
}

bool KeyDataProviderAsh::IsReady() {
  DCHECK(device_key_);
  return device_key_->IsReady();
}

std::optional<uint64_t> KeyDataProviderAsh::GetId(
    const std::string& project_name) {
  KeyDataProvider* key_data_provider = GetKeyDataProvider(project_name);
  if (!key_data_provider) {
    return std::nullopt;
  }
  return key_data_provider->GetId(project_name);
}

std::optional<uint64_t> KeyDataProviderAsh::GetSecondaryId(
    const std::string& project_name) {
  const auto* project_validator =
      validator::Validators::Get()->GetProjectValidator(project_name);
  if (!project_validator) {
    return std::nullopt;
  }

  // If |project_name| is not of type sequence, return std::nullopt as it
  // should not have a corresponding secondary ID.
  if (project_validator->event_type() != StructuredEventProto::SEQUENCE) {
    return std::nullopt;
  }

  DCHECK(device_key_);
  if (device_key_->IsReady()) {
    return device_key_->GetId(project_name);
  }

  return std::nullopt;
}

KeyData* KeyDataProviderAsh::GetKeyData(const std::string& project_name) {
  auto* key_data_provider = GetKeyDataProvider(project_name);
  if (!key_data_provider || !key_data_provider->IsReady()) {
    return nullptr;
  }

  return key_data_provider->GetKeyData(project_name);
}

void KeyDataProviderAsh::Purge() {
  if (device_key_) {
    device_key_->Purge();
  }

  if (profile_key_) {
    profile_key_->Purge();
  }
}

void KeyDataProviderAsh::OnKeyReady() {
  NotifyKeyReady();
}

void KeyDataProviderAsh::ProfileAdded(const Profile& profile) {
  // Only the primary user's keys should be loaded. If there is already is a
  // profile key, no-op.
  if (profile_key_) {
    return;
  }

  const base::FilePath& profile_path = profile.GetPath();

  profile_key_ = std::make_unique<KeyDataProviderFile>(
      profile_path.Append(kProfileKeyPath), write_delay_);
  profile_key_->AddObserver(this);
}

KeyDataProvider* KeyDataProviderAsh::GetKeyDataProvider(
    const std::string& project_name) {
  const auto* project_validator =
      validator::Validators::Get()->GetProjectValidator(project_name);
  if (!project_validator) {
    return nullptr;
  }

  switch (project_validator->id_scope()) {
    case IdScope::kPerProfile: {
      if (profile_key_) {
        return profile_key_.get();
      }
      break;
    }
    case IdScope::kPerDevice: {
      // Retrieve the profile key if the type is a sequence.
      if (project_validator->event_type() == StructuredEventProto::SEQUENCE) {
        return profile_key_ ? profile_key_.get() : nullptr;
      }
      if (device_key_) {
        return device_key_.get();
      }
      break;
    }
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  return nullptr;
}

}  // namespace metrics::structured