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

#ifndef CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
#define CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_

#include <memory>
#include <optional>
#include <string>
#include <variant>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/memory/raw_ref.h"
#include "base/memory/ref_counted.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/time/time.h"
#include "base/types/strong_alias.h"
#include "build/build_config.h"
#include "chrome/browser/webauthn/authenticator_transport.h"
#include "chrome/browser/webauthn/gpm_enclave_transaction.h"
#include "chrome/browser/webauthn/local_authentication_token.h"
#include "chrome/browser/webauthn/shared_types.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/global_routing_id.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/pin.h"
#include "device/fido/public/fido_constants.h"
#include "device/fido/public/fido_types.h"
#include "device/fido/public/public_key_credential_user_entity.h"

namespace content {
class RenderFrameHost;
}  // namespace content

namespace gfx {
struct VectorIcon;
}

struct AccountInfo;
class AuthenticatorRequestDialogViewController;
class Profile;

enum class EnclaveEnabledStatus {
  kDisabled,
  kEnabled,
  kEnabledAndReauthNeeded,
};

using UIPresentation =
    content::AuthenticatorRequestClientDelegate::UIPresentation;
//                ┌───────┐
//                │ View  │
//                └───────┘ Events are
//   Views call         ▲  broadcast to
//    methods,     │    │  Views, which
//  which trigger  │    │    also read
//   broadcasts    │    │   Model fields
//                 ▼
//                ┌───────┐
//                │ Model │
//                └───────┘
//                      ▲   Controller sets
//   Events are    │    │   fields and calls
// also broadcast  │    │      methods to
//     to the      │    │   broadcast events
//   Controller    ▼            to Views
//              ┌──────────┐
//              │Controller│
//              └──────────┘
//                      ▲
//                      │   Calls from other
//                      │      components
//                      │

