Bug 1811076: Part 7 - Send Acknowledge response to Content Analysis agent r=rkraesig

The agent/browser protocol is a request-response-acknowledge
format.  We were only sending Acknowledge for the download case.

Differential Revision: https://phabricator.services.mozilla.com/D189579
This commit is contained in:
Greg Stoll 2023-12-13 03:01:04 +00:00
Родитель 9a656455d8
Коммит ebf37de3ae
3 изменённых файлов: 249 добавлений и 134 удалений

Просмотреть файл

@ -82,6 +82,27 @@ static nsresult GetFileDisplayName(const nsString& aFilePath,
return file->GetDisplayName(aFileDisplayName);
}
uint32_t ResponseResultToAcknowledgementResult(uint32_t responseResult) {
switch (responseResult) {
case nsIContentAnalysisResponse::REPORT_ONLY:
return nsIContentAnalysisAcknowledgement::REPORT_ONLY;
case nsIContentAnalysisResponse::WARN:
return nsIContentAnalysisAcknowledgement::WARN;
case nsIContentAnalysisResponse::BLOCK:
return nsIContentAnalysisAcknowledgement::BLOCK;
case nsIContentAnalysisResponse::ALLOW:
return nsIContentAnalysisAcknowledgement::ALLOW;
case nsIContentAnalysisResponse::ACTION_UNSPECIFIED:
return nsIContentAnalysisAcknowledgement::ACTION_UNSPECIFIED;
default:
LOGE(
"ResponseResultToAcknowledgementResult got unexpected responseResult "
"%d",
responseResult);
return nsIContentAnalysisAcknowledgement::ACTION_UNSPECIFIED;
}
}
} // anonymous namespace
namespace mozilla::contentanalysis {
@ -374,7 +395,8 @@ static void LogRequest(
}
ContentAnalysisResponse::ContentAnalysisResponse(
content_analysis::sdk::ContentAnalysisResponse&& aResponse) {
content_analysis::sdk::ContentAnalysisResponse&& aResponse)
: mHasAcknowledged(false) {
mAction = nsIContentAnalysisResponse::ACTION_UNSPECIFIED;
for (const auto& result : aResponse.results()) {
if (!result.has_status() ||
@ -400,7 +422,7 @@ ContentAnalysisResponse::ContentAnalysisResponse(
ContentAnalysisResponse::ContentAnalysisResponse(
unsigned long aAction, const nsACString& aRequestToken)
: mAction(aAction), mRequestToken(aRequestToken) {}
: mAction(aAction), mRequestToken(aRequestToken), mHasAcknowledged(false) {}
/* static */
already_AddRefed<ContentAnalysisResponse> ContentAnalysisResponse::FromProtobuf(
@ -531,7 +553,23 @@ static void LogAcknowledgement(
}
void ContentAnalysisResponse::SetOwner(RefPtr<ContentAnalysis> aOwner) {
mOwner = aOwner;
mOwner = std::move(aOwner);
}
ContentAnalysisAcknowledgement::ContentAnalysisAcknowledgement(
unsigned long aResult, unsigned long aFinalAction)
: mResult(aResult), mFinalAction(aFinalAction) {}
NS_IMETHODIMP
ContentAnalysisAcknowledgement::GetResult(uint32_t* aResult) {
*aResult = mResult;
return NS_OK;
}
NS_IMETHODIMP
ContentAnalysisAcknowledgement::GetFinalAction(uint32_t* aFinalAction) {
*aFinalAction = mFinalAction;
return NS_OK;
}
namespace {
@ -565,6 +603,8 @@ NS_IMPL_CLASSINFO(ContentAnalysisRequest, nullptr, 0, {0});
NS_IMPL_ISUPPORTS_CI(ContentAnalysisRequest, nsIContentAnalysisRequest);
NS_IMPL_CLASSINFO(ContentAnalysisResponse, nullptr, 0, {0});
NS_IMPL_ISUPPORTS_CI(ContentAnalysisResponse, nsIContentAnalysisResponse);
NS_IMPL_ISUPPORTS(ContentAnalysisAcknowledgement,
nsIContentAnalysisAcknowledgement);
NS_IMPL_ISUPPORTS(ContentAnalysisCallback, nsIContentAnalysisCallback);
NS_IMPL_ISUPPORTS(ContentAnalysisResult, nsIContentAnalysisResult);
NS_IMPL_ISUPPORTS(ContentAnalysis, nsIContentAnalysis, ContentAnalysis);
@ -672,8 +712,8 @@ RefPtr<ContentAnalysis> ContentAnalysis::GetContentAnalysisFromService() {
}
nsresult ContentAnalysis::RunAnalyzeRequestTask(
RefPtr<nsIContentAnalysisRequest> aRequest,
RefPtr<nsIContentAnalysisCallback> aCallback) {
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
const RefPtr<nsIContentAnalysisCallback>& aCallback) {
nsresult rv = NS_ERROR_FAILURE;
auto callbackCopy = aCallback;
auto se = MakeScopeExit([&] {
@ -687,14 +727,11 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
rv = ConvertToProtobuf(aRequest, &pbRequest);
NS_ENSURE_SUCCESS(rv, rv);
LOGD("Issuing ContentAnalysisRequest");
LogRequest(&pbRequest);
nsCString requestToken;
nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolderCopy(
new nsMainThreadPtrHolder<nsIContentAnalysisCallback>(
"content analysis callback", aCallback));
CallbackData callbackData(std::move(callbackHolderCopy));
CallbackData callbackData(std::move(callbackHolderCopy), aAutoAcknowledge);
rv = aRequest->GetRequestToken(requestToken);
NS_ENSURE_SUCCESS(rv, rv);
{
@ -708,117 +745,18 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
mCaClientPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[requestToken, pbRequest = std::move(pbRequest)](
std::shared_ptr<content_analysis::sdk::Client> client) {
std::shared_ptr<content_analysis::sdk::Client> client) mutable {
// The content analysis call is synchronous so run in the background.
NS_DispatchBackgroundTask(
NS_NewCancelableRunnableFunction(
__func__,
[requestToken, pbRequest = std::move(pbRequest), client] {
RefPtr<ContentAnalysis> owner =
GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
return;
}
if (!client) {
owner->CancelWithError(std::move(requestToken),
NS_ERROR_NOT_AVAILABLE);
return;
}
{
auto callbackMap = owner->mCallbackMap.Lock();
if (!callbackMap->Contains(requestToken)) {
LOGD(
"RunAnalyzeRequestTask token %s has already been "
"cancelled - not issuing request",
requestToken.get());
return;
}
}
// Run request, then dispatch back to main thread to resolve
// aCallback
content_analysis::sdk::ContentAnalysisResponse pbResponse;
int err = client->Send(pbRequest, &pbResponse);
if (err != 0) {
LOGE("RunAnalyzeRequestTask client transaction failed");
owner->CancelWithError(std::move(requestToken),
NS_ERROR_FAILURE);
return;
}
LOGD("Content analysis client transaction succeeded");
LogResponse(&pbResponse);
NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
"ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
[pbResponse = std::move(pbResponse)]() mutable {
RefPtr<ContentAnalysis> owner =
GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
return;
}
RefPtr<ContentAnalysisResponse> response =
ContentAnalysisResponse::FromProtobuf(
std::move(pbResponse));
if (!response) {
LOGE("Content analysis got invalid response!");
return;
}
nsCString responseRequestToken;
nsresult requestRv =
response->GetRequestToken(responseRequestToken);
if (NS_FAILED(requestRv)) {
LOGE(
"Content analysis couldn't get request token "
"from response!");
return;
}
Maybe<CallbackData> maybeCallbackData;
{
auto callbackMap = owner->mCallbackMap.Lock();
maybeCallbackData =
callbackMap->Extract(responseRequestToken);
}
if (maybeCallbackData.isNothing()) {
LOGD(
"Content analysis did not find callback for "
"token %s",
responseRequestToken.get());
return;
}
response->SetOwner(owner);
if (maybeCallbackData->Canceled()) {
// request has already been cancelled, so there's
// nothing to do
LOGD(
"Content analysis got response but ignoring "
"because it was already cancelled for token %s",
responseRequestToken.get());
return;
}
LOGD(
"Content analysis resolving response promise for "
"token %s",
responseRequestToken.get());
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
obsServ->NotifyObservers(response, "dlp-response",
nullptr);
nsMainThreadPtrHandle<nsIContentAnalysisCallback>
callbackHolder =
maybeCallbackData->TakeCallbackHolder();
callbackHolder->ContentResult(response);
}));
[requestToken, pbRequest = std::move(pbRequest),
client = std::move(client)]() mutable {
DoAnalyzeRequest(requestToken, std::move(pbRequest), client);
}),
NS_DISPATCH_EVENT_MAY_BLOCK);
},
[requestToken](nsresult rv) {
[requestToken](nsresult rv) mutable {
LOGD("RunAnalyzeRequestTask failed to get client");
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
if (!owner) {
@ -831,9 +769,127 @@ nsresult ContentAnalysis::RunAnalyzeRequestTask(
return rv;
}
void ContentAnalysis::DoAnalyzeRequest(
nsCString aRequestToken,
content_analysis::sdk::ContentAnalysisRequest&& aRequest,
const std::shared_ptr<content_analysis::sdk::Client>& aClient) {
MOZ_ASSERT(!NS_IsMainThread());
RefPtr<ContentAnalysis> owner =
ContentAnalysis::GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
return;
}
if (!aClient) {
owner->CancelWithError(std::move(aRequestToken), NS_ERROR_NOT_AVAILABLE);
return;
}
{
auto callbackMap = owner->mCallbackMap.Lock();
if (!callbackMap->Contains(aRequestToken)) {
LOGD(
"RunAnalyzeRequestTask token %s has already been "
"cancelled - not issuing request",
aRequestToken.get());
return;
}
}
// Run request, then dispatch back to main thread to resolve
// aCallback
content_analysis::sdk::ContentAnalysisResponse pbResponse;
int err = aClient->Send(aRequest, &pbResponse);
if (err != 0) {
LOGE("RunAnalyzeRequestTask client transaction failed");
owner->CancelWithError(std::move(aRequestToken), NS_ERROR_FAILURE);
return;
}
LOGD("Content analysis client transaction succeeded");
LogResponse(&pbResponse);
NS_DispatchToMainThread(NS_NewCancelableRunnableFunction(
"ContentAnalysis::RunAnalyzeRequestTask::HandleResponse",
[pbResponse = std::move(pbResponse)]() mutable {
RefPtr<ContentAnalysis> owner = GetContentAnalysisFromService();
if (!owner) {
// May be shutting down
return;
}
RefPtr<ContentAnalysisResponse> response =
ContentAnalysisResponse::FromProtobuf(std::move(pbResponse));
if (!response) {
LOGE("Content analysis got invalid response!");
return;
}
nsCString responseRequestToken;
nsresult requestRv = response->GetRequestToken(responseRequestToken);
if (NS_FAILED(requestRv)) {
LOGE(
"Content analysis couldn't get request token "
"from response!");
return;
}
Maybe<CallbackData> maybeCallbackData;
{
auto callbackMap = owner->mCallbackMap.Lock();
maybeCallbackData = callbackMap->Extract(responseRequestToken);
}
if (maybeCallbackData.isNothing()) {
LOGD(
"Content analysis did not find callback for "
"token %s",
responseRequestToken.get());
return;
}
response->SetOwner(owner);
if (maybeCallbackData->Canceled()) {
// request has already been cancelled, so there's
// nothing to do
LOGD(
"Content analysis got response but ignoring "
"because it was already cancelled for token %s",
responseRequestToken.get());
// Note that we always acknowledge here, even if
// autoAcknowledge isn't set. From the caller's perspective
// the request has already been
// cancelled and the caller isn't going to get any
// notification that the DLP agent sent a response, so
// the caller wouldn't be able to send an
// acknowledgment.
auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
nsIContentAnalysisAcknowledgement::TOO_LATE,
nsIContentAnalysisAcknowledgement::BLOCK);
response->Acknowledge(acknowledgement);
return;
}
LOGD(
"Content analysis resolving response promise for "
"token %s",
responseRequestToken.get());
nsCOMPtr<nsIObserverService> obsServ =
mozilla::services::GetObserverService();
obsServ->NotifyObservers(response, "dlp-response", nullptr);
if (maybeCallbackData->AutoAcknowledge()) {
uint32_t action = response->GetAction();
auto acknowledgement = MakeRefPtr<ContentAnalysisAcknowledgement>(
nsIContentAnalysisAcknowledgement::SUCCESS,
ResponseResultToAcknowledgementResult(action));
response->Acknowledge(acknowledgement);
}
nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
maybeCallbackData->TakeCallbackHolder();
callbackHolder->ContentResult(response);
}));
}
NS_IMETHODIMP
ContentAnalysis::AnalyzeContentRequest(nsIContentAnalysisRequest* aRequest,
JSContext* aCx,
bool aAutoAcknowledge, JSContext* aCx,
mozilla::dom::Promise** aPromise) {
RefPtr<mozilla::dom::Promise> promise;
nsresult rv = MakePromise(aCx, &promise);
@ -841,12 +897,13 @@ ContentAnalysis::AnalyzeContentRequest(nsIContentAnalysisRequest* aRequest,
RefPtr<ContentAnalysisCallback> callbackPtr =
new ContentAnalysisCallback(promise);
promise.forget(aPromise);
return AnalyzeContentRequestCallback(aRequest, callbackPtr.get());
return AnalyzeContentRequestCallback(aRequest, aAutoAcknowledge,
callbackPtr.get());
}
NS_IMETHODIMP
ContentAnalysis::AnalyzeContentRequestCallback(
nsIContentAnalysisRequest* aRequest,
nsIContentAnalysisRequest* aRequest, bool aAutoAcknowledge,
nsIContentAnalysisCallback* aCallback) {
NS_ENSURE_ARG(aRequest);
NS_ENSURE_ARG(aCallback);
@ -862,7 +919,7 @@ ContentAnalysis::AnalyzeContentRequestCallback(
mozilla::services::GetObserverService();
obsServ->NotifyObservers(aRequest, "dlp-request-made", nullptr);
rv = RunAnalyzeRequestTask(aRequest, aCallback);
rv = RunAnalyzeRequestTask(aRequest, aAutoAcknowledge, aCallback);
return rv;
}
@ -880,15 +937,20 @@ ContentAnalysis::CancelContentAnalysisRequest(const nsACString& aRequestToken) {
auto callbackMap = self->mCallbackMap.Lock();
auto entry = callbackMap->Lookup(requestToken);
LOGD("Content analysis cancelling request %s", requestToken.get());
if (entry) {
entry->SetCanceled();
// Make sure the entry hasn't been cancelled already
if (entry && !entry->Canceled()) {
RefPtr<ContentAnalysisResponse> cancelResponse =
ContentAnalysisResponse::FromAction(
nsIContentAnalysisResponse::CANCELED, requestToken);
cancelResponse->SetOwner(self);
nsMainThreadPtrHandle<nsIContentAnalysisCallback> callbackHolder =
entry->TakeCallbackHolder();
callbackHolder->ContentResult(cancelResponse.get());
entry->SetCanceled();
// Should only be called once
MOZ_ASSERT(callbackHolder);
if (callbackHolder) {
callbackHolder->ContentResult(cancelResponse.get());
}
} else {
LOGD("Content analysis request not found when trying to cancel %s",
requestToken.get());
@ -901,6 +963,11 @@ NS_IMETHODIMP
ContentAnalysisResponse::Acknowledge(
nsIContentAnalysisAcknowledgement* aAcknowledgement) {
MOZ_ASSERT(mOwner);
if (mHasAcknowledged) {
MOZ_ASSERT(false, "Already acknowledged this ContentAnalysisResponse!");
return NS_ERROR_FAILURE;
}
mHasAcknowledged = true;
return mOwner->RunAcknowledgeTask(aAcknowledgement, mRequestToken);
};
@ -926,11 +993,12 @@ nsresult ContentAnalysis::RunAcknowledgeTask(
mCaClientPromise->Then(
GetCurrentSerialEventTarget(), __func__,
[pbAck = std::move(pbAck)](
std::shared_ptr<content_analysis::sdk::Client> client) {
std::shared_ptr<content_analysis::sdk::Client> client) mutable {
NS_DispatchBackgroundTask(
NS_NewCancelableRunnableFunction(
__func__,
[pbAck = std::move(pbAck), client] {
[pbAck = std::move(pbAck),
client = std::move(client)]() mutable {
RefPtr<ContentAnalysis> owner =
GetContentAnalysisFromService();
if (!owner) {

Просмотреть файл

@ -18,6 +18,7 @@
namespace content_analysis::sdk {
class Client;
class ContentAnalysisRequest;
class ContentAnalysisResponse;
} // namespace content_analysis::sdk
@ -97,13 +98,18 @@ class ContentAnalysis final : public nsIContentAnalysis {
ContentAnalysis& operator=(ContentAnalysis&) = delete;
nsresult CreateContentAnalysisClient(nsCString&& aPipePathName,
bool aIsPerUser);
nsresult RunAnalyzeRequestTask(RefPtr<nsIContentAnalysisRequest> aRequest,
RefPtr<nsIContentAnalysisCallback> aCallback);
nsresult RunAnalyzeRequestTask(
const RefPtr<nsIContentAnalysisRequest>& aRequest, bool aAutoAcknowledge,
const RefPtr<nsIContentAnalysisCallback>& aCallback);
nsresult RunAcknowledgeTask(
nsIContentAnalysisAcknowledgement* aAcknowledgement,
const nsACString& aRequestToken);
nsresult CancelWithError(nsCString aRequestToken, nsresult aResult);
static RefPtr<ContentAnalysis> GetContentAnalysisFromService();
static void DoAnalyzeRequest(
nsCString aRequestToken,
content_analysis::sdk::ContentAnalysisRequest&& aRequest,
const std::shared_ptr<content_analysis::sdk::Client>& aClient);
using ClientPromise =
MozPromise<std::shared_ptr<content_analysis::sdk::Client>, nsresult,
@ -112,20 +118,24 @@ class ContentAnalysis final : public nsIContentAnalysis {
// Only accessed from the main thread
bool mClientCreationAttempted;
class CallbackData {
class CallbackData final {
public:
explicit CallbackData(
nsMainThreadPtrHandle<nsIContentAnalysisCallback>&& aCallbackHolder)
: mCallbackHolder(aCallbackHolder) {}
CallbackData(
nsMainThreadPtrHandle<nsIContentAnalysisCallback>&& aCallbackHolder,
bool aAutoAcknowledge)
: mCallbackHolder(aCallbackHolder),
mAutoAcknowledge(aAutoAcknowledge) {}
nsMainThreadPtrHandle<nsIContentAnalysisCallback> TakeCallbackHolder() {
return std::move(mCallbackHolder);
}
bool AutoAcknowledge() const { return mAutoAcknowledge; }
void SetCanceled() { mCallbackHolder = nullptr; }
bool Canceled() const { return !mCallbackHolder; }
private:
nsMainThreadPtrHandle<nsIContentAnalysisCallback> mCallbackHolder;
bool mAutoAcknowledge;
};
DataMutex<nsTHashMap<nsCString, CallbackData>> mCallbackMap;
friend class ContentAnalysisResponse;
@ -165,9 +175,28 @@ class ContentAnalysisResponse final : public nsIContentAnalysisResponse {
// the transaction.
RefPtr<ContentAnalysis> mOwner;
// Whether the response has been acknowledged
bool mHasAcknowledged;
friend class ContentAnalysis;
};
class ContentAnalysisAcknowledgement final
: public nsIContentAnalysisAcknowledgement {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSICONTENTANALYSISACKNOWLEDGEMENT
ContentAnalysisAcknowledgement(unsigned long aResult,
unsigned long aFinalAction);
private:
~ContentAnalysisAcknowledgement() = default;
unsigned long mResult;
unsigned long mFinalAction;
};
class ContentAnalysisCallback final : public nsIContentAnalysisCallback {
public:
NS_DECL_THREADSAFE_ISUPPORTS

Просмотреть файл

@ -56,8 +56,9 @@ interface nsIContentAnalysisResponse : nsISupports
/**
* Acknowledge receipt of an analysis response.
* Should always be called after successful resolution of the promise
* from AnalyzeContentRequest.
* If false is passed for aAutoAcknowledge to AnalyzeContentRequest,
* the caller is responsible for calling this after successful
* resolution of the promise.
*/
void acknowledge(in nsIContentAnalysisAcknowledgement aCaa);
};
@ -185,22 +186,39 @@ interface nsIContentAnalysis : nsISupports
* The resulting Promise resolves to a nsIContentAnalysisResponse,
* which may take some time to get from the analysis server. It will
* be rejected, with an string error description, if any error occurs.
* A successful nsIContentAnalysisResponse must be acknowledged.
* See @nsIContentAnalysisResponse.
*
* @param aCar
* The request to analyze.
* @param aAutoAcknowledge
* Whether to send an acknowledge message to the agent after the agent sends a response.
* Passing false means that the caller is responsible for
* calling nsIContentAnalysisResponse::acknowledge(), unless the request is cancelled.
*/
[implicit_jscontext]
Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar);
Promise analyzeContentRequest(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge);
/**
* Same functionality as AnalyzeContentRequest(), but more convenient to call
* from C++ since it takes a callback instead of returning a Promise.
*
* @param aCar
* The request to analyze.
* @param aAutoAcknowledge
* Whether to send an acknowledge message to the agent after the agent sends a response.
* Passing false means that the caller is responsible for
* calling nsIContentAnalysisResponse::acknowledge(), unless the request is cancelled.
* @param callback
* Callbacks to be called when the agent sends a response message (or when there is an error).
*/
void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in nsIContentAnalysisCallback callback);
void analyzeContentRequestCallback(in nsIContentAnalysisRequest aCar, in bool aAutoAcknowledge, in nsIContentAnalysisCallback callback);
/**
* Cancels the request that is in progress. This may not actually cancel the request
* with the analysis server, but it means that Gecko will immediately act like the request
* was denied.
*
* @param aRequestToken
* The token for the request to cancel.
*/
void cancelContentAnalysisRequest(in ACString aRequestToken);
};