chromium/chrome/browser/ui/webui/ash/onc_import_message_handler.cc

// Copyright 2020 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/webui/ash/onc_import_message_handler.h"

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/stringprintf.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/network/onc/network_onc_utils.h"
#include "chromeos/ash/components/network/onc/onc_certificate_importer_impl.h"
#include "chromeos/components/onc/onc_parsed_certificates.h"
#include "chromeos/components/onc/onc_utils.h"
#include "components/onc/onc_constants.h"
#include "components/policy/core/browser/policy_conversions.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"

namespace ash {

namespace {

void GetCertDBOnIOThread(
    NssCertDatabaseGetter database_getter,
    base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  auto split_callback = base::SplitOnceCallback(std::move(callback));
  net::NSSCertDatabase* cert_db =
      std::move(database_getter).Run(std::move(split_callback.first));
  if (cert_db)
    std::move(split_callback.second).Run(cert_db);
}

}  // namespace

OncImportMessageHandler::OncImportMessageHandler() = default;

OncImportMessageHandler::~OncImportMessageHandler() = default;

void OncImportMessageHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "importONC", base::BindRepeating(&OncImportMessageHandler::OnImportONC,
                                       base::Unretained(this)));
}

void OncImportMessageHandler::Respond(const std::string& callback_id,
                                      const std::string& result,
                                      bool is_error) {
  base::Value::List response;
  response.Append(result);
  response.Append(is_error);
  ResolveJavascriptCallback(base::Value(callback_id), response);
}

void OncImportMessageHandler::OnImportONC(const base::Value::List& list) {
  CHECK_EQ(2u, list.size());
  std::string callback_id = list[0].GetString();
  std::string onc_blob = list[1].GetString();
  AllowJavascript();

  // TODO(crbug.com/40753707): Pass the `NssCertDatabaseGetter` to
  // the `CertImporter`. This is not unsafe if the profile shuts down during
  // this operation.
  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(
          &GetCertDBOnIOThread,
          NssServiceFactory::GetForContext(Profile::FromWebUI(web_ui()))
              ->CreateNSSCertDatabaseGetterForIOThread(),
          base::BindPostTaskToCurrentDefault(base::BindOnce(
              &OncImportMessageHandler::ImportONCToNSSDB,
              weak_factory_.GetWeakPtr(), callback_id, onc_blob))));
}

void OncImportMessageHandler::ImportONCToNSSDB(const std::string& callback_id,
                                               const std::string& onc_blob,
                                               net::NSSCertDatabase* nssdb) {
  const user_manager::User* user =
      ProfileHelper::Get()->GetUserByProfile(Profile::FromWebUI(web_ui()));
  if (!user) {
    Respond(callback_id, "User not found.", /*is_error=*/true);
    return;
  }

  std::string result;
  bool has_error = false;

  ::onc::ONCSource onc_source = ::onc::ONC_SOURCE_USER_IMPORT;
  base::Value::List network_configs;
  base::Value::Dict global_network_config;
  base::Value::List certificates;
  if (!chromeos::onc::ParseAndValidateOncForImport(
          onc_blob, onc_source, /*passphrase=*/std::string(), &network_configs,
          &global_network_config, &certificates)) {
    has_error = true;
    result += "Errors occurred during ONC parsing.\n";
  }

  std::string import_error;
  int num_networks_imported =
      onc::ImportNetworksForUser(user, network_configs, &import_error);
  if (!import_error.empty()) {
    has_error = true;
    result += "Error importing networks: " + import_error + "\n";
  }
  result +=
      base::StringPrintf("Networks imported: %d\n", num_networks_imported);
  if (certificates.empty()) {
    if (!num_networks_imported)
      has_error = true;
    Respond(callback_id, result, has_error);
    return;
  }

  auto cert_importer = std::make_unique<onc::CertificateImporterImpl>(
      content::GetIOThreadTaskRunner({}), nssdb);
  auto certs =
      std::make_unique<chromeos::onc::OncParsedCertificates>(certificates);
  if (certs->has_error()) {
    has_error = true;
    result += "Some certificates could not be parsed.\n";
  }
  auto* const cert_importer_ptr = cert_importer.get();
  cert_importer_ptr->ImportAllCertificatesUserInitiated(
      certs->server_or_authority_certificates(), certs->client_certificates(),
      base::BindOnce(&OncImportMessageHandler::OnCertificatesImported,
                     weak_factory_.GetWeakPtr(), std::move(cert_importer),
                     callback_id, result, has_error));
}

void OncImportMessageHandler::OnCertificatesImported(
    std::unique_ptr<onc::CertificateImporterImpl> cert_importer,
    const std::string& callback_id,
    const std::string& previous_result,
    bool has_error,
    bool cert_import_success) {
  std::string result = previous_result;
  if (!cert_import_success) {
    has_error = true;
    result += "Some certificates couldn't be imported.\n";
  }
  Respond(callback_id, result, has_error);
  // |cert_importer| will be destroyed when the callback exits.
}

}  // namespace ash