chromium/chrome/browser/ui/webui/ash/settings/pages/kerberos/kerberos_accounts_handler.cc

// Copyright 2019 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/settings/pages/kerberos/kerberos_accounts_handler.h"

#include <string>
#include <utility>

#include "ash/constants/ash_features.h"
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "chrome/browser/ash/kerberos/kerberos_credentials_manager.h"
#include "chrome/browser/ash/kerberos/kerberos_credentials_manager_factory.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/ash/settings/pages/os_settings_section.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/web_ui_data_source.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/l10n/time_format.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/chromeos/resources/grit/ui_chromeos_resources.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_rep.h"

namespace ash::settings {
namespace {

bool IsKerberosEnabled(
    KerberosCredentialsManager* kerberos_credentials_manager) {
  return kerberos_credentials_manager != nullptr &&
         kerberos_credentials_manager->IsKerberosEnabled();
}

// Adds title for Kerberos subsection and Add Accounts page.
void AddKerberosTitleStrings(content::WebUIDataSource* html_source) {
  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"kerberosAccountsSubMenuLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_SUBMENU_LABEL},
      {"kerberosAccountsPageTitle", IDS_SETTINGS_KERBEROS_ACCOUNTS_PAGE_TITLE},
  };
  html_source->AddLocalizedStrings(kLocalizedStrings);
}

// Adds load time boolean corresponding to Kerberos enable state.
void AddKerberosEnabledFlag(
    content::WebUIDataSource* html_source,
    KerberosCredentialsManager* kerberos_credentials_manager) {
  html_source->AddBoolean("isKerberosEnabled",
                          IsKerberosEnabled(kerberos_credentials_manager));
}

// Adds load time strings to Kerberos Add Accounts dialog.
void AddKerberosAddAccountDialogStrings(content::WebUIDataSource* html_source) {
  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"kerberosAccountsAdvancedConfigLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_ADVANCED_CONFIG_LABEL},
      {"kerberosAdvancedConfigTitle",
       IDS_SETTINGS_KERBEROS_ADVANCED_CONFIG_TITLE},
      {"kerberosAdvancedConfigDesc",
       IDS_SETTINGS_KERBEROS_ADVANCED_CONFIG_DESC},
      {"addKerberosAccountRememberPassword",
       IDS_SETTINGS_ADD_KERBEROS_ACCOUNT_REMEMBER_PASSWORD},
      {"kerberosPassword", IDS_SETTINGS_KERBEROS_PASSWORD},
      {"kerberosUsername", IDS_SETTINGS_KERBEROS_USERNAME},
      {"addKerberosAccountDescription",
       IDS_SETTINGS_ADD_KERBEROS_ACCOUNT_DESCRIPTION},
      {"kerberosErrorNetworkProblem",
       IDS_SETTINGS_KERBEROS_ERROR_NETWORK_PROBLEM},
      {"kerberosErrorUsernameInvalid",
       IDS_SETTINGS_KERBEROS_ERROR_USERNAME_INVALID},
      {"kerberosErrorUsernameUnknown",
       IDS_SETTINGS_KERBEROS_ERROR_USERNAME_UNKNOWN},
      {"kerberosErrorDuplicatePrincipalName",
       IDS_SETTINGS_KERBEROS_ERROR_DUPLICATE_PRINCIPAL_NAME},
      {"kerberosErrorContactingServer",
       IDS_SETTINGS_KERBEROS_ERROR_CONTACTING_SERVER},
      {"kerberosErrorPasswordInvalid",
       IDS_SETTINGS_KERBEROS_ERROR_PASSWORD_INVALID},
      {"kerberosErrorPasswordExpired",
       IDS_SETTINGS_KERBEROS_ERROR_PASSWORD_EXPIRED},
      {"kerberosErrorKdcEncType", IDS_SETTINGS_KERBEROS_ERROR_KDC_ENC_TYPE},
      {"kerberosErrorGeneral", IDS_SETTINGS_KERBEROS_ERROR_GENERAL},
      {"kerberosConfigErrorSectionNestedInGroup",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NESTED_IN_GROUP},
      {"kerberosConfigErrorSectionSyntax",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_SYNTAX},
      {"kerberosConfigErrorExpectedOpeningCurlyBrace",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXPECTED_OPENING_CURLY_BRACE},
      {"kerberosConfigErrorExtraCurlyBrace",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_EXTRA_CURLY_BRACE},
      {"kerberosConfigErrorRelationSyntax",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_RELATION_SYNTAX_ERROR},
      {"kerberosConfigErrorKeyNotSupported",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KEY_NOT_SUPPORTED},
      {"kerberosConfigErrorSectionNotSupported",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_SECTION_NOT_SUPPORTED},
      {"kerberosConfigErrorKrb5FailedToParse",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_KRB5_FAILED_TO_PARSE},
      {"kerberosConfigErrorTooManyNestedGroups",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_TOO_MANY_NESTED_GROUPS},
      {"kerberosConfigErrorLineTooLong",
       IDS_SETTINGS_KERBEROS_CONFIG_ERROR_LINE_TOO_LONG},
      {"addKerberosAccountRefreshButtonLabel",
       IDS_SETTINGS_ADD_KERBEROS_ACCOUNT_REFRESH_BUTTON_LABEL},
      {"addKerberosAccount", IDS_SETTINGS_ADD_KERBEROS_ACCOUNT},
      {"refreshKerberosAccount", IDS_SETTINGS_REFRESH_KERBEROS_ACCOUNT},
  };
  html_source->AddLocalizedStrings(kLocalizedStrings);

