blob: f024101b05c152a49e9f55315a77d595817698ea [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/api/file_system/consent_provider.h"
#include <memory>
#include <string>
#include "base/bind.h"
#include "base/logging.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/chromeos/app_mode/kiosk_app_manager.h"
#include "chrome/browser/chromeos/file_manager/app_id.h"
#include "chrome/browser/chromeos/file_manager/volume_manager.h"
#include "chrome/browser/extensions/api/file_system/request_file_system_notification.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/views/extensions/request_file_system_dialog_view.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/common/api/file_system.h"
#include "extensions/common/manifest_handlers/kiosk_mode_info.h"
#include "extensions/common/permissions/permissions_data.h"
namespace extensions {
namespace {
// List of whitelisted component apps and extensions by their ids for
// chrome.fileSystem.requestFileSystem.
const char* const kRequestFileSystemComponentWhitelist[] = {
file_manager::kFileManagerAppId, file_manager::kVideoPlayerAppId,
file_manager::kGalleryAppId, file_manager::kAudioPlayerAppId,
file_manager::kImageLoaderExtensionId, file_manager::kZipArchiverId,
// TODO(henryhsu,b/110126438): Remove this extension id, and add it only
// for tests.
"pkplfbidichfdicaijlchgnapepdginl" // Testing extensions.
};
ui::DialogButton g_auto_dialog_button_for_test = ui::DIALOG_BUTTON_NONE;
// Gets a WebContents instance handle for a current window of a platform app
// with |app_id|. If not found, then returns NULL.
content::WebContents* GetWebContentsForAppId(Profile* profile,
const std::string& app_id) {
AppWindowRegistry* const registry = AppWindowRegistry::Get(profile);
DCHECK(registry);
AppWindow* const app_window = registry->GetCurrentAppWindowForApp(app_id);
return app_window ? app_window->web_contents() : nullptr;
}
// Converts the clicked button to a consent result and passes it via the
// |callback|.
void DialogResultToConsent(
const file_system_api::ConsentProvider::ConsentCallback& callback,
ui::DialogButton button) {
switch (button) {
case ui::DIALOG_BUTTON_NONE:
callback.Run(file_system_api::ConsentProvider::CONSENT_IMPOSSIBLE);
break;
case ui::DIALOG_BUTTON_OK:
callback.Run(file_system_api::ConsentProvider::CONSENT_GRANTED);
break;
case ui::DIALOG_BUTTON_CANCEL:
callback.Run(file_system_api::ConsentProvider::CONSENT_REJECTED);
break;
}
}
} // namespace
namespace file_system_api {
ConsentProvider::ConsentProvider(DelegateInterface* delegate)
: delegate_(delegate) {
DCHECK(delegate_);
}
ConsentProvider::~ConsentProvider() {
}
void ConsentProvider::RequestConsent(
const Extension& extension,
content::RenderFrameHost* host,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable,
const ConsentCallback& callback) {
DCHECK(IsGrantableForVolume(extension, volume));
// If a whitelisted component, then no need to ask or inform the user.
if (extension.location() == Manifest::COMPONENT &&
delegate_->IsWhitelistedComponent(extension)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, CONSENT_GRANTED));
return;
}
// If a whitelisted app or extensions to access Downloads folder, then no
// need to ask or inform the user.
if (volume.get() &&
volume->type() == file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY &&
delegate_->HasRequestDownloadsPermission(extension)) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, CONSENT_GRANTED));
return;
}
// If auto-launched kiosk app, then no need to ask user either, but show the
// notification.
if (delegate_->IsAutoLaunched(extension)) {
delegate_->ShowNotification(extension, volume, writable);
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, CONSENT_GRANTED));
return;
}
// If it's a kiosk app running in manual-launch kiosk session, then show
// the confirmation dialog.
if (KioskModeInfo::IsKioskOnly(&extension) &&
user_manager::UserManager::Get()->IsLoggedInAsKioskApp()) {
delegate_->ShowDialog(extension, host, volume, writable,
base::Bind(&DialogResultToConsent, callback));
return;
}
NOTREACHED() << "Cannot request consent for non-grantable extensions.";
}
FileSystemDelegate::GrantVolumesMode ConsentProvider::GetGrantVolumesMode(
const Extension& extension) {
const bool is_whitelisted_component =
delegate_->IsWhitelistedComponent(extension);
const bool is_running_in_kiosk_session =
KioskModeInfo::IsKioskOnly(&extension) &&
user_manager::UserManager::Get()->IsLoggedInAsKioskApp();
if (is_whitelisted_component || is_running_in_kiosk_session) {
return FileSystemDelegate::kGrantAll;
}
const bool is_whitelisted_non_component =
delegate_->HasRequestDownloadsPermission(extension);
return is_whitelisted_non_component ? FileSystemDelegate::kGrantPerVolume
: FileSystemDelegate::kGrantNone;
}
bool ConsentProvider::IsGrantableForVolume(
const Extension& extension,
const base::WeakPtr<file_manager::Volume>& volume) {
if (volume.get() &&
volume->type() == file_manager::VOLUME_TYPE_DOWNLOADS_DIRECTORY &&
delegate_->HasRequestDownloadsPermission(extension)) {
return true;
}
return GetGrantVolumesMode(extension) == FileSystemDelegate::kGrantAll;
}
ConsentProviderDelegate::ConsentProviderDelegate(Profile* profile)
: profile_(profile) {
DCHECK(profile_);
}
ConsentProviderDelegate::~ConsentProviderDelegate() {
}
// static
void ConsentProviderDelegate::SetAutoDialogButtonForTest(
ui::DialogButton button) {
g_auto_dialog_button_for_test = button;
}
void ConsentProviderDelegate::ShowDialog(
const Extension& extension,
content::RenderFrameHost* host,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable,
const file_system_api::ConsentProvider::ShowDialogCallback& callback) {
DCHECK(host);
content::WebContents* web_contents = nullptr;
// Find an app window to host the dialog.
content::WebContents* const foreground_contents =
content::WebContents::FromRenderFrameHost(host);
if (AppWindowRegistry::Get(profile_)->GetAppWindowForWebContents(
foreground_contents)) {
web_contents = foreground_contents;
}
// If there is no web contents handle, then the method is most probably
// executed from a background page.
if (!web_contents)
web_contents = GetWebContentsForAppId(profile_, extension.id());
if (!web_contents) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, ui::DIALOG_BUTTON_NONE));
return;
}
// Short circuit the user consent dialog for tests. This is far from a pretty
// code design.
if (g_auto_dialog_button_for_test != ui::DIALOG_BUTTON_NONE) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(callback, g_auto_dialog_button_for_test /* result */));
return;
}
// If the volume is gone, then cancel the dialog.
if (!volume.get()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, ui::DIALOG_BUTTON_CANCEL));
return;
}
RequestFileSystemDialogView::ShowDialog(
web_contents, extension.name(),
(volume->volume_label().empty() ? volume->volume_id()
: volume->volume_label()),
writable, callback);
}
void ConsentProviderDelegate::ShowNotification(
const Extension& extension,
const base::WeakPtr<file_manager::Volume>& volume,
bool writable) {
ShowNotificationForAutoGrantedRequestFileSystem(profile_, extension, volume,
writable);
}
bool ConsentProviderDelegate::IsAutoLaunched(const Extension& extension) {
chromeos::KioskAppManager::App app_info;
return chromeos::KioskAppManager::Get()->GetApp(extension.id(), &app_info) &&
app_info.was_auto_launched_with_zero_delay;
}
bool ConsentProviderDelegate::IsWhitelistedComponent(
const Extension& extension) {
for (auto* whitelisted_id : kRequestFileSystemComponentWhitelist) {
if (extension.id().compare(whitelisted_id) == 0)
return true;
}
return false;
}
bool ConsentProviderDelegate::HasRequestDownloadsPermission(
const Extension& extension) {
return extension.permissions_data()->HasAPIPermission(
APIPermission::kFileSystemRequestDownloads);
}
} // namespace file_system_api
} // namespace extensions