blob: 8f2a1cdf07dd221666acaae0dae4a99af0036b0d [file] [log] [blame]
// Copyright 2015 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 "chromeos/services/device_sync/cryptauth_gcm_manager_impl.h"
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "chromeos/services/device_sync/pref_names.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/services/device_sync/public/cpp/gcm_constants.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/prefs/pref_service.h"
namespace chromeos {
namespace device_sync {
namespace {
// The 'registrationTickleType' key-value pair is present in GCM push
// messages. The values correspond to a server-side enum.
const char kRegistrationTickleTypeKey[] = "registrationTickleType";
const char kRegistrationTickleTypeForceEnrollment[] = "1";
const char kRegistrationTickleTypeUpdateEnrollment[] = "2";
const char kRegistrationTickleTypeDevicesSync[] = "3";
// Used in GCM messages sent by CryptAuth v2 DeviceSync. The value corresponding
// to this key specifies the service to notify, 1 for Enrollment and 2 for
// DeviceSync, as enumerated in cryptauthv2::TargetService.
const char kTargetServiceKey[] = "S";
// Only used in GCM messages sent by CryptAuth v2 DeviceSync. The session_id
// field of ClientAppMetadata should be set to the value corresponding to this
// key.
const char kSessionIdKey[] = "I";
// Used in GCM messages triggered by a BatchNofityGroupDevices request. The
// value corresponding to this key is the feature_type field forwarded from the
// BatchNotifyGroupDevicesRequest.
const char kFeatureTypeKey[] = "F";
// Only used in GCM messages sent by CryptAuth v2 DeviceSync. The value
// corresponding to this key specifies the relevant DeviceSync group. Currently,
// the value should always be "DeviceSync:BetterTogether".
const char kDeviceSyncGroupNameKey[] = "K";
// Determine the target service based on the keys "registrationTickleType" and
// "S". In practice, one and only one of these keys should exist in a GCM
// message. Return null if neither is set to a valid value. If both are set for
// some reason, arbitrarily prefer a valid "S" value.
base::Optional<cryptauthv2::TargetService> TargetServiceFromMessage(
const gcm::IncomingMessage& message) {
base::Optional<cryptauthv2::TargetService>
target_from_registration_tickle_type;
base::Optional<cryptauthv2::TargetService> target_from_target_service;
auto it = message.data.find(kRegistrationTickleTypeKey);
if (it != message.data.end()) {
if (it->second == kRegistrationTickleTypeForceEnrollment ||
it->second == kRegistrationTickleTypeUpdateEnrollment) {
target_from_registration_tickle_type =
cryptauthv2::TargetService::ENROLLMENT;
} else if (it->second == kRegistrationTickleTypeDevicesSync) {
target_from_registration_tickle_type =
cryptauthv2::TargetService::DEVICE_SYNC;
} else {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(WARNING) << "Unknown tickle type in GCM message: " << it->second;
}
}
it = message.data.find(kTargetServiceKey);
if (it != message.data.end()) {
if (it->second ==
base::NumberToString(cryptauthv2::TargetService::ENROLLMENT)) {
target_from_target_service = cryptauthv2::TargetService::ENROLLMENT;
} else if (it->second ==
base::NumberToString(cryptauthv2::TargetService::DEVICE_SYNC)) {
target_from_target_service = cryptauthv2::TargetService::DEVICE_SYNC;
} else {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(WARNING) << "Invalid TargetService in GCM message: " << it->second;
}
}
if (target_from_registration_tickle_type && target_from_target_service) {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(WARNING) << "Registration tickle type, "
<< *target_from_registration_tickle_type
<< ", and target service, " << *target_from_target_service
<< ", are both set in the same GCM message";
}
return target_from_target_service ? target_from_target_service
: target_from_registration_tickle_type;
}
// Returns null if |key| doesn't exist in the |message.data| map.
base::Optional<std::string> StringValueFromMessage(
const std::string& key,
const gcm::IncomingMessage& message) {
auto it = message.data.find(key);
if (it == message.data.end())
return base::nullopt;
return it->second;
}
// Returns null if |message| does not contain the feature type key-value pair or
// if the value does not correspond to one of the CryptAuthFeatureType enums.
base::Optional<CryptAuthFeatureType> FeatureTypeFromMessage(
const gcm::IncomingMessage& message) {
base::Optional<std::string> feature_type_string =
StringValueFromMessage(kFeatureTypeKey, message);
if (!feature_type_string)
return base::nullopt;
base::Optional<CryptAuthFeatureType> feature_type =
CryptAuthFeatureTypeFromString(*feature_type_string);
if (!feature_type) {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(WARNING) << "GCM message contains unknown feature type: "
<< *feature_type_string;
}
return feature_type;
}
// If the DeviceSync group name is provided in the GCM message, verify that the
// value agrees with the name of the corresponding enrolled key. On Chrome OS,
// the only relevant DeviceSync group name is "DeviceSync:BetterTogether".
bool IsDeviceSyncGroupNameValid(const gcm::IncomingMessage& message) {
base::Optional<std::string> group_name =
StringValueFromMessage(kDeviceSyncGroupNameKey, message);
return !group_name ||
*group_name ==
CryptAuthKeyBundle::KeyBundleNameEnumToString(
CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether);
}
} // namespace
// static
CryptAuthGCMManagerImpl::Factory*
CryptAuthGCMManagerImpl::Factory::factory_instance_ = nullptr;
// static
std::unique_ptr<CryptAuthGCMManager>
CryptAuthGCMManagerImpl::Factory::NewInstance(gcm::GCMDriver* gcm_driver,
PrefService* pref_service) {
if (!factory_instance_)
factory_instance_ = new Factory();
return factory_instance_->BuildInstance(gcm_driver, pref_service);
}
// static
void CryptAuthGCMManagerImpl::Factory::SetInstanceForTesting(Factory* factory) {
factory_instance_ = factory;
}
CryptAuthGCMManagerImpl::Factory::~Factory() = default;
std::unique_ptr<CryptAuthGCMManager>
CryptAuthGCMManagerImpl::Factory::BuildInstance(gcm::GCMDriver* gcm_driver,
PrefService* pref_service) {
return base::WrapUnique(
new CryptAuthGCMManagerImpl(gcm_driver, pref_service));
}
CryptAuthGCMManagerImpl::CryptAuthGCMManagerImpl(gcm::GCMDriver* gcm_driver,
PrefService* pref_service)
: gcm_driver_(gcm_driver),
pref_service_(pref_service),
registration_in_progress_(false),
weak_ptr_factory_(this) {}
CryptAuthGCMManagerImpl::~CryptAuthGCMManagerImpl() {
if (gcm_driver_->GetAppHandler(kCryptAuthGcmAppId) == this)
gcm_driver_->RemoveAppHandler(kCryptAuthGcmAppId);
}
void CryptAuthGCMManagerImpl::StartListening() {
if (gcm_driver_->GetAppHandler(kCryptAuthGcmAppId) == this) {
PA_LOG(VERBOSE) << "GCM app handler already added";
return;
}
gcm_driver_->AddAppHandler(kCryptAuthGcmAppId, this);
}
void CryptAuthGCMManagerImpl::RegisterWithGCM() {
if (registration_in_progress_) {
PA_LOG(VERBOSE) << "GCM Registration is already in progress";
return;
}
PA_LOG(VERBOSE) << "Beginning GCM registration...";
registration_in_progress_ = true;
std::vector<std::string> sender_ids(1, kCryptAuthGcmSenderId);
gcm_driver_->Register(
kCryptAuthGcmAppId, sender_ids,
base::Bind(&CryptAuthGCMManagerImpl::OnRegistrationCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
std::string CryptAuthGCMManagerImpl::GetRegistrationId() {
return pref_service_->GetString(prefs::kCryptAuthGCMRegistrationId);
}
void CryptAuthGCMManagerImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void CryptAuthGCMManagerImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void CryptAuthGCMManagerImpl::ShutdownHandler() {}
void CryptAuthGCMManagerImpl::OnStoreReset() {
// We will automatically re-register to GCM and re-enroll the new registration
// ID to Cryptauth during the next scheduled sync.
pref_service_->ClearPref(prefs::kCryptAuthGCMRegistrationId);
}
void CryptAuthGCMManagerImpl::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
std::vector<std::string> fields;
for (const auto& kv : message.data)
fields.push_back(std::string(kv.first) + ": " + std::string(kv.second));
PA_LOG(VERBOSE) << "GCM message received:\n"
<< " sender_id: " << message.sender_id << "\n"
<< " collapse_key: " << message.collapse_key << "\n"
<< " data:\n " << base::JoinString(fields, "\n ");
base::Optional<cryptauthv2::TargetService> target_service =
TargetServiceFromMessage(message);
if (!target_service) {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(ERROR) << "GCM message does not specify a valid target service.";
return;
}
if (!IsDeviceSyncGroupNameValid(message)) {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(ERROR) << "GCM message contains unexpected DeviceSync group name: "
<< *StringValueFromMessage(kDeviceSyncGroupNameKey, message);
return;
}
base::Optional<std::string> session_id =
StringValueFromMessage(kSessionIdKey, message);
base::Optional<CryptAuthFeatureType> feature_type =
FeatureTypeFromMessage(message);
if (target_service == cryptauthv2::TargetService::ENROLLMENT) {
for (auto& observer : observers_)
observer.OnReenrollMessage(session_id, feature_type);
return;
}
DCHECK(target_service == cryptauthv2::TargetService::DEVICE_SYNC);
for (auto& observer : observers_)
observer.OnResyncMessage(session_id, feature_type);
}
void CryptAuthGCMManagerImpl::OnMessagesDeleted(const std::string& app_id) {}
void CryptAuthGCMManagerImpl::OnSendError(
const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& details) {
NOTREACHED();
}
void CryptAuthGCMManagerImpl::OnSendAcknowledged(
const std::string& app_id,
const std::string& message_id) {
NOTREACHED();
}
void CryptAuthGCMManagerImpl::OnRegistrationCompleted(
const std::string& registration_id,
gcm::GCMClient::Result result) {
registration_in_progress_ = false;
if (result != gcm::GCMClient::SUCCESS) {
// TODO(https://crbug.com/956592): Add metrics.
PA_LOG(WARNING) << "GCM registration failed with result="
<< static_cast<int>(result);
for (auto& observer : observers_)
observer.OnGCMRegistrationResult(false);
return;
}
PA_LOG(VERBOSE) << "GCM registration success, registration_id="
<< registration_id;
pref_service_->SetString(prefs::kCryptAuthGCMRegistrationId, registration_id);
for (auto& observer : observers_)
observer.OnGCMRegistrationResult(true);
}
} // namespace device_sync
} // namespace chromeos