  PrefService* local_state = g_browser_process->local_state();

  // Whether the 'Remember password' checkbox is enabled.
  html_source->AddBoolean(
      "kerberosRememberPasswordEnabled",
      local_state->GetBoolean(::prefs::kKerberosRememberPasswordEnabled));

  // Whether the 'Remember password' checkbox should be checked by default.
  html_source->AddBoolean(
      "kerberosRememberPasswordByDefault",
      features::IsKerberosRememberPasswordByDefaultEnabled());

  // Prefilled domain if policy is enabled. Note that Kerberos
  // domains should be in all uppercase.
  html_source->AddString("kerberosDomainAutocomplete",
                         base::ToUpperASCII(local_state->GetString(
                             ::prefs::kKerberosDomainAutocomplete)));

  // Kerberos default prefilled configuration.
  // If the 'KerberosUseCustomPrefilledConfig' policy is set to 'true', the
  // configuration comes from the 'KerberosCustomPrefilledConfig' policy.
  // Otherwise the default value is used.
  const std::string prefilledConfig =
      local_state->GetBoolean(::prefs::kKerberosUseCustomPrefilledConfig)
          ? local_state->GetString(::prefs::kKerberosCustomPrefilledConfig)
          : KerberosCredentialsManager::GetDefaultKerberosConfig();
  html_source->AddString("defaultKerberosConfig", prefilledConfig);
}