// This lists the events on the model. Each becomes:
//   1) A virtual method on AuthenticatorRequestDialogModel::Observer.
//   2) A method on the Model that broadcasts the event to all observers.
#define AUTHENTICATOR_EVENTS                                                  \
  /* Cancels the flow as a result of the user clicking `Cancel` on the */     \
  /* UI. Valid action at all steps. */                                        \
  AUTHENTICATOR_REQUEST_EVENT_0(CancelAuthenticatorRequest)                   \
  /* Continues with the BLE/caBLE flow now that the Bluetooth adapter is */   \
  /* powered. Valid action when at step: kBlePowerOnManual, */                \
  /* kBlePowerOnAutomatic. */                                                 \
  AUTHENTICATOR_REQUEST_EVENT_0(ContinueWithFlowAfterBleAdapterPowered)       \
  /* Called when the enclave authenticator is available for a request or */   \
  /* the enclave authenticator needs a reauth before it is available for a */ \
  /* request. */                                                              \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMEnclaveEnabledStatusChanged,             \
                                EnclaveEnabledStatus)                         \
  AUTHENTICATOR_REQUEST_EVENT_0(OnBioEnrollmentDone)                          \
  /* Called when the power state of the Bluetooth adapter has changed. */     \
  AUTHENTICATOR_REQUEST_EVENT_0(OnBluetoothPoweredStateChanged)               \
  /* Called when the UI should update the state of the buttons. */            \
  AUTHENTICATOR_REQUEST_EVENT_0(OnButtonsStateChanged)                        \
  /* Called when the user cancelled WebAuthn request by clicking the */       \
  /* "cancel" button or the back arrow in the UI dialog. */                   \
  AUTHENTICATOR_REQUEST_EVENT_0(OnCancelRequest)                              \
  /* Called when the user picks Google Password Manager from the */           \
  /* mechanism selection sheet. */                                            \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMCreationSelected)                        \
  /* Called when the user accepts the create passkey sheet. */                \
  /* (But not the GPM one.) */                                                \
  AUTHENTICATOR_REQUEST_EVENT_0(OnChromeProfileCreatePasskeyAccepted)         \
  /* Called when the user accepts passkey creation dialog. */                 \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMCreationConfirmed)                       \
  /* Called when the user clicks "Forgot PIN" during UV. */                   \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMForgotPinPressed)                        \
  /* OnOffTheRecordInterstitialAccepted is called when the user accepts */    \
  /* the interstitial that warns that platform/caBLE authenticators may */    \
  /* record information even in incognito mode. */                            \
  AUTHENTICATOR_REQUEST_EVENT_0(OnOffTheRecordInterstitialAccepted)           \
  /* Sent by GPMEnclaveController when it's ready for the UI to be */         \
  /* displayed. */                                                            \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMReadyForUI)                              \
  /* Called when a user closes the MagicArch window. */                       \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMRecoverSecurityDomainClosed)             \
  /* To be called when the Web Authentication request is complete. */         \
  AUTHENTICATOR_REQUEST_EVENT_0(OnRequestComplete)                            \
  /* OnResidentCredentialConfirmed is called when a user accepts a dialog */  \
  /* confirming that they're happy to create a resident credential. */        \
  AUTHENTICATOR_REQUEST_EVENT_0(OnResidentCredentialConfirmed)                \
  /* Called when the model corresponding to the current sheet of the UX */    \
  /* flow was updated, so UI should update. */                                \
  AUTHENTICATOR_REQUEST_EVENT_0(OnSheetModelChanged)                          \
  AUTHENTICATOR_REQUEST_EVENT_0(OnStartOver)                                  \
  /* Called when the UX flow has navigated to a different step, so the UI */  \
  /* should update. */                                                        \
  AUTHENTICATOR_REQUEST_EVENT_0(OnStepTransition)                             \
  /* Called when the user accepts enrolling a device to use passkeys. */      \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMTrustThisComputer)                       \
  AUTHENTICATOR_REQUEST_EVENT_0(OnUserConfirmedPriorityMechanism)             \
  /* Open the system dialog to grant BLE permission to Chrome. Valid */       \
  /* action when at step: kBlePermissionMac. */                               \
  AUTHENTICATOR_REQUEST_EVENT_0(OpenBlePreferences)                           \
  /* Open the Google Password Manager settings page. */                       \
  AUTHENTICATOR_REQUEST_EVENT_0(OpenGpmSettings)                              \
  /* Turns on the BLE adapter automatically. Valid action when at step: */    \
  /* kBlePowerOnAutomatic. */                                                 \
  AUTHENTICATOR_REQUEST_EVENT_0(PowerOnBleAdapter)                            \
  /* Called when loading the enclave times out. */                            \
  AUTHENTICATOR_REQUEST_EVENT_0(OnGPMLoadingEnclaveTimeout)                   \
  /* Restarts the UX flow. */                                                 \
  AUTHENTICATOR_REQUEST_EVENT_0(StartOver)                                    \
  /* Like `OnAccountPreselected()`, but this takes an index into `creds()` */ \
  /* instead of a credential ID. */                                           \
  AUTHENTICATOR_REQUEST_EVENT_1(OnAccountPreselectedIndex, size_t)            \
  /* OnAccountSelected is called when one of the accounts from  */            \
  /* |SelectAccount| has been picked. The argument is the index of the */     \
  /* selected account in |creds()|. */                                        \
  AUTHENTICATOR_REQUEST_EVENT_1(OnAccountSelected, size_t)                    \
  /* Called when the user selects a GPM passkey. */                           \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPasskeySelected, std::vector<uint8_t>)   \
  /* Called when the user enters the GPM pin in the UI (during initial */     \
  /* setup or authentication). */                                             \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPinEntered, const std::u16string&)       \
  /* Called when the user chooses an option of creating a GPM pin. The */     \
  /* argument is true if the user chooses an arbitrary PIN, and false if */   \
  /* the user chose to use a 6-digit PIN. */                                  \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPinOptionChanged, bool)                  \
  /* OnHavePIN is called when the user enters a PIN in the UI. */             \
  AUTHENTICATOR_REQUEST_EVENT_1(OnHavePIN, std::u16string)                    \
  /* Called when a local Touch ID prompt finishes. The first parameter is */  \
  /* true for success, false for failure. */                                  \
  /* On success, the emitter must set the model's `local_auth_token` to an */ \
  /* authenticated one. In MacOS this is a ScopedLAContext. */                \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMTouchIDComplete, bool)                   \
  /* Called when GAIA reauth has completed. The argument is the reauth */     \
  /* proof token. */                                                          \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMReauthComplete, std::string)             \
  /* Called just before the model is destructed. */                           \
  AUTHENTICATOR_REQUEST_EVENT_1(OnModelDestroyed,                             \
                                AuthenticatorRequestDialogModel*)             \
  /* Called when the GPM passkeys are reset successfully or not. */           \
  AUTHENTICATOR_REQUEST_EVENT_1(OnGPMPasskeysReset, bool)                     \
  /* Called when a password mechanism is selected */                          \
  AUTHENTICATOR_REQUEST_EVENT_1(OnPasswordCredentialSelected,                 \
                                PasswordCredentialPair)

