1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "base/bind.h"
6 #include "base/command_line.h"
7 #include "base/files/file_path.h"
8 #include "base/macros.h"
9 #include "build/build_config.h"
10 #include "chrome/browser/download/download_danger_prompt.h"
11 #include "chrome/browser/profiles/profile.h"
12 #include "chrome/browser/safe_browsing/download_protection/download_protection_service.h"
13 #include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
14 #include "chrome/browser/ui/browser.h"
15 #include "chrome/browser/ui/browser_commands.h"
16 #include "chrome/browser/ui/browser_tabstrip.h"
17 #include "chrome/browser/ui/tabs/tab_strip_model.h"
18 #include "chrome/browser/ui/test/test_browser_dialog.h"
19 #include "chrome/test/base/in_process_browser_test.h"
20 #include "chrome/test/base/ui_test_utils.h"
21 #include "components/download/public/common/mock_download_item.h"
22 #include "components/safe_browsing/core/db/database_manager.h"
23 #include "components/safe_browsing/core/proto/csd.pb.h"
24 #include "content/public/browser/download_item_utils.h"
25 #include "content/public/test/browser_test.h"
26 #include "testing/gmock/include/gmock/gmock.h"
27 #include "testing/gtest/include/gtest/gtest.h"
28 #include "url/gurl.h"
29 
30 using ::testing::_;
31 using ::testing::ByRef;
32 using ::testing::Eq;
33 using ::testing::Return;
34 using ::testing::ReturnRef;
35 using ::testing::SaveArg;
36 
37 namespace safe_browsing {
38 
39 namespace {
40 
41 const char kTestDownloadUrl[] = "http://evildownload.com";
42 const char kDownloadResponseToken[] = "default_token";
43 
44 }  // namespace
45 
46 class DownloadDangerPromptTest : public InProcessBrowserTest {
47  public:
DownloadDangerPromptTest()48   DownloadDangerPromptTest()
49       : prompt_(nullptr),
50         expected_action_(DownloadDangerPrompt::CANCEL),
51         did_receive_callback_(false),
52         test_safe_browsing_factory_(
53             std::make_unique<TestSafeBrowsingServiceFactory>()) {}
54 
~DownloadDangerPromptTest()55   ~DownloadDangerPromptTest() override {}
56 
SetUp()57   void SetUp() override {
58     SafeBrowsingService::RegisterFactory(test_safe_browsing_factory_.get());
59     InProcessBrowserTest::SetUp();
60   }
61 
TearDown()62   void TearDown() override {
63     SafeBrowsingService::RegisterFactory(nullptr);
64     InProcessBrowserTest::TearDown();
65   }
66 
67   // Opens a new tab and waits for navigations to finish. If there are pending
68   // navigations, the constrained prompt might be dismissed when the navigation
69   // completes.
OpenNewTab()70   void OpenNewTab() {
71     ui_test_utils::NavigateToURLWithDisposition(
72         browser(), GURL("about:blank"),
73         WindowOpenDisposition::NEW_FOREGROUND_TAB,
74         ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
75             ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
76   }
77 
SetUpExpectations(const DownloadDangerPrompt::Action & expected_action,const download::DownloadDangerType & danger_type,const ClientDownloadResponse::Verdict & download_verdict,const std::string & token,bool from_download_api)78   void SetUpExpectations(
79       const DownloadDangerPrompt::Action& expected_action,
80       const download::DownloadDangerType& danger_type,
81       const ClientDownloadResponse::Verdict& download_verdict,
82       const std::string& token,
83       bool from_download_api) {
84     did_receive_callback_ = false;
85     expected_action_ = expected_action;
86     SetUpDownloadItemExpectations(danger_type, token);
87     SetUpSafeBrowsingReportExpectations(
88         expected_action == DownloadDangerPrompt::ACCEPT,
89         download_verdict,
90         token,
91         from_download_api);
92     CreatePrompt(from_download_api);
93   }
94 
VerifyExpectations(bool should_send_report)95   void VerifyExpectations(bool should_send_report) {
96     content::RunAllPendingInMessageLoop();
97     // At the end of each test, we expect no more activity from the prompt. The
98     // prompt shouldn't exist anymore either.
99     EXPECT_TRUE(did_receive_callback_);
100     EXPECT_FALSE(prompt_);
101 
102     if (should_send_report) {
103       EXPECT_EQ(expected_serialized_report_,
104                 test_safe_browsing_factory_->test_safe_browsing_service()
105                     ->serilized_download_report());
106     } else {
107       EXPECT_TRUE(test_safe_browsing_factory_->test_safe_browsing_service()
108                       ->serilized_download_report()
109                       .empty());
110     }
111     testing::Mock::VerifyAndClearExpectations(&download_);
112     test_safe_browsing_factory_->test_safe_browsing_service()
113         ->ClearDownloadReport();
114   }
115 
SimulatePromptAction(DownloadDangerPrompt::Action action)116   void SimulatePromptAction(DownloadDangerPrompt::Action action) {
117     prompt_->InvokeActionForTesting(action);
118   }
119 
download()120   download::MockDownloadItem& download() { return download_; }
121 
prompt()122   DownloadDangerPrompt* prompt() { return prompt_; }
123 
124  private:
SetUpDownloadItemExpectations(const download::DownloadDangerType & danger_type,const std::string & token)125   void SetUpDownloadItemExpectations(
126       const download::DownloadDangerType& danger_type,
127       const std::string& token) {
128     EXPECT_CALL(download_, GetFileNameToReportUser()).WillRepeatedly(Return(
129         base::FilePath(FILE_PATH_LITERAL("evil.exe"))));
130     EXPECT_CALL(download_, GetDangerType()).WillRepeatedly(Return(danger_type));
131     auto token_obj =
132         std::make_unique<DownloadProtectionService::DownloadPingToken>(token);
133     download_.SetUserData(DownloadProtectionService::kDownloadPingTokenKey,
134                           std::move(token_obj));
135   }
136 
SetUpSafeBrowsingReportExpectations(bool did_proceed,const ClientDownloadResponse::Verdict & download_verdict,const std::string & token,bool from_download_api)137   void SetUpSafeBrowsingReportExpectations(
138       bool did_proceed,
139       const ClientDownloadResponse::Verdict& download_verdict,
140       const std::string& token,
141       bool from_download_api) {
142     ClientSafeBrowsingReportRequest expected_report;
143     expected_report.set_url(GURL(kTestDownloadUrl).spec());
144     if (from_download_api)
145       expected_report.set_type(
146           ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_BY_API);
147     else
148       expected_report.set_type(
149           ClientSafeBrowsingReportRequest::DANGEROUS_DOWNLOAD_RECOVERY);
150     expected_report.set_download_verdict(download_verdict);
151     expected_report.set_did_proceed(did_proceed);
152     if (!token.empty())
153       expected_report.set_token(token);
154     expected_report.SerializeToString(&expected_serialized_report_);
155   }
156 
CreatePrompt(bool from_download_api)157   void CreatePrompt(bool from_download_api) {
158     prompt_ = DownloadDangerPrompt::Create(
159         &download_,
160         browser()->tab_strip_model()->GetActiveWebContents(),
161         from_download_api,
162         base::Bind(&DownloadDangerPromptTest::PromptCallback,
163                    base::Unretained(this)));
164     content::RunAllPendingInMessageLoop();
165   }
166 
PromptCallback(DownloadDangerPrompt::Action action)167   void PromptCallback(DownloadDangerPrompt::Action action) {
168     EXPECT_FALSE(did_receive_callback_);
169     EXPECT_EQ(expected_action_, action);
170     did_receive_callback_ = true;
171     prompt_ = nullptr;
172   }
173 
174   download::MockDownloadItem download_;
175   DownloadDangerPrompt* prompt_;
176   DownloadDangerPrompt::Action expected_action_;
177   bool did_receive_callback_;
178   std::unique_ptr<TestSafeBrowsingServiceFactory> test_safe_browsing_factory_;
179   std::string expected_serialized_report_;
180 
181   DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptTest);
182 };
183 
184 // Disabled for flaky timeouts on Windows. crbug.com/446696
185 #if defined(OS_WIN)
186 #define MAYBE_TestAll DISABLED_TestAll
187 #else
188 #define MAYBE_TestAll TestAll
189 #endif
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest,MAYBE_TestAll)190 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptTest, MAYBE_TestAll) {
191   GURL download_url(kTestDownloadUrl);
192   ON_CALL(download(), GetURL()).WillByDefault(ReturnRef(download_url));
193   ON_CALL(download(), GetReferrerUrl())
194       .WillByDefault(ReturnRef(GURL::EmptyGURL()));
195   content::DownloadItemUtils::AttachInfo(&download(), browser()->profile(),
196                                          nullptr);
197   base::FilePath empty_file_path;
198   ON_CALL(download(), GetTargetFilePath())
199       .WillByDefault(ReturnRef(empty_file_path));
200 
201   OpenNewTab();
202 
203   // Clicking the Accept button should invoke the ACCEPT action.
204   SetUpExpectations(DownloadDangerPrompt::ACCEPT,
205                     download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL,
206                     ClientDownloadResponse::DANGEROUS, kDownloadResponseToken,
207                     false);
208   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
209   SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
210   VerifyExpectations(true);
211 
212   // Clicking the Cancel button should invoke the CANCEL action.
213   SetUpExpectations(DownloadDangerPrompt::CANCEL,
214                     download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT,
215                     ClientDownloadResponse::UNCOMMON, std::string(), false);
216   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
217   SimulatePromptAction(DownloadDangerPrompt::CANCEL);
218   VerifyExpectations(true);
219 
220   // If the download is no longer dangerous (because it was accepted), the
221   // dialog should DISMISS itself.
222   SetUpExpectations(DownloadDangerPrompt::DISMISS,
223                     download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED,
224                     ClientDownloadResponse::POTENTIALLY_UNWANTED,
225                     kDownloadResponseToken, false);
226   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(false));
227   download().NotifyObserversDownloadUpdated();
228   VerifyExpectations(false);
229 
230   // If the download is in a terminal state then the dialog should DISMISS
231   // itself.
232   SetUpExpectations(DownloadDangerPrompt::DISMISS,
233                     download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST,
234                     ClientDownloadResponse::DANGEROUS_HOST,
235                     kDownloadResponseToken, false);
236   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
237   EXPECT_CALL(download(), IsDone()).WillRepeatedly(Return(true));
238   download().NotifyObserversDownloadUpdated();
239   VerifyExpectations(false);
240 
241   // If the download is dangerous and is not in a terminal state, don't dismiss
242   // the dialog.
243   SetUpExpectations(DownloadDangerPrompt::ACCEPT,
244                     download::DOWNLOAD_DANGER_TYPE_DANGEROUS_CONTENT,
245                     ClientDownloadResponse::DANGEROUS, kDownloadResponseToken,
246                     false);
247   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
248   EXPECT_CALL(download(), IsDone()).WillRepeatedly(Return(false));
249   download().NotifyObserversDownloadUpdated();
250   EXPECT_TRUE(prompt());
251   SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
252   VerifyExpectations(true);
253 
254   // If the download is not dangerous, no report will be sent.
255   SetUpExpectations(DownloadDangerPrompt::ACCEPT,
256                     download::DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS,
257                     ClientDownloadResponse::SAFE, kDownloadResponseToken,
258                     false);
259   SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
260   VerifyExpectations(false);
261 
262   // If the containing tab is closed, the dialog should DISMISS itself.
263   OpenNewTab();
264   SetUpExpectations(DownloadDangerPrompt::DISMISS,
265                     download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL,
266                     ClientDownloadResponse::DANGEROUS, kDownloadResponseToken,
267                     false);
268   chrome::CloseTab(browser());
269   VerifyExpectations(false);
270 
271   // If file is downloaded through download api, a confirm download dialog
272   // instead of a recovery dialog is shown. Clicking the Accept button should
273   // invoke the ACCEPT action, a report will be sent with type
274   // DANGEROUS_DOWNLOAD_BY_API.
275   SetUpExpectations(DownloadDangerPrompt::ACCEPT,
276                     download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL,
277                     ClientDownloadResponse::DANGEROUS, kDownloadResponseToken,
278                     true);
279   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
280   SimulatePromptAction(DownloadDangerPrompt::ACCEPT);
281   VerifyExpectations(true);
282 
283   // If file is downloaded through download api, a confirm download dialog
284   // instead of a recovery dialog is shown. Clicking the Cancel button should
285   // invoke the CANCEL action, a report will be sent with type
286   // DANGEROUS_DOWNLOAD_BY_API.
287   SetUpExpectations(DownloadDangerPrompt::CANCEL,
288                     download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT,
289                     ClientDownloadResponse::UNCOMMON, std::string(), true);
290   EXPECT_CALL(download(), IsDangerous()).WillRepeatedly(Return(true));
291   SimulatePromptAction(DownloadDangerPrompt::CANCEL);
292   VerifyExpectations(true);
293 }
294 
295 // Class for testing interactive dialogs.
296 class DownloadDangerPromptBrowserTest : public DialogBrowserTest {
297  protected:
298   enum InvocationType { USER_INITIATED, FROM_DOWNLOAD_API };
DownloadDangerPromptBrowserTest()299   DownloadDangerPromptBrowserTest() : download_url_(kTestDownloadUrl) {}
300 
RunTest(download::DownloadDangerType danger_type,InvocationType invocation_type)301   void RunTest(download::DownloadDangerType danger_type,
302                InvocationType invocation_type) {
303     danger_type_ = danger_type;
304     invocation_type_ = invocation_type;
305 
306     ShowAndVerifyUi();
307   }
308 
309  private:
ShowUi(const std::string & name)310   void ShowUi(const std::string& name) override {
311     ON_CALL(download_, GetURL()).WillByDefault(ReturnRef(download_url_));
312     ON_CALL(download_, GetReferrerUrl())
313         .WillByDefault(ReturnRef(GURL::EmptyGURL()));
314     ON_CALL(download_, GetTargetFilePath())
315         .WillByDefault(ReturnRef(empty_file_path_));
316     ON_CALL(download_, IsDangerous()).WillByDefault(Return(true));
317     ON_CALL(download_, GetFileNameToReportUser())
318         .WillByDefault(Return(base::FilePath(FILE_PATH_LITERAL("evil.exe"))));
319 
320     // Set up test-specific parameters
321     ON_CALL(download_, GetDangerType()).WillByDefault(Return(danger_type_));
322     content::DownloadItemUtils::AttachInfo(&download_, browser()->profile(),
323                                            nullptr);
324     DownloadDangerPrompt::Create(
325         &download_, browser()->tab_strip_model()->GetActiveWebContents(),
326         invocation_type_ == FROM_DOWNLOAD_API, DownloadDangerPrompt::OnDone());
327   }
328 
329   const GURL download_url_;
330   const base::FilePath empty_file_path_;
331 
332   download::DownloadDangerType danger_type_;
333   InvocationType invocation_type_;
334   download::MockDownloadItem download_;
335 
336   DISALLOW_COPY_AND_ASSIGN(DownloadDangerPromptBrowserTest);
337 };
338 
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_DangerousFile)339 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
340                        InvokeUi_DangerousFile) {
341   RunTest(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, USER_INITIATED);
342 }
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_DangerousFileFromApi)343 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
344                        InvokeUi_DangerousFileFromApi) {
345   RunTest(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_FILE, FROM_DOWNLOAD_API);
346 }
347 
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_DangerousUrl)348 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest, InvokeUi_DangerousUrl) {
349   RunTest(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, USER_INITIATED);
350 }
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_DangerousUrlFromApi)351 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
352                        InvokeUi_DangerousUrlFromApi) {
353   RunTest(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_URL, FROM_DOWNLOAD_API);
354 }
355 
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_UncommonContent)356 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
357                        InvokeUi_UncommonContent) {
358   RunTest(download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT, USER_INITIATED);
359 }
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_UncommonContentFromApi)360 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
361                        InvokeUi_UncommonContentFromApi) {
362   RunTest(download::DOWNLOAD_DANGER_TYPE_UNCOMMON_CONTENT, FROM_DOWNLOAD_API);
363 }
364 
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_PotentiallyUnwanted)365 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
366                        InvokeUi_PotentiallyUnwanted) {
367   RunTest(download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED, USER_INITIATED);
368 }
IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,InvokeUi_PotentiallyUnwantedFromApi)369 IN_PROC_BROWSER_TEST_F(DownloadDangerPromptBrowserTest,
370                        InvokeUi_PotentiallyUnwantedFromApi) {
371   RunTest(download::DOWNLOAD_DANGER_TYPE_POTENTIALLY_UNWANTED,
372           FROM_DOWNLOAD_API);
373 }
374 
375 }  // namespace safe_browsing
376