// 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 "chromeos/ash/components/nearby/presence/credentials/local_device_data_provider_impl.h"
#include "base/base64url.h"
#include "base/containers/contains.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chromeos/ash/components/nearby/presence/conversions/proto_conversions.h"
#include "chromeos/ash/components/nearby/presence/credentials/prefs.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/prefs/pref_service.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "crypto/random.h"
#include "google_apis/gaia/gaia_auth_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/chromeos/devicetype_utils.h"
namespace {
// The device ID stored in `::nearby::internal::DeviceIdentityMetaData`
// is a 128-bit integer, represented by a byte (8-bits) array.
// This length of 16 is thus derived by dividing 128 by 8.
const size_t kDeviceIdLength = 16;
std::string GenerateDeviceId() {
std::vector<uint8_t> device_id_bytes =
crypto::RandBytesAsVector(kDeviceIdLength);
return std::string(device_id_bytes.begin(), device_id_bytes.end());
}
} // namespace
namespace ash::nearby::presence {
LocalDeviceDataProviderImpl::LocalDeviceDataProviderImpl(
PrefService* pref_service,
signin::IdentityManager* identity_manager)
: pref_service_(pref_service), identity_manager_(identity_manager) {
CHECK(identity_manager_);
CHECK(pref_service_);
}
LocalDeviceDataProviderImpl::~LocalDeviceDataProviderImpl() = default;
void LocalDeviceDataProviderImpl::UpdatePersistedSharedCredentials(
const std::vector<::nearby::internal::SharedCredential>&
new_shared_credentials) {
base::Value::List list;
for (const auto& credential : new_shared_credentials) {
list.Append(base::NumberToString(credential.id()));
}
pref_service_->SetList(prefs::kNearbyPresenceSharedCredentialIdListPrefName,
std::move(list));
}
bool LocalDeviceDataProviderImpl::HaveSharedCredentialsChanged(
const std::vector<::nearby::internal::SharedCredential>&
new_shared_credentials) {
std::set<std::string> persisted_shared_credential_ids;
const base::Value::List& list = pref_service_->GetList(
prefs::kNearbyPresenceSharedCredentialIdListPrefName);
for (const auto& id : list) {
persisted_shared_credential_ids.insert(id.GetString());
}
std::set<std::string> new_shared_credential_ids;
for (const auto& credential : new_shared_credentials) {
new_shared_credential_ids.insert(base::NumberToString(credential.id()));
}
return new_shared_credential_ids != persisted_shared_credential_ids;
}
std::string LocalDeviceDataProviderImpl::GetDeviceId() {
auto decoded_device_id = FetchAndDecodeDeviceId();
if (!decoded_device_id) {
decoded_device_id = GenerateDeviceId();
EncodeAndPersistDeviceId(decoded_device_id.value());
}
return decoded_device_id.value();
}
::nearby::internal::DeviceIdentityMetaData
LocalDeviceDataProviderImpl::GetDeviceMetadata() {
std::string user_name =
pref_service_->GetString(prefs::kNearbyPresenceUserNamePrefName);
std::string profile_url =
pref_service_->GetString(prefs::kNearbyPresenceProfileUrlPrefName);
// At this point in the Nearby Presence flow, if the `user_name` and
// `profile_url` are not available, something is wrong. The `user_name` and
// `profile_url` are persisted to prefs during first time registration flow,
// which happens before the Device Metadata is needed to construct
// credentials.
CHECK(!user_name.empty());
CHECK(!profile_url.empty());
// `mac_address` is empty for Nearby Presence MVP on ChromeOS since
// broadcasting is not supported.
return proto::BuildMetadata(
/*device_type=*/::nearby::internal::DeviceType::DEVICE_TYPE_CHROMEOS,
/*device_name=*/GetDeviceName(),
/*mac_address=*/std::string(),
/*device_id=*/GetDeviceId());
}
std::string LocalDeviceDataProviderImpl::GetAccountName() {
const std::string& email =
identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.email;
return gaia::CanonicalizeEmail(email);
}
void LocalDeviceDataProviderImpl::SaveUserRegistrationInfo(
const std::string& display_name,
const std::string& image_url) {
pref_service_->SetString(prefs::kNearbyPresenceUserNamePrefName,
display_name);
pref_service_->SetString(prefs::kNearbyPresenceProfileUrlPrefName, image_url);
}
bool LocalDeviceDataProviderImpl::IsRegistrationCompleteAndUserInfoSaved() {
// The user name pref and image url are set during first time registration
// flow with the server. If they are not set, that means that the first time
// registration flow (and therefore Nearby Presence initialization) has not
// occurred. These fields are both set in the same step via
// |SaveUserRegistrationInfo|. Additionally, check for the full
// registration flow to be completed.
std::string user_name =
pref_service_->GetString(prefs::kNearbyPresenceUserNamePrefName);
std::string image_url =
pref_service_->GetString(prefs::kNearbyPresenceProfileUrlPrefName);
bool registration_complete = pref_service_->GetBoolean(
prefs::kNearbyPresenceFirstTimeRegistrationComplete);
return (!user_name.empty() && !image_url.empty()) && registration_complete;
}
void LocalDeviceDataProviderImpl::SetRegistrationComplete(bool completed) {
pref_service_->SetBoolean(prefs::kNearbyPresenceFirstTimeRegistrationComplete,
completed);
}
std::string LocalDeviceDataProviderImpl::GetDeviceName() const {
// TODO(b/283987579): When NP Settings page is implemented, check for any
// changes to the user set device name.
std::u16string device_type = ui::GetChromeOSDeviceName();
const CoreAccountInfo account_info =
identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
std::string given_name =
identity_manager_->FindExtendedAccountInfo(account_info).given_name;
if (given_name.empty()) {
return base::UTF16ToUTF8(device_type);
}
std::string device_name =
l10n_util::GetStringFUTF8(IDS_NEARBY_PRESENCE_DEVICE_NAME,
base::UTF8ToUTF16(given_name), device_type);
return device_name;
}
std::optional<std::string>
LocalDeviceDataProviderImpl::FetchAndDecodeDeviceId() {
std::string encoded_device_id =
pref_service_->GetString(prefs::kNearbyPresenceDeviceIdPrefName);
if (encoded_device_id.empty()) {
return std::nullopt;
}
std::string decoded_device_id;
if (!base::Base64UrlDecode(encoded_device_id,
base::Base64UrlDecodePolicy::REQUIRE_PADDING,
&decoded_device_id)) {
return std::nullopt;
}
return decoded_device_id;
}
void LocalDeviceDataProviderImpl::EncodeAndPersistDeviceId(
std::string raw_device_id_bytes) {
std::string encoded_device_id;
base::Base64UrlEncode(raw_device_id_bytes,
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&encoded_device_id);
pref_service_->SetString(prefs::kNearbyPresenceDeviceIdPrefName,
encoded_device_id);
}
} // namespace ash::nearby::presence