blob: 20fee7fee69621ef76fe53ea193f2a21fd9ff433 [file] [log] [blame]
// 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 "components/data_sharing/internal/data_sharing_service_impl.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "base/task/single_thread_task_runner.h"
#include "base/version_info/channel.h"
#include "components/data_sharing/internal/collaboration_group_sync_bridge.h"
#include "components/data_sharing/internal/data_sharing_network_loader_impl.h"
#include "components/data_sharing/internal/group_data_proto_utils.h"
#include "components/data_sharing/public/data_sharing_sdk_delegate.h"
#include "components/data_sharing/public/data_sharing_service.h"
#include "components/data_sharing/public/features.h"
#include "components/data_sharing/public/group_data.h"
#include "components/data_sharing/public/protocol/data_sharing_sdk.pb.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/report_unrecoverable_error.h"
#include "components/sync/model/client_tag_based_data_type_processor.h"
#include "components/sync/model/data_type_store.h"
#include "components/sync/model/data_type_sync_bridge.h"
#include "net/base/url_util.h"
#include "third_party/abseil-cpp/absl/status/status.h"
namespace data_sharing {
namespace {
const char kGroupIdKey[] = "group_id";
const char kTokenBlobKey[] = "token_blob";
// Should not be called with kOk StatusCode, unless SDK delegate misbehaves by
// passing it as an error value.
DataSharingService::PeopleGroupActionFailure StatusToPeopleGroupActionFailure(
const absl::Status& status) {
switch (status.code()) {
case absl::StatusCode::kOk:
// Not expected here, treat as a persistent failure.
return DataSharingService::PeopleGroupActionFailure::kPersistentFailure;
case absl::StatusCode::kCancelled:
case absl::StatusCode::kUnknown:
case absl::StatusCode::kDeadlineExceeded:
case absl::StatusCode::kResourceExhausted:
case absl::StatusCode::kAborted:
case absl::StatusCode::kInternal:
case absl::StatusCode::kUnavailable:
return DataSharingService::PeopleGroupActionFailure::kTransientFailure;
case absl::StatusCode::kNotFound:
case absl::StatusCode::kAlreadyExists:
case absl::StatusCode::kPermissionDenied:
case absl::StatusCode::kFailedPrecondition:
case absl::StatusCode::kOutOfRange:
case absl::StatusCode::kUnimplemented:
case absl::StatusCode::kDataLoss:
case absl::StatusCode::kUnauthenticated:
return DataSharingService::PeopleGroupActionFailure::kPersistentFailure;
default:
// absl::StatusCode should always have "default:" in `switch()`.
return DataSharingService::PeopleGroupActionFailure::kPersistentFailure;
}
}
DataSharingService::PeopleGroupActionOutcome StatusToPeopleGroupActionOutcome(
const absl::Status& status) {
if (status.ok()) {
return DataSharingService::PeopleGroupActionOutcome::kSuccess;
}
switch (StatusToPeopleGroupActionFailure(status)) {
case DataSharingService::PeopleGroupActionFailure::kUnknown:
return DataSharingService::PeopleGroupActionOutcome::kUnknown;
case DataSharingService::PeopleGroupActionFailure::kPersistentFailure:
return DataSharingService::PeopleGroupActionOutcome::kPersistentFailure;
case DataSharingService::PeopleGroupActionFailure::kTransientFailure:
return DataSharingService::PeopleGroupActionOutcome::kTransientFailure;
}
}
} // namespace
DataSharingServiceImpl::DataSharingServiceImpl(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
signin::IdentityManager* identity_manager,
syncer::OnceDataTypeStoreFactory data_type_store_factory,
version_info::Channel channel,
std::unique_ptr<DataSharingSDKDelegate> sdk_delegate,
std::unique_ptr<DataSharingUIDelegate> ui_delegate)
: data_sharing_network_loader_(
std::make_unique<DataSharingNetworkLoaderImpl>(url_loader_factory,
identity_manager)),
sdk_delegate_(std::move(sdk_delegate)),
ui_delegate_(std::move(ui_delegate)) {
auto change_processor =
std::make_unique<syncer::ClientTagBasedDataTypeProcessor>(
syncer::COLLABORATION_GROUP,
base::BindRepeating(&syncer::ReportUnrecoverableError, channel));
collaboration_group_sync_bridge_ =
std::make_unique<CollaborationGroupSyncBridge>(
std::move(change_processor), std::move(data_type_store_factory));
collaboration_group_sync_bridge_->AddObserver(this);
if (sdk_delegate_) {
sdk_delegate_->Initialize(data_sharing_network_loader_.get());
}
}
DataSharingServiceImpl::~DataSharingServiceImpl() {
collaboration_group_sync_bridge_->RemoveObserver(this);
}
bool DataSharingServiceImpl::IsEmptyService() {
return false;
}
void DataSharingServiceImpl::AddObserver(
DataSharingService::Observer* observer) {
observers_.AddObserver(observer);
}
void DataSharingServiceImpl::RemoveObserver(
DataSharingService::Observer* observer) {
observers_.RemoveObserver(observer);
}
DataSharingNetworkLoader*
DataSharingServiceImpl::GetDataSharingNetworkLoader() {
return data_sharing_network_loader_.get();
}
base::WeakPtr<syncer::DataTypeControllerDelegate>
DataSharingServiceImpl::GetCollaborationGroupControllerDelegate() {
return collaboration_group_sync_bridge_->change_processor()
->GetControllerDelegate();
}
void DataSharingServiceImpl::ReadAllGroups(
base::OnceCallback<void(const GroupsDataSetOrFailureOutcome&)> callback) {
// TODO(crbug.com/301390275): this method should read data from the cache
// instead of SDK.
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected(PeopleGroupActionFailure::kPersistentFailure)));
return;
}
data_sharing_pb::ReadGroupsParams params;
for (const GroupId& group_id :
collaboration_group_sync_bridge_->GetCollaborationGroupIds()) {
params.add_group_ids(group_id.value());
}
if (params.group_ids().empty()) {
// No groups to read.
std::move(callback).Run(std::set<GroupData>());
return;
}
sdk_delegate_->ReadGroups(
params,
base::BindOnce(&DataSharingServiceImpl::OnReadAllGroupsCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::ReadGroup(
const GroupId& group_id,
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback) {
// TODO(crbug.com/301390275): this method should read data from the cache
// instead of SDK.
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected(PeopleGroupActionFailure::kPersistentFailure)));
return;
}
data_sharing_pb::ReadGroupsParams params;
params.add_group_ids(group_id.value());
sdk_delegate_->ReadGroups(
params,
base::BindOnce(&DataSharingServiceImpl::OnReadSingleGroupCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::CreateGroup(
const std::string& group_name,
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected(PeopleGroupActionFailure::kPersistentFailure)));
return;
}
data_sharing_pb::CreateGroupParams params;
params.set_display_name(group_name);
sdk_delegate_->CreateGroup(
params,
base::BindOnce(&DataSharingServiceImpl::OnCreateGroupCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::DeleteGroup(
const GroupId& group_id,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
PeopleGroupActionOutcome::kPersistentFailure));
return;
}
data_sharing_pb::DeleteGroupParams params;
params.set_group_id(group_id.value());
sdk_delegate_->DeleteGroup(
params,
base::BindOnce(&DataSharingServiceImpl::OnSimpleGroupActionCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::InviteMember(
const GroupId& group_id,
const std::string& invitee_email,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
PeopleGroupActionOutcome::kPersistentFailure));
return;
}
data_sharing_pb::LookupGaiaIdByEmailParams lookup_params;
lookup_params.set_email(invitee_email);
sdk_delegate_->LookupGaiaIdByEmail(
lookup_params,
base::BindOnce(
&DataSharingServiceImpl::OnGaiaIdLookupForAddMemberCompleted,
weak_ptr_factory_.GetWeakPtr(), group_id, std::move(callback)));
}
void DataSharingServiceImpl::AddMember(
const GroupId& group_id,
const std::string& access_token,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
PeopleGroupActionOutcome::kPersistentFailure));
return;
}
data_sharing_pb::AddMemberParams params;
params.set_group_id(group_id.value());
params.set_access_token(access_token);
sdk_delegate_->AddMember(
params,
base::BindOnce(&DataSharingServiceImpl::OnSimpleGroupActionCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::RemoveMember(
const GroupId& group_id,
const std::string& member_email,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback),
PeopleGroupActionOutcome::kPersistentFailure));
return;
}
data_sharing_pb::LookupGaiaIdByEmailParams lookup_params;
lookup_params.set_email(member_email);
sdk_delegate_->LookupGaiaIdByEmail(
lookup_params,
base::BindOnce(
&DataSharingServiceImpl::OnGaiaIdLookupForRemoveMemberCompleted,
weak_ptr_factory_.GetWeakPtr(), group_id, std::move(callback)));
}
void DataSharingServiceImpl::OnGroupsUpdated(
const std::vector<GroupId>& added_group_ids,
const std::vector<GroupId>& updated_group_ids,
const std::vector<GroupId>& deleted_group_ids) {
// TODO(crbug.com/301390275): get rid of this method and corresponding
// asynchronous logic. Once caching is supported, observers should be
// notified upon cache updates instead.
if (!sdk_delegate_) {
return;
}
// Deletions could be notified immediately.
for (const GroupId& group_id : deleted_group_ids) {
for (auto& observer : observers_) {
observer.OnGroupRemoved(group_id);
}
}
// Fetch added and updated groups.
data_sharing_pb::ReadGroupsParams params;
for (const GroupId& group_id : added_group_ids) {
params.add_group_ids(group_id.value());
}
for (const GroupId& group_id : updated_group_ids) {
params.add_group_ids(group_id.value());
}
if (params.group_ids().empty()) {
// No groups to read.
return;
}
sdk_delegate_->ReadGroups(
params,
base::BindOnce(
&DataSharingServiceImpl::OnReadGroupsToNotifyObserversCompleted,
weak_ptr_factory_.GetWeakPtr(),
/*added_group_ids=*/
std::set<GroupId>(added_group_ids.begin(), added_group_ids.end()),
/*updated_group_ids=*/
std::set<GroupId>(updated_group_ids.begin(),
updated_group_ids.end())));
}
void DataSharingServiceImpl::OnDataLoaded() {
// TODO(crbug.com/301390275): once caching is supported, this method should
// be removed and ReadAllGroups() should read cached groups instead. Right
// now it will read no groups before collaboration group data is loaded and
// we need to issue another read afterwards and notify observers.
if (!sdk_delegate_) {
return;
}
data_sharing_pb::ReadGroupsParams params;
std::vector<GroupId> group_ids =
collaboration_group_sync_bridge_->GetCollaborationGroupIds();
for (const GroupId& group_id : group_ids) {
params.add_group_ids(group_id.value());
}
if (params.group_ids().empty()) {
// No groups to read.
return;
}
sdk_delegate_->ReadGroups(
params,
base::BindOnce(
&DataSharingServiceImpl::OnReadGroupsToNotifyObserversCompleted,
weak_ptr_factory_.GetWeakPtr(), /*added_group_ids=*/
std::set<GroupId>(group_ids.begin(), group_ids.end()),
/*updated_group_ids=*/std::set<GroupId>()));
}
void DataSharingServiceImpl::Shutdown() {
if (sdk_delegate_) {
sdk_delegate_->Shutdown();
}
}
void DataSharingServiceImpl::OnReadSingleGroupCompleted(
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback,
const base::expected<data_sharing_pb::ReadGroupsResult, absl::Status>&
result) {
if (result.has_value()) {
if (result.value().group_data_size() == 1) {
std::move(callback).Run(GroupDataFromProto(result.value().group_data(0)));
return;
} else {
// SDK indicated success, but didn't return exactly single group,
// indicating serious bug in SDK.
std::move(callback).Run(
base::unexpected(PeopleGroupActionFailure::kPersistentFailure));
return;
}
}
std::move(callback).Run(
base::unexpected(StatusToPeopleGroupActionFailure(result.error())));
}
void DataSharingServiceImpl::OnReadAllGroupsCompleted(
base::OnceCallback<void(const GroupsDataSetOrFailureOutcome&)> callback,
const base::expected<data_sharing_pb::ReadGroupsResult, absl::Status>&
result) {
if (result.has_value()) {
std::set<GroupData> groups;
for (const data_sharing_pb::GroupData& group_data :
result.value().group_data()) {
groups.insert(GroupDataFromProto(group_data));
}
std::move(callback).Run(groups);
return;
}
std::move(callback).Run(
base::unexpected(StatusToPeopleGroupActionFailure(result.error())));
}
void DataSharingServiceImpl::OnCreateGroupCompleted(
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback,
const base::expected<data_sharing_pb::CreateGroupResult, absl::Status>&
result) {
if (result.has_value()) {
std::move(callback).Run(GroupDataFromProto(result.value().group_data()));
return;
}
std::move(callback).Run(
base::unexpected(StatusToPeopleGroupActionFailure(result.error())));
}
void DataSharingServiceImpl::OnGaiaIdLookupForAddMemberCompleted(
const GroupId& group_id,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback,
const base::expected<data_sharing_pb::LookupGaiaIdByEmailResult,
absl::Status>& result) {
if (!result.has_value()) {
std::move(callback).Run(StatusToPeopleGroupActionOutcome(result.error()));
return;
}
data_sharing_pb::AddMemberParams params;
params.set_group_id(group_id.value());
params.set_member_gaia_id(result.value().gaia_id());
sdk_delegate_->AddMember(
params,
base::BindOnce(&DataSharingServiceImpl::OnSimpleGroupActionCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::OnGaiaIdLookupForRemoveMemberCompleted(
const GroupId& group_id,
base::OnceCallback<void(PeopleGroupActionOutcome)> callback,
const base::expected<data_sharing_pb::LookupGaiaIdByEmailResult,
absl::Status>& result) {
if (!result.has_value()) {
std::move(callback).Run(StatusToPeopleGroupActionOutcome(result.error()));
return;
}
data_sharing_pb::RemoveMemberParams params;
params.set_group_id(group_id.value());
params.set_member_gaia_id(result.value().gaia_id());
sdk_delegate_->RemoveMember(
params,
base::BindOnce(&DataSharingServiceImpl::OnSimpleGroupActionCompleted,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::OnReadGroupsToNotifyObserversCompleted(
const std::set<GroupId>& added_group_ids,
const std::set<GroupId>& updated_group_ids,
const base::expected<data_sharing_pb::ReadGroupsResult, absl::Status>&
read_groups_result) {
if (!read_groups_result.has_value()) {
// TODO(crbug.com/301390275): remove this method or add error handling
// (retries).
return;
}
for (const data_sharing_pb::GroupData& group_data_proto :
read_groups_result.value().group_data()) {
GroupData group_data = GroupDataFromProto(group_data_proto);
if (added_group_ids.count(group_data.group_token.group_id) > 0) {
for (auto& observer : observers_) {
observer.OnGroupAdded(group_data);
}
}
if (updated_group_ids.count(group_data.group_token.group_id) > 0) {
for (auto& observer : observers_) {
observer.OnGroupChanged(group_data);
}
}
}
}
void DataSharingServiceImpl::OnSimpleGroupActionCompleted(
base::OnceCallback<void(PeopleGroupActionOutcome)> callback,
const absl::Status& status) {
std::move(callback).Run(StatusToPeopleGroupActionOutcome(status));
}
CollaborationGroupSyncBridge*
DataSharingServiceImpl::GetCollaborationGroupSyncBridgeForTesting() {
return collaboration_group_sync_bridge_.get();
}
bool DataSharingServiceImpl::ShouldInterceptNavigationForShareURL(
const GURL& url) {
// TODO(b/336873603): Implement logic to filter URL.
NOTIMPLEMENTED();
return false;
}
void DataSharingServiceImpl::HandleShareURLNavigationIntercepted(
const GURL& url) {
if (!ui_delegate_) {
return;
}
ui_delegate_->HandleShareURLIntercepted(url);
}
std::unique_ptr<GURL> DataSharingServiceImpl::GetDataSharingURL(
const GroupData& group_data) {
if (!group_data.group_token.IsValid()) {
return nullptr;
}
GURL url = GURL(data_sharing::features::kDataSharingURL.Get());
url = net::AppendQueryParameter(url, kGroupIdKey,
group_data.group_token.group_id.value());
url = net::AppendQueryParameter(url, kTokenBlobKey,
group_data.group_token.access_token);
return std::make_unique<GURL>(url);
}
DataSharingService::ParseURLResult DataSharingServiceImpl::ParseDataSharingURL(
const GURL& url) {
GURL data_sharing_url = GURL(data_sharing::features::kDataSharingURL.Get());
if (url.host() != data_sharing_url.host() ||
url.path() != data_sharing_url.path()) {
return base::unexpected(ParseURLStatus::kHostOrPathMismatchFailure);
}
std::string group_id;
std::string access_token;
net::GetValueForKeyInQuery(url, kGroupIdKey, &group_id);
net::GetValueForKeyInQuery(url, kTokenBlobKey, &access_token);
if (group_id.empty() || access_token.empty()) {
return base::unexpected(ParseURLStatus::kQueryMissingFailure);
}
return GroupToken(GroupId(group_id), access_token);
}
void DataSharingServiceImpl::EnsureGroupVisibility(
const GroupId& group_id,
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback) {
if (!sdk_delegate_) {
// Reply in a posted task to avoid reentrance on the calling side.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
std::move(callback),
base::unexpected(PeopleGroupActionFailure::kPersistentFailure)));
return;
}
// TODO(ritikagup@): If a token was added recently then skip adding and return
// read group.
data_sharing_pb::AddAccessTokenParams params;
params.set_group_id(group_id.value());
sdk_delegate_->AddAccessToken(
params,
base::BindOnce(&DataSharingServiceImpl::OnAccessTokenAdded,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void DataSharingServiceImpl::OnAccessTokenAdded(
base::OnceCallback<void(const GroupDataOrFailureOutcome&)> callback,
const base::expected<data_sharing_pb::AddAccessTokenResult, absl::Status>&
result) {
if (result.has_value()) {
std::move(callback).Run(GroupDataFromProto(result.value().group_data()));
return;
}
std::move(callback).Run(
base::unexpected(StatusToPeopleGroupActionFailure(result.error())));
}
} // namespace data_sharing