chromium/chromeos/ash/components/auth_panel/impl/auth_factor_store.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_factor_store.h"

#include "ash/public/cpp/ime_controller.h"
#include "base/callback_list.h"
#include "base/check.h"
#include "base/check_op.h"
#include "base/notimplemented.h"
#include "base/notreached.h"
#include "chromeos/ash/components/auth_panel/impl/auth_panel_event_dispatcher.h"
#include "chromeos/ash/components/osauth/public/auth_engine_api.h"
#include "chromeos/ash/components/osauth/public/common_types.h"

namespace ash {

AuthFactorStore::State::State() = default;

AuthFactorStore::State::~State() = default;

AuthFactorStore::State::PasswordViewState::PasswordViewState(
    bool is_capslock_on)
    : is_capslock_on_(is_capslock_on) {}

void AuthFactorStore::State::InitializePasswordViewState(bool is_capslock_on) {
  password_view_state_.emplace(is_capslock_on);
}

AuthFactorStore::State::PasswordViewState::~PasswordViewState() = default;

AuthFactorStore::AuthFactorStore(ImeController* ime_controller,
                                 AuthHubConnector* connector,
                                 std::optional<AshAuthFactor> password_type,
                                 AuthHub* auth_hub)
    : auth_hub_connector_(connector), auth_hub_(auth_hub) {
  password_type_ = password_type;

  // For now, assume the password view state is always required to be present
  // because we always have a password. We default to `false` for the state of
  // capslock if `ime_controller` is not available.
  state_.InitializePasswordViewState(
      ime_controller == nullptr ? false : ime_controller->IsCapsLockEnabled());

  submit_password_callback_ =
      base::BindRepeating(&AuthEngineApi::AuthenticateWithPassword);
}

AuthFactorStore::~AuthFactorStore() = default;

base::CallbackListSubscription AuthFactorStore::Subscribe(
    OnStateUpdatedCallback callback) {
  return state_update_callbacks_.Add(callback);
}

void AuthFactorStore::OnUserAction(
    const AuthPanelEventDispatcher::UserAction& action) {
  switch (action.type_) {
    case AuthPanelEventDispatcher::UserAction::kPasswordPinToggle: {
      NOTIMPLEMENTED();
      break;
    }
    case AuthPanelEventDispatcher::UserAction::kPasswordSubmit: {
      CHECK(state_.password_view_state_.has_value());

      auto password =
          state_.password_view_state_->auth_textfield_state_.password_;
      if (!password.empty() &&
          state_.password_view_state_->is_factor_enabled_) {
        state_.authentication_stage_ =
            State::AuthenticationStage::kAuthenticating;
        SubmitPassword(password);
      } else {
        NOTREACHED_IN_MIGRATION()
            << "AuthPanel: Password was submitted while textfield is "
            << "empty or password factor is disabled";
      }

      break;
    }
    case AuthPanelEventDispatcher::UserAction::kDisplayPasswordButtonPressed: {
      CHECK(state_.password_view_state_.has_value());

      bool& previous = state_.password_view_state_->auth_textfield_state_
                           .is_password_visible_;
      previous = !previous;

      break;
    }
    case AuthPanelEventDispatcher::UserAction::
        kPasswordTextfieldContentsChanged: {
      CHECK(action.payload_.has_value());
      CHECK(state_.password_view_state_.has_value());

      auto new_field_contents = *action.payload_;
      state_.password_view_state_->auth_textfield_state_.password_ =
          new_field_contents;
      break;
    }
    case AuthPanelEventDispatcher::UserAction::kCapslockKeyPressed: {
      CHECK(state_.password_view_state_.has_value());

      state_.password_view_state_->is_capslock_on_ =
          !state_.password_view_state_->is_capslock_on_;
      break;
    }
    case AuthPanelEventDispatcher::UserAction::kPasswordTextfieldFocused: {
      CHECK(state_.password_view_state_.has_value());

      state_.password_view_state_->is_password_textfield_focused_ = true;
      break;
    }
    case AuthPanelEventDispatcher::UserAction::kPasswordTextfieldBlurred: {
      CHECK(state_.password_view_state_.has_value());

      state_.password_view_state_->is_password_textfield_focused_ = false;
      break;
    }
    case AuthPanelEventDispatcher::UserAction::
        kEscapePressedOnPasswordTextfield: {
      CHECK(state_.password_view_state_.has_value());

      auth_hub_->CancelCurrentAttempt(auth_hub_connector_);
      break;
    }
  }

  NotifyStateChanged();
}

void AuthFactorStore::OnFactorStateChanged(AshAuthFactor factor,
                                           AuthFactorState state) {
  switch (factor) {
    case AshAuthFactor::kGaiaPassword:
    case AshAuthFactor::kLocalPassword:
      CHECK(password_type_.has_value());
      CHECK_EQ(factor, password_type_.value());

      state_.password_view_state_->factor_state_ = state;
      break;
    case AshAuthFactor::kCryptohomePin:
    case AshAuthFactor::kFingerprint:
    case AshAuthFactor::kSmartCard:
    case AshAuthFactor::kSmartUnlock:
    case AshAuthFactor::kRecovery:
    case AshAuthFactor::kLegacyPin:
    case AshAuthFactor::kLegacyFingerprint:
      NOTIMPLEMENTED();
      break;
  }

  NotifyStateChanged();
}

void AuthFactorStore::OnAuthVerdict(
    AshAuthFactor factor,
    AuthPanelEventDispatcher::AuthVerdict verdict) {
  NOTIMPLEMENTED();
}

void AuthFactorStore::NotifyStateChanged() {
  state_update_callbacks_.Notify(state_);
}

void AuthFactorStore::SubmitPassword(const std::string& password) {
  if (state_.password_view_state_->factor_state_ !=
      AuthFactorState::kFactorReady) {
    LOG(ERROR) << "Attempting to submit password while password factor state "
                  "is not ready";
    return;
  }

  // We cannot reach here if the password factor was unavailable, as the UI
  // for it would not have been shown. Check this invariant here.
  CHECK(password_type_.has_value());

  submit_password_callback_.Run(auth_hub_connector_, password_type_.value(),
                                password);
}

}  // namespace ash