blob: 3a1948035064d4391f0583b60d777d7e8fc1a5ee [file] [log] [blame]
// 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 "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_constants.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_types.h"
#include "device/fido/pin.h"
#include "device/fido/public_key_credential_user_entity.h"
namespace content {
class RenderFrameHost;
} // namespace content
namespace gfx {
struct VectorIcon;
}
struct AccountInfo;
class AuthenticatorRequestDialogViewController;
class Profile;
using PasswordCredentialPair = std::pair<std::u16string, std::u16string>;
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(EnclaveEnabledStatusChanged, \
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(OnGPMSelected) \
/* Called when the user accepts the create passkey sheet. */ \
/* (But not the GPM one.) */ \
AUTHENTICATOR_REQUEST_EVENT_0(OnCreatePasskeyAccepted) \
/* Called when the user accepts passkey creation dialog. */ \
AUTHENTICATOR_REQUEST_EVENT_0(OnGPMCreatePasskey) \
/* Called when the user accepts the warning dialog for creating a GPM */ \
/* passkey in incognito mode.*/ \
AUTHENTICATOR_REQUEST_EVENT_0(OnGPMConfirmOffTheRecordCreate) \
/* Called when the user clicks "Forgot PIN" during UV. */ \
AUTHENTICATOR_REQUEST_EVENT_0(OnForgotGPMPinPressed) \
/* 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(OnReadyForUI) \
/* Called when a user closes the MagicArch window. */ \
AUTHENTICATOR_REQUEST_EVENT_0(OnRecoverSecurityDomainClosed) \
/* 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(OnTrustThisComputer) \
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) \
/* 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(OnLoadingEnclaveTimeout) \
/* 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(OnTouchIDComplete, bool) \
/* Called when GAIA reauth has completed. The argument is the reauth */ \
/* proof token. */ \
AUTHENTICATOR_REQUEST_EVENT_1(OnReauthComplete, 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,
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.
kCableActivate,
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,
kGPMConfirmOffTheRecordCreate,
kCreatePasskey,
kGPMError,
kGPMConnecting,
// Device bootstrap to use GPM passkeys.
kRecoverSecurityDomain,
kTrustThisComputerAssertion,
kTrustThisComputerCreation,
// Changing GPM PIN.
kGPMReauthForPinReset,
kGPMLockedPin,
// ChallengeUrl failure.
kErrorFetchingChallenge,
// OS authentication after selecting a password.
kPasswordOsAuth,
kMaxValue = kPasswordOsAuth,
};
// 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);
PasswordInfo(const PasswordInfo&);
~PasswordInfo();
bool operator==(const PasswordInfo& other) const;
const std::optional<base::Time> last_used_time;
};
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 AddPhone = base::StrongAlias<class AddPhoneTag, 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,
AddPhone,
ICloudKeychain,
Enclave,
SignInAgain>;
Mechanism(Type type,
std::u16string name,
std::u16string short_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;
// TODO(crbug.com/422394117): This is not used anywhere. Remove it.
const std::u16string short_name;
const std::u16string display_name;
std::u16string description;
const raw_ref<const gfx::VectorIcon> icon;
const base::RepeatingClosure callback;
};
// CableUIType enumerates the different types of caBLE UI that we've ended
// up with.
enum class CableUIType {
CABLE_V1,
CABLE_V2_SERVER_LINK,
CABLE_V2_2ND_FACTOR,
};
// 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.
base::ObserverList<Observer> 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::OnGPMSelected 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;
// cable_ui_type contains the type of UI to display for a caBLE transaction.
std::optional<CableUIType> cable_ui_type;
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_;
// 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_