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.
5 #include "chrome/browser/extensions/api/file_system/file_system_api.h"
9 #include "base/files/file_path.h"
10 #include "base/run_loop.h"
11 #include "base/strings/string_split.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/utf_string_conversions.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 #include "ui/shell_dialogs/select_file_dialog.h"
17 #if defined(OS_CHROMEOS)
18 #include "base/memory/ref_counted.h"
19 #include "base/memory/weak_ptr.h"
20 #include "base/prefs/testing_pref_service.h"
21 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
22 #include "chrome/browser/chromeos/login/users/fake_chrome_user_manager.h"
23 #include "chrome/browser/chromeos/login/users/scoped_user_manager_enabler.h"
24 #include "chrome/test/base/testing_browser_process.h"
25 #include "content/public/test/test_browser_thread_bundle.h"
26 #include "extensions/common/extension.h"
27 #include "extensions/common/extension_builder.h"
28 #include "extensions/common/manifest.h"
29 #include "extensions/common/test_util.h"
30 #include "extensions/common/value_builder.h"
33 using extensions::FileSystemChooseEntryFunction
;
34 using extensions::api::file_system::AcceptOption
;
36 #if defined(OS_CHROMEOS)
37 using extensions::file_system_api::ConsentProvider
;
38 using file_manager::Volume
;
41 namespace extensions
{
44 void CheckExtensions(const std::vector
<base::FilePath::StringType
>& expected
,
45 const std::vector
<base::FilePath::StringType
>& actual
) {
46 EXPECT_EQ(expected
.size(), actual
.size());
47 if (expected
.size() != actual
.size())
50 for (size_t i
= 0; i
< expected
.size(); ++i
) {
51 EXPECT_EQ(expected
[i
], actual
[i
]);
55 AcceptOption
* BuildAcceptOption(const std::string
& description
,
56 const std::string
& mime_types
,
57 const std::string
& extensions
) {
58 AcceptOption
* option
= new AcceptOption();
60 if (!description
.empty())
61 option
->description
.reset(new std::string(description
));
63 if (!mime_types
.empty()) {
64 option
->mime_types
.reset(new std::vector
<std::string
>(
65 base::SplitString(mime_types
, ",",
66 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)));
69 if (!extensions
.empty()) {
70 option
->extensions
.reset(new std::vector
<std::string
>(
71 base::SplitString(extensions
, ",",
72 base::TRIM_WHITESPACE
, base::SPLIT_WANT_ALL
)));
79 #define ToStringType base::UTF8ToWide
84 #if defined(OS_CHROMEOS)
85 class TestingConsentProviderDelegate
86 : public ConsentProvider::DelegateInterface
{
88 TestingConsentProviderDelegate()
89 : show_dialog_counter_(0),
90 show_notification_counter_(0),
91 dialog_button_(ui::DIALOG_BUTTON_NONE
),
92 is_auto_launched_(false) {}
94 ~TestingConsentProviderDelegate() {}
96 // Sets a fake dialog response.
97 void SetDialogButton(ui::DialogButton button
) { dialog_button_
= button
; }
99 // Sets a fake result of detection the auto launch kiosk mode.
100 void SetIsAutoLaunched(bool is_auto_launched
) {
101 is_auto_launched_
= is_auto_launched
;
104 // Sets a whitelisted components list with a single id.
105 void SetComponentWhitelist(const std::string
& extension_id
) {
106 whitelisted_component_id_
= extension_id
;
109 int show_dialog_counter() const { return show_dialog_counter_
; }
110 int show_notification_counter() const { return show_notification_counter_
; }
113 // ConsentProvider::DelegateInterface overrides:
115 const extensions::Extension
& extension
,
116 const base::WeakPtr
<Volume
>& volume
,
118 const ConsentProvider::ShowDialogCallback
& callback
) override
{
119 ++show_dialog_counter_
;
120 callback
.Run(dialog_button_
);
123 void ShowNotification(const extensions::Extension
& extension
,
124 const base::WeakPtr
<Volume
>& volume
,
125 bool writable
) override
{
126 ++show_notification_counter_
;
129 bool IsAutoLaunched(const extensions::Extension
& extension
) override
{
130 return is_auto_launched_
;
133 bool IsWhitelistedComponent(const extensions::Extension
& extension
) override
{
134 return whitelisted_component_id_
.compare(extension
.id()) == 0;
137 int show_dialog_counter_
;
138 int show_notification_counter_
;
139 ui::DialogButton dialog_button_
;
140 bool is_auto_launched_
;
141 std::string whitelisted_component_id_
;
143 DISALLOW_COPY_AND_ASSIGN(TestingConsentProviderDelegate
);
146 // Rewrites result of a consent request from |result| to |log|.
147 void OnConsentReceived(ConsentProvider::Consent
* log
,
148 const ConsentProvider::Consent result
) {
155 #if defined(OS_CHROMEOS)
156 class FileSystemApiConsentProviderTest
: public testing::Test
{
158 FileSystemApiConsentProviderTest() {}
160 void SetUp() override
{
161 testing_pref_service_
.reset(new TestingPrefServiceSimple
);
162 TestingBrowserProcess::GetGlobal()->SetLocalState(
163 testing_pref_service_
.get());
164 user_manager_
= new chromeos::FakeChromeUserManager
;
165 scoped_user_manager_enabler_
.reset(
166 new chromeos::ScopedUserManagerEnabler(user_manager_
));
169 void TearDown() override
{
170 scoped_user_manager_enabler_
.reset();
171 user_manager_
= nullptr;
172 testing_pref_service_
.reset();
173 TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
177 base::WeakPtr
<Volume
> volume_
;
178 scoped_ptr
<TestingPrefServiceSimple
> testing_pref_service_
;
179 chromeos::FakeChromeUserManager
*
180 user_manager_
; // Owned by the scope enabler.
181 scoped_ptr
<chromeos::ScopedUserManagerEnabler
> scoped_user_manager_enabler_
;
182 content::TestBrowserThreadBundle thread_bundle_
;
186 TEST(FileSystemApiUnitTest
, FileSystemChooseEntryFunctionFileTypeInfoTest
) {
187 // AcceptsAllTypes is ignored when no other extensions are available.
188 ui::SelectFileDialog::FileTypeInfo file_type_info
;
189 bool acceptsAllTypes
= false;
190 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
191 base::FilePath::StringType(), NULL
, &acceptsAllTypes
);
192 EXPECT_TRUE(file_type_info
.include_all_files
);
193 EXPECT_TRUE(file_type_info
.extensions
.empty());
195 // Test grouping of multiple types.
196 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
197 std::vector
<linked_ptr
<AcceptOption
> > options
;
198 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
199 std::string(), "application/x-chrome-extension", "jso")));
200 acceptsAllTypes
= false;
201 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
202 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
203 EXPECT_FALSE(file_type_info
.include_all_files
);
204 ASSERT_EQ(file_type_info
.extensions
.size(), (size_t) 1);
205 EXPECT_TRUE(file_type_info
.extension_description_overrides
[0].empty()) <<
206 "No override must be specified for boring accept types";
207 // Note here (and below) that the expectedTypes are sorted, because we use a
208 // set internally to generate the output: thus, the output is sorted.
209 std::vector
<base::FilePath::StringType
> expectedTypes
;
210 expectedTypes
.push_back(ToStringType("crx"));
211 expectedTypes
.push_back(ToStringType("jso"));
212 CheckExtensions(expectedTypes
, file_type_info
.extensions
[0]);
214 // Test that not satisfying the extension will force all types.
215 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
217 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
218 std::string(), std::string(), "unrelated")));
219 acceptsAllTypes
= false;
220 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
221 ToStringType(".jso"), &options
, &acceptsAllTypes
);
222 EXPECT_TRUE(file_type_info
.include_all_files
);
224 // Test multiple list entries, all containing their own types.
225 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
227 options
.push_back(linked_ptr
<AcceptOption
>(
228 BuildAcceptOption(std::string(), std::string(), "jso,js")));
229 options
.push_back(linked_ptr
<AcceptOption
>(
230 BuildAcceptOption(std::string(), std::string(), "cpp,cc")));
231 acceptsAllTypes
= false;
232 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
233 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
234 ASSERT_EQ(file_type_info
.extensions
.size(), options
.size());
236 expectedTypes
.clear();
237 expectedTypes
.push_back(ToStringType("js"));
238 expectedTypes
.push_back(ToStringType("jso"));
239 CheckExtensions(expectedTypes
, file_type_info
.extensions
[0]);
241 expectedTypes
.clear();
242 expectedTypes
.push_back(ToStringType("cc"));
243 expectedTypes
.push_back(ToStringType("cpp"));
244 CheckExtensions(expectedTypes
, file_type_info
.extensions
[1]);
246 // Test accept type that causes description override.
247 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
249 options
.push_back(linked_ptr
<AcceptOption
>(
250 BuildAcceptOption(std::string(), "image/*", "html")));
251 acceptsAllTypes
= false;
252 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
253 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
254 ASSERT_EQ(file_type_info
.extension_description_overrides
.size(), (size_t) 1);
255 EXPECT_FALSE(file_type_info
.extension_description_overrides
[0].empty()) <<
256 "Accept type \"image/*\" must generate description override";
258 // Test multiple accept types that cause description override causes us to
259 // still present the default.
260 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
262 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
263 std::string(), "image/*,audio/*,video/*", std::string())));
264 acceptsAllTypes
= false;
265 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
266 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
267 ASSERT_EQ(file_type_info
.extension_description_overrides
.size(), (size_t) 1);
268 EXPECT_TRUE(file_type_info
.extension_description_overrides
[0].empty());
270 // Test explicit description override.
271 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
273 options
.push_back(linked_ptr
<AcceptOption
>(
274 BuildAcceptOption("File Types 101", "image/jpeg", std::string())));
275 acceptsAllTypes
= false;
276 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
277 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
278 EXPECT_EQ(file_type_info
.extension_description_overrides
[0],
279 base::UTF8ToUTF16("File Types 101"));
282 TEST(FileSystemApiUnitTest
, FileSystemChooseEntryFunctionSuggestionTest
) {
283 std::string opt_name
;
284 base::FilePath suggested_name
;
285 base::FilePath::StringType suggested_extension
;
287 opt_name
= std::string("normal_path.txt");
288 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
289 &suggested_extension
);
290 EXPECT_FALSE(suggested_name
.IsAbsolute());
291 EXPECT_EQ(suggested_name
.MaybeAsASCII(), "normal_path.txt");
292 EXPECT_EQ(suggested_extension
, ToStringType("txt"));
294 // We should provide just the basename, i.e., "path".
295 opt_name
= std::string("/a/bad/path");
296 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
297 &suggested_extension
);
298 EXPECT_FALSE(suggested_name
.IsAbsolute());
299 EXPECT_EQ(suggested_name
.MaybeAsASCII(), "path");
300 EXPECT_TRUE(suggested_extension
.empty());
303 // TODO(thorogood): Fix this test on Windows.
304 // Filter out absolute paths with no basename.
305 opt_name
= std::string("/");
306 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
307 &suggested_extension
);
308 EXPECT_FALSE(suggested_name
.IsAbsolute());
309 EXPECT_TRUE(suggested_name
.MaybeAsASCII().empty());
310 EXPECT_TRUE(suggested_extension
.empty());
314 #if defined(OS_CHROMEOS)
315 TEST_F(FileSystemApiConsentProviderTest
, ForNonKioskApps
) {
316 // Component apps are not granted unless they are whitelisted.
318 scoped_refptr
<Extension
> component_extension(
319 test_util::BuildApp(ExtensionBuilder().SetLocation(Manifest::COMPONENT
))
321 TestingConsentProviderDelegate delegate
;
322 ConsentProvider
provider(&delegate
);
323 EXPECT_FALSE(provider
.IsGrantable(*component_extension
));
326 // Whitelitsed component apps are instantly granted access without asking
329 scoped_refptr
<Extension
> whitelisted_component_extension(
330 test_util::BuildApp(ExtensionBuilder().SetLocation(Manifest::COMPONENT
))
332 TestingConsentProviderDelegate delegate
;
333 delegate
.SetComponentWhitelist(whitelisted_component_extension
->id());
334 ConsentProvider
provider(&delegate
);
335 EXPECT_TRUE(provider
.IsGrantable(*whitelisted_component_extension
));
337 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
338 provider
.RequestConsent(*whitelisted_component_extension
.get(), volume_
,
340 base::Bind(&OnConsentReceived
, &result
));
341 base::RunLoop().RunUntilIdle();
343 EXPECT_EQ(0, delegate
.show_dialog_counter());
344 EXPECT_EQ(0, delegate
.show_notification_counter());
345 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
348 // Non-component apps in non-kiosk mode will be rejected instantly, without
349 // asking for user consent.
351 scoped_refptr
<Extension
> non_component_extension(
352 test_util::CreateEmptyExtension());
353 TestingConsentProviderDelegate delegate
;
354 ConsentProvider
provider(&delegate
);
355 EXPECT_FALSE(provider
.IsGrantable(*non_component_extension
));
359 TEST_F(FileSystemApiConsentProviderTest
, ForKioskApps
) {
360 // Non-component apps in auto-launch kiosk mode will be granted access
361 // instantly without asking for user consent, but with a notification.
363 scoped_refptr
<Extension
> auto_launch_kiosk_app(
364 test_util::BuildApp(ExtensionBuilder().Pass())
365 .MergeManifest(DictionaryBuilder()
366 .SetBoolean("kiosk_enabled", true)
367 .SetBoolean("kiosk_only", true))
369 user_manager_
->AddKioskAppUser(auto_launch_kiosk_app
->id());
370 user_manager_
->LoginUser(auto_launch_kiosk_app
->id());
372 TestingConsentProviderDelegate delegate
;
373 delegate
.SetIsAutoLaunched(true);
374 ConsentProvider
provider(&delegate
);
375 EXPECT_TRUE(provider
.IsGrantable(*auto_launch_kiosk_app
));
377 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
378 provider
.RequestConsent(*auto_launch_kiosk_app
.get(), volume_
,
380 base::Bind(&OnConsentReceived
, &result
));
381 base::RunLoop().RunUntilIdle();
383 EXPECT_EQ(0, delegate
.show_dialog_counter());
384 EXPECT_EQ(1, delegate
.show_notification_counter());
385 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
388 // Non-component apps in manual-launch kiosk mode will be granted access after
389 // receiving approval from the user.
390 scoped_refptr
<Extension
> manual_launch_kiosk_app(
391 test_util::BuildApp(ExtensionBuilder().Pass())
392 .MergeManifest(DictionaryBuilder()
393 .SetBoolean("kiosk_enabled", true)
394 .SetBoolean("kiosk_only", true))
396 user_manager_
->KioskAppLoggedIn(manual_launch_kiosk_app
->id());
398 TestingConsentProviderDelegate delegate
;
399 delegate
.SetDialogButton(ui::DIALOG_BUTTON_OK
);
400 ConsentProvider
provider(&delegate
);
401 EXPECT_TRUE(provider
.IsGrantable(*manual_launch_kiosk_app
));
403 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
404 provider
.RequestConsent(*manual_launch_kiosk_app
.get(), volume_
,
406 base::Bind(&OnConsentReceived
, &result
));
407 base::RunLoop().RunUntilIdle();
409 EXPECT_EQ(1, delegate
.show_dialog_counter());
410 EXPECT_EQ(0, delegate
.show_notification_counter());
411 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
414 // Non-component apps in manual-launch kiosk mode will be rejected access
415 // after rejecting by a user.
417 TestingConsentProviderDelegate delegate
;
418 ConsentProvider
provider(&delegate
);
419 delegate
.SetDialogButton(ui::DIALOG_BUTTON_CANCEL
);
420 EXPECT_TRUE(provider
.IsGrantable(*manual_launch_kiosk_app
));
422 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
423 provider
.RequestConsent(*manual_launch_kiosk_app
.get(), volume_
,
425 base::Bind(&OnConsentReceived
, &result
));
426 base::RunLoop().RunUntilIdle();
428 EXPECT_EQ(1, delegate
.show_dialog_counter());
429 EXPECT_EQ(0, delegate
.show_notification_counter());
430 EXPECT_EQ(ConsentProvider::CONSENT_REJECTED
, result
);
435 } // namespace extensions