// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "extensions/browser/network_permissions_updater.h"

#include "base/barrier_closure.h"
#include "base/functional/callback_helpers.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/cors_origin_pattern_setter.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/extensions_browser_client.h"
#include "extensions/common/cors_util.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"

namespace extensions {

namespace {

void SetCorsOriginAccessListForExtensionHelper(
    const std::vector<content::BrowserContext*>& browser_contexts,
    const Extension& extension,
    std::vector<network::mojom::CorsOriginPatternPtr> allow_patterns,
    std::vector<network::mojom::CorsOriginPatternPtr> block_patterns,
    base::OnceClosure closure) {
  auto barrier_closure =
      BarrierClosure(browser_contexts.size(), std::move(closure));
  for (content::BrowserContext* browser_context : browser_contexts) {
    // SetCorsOriginAccessListForExtensionHelper should only affect an incognito
    // profile if the extension is actually allowed to run in an incognito
    // profile (not just by the extension manifest, but also by user
    // preferences).
    if (browser_context->IsOffTheRecord()) {
      DCHECK(util::IsIncognitoEnabled(extension.id(), browser_context));
    }

    content::CorsOriginPatternSetter::Set(
        browser_context, extension.origin(), mojo::Clone(allow_patterns),
        mojo::Clone(block_patterns), barrier_closure);
  }
}

}  // namespace

NetworkPermissionsUpdater::NetworkPermissionsUpdater(
    PassKey pass_key,
    content::BrowserContext& browser_context,
    base::OnceClosure completion_callback)
    : browser_context_(&browser_context),
      completion_callback_(std::move(completion_callback)) {}

NetworkPermissionsUpdater::~NetworkPermissionsUpdater() = default;

// static
void NetworkPermissionsUpdater::UpdateExtension(
    content::BrowserContext& browser_context,
    const Extension& extension,
    ContextSet context_set,
    base::OnceClosure completion_callback) {
  auto updater = std::make_unique<NetworkPermissionsUpdater>(
      PassKey(), browser_context, std::move(completion_callback));
  auto* updater_raw = updater.get();

  // The callback takes ownership of `updater`, ensuring it's deleted when
  // the update completes.
  updater_raw->UpdateExtension(
      extension, context_set,
      base::BindOnce(&NetworkPermissionsUpdater::OnOriginAccessUpdated,
                     std::move(updater)));
}

// static
void NetworkPermissionsUpdater::UpdateAllExtensions(
    content::BrowserContext& browser_context,
    base::OnceClosure completion_callback) {
  auto updater = std::make_unique<NetworkPermissionsUpdater>(
      PassKey(), browser_context, std::move(completion_callback));
  auto* updater_raw = updater.get();

  const ExtensionSet& extensions =
      ExtensionRegistry::Get(&browser_context)->enabled_extensions();

  // The `barrier_closure` takes ownership of `updater`, ensuring it's deleted
  // when the update completes.
  base::RepeatingClosure barrier_closure = base::BarrierClosure(
      extensions.size(),
      base::BindOnce(&NetworkPermissionsUpdater::OnOriginAccessUpdated,
                     std::move(updater)));

  // When updating all extensions, we always use "all related contexts".
  constexpr ContextSet kContextSet = ContextSet::kAllRelatedContexts;

  for (const auto& extension : extensions) {
    updater_raw->UpdateExtension(*extension, kContextSet, barrier_closure);
  }
}

void NetworkPermissionsUpdater::ResetOriginAccessForExtension(
    content::BrowserContext& browser_context,
    const Extension& extension) {
  SetCorsOriginAccessListForExtensionHelper({&browser_context}, extension, {},
                                            {}, base::DoNothing());
}

void NetworkPermissionsUpdater::UpdateExtension(
    const Extension& extension,
    ContextSet context_set,
    base::OnceClosure completion_callback) {
  std::vector<content::BrowserContext*> target_contexts;
  if (context_set == ContextSet::kCurrentContextOnly) {
    target_contexts = {browser_context_.get()};
  } else {
    DCHECK_EQ(ContextSet::kAllRelatedContexts, context_set);
    target_contexts =
        ExtensionsBrowserClient::Get()->GetRelatedContextsForExtension(
            browser_context_, extension);
  }

  SetCorsOriginAccessListForExtensionHelper(
      target_contexts, extension, CreateCorsOriginAccessAllowList(extension),
      CreateCorsOriginAccessBlockList(extension),
      std::move(completion_callback));
}

// static
void NetworkPermissionsUpdater::OnOriginAccessUpdated(
    std::unique_ptr<NetworkPermissionsUpdater> updater) {
  std::move(updater->completion_callback_).Run();
}

}  // namespace extensions
