// 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/check.h"
#include "base/functional/bind.h"
#include "base/notreached.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/supervised_user/supervised_user_browser_utils.h"
#include "chrome/browser/ui/extensions/extension_enable_flow_delegate.h"
#include "extensions/browser/api/management/management_api.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_system.h"
#include "ui/gfx/native_ui_types.h"

#if !BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/profiles/profile_picker.h"
#endif  // !BUILDFLAG(IS_CHROMEOS)

using extensions::Extension;
using extensions::InstallPromptData;

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_ = gfx::NativeWindow();
  Run();
}

void ExtensionEnableFlow::StartForNativeWindow(
    gfx::NativeWindow parent_window) {
  parent_contents_ = nullptr;
  parent_window_ = parent_window;
  Run();
}

void ExtensionEnableFlow::Start() {
  Run();
}

void ExtensionEnableFlow::Run() {
  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.
    extensions::ExtensionRegistrar::Get(profile_)->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() {
  auto* system = extensions::ExtensionSystem::Get(profile_);
  auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
  auto* registry = extensions::ExtensionRegistry::Get(profile_);
  const Extension* extension =
      registry->disabled_extensions().GetByID(extension_id_);

  extensions::SupervisedUserExtensionsDelegate*
      supervised_user_extensions_delegate =
          extensions::ManagementAPI::GetFactoryInstance()
              ->Get(profile_)
              ->GetSupervisedUserExtensionsDelegate();
  DCHECK(supervised_user_extensions_delegate);
  if (supervised_user::AreExtensionsPermissionsEnabled(profile_) && extension &&

      // Only ask for parent approval if the extension still requires approval.
      !supervised_user_extensions_delegate->IsExtensionAllowedByParent(
          *extension)) {
    // Either ask for parent permission or notify the child that their parent
    // has disabled this action.
    auto extension_approval_callback =
        base::BindOnce(&ExtensionEnableFlow::OnExtensionApprovalDone,
                       weak_ptr_factory_.GetWeakPtr());
    supervised_user_extensions_delegate->RequestToEnableExtensionOrShowError(
        *extension, parent_contents_, std::move(extension_approval_callback));
    return;
  }

  bool abort =
      !extension ||
      // The extension might be force-disabled by policy.
      system->management_policy()->MustRemainDisabled(extension, nullptr);
  if (abort) {
    delegate_->ExtensionEnableFlowAborted(
        /*user_initiated=*/false);  // |delegate_| may delete us.
    return;
  }

  if (profiles::IsProfileLocked(profile_->GetPath())) {
#if !BUILDFLAG(IS_CHROMEOS)
    ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
        ProfilePicker::EntryPoint::kProfileLocked));

#endif  // !BUILDFLAG(IS_CHROMEOS)
    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.
    registrar->EnableExtension(extension_id_);

    DCHECK(registrar->IsExtensionEnabled(extension_id_));
    delegate_->ExtensionEnableFlowFinished();  // |delegate_| may delete us.
    return;
  }

  InstallPromptData::PromptType type =
      ExtensionInstallPrompt::GetReEnablePromptTypeForExtension(profile_,
                                                                extension);
  CreatePrompt(std::make_unique<InstallPromptData>(type));
  prompt_->ShowDialog(base::BindOnce(&ExtensionEnableFlow::InstallPromptDone,
                                     weak_ptr_factory_.GetWeakPtr()),
                      extension, nullptr,
                      ExtensionInstallPrompt::GetDefaultShowDialogCallback());
}

void ExtensionEnableFlow::CreatePrompt(
    std::unique_ptr<InstallPromptData> prompt) {
  prompt_.reset(
      parent_contents_
          ? new ExtensionInstallPrompt(parent_contents_, std::move(prompt))
          : new ExtensionInstallPrompt(profile_, gfx::NativeWindow(),
                                       std::move(prompt)));
}

void ExtensionEnableFlow::OnExtensionApprovalDone(
    extensions::SupervisedExtensionApprovalResult result) {
  switch (result) {
    case extensions::SupervisedExtensionApprovalResult::kApproved:
      EnableExtension();
      break;
    case extensions::SupervisedExtensionApprovalResult::kCanceled:
      delegate_->ExtensionEnableFlowAborted(
          /*user_initiated=*/true);  // |delegate_| may delete us.
      break;
    case extensions::SupervisedExtensionApprovalResult::kFailed:
    case extensions::SupervisedExtensionApprovalResult::kBlocked:
      delegate_->ExtensionEnableFlowAborted(
          /*user_initiated=*/false);  // |delegate_| may delete us.
      break;
  }
}

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::u16string& 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::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 (supervised_user::AreExtensionsPermissionsEnabled(profile_)) {
    // We need to add parent approval first.
    extensions::SupervisedUserExtensionsDelegate*
        supervised_user_extensions_delegate =
            extensions::ManagementAPI::GetFactoryInstance()
                ->Get(profile_)
                ->GetSupervisedUserExtensionsDelegate();
    CHECK(supervised_user_extensions_delegate);
    supervised_user_extensions_delegate->AddExtensionApproval(*extension);
    supervised_user_extensions_delegate->MaybeRecordPermissionsIncreaseMetrics(
        *extension);
    supervised_user_extensions_delegate->RecordExtensionEnablementUmaMetrics(
        /*enabled=*/true);
  }
  auto* registrar = extensions::ExtensionRegistrar::Get(profile_);
  registrar->GrantPermissionsAndEnableExtension(*extension);

  DCHECK(registrar->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();
    case ExtensionInstallPrompt::Result::USER_CANCELED:
    case ExtensionInstallPrompt::Result::ABORTED:
      delegate_->ExtensionEnableFlowAborted(/*user_initiated=*/
                                            payload.result ==
                                            ExtensionInstallPrompt::Result::
                                                USER_CANCELED);
      // `delegate_` may delete us.
      break;
  }
}
