blob: e4ba18fe331ab8e6f922b6e30e02378d5e75e39e [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_H_
#define CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_H_
#include <memory>
#include <string>
#include <vector>
#include "base/files/file_path.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/read_only_shared_memory_region.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "chrome/browser/enterprise/connectors/analysis/analysis_settings.h"
#include "chrome/browser/enterprise/connectors/analysis/content_analysis_delegate_base.h"
#include "chrome/browser/enterprise/connectors/common.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/binary_upload_service.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "components/enterprise/common/proto/connectors.pb.h"
#include "url/gurl.h"
class Profile;
namespace content {
class WebContent;
} // namespace content
namespace enterprise_connectors {
class ContentAnalysisDialog;
class FilesRequestHandler;
// A BinaryUploadService::Request implementation that gets the data to scan
// from a string. This class is public to allow testing.
class StringAnalysisRequest
: public safe_browsing::BinaryUploadService::Request {
public:
StringAnalysisRequest(
CloudOrLocalAnalysisSettings settings,
std::string text,
safe_browsing::BinaryUploadService::ContentAnalysisCallback callback);
~StringAnalysisRequest() override;
StringAnalysisRequest(const StringAnalysisRequest&) = delete;
StringAnalysisRequest& operator=(const StringAnalysisRequest&) = delete;
// safe_browsing::BinaryUploadService::Request implementation.
void GetRequestData(DataCallback callback) override;
private:
Data data_;
safe_browsing::BinaryUploadService::Result result_ =
safe_browsing::BinaryUploadService::Result::FILE_TOO_LARGE;
};
// A class that performs deep scans of data (for example malicious or sensitive
// content checks) before allowing a page to access it.
//
// If the UI is enabled, then a dialog is shown and blocks user interactions
// with the page until a scan verdict is obtained.
//
// If the UI is not enabled, then the dialog is not shown and the delegate
// proceeds as if all data (strings and files) have successfully passed the
// deep checks. However the checks are still performed in the background and
// verdicts reported, implementing an audit-only mode.
//
// Users of this class normally call IsEnabled() first to determine if deep
// scanning is required for the given web page. If so, the caller fills in
// the appropriate data members of `Data` and then call CreateForWebContents()
// to start the scan.
//
// Example:
//
// contents::WebContent* contents = ...
// Profile* profile = ...
// safe_browsing::ContentAnalysisDelegate::Data data;
// if (safe_browsing::ContentAnalysisDelegate::IsEnabled(
// profile, contents->GetLastCommittedURL(), &data)) {
// data.text.push_back(...); // As needed.
// data.paths.push_back(...); // As needed.
// safe_browsing::ContentAnalysisDelegate::CreateForWebContents(
// contents, std::move(data), base::BindOnce(...));
// }
class ContentAnalysisDelegate : public ContentAnalysisDelegateBase {
public:
// Used as an input to CreateForWebContents() to describe what data needs
// deeper scanning. Any members can be empty.
struct Data {
Data();
Data(Data&& other);
Data& operator=(Data&&);
~Data();
// URL of the page that is to receive sensitive data.
GURL url;
// UTF-8 encoded text data to scan, such as plain text, URLs, HTML, etc.
std::vector<std::string> text;
// Binary image data to scan, such as png, svg, etc (here we assume the data
// struct holds one image only).
std::string image;
// List of files to scan.
std::vector<base::FilePath> paths;
// Page to be printed to scan.
base::ReadOnlySharedMemoryRegion page;
// TODO(b/280457160): Set printer name once scan after printer preview
// is done.
// Printer name of the page being sent to, empty for non-print actions.
std::string printer_name;
// TODO(b/280457160): Set printer type once scan after printer preview
// is done.
// Printer type of the page being sent to, the default value is UNKNOWN.
ContentMetaData::PrintMetadata::PrinterType printer_type =
ContentMetaData::PrintMetadata::UNKNOWN;
// The settings to use for the analysis of the data in this struct.
AnalysisSettings settings;
};
// Result of deep scanning. Each Result contains the verdicts of deep scans
// specified by one Data.
struct Result {
Result();
Result(Result&& other);
~Result();
// String data result. Each element in this array is the result for the
// corresponding Data::text element. A value of true means the text
// complies with all checks and is safe to be used. A false means the
// text does not comply with all checks and the caller should not use it.
std::vector<bool> text_results;
// Image data result. A value of true means the image complies with all
// checks and is safe to be used. A false means the image does not comply
// with all checks and the caller should not use it.
bool image_result;
// File data result. Each element in this array is the result for the
// corresponding Data::paths element. A value of true means the file
// complies with all checks and is safe to be used. A false means the
// file does not comply with all checks and the caller should not use it.
std::vector<bool> paths_results;
// Page data result. A value of true means the page complies with checks and
// can be printed, and a value of false means it shouldn't be allowed to
// print.
bool page_result;
};
// Callback used with CreateForWebContents() that informs caller of verdict
// of deep scans.
using CompletionCallback =
base::OnceCallback<void(const Data& data, Result& result)>;
// A factory function used in tests to create fake ContentAnalysisDelegate
// instances.
using Factory =
base::RepeatingCallback<std::unique_ptr<ContentAnalysisDelegate>(
content::WebContents*,
Data,
CompletionCallback)>;
ContentAnalysisDelegate(const ContentAnalysisDelegate&) = delete;
ContentAnalysisDelegate& operator=(const ContentAnalysisDelegate&) = delete;
~ContentAnalysisDelegate() override;
// ContentAnalysisDelegateBase:
// Called when the user decides to bypass the verdict they obtained from DLP.
// This will allow the upload of files marked as DLP warnings.
void BypassWarnings(
absl::optional<std::u16string> user_justification) override;
// Called when the user decides to cancel the file upload. This will stop the
// upload to Chrome since the scan wasn't allowed to complete. If `warning` is
// true, it means the user clicked Cancel after getting a warning, meaning the
// "CancelledByUser" metrics should not be recorded.
void Cancel(bool warning) override;
absl::optional<std::u16string> GetCustomMessage() const override;
absl::optional<GURL> GetCustomLearnMoreUrl() const override;
bool BypassRequiresJustification() const override;
std::u16string GetBypassJustificationLabel() const override;
absl::optional<std::u16string> OverrideCancelButtonText() const override;
// Returns true if the deep scanning feature is enabled in the upload
// direction via enterprise policies. If the appropriate enterprise policies
// are not set this feature is not enabled.
//
// The scan tags in `data.settings.tags` are filled in as needed. If that set
// is not empty and there is at least 1 tag to scan for, the function returns
// true, otherwise it returns false.
static bool IsEnabled(Profile* profile,
GURL url,
Data* data,
AnalysisConnector connector);
// Entry point for starting a deep scan, with the callback being called once
// all results are available. When the UI is enabled, a tab-modal dialog
// is shown while the scans proceed in the background. When the UI is
// disabled, the callback will immedaitely inform the callers that all data
// has successfully passed the checks, even though the checks will proceed
// in the background.
//
// Whether the UI is enabled or not, verdicts of the scan will be reported.
static void CreateForWebContents(
content::WebContents* web_contents,
Data data,
CompletionCallback callback,
safe_browsing::DeepScanAccessPoint access_point);
// In tests, sets a factory function for creating fake
// ContentAnalysisDelegates.
static void SetFactoryForTesting(Factory factory);
static void ResetFactoryForTesting();
// Showing the UI is not possible in unit tests, call this to disable it.
static void DisableUIForTesting();
// Add a callback to allow tests to validate `AckAllRequests` will send the
// appropriate actions.
using OnAckAllRequestsCallback = base::OnceCallback<void(
const std::map<std::string,
ContentAnalysisAcknowledgement::FinalAction>&)>;
static void SetOnAckAllRequestsCallbackForTesting(
OnAckAllRequestsCallback callback);
protected:
ContentAnalysisDelegate(content::WebContents* web_contents,
Data data,
CompletionCallback callback,
safe_browsing::DeepScanAccessPoint access_point);
// Callbacks from uploading data. Protected so they can be called from
// testing derived classes.
// TODO(crbug.com/1324892): Adapt once TextRequestHandler and
// PageRequestHandler are created and move reporting to the RequestHandlers.
void StringRequestCallback(safe_browsing::BinaryUploadService::Result result,
ContentAnalysisResponse response);
void ImageRequestCallback(safe_browsing::BinaryUploadService::Result result,
ContentAnalysisResponse response);
void PageRequestCallback(safe_browsing::BinaryUploadService::Result result,
ContentAnalysisResponse response);
// Callback called after all files are scanned by the FilesRequestHandler.
void FilesRequestCallback(std::vector<RequestHandlerResult> results);
base::WeakPtr<ContentAnalysisDelegate> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
FilesRequestHandler* GetFilesRequestHandlerForTesting();
const Data& GetDataForTesting() { return data_; }
const std::map<std::string, ContentAnalysisAcknowledgement::FinalAction>&
GetFinalActionsForTesting() {
return final_actions_;
}
// Methods to either show the final result in the analysis dialog and to
// cancel the dialog. These methods are protected and virtual for testing.
// Returns false if the UI was not enabled to indicate no action was taken.
virtual bool ShowFinalResultInDialog();
virtual bool CancelDialog();
private:
// Uploads data for deep scanning. Returns true if uploading is occurring in
// the background and false if there is nothing to do. Sets `data_uploaded_`
// to true right before returning.
bool UploadData();
// Prepares an upload request for the text in `data_`. If `data_.text` is
// empty, this method does nothing.
// TODO(crbug.com/1324892): Move to TextRequestHandler.
void PrepareTextRequest();
// Prepares an upload request for the image in `data_`. If `data_.image` is
// empty, this method does nothing.
void PrepareImageRequest();
// Prepares an upload request for the printed page bytes in `data_`. If there
// aren't any, this method does nothing.
// TODO(crbug.com/1324892): Move to PageRequestHandler.
void PreparePageRequest();
// Adds required fields to `request` before sending it to the binary upload
// service.
// TODO(crbug.com/1324892): Remove once TextRequestHandler and
// PageRequestHandler are created.
void PrepareRequest(AnalysisConnector connector,
safe_browsing::BinaryUploadService::Request* request);
// Fills the arrays in `result_` with the given boolean status.
void FillAllResultsWith(bool status);
// Upload the request for deep scanning using the binary upload service.
// These methods exist so they can be overridden in tests as needed.
// The `result` argument exists as an optimization to finish the request early
// when the result is known in advance to avoid using the upload service.
// TODO(crbug.com/1324892): Remove once TextRequestHandler and
// PageRequestHandler are created.
virtual void UploadTextForDeepScanning(
std::unique_ptr<safe_browsing::BinaryUploadService::Request> request);
virtual void UploadImageForDeepScanning(
std::unique_ptr<safe_browsing::BinaryUploadService::Request> request);
virtual void UploadPageForDeepScanning(
std::unique_ptr<safe_browsing::BinaryUploadService::Request> request);
// Updates the tab modal dialog to show the scanning results. Returns false if
// the UI was not enabled to indicate no action was taken. Virtual to override
// in tests.
virtual bool UpdateDialog();
// Calls the CompletionCallback `callback_` if all requests associated with
// scans of `data_` are finished. This function may delete `this` so no
// members should be accessed after this call.
void MaybeCompleteScanRequest();
// Runs `callback_` with the calculated results if it is non null.
// `callback_` is cleared after being run.
void RunCallback();
// Updates `final_result_` following the precedence established by the
// FinalResult enum.
void UpdateFinalResult(FinalContentAnalysisResult message,
const std::string& tag);
// Send an acknowledgement to the service provider of the final result
// for the requests of this ContentAnalysisDelegate instance.
void AckAllRequests();
// Returns the BinaryUploadService used to upload content for deep scanning.
// Virtual to override in tests.
virtual safe_browsing::BinaryUploadService* GetBinaryUploadService();
// The Profile corresponding to the pending scan request(s).
raw_ptr<Profile> profile_ = nullptr;
// The GURL corresponding to the page where the scan triggered.
GURL url_;
// The title corresponding to the WebContents triggering the scan.
std::string title_;
// The unique ID for keeping track of each user action.
std::string user_action_id_;
// Description of the data being scanned and the results of the scan.
Data data_;
Result result_;
// Set to true if the full text got a DLP warning verdict.
bool text_warning_ = false;
ContentAnalysisResponse text_response_;
// Set to true if the full image got a DLP warning verdict.
bool image_warning_ = false;
ContentAnalysisResponse image_response_;
// Indices of warned files.
std::vector<size_t> warned_file_indices_;
// Set to true if the printed page got a DLP warning verdict.
bool page_warning_ = false;
ContentAnalysisResponse page_response_;
// Stores the scanned page's size since it moves from `data_` to be uploaded.
// TODO(crbug.com/1324892): Move to PageRequestHandler.
int64_t page_size_bytes_ = 0;
// Stores the total number of requests associated with one user action.
int64_t total_requests_count_ = 0;
// Set to true once the scan of text has completed. If the scan request has
// no text requiring deep scanning, this is set to true immediately.
bool text_request_complete_ = false;
// Set to true once the scan of image has completed. If the scan request has
// no image requiring deep scanning, this is set to true immediately.
bool image_request_complete_ = false;
// Set to true once all file scans have completed. If the scan requests have
// no files requiring deep scanning, this is set to true immediately.
bool files_request_complete_ = false;
// Set to true once the scan of a printed page has completed. If the scan
// request has no page requiring deep scanning, this is set to true
// immediately.
bool page_request_complete_ = false;
// Called once all text and files have completed deep scanning.
CompletionCallback callback_;
// Pointer to UI when enabled.
raw_ptr<ContentAnalysisDialog> dialog_ = nullptr;
// Access point to use to record UMA metrics.
safe_browsing::DeepScanAccessPoint access_point_;
// Scanning result to be shown to the user once every request is done.
FinalContentAnalysisResult final_result_ =
FinalContentAnalysisResult::SUCCESS;
// The tag (dlp, malware, etc) of the result that triggered the verdict
// represented by `final_result_`.
std::string final_result_tag_;
// Set to true at the end of UploadData to indicate requests have been made
// for every file/text. This is read to ensure `this` isn't deleted too early.
bool data_uploaded_ = false;
// Responsible for opening and scanning multiple files on parallel threads.
// Always nullptr for non-file content scanning.
std::unique_ptr<FilesRequestHandler> files_request_handler_;
// A mapping of request tokens to ack final actions for all requests that make
// up the user action represented by this ContentAnalysisDelegate.
std::map<std::string, ContentAnalysisAcknowledgement::FinalAction>
final_actions_;
// Results returned from files_request_handler_.
std::vector<RequestHandlerResult> files_request_results_;
// Result updated in StringRequestCallback().
RequestHandlerResult string_request_result_;
// Result updated in ImageRequestCallback().
RequestHandlerResult image_request_result_;
// Indicate that `callback_` is currently being called. This is almost always
// false, but in some cases UI thread tasks can run while `callback_` is not
// over due showing UI, such as during native print dialogs.
bool callback_running_ = false;
// Indicates that `this` can be deleted right away. This is used with
// `callback_running_` to handle race conditions where non-blocking scans
// should wait before deleting `this`.
bool all_work_done_ = false;
// Content type of the page that triggered the action.
std::string page_content_type_;
base::TimeTicks upload_start_time_;
base::WeakPtrFactory<ContentAnalysisDelegate> weak_ptr_factory_{this};
};
} // namespace enterprise_connectors
#endif // CHROME_BROWSER_ENTERPRISE_CONNECTORS_ANALYSIS_CONTENT_ANALYSIS_DELEGATE_H_