1 // Copyright 2017 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 "chrome/browser/extensions/api/file_system/chrome_file_system_delegate.h"
6
7 #include <utility>
8 #include <vector>
9
10 #include "apps/saved_files_service.h"
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/check.h"
14 #include "base/files/file_path.h"
15 #include "base/path_service.h"
16 #include "base/strings/string16.h"
17 #include "chrome/browser/extensions/api/file_system/file_entry_picker.h"
18 #include "chrome/browser/extensions/chrome_extension_function_details.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/ui/apps/directory_access_confirmation_dialog.h"
21 #include "chrome/common/chrome_paths.h"
22 #include "chrome/grit/generated_resources.h"
23 #include "content/public/browser/browser_context.h"
24 #include "content/public/browser/child_process_security_policy.h"
25 #include "content/public/browser/render_frame_host.h"
26 #include "content/public/browser/render_process_host.h"
27 #include "content/public/browser/storage_partition.h"
28 #include "content/public/browser/web_contents.h"
29 #include "extensions/browser/api/file_handlers/app_file_handler_util.h"
30 #include "extensions/browser/api/file_system/saved_files_service_interface.h"
31 #include "extensions/browser/app_window/app_window.h"
32 #include "extensions/browser/app_window/app_window_registry.h"
33 #include "extensions/browser/extension_function.h"
34 #include "extensions/browser/extension_prefs.h"
35 #include "extensions/browser/extension_system.h"
36 #include "extensions/browser/extension_util.h"
37 #include "extensions/common/api/file_system.h"
38 #include "extensions/common/extension.h"
39 #include "storage/browser/file_system/external_mount_points.h"
40 #include "storage/browser/file_system/isolated_context.h"
41 #include "storage/common/file_system/file_system_types.h"
42 #include "storage/common/file_system/file_system_util.h"
43 #include "ui/shell_dialogs/select_file_dialog.h"
44
45 #if defined(OS_MAC)
46 #include <CoreFoundation/CoreFoundation.h>
47 #include "base/mac/foundation_util.h"
48 #endif
49
50 #if defined(OS_CHROMEOS)
51 #include "chrome/browser/chromeos/file_manager/volume_manager.h"
52 #include "chrome/browser/extensions/api/file_system/consent_provider.h"
53 #include "extensions/browser/event_router.h"
54 #include "extensions/browser/extension_registry.h"
55 #include "extensions/common/constants.h"
56 #include "url/gurl.h"
57 #include "url/origin.h"
58 #include "url/url_constants.h"
59 #endif
60
61 namespace extensions {
62
63 namespace file_system = api::file_system;
64
65 #if defined(OS_CHROMEOS)
66 using file_system_api::ConsentProvider;
67 using file_system_api::ConsentProviderDelegate;
68
69 namespace {
70
71 const char kConsentImpossible[] =
72 "Impossible to ask for user consent as there is no app window visible.";
73 const char kNotSupportedOnNonKioskSessionError[] =
74 "Operation only supported for kiosk apps running in a kiosk session.";
75 const char kRequiresFileSystemWriteError[] =
76 "Operation requires fileSystem.write permission";
77 const char kSecurityError[] = "Security error.";
78 const char kVolumeNotFoundError[] = "Volume not found.";
79
80 // Fills a list of volumes mounted in the system.
GetVolumeListForExtension(const std::vector<base::WeakPtr<file_manager::Volume>> & available_volumes,ConsentProvider * consent_provider,const Extension & extension,std::vector<file_system::Volume> * result_volumes)81 bool GetVolumeListForExtension(
82 const std::vector<base::WeakPtr<file_manager::Volume>>& available_volumes,
83 ConsentProvider* consent_provider,
84 const Extension& extension,
85 std::vector<file_system::Volume>* result_volumes) {
86 if (!consent_provider)
87 return false;
88
89 const FileSystemDelegate::GrantVolumesMode mode =
90 consent_provider->GetGrantVolumesMode(extension);
91 if (mode == FileSystemDelegate::kGrantNone)
92 return false;
93
94 // Convert available_volumes to result_volume_list.
95 for (const auto& volume : available_volumes) {
96 if (mode == FileSystemDelegate::kGrantAll ||
97 (mode == FileSystemDelegate::kGrantPerVolume &&
98 consent_provider->IsGrantableForVolume(extension, volume))) {
99 file_system::Volume result_volume;
100 result_volume.volume_id = volume->volume_id();
101 result_volume.writable = !volume->is_read_only();
102 result_volumes->push_back(std::move(result_volume));
103 }
104 }
105 return true;
106 }
107
108 // Callback called when consent is granted or denied.
OnConsentReceived(content::BrowserContext * browser_context,scoped_refptr<ExtensionFunction> requester,FileSystemDelegate::FileSystemCallback success_callback,FileSystemDelegate::ErrorCallback error_callback,const std::string & extension_id,const base::WeakPtr<file_manager::Volume> & volume,bool writable,ConsentProvider::Consent result)109 void OnConsentReceived(content::BrowserContext* browser_context,
110 scoped_refptr<ExtensionFunction> requester,
111 FileSystemDelegate::FileSystemCallback success_callback,
112 FileSystemDelegate::ErrorCallback error_callback,
113 const std::string& extension_id,
114 const base::WeakPtr<file_manager::Volume>& volume,
115 bool writable,
116 ConsentProvider::Consent result) {
117 using file_manager::VolumeManager;
118 using file_manager::Volume;
119
120 // Render frame host can be gone before this callback method is executed.
121 if (!requester->render_frame_host()) {
122 std::move(error_callback).Run(std::string());
123 return;
124 }
125
126 switch (result) {
127 case ConsentProvider::CONSENT_REJECTED:
128 std::move(error_callback).Run(kSecurityError);
129 return;
130
131 case ConsentProvider::CONSENT_IMPOSSIBLE:
132 std::move(error_callback).Run(kConsentImpossible);
133 return;
134
135 case ConsentProvider::CONSENT_GRANTED:
136 break;
137 }
138
139 if (!volume.get()) {
140 std::move(error_callback).Run(kVolumeNotFoundError);
141 return;
142 }
143
144 scoped_refptr<storage::FileSystemContext> file_system_context =
145 util::GetStoragePartitionForExtensionId(extension_id, browser_context)
146 ->GetFileSystemContext();
147 storage::ExternalFileSystemBackend* const backend =
148 file_system_context->external_backend();
149 DCHECK(backend);
150
151 base::FilePath virtual_path;
152 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
153 std::move(error_callback).Run(kSecurityError);
154 return;
155 }
156
157 storage::IsolatedContext* const isolated_context =
158 storage::IsolatedContext::GetInstance();
159 DCHECK(isolated_context);
160
161 const storage::FileSystemURL original_url =
162 file_system_context->CreateCrackedFileSystemURL(
163 url::Origin::Create(GURL(std::string(kExtensionScheme) +
164 url::kStandardSchemeSeparator +
165 extension_id)),
166 storage::kFileSystemTypeExternal, virtual_path);
167
168 // Set a fixed register name, as the automatic one would leak the mount point
169 // directory.
170 std::string register_name = "fs";
171 const storage::IsolatedContext::ScopedFSHandle file_system =
172 isolated_context->RegisterFileSystemForPath(
173 storage::kFileSystemTypeNativeForPlatformApp,
174 std::string() /* file_system_id */, original_url.path(),
175 ®ister_name);
176 if (!file_system.is_valid()) {
177 std::move(error_callback).Run(kSecurityError);
178 return;
179 }
180
181 backend->GrantFileAccessToExtension(extension_id, virtual_path);
182
183 // Grant file permissions to the renderer hosting component.
184 content::ChildProcessSecurityPolicy* policy =
185 content::ChildProcessSecurityPolicy::GetInstance();
186 DCHECK(policy);
187
188 const auto process_id = requester->source_process_id();
189 // Read-only permisisons.
190 policy->GrantReadFile(process_id, volume->mount_path());
191 policy->GrantReadFileSystem(process_id, file_system.id());
192
193 // Additional write permissions.
194 if (writable) {
195 policy->GrantCreateReadWriteFile(process_id, volume->mount_path());
196 policy->GrantCopyInto(process_id, volume->mount_path());
197 policy->GrantWriteFileSystem(process_id, file_system.id());
198 policy->GrantDeleteFromFileSystem(process_id, file_system.id());
199 policy->GrantCreateFileForFileSystem(process_id, file_system.id());
200 }
201
202 std::move(success_callback).Run(file_system.id(), register_name);
203 }
204
205 } // namespace
206
207 namespace file_system_api {
208
DispatchVolumeListChangeEvent(content::BrowserContext * browser_context)209 void DispatchVolumeListChangeEvent(content::BrowserContext* browser_context) {
210 DCHECK(browser_context);
211 EventRouter* const event_router = EventRouter::Get(browser_context);
212 if (!event_router) // Possible on shutdown.
213 return;
214
215 ExtensionRegistry* const registry = ExtensionRegistry::Get(browser_context);
216 if (!registry) // Possible on shutdown.
217 return;
218
219 ConsentProviderDelegate consent_provider_delegate(
220 Profile::FromBrowserContext(browser_context));
221 ConsentProvider consent_provider(&consent_provider_delegate);
222
223 const std::vector<base::WeakPtr<file_manager::Volume>> volume_list =
224 file_manager::VolumeManager::Get(browser_context)->GetVolumeList();
225
226 for (const auto& extension : registry->enabled_extensions()) {
227 file_system::VolumeListChangedEvent event_args;
228 if (!GetVolumeListForExtension(volume_list, &consent_provider,
229 *extension.get(), &event_args.volumes)) {
230 continue;
231 }
232
233 event_router->DispatchEventToExtension(
234 extension->id(),
235 std::make_unique<Event>(
236 events::FILE_SYSTEM_ON_VOLUME_LIST_CHANGED,
237 file_system::OnVolumeListChanged::kEventName,
238 file_system::OnVolumeListChanged::Create(event_args)));
239 }
240 }
241
242 } // namespace file_system_api
243 #endif // defined(OS_CHROMEOS)
244
ChromeFileSystemDelegate()245 ChromeFileSystemDelegate::ChromeFileSystemDelegate() {}
246
~ChromeFileSystemDelegate()247 ChromeFileSystemDelegate::~ChromeFileSystemDelegate() {}
248
GetDefaultDirectory()249 base::FilePath ChromeFileSystemDelegate::GetDefaultDirectory() {
250 base::FilePath documents_dir;
251 base::PathService::Get(chrome::DIR_USER_DOCUMENTS, &documents_dir);
252 return documents_dir;
253 }
254
ShowSelectFileDialog(scoped_refptr<ExtensionFunction> extension_function,ui::SelectFileDialog::Type type,const base::FilePath & default_path,const ui::SelectFileDialog::FileTypeInfo * file_types,FileSystemDelegate::FilesSelectedCallback files_selected_callback,base::OnceClosure file_selection_canceled_callback)255 bool ChromeFileSystemDelegate::ShowSelectFileDialog(
256 scoped_refptr<ExtensionFunction> extension_function,
257 ui::SelectFileDialog::Type type,
258 const base::FilePath& default_path,
259 const ui::SelectFileDialog::FileTypeInfo* file_types,
260 FileSystemDelegate::FilesSelectedCallback files_selected_callback,
261 base::OnceClosure file_selection_canceled_callback) {
262 const Extension* extension = extension_function->extension();
263 content::WebContents* web_contents =
264 extension_function->GetSenderWebContents();
265
266 if (!web_contents)
267 return false;
268
269 // TODO(asargent/benwells) - As a short term remediation for
270 // crbug.com/179010 we're adding the ability for a allowlisted extension to
271 // use this API since chrome.fileBrowserHandler.selectFile is ChromeOS-only.
272 // Eventually we'd like a better solution and likely this code will go back
273 // to being platform-app only.
274
275 // Make sure there is an app window associated with the web contents, so that
276 // platform apps cannot open the file picker from a background page.
277 // TODO(michaelpg): As a workaround for https://crbug.com/736930, allow this
278 // to work from a background page for non-platform apps (which, in practice,
279 // is restricted to allowlisted extensions).
280 if (extension->is_platform_app() &&
281 !AppWindowRegistry::Get(extension_function->browser_context())
282 ->GetAppWindowForWebContents(web_contents)) {
283 return false;
284 }
285
286 // The file picker will hold a reference to the ExtensionFunction
287 // instance, preventing its destruction (and subsequent sending of the
288 // function response) until the user has selected a file or cancelled the
289 // picker. At that point, the picker will delete itself, which will also free
290 // the function instance.
291 new FileEntryPicker(web_contents, default_path, *file_types, type,
292 std::move(files_selected_callback),
293 std::move(file_selection_canceled_callback));
294 return true;
295 }
296
ConfirmSensitiveDirectoryAccess(bool has_write_permission,const base::string16 & app_name,content::WebContents * web_contents,base::OnceClosure on_accept,base::OnceClosure on_cancel)297 void ChromeFileSystemDelegate::ConfirmSensitiveDirectoryAccess(
298 bool has_write_permission,
299 const base::string16& app_name,
300 content::WebContents* web_contents,
301 base::OnceClosure on_accept,
302 base::OnceClosure on_cancel) {
303 CreateDirectoryAccessConfirmationDialog(has_write_permission, app_name,
304 web_contents, std::move(on_accept),
305 std::move(on_cancel));
306 }
307
GetDescriptionIdForAcceptType(const std::string & accept_type)308 int ChromeFileSystemDelegate::GetDescriptionIdForAcceptType(
309 const std::string& accept_type) {
310 if (accept_type == "image/*")
311 return IDS_IMAGE_FILES;
312 if (accept_type == "audio/*")
313 return IDS_AUDIO_FILES;
314 if (accept_type == "video/*")
315 return IDS_VIDEO_FILES;
316 return 0;
317 }
318
319 #if defined(OS_CHROMEOS)
320 FileSystemDelegate::GrantVolumesMode
GetGrantVolumesMode(content::BrowserContext * browser_context,content::RenderFrameHost * render_frame_host,const Extension & extension)321 ChromeFileSystemDelegate::GetGrantVolumesMode(
322 content::BrowserContext* browser_context,
323 content::RenderFrameHost* render_frame_host,
324 const Extension& extension) {
325 // Only kiosk apps in kiosk sessions can use this API.
326 // Additionally it is enabled for allowlisted component extensions and apps.
327 ConsentProviderDelegate consent_provider_delegate(
328 Profile::FromBrowserContext(browser_context));
329 return ConsentProvider(&consent_provider_delegate)
330 .GetGrantVolumesMode(extension);
331 }
332
RequestFileSystem(content::BrowserContext * browser_context,scoped_refptr<ExtensionFunction> requester,const Extension & extension,std::string volume_id,bool writable,FileSystemCallback success_callback,ErrorCallback error_callback)333 void ChromeFileSystemDelegate::RequestFileSystem(
334 content::BrowserContext* browser_context,
335 scoped_refptr<ExtensionFunction> requester,
336 const Extension& extension,
337 std::string volume_id,
338 bool writable,
339 FileSystemCallback success_callback,
340 ErrorCallback error_callback) {
341 ConsentProviderDelegate consent_provider_delegate(
342 Profile::FromBrowserContext(browser_context));
343 ConsentProvider consent_provider(&consent_provider_delegate);
344
345 using file_manager::VolumeManager;
346 using file_manager::Volume;
347 VolumeManager* const volume_manager = VolumeManager::Get(browser_context);
348 DCHECK(volume_manager);
349
350 if (writable &&
351 !app_file_handler_util::HasFileSystemWritePermission(&extension)) {
352 std::move(error_callback).Run(kRequiresFileSystemWriteError);
353 return;
354 }
355
356 if (consent_provider.GetGrantVolumesMode(extension) ==
357 FileSystemDelegate::kGrantNone) {
358 std::move(error_callback).Run(kNotSupportedOnNonKioskSessionError);
359 return;
360 }
361
362 base::WeakPtr<file_manager::Volume> volume =
363 volume_manager->FindVolumeById(volume_id);
364 if (!volume.get() ||
365 !consent_provider.IsGrantableForVolume(extension, volume)) {
366 std::move(error_callback).Run(kVolumeNotFoundError);
367 return;
368 }
369
370 scoped_refptr<storage::FileSystemContext> file_system_context =
371 util::GetStoragePartitionForExtensionId(extension.id(), browser_context)
372 ->GetFileSystemContext();
373 storage::ExternalFileSystemBackend* const backend =
374 file_system_context->external_backend();
375 DCHECK(backend);
376
377 base::FilePath virtual_path;
378 if (!backend->GetVirtualPath(volume->mount_path(), &virtual_path)) {
379 std::move(error_callback).Run(kSecurityError);
380 return;
381 }
382
383 if (writable && (volume->is_read_only())) {
384 std::move(error_callback).Run(kSecurityError);
385 return;
386 }
387
388 ConsentProvider::ConsentCallback callback =
389 base::BindOnce(&OnConsentReceived, browser_context, requester,
390 std::move(success_callback), std::move(error_callback),
391 extension.id(), volume, writable);
392
393 consent_provider.RequestConsent(extension, requester->render_frame_host(),
394 volume, writable, std::move(callback));
395 }
396
GetVolumeList(content::BrowserContext * browser_context,const Extension & extension,VolumeListCallback success_callback,ErrorCallback error_callback)397 void ChromeFileSystemDelegate::GetVolumeList(
398 content::BrowserContext* browser_context,
399 const Extension& extension,
400 VolumeListCallback success_callback,
401 ErrorCallback error_callback) {
402 ConsentProviderDelegate consent_provider_delegate(
403 Profile::FromBrowserContext(browser_context));
404 ConsentProvider consent_provider(&consent_provider_delegate);
405
406 const std::vector<base::WeakPtr<file_manager::Volume>> volume_list =
407 file_manager::VolumeManager::Get(browser_context)->GetVolumeList();
408 std::vector<file_system::Volume> result_volume_list;
409
410 GetVolumeListForExtension(volume_list, &consent_provider, extension,
411 &result_volume_list);
412 std::move(success_callback).Run(result_volume_list);
413 }
414
415 #endif // defined(OS_CHROMEOS)
416
GetSavedFilesService(content::BrowserContext * browser_context)417 SavedFilesServiceInterface* ChromeFileSystemDelegate::GetSavedFilesService(
418 content::BrowserContext* browser_context) {
419 return apps::SavedFilesService::Get(browser_context);
420 }
421
422 } // namespace extensions
423