// Adds load time strings to Kerberos Accounts page.
void AddKerberosAccountsPageStrings(content::WebUIDataSource* html_source) {
  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"kerberosAccountsAddAccountLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_ADD_ACCOUNT_LABEL},
      {"kerberosAccountsRefreshNowLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_REFRESH_NOW_LABEL},
      {"kerberosAccountsSetAsActiveAccountLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_SET_AS_ACTIVE_ACCOUNT_LABEL},
      {"kerberosAccountsSignedOut", IDS_SETTINGS_KERBEROS_ACCOUNTS_SIGNED_OUT},
      {"kerberosAccountsListHeader",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_LIST_HEADER},
      {"kerberosAccountsRemoveAccountLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_REMOVE_ACCOUNT_LABEL},
      {"kerberosAccountsReauthenticationLabel",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_REAUTHENTICATION_LABEL},
      {"kerberosAccountsTicketActive",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_TICKET_ACTIVE},
      {"kerberosAccountsAccountRemovedTip",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_ACCOUNT_REMOVED_TIP},
      {"kerberosAccountsAccountRefreshedTip",
       IDS_SETTINGS_KERBEROS_ACCOUNTS_ACCOUNT_REFRESHED_TIP},
      {"kerberosAccountsSignedIn", IDS_SETTINGS_KERBEROS_ACCOUNTS_SIGNED_IN},
  };
  html_source->AddLocalizedStrings(kLocalizedStrings);

  PrefService* local_state = g_browser_process->local_state();

  // Whether new Kerberos accounts may be added.
  html_source->AddBoolean(
      "kerberosAddAccountsAllowed",
      local_state->GetBoolean(::prefs::kKerberosAddAccountsAllowed));

  // Kerberos accounts page with "Learn more" link.
  html_source->AddString(
      "kerberosAccountsDescription",
      l10n_util::GetStringFUTF16(IDS_SETTINGS_KERBEROS_ACCOUNTS_DESCRIPTION,
                                 OsSettingsSection::GetHelpUrlWithBoard(
                                     chrome::kKerberosAccountsLearnMoreURL)));
}

}  // namespace

// static
std::unique_ptr<KerberosAccountsHandler>
KerberosAccountsHandler::CreateIfKerberosEnabled(Profile* profile) {
  KerberosCredentialsManager* kerberos_credentials_manager =
      KerberosCredentialsManagerFactory::GetExisting(profile);
  if (!IsKerberosEnabled(kerberos_credentials_manager)) {
    return nullptr;
  }
  return base::WrapUnique(
      new KerberosAccountsHandler(kerberos_credentials_manager));
}

// static
void KerberosAccountsHandler::AddLoadTimeKerberosStrings(
    content::WebUIDataSource* html_source,
    KerberosCredentialsManager* kerberos_credentials_manager) {
  AddKerberosEnabledFlag(html_source, kerberos_credentials_manager);
  AddKerberosTitleStrings(html_source);
  AddKerberosAccountsPageStrings(html_source);
  AddKerberosAddAccountDialogStrings(html_source);
}

KerberosAccountsHandler::~KerberosAccountsHandler() = default;

KerberosAccountsHandler::KerberosAccountsHandler(
    KerberosCredentialsManager* kerberos_credentials_manager)
    : kerberos_credentials_manager_(kerberos_credentials_manager) {
  DCHECK(kerberos_credentials_manager_);
}

void KerberosAccountsHandler::RegisterMessages() {
  web_ui()->RegisterMessageCallback(
      "getKerberosAccounts",
      base::BindRepeating(&KerberosAccountsHandler::HandleGetKerberosAccounts,
                          weak_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "addKerberosAccount",
      base::BindRepeating(&KerberosAccountsHandler::HandleAddKerberosAccount,
                          weak_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "removeKerberosAccount",
      base::BindRepeating(&KerberosAccountsHandler::HandleRemoveKerberosAccount,
                          weak_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "validateKerberosConfig",
      base::BindRepeating(
          &KerberosAccountsHandler::HandleValidateKerberosConfig,
          weak_factory_.GetWeakPtr()));
  web_ui()->RegisterMessageCallback(
      "setAsActiveKerberosAccount",
      base::BindRepeating(
          &KerberosAccountsHandler::HandleSetAsActiveKerberosAccount,
          weak_factory_.GetWeakPtr()));
}

void KerberosAccountsHandler::HandleGetKerberosAccounts(
    const base::Value::List& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const std::string& callback_id = args[0].GetString();

  if (!kerberos_credentials_manager_->IsKerberosEnabled()) {
    ResolveJavascriptCallback(base::Value(callback_id), base::Value());
    return;
  }

  kerberos_credentials_manager_->ListAccounts(
      base::BindOnce(&KerberosAccountsHandler::OnListAccounts,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void KerberosAccountsHandler::OnListAccounts(
    const std::string& callback_id,
    const kerberos::ListAccountsResponse& response) {
  base::Value::List accounts;

  // Ticket icon is a key.
  gfx::ImageSkia skia_ticket_icon =
      *ui::ResourceBundle::GetSharedInstance().GetImageSkiaNamed(
          IDR_KERBEROS_ICON_KEY);
  std::string ticket_icon = webui::GetBitmapDataUrl(
      skia_ticket_icon.GetRepresentation(1.0f).GetBitmap());

  const std::string& active_principal =
      kerberos_credentials_manager_->GetActiveAccount();

  for (int n = 0; n < response.accounts_size(); ++n) {
    const kerberos::Account& account = response.accounts(n);

    // Format validity time as 'xx hours yy minutes' for validity < 1 day and
    // 'nn days' otherwise.
    base::TimeDelta tgt_validity =
        base::Seconds(account.tgt_validity_seconds());
    const std::u16string valid_for_duration = ui::TimeFormat::Detailed(
        ui::TimeFormat::FORMAT_DURATION, ui::TimeFormat::LENGTH_LONG,
        tgt_validity < base::Days(1) ? -1 : 0, tgt_validity);

    base::Value::Dict account_dict;
    account_dict.Set("principalName", account.principal_name());
    account_dict.Set("config", account.krb5conf());
    account_dict.Set("isSignedIn", account.tgt_validity_seconds() > 0);
    account_dict.Set("validForDuration", valid_for_duration);
    account_dict.Set("isActive", account.principal_name() == active_principal);
    account_dict.Set("isManaged", account.is_managed());
    account_dict.Set("passwordWasRemembered",
                     account.password_was_remembered());
    account_dict.Set("pic", ticket_icon);
    accounts.Append(std::move(account_dict));
  }

  ResolveJavascriptCallback(base::Value(callback_id), accounts);
}

void KerberosAccountsHandler::HandleAddKerberosAccount(
    const base::Value::List& args) {
  AllowJavascript();

  CHECK_EQ(6U, args.size());
  const std::string& callback_id = args[0].GetString();
  const std::string& principal_name = args[1].GetString();
  const std::string& password = args[2].GetString();
  const bool remember_password = args[3].GetBool();
  const std::string& config = args[4].GetString();
  const bool allow_existing = args[5].GetBool();

  if (!kerberos_credentials_manager_->IsKerberosEnabled()) {
    ResolveJavascriptCallback(base::Value(callback_id),
                              base::Value(kerberos::ERROR_KERBEROS_DISABLED));
    return;
  }

  kerberos_credentials_manager_->AddAccountAndAuthenticate(
      principal_name, false /* is_managed */, password, remember_password,
      config, allow_existing,
      base::BindOnce(&KerberosAccountsHandler::OnAddAccountAndAuthenticate,
                     weak_factory_.GetWeakPtr(), callback_id));
}

void KerberosAccountsHandler::OnAddAccountAndAuthenticate(
    const std::string& callback_id,
    kerberos::ErrorType error) {
  ResolveJavascriptCallback(base::Value(callback_id),
                            base::Value(static_cast<int>(error)));
}

void KerberosAccountsHandler::HandleRemoveKerberosAccount(
    const base::Value::List& args) {
  AllowJavascript();

  CHECK_EQ(2U, args.size());
  const std::string& callback_id = args[0].GetString();
  const std::string& principal_name = args[1].GetString();

  if (!kerberos_credentials_manager_->IsKerberosEnabled()) {
    ResolveJavascriptCallback(base::Value(callback_id),
                              base::Value(kerberos::ERROR_KERBEROS_DISABLED));
    return;
  }

  kerberos_credentials_manager_->RemoveAccount(
      principal_name, base::BindOnce(&KerberosAccountsHandler::OnRemoveAccount,
                                     weak_factory_.GetWeakPtr(), callback_id));
}

void KerberosAccountsHandler::OnRemoveAccount(const std::string& callback_id,
                                              kerberos::ErrorType error) {
  ResolveJavascriptCallback(base::Value(callback_id),
                            base::Value(static_cast<int>(error)));
}

void KerberosAccountsHandler::HandleValidateKerberosConfig(
    const base::Value::List& args) {
  AllowJavascript();

  CHECK_EQ(2U, args.size());
  const std::string& callback_id = args[0].GetString();
  const std::string& krb5conf = args[1].GetString();

  if (!kerberos_credentials_manager_->IsKerberosEnabled()) {
    ResolveJavascriptCallback(base::Value(callback_id),
                              base::Value(kerberos::ERROR_KERBEROS_DISABLED));
    return;
  }

  kerberos_credentials_manager_->ValidateConfig(
      krb5conf, base::BindOnce(&KerberosAccountsHandler::OnValidateConfig,
                               weak_factory_.GetWeakPtr(), callback_id));
}

void KerberosAccountsHandler::OnValidateConfig(
    const std::string& callback_id,
    const kerberos::ValidateConfigResponse& response) {
  base::Value::Dict error_info;
  error_info.Set("code", response.error_info().code());
  if (response.error_info().has_line_index()) {
    error_info.Set("lineIndex", response.error_info().line_index());
  }

  base::Value::Dict value;
  value.Set("error", static_cast<int>(response.error()));
  value.Set("errorInfo", std::move(error_info));
  ResolveJavascriptCallback(base::Value(callback_id), value);
}

void KerberosAccountsHandler::HandleSetAsActiveKerberosAccount(
    const base::Value::List& args) {
  AllowJavascript();

  CHECK_EQ(1U, args.size());
  const std::string& principal_name = args[0].GetString();

  kerberos_credentials_manager_->SetActiveAccount(principal_name);
}

void KerberosAccountsHandler::OnJavascriptAllowed() {
  credentials_manager_observation_.Observe(kerberos_credentials_manager_.get());
}

void KerberosAccountsHandler::OnJavascriptDisallowed() {
  credentials_manager_observation_.Reset();
}

void KerberosAccountsHandler::OnAccountsChanged() {
  RefreshUI();
}

void KerberosAccountsHandler::RefreshUI() {
  FireWebUIListener("kerberos-accounts-changed");
}

}  // namespace ash::settings