// AuthenticatorRequestDialogModel holds the UI state for a WebAuthn request.
// This class is refcounted so that its ownership can be shared between the
// dialog view and the request delegate, which both depend on its state, and
// don't have coupled lifetimes.
struct AuthenticatorRequestDialogModel
    : public base::RefCounted<AuthenticatorRequestDialogModel> {
  // Each Step defines a unique UI state. Setting a Step causes the matching
  // dialog or window to appear.
  //
  // Append new values to the end and update `kMaxValue`.
  enum class Step {
    // The UX flow has not started yet, the dialog should still be hidden.
    kNotStarted,
    // Passkey autofill (i.e. WebAuthn get() with conditional mediation). No
    // dialog is shown, instead credentials are offered to the user on the
    // password autofill prompt.
    kPasskeyAutofill,
    // During passkey upgrade (i.e. WebAuthn create() with conditional
    // mediation), the WebAuthn tab-modal dialog is not used. A separate dialog
    // controller implements its own UI.
    kPasskeyUpgrade,
    kMechanismSelection,
    // The request errored out before completing. Error will only be sent
    // after user interaction.
    kErrorNoAvailableTransports,
    kErrorNoPasskeys,
    kErrorGpmDisabled,
    kErrorInternalUnrecognized,
    kErrorWindowsHelloNotEnabled,
    // The request is already complete, but the error dialog should wait
    // until user acknowledgement.
    kTimedOut,
    kKeyNotRegistered,
    kKeyAlreadyRegistered,
    kMissingCapability,
    kStorageFull,
    // The request is completed, and the dialog should be closed.
    kClosed,
    // Universal Serial Bus (USB).
    kUsbInsertAndActivate,
    // Bluetooth Low Energy (BLE).
    kBlePowerOnAutomatic,
    kBlePowerOnManual,
    kBlePermissionMac,
    // Let the user confirm that they want to create a credential in an
    // off-the-record browsing context. Used for platform and caBLE
    // credential, where we feel that it's perhaps not obvious that something
    // will be recorded.
    kOffTheRecordInterstitial,
    // Phone as a security key.
    kCableV2QRCode,
    kCableV2Connecting,
    kCableV2Connected,
    kCableV2Error,
    // Authenticator Client PIN.
    kClientPinChange,
    kClientPinEntry,
    kClientPinSetup,
    kClientPinTapAgain,
    kClientPinErrorSoftBlock,
    kClientPinErrorHardBlock,
    kClientPinErrorAuthenticatorRemoved,
    // Authenticator Internal User Verification
    kInlineBioEnrollment,
    kRetryInternalUserVerification,
    // Confirm user consent to create a resident credential. Used prior to
    // triggering Windows-native APIs when Windows itself won't show any
    // notice about resident credentials.
    kResidentCredentialConfirmation,
    // Account selection. This occurs prior to performing user verification
    // for platform authenticators ("pre-select"), or afterwards for USB
    // security keys. In each mode, there are different sheets for confirming
    // a single available credential and choosing one from a list of multiple
    // options.
    kSelectAccount,
    kPreSelectAccount,
    // kSelectPriorityMechanism lets the user confirm a single "priority"
    // mechanism.
    kSelectPriorityMechanism,
    // GPM Pin (6-digit).
    kGPMChangePin,
    kGPMCreatePin,
    kGPMEnterPin,
    // GPM Pin (alphanumeric).
    kGPMChangeArbitraryPin,
    kGPMCreateArbitraryPin,
    kGPMEnterArbitraryPin,
    // User verification prompt for GPM.
    kGPMTouchID,
    // GPM passkey creation.
    kGPMCreatePasskey,
    kChromeProfileCreatePasskey,
    kGPMError,
    kGPMConnecting,
    // Device bootstrap to use GPM passkeys.
    kGPMRecoverSecurityDomain,
    kGPMTrustThisComputerAssertion,
    kGPMTrustThisComputerCreation,
    // Changing GPM PIN.
    kGPMReauthForPinReset,
    kGPMLockedPin,
    // OS authentication after selecting a password.
    kPasswordOsAuth,
    // The request is being dispatched to a platform authenticator.
    kPlatformAuthenticator,
    kMaxValue = kPlatformAuthenticator,
  };

  // Views and controllers implement this interface to receive events, which
  // flow both from controllers to the views, and from the views to the
  // controllers.
  class Observer : public base::CheckedObserver {
   public:
#define AUTHENTICATOR_REQUEST_EVENT_0(name) virtual void name();
#define AUTHENTICATOR_REQUEST_EVENT_1(name, arg1type) \
  virtual void name(arg1type arg1);
    AUTHENTICATOR_EVENTS
#undef AUTHENTICATOR_REQUEST_EVENT_0
#undef AUTHENTICATOR_REQUEST_EVENT_1
  };

  // A Mechanism is a user-visible method of authenticating. It might be a
  // transport (such as USB), a platform authenticator, or even a delegation to
  // a platform API. Selecting a mechanism starts the flow for the user to
  // authenticate with it (e.g. by showing a QR code or dispatching to a
  // platform authenticator).
  //
  // On get assertion requests, mechanisms can also represent credentials for
  // authenticators that support silent discovery. In this case, the |type| is
  // |Credential| and it is annotated with the source of the credential (icloud,
  // etc). Selecting such a mechanism dispatches a request narrowed down to the
  // specific credential to an authenticator that can fulfill it.
  struct Mechanism {
    // These types describe the type of Mechanism.
    struct CredentialInfo {
      CredentialInfo(device::AuthenticatorType source_in,
                     std::vector<uint8_t> user_id_in,
                     std::optional<base::Time> last_used_time_in);
      CredentialInfo(const CredentialInfo&);
      ~CredentialInfo();
      bool operator==(const CredentialInfo&) const;

      const device::AuthenticatorType source;
      const std::vector<uint8_t> user_id;
      const std::optional<base::Time> last_used_time;
    };
    using Credential = base::StrongAlias<class CredentialTag, CredentialInfo>;

    struct PasswordInfo {
      explicit PasswordInfo(
          std::optional<base::Time> last_used_time_in,
          std::optional<std::u16string> origin_in = std::nullopt);

      PasswordInfo(const PasswordInfo&);
      ~PasswordInfo();
      bool operator==(const PasswordInfo& other) const;

      const std::optional<base::Time> last_used_time;
      const std::optional<std::u16string> origin;
    };
    using Password = base::StrongAlias<class PasswordTag, PasswordInfo>;
    using Transport =
        base::StrongAlias<class TransportTag, AuthenticatorTransport>;
    using WindowsAPI = base::StrongAlias<class WindowsAPITag, std::monostate>;
    using ICloudKeychain =
        base::StrongAlias<class iCloudKeychainTag, std::monostate>;
    using Hybrid = base::StrongAlias<class HybridTag, std::monostate>;
    using Enclave = base::StrongAlias<class EnclaveTag, std::monostate>;
    using SignInAgain = base::StrongAlias<class SignInAgainTag, std::monostate>;
    using Type = std::variant<Credential,
                              Password,
                              Transport,
                              WindowsAPI,
                              Hybrid,
                              ICloudKeychain,
                              Enclave,
                              SignInAgain>;

    Mechanism(Type type,
              std::u16string name,
              const gfx::VectorIcon& icon,
              base::RepeatingClosure callback,
              std::u16string display_name = std::u16string());
    ~Mechanism();
    Mechanism(Mechanism&&);
    Mechanism(const Mechanism&) = delete;
    Mechanism& operator=(const Mechanism&) = delete;

    const Type type;
    const std::u16string name;
    const std::u16string display_name;
    std::u16string description;
    const raw_ref<const gfx::VectorIcon> icon;
    const base::RepeatingClosure callback;
  };

  // Returns a user-friendly description for a |type|.
  static std::u16string GetMechanismDescription(
      const device::DiscoverableCredentialMetadata& cred,
      UIPresentation ui_presentation = UIPresentation::kModal);

  explicit AuthenticatorRequestDialogModel(
      content::RenderFrameHost* render_frame_host);
  AuthenticatorRequestDialogModel(AuthenticatorRequestDialogModel&) = delete;
  AuthenticatorRequestDialogModel(const AuthenticatorRequestDialogModel&&) =
      delete;
  AuthenticatorRequestDialogModel& operator=(
      AuthenticatorRequestDialogModel&&) = delete;
  AuthenticatorRequestDialogModel& operator=(
      const AuthenticatorRequestDialogModel&) = delete;

  // This causes the events to become methods on the Model. Views and
  // Controllers call these methods to broadcast events to all observers.
#define AUTHENTICATOR_REQUEST_EVENT_0(name) void name();
#define AUTHENTICATOR_REQUEST_EVENT_1(name, arg1type) void name(arg1type arg1);
  AUTHENTICATOR_EVENTS
#undef AUTHENTICATOR_REQUEST_EVENT_0
#undef AUTHENTICATOR_REQUEST_EVENT_1

  // Below methods are used for base::ScopedObserver:
  void AddObserver(AuthenticatorRequestDialogModel::Observer* observer);
  void RemoveObserver(AuthenticatorRequestDialogModel::Observer* observer);

  // Views and controllers add themselves as observers here to receive events.
  // TODO(crbug.com/484371187): Investigate if reentrancy can be removed.
  base::ObserverList<
      Observer,
      /*check_empty=*/false,
      base::ObserverListReentrancyPolicy::kAllowReentrancyUntriaged>
      observers;

  // The primary state of the model is the current `Step`. It's important that
  // this always be changed via `SetStep` so the field isn't exposed directly.
  Step step() const { return step_; }
  void SetStep(Step step);

  void DisableUiOrShowLoadingDialog();

  void set_ui_presentation(UIPresentation presentation) {
    ui_presentation = presentation;
  }

  // generation is incremented each time the request is restarted so that events
  // from different request generations can be distinguished.
  int generation = 0;

  // The following methods and fields are read by views and both read and
  // written by controllers. Views use these values to determine what
  // information to show.

  // Returns whether the visible dialog should be closed. This means
  // that either the request has finished, or that the current step
  // has no UI, or a different style of UI.
  bool should_dialog_be_closed() const;

  device::FidoRequestType request_type = device::FidoRequestType::kGetAssertion;
  device::ResidentKeyRequirement resident_key_requirement =
      device::ResidentKeyRequirement::kDiscouraged;
  // This value is present if, and only if, `request_type` is `kMakeCredential`.
  std::optional<device::AttestationConveyancePreference>
      attestation_conveyance_preference;
  std::string relying_party_id;
  // mechanisms contains the entries that appear in the "transport" selection
  // sheet and the drop-down menu.
  std::vector<Mechanism> mechanisms;
  std::optional<size_t> priority_mechanism_index;
  // For MakeCredential requests, the PublicKeyCredentialUserEntity associated
  // with the request.
  device::PublicKeyCredentialUserEntity user_entity;
  // creds contains possible credentials to select between before or after an
  // authenticator has responded to a request.
  std::vector<device::DiscoverableCredentialMetadata> creds;
  // preselected_cred contains a credential preselected by the user.
  std::optional<device::DiscoverableCredentialMetadata> preselected_cred;
  // Whether the platform can check biometrics and has biometrics configured.
  std::optional<bool> platform_has_biometrics;
  UIPresentation ui_presentation = UIPresentation::kModal;
  // Stores the pre-calculated GPM UV method for `getAssertion`.
  // This is set by `ChromeAuthenticatorRequestDelegate` before `StartFlow` is
  // called on the controller. This is used for immediate mode requests.
  std::optional<EnclaveUserVerificationMethod> gpm_uv_method;

  // offer_try_again_in_ui indicates whether a button to retry the request
  // should be included on the dialog sheet shown when encountering certain
  // errors.
  bool offer_try_again_in_ui = true;
  bool ble_adapter_is_powered = false;
  // show_security_key_on_qr_sheet is true if the security key option should be
  // offered on the QR sheet.
  bool show_security_key_on_qr_sheet = false;
  bool is_off_the_record = false;

  // Tracks whether the model is in the GPM onboarding state.
  // This value is set/reset only in GPMEnclaveController::OnGPMCreationSelected
  // and read only to record metrics (WebAuthentication.OnboardingEvents) during
  // the onboarding flow.
  bool in_onboarding_flow = false;

  std::optional<int> max_bio_samples;
  std::optional<int> bio_samples_remaining;
  uint32_t min_pin_length = device::kMinPinLength;
  std::optional<int> pin_attempts;
  std::optional<int> uv_attempts;
  device::pin::PINEntryError pin_error = device::pin::PINEntryError::kNoError;

  std::optional<std::string> cable_qr_string;

  // Number of remaining GPM pin entry attempts before getting locked out or
  // `std::nullopt` if there was no failed attempts during that request.
  std::optional<int> gpm_pin_remaining_attempts_;

  // True if GPM passkey creation is available has been disabled by enterprise
  // policy or Chrome settings.
  bool gpm_create_available_but_disabled_by_policy = false;

  // Whether the UI is currently in a disabled state, which is required for some
  // transitions (e.g. when waiting for the response from the enclave). When
  // true, the sheets will by default display an activity indicator at the top
  // and disable all the usual buttons (e.g. accept or "other mechanisms")
  // except for the cancel button.
  bool ui_disabled_ = false;

  // local_auth_token contains an authentication token after a successful local
  // authentication. In MacOS this is a wrapped LAContext and it is after a
  // successful Touch ID prompt.
  std::optional<webauthn::LocalAuthenticationToken> local_auth_token;

  // Returns the AccountInfo for the profile associated with the request.
  std::optional<AccountInfo> GetGpmAccountInfo();

  // Returns the account email for the profile associated with the request,
  // which may be empty.
  std::string GetGpmAccountEmail();

 private:
  friend class base::RefCounted<AuthenticatorRequestDialogModel>;
  ~AuthenticatorRequestDialogModel();

  // The Profile for the request, which may be an off-the-record profile.
  //
  // Returns nullptr if the RenderFrameHost for the request doesn't exist
  // anymore.
  Profile* GetProfile();

  Step step_ = Step::kNotStarted;
  const std::optional<content::GlobalRenderFrameHostId> frame_host_id;
  std::unique_ptr<AuthenticatorRequestDialogViewController> view_controller_;
};

std::ostream& operator<<(std::ostream& os,
                         const AuthenticatorRequestDialogModel::Step& step);

#endif  // CHROME_BROWSER_WEBAUTHN_AUTHENTICATOR_REQUEST_DIALOG_MODEL_H_
