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           &register_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