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
, ',', option
->mime_types
.get());
68 if (!extensions
.empty()) {
69 option
->extensions
.reset(new std::vector
<std::string
>());
70 base::SplitString(extensions
, ',', option
->extensions
.get());
77 #define ToStringType base::UTF8ToWide
82 #if defined(OS_CHROMEOS)
83 class TestingConsentProviderDelegate
84 : public ConsentProvider::DelegateInterface
{
86 TestingConsentProviderDelegate()
87 : show_dialog_counter_(0),
88 show_notification_counter_(0),
89 dialog_button_(ui::DIALOG_BUTTON_NONE
),
90 is_auto_launched_(false) {}
92 ~TestingConsentProviderDelegate() {}
94 // Sets a fake dialog response.
95 void SetDialogButton(ui::DialogButton button
) { dialog_button_
= button
; }
97 // Sets a fake result of detection the auto launch kiosk mode.
98 void SetIsAutoLaunched(bool is_auto_launched
) {
99 is_auto_launched_
= is_auto_launched
;
102 // Sets a whitelisted components list with a single id.
103 void SetComponentWhitelist(const std::string
& extension_id
) {
104 whitelisted_component_id_
= extension_id
;
107 int show_dialog_counter() const { return show_dialog_counter_
; }
108 int show_notification_counter() const { return show_notification_counter_
; }
111 // ConsentProvider::DelegateInterface overrides:
113 const extensions::Extension
& extension
,
114 const base::WeakPtr
<Volume
>& volume
,
116 const ConsentProvider::ShowDialogCallback
& callback
) override
{
117 ++show_dialog_counter_
;
118 callback
.Run(dialog_button_
);
121 void ShowNotification(const extensions::Extension
& extension
,
122 const base::WeakPtr
<Volume
>& volume
,
123 bool writable
) override
{
124 ++show_notification_counter_
;
127 bool IsAutoLaunched(const extensions::Extension
& extension
) override
{
128 return is_auto_launched_
;
131 bool IsWhitelistedComponent(const extensions::Extension
& extension
) override
{
132 return whitelisted_component_id_
.compare(extension
.id()) == 0;
135 int show_dialog_counter_
;
136 int show_notification_counter_
;
137 ui::DialogButton dialog_button_
;
138 bool is_auto_launched_
;
139 std::string whitelisted_component_id_
;
141 DISALLOW_COPY_AND_ASSIGN(TestingConsentProviderDelegate
);
144 // Rewrites result of a consent request from |result| to |log|.
145 void OnConsentReceived(ConsentProvider::Consent
* log
,
146 const ConsentProvider::Consent result
) {
153 #if defined(OS_CHROMEOS)
154 class FileSystemApiConsentProviderTest
: public testing::Test
{
156 FileSystemApiConsentProviderTest() {}
158 void SetUp() override
{
159 testing_pref_service_
.reset(new TestingPrefServiceSimple
);
160 TestingBrowserProcess::GetGlobal()->SetLocalState(
161 testing_pref_service_
.get());
162 user_manager_
= new chromeos::FakeChromeUserManager
;
163 scoped_user_manager_enabler_
.reset(
164 new chromeos::ScopedUserManagerEnabler(user_manager_
));
167 void TearDown() override
{
168 scoped_user_manager_enabler_
.reset();
169 user_manager_
= nullptr;
170 testing_pref_service_
.reset();
171 TestingBrowserProcess::GetGlobal()->SetLocalState(nullptr);
175 base::WeakPtr
<Volume
> volume_
;
176 scoped_ptr
<TestingPrefServiceSimple
> testing_pref_service_
;
177 chromeos::FakeChromeUserManager
*
178 user_manager_
; // Owned by the scope enabler.
179 scoped_ptr
<chromeos::ScopedUserManagerEnabler
> scoped_user_manager_enabler_
;
180 content::TestBrowserThreadBundle thread_bundle_
;
184 TEST(FileSystemApiUnitTest
, FileSystemChooseEntryFunctionFileTypeInfoTest
) {
185 // AcceptsAllTypes is ignored when no other extensions are available.
186 ui::SelectFileDialog::FileTypeInfo file_type_info
;
187 bool acceptsAllTypes
= false;
188 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
189 base::FilePath::StringType(), NULL
, &acceptsAllTypes
);
190 EXPECT_TRUE(file_type_info
.include_all_files
);
191 EXPECT_TRUE(file_type_info
.extensions
.empty());
193 // Test grouping of multiple types.
194 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
195 std::vector
<linked_ptr
<AcceptOption
> > options
;
196 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
197 std::string(), "application/x-chrome-extension", "jso")));
198 acceptsAllTypes
= false;
199 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
200 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
201 EXPECT_FALSE(file_type_info
.include_all_files
);
202 ASSERT_EQ(file_type_info
.extensions
.size(), (size_t) 1);
203 EXPECT_TRUE(file_type_info
.extension_description_overrides
[0].empty()) <<
204 "No override must be specified for boring accept types";
205 // Note here (and below) that the expectedTypes are sorted, because we use a
206 // set internally to generate the output: thus, the output is sorted.
207 std::vector
<base::FilePath::StringType
> expectedTypes
;
208 expectedTypes
.push_back(ToStringType("crx"));
209 expectedTypes
.push_back(ToStringType("jso"));
210 CheckExtensions(expectedTypes
, file_type_info
.extensions
[0]);
212 // Test that not satisfying the extension will force all types.
213 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
215 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
216 std::string(), std::string(), "unrelated")));
217 acceptsAllTypes
= false;
218 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
219 ToStringType(".jso"), &options
, &acceptsAllTypes
);
220 EXPECT_TRUE(file_type_info
.include_all_files
);
222 // Test multiple list entries, all containing their own types.
223 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
225 options
.push_back(linked_ptr
<AcceptOption
>(
226 BuildAcceptOption(std::string(), std::string(), "jso,js")));
227 options
.push_back(linked_ptr
<AcceptOption
>(
228 BuildAcceptOption(std::string(), std::string(), "cpp,cc")));
229 acceptsAllTypes
= false;
230 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
231 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
232 ASSERT_EQ(file_type_info
.extensions
.size(), options
.size());
234 expectedTypes
.clear();
235 expectedTypes
.push_back(ToStringType("js"));
236 expectedTypes
.push_back(ToStringType("jso"));
237 CheckExtensions(expectedTypes
, file_type_info
.extensions
[0]);
239 expectedTypes
.clear();
240 expectedTypes
.push_back(ToStringType("cc"));
241 expectedTypes
.push_back(ToStringType("cpp"));
242 CheckExtensions(expectedTypes
, file_type_info
.extensions
[1]);
244 // Test accept type that causes description override.
245 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
247 options
.push_back(linked_ptr
<AcceptOption
>(
248 BuildAcceptOption(std::string(), "image/*", "html")));
249 acceptsAllTypes
= false;
250 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
251 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
252 ASSERT_EQ(file_type_info
.extension_description_overrides
.size(), (size_t) 1);
253 EXPECT_FALSE(file_type_info
.extension_description_overrides
[0].empty()) <<
254 "Accept type \"image/*\" must generate description override";
256 // Test multiple accept types that cause description override causes us to
257 // still present the default.
258 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
260 options
.push_back(linked_ptr
<AcceptOption
>(BuildAcceptOption(
261 std::string(), "image/*,audio/*,video/*", std::string())));
262 acceptsAllTypes
= false;
263 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
264 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
265 ASSERT_EQ(file_type_info
.extension_description_overrides
.size(), (size_t) 1);
266 EXPECT_TRUE(file_type_info
.extension_description_overrides
[0].empty());
268 // Test explicit description override.
269 file_type_info
= ui::SelectFileDialog::FileTypeInfo();
271 options
.push_back(linked_ptr
<AcceptOption
>(
272 BuildAcceptOption("File Types 101", "image/jpeg", std::string())));
273 acceptsAllTypes
= false;
274 FileSystemChooseEntryFunction::BuildFileTypeInfo(&file_type_info
,
275 base::FilePath::StringType(), &options
, &acceptsAllTypes
);
276 EXPECT_EQ(file_type_info
.extension_description_overrides
[0],
277 base::UTF8ToUTF16("File Types 101"));
280 TEST(FileSystemApiUnitTest
, FileSystemChooseEntryFunctionSuggestionTest
) {
281 std::string opt_name
;
282 base::FilePath suggested_name
;
283 base::FilePath::StringType suggested_extension
;
285 opt_name
= std::string("normal_path.txt");
286 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
287 &suggested_extension
);
288 EXPECT_FALSE(suggested_name
.IsAbsolute());
289 EXPECT_EQ(suggested_name
.MaybeAsASCII(), "normal_path.txt");
290 EXPECT_EQ(suggested_extension
, ToStringType("txt"));
292 // We should provide just the basename, i.e., "path".
293 opt_name
= std::string("/a/bad/path");
294 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
295 &suggested_extension
);
296 EXPECT_FALSE(suggested_name
.IsAbsolute());
297 EXPECT_EQ(suggested_name
.MaybeAsASCII(), "path");
298 EXPECT_TRUE(suggested_extension
.empty());
301 // TODO(thorogood): Fix this test on Windows.
302 // Filter out absolute paths with no basename.
303 opt_name
= std::string("/");
304 FileSystemChooseEntryFunction::BuildSuggestion(&opt_name
, &suggested_name
,
305 &suggested_extension
);
306 EXPECT_FALSE(suggested_name
.IsAbsolute());
307 EXPECT_TRUE(suggested_name
.MaybeAsASCII().empty());
308 EXPECT_TRUE(suggested_extension
.empty());
312 #if defined(OS_CHROMEOS)
313 TEST_F(FileSystemApiConsentProviderTest
, ForNonKioskApps
) {
314 // Component apps are not granted unless they are whitelisted.
316 scoped_refptr
<Extension
> component_extension(
317 test_util::BuildApp(ExtensionBuilder().SetLocation(Manifest::COMPONENT
))
319 TestingConsentProviderDelegate delegate
;
320 ConsentProvider
provider(&delegate
);
321 EXPECT_FALSE(provider
.IsGrantable(*component_extension
));
324 // Whitelitsed component apps are instantly granted access without asking
327 scoped_refptr
<Extension
> whitelisted_component_extension(
328 test_util::BuildApp(ExtensionBuilder().SetLocation(Manifest::COMPONENT
))
330 TestingConsentProviderDelegate delegate
;
331 delegate
.SetComponentWhitelist(whitelisted_component_extension
->id());
332 ConsentProvider
provider(&delegate
);
333 EXPECT_TRUE(provider
.IsGrantable(*whitelisted_component_extension
));
335 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
336 provider
.RequestConsent(*whitelisted_component_extension
.get(), volume_
,
338 base::Bind(&OnConsentReceived
, &result
));
339 base::RunLoop().RunUntilIdle();
341 EXPECT_EQ(0, delegate
.show_dialog_counter());
342 EXPECT_EQ(0, delegate
.show_notification_counter());
343 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
346 // Non-component apps in non-kiosk mode will be rejected instantly, without
347 // asking for user consent.
349 scoped_refptr
<Extension
> non_component_extension(
350 test_util::CreateEmptyExtension());
351 TestingConsentProviderDelegate delegate
;
352 ConsentProvider
provider(&delegate
);
353 EXPECT_FALSE(provider
.IsGrantable(*non_component_extension
));
357 TEST_F(FileSystemApiConsentProviderTest
, ForKioskApps
) {
358 // Non-component apps in auto-launch kiosk mode will be granted access
359 // instantly without asking for user consent, but with a notification.
361 scoped_refptr
<Extension
> auto_launch_kiosk_app(
362 test_util::BuildApp(ExtensionBuilder().Pass())
363 .MergeManifest(DictionaryBuilder()
364 .SetBoolean("kiosk_enabled", true)
365 .SetBoolean("kiosk_only", true))
367 user_manager_
->AddKioskAppUser(auto_launch_kiosk_app
->id());
368 user_manager_
->LoginUser(auto_launch_kiosk_app
->id());
370 TestingConsentProviderDelegate delegate
;
371 delegate
.SetIsAutoLaunched(true);
372 ConsentProvider
provider(&delegate
);
373 EXPECT_TRUE(provider
.IsGrantable(*auto_launch_kiosk_app
));
375 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
376 provider
.RequestConsent(*auto_launch_kiosk_app
.get(), volume_
,
378 base::Bind(&OnConsentReceived
, &result
));
379 base::RunLoop().RunUntilIdle();
381 EXPECT_EQ(0, delegate
.show_dialog_counter());
382 EXPECT_EQ(1, delegate
.show_notification_counter());
383 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
386 // Non-component apps in manual-launch kiosk mode will be granted access after
387 // receiving approval from the user.
388 scoped_refptr
<Extension
> manual_launch_kiosk_app(
389 test_util::BuildApp(ExtensionBuilder().Pass())
390 .MergeManifest(DictionaryBuilder()
391 .SetBoolean("kiosk_enabled", true)
392 .SetBoolean("kiosk_only", true))
394 user_manager_
->KioskAppLoggedIn(manual_launch_kiosk_app
->id());
396 TestingConsentProviderDelegate delegate
;
397 delegate
.SetDialogButton(ui::DIALOG_BUTTON_OK
);
398 ConsentProvider
provider(&delegate
);
399 EXPECT_TRUE(provider
.IsGrantable(*manual_launch_kiosk_app
));
401 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
402 provider
.RequestConsent(*manual_launch_kiosk_app
.get(), volume_
,
404 base::Bind(&OnConsentReceived
, &result
));
405 base::RunLoop().RunUntilIdle();
407 EXPECT_EQ(1, delegate
.show_dialog_counter());
408 EXPECT_EQ(0, delegate
.show_notification_counter());
409 EXPECT_EQ(ConsentProvider::CONSENT_GRANTED
, result
);
412 // Non-component apps in manual-launch kiosk mode will be rejected access
413 // after rejecting by a user.
415 TestingConsentProviderDelegate delegate
;
416 ConsentProvider
provider(&delegate
);
417 delegate
.SetDialogButton(ui::DIALOG_BUTTON_CANCEL
);
418 EXPECT_TRUE(provider
.IsGrantable(*manual_launch_kiosk_app
));
420 ConsentProvider::Consent result
= ConsentProvider::CONSENT_IMPOSSIBLE
;
421 provider
.RequestConsent(*manual_launch_kiosk_app
.get(), volume_
,
423 base::Bind(&OnConsentReceived
, &result
));
424 base::RunLoop().RunUntilIdle();
426 EXPECT_EQ(1, delegate
.show_dialog_counter());
427 EXPECT_EQ(0, delegate
.show_notification_counter());
428 EXPECT_EQ(ConsentProvider::CONSENT_REJECTED
, result
);
433 } // namespace extensions