blob: ae5336bb0d33e9df89dbb24e8ec81cf643009a17 [file] [log] [blame]
// Copyright 2022 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/enterprise/connectors/analysis/local_binary_upload_service.h"
#include <memory>
#include "base/notreached.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "chrome/browser/enterprise/connectors/analysis/analysis_settings.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_sdk_manager.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "content/public/browser/browser_thread.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/content_analysis_sdk/src/browser/include/content_analysis/sdk/analysis_client.h"
namespace enterprise_connectors {
namespace {
constexpr base::TimeDelta kScanningTimeout = base::Minutes(5);
// Build a content analysis SDK client config based on the request being sent.
content_analysis::sdk::Client::Config SDKConfigFromRequest(
const safe_browsing::BinaryUploadService::Request* request) {
return {request->cloud_or_local_settings().local_path(),
request->cloud_or_local_settings().user_specific()};
}
// Build a content analysis SDK client config based on the ack being sent.
content_analysis::sdk::Client::Config SDKConfigFromAck(
const safe_browsing::BinaryUploadService::Ack* ack) {
return {ack->cloud_or_local_settings().local_path(),
ack->cloud_or_local_settings().user_specific()};
}
// Convert enterprise connector ContentAnalysisRequest into the SDK equivalent.
// SDK ContentAnalysisRequest is a strict subset of the enterprise connector
// version, therefore the function should always work.
content_analysis::sdk::ContentAnalysisRequest ConvertChromeRequestToSDKRequest(
const ContentAnalysisRequest& req) {
content_analysis::sdk::ContentAnalysisRequest request;
// TODO(b/226679912): Add unit tests to
// components/enterprise/common/proto/connectors_unittest.cc to ensure the
// conversion methods here and below always work.
if (!request.ParseFromString(req.SerializeAsString())) {
return content_analysis::sdk::ContentAnalysisRequest();
}
// Provide a deadline for the service provider to respond.
base::Time expires_at = base::Time::Now() + kScanningTimeout;
request.set_expires_at(expires_at.ToTimeT());
return request;
}
// Convert SDK ContentAnalysisResponse into the enterprise connector equivalent.
// SDK ContentAnalysisResponse is a strict subset of the enterprise connector
// version, therefore the function should always work.
ContentAnalysisResponse ConvertSDKResponseToChromeResponse(
const content_analysis::sdk::ContentAnalysisResponse& res) {
ContentAnalysisResponse response;
if (!response.ParseFromString(res.SerializeAsString())) {
return ContentAnalysisResponse();
}
return response;
}
content_analysis::sdk::ContentAnalysisAcknowledgement ConvertChromeAckToSDKAck(
const ContentAnalysisAcknowledgement& ack) {
content_analysis::sdk::ContentAnalysisAcknowledgement sdk_ack;
// TODO(b/226679912): Add unit tests to
// components/enterprise/common/proto/connectors_unittest.cc to ensure the
// conversion methods here and below always work.
if (!sdk_ack.ParseFromString(ack.SerializeAsString())) {
return content_analysis::sdk::ContentAnalysisAcknowledgement();
}
return sdk_ack;
}
int SendAckToSDK(
scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
content_analysis::sdk::ContentAnalysisAcknowledgement sdk_ack) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
int status = -1;
if (wrapped && wrapped->client())
status = wrapped->client()->Acknowledge(sdk_ack);
return status;
}
void HandleAckResponse(
std::unique_ptr<safe_browsing::BinaryUploadService::Ack> ack,
int status) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (status != 0)
ContentAnalysisSdkManager::Get()->ResetClient(SDKConfigFromAck(ack.get()));
}
void DoSendAck(scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
std::unique_ptr<safe_browsing::BinaryUploadService::Ack> ack) {
content_analysis::sdk::ContentAnalysisAcknowledgement sdk_ack =
ConvertChromeAckToSDKAck(ack->ack());
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&SendAckToSDK, wrapped, std::move(sdk_ack)),
base::BindOnce(&HandleAckResponse, std::move(ack)));
}
// Sends a request to the local server provider and waits for a response.
absl::optional<content_analysis::sdk::ContentAnalysisResponse> SendRequestToSDK(
scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
content_analysis::sdk::ContentAnalysisRequest sdk_request) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
content_analysis::sdk::ContentAnalysisResponse response;
if (wrapped && wrapped->client()) {
int status = wrapped->client()->Send(sdk_request, &response);
if (status == 0)
return response;
}
return absl::nullopt;
}
// Handles the response from the local service provider.
void HandleResponseFromSDK(
int64_t expires_at,
std::unique_ptr<safe_browsing::BinaryUploadService::Request> request,
scoped_refptr<ContentAnalysisSdkManager::WrappedClient> wrapped,
absl::optional<content_analysis::sdk::ContentAnalysisResponse>
sdk_response) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
safe_browsing::BinaryUploadService::Result result;
ContentAnalysisResponse response;
if (base::Time::Now() > base::Time::FromTimeT(expires_at)) {
result = safe_browsing::BinaryUploadService::Result::TIMEOUT;
// Send timeout back to service provider.
auto ack = std::make_unique<safe_browsing::BinaryUploadService::Ack>(
request->cloud_or_local_settings());
ack->set_request_token(request->request_token());
ack->set_status(ContentAnalysisAcknowledgement::TOO_LATE);
ack->set_final_action(ContentAnalysisAcknowledgement::ALLOW);
DoSendAck(wrapped, std::move(ack));
} else if (sdk_response.has_value()) {
result = safe_browsing::BinaryUploadService::Result::SUCCESS;
response = ConvertSDKResponseToChromeResponse(sdk_response.value());
} else {
result = safe_browsing::BinaryUploadService::Result::UPLOAD_FAILURE;
ContentAnalysisSdkManager::Get()->ResetClient(
SDKConfigFromRequest(request.get()));
}
request->FinishRequest(result, std::move(response));
}
// Sends a request's data to the local service provider. Handles the response
// the service provider asynchronously.
void DoLocalContentAnalysis(
std::unique_ptr<safe_browsing::BinaryUploadService::Request> request,
safe_browsing::BinaryUploadService::Result result,
safe_browsing::BinaryUploadService::Request::Data data) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (LocalResultIsFailure(result)) {
request->FinishRequest(result, ContentAnalysisResponse());
return;
}
request->SetRandomRequestToken();
DCHECK(request->cloud_or_local_settings().is_local_analysis());
auto client = ContentAnalysisSdkManager::Get()->GetClient(
SDKConfigFromRequest(request.get()));
content_analysis::sdk::ContentAnalysisRequest sdk_request =
ConvertChromeRequestToSDKRequest(request->content_analysis_request());
if (!data.contents.empty()) {
sdk_request.set_text_content(std::move(data.contents));
} else if (!data.path.empty()) {
sdk_request.set_file_path(data.path.AsUTF8Unsafe());
} else {
NOTREACHED();
}
auto expires_at = sdk_request.expires_at();
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE,
{base::TaskPriority::USER_VISIBLE, base::MayBlock(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&SendRequestToSDK, client, std::move(sdk_request)),
base::BindOnce(&HandleResponseFromSDK, expires_at, std::move(request),
client));
}
} // namespace
LocalBinaryUploadService::LocalBinaryUploadService() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
LocalBinaryUploadService::~LocalBinaryUploadService() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
}
void LocalBinaryUploadService::MaybeUploadForDeepScanning(
std::unique_ptr<LocalBinaryUploadService::Request> request) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Request* raw_request = request.get();
raw_request->GetRequestData(
base::BindOnce(&DoLocalContentAnalysis, std::move(request)));
}
void LocalBinaryUploadService::MaybeAcknowledge(std::unique_ptr<Ack> ack) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
Ack* ack_ptr = ack.get();
DoSendAck(
ContentAnalysisSdkManager::Get()->GetClient(SDKConfigFromAck(ack_ptr)),
std::move(ack));
}
} // namespace enterprise_connectors