blob: 84eb79d9a6688d19cdf367029643454eb53be290 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/extensions/extension_enable_flow.h"
#include <memory>
#include "base/bind.h"
#include "base/notreached.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_system.h"
#if !BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/ui/profile_picker.h"
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "extensions/browser/api/management/management_api.h"
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
using extensions::Extension;
ExtensionEnableFlow::ExtensionEnableFlow(Profile* profile,
const std::string& extension_id,
ExtensionEnableFlowDelegate* delegate)
: profile_(profile), extension_id_(extension_id), delegate_(delegate) {}
ExtensionEnableFlow::~ExtensionEnableFlow() = default;
void ExtensionEnableFlow::StartForWebContents(
content::WebContents* parent_contents) {
parent_contents_ = parent_contents;
parent_window_ = nullptr;
Run();
}
void ExtensionEnableFlow::StartForNativeWindow(
gfx::NativeWindow parent_window) {
parent_contents_ = nullptr;
parent_window_ = parent_window;
Run();
}
void ExtensionEnableFlow::Start() {
Run();
}
void ExtensionEnableFlow::Run() {
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile_);
const Extension* extension =
registry->disabled_extensions().GetByID(extension_id_);
if (!extension) {
extension = registry->terminated_extensions().GetByID(extension_id_);
// It's possible (though unlikely) the app could have been uninstalled since
// the user clicked on it.
if (!extension)
return;
// If the app was terminated, reload it first.
service->ReloadExtension(extension_id_);
// ReloadExtension reallocates the Extension object.
extension = registry->disabled_extensions().GetByID(extension_id_);
// |extension| could be nullptr for asynchronous load, such as the case of
// an unpacked extension. Wait for the load to continue the flow.
if (!extension) {
StartObserving();
return;
}
}
CheckPermissionAndMaybePromptUser();
}
void ExtensionEnableFlow::CheckPermissionAndMaybePromptUser() {
extensions::ExtensionSystem* system =
extensions::ExtensionSystem::Get(profile_);
extensions::ExtensionService* service = system->extension_service();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile_);
const Extension* extension =
registry->disabled_extensions().GetByID(extension_id_);
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
extensions::SupervisedUserExtensionsDelegate*
supervised_user_extensions_delegate =
extensions::ManagementAPI::GetFactoryInstance()
->Get(profile_)
->GetSupervisedUserExtensionsDelegate();
DCHECK(supervised_user_extensions_delegate);
if (profile_->IsChild() && extension &&
// Only ask for parent approval if the extension still requires approval.
!supervised_user_extensions_delegate->IsExtensionAllowedByParent(
*extension, profile_)) {
// Either ask for parent permission or notify the child that their parent
// has disabled this action.
auto parent_permission_callback =
base::BindOnce(&ExtensionEnableFlow::OnParentPermissionDialogDone,
weak_ptr_factory_.GetWeakPtr());
auto error_callback =
base::BindOnce(&ExtensionEnableFlow::OnBlockedByParentDialogDone,
weak_ptr_factory_.GetWeakPtr());
supervised_user_extensions_delegate->PromptForParentPermissionOrShowError(
*extension, profile_, parent_contents_,
std::move(parent_permission_callback), std::move(error_callback));
return;
}
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
bool abort = !extension ||
// The extension might be force-disabled by policy.
system->management_policy()->MustRemainDisabled(
extension, nullptr, nullptr);
if (abort) {
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/false); // |delegate_| may delete us.
return;
}
if (profiles::IsProfileLocked(profile_->GetPath())) {
#if !BUILDFLAG(IS_CHROMEOS_ASH)
ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
ProfilePicker::EntryPoint::kProfileLocked));
#endif // !BUILDFLAG(IS_CHROMEOS_ASH)
return;
}
extensions::ExtensionPrefs* prefs = extensions::ExtensionPrefs::Get(profile_);
if (!prefs->DidExtensionEscalatePermissions(extension_id_)) {
// Enable the extension immediately if its privileges weren't escalated.
// This is a no-op if the extension was previously terminated.
service->EnableExtension(extension_id_);
DCHECK(service->IsExtensionEnabled(extension_id_));
delegate_->ExtensionEnableFlowFinished(); // |delegate_| may delete us.
return;
}
CreatePrompt();
ExtensionInstallPrompt::PromptType type =
ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(profile_,
extension);
prompt_->ShowDialog(base::BindOnce(&ExtensionEnableFlow::InstallPromptDone,
weak_ptr_factory_.GetWeakPtr()),
extension, nullptr,
std::make_unique<ExtensionInstallPrompt::Prompt>(type),
ExtensionInstallPrompt::GetDefaultShowDialogCallback());
}
void ExtensionEnableFlow::CreatePrompt() {
prompt_.reset(parent_contents_
? new ExtensionInstallPrompt(parent_contents_)
: new ExtensionInstallPrompt(profile_, nullptr));
}
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
void ExtensionEnableFlow::OnParentPermissionDialogDone(
extensions::SupervisedUserExtensionsDelegate::ParentPermissionDialogResult
result) {
switch (result) {
case extensions::SupervisedUserExtensionsDelegate::
ParentPermissionDialogResult::kParentPermissionReceived:
EnableExtension();
break;
case extensions::SupervisedUserExtensionsDelegate::
ParentPermissionDialogResult::kParentPermissionCanceled:
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/true); // |delegate_| may delete us.
break;
case extensions::SupervisedUserExtensionsDelegate::
ParentPermissionDialogResult::kParentPermissionFailed:
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/false); // |delegate_| may delete us.
break;
}
}
void ExtensionEnableFlow::OnBlockedByParentDialogDone() {
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/false); // |delegate_| may delete us.
}
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
void ExtensionEnableFlow::StartObserving() {
extension_registry_observation_.Observe(
extensions::ExtensionRegistry::Get(profile_));
load_error_observation_.Observe(extensions::LoadErrorReporter::GetInstance());
}
void ExtensionEnableFlow::StopObserving() {
extension_registry_observation_.Reset();
load_error_observation_.Reset();
}
void ExtensionEnableFlow::OnLoadFailure(
content::BrowserContext* browser_context,
const base::FilePath& file_path,
const std::string& error) {
StopObserving();
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/false); // |delegate_| may delete us.
}
void ExtensionEnableFlow::OnExtensionLoaded(
content::BrowserContext* browser_context,
const Extension* extension) {
if (extension->id() == extension_id_) {
StopObserving();
CheckPermissionAndMaybePromptUser();
}
}
void ExtensionEnableFlow::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const Extension* extension,
extensions::UninstallReason reason) {
if (extension->id() == extension_id_) {
StopObserving();
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/false); // |delegate_| may delete us.
}
}
void ExtensionEnableFlow::EnableExtension() {
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(profile_)->extension_service();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile_);
// The extension can be uninstalled in another window while the UI was
// showing. Treat it as a cancellation and notify |delegate_|.
const Extension* extension =
registry->disabled_extensions().GetByID(extension_id_);
if (!extension) {
delegate_->ExtensionEnableFlowAborted(
/*user_initiated=*/true); // |delegate_| may delete us.
return;
}
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
if (profile_->IsChild()) {
// We need to add parent approval first.
SupervisedUserService* supervised_user_service =
SupervisedUserServiceFactory::GetForProfile(profile_);
supervised_user_service->AddExtensionApproval(*extension);
supervised_user_service->RecordExtensionEnablementUmaMetrics(
/*enabled=*/true);
}
#endif // BUILDFLAG(ENABLE_SUPERVISED_USERS)
service->GrantPermissionsAndEnableExtension(extension);
DCHECK(service->IsExtensionEnabled(extension_id_));
delegate_->ExtensionEnableFlowFinished(); // |delegate_| may delete us.
}
void ExtensionEnableFlow::InstallPromptDone(
ExtensionInstallPrompt::DoneCallbackPayload payload) {
switch (payload.result) {
case ExtensionInstallPrompt::Result::ACCEPTED:
EnableExtension();
break;
case ExtensionInstallPrompt::Result::ACCEPTED_WITH_WITHHELD_PERMISSIONS:
// This dialog doesn't support the "withhold permissions" checkbox.
NOTREACHED();
break;
case ExtensionInstallPrompt::Result::USER_CANCELED:
case ExtensionInstallPrompt::Result::ABORTED:
delegate_->ExtensionEnableFlowAborted(/*user_initiated=*/
payload.result ==
ExtensionInstallPrompt::Result::
USER_CANCELED);
// `delegate_` may delete us.
break;
}
}