Chromium Code Reviews| Index: chrome/browser/extensions/api/file_system/file_system_api.cc |
| diff --git a/chrome/browser/extensions/api/file_system/file_system_api.cc b/chrome/browser/extensions/api/file_system/file_system_api.cc |
| index dbe8bfdc83aa506e9c2dc6cabb020662319e8094..972f0f31eb9c69c713ddd84641e5b3264c0b7a53 100644 |
| --- a/chrome/browser/extensions/api/file_system/file_system_api.cc |
| +++ b/chrome/browser/extensions/api/file_system/file_system_api.cc |
| @@ -58,7 +58,12 @@ |
| #endif |
| #if defined(OS_CHROMEOS) |
| +#include "base/thread_task_runner_handle.h" |
| +#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h" |
| #include "chrome/browser/chromeos/file_manager/filesystem_api_util.h" |
| +#include "chrome/browser/chromeos/file_manager/volume_manager.h" |
| +#include "components/user_manager/user_manager.h" |
| +#include "extensions/common/manifest_handlers/kiosk_mode_info.h" |
| #endif |
| using apps::SavedFileEntry; |
| @@ -76,6 +81,12 @@ const char kRequiresFileSystemDirectoryError[] = |
| const char kMultipleUnsupportedError[] = |
| "acceptsMultiple: true is not supported for 'saveFile'"; |
| const char kUnknownIdError[] = "Unknown id"; |
| +const char kNotSupportedError[] = "Operation not supported."; |
|
benwells
2015/03/20 00:42:39
It would be good to provide users with info on why
mtomasz
2015/03/20 01:56:00
Done.
|
| + |
| +#if defined(OS_CHROMEOS) |
| +const char kVolumeNotFoundError[] = "Volume not found."; |
| +const char kSecurityError[] = "Security error."; |
| +#endif |
| namespace file_system = extensions::api::file_system; |
| namespace ChooseEntry = file_system::ChooseEntry; |
| @@ -994,4 +1005,201 @@ bool FileSystemGetObservedEntriesFunction::RunSync() { |
| return false; |
| } |
| +FileSystemRequestFileSystemFunction::FileSystemRequestFileSystemFunction() |
| + : chrome_details_(this) { |
| +} |
| + |
| +ExtensionFunction::ResponseAction FileSystemRequestFileSystemFunction::Run() { |
| + using extensions::api::file_system::RequestFileSystem::Params; |
| + const scoped_ptr<Params> params(Params::Create(*args_)); |
| + EXTENSION_FUNCTION_VALIDATE(params); |
| + |
| +#if !defined(OS_CHROMEOS) |
| + NOTIMPLEMENTED(); |
| + return RespondNow(Error(kNotSupportedError)); |
| + |
| +#else |
| + using file_manager::VolumeManager; |
| + using file_manager::VolumeInfo; |
| + VolumeManager* const volume_manager = |
| + VolumeManager::Get(chrome_details_.GetProfile()); |
| + DCHECK(volume_manager); |
| + |
| + const bool writable = |
| + params->options.writable.get() && *params->options.writable.get(); |
| + |
| + // Only kiosk apps in kiosk sessions can use this API. Additionally component |
|
benwells
2015/03/20 00:42:39
This check (kiosk mode) can go up before the code
mtomasz
2015/03/20 01:56:00
Done.
|
| + // extensions and apps, which is not documented though. |
| + if ((!user_manager::UserManager::Get()->IsLoggedInAsKioskApp() || |
| + !KioskModeInfo::IsKioskEnabled(extension())) && |
|
benwells
2015/03/20 00:42:39
I don't understand kiosk mode that well but rememb
mtomasz
2015/03/20 01:56:00
Yes, the API is meant to work in both modes, but f
|
| + extension()->location() != Manifest::COMPONENT) { |
| + return RespondNow(Error(kNotSupportedError)); |
| + } |
| + |
| + if (writable && |
| + !app_file_handler_util::HasFileSystemWritePermission(extension_.get())) { |
| + return RespondNow(Error(kRequiresFileSystemWriteError)); |
| + } |
| + |
| + VolumeInfo volume_info; |
| + if (!volume_manager->FindVolumeInfoById(params->options.volume_id, |
| + &volume_info)) { |
| + return RespondNow(Error(kVolumeNotFoundError)); |
| + } |
| + |
| + const GURL site = extensions::util::GetSiteForExtensionId( |
| + extension_id(), chrome_details_.GetProfile()); |
| + scoped_refptr<storage::FileSystemContext> file_system_context = |
| + content::BrowserContext::GetStoragePartitionForSite( |
| + chrome_details_.GetProfile(), site)->GetFileSystemContext(); |
| + storage::ExternalFileSystemBackend* const backend = |
| + file_system_context->external_backend(); |
| + DCHECK(backend); |
| + |
| + base::FilePath virtual_path; |
| + if (!backend->GetVirtualPath(volume_info.mount_path, &virtual_path)) |
| + return RespondNow(Error(kSecurityError)); |
| + |
| + if (writable && (volume_info.is_read_only)) |
| + return RespondNow(Error(kSecurityError)); |
| + |
| + const bool requires_consent = |
| + !chromeos::KioskAppManager::Get()->IsAutoLaunchEnabled() && |
| + extension()->location() != Manifest::COMPONENT; |
|
benwells
2015/03/20 00:42:39
Right now this will never be true due to the check
mtomasz
2015/03/20 01:56:00
Not really. It will be true for kiosk apps launche
benwells
2015/03/20 02:43:57
Ohhh, I'd misread the logic above...
|
| + if (!requires_consent) { |
| + // Grant the permission without showing the dialog. |
| + base::ThreadTaskRunnerHandle::Get()->PostTask( |
| + FROM_HERE, |
| + base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, |
| + this, volume_info.volume_id, writable, true /* granted */)); |
| + } else { |
| + // TODO(mtomasz): Create a better display name, which is the most meaningful |
| + // to the user. |
| + const std::string display_name = !volume_info.volume_label.empty() |
| + ? volume_info.volume_label |
| + : volume_info.volume_id; |
| + RequestConsent( |
| + display_name, writable, |
| + base::Bind(&FileSystemRequestFileSystemFunction::OnConsentReceived, |
| + this, volume_info.volume_id, writable)); |
| + } |
| + |
| + return RespondLater(); |
| +#endif |
| +} |
| + |
| +#if defined(OS_CHROMEOS) |
| +void FileSystemRequestFileSystemFunction::RequestConsent( |
| + const std::string& display_name, |
| + bool writable, |
| + const base::Callback<void(bool)>& callback) { |
| + // TODO(mtomasz): Implement the consent dialog. |
| + callback.Run(false); |
|
benwells
2015/03/20 00:42:39
I think you should use PostTask here, as you're ca
mtomasz
2015/03/20 01:56:00
Done.
|
| +} |
| + |
| +void FileSystemRequestFileSystemFunction::OnConsentReceived( |
| + const std::string& volume_id, |
| + bool writable, |
| + bool granted) { |
| + using file_manager::VolumeManager; |
| + using file_manager::VolumeInfo; |
| + |
| + if (!granted) { |
| + SetError(kSecurityError); |
| + SendResponse(false); |
| + return; |
| + } |
| + |
| + // Fetch the volume again, in case it's gone by the time the permission is |
| + // granted. |
| + VolumeManager* const volume_manager = |
| + VolumeManager::Get(chrome_details_.GetProfile()); |
| + DCHECK(volume_manager); |
| + |
| + VolumeInfo volume_info; |
| + if (!volume_manager->FindVolumeInfoById(volume_id, &volume_info)) { |
| + SetError(kVolumeNotFoundError); |
| + SendResponse(false); |
| + return; |
| + } |
| + |
| + const GURL site = extensions::util::GetSiteForExtensionId( |
| + extension_id(), chrome_details_.GetProfile()); |
| + scoped_refptr<storage::FileSystemContext> file_system_context = |
| + content::BrowserContext::GetStoragePartitionForSite( |
| + chrome_details_.GetProfile(), site)->GetFileSystemContext(); |
| + storage::ExternalFileSystemBackend* const backend = |
| + file_system_context->external_backend(); |
| + DCHECK(backend); |
| + |
| + // The volume may be unmounted and remounted by the time we reach this logic. |
| + // TODO(mtomasz): Add a unique identifier to VolumeInfo to guarantee that the |
| + // permissions are granted to exactly that volume which was plugged in when |
| + // the dialog was shown. |
| + base::FilePath virtual_path; |
| + if (!backend->GetVirtualPath(volume_info.mount_path, &virtual_path)) { |
| + SetError(kSecurityError); |
|
benwells
2015/03/20 00:42:39
Is this the correct error?
mtomasz
2015/03/20 01:56:00
GetVirtualPath() is not used to check if the volum
|
| + SendResponse(false); |
| + return; |
| + } |
| + |
| + storage::IsolatedContext* const isolated_context = |
| + storage::IsolatedContext::GetInstance(); |
| + DCHECK(isolated_context); |
| + |
| + const storage::FileSystemURL original_url = |
| + file_system_context->CreateCrackedFileSystemURL( |
| + GURL("chrome-extension://" + extension_id()), |
|
benwells
2015/03/20 00:42:39
I think there is a constant you can use for the sc
mtomasz
2015/03/20 01:56:00
Done.
|
| + storage::kFileSystemTypeExternal, virtual_path); |
| + |
| + // Set a fixed register name, as the automatic one would leak the mount point |
| + // directory. |
| + std::string register_name = "fs"; |
| + const std::string file_system_id = |
| + isolated_context->RegisterFileSystemForPath( |
| + storage::kFileSystemTypeNativeForPlatformApp, |
| + std::string() /* file_system_id */, original_url.path(), |
| + ®ister_name); |
| + if (file_system_id.empty()) { |
| + SetError(kSecurityError); |
| + SendResponse(false); |
| + return; |
| + } |
| + |
| + backend->GrantFileAccessToExtension(extension_->id(), virtual_path); |
| + |
| + // Grant file permissions to the renderer hosting component. |
| + content::ChildProcessSecurityPolicy* policy = |
| + content::ChildProcessSecurityPolicy::GetInstance(); |
| + DCHECK(policy); |
| + |
| + // Read-only permisisons. |
| + policy->GrantReadFile(render_view_host()->GetProcess()->GetID(), |
| + volume_info.mount_path); |
| + policy->GrantReadFileSystem(render_view_host()->GetProcess()->GetID(), |
| + file_system_id); |
| + |
| + // Additional write permissions. |
| + if (writable) { |
| + policy->GrantCreateReadWriteFile(render_view_host()->GetProcess()->GetID(), |
| + volume_info.mount_path); |
| + policy->GrantCopyInto(render_view_host()->GetProcess()->GetID(), |
| + volume_info.mount_path); |
| + policy->GrantWriteFileSystem(render_view_host()->GetProcess()->GetID(), |
| + file_system_id); |
| + policy->GrantDeleteFromFileSystem(render_view_host()->GetProcess()->GetID(), |
| + file_system_id); |
| + policy->GrantCreateFileForFileSystem( |
| + render_view_host()->GetProcess()->GetID(), file_system_id); |
| + } |
| + |
| + base::DictionaryValue* const dict = new base::DictionaryValue(); |
| + dict->SetString("file_system_id", file_system_id); |
| + dict->SetString("file_system_path", register_name); |
| + |
| + SetResult(dict); |
| + SendResponse(true); |
| +} |
| +#endif |
| + |
| } // namespace extensions |