chromium/chromeos/ash/components/auth_panel/impl/auth_panel.cc

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

#include "chromeos/ash/components/auth_panel/impl/auth_panel.h"

#include <memory>
#include <vector>

#include "ash/auth/views/auth_textfield.h"
#include "ash/login/ui/arrow_button_view.h"
#include "ash/login/ui/non_accessible_view.h"
#include "ash/public/cpp/ime_controller.h"
#include "ash/strings/grit/ash_strings.h"
#include "ash/style/ash_color_id.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "chromeos/ash/components/auth_panel/impl/auth_factor_store.h"
#include "chromeos/ash/components/auth_panel/impl/auth_panel_event_dispatcher.h"
#include "chromeos/ash/components/auth_panel/impl/factor_auth_view.h"
#include "chromeos/ash/components/auth_panel/impl/factor_auth_view_factory.h"
#include "chromeos/ash/components/auth_panel/impl/views/password_auth_view.h"
#include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h"
#include "chromeos/ash/components/osauth/public/common_types.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/views/bubble/bubble_border.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/controls/button/toggle_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_types.h"

namespace ash {

namespace {

std::optional<AshAuthFactor> GetPasswordFactorType(AuthFactorsSet factors) {
  bool has_local_password = factors.Has(AshAuthFactor::kLocalPassword);
  bool has_gaia_password = factors.Has(AshAuthFactor::kGaiaPassword);

  // We currently only support having either local passwords or gaia passwords,
  // but not both.
  CHECK(!has_local_password || !has_gaia_password);

  if (has_local_password) {
    return AshAuthFactor::kLocalPassword;
  } else if (has_gaia_password) {
    return AshAuthFactor::kGaiaPassword;
  } else {
    return std::nullopt;
  }
}

}  // namespace

AuthPanel::TestApi::TestApi(AuthPanel* auth_panel) : auth_panel_(auth_panel) {}

AuthPanel::TestApi::~TestApi() = default;

PasswordAuthView* AuthPanel::TestApi::GetPasswordAuthView() {
  auto password_view_wrapper =
      auth_panel_->views_[AshAuthFactor::kLocalPassword]
          ? auth_panel_->views_[AshAuthFactor::kLocalPassword]
          : auth_panel_->views_[AshAuthFactor::kGaiaPassword];

  auto children = password_view_wrapper->GetChildrenInZOrder();

  // Each wrapper should only contain a single FactorAuthView.
  // Verify this invariant here.
  CHECK(children.size() == 1);

  return static_cast<PasswordAuthView*>(children.front());
}

void AuthPanel::TestApi::SetSubmitPasswordCallback(
    auth_panel::SubmitPasswordCallback callback) {
  auth_panel_->store_->submit_password_callback_ = std::move(callback);
}

AuthPanel::AuthPanel(
    std::unique_ptr<FactorAuthViewFactory> view_factory,
    std::unique_ptr<AuthFactorStoreFactory> store_factory,
    std::unique_ptr<AuthPanelEventDispatcherFactory> event_dispatcher_factory,
    base::OnceClosure on_end_authentication,
    base::RepeatingClosure on_prefered_size_changed,
    AuthHubConnector* connector)
    : event_dispatcher_factory_(std::move(event_dispatcher_factory)),
      view_factory_(std::move(view_factory)),
      store_factory_(std::move(store_factory)),
      on_end_authentication_(std::move(on_end_authentication)),
      on_ui_changed_(std::move(on_prefered_size_changed)),
      auth_hub_connector_(connector) {
  SetLayoutManager(std::make_unique<views::FlexLayout>())
      ->SetOrientation(views::LayoutOrientation::kVertical)
      .SetMainAxisAlignment(views::LayoutAlignment::kCenter)
      .SetCrossAxisAlignment(views::LayoutAlignment::kCenter)
      .SetCollapseMargins(false);

  InitializeViewPlaceholders();
}

AuthPanel::~AuthPanel() = default;

void AuthPanel::InitializeViewPlaceholders() {
  // The order in which the views will be laid out in AuthPanel. We create
  // placeholder spots for them in the UI beforehand, and fill them out later
  // depending on the presence of factors.
  std::vector<AshAuthFactor> view_order{
      AshAuthFactor::kGaiaPassword,
  };

  for (AshAuthFactor factor : view_order) {
    views_[factor] = AddChildView(std::make_unique<NonAccessibleView>());
    views_[factor]->SetLayoutManager(std::make_unique<views::FlexLayout>());
  }
}

void AuthPanel::InitializeUi(AuthFactorsSet factors,
                             AuthHubConnector* connector) {
  std::optional<AshAuthFactor> password_type = GetPasswordFactorType(factors);

  store_ =
      store_factory_->CreateAuthFactorStore(ImeController::Get(), connector,
                                            /*password_type=*/password_type);
  event_dispatcher_ =
      event_dispatcher_factory_->CreateAuthPanelEventDispatcher(store_.get());

  for (auto&& factor : factors) {
    auto factor_auth_view = view_factory_->CreateFactorAuthView(
        factor, store_.get(), event_dispatcher_.get());
    if (factor_auth_view != nullptr) {
      // We ignore views that the factory doesn't know how to construct.
      views_[factor]->AddChildView(std::move(factor_auth_view));
    }
    event_dispatcher_->DispatchEvent(factor,
                                     AuthFactorState::kCheckingForPresence);
  }

  on_ui_changed_.Run();
}

void AuthPanel::OnFactorListChanged(FactorsStatusMap factors_with_status) {
  // Here, the list of auth factors can potentially change. Previously
  // available auth factors (at the time of `AuthPanel::InitializeUI`) can now
  // be unavailable. The converse is also true.
  // Existing auth factors can have their status changed.
  // To that end, we destroy the UI and recreate it, to ensure consistency.
  // It is also assumed that the password type will not change from
  // `kGaiaPassword` to `kLocalPassword` or vice-versa. Therefore we don't have
  // to inform `store_` of a new password type.

  // Order of operations is important, removing child views before clearing
  // our non-owning references will cause them to dangle.
  views_.clear();
  RemoveAllChildViews();

  InitializeViewPlaceholders();

  for (const auto& [factor, status] : factors_with_status) {
    auto factor_auth_view = view_factory_->CreateFactorAuthView(
        factor, store_.get(), event_dispatcher_.get());
    if (factor_auth_view != nullptr) {
      // We ignore views that the factory doesn't know how to construct.
      views_[factor]->AddChildView(std::move(factor_auth_view));
    }
    event_dispatcher_->DispatchEvent(factor, status);
  }

  on_ui_changed_.Run();
}

void AuthPanel::OnFactorStatusesChanged(FactorsStatusMap incremental_update) {
  for (const auto& [factor, status] : incremental_update) {
    event_dispatcher_->DispatchEvent(factor, status);
  }
}

void AuthPanel::OnFactorCustomSignal(AshAuthFactor factor) {
  NOTIMPLEMENTED();
}

void AuthPanel::OnFactorAuthFailure(AshAuthFactor factor) {
  event_dispatcher_->DispatchEvent(
      factor, AuthPanelEventDispatcher::AuthVerdict::kFailure);
}

void AuthPanel::OnFactorAuthSuccess(AshAuthFactor factor) {
  event_dispatcher_->DispatchEvent(
      factor, AuthPanelEventDispatcher::AuthVerdict::kSuccess);
}

void AuthPanel::OnEndAuthentication() {
  auth_hub_connector_ = nullptr;
  std::move(on_end_authentication_).Run();
}

BEGIN_METADATA(AuthPanel)
END_METADATA

}  // namespace ash