chromium/chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/ui/webui/ash/in_session_password_change/password_change_ui.h"

#include <memory>

#include "ash/constants/ash_switches.h"
#include "ash/webui/common/trusted_types_util.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/login/login_pref_names.h"
#include "chrome/browser/ash/login/saml/password_expiry_notification.h"
#include "chrome/browser/ash/policy/core/user_cloud_policy_manager_ash.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/webui/ash/in_session_password_change/password_change_dialogs.h"
#include "chrome/browser/ui/webui/ash/in_session_password_change/password_change_handler.h"
#include "chrome/browser/ui/webui/ash/in_session_password_change/urgent_password_expiry_notification_handler.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/browser_resources.h"
#include "chrome/grit/gaia_auth_host_resources_map.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/password_change_resources.h"
#include "chrome/grit/password_change_resources_map.h"
#include "chromeos/ash/components/login/auth/public/saml_password_attributes.h"
#include "chromeos/strings/grit/chromeos_strings.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_ui_data_source.h"
#include "net/base/url_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/strings/grit/ui_strings.h"

namespace ash {

namespace {

std::string GetPasswordChangeUrl(Profile* profile) {
  if (base::CommandLine::ForCurrentProcess()->HasSwitch(
          switches::kSamlPasswordChangeUrl)) {
    return base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
        switches::kSamlPasswordChangeUrl);
  }

  const policy::UserCloudPolicyManagerAsh* user_cloud_policy_manager =
      profile->GetUserCloudPolicyManagerAsh();
  if (user_cloud_policy_manager) {
    const enterprise_management::PolicyData* policy =
        user_cloud_policy_manager->core()->store()->policy();
    if (policy->has_change_password_uri()) {
      return policy->change_password_uri();
    }
  }

  return SamlPasswordAttributes::LoadFromPrefs(profile->GetPrefs())
      .password_change_url();
}

std::u16string GetHostedHeaderText(const std::string& password_change_url) {
  std::u16string host =
      base::UTF8ToUTF16(net::GetHostAndOptionalPort(GURL(password_change_url)));
  DCHECK(!host.empty());
  return l10n_util::GetStringFUTF16(IDS_LOGIN_SAML_PASSWORD_CHANGE_NOTICE,
                                    host);
}

void AddSize(content::WebUIDataSource* source,
             const std::string& suffix,
             const gfx::Size& size) {
  source->AddInteger("width" + suffix, size.width());
  source->AddInteger("height" + suffix, size.height());
}

}  // namespace

bool PasswordChangeUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  return Profile::FromBrowserContext(browser_context)
      ->GetPrefs()
      ->GetBoolean(ash::prefs::kSamlInSessionPasswordChangeEnabled);
}

PasswordChangeUI::PasswordChangeUI(content::WebUI* web_ui)
    : ui::WebDialogUI(web_ui) {
  Profile* profile = Profile::FromWebUI(web_ui);
  CHECK(profile->GetPrefs()->GetBoolean(
      prefs::kSamlInSessionPasswordChangeEnabled));
  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
      profile, chrome::kChromeUIPasswordChangeHost);
  ash::EnableTrustedTypesCSP(source);

  const std::string password_change_url = GetPasswordChangeUrl(profile);
  web_ui->AddMessageHandler(
      std::make_unique<PasswordChangeHandler>(password_change_url));

  source->AddString("hostedHeader", GetHostedHeaderText(password_change_url));
  source->UseStringsJs();

  source->AddResourcePaths(
      base::make_span(kPasswordChangeResources, kPasswordChangeResourcesSize));
  source->SetDefaultResource(IDR_PASSWORD_CHANGE_PASSWORD_CHANGE_APP_HTML);

  // Add Gaia Authenticator resources
  source->AddResourcePaths(
      base::make_span(kGaiaAuthHostResources, kGaiaAuthHostResourcesSize));
}

PasswordChangeUI::~PasswordChangeUI() = default;

bool ConfirmPasswordChangeUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  return Profile::FromBrowserContext(browser_context)
      ->GetPrefs()
      ->GetBoolean(ash::prefs::kSamlInSessionPasswordChangeEnabled);
}

