blob: b857baf57068163adeab250ebbffd17ab780b941 [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/ash/cert_provisioning/cert_provisioning_invalidator.h"
#include <utility>
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/ash/cert_provisioning/cert_provisioning_common.h"
#include "chrome/browser/invalidation/profile_invalidation_provider_factory.h"
#include "components/invalidation/impl/profile_invalidation_provider.h"
#include "components/invalidation/public/invalidator_state.h"
#include "components/invalidation/public/topic_invalidation_map.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
namespace ash {
namespace cert_provisioning {
namespace {
// Topics that start with this prefix are considered to be "public" FCM topics.
// This allows us to migrate to "private" FCM topics (which would get a
// different prefix) server-side without client-side changes.
constexpr char kFcmCertProvisioningPublicTopicPrefix[] = "cert-";
// Shall be expanded to cert.[scope].[topic]
const char* kOwnerNameFormat = "cert.%s.%s";
const char* CertScopeToString(CertScope scope) {
switch (scope) {
case CertScope::kUser:
return "user";
case CertScope::kDevice:
return "device";
}
NOTREACHED() << "Unknown cert scope: " << static_cast<int>(scope);
return "";
}
} // namespace
//=============== CertProvisioningInvalidationHandler ==========================
namespace internal {
// static
std::unique_ptr<CertProvisioningInvalidationHandler>
CertProvisioningInvalidationHandler::BuildAndRegister(
CertScope scope,
invalidation::InvalidationService* invalidation_service,
const invalidation::Topic& topic,
OnInvalidationCallback on_invalidation_callback) {
auto invalidator = std::make_unique<CertProvisioningInvalidationHandler>(
scope, invalidation_service, topic, std::move(on_invalidation_callback));
if (!invalidator->Register()) {
return nullptr;
}
return invalidator;
}
CertProvisioningInvalidationHandler::CertProvisioningInvalidationHandler(
CertScope scope,
invalidation::InvalidationService* invalidation_service,
const invalidation::Topic& topic,
OnInvalidationCallback on_invalidation_callback)
: scope_(scope),
invalidation_service_(invalidation_service),
topic_(topic),
on_invalidation_callback_(std::move(on_invalidation_callback)) {
DCHECK(invalidation_service_);
DCHECK(!on_invalidation_callback_.is_null());
}
CertProvisioningInvalidationHandler::~CertProvisioningInvalidationHandler() {
// Unregister is not called here so that subscription can be preserved if
// browser restarts. If subscription is not needed a user must call Unregister
// explicitly.
}
bool CertProvisioningInvalidationHandler::Register() {
if (state_.is_registered) {
return true;
}
OnInvalidatorStateChange(invalidation_service_->GetInvalidatorState());
invalidation_service_observation_.Observe(invalidation_service_);
if (!invalidation_service_->UpdateInterestedTopics(this,
/*topics=*/{topic_})) {
LOG(WARNING) << "Failed to register with topic " << topic_;
return false;
}
state_.is_registered = true;
return true;
}
void CertProvisioningInvalidationHandler::Unregister() {
if (!state_.is_registered) {
return;
}
// Assuming that updating invalidator's topics with empty set can never fail
// since failure is only possible non-empty set with topic associated with
// some other invalidator.
const bool topics_reset = invalidation_service_->UpdateInterestedTopics(
this, invalidation::TopicSet());
DCHECK(topics_reset);
DCHECK(invalidation_service_observation_.IsObservingSource(
invalidation_service_));
invalidation_service_observation_.Reset();
state_.is_registered = false;
}
void CertProvisioningInvalidationHandler::OnInvalidatorStateChange(
invalidation::InvalidatorState state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
state_.is_invalidation_service_enabled =
state == invalidation::INVALIDATIONS_ENABLED;
}
void CertProvisioningInvalidationHandler::OnIncomingInvalidation(
const invalidation::TopicInvalidationMap& invalidation_map) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!state_.is_invalidation_service_enabled) {
LOG(WARNING) << "Unexpected invalidation received.";
}
const invalidation::SingleTopicInvalidationSet& list =
invalidation_map.ForTopic(topic_);
if (list.IsEmpty()) {
NOTREACHED() << "Incoming invlaidation does not contain invalidation"
" for certificate topic";
return;
}
for (const auto& it : list) {
it.Acknowledge();
}
on_invalidation_callback_.Run();
}
std::string CertProvisioningInvalidationHandler::GetOwnerName() const {
return base::StringPrintf(kOwnerNameFormat, CertScopeToString(scope_),
topic_.c_str());
}
bool CertProvisioningInvalidationHandler::IsPublicTopic(
const invalidation::Topic& topic) const {
return base::StartsWith(topic, kFcmCertProvisioningPublicTopicPrefix,
base::CompareCase::SENSITIVE);
}
} // namespace internal
//=============== CertProvisioningUserInvalidator ==============================
CertProvisioningInvalidator::CertProvisioningInvalidator() = default;
CertProvisioningInvalidator::~CertProvisioningInvalidator() = default;
void CertProvisioningInvalidator::Unregister() {
if (invalidation_handler_) {
invalidation_handler_->Unregister();
invalidation_handler_.reset();
}
}
//=============== CertProvisioningUserInvalidatorFactory =======================
CertProvisioningUserInvalidatorFactory::CertProvisioningUserInvalidatorFactory(
Profile* profile)
: profile_(profile) {
DCHECK(profile_);
}
std::unique_ptr<CertProvisioningInvalidator>
CertProvisioningUserInvalidatorFactory::Create() {
return std::make_unique<CertProvisioningUserInvalidator>(profile_);
}
//=============== CertProvisioningUserInvalidator ==============================
CertProvisioningUserInvalidator::CertProvisioningUserInvalidator(
Profile* profile)
: profile_(profile) {
DCHECK(profile_);
}
void CertProvisioningUserInvalidator::Register(
const invalidation::Topic& topic,
OnInvalidationCallback on_invalidation_callback) {
invalidation::ProfileInvalidationProvider* invalidation_provider =
invalidation::ProfileInvalidationProviderFactory::GetForProfile(profile_);
DCHECK(invalidation_provider);
invalidation::InvalidationService* invalidation_service =
invalidation_provider->GetInvalidationServiceForCustomSender(
policy::kPolicyFCMInvalidationSenderID);
DCHECK(invalidation_service);
invalidation_handler_ =
internal::CertProvisioningInvalidationHandler::BuildAndRegister(
CertScope::kUser, invalidation_service, topic,
std::move(on_invalidation_callback));
if (!invalidation_handler_) {
LOG(ERROR) << "Failed to register for invalidation topic";
}
}
//=============== CertProvisioningDeviceInvalidatorFactory =====================
CertProvisioningDeviceInvalidatorFactory::
CertProvisioningDeviceInvalidatorFactory(
policy::AffiliatedInvalidationServiceProvider* service_provider)
: service_provider_(service_provider) {
DCHECK(service_provider_);
}
std::unique_ptr<CertProvisioningInvalidator>
CertProvisioningDeviceInvalidatorFactory::Create() {
return std::make_unique<CertProvisioningDeviceInvalidator>(service_provider_);
}
//=============== CertProvisioningDeviceInvalidator ============================
CertProvisioningDeviceInvalidator::CertProvisioningDeviceInvalidator(
policy::AffiliatedInvalidationServiceProvider* service_provider)
: service_provider_(service_provider) {
DCHECK(service_provider_);
}
CertProvisioningDeviceInvalidator::~CertProvisioningDeviceInvalidator() {
service_provider_->UnregisterConsumer(this);
}
void CertProvisioningDeviceInvalidator::Register(
const invalidation::Topic& topic,
OnInvalidationCallback on_invalidation_callback) {
topic_ = topic;
on_invalidation_callback_ = std::move(on_invalidation_callback);
service_provider_->RegisterConsumer(this);
}
void CertProvisioningDeviceInvalidator::Unregister() {
service_provider_->UnregisterConsumer(this);
CertProvisioningInvalidator::Unregister();
}
void CertProvisioningDeviceInvalidator::OnInvalidationServiceSet(
invalidation::InvalidationService* invalidation_service) {
if (!invalidation_service) {
invalidation_handler_.reset();
return;
}
invalidation_handler_ =
internal::CertProvisioningInvalidationHandler::BuildAndRegister(
CertScope::kDevice, invalidation_service, topic_,
on_invalidation_callback_);
if (!invalidation_handler_) {
LOG(ERROR) << "Failed to register for invalidation topic";
}
}
} // namespace cert_provisioning
} // namespace ash