| // Copyright 2020 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/nearby_sharing/client/nearby_share_client_impl.h" |
| |
| #include <memory> |
| |
| #include "base/base64url.h" |
| #include "base/command_line.h" |
| #include "base/functional/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "chrome/browser/nearby_sharing/client/nearby_share_http_notifier.h" |
| #include "chrome/browser/nearby_sharing/common/nearby_share_switches.h" |
| #include "chromeos/ash/components/nearby/common/client/nearby_api_call_flow_impl.h" |
| #include "chromeos/ash/components/nearby/common/client/nearby_http_result.h" |
| #include "components/cross_device/logging/logging.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/access_token_info.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/signin/public/identity_manager/primary_account_access_token_fetcher.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "third_party/nearby/sharing/proto/certificate_rpc.pb.h" |
| #include "third_party/nearby/sharing/proto/contact_rpc.pb.h" |
| #include "third_party/nearby/sharing/proto/device_rpc.pb.h" |
| #include "third_party/nearby/sharing/proto/rpc_resources.pb.h" |
| |
| namespace { |
| |
| // -------------------- Nearby Share Service v1 Endpoints -------------------- |
| |
| const char kDefaultNearbyShareV1HTTPHost[] = |
| "https://nearbysharing-pa.googleapis.com"; |
| |
| const char kNearbyShareV1Path[] = "v1/"; |
| |
| const char kListContactPeoplePath[] = "contactRecords"; |
| const char kListPublicCertificatesPath[] = "publicCertificates"; |
| |
| const char kPageSize[] = "page_size"; |
| const char kPageToken[] = "page_token"; |
| const char kSecretIds[] = "secret_ids"; |
| |
| // Creates the full Nearby Share v1 URL for endpoint to the API with |
| // |request_path|. |
| GURL CreateV1RequestUrl(const std::string& request_path) { |
| base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); |
| GURL google_apis_url = command_line->HasSwitch(switches::kNearbyShareHTTPHost) |
| ? GURL(command_line->GetSwitchValueASCII( |
| switches::kNearbyShareHTTPHost)) |
| : GURL(kDefaultNearbyShareV1HTTPHost); |
| return google_apis_url.Resolve(kNearbyShareV1Path + request_path); |
| } |
| |
| ash::nearby::NearbyApiCallFlow::QueryParameters |
| ListContactPeopleRequestToQueryParameters( |
| const nearby::sharing::proto::ListContactPeopleRequest& request) { |
| ash::nearby::NearbyApiCallFlow::QueryParameters query_parameters; |
| if (request.page_size() > 0) { |
| query_parameters.emplace_back(kPageSize, |
| base::NumberToString(request.page_size())); |
| } |
| if (!request.page_token().empty()) { |
| query_parameters.emplace_back(kPageToken, request.page_token()); |
| } |
| return query_parameters; |
| } |
| |
| ash::nearby::NearbyApiCallFlow::QueryParameters |
| ListPublicCertificatesRequestToQueryParameters( |
| const nearby::sharing::proto::ListPublicCertificatesRequest& request) { |
| ash::nearby::NearbyApiCallFlow::QueryParameters query_parameters; |
| if (request.page_size() > 0) { |
| query_parameters.emplace_back(kPageSize, |
| base::NumberToString(request.page_size())); |
| } |
| if (!request.page_token().empty()) { |
| query_parameters.emplace_back(kPageToken, request.page_token()); |
| } |
| for (const std::string& id : request.secret_ids()) { |
| // NOTE: One Platform requires that byte fields be URL-safe base64 encoded. |
| std::string encoded_id; |
| base::Base64UrlEncode(id, base::Base64UrlEncodePolicy::INCLUDE_PADDING, |
| &encoded_id); |
| query_parameters.emplace_back(kSecretIds, encoded_id); |
| } |
| return query_parameters; |
| } |
| |
| // TODO(crbug.com/1103471): Update "chrome_policy" when a Nearby Share |
| // enterprise policy is created. |
| const net::PartialNetworkTrafficAnnotationTag& GetUpdateDeviceAnnotation() { |
| static const net::PartialNetworkTrafficAnnotationTag annotation = |
| net::DefinePartialNetworkTrafficAnnotation("nearby_share_update_device", |
| "oauth2_api_call_flow", |
| R"( |
| semantics { |
| sender: "Nearby Share" |
| description: |
| "Used as part of the Nearby Share feature that allows users to " |
| "share files or text with trusted contacts within a certain physical " |
| "proximity. The call sends the local device's user-defined name and " |
| "their list of allowed contacts to the Google-owned Nearby server. " |
| "Nearby-Share-specific crypto data from the local device is also " |
| "uploaded to the server and distributed to trusted contacts to help " |
| "establish an authenticated channel during the Nearby Share flow. " |
| "This crypto data can be immediately invalidated by the local device " |
| "at any time without needing to communicate with the server. For " |
| "example, it expires after three days and new data needs to be " |
| "uploaded. Crypto data is also invalidated if the user's list of " |
| "allowed contacts changes. The server returns the local device " |
| "user's full name and icon URL if available on the Google server." |
| trigger: |
| "Automatically called daily to retrieve any updates to the user's " |
| "full name or icon URL. This request is also sent whenever the user " |
| "changes their device name in settings, whenever the user changes " |
| "their list of allowed contacts, or whenever new crypto data is " |
| "generated by the local device and needs to be shared with trusted " |
| "contacts." |
| data: |
| "Sends an OAuth 2.0 token, the local device's name, contact, and/or " |
| "Nearby-Share-specific crypto data. Possibly receives the user's " |
| "full name and icon URL from the Google server." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| setting: |
| "Only sent when Nearby Share is enabled and the user is signed in " |
| "with their Google account." |
| chrome_policy { |
| SigninAllowed { |
| SigninAllowed: false |
| } |
| } |
| })"); |
| return annotation; |
| } |
| |
| // TODO(crbug.com/1103471): Update "chrome_policy" when a Nearby Share |
| // enterprise policy is created. |
| const net::PartialNetworkTrafficAnnotationTag& GetContactsAnnotation() { |
| static const net::PartialNetworkTrafficAnnotationTag annotation = |
| net::DefinePartialNetworkTrafficAnnotation("nearby_share_contacts", |
| "oauth2_api_call_flow", |
| R"( |
| semantics { |
| sender: "Nearby Share" |
| description: |
| "Used as part of the Nearby Share feature that allows users to " |
| "share files or text with trusted contacts within a certain physical " |
| "proximity. The call retrieves the user's list of contacts from the " |
| "Google-owned People server via the Google-owned Nearby server." |
| trigger: |
| "Called multiple times a day to check for possible updates to the " |
| "users's contact list. It is also invoked during Nearby Share " |
| "onboarding and when the user is in the Nearby Share settings." |
| data: |
| "Sends an OAuth 2.0 token. Receives the user's contact list, which " |
| "includes phone numbers and email addresses." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| setting: |
| "Only sent when Nearby Share is enabled and the user is signed in " |
| "with their Google account." |
| chrome_policy { |
| SigninAllowed { |
| SigninAllowed: false |
| } |
| } |
| })"); |
| return annotation; |
| } |
| |
| // TODO(crbug.com/1103471): Update "chrome_policy" when a Nearby Share |
| // enterprise policy is created. |
| const net::PartialNetworkTrafficAnnotationTag& |
| GetListPublicCertificatesAnnotation() { |
| static const net::PartialNetworkTrafficAnnotationTag annotation = |
| net::DefinePartialNetworkTrafficAnnotation( |
| "nearby_share_list_public_certificates", "oauth2_api_call_flow", |
| R"( |
| semantics { |
| sender: "Nearby Share" |
| description: |
| "Used as part of the Nearby Share feature that allows users to " |
| "share files or text with trusted contacts within a certain physical " |
| "proximity. The call retrieves Nearby-Share-specific crypto data " |
| "from the Google-owned Nearby server. The data was uploaded by other " |
| "devices and is needed to establish an authenticated connection with " |
| "those device during the Nearby Share flow." |
| trigger: |
| "Automatically called at least once a day to retrieve any updates to " |
| "the list of crypto data. It is also called when Nearby Share is in " |
| "use to ensure up-to-date data." |
| data: |
| "Sends an OAuth 2.0 token. Receives Nearby-Share-specific crypto " |
| "necessary for establishing an authenticated channel with other " |
| "devices." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| setting: |
| "Only sent when Nearby Share is enabled and the user is signed in " |
| "with their Google account." |
| chrome_policy { |
| SigninAllowed { |
| SigninAllowed: false |
| } |
| } |
| })"); |
| return annotation; |
| } |
| |
| } // namespace |
| |
| NearbyShareClientImpl::NearbyShareClientImpl( |
| std::unique_ptr<ash::nearby::NearbyApiCallFlow> api_call_flow, |
| signin::IdentityManager* identity_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| NearbyShareHttpNotifier* notifier) |
| : api_call_flow_(std::move(api_call_flow)), |
| identity_manager_(identity_manager), |
| url_loader_factory_(std::move(url_loader_factory)), |
| notifier_(notifier), |
| has_call_started_(false) {} |
| |
| NearbyShareClientImpl::~NearbyShareClientImpl() = default; |
| |
| void NearbyShareClientImpl::UpdateDevice( |
| const nearby::sharing::proto::UpdateDeviceRequest& request, |
| UpdateDeviceCallback&& callback, |
| ErrorCallback&& error_callback) { |
| notifier_->NotifyOfRequest(request); |
| MakeApiCall(CreateV1RequestUrl(request.device().name()), RequestType::kPatch, |
| request.SerializeAsString(), |
| /*request_as_query_parameters=*/std::nullopt, std::move(callback), |
| std::move(error_callback), GetUpdateDeviceAnnotation()); |
| } |
| |
| void NearbyShareClientImpl::ListContactPeople( |
| const nearby::sharing::proto::ListContactPeopleRequest& request, |
| ListContactPeopleCallback&& callback, |
| ErrorCallback&& error_callback) { |
| notifier_->NotifyOfRequest(request); |
| MakeApiCall(CreateV1RequestUrl(kListContactPeoplePath), RequestType::kGet, |
| /*serialized_request=*/std::nullopt, |
| ListContactPeopleRequestToQueryParameters(request), |
| std::move(callback), std::move(error_callback), |
| GetContactsAnnotation()); |
| } |
| |
| void NearbyShareClientImpl::ListPublicCertificates( |
| const nearby::sharing::proto::ListPublicCertificatesRequest& request, |
| ListPublicCertificatesCallback&& callback, |
| ErrorCallback&& error_callback) { |
| notifier_->NotifyOfRequest(request); |
| MakeApiCall( |
| CreateV1RequestUrl(request.parent() + "/" + kListPublicCertificatesPath), |
| RequestType::kGet, /*serialized_request=*/std::nullopt, |
| ListPublicCertificatesRequestToQueryParameters(request), |
| std::move(callback), std::move(error_callback), |
| GetListPublicCertificatesAnnotation()); |
| } |
| |
| std::string NearbyShareClientImpl::GetAccessTokenUsed() { |
| return access_token_used_; |
| } |
| |
| template <class ResponseProto> |
| void NearbyShareClientImpl::MakeApiCall( |
| const GURL& request_url, |
| RequestType request_type, |
| const std::optional<std::string>& serialized_request, |
| const std::optional<ash::nearby::NearbyApiCallFlow::QueryParameters>& |
| request_as_query_parameters, |
| base::OnceCallback<void(const ResponseProto&)>&& response_callback, |
| ErrorCallback&& error_callback, |
| const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) { |
| DCHECK(!has_call_started_) |
| << "NearbyShareClientImpl::MakeApiCall(): Tried to make an API " |
| << "call, but the client had already been used."; |
| has_call_started_ = true; |
| |
| api_call_flow_->SetPartialNetworkTrafficAnnotation( |
| partial_traffic_annotation); |
| |
| request_url_ = request_url; |
| error_callback_ = std::move(error_callback); |
| |
| access_token_fetcher_ = |
| std::make_unique<signin::PrimaryAccountAccessTokenFetcher>( |
| signin::OAuthConsumerId::kNearbyShare, identity_manager_, |
| base::BindOnce( |
| &NearbyShareClientImpl::OnAccessTokenFetched<ResponseProto>, |
| weak_ptr_factory_.GetWeakPtr(), request_type, serialized_request, |
| request_as_query_parameters, std::move(response_callback)), |
| signin::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable, |
| signin::ConsentLevel::kSignin); |
| } |
| |
| template <class ResponseProto> |
| void NearbyShareClientImpl::OnAccessTokenFetched( |
| RequestType request_type, |
| const std::optional<std::string>& serialized_request, |
| const std::optional<ash::nearby::NearbyApiCallFlow::QueryParameters>& |
| request_as_query_parameters, |
| base::OnceCallback<void(const ResponseProto&)>&& response_callback, |
| GoogleServiceAuthError error, |
| signin::AccessTokenInfo access_token_info) { |
| access_token_fetcher_.reset(); |
| |
| if (error.state() != GoogleServiceAuthError::NONE) { |
| OnApiCallFailed(ash::nearby::NearbyHttpError::kAuthenticationError); |
| return; |
| } |
| access_token_used_ = access_token_info.token; |
| |
| switch (request_type) { |
| case RequestType::kGet: |
| DCHECK(request_as_query_parameters && !serialized_request); |
| api_call_flow_->StartGetRequest( |
| request_url_, *request_as_query_parameters, url_loader_factory_, |
| access_token_used_, |
| base::BindOnce(&NearbyShareClientImpl::OnFlowSuccess<ResponseProto>, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(response_callback)), |
| base::BindOnce(&NearbyShareClientImpl::OnApiCallFailed, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| case RequestType::kPost: |
| DCHECK(serialized_request && !request_as_query_parameters); |
| api_call_flow_->StartPostRequest( |
| request_url_, *serialized_request, url_loader_factory_, |
| access_token_used_, |
| base::BindOnce(&NearbyShareClientImpl::OnFlowSuccess<ResponseProto>, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(response_callback)), |
| base::BindOnce(&NearbyShareClientImpl::OnApiCallFailed, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| case RequestType::kPatch: |
| DCHECK(serialized_request && !request_as_query_parameters); |
| api_call_flow_->StartPatchRequest( |
| request_url_, *serialized_request, url_loader_factory_, |
| access_token_used_, |
| base::BindOnce(&NearbyShareClientImpl::OnFlowSuccess<ResponseProto>, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(response_callback)), |
| base::BindOnce(&NearbyShareClientImpl::OnApiCallFailed, |
| weak_ptr_factory_.GetWeakPtr())); |
| break; |
| } |
| } |
| |
| template <class ResponseProto> |
| void NearbyShareClientImpl::OnFlowSuccess( |
| base::OnceCallback<void(const ResponseProto&)>&& result_callback, |
| const std::string& serialized_response) { |
| ResponseProto response; |
| if (!response.ParseFromString(serialized_response)) { |
| OnApiCallFailed(ash::nearby::NearbyHttpError::kResponseMalformed); |
| return; |
| } |
| notifier_->NotifyOfResponse(response); |
| std::move(result_callback).Run(response); |
| } |
| |
| void NearbyShareClientImpl::OnApiCallFailed( |
| ash::nearby::NearbyHttpError error) { |
| CD_LOG(ERROR, Feature::NS) |
| << "Nearby Share RPC call failed with error " << error; |
| std::move(error_callback_).Run(error); |
| } |
| |
| NearbyShareClientFactoryImpl::NearbyShareClientFactoryImpl( |
| signin::IdentityManager* identity_manager, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| NearbyShareHttpNotifier* notifier) |
| : identity_manager_(identity_manager), |
| url_loader_factory_(std::move(url_loader_factory)), |
| notifier_(notifier) {} |
| |
| NearbyShareClientFactoryImpl::~NearbyShareClientFactoryImpl() = default; |
| |
| std::unique_ptr<NearbyShareClient> |
| NearbyShareClientFactoryImpl::CreateInstance() { |
| return std::make_unique<NearbyShareClientImpl>( |
| std::make_unique<ash::nearby::NearbyApiCallFlowImpl>(), identity_manager_, |
| url_loader_factory_, notifier_); |
| } |