ConfirmPasswordChangeUI::ConfirmPasswordChangeUI(content::WebUI* web_ui)
    : ui::WebDialogUI(web_ui) {
  Profile* profile = Profile::FromWebUI(web_ui);
  CHECK(profile->GetPrefs()->GetBoolean(
      prefs::kSamlInSessionPasswordChangeEnabled));
  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
      profile, chrome::kChromeUIConfirmPasswordChangeHost);
  ash::EnableTrustedTypesCSP(source);

  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"title", IDS_PASSWORD_CHANGE_CONFIRM_DIALOG_TITLE},
      {"bothPasswordsPrompt",
       IDS_PASSWORD_CHANGE_CONFIRM_DIALOG_BOTH_PASSWORDS_PROMPT},
      {"oldPasswordPrompt",
       IDS_PASSWORD_CHANGE_CONFIRM_DIALOG_OLD_PASSWORD_PROMPT},
      {"newPasswordPrompt",
       IDS_PASSWORD_CHANGE_CONFIRM_DIALOG_NEW_PASSWORD_PROMPT},
      {"oldPassword", IDS_PASSWORD_CHANGE_OLD_PASSWORD_LABEL},
      {"newPassword", IDS_PASSWORD_CHANGE_NEW_PASSWORD_LABEL},
      {"confirmNewPassword", IDS_PASSWORD_CHANGE_CONFIRM_NEW_PASSWORD_LABEL},
      {"incorrectPassword", IDS_LOGIN_CONFIRM_PASSWORD_INCORRECT_PASSWORD},
      {"matchError", IDS_PASSWORD_CHANGE_PASSWORDS_DONT_MATCH},
      {"save", IDS_PASSWORD_CHANGE_CONFIRM_SAVE_BUTTON}};

  source->AddLocalizedStrings(kLocalizedStrings);

  AddSize(source, "", ConfirmPasswordChangeDialog::GetSize(false, false));
  AddSize(source, "Old", ConfirmPasswordChangeDialog::GetSize(true, false));
  AddSize(source, "New", ConfirmPasswordChangeDialog::GetSize(false, true));
  AddSize(source, "OldNew", ConfirmPasswordChangeDialog::GetSize(true, true));

  source->UseStringsJs();

  source->AddResourcePaths(
      base::make_span(kPasswordChangeResources, kPasswordChangeResourcesSize));
  source->SetDefaultResource(
      IDR_PASSWORD_CHANGE_CONFIRM_PASSWORD_CHANGE_APP_HTML);

  // The ConfirmPasswordChangeHandler is added by the dialog, so no need to add
  // it here.
}

ConfirmPasswordChangeUI::~ConfirmPasswordChangeUI() = default;

bool UrgentPasswordExpiryNotificationUIConfig::IsWebUIEnabled(
    content::BrowserContext* browser_context) {
  return Profile::FromBrowserContext(browser_context)
      ->GetPrefs()
      ->GetBoolean(ash::prefs::kSamlInSessionPasswordChangeEnabled);
}

UrgentPasswordExpiryNotificationUI::UrgentPasswordExpiryNotificationUI(
    content::WebUI* web_ui)
    : ui::WebDialogUI(web_ui) {
  Profile* profile = Profile::FromWebUI(web_ui);
  PrefService* prefs = profile->GetPrefs();
  CHECK(prefs->GetBoolean(prefs::kSamlInSessionPasswordChangeEnabled));

  content::WebUIDataSource* source = content::WebUIDataSource::CreateAndAdd(
      profile, chrome::kChromeUIUrgentPasswordExpiryNotificationHost);
  ash::EnableTrustedTypesCSP(source);

  SamlPasswordAttributes attrs = SamlPasswordAttributes::LoadFromPrefs(prefs);
  if (attrs.has_expiration_time()) {
    const base::Time expiration_time = attrs.expiration_time();
    source->AddString("initialTitle", PasswordExpiryNotification::GetTitleText(
                                          expiration_time - base::Time::Now()));
    source->AddString(
        "expirationTime",
        base::NumberToString(expiration_time.InMillisecondsFSinceUnixEpoch()));
  }

  static constexpr webui::LocalizedString kLocalizedStrings[] = {
      {"body", IDS_PASSWORD_EXPIRY_CALL_TO_ACTION_CRITICAL},
      {"button", IDS_OK}};
  source->AddLocalizedStrings(kLocalizedStrings);

  source->UseStringsJs();

  source->AddResourcePaths(
      base::make_span(kPasswordChangeResources, kPasswordChangeResourcesSize));
  source->SetDefaultResource(
      IDR_PASSWORD_CHANGE_URGENT_PASSWORD_EXPIRY_NOTIFICATION_HTML);

  web_ui->AddMessageHandler(
      std::make_unique<UrgentPasswordExpiryNotificationHandler>());
}

UrgentPasswordExpiryNotificationUI::~UrgentPasswordExpiryNotificationUI() =
    default;

}  // namespace ash