Commit c3e2bfe5 authored by Dominique Fauteux-Chapleau's avatar Dominique Fauteux-Chapleau Committed by Commit Bot

Add DLP warning upload UI for Deep Scanning

The UI now offers the possibility to bypass DLP warnings via buttons in
the dialog. The button interactions are tested via a browser test.

This involves a few refactors:
- DeepScanUploadStatus becomes DeepScanningFinalResult in order to
  clearly represent what to show the user.
- There is now a 4th DeepScanningDialogStatus to represent bypassable
  warnings.
- The callback to allow/deny a paste/upload/drag is delayed in the
  warning case.
- The "Cancel" override in DeepScanningDialogViews is replaced by using
  set_cancel_callback instead since the virtual method approach is
  deprecated.

Bug: 1044290
Change-Id: I477db8ba06dec26dc12ebc17fdfce94d019787d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2072584
Commit-Queue: Dominique Fauteux-Chapleau <domfc@chromium.org>
Reviewed-by: default avatarRoger Tawa <rogerta@chromium.org>
Cr-Commit-Position: refs/heads/master@{#747354}
parent dfc8b00b
......@@ -10473,9 +10473,27 @@ Please help our engineers fix this problem. Tell us what happened right before y
<message name="IDS_DEEP_SCANNING_DIALOG_DRAG_FILES_FAILURE_MESSAGE" desc="Message shown in tab modal dialog after performing a deep scan of dragged files when they doesn't comply with enterprise security policies">
These files violate your organization's security policies and can't be dropped.
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_UPLOAD_WARNING_MESSAGE" desc="Message shown in tab modal dialog after performing a deep scan of uploaded data when it doesn't comply with enterprise security policies to warn the user and allow them to proceed or not">
This upload violates your organization's security policies. Do you want to proceed anyway?
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_PASTE_WARNING_MESSAGE" desc="Message shown in tab modal dialog after performing a deep scan of pasted data when it doesn't comply with enterprise security policies to warn the user and allow them to proceed or not">
This pasted data violates your organization's security policies. Do you want to proceed anyway?
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_DRAG_DATA_WARNING_MESSAGE" desc="Message shown in tab modal dialog after performing a deep scan of dragged data when it doesn't comply with enterprise security policies to warn the user and allow them to proceed or not">
This dropped data violates your organization's security policies. Do you want to proceed anyway?
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_DRAG_FILES_WARNING_MESSAGE" desc="Message shown in tab modal dialog after performing a deep scan of dragged files when they doesn't comply with enterprise security policies to warn the user and allow them to proceed or not">
These dropped files violate your organization's security policies. Do you want to proceed anyway?
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_TIMEOUT_MESSAGE" desc="Message shown in tab modal dialog after a deep scan times out.">
Something went wrong. Scanning could not be completed. Please try again.
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_PROCEED_BUTTON" desc="Text shown in the proceed button of a tab modal dialog after performing a deep scan of data to ignore the obtained warning and proceed with the upload.">
Proceed anyway
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_CANCEL_WARNING_BUTTON" desc="Text shown in the cancel button of a tab modal dialog after performing a deep scan of data to acknowledge the obtained warning and close the dialog.">
Cancel
</message>
<message name="IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON" desc="Text shown in the cancel button of a tab modal dialog while performing a deep scan of data to see if it complies with enterprise policies">
Cancel upload
</message>
......
......@@ -29,6 +29,7 @@
#include "chrome/browser/file_util_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_dialog_views.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
#include "chrome/browser/safe_browsing/dm_token_utils.h"
#include "chrome/browser/safe_browsing/download_protection/check_client_download_request.h"
#include "chrome/grit/generated_resources.h"
......@@ -166,13 +167,28 @@ bool DlpTriggeredRulesOK(
for (int i = 0; i < verdict.triggered_rules_size(); ++i) {
if (verdict.triggered_rules(i).action() ==
DlpDeepScanningVerdict::TriggeredRule::BLOCK) {
DlpDeepScanningVerdict::TriggeredRule::BLOCK ||
verdict.triggered_rules(i).action() ==
DlpDeepScanningVerdict::TriggeredRule::WARN) {
return false;
}
}
return true;
}
bool ShouldShowWarning(const DlpDeepScanningVerdict& verdict) {
// Show a warning if one of the triggered rules is WARN and no other rule is
// BLOCK.
auto rules = verdict.triggered_rules();
bool no_block = std::all_of(rules.begin(), rules.end(), [](const auto& rule) {
return rule.action() != DlpDeepScanningVerdict::TriggeredRule::BLOCK;
});
bool warning = std::any_of(rules.begin(), rules.end(), [](const auto& rule) {
return rule.action() == DlpDeepScanningVerdict::TriggeredRule::WARN;
});
return no_block && warning;
}
std::string GetFileMimeType(base::FilePath path) {
// TODO(crbug.com/1013252): Obtain a more accurate MimeType by parsing the
// file content.
......@@ -285,13 +301,54 @@ DeepScanningDialogDelegate::FileContents::operator=(
DeepScanningDialogDelegate::FileContents&& other) = default;
DeepScanningDialogDelegate::~DeepScanningDialogDelegate() = default;
void DeepScanningDialogDelegate::Cancel() {
void DeepScanningDialogDelegate::BypassWarnings() {
if (callback_.is_null())
return;
RecordDeepScanMetrics(access_point_,
base::TimeTicks::Now() - upload_start_time_, 0,
"CancelledByUser", false);
// Mark the full text as complying and report a warning bypass.
if (text_warning_) {
std::fill(result_.text_results.begin(), result_.text_results.end(), true);
int64_t content_size = 0;
for (const base::string16& entry : data_.text)
content_size += (entry.size() * sizeof(base::char16));
ReportSensitiveDataWarningBypass(
Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
web_contents_->GetLastCommittedURL(), "Text data", std::string(),
"text/plain",
extensions::SafeBrowsingPrivateEventRouter::kTriggerWebContentUpload,
content_size);
}
// Mark every "warning" file as complying and report a warning bypass.
for (size_t index : file_warnings_) {
result_.paths_results[index] = true;
ReportSensitiveDataWarningBypass(
Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
web_contents_->GetLastCommittedURL(), data_.paths[index].AsUTF8Unsafe(),
base::HexEncode(file_info_[index].sha256.data(),
file_info_[index].sha256.size()),
file_info_[index].mime_type,
extensions::SafeBrowsingPrivateEventRouter::kTriggerFileUpload,
file_info_[index].size);
}
RunCallback();
}
void DeepScanningDialogDelegate::Cancel(bool warning) {
if (callback_.is_null())
return;
// Don't report this upload as cancelled if the user didn't bypass the
// warning.
if (!warning) {
RecordDeepScanMetrics(access_point_,
base::TimeTicks::Now() - upload_start_time_, 0,
"CancelledByUser", false);
}
// Make sure to reject everything.
FillAllResultsWith(false);
......@@ -499,6 +556,16 @@ void DeepScanningDialogDelegate::StringRequestCallback(
DlpTriggeredRulesOK(response.dlp_scan_verdict());
std::fill(result_.text_results.begin(), result_.text_results.end(),
text_complies);
if (!text_complies) {
if (ShouldShowWarning(response.dlp_scan_verdict())) {
text_warning_ = true;
UpdateFinalResult(DeepScanningFinalResult::WARNING);
} else {
UpdateFinalResult(DeepScanningFinalResult::FAILURE);
}
}
MaybeCompleteScanRequest();
}
......@@ -508,6 +575,7 @@ void DeepScanningDialogDelegate::CompleteFileRequestCallback(
BinaryUploadService::Result result,
DeepScanningClientResponse response,
std::string mime_type) {
file_info_[index].mime_type = mime_type;
MaybeReportDeepScanningVerdict(
Profile::FromBrowserContext(web_contents_->GetBrowserContext()),
web_contents_->GetLastCommittedURL(), path.AsUTF8Unsafe(),
......@@ -528,10 +596,18 @@ void DeepScanningDialogDelegate::CompleteFileRequestCallback(
++file_result_count_;
if (!file_complies && result == BinaryUploadService::Result::FILE_TOO_LARGE)
upload_status_ = DeepScanUploadStatus::LARGE_FILES;
if (!file_complies && result == BinaryUploadService::Result::FILE_ENCRYPTED)
upload_status_ = DeepScanUploadStatus::ENCRYPTED_FILES;
if (!file_complies) {
if (result == BinaryUploadService::Result::FILE_TOO_LARGE) {
UpdateFinalResult(DeepScanningFinalResult::LARGE_FILES);
} else if (result == BinaryUploadService::Result::FILE_ENCRYPTED) {
UpdateFinalResult(DeepScanningFinalResult::ENCRYPTED_FILES);
} else if (ShouldShowWarning(response.dlp_scan_verdict())) {
file_warnings_.insert(index);
UpdateFinalResult(DeepScanningFinalResult::WARNING);
} else {
UpdateFinalResult(DeepScanningFinalResult::FAILURE);
}
}
MaybeCompleteScanRequest();
}
......@@ -694,17 +770,11 @@ void DeepScanningDialogDelegate::UploadFileForDeepScanning(
upload_service->MaybeUploadForDeepScanning(std::move(request));
}
bool DeepScanningDialogDelegate::CloseTabModalDialog() {
bool DeepScanningDialogDelegate::UpdateDialog() {
if (!dialog_)
return false;
auto is_true = [](bool x) { return x; };
bool success = std::all_of(result_.text_results.begin(),
result_.text_results.end(), is_true) &&
std::all_of(result_.paths_results.begin(),
result_.paths_results.end(), is_true);
dialog_->ShowResult(success, upload_status_);
dialog_->ShowResult(final_result_);
return true;
}
......@@ -712,9 +782,12 @@ void DeepScanningDialogDelegate::MaybeCompleteScanRequest() {
if (!text_request_complete_ || file_result_count_ < data_.paths.size())
return;
RunCallback();
// If showing the warning message, wait before running the callback. The
// callback will be called either in BypassWarnings or Cancel.
if (final_result_ != DeepScanningFinalResult::WARNING)
RunCallback();
if (!CloseTabModalDialog()) {
if (!UpdateDialog()) {
// No UI was shown. Delete |this| to cleanup.
delete this;
}
......@@ -735,4 +808,10 @@ void DeepScanningDialogDelegate::SetFileInfo(const base::FilePath& path,
file_info_[index].size = size;
}
void DeepScanningDialogDelegate::UpdateFinalResult(
DeepScanningFinalResult result) {
if (result < final_result_)
final_result_ = result;
}
} // namespace safe_browsing
......@@ -115,6 +115,9 @@ class DeepScanningDialogDelegate {
// File size in bytes. -1 represents an unknown size.
uint64_t size = 0;
// File mime type.
std::string mime_type;
};
// File contents used as input for |file_info_| and the BinaryUploadService.
......@@ -133,19 +136,24 @@ class DeepScanningDialogDelegate {
std::string sha256;
};
// Enum to identify special cases when deep scanning doesn't happen either
// because the file is too large or encrypted. This is used to show
// appropriate messages in the upload UI dialog to inform the user of why
// their file(s) we not scanned and blocked.
enum class DeepScanUploadStatus {
// The data was uploaded and scanned.
NORMAL,
// Enum to identify which message to show once scanning is complete. Ordered
// by precedence for when multiple files have conflicting results.
// TODO(crbug.com/1055785): Refactor this to whatever solution is chosen.
enum class DeepScanningFinalResult {
// Show that an issue was found and that the upload is blocked.
FAILURE = 0,
// Show that files were not uploaded since they were too large.
LARGE_FILES = 1,
// The file(s) were not uploaded since they were too large.
LARGE_FILES,
// Show that files were not uploaded since they were encrypted.
ENCRYPTED_FILES = 2,
// The file(s) were not uploaded since they were encrypted.
ENCRYPTED_FILES,
// Show that DLP checks failed, but that the user can proceed if they want.
WARNING = 3,
// Show that no issue was found and that the user may proceed.
SUCCESS = 4,
};
// Callback used with ShowForWebContents() that informs caller of verdict
......@@ -169,9 +177,15 @@ class DeepScanningDialogDelegate {
delete;
virtual ~DeepScanningDialogDelegate();
// 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();
// 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.
void Cancel();
// 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);
// Returns true if the deep scanning feature is enabled in the upload
// direction via enterprise policies. If the appropriate enterprise policies
......@@ -261,10 +275,10 @@ class DeepScanningDialogDelegate {
const base::FilePath& path,
std::unique_ptr<BinaryUploadService::Request> request);
// Closes the tab modal dialog. Returns false if the UI was not enabled to
// indicate no action was taken. Otherwise returns true.
// Virtual to override in tests.
virtual bool CloseTabModalDialog();
// 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
......@@ -288,6 +302,10 @@ class DeepScanningDialogDelegate {
DeepScanningClientResponse response,
std::string mime_type);
// Updates |final_result_| following the precedence established by the
// DeepScanningFinalResult enum.
void UpdateFinalResult(DeepScanningFinalResult message);
// The web contents that is attempting to access the data.
content::WebContents* web_contents_ = nullptr;
......@@ -298,6 +316,12 @@ class DeepScanningDialogDelegate {
Result result_;
std::vector<FileInfo> file_info_;
// Set to true if the full text got a DLP warning verdict.
bool text_warning_ = false;
// Indexes of files that got DLP warning verdicts.
std::set<size_t> file_warnings_;
// 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;
......@@ -316,9 +340,8 @@ class DeepScanningDialogDelegate {
// Access point to use to record UMA metrics.
DeepScanAccessPoint access_point_;
// Upload status indicating if one of the files was not scanned because of
// policies.
DeepScanUploadStatus upload_status_ = DeepScanUploadStatus::NORMAL;
// Scanning result to be shown to the user once every request is done.
DeepScanningFinalResult final_result_ = DeepScanningFinalResult::SUCCESS;
base::TimeTicks upload_start_time_;
......
......@@ -6,6 +6,7 @@
#include <memory>
#include "base/bind.h"
#include "base/task/post_task.h"
#include "cc/paint/paint_flags.h"
#include "chrome/browser/safe_browsing/cloud_content_scanning/deep_scanning_utils.h"
......@@ -147,7 +148,7 @@ class DeepScanningMessageView : public DeepScanningBaseView,
using DeepScanningBaseView::DeepScanningBaseView;
void Update() {
if (dialog()->is_failure())
if (dialog()->is_failure() || dialog()->is_warning())
SetEnabledColor(dialog()->GetSideImageBackgroundColor());
}
......@@ -193,9 +194,15 @@ base::string16 DeepScanningDialogViews::GetWindowTitle() const {
return base::string16();
}
bool DeepScanningDialogViews::Cancel() {
delegate_->Cancel();
return true;
void DeepScanningDialogViews::AcceptButtonCallback() {
DCHECK(delegate_);
DCHECK(is_warning());
delegate_->BypassWarnings();
}
void DeepScanningDialogViews::CancelButtonCallback() {
DCHECK(delegate_);
delegate_->Cancel(is_warning());
}
bool DeepScanningDialogViews::ShouldShowCloseButton() const {
......@@ -223,18 +230,28 @@ ui::ModalType DeepScanningDialogViews::GetModalType() const {
}
void DeepScanningDialogViews::ShowResult(
bool success,
DeepScanningDialogDelegate::DeepScanUploadStatus upload_status) {
DeepScanningDialogDelegate::DeepScanningFinalResult result) {
DCHECK(is_pending());
dialog_status_ = success ? DeepScanningDialogStatus::SUCCESS
: DeepScanningDialogStatus::FAILURE;
upload_status_ = upload_status;
final_result_ = result;
switch (final_result_) {
case DeepScanningDialogDelegate::DeepScanningFinalResult::ENCRYPTED_FILES:
case DeepScanningDialogDelegate::DeepScanningFinalResult::LARGE_FILES:
case DeepScanningDialogDelegate::DeepScanningFinalResult::FAILURE:
dialog_status_ = DeepScanningDialogStatus::FAILURE;
break;
case DeepScanningDialogDelegate::DeepScanningFinalResult::SUCCESS:
dialog_status_ = DeepScanningDialogStatus::SUCCESS;
break;
case DeepScanningDialogDelegate::DeepScanningFinalResult::WARNING:
dialog_status_ = DeepScanningDialogStatus::WARNING;
break;
}
// Do nothing if the pending dialog wasn't shown, the delayed |Show| callback
// will show the negative result later if that's the verdict.
if (!shown_) {
// Cleanup if the pending dialog wasn't shown and the verdict is safe.
if (success)
if (is_success())
delete this;
return;
}
......@@ -296,7 +313,7 @@ void DeepScanningDialogViews::UpdateDialog() {
}
if (observer_for_testing)
observer_for_testing->DialogUpdated(this, is_success());
observer_for_testing->DialogUpdated(this, final_result_);
// Cancel the dialog as it is updated in tests in the failure dialog case.
// This is necessary to terminate tests that end when the dialog is closed.
......@@ -347,16 +364,37 @@ void DeepScanningDialogViews::Resize(int height_to_add) {
void DeepScanningDialogViews::SetupButtons() {
// TODO(domfc): Add "Learn more" button on scan failure.
if (is_pending() || is_failure()) {
DialogDelegate::set_buttons(ui::DIALOG_BUTTON_CANCEL);
if (is_warning()) {
// Include the Ok and Cancel buttons if there is a bypassable warning.
DialogDelegate::set_buttons(ui::DIALOG_BUTTON_CANCEL |
ui::DIALOG_BUTTON_OK);
DialogDelegate::set_default_button(ui::DIALOG_BUTTON_CANCEL);
DialogDelegate::set_button_label(ui::DIALOG_BUTTON_CANCEL,
GetCancelButtonText());
DialogDelegate::set_cancel_callback(
base::BindOnce(&DeepScanningDialogViews::CancelButtonCallback,
weak_ptr_factory_.GetWeakPtr()));
DialogDelegate::set_button_label(ui::DIALOG_BUTTON_OK,
GetBypassWarningButtonText());
DialogDelegate::set_accept_callback(
base::BindOnce(&DeepScanningDialogViews::AcceptButtonCallback,
weak_ptr_factory_.GetWeakPtr()));
} else if (is_failure() || is_pending()) {
// Include the Cancel button when the scan is pending or failing.
DialogDelegate::set_buttons(ui::DIALOG_BUTTON_CANCEL);
DialogDelegate::set_default_button(ui::DIALOG_BUTTON_NONE);
DialogDelegate::set_button_label(ui::DIALOG_BUTTON_CANCEL,
GetCancelButtonText());
DialogDelegate::set_cancel_callback(
base::BindOnce(&DeepScanningDialogViews::CancelButtonCallback,
weak_ptr_factory_.GetWeakPtr()));
} else {
// Include no buttons otherwise.
DialogDelegate::set_buttons(ui::DIALOG_BUTTON_NONE);
}
// TODO(domfc): Add "Learn more" button setup for scan failures.
}
base::string16 DeepScanningDialogViews::GetDialogMessage() const {
......@@ -371,22 +409,44 @@ base::string16 DeepScanningDialogViews::GetDialogMessage() const {
case DeepScanningDialogStatus::SUCCESS:
text_id = IDS_DEEP_SCANNING_DIALOG_SUCCESS_MESSAGE;
break;
case DeepScanningDialogStatus::WARNING:
text_id = GetWarningMessageId();
break;
}
return l10n_util::GetStringUTF16(text_id);
}
base::string16 DeepScanningDialogViews::GetCancelButtonText() const {
if (is_pending()) {
return l10n_util::GetStringUTF16(
IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON);
int text_id;
switch (dialog_status_) {
case DeepScanningDialogStatus::SUCCESS:
NOTREACHED();
FALLTHROUGH;
case DeepScanningDialogStatus::PENDING:
text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_UPLOAD_BUTTON;
break;
case DeepScanningDialogStatus::FAILURE:
text_id = IDS_CLOSE;
break;
case DeepScanningDialogStatus::WARNING:
text_id = IDS_DEEP_SCANNING_DIALOG_CANCEL_WARNING_BUTTON;
break;
}
DCHECK(!is_success());
return l10n_util::GetStringUTF16(IDS_CLOSE);
return l10n_util::GetStringUTF16(text_id);
}
base::string16 DeepScanningDialogViews::GetBypassWarningButtonText() const {
DCHECK(is_warning());
return l10n_util::GetStringUTF16(IDS_DEEP_SCANNING_DIALOG_PROCEED_BUTTON);
}
void DeepScanningDialogViews::Show() {
DCHECK(!shown_);
DCHECK(is_pending() || is_failure());
// The only state that cannot be shown immediately is SUCCESS, the dialog
// doesn't appear in that case.
DCHECK(!is_success());
shown_ = true;
first_shown_timestamp_ = base::TimeTicks::Now();
......@@ -457,7 +517,7 @@ std::unique_ptr<views::View> DeepScanningDialogViews::CreateSideIcon() {
// The side icon is created either:
// - When the pending dialog is shown
// - When the response was fast enough that the failure dialog is shown first
DCHECK(is_pending() || !is_success());
DCHECK(!is_success());
// The icon left of the text has the appearance of a blue "Enterprise" logo
// with a spinner when the scan is pending.
......@@ -527,13 +587,13 @@ int DeepScanningDialogViews::GetPendingMessageId() const {
int DeepScanningDialogViews::GetFailureMessageId() const {
DCHECK(is_failure());
if (upload_status_ ==
DeepScanningDialogDelegate::DeepScanUploadStatus::LARGE_FILES) {
if (final_result_ ==
DeepScanningDialogDelegate::DeepScanningFinalResult::LARGE_FILES) {
return IDS_DEEP_SCANNING_DIALOG_LARGE_FILE_FAILURE_MESSAGE;
}
if (upload_status_ ==
DeepScanningDialogDelegate::DeepScanUploadStatus::ENCRYPTED_FILES) {
if (final_result_ ==
DeepScanningDialogDelegate::DeepScanningFinalResult::ENCRYPTED_FILES) {
return IDS_DEEP_SCANNING_DIALOG_ENCRYPTED_FILE_FAILURE_MESSAGE;
}
......@@ -553,6 +613,24 @@ int DeepScanningDialogViews::GetFailureMessageId() const {
}
}
int DeepScanningDialogViews::GetWarningMessageId() const {
DCHECK(is_warning());
switch (access_point_) {
case DeepScanAccessPoint::DOWNLOAD:
// This dialog should not appear on the download path. If it somehow does,
// treat it as an upload.
NOTREACHED();
FALLTHROUGH;
case DeepScanAccessPoint::UPLOAD:
return IDS_DEEP_SCANNING_DIALOG_UPLOAD_WARNING_MESSAGE;
case DeepScanAccessPoint::PASTE:
return IDS_DEEP_SCANNING_DIALOG_PASTE_WARNING_MESSAGE;
case DeepScanAccessPoint::DRAG_AND_DROP:
return is_file_scan_ ? IDS_DEEP_SCANNING_DIALOG_DRAG_FILES_WARNING_MESSAGE
: IDS_DEEP_SCANNING_DIALOG_DRAG_DATA_WARNING_MESSAGE;
}
}
const gfx::ImageSkia* DeepScanningDialogViews::GetTopImage() const {
const bool use_dark = color_utils::IsDark(GetBackgroundColor(GetWidget()));
const bool treat_as_text_paste =
......@@ -575,6 +653,7 @@ SkColor DeepScanningDialogViews::GetSideImageLogoColor() const {
ui::NativeTheme::kColorId_ThrobberSpinningColor);
case DeepScanningDialogStatus::SUCCESS:
case DeepScanningDialogStatus::FAILURE:
case DeepScanningDialogStatus::WARNING:
// In a result state the background will have the result's color, so the
// logo should have the same color as the background.
return GetBackgroundColor(widget);
......
......@@ -52,6 +52,11 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// and that the user may not proceed with their upload, drag-and-drop or
// paste.
FAILURE,
// The dialog is shown with a message indicating that the scan was a
// failure, but that the user may proceed with their upload, drag-and-drop
// or paste if they want to.
WARNING,
};
// TestObserver should be implemented by tests that need to track when certain
......@@ -75,8 +80,10 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// Called at the end of DeepScanningDialogViews::UpdateDialog. |result| is
// the value that UpdatedDialog used to transition from the pending state to
// the success or failure state.
virtual void DialogUpdated(DeepScanningDialogViews* views, bool result) {}
// the success/failure/warning state.
virtual void DialogUpdated(
DeepScanningDialogViews* views,
DeepScanningDialogDelegate::DeepScanningFinalResult result) {}
// Called at the end of DeepScanningDialogViews's destructor. |views| is a
// pointer to the DeepScanningDialogViews being destructed. It can be used
......@@ -102,7 +109,6 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// views::DialogDelegate:
base::string16 GetWindowTitle() const override;
bool Cancel() override;
bool ShouldShowCloseButton() const override;
views::View* GetContentsView() override;
views::Widget* GetWidget() override;
......@@ -112,9 +118,7 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// Updates the dialog with the result, and simply delete it from memory if
// nothing should be shown.
void ShowResult(
bool success,
DeepScanningDialogDelegate::DeepScanUploadStatus upload_status);
void ShowResult(DeepScanningDialogDelegate::DeepScanningFinalResult result);
// Accessors to simplify |dialog_status_| checking.
inline bool is_success() const {
......@@ -125,7 +129,11 @@ class DeepScanningDialogViews : public views::DialogDelegate {
return dialog_status_ == DeepScanningDialogStatus::FAILURE;
}
inline bool is_result() const { return is_success() || is_failure(); }
inline bool is_warning() const {
return dialog_status_ == DeepScanningDialogStatus::WARNING;
}
inline bool is_result() const { return !is_pending(); }
inline bool is_pending() const {
return dialog_status_ == DeepScanningDialogStatus::PENDING;
......@@ -159,9 +167,12 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// Returns the appropriate dialog message depending on |dialog_status_|.
base::string16 GetDialogMessage() const;
// Returns the appropriate dialog message depending on |dialog_status_|.
// Returns the text for the Cancel button depending on |dialog_status_|.
base::string16 GetCancelButtonText() const;
// Returns the text for the Ok button for the warning case.
base::string16 GetBypassWarningButtonText() const;
// Returns the appropriate paste top image ID depending on |dialog_status_|.
int GetPasteImageId(bool use_dark) const;
......@@ -176,9 +187,16 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// |is_file_scan_|.
int GetFailureMessageId() const;
// Returns the appropriate warning message ID depending on |access_point_| and
// |is_file_scan_|.
int GetWarningMessageId() const;
// Show the dialog. Sets |shown_| to true.
void Show();
void AcceptButtonCallback();
void CancelButtonCallback();
std::unique_ptr<DeepScanningDialogDelegate> delegate_;
content::WebContents* web_contents_;
......@@ -197,9 +215,9 @@ class DeepScanningDialogViews : public views::DialogDelegate {
// Used to show the appropriate dialog depending on the scan's status.
DeepScanningDialogStatus dialog_status_ = DeepScanningDialogStatus::PENDING;
// Used to show the appropriate message if the scan did not occur.
DeepScanningDialogDelegate::DeepScanUploadStatus upload_status_ =
DeepScanningDialogDelegate::DeepScanUploadStatus::NORMAL;
// Used to show the appropriate message.
DeepScanningDialogDelegate::DeepScanningFinalResult final_result_ =
DeepScanningDialogDelegate::DeepScanningFinalResult::SUCCESS;
// Used to animate dialog height changes.
std::unique_ptr<views::BoundsAnimator> bounds_animator_;
......
......@@ -80,7 +80,9 @@ class DeepScanningDialogViewsBehaviorBrowserTest
views_first_shown_ = true;
}
void DialogUpdated(DeepScanningDialogViews* views, bool result) override {
void DialogUpdated(
DeepScanningDialogViews* views,
DeepScanningDialogDelegate::DeepScanningFinalResult result) override {
DCHECK_EQ(views, dialog_);
dialog_updated_timestamp_ = base::TimeTicks::Now();
......@@ -93,13 +95,16 @@ class DeepScanningDialogViewsBehaviorBrowserTest
// The dialog can only be updated to the success or failure case.
EXPECT_TRUE(dialog_->is_result());
EXPECT_EQ(dialog_->is_success(), result);
bool is_success =
result == DeepScanningDialogDelegate::DeepScanningFinalResult::SUCCESS;
EXPECT_EQ(dialog_->is_success(), is_success);
EXPECT_EQ(dialog_->is_success(), expected_scan_result_);
// The dialog's buttons should be Cancel in the fail case and nothing in the
// success case.
ui::DialogButton expected_buttons =
result ? ui::DIALOG_BUTTON_NONE : ui::DIALOG_BUTTON_CANCEL;
ui::DialogButton expected_buttons = dialog_->is_success()
? ui::DIALOG_BUTTON_NONE
: ui::DIALOG_BUTTON_CANCEL;
EXPECT_EQ(expected_buttons, dialog_->GetDialogButtons());
// The dialog should only be updated once some time after being shown.
......@@ -213,6 +218,70 @@ class DeepScanningDialogViewsCancelPendingScanBrowserTest
base::HistogramTester histograms_;
};
// Tests the behavior of the dialog in the following ways:
// - It shows the appropriate buttons depending when showing a warning.
// - It calls the appropriate methods when the user bypasses/respects the
// warning.
// - It shows up in the warning state immediately if the response is fast.
class DeepScanningDialogViewsWarningBrowserTest
: public DeepScanningBrowserTestBase,
public DeepScanningDialogViews::TestObserver,
public testing::WithParamInterface<std::tuple<base::TimeDelta, bool>> {
public:
DeepScanningDialogViewsWarningBrowserTest() {
DeepScanningDialogViews::SetObserverForTesting(this);
}
void ViewsFirstShown(DeepScanningDialogViews* views,
base::TimeTicks timestamp) override {
// The dialog is first shown in the pending state if the response is slow or
// in the warning state if it's not slow.
ASSERT_TRUE(views->is_pending() || views->is_warning());
// If the warning dialog was shown immediately, ensure that was expected and
// set |warning_shown_immediately_| for future assertions.
if (views->is_warning()) {
ASSERT_EQ(response_delay(), kNoDelay);
warning_shown_immediately_ = true;
ASSERT_EQ(views->GetDialogButtons(),
ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL);
SimulateClickAndEndTest(views);
} else {
ASSERT_EQ(response_delay(), kSmallDelay);
ASSERT_EQ(views->GetDialogButtons(), ui::DIALOG_BUTTON_CANCEL);
}
}
void DialogUpdated(
DeepScanningDialogViews* views,
DeepScanningDialogDelegate::DeepScanningFinalResult result) override {
ASSERT_FALSE(warning_shown_immediately_);
ASSERT_TRUE(views->is_warning());
// The dialog's buttons should be Ok and Cancel.
ASSERT_EQ(ui::DIALOG_BUTTON_OK | ui::DIALOG_BUTTON_CANCEL,
views->GetDialogButtons());
SimulateClickAndEndTest(views);
}
void SimulateClickAndEndTest(DeepScanningDialogViews* views) {
if (user_bypasses_warning())
views->AcceptDialog();
else
views->CancelDialog();
CallQuitClosure();
}
base::TimeDelta response_delay() { return std::get<0>(GetParam()); }
bool user_bypasses_warning() { return std::get<1>(GetParam()); }
private:
bool warning_shown_immediately_ = false;
};
} // namespace
IN_PROC_BROWSER_TEST_P(DeepScanningDialogViewsBehaviorBrowserTest, Test) {
......@@ -336,4 +405,59 @@ IN_PROC_BROWSER_TEST_F(DeepScanningDialogViewsCancelPendingScanBrowserTest,
ValidateMetrics();
}
IN_PROC_BROWSER_TEST_P(DeepScanningDialogViewsWarningBrowserTest, Test) {
// Setup policies.
SetDlpPolicy(CHECK_UPLOADS);
SetWaitPolicy(DELAY_UPLOADS);
// Setup the DLP warning response.
DeepScanningClientResponse response;
response.mutable_dlp_scan_verdict()->set_status(
DlpDeepScanningVerdict::SUCCESS);
DlpDeepScanningVerdict::TriggeredRule* rule =
response.mutable_dlp_scan_verdict()->add_triggered_rules();
rule->set_rule_name("warning_rule_name");
rule->set_action(DlpDeepScanningVerdict::TriggeredRule::WARN);
SetStatusCallbackResponse(response);
// Set up delegate test values.
FakeDeepScanningDialogDelegate::SetResponseDelay(response_delay());
SetUpDelegate();
bool called = false;
base::RunLoop run_loop;
SetQuitClosure(run_loop.QuitClosure());
DeepScanningDialogDelegate::Data data;
data.do_dlp_scan = true;
data.text.emplace_back(base::UTF8ToUTF16("foo"));
data.text.emplace_back(base::UTF8ToUTF16("bar"));
data.paths.emplace_back(FILE_PATH_LITERAL("/tmp/foo.doc"));
data.paths.emplace_back(FILE_PATH_LITERAL("/tmp/bar.doc"));
DeepScanningDialogDelegate::ShowForWebContents(
browser()->tab_strip_model()->GetActiveWebContents(), std::move(data),
base::BindOnce(
[](bool* called, bool user_bypasses_warning,
const DeepScanningDialogDelegate::Data& data,
const DeepScanningDialogDelegate::Result& result) {
ASSERT_EQ(result.text_results.size(), 2u);
ASSERT_EQ(result.text_results[0], user_bypasses_warning);
ASSERT_EQ(result.text_results[1], user_bypasses_warning);
ASSERT_EQ(result.paths_results.size(), 2u);
ASSERT_EQ(result.paths_results[0], user_bypasses_warning);
ASSERT_EQ(result.paths_results[1], user_bypasses_warning);
*called = true;
},
&called, user_bypasses_warning()),
DeepScanAccessPoint::UPLOAD);
run_loop.Run();
EXPECT_TRUE(called);
}
INSTANTIATE_TEST_SUITE_P(
DeepScanningDialogViewsWarningBrowserTest,
DeepScanningDialogViewsWarningBrowserTest,
testing::Combine(testing::Values(kNoDelay, kSmallDelay), testing::Bool()));
} // namespace safe_browsing
......@@ -129,6 +129,23 @@ void MaybeReportDeepScanningVerdict(Profile* profile,
}
}
void ReportSensitiveDataWarningBypass(Profile* profile,
const GURL& url,
const std::string& file_name,
const std::string& download_digest_sha256,
const std::string& mime_type,
const std::string& trigger,
const int64_t content_size) {
DCHECK(std::all_of(download_digest_sha256.begin(),
download_digest_sha256.end(), [](const char& c) {
return (c >= '0' && c <= '9') ||
(c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}));
extensions::SafeBrowsingPrivateEventRouterFactory::GetForProfile(profile)
->OnSensitiveDataWarningBypassed(url, file_name, download_digest_sha256,
mime_type, trigger, content_size);
}
std::string DeepScanAccessPointToString(DeepScanAccessPoint access_point) {
switch (access_point) {
case DeepScanAccessPoint::DOWNLOAD:
......
......@@ -27,6 +27,18 @@ void MaybeReportDeepScanningVerdict(Profile* profile,
BinaryUploadService::Result result,
DeepScanningClientResponse response);
// Helper function to report the user bypassed a warning to the enterprise
// admin. This is split from MaybeReportDeepScanningVerdict since it happens
// after getting a response. |download_digest_sha256| must be encoded using
// base::HexEncode.
void ReportSensitiveDataWarningBypass(Profile* profile,
const GURL& url,
const std::string& file_name,
const std::string& download_digest_sha256,
const std::string& mime_type,
const std::string& trigger,
const int64_t content_size);
// Access points used to record UMA metrics and specify which code location is
// initiating a deep scan. Any new caller of
// DeepScanningDialogDelegate::ShowForWebContents should add an access point
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment