blob: 7e0b7559f99b99d9c9618fdb3cb0e69edf5ffa3f [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_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_H_
#define CHROME_BROWSER_WEBAUTHN_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_H_
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/span.h"
#include "base/functional/callback.h"
#include "base/gtest_prod_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include "chrome/browser/webauthn/password_credential_controller.h"
#include "components/trusted_vault/trusted_vault_connection.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/global_routing_id.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/cable/cable_discovery_data.h"
#include "device/fido/cable/v2_constants.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/fido_request_handler_base.h"
#include "device/fido/fido_transport_protocol.h"
#include "device/fido/fido_types.h"
#include "third_party/blink/public/mojom/credentialmanagement/credential_type_flags.mojom.h"
class AuthenticatorRequestDialogController;
class GPMEnclaveController;
class PrefService;
namespace base {
class SequencedTaskRunner;
class TickClock;
} // namespace base
namespace content {
class BrowserContext;
class RenderFrameHost;
} // namespace content
namespace device {
class FidoAuthenticator;
class FidoDiscoveryFactory;
class PublicKeyCredentialDescriptor;
class PublicKeyCredentialUserEntity;
enum class FidoRequestType : uint8_t;
} // namespace device
namespace user_prefs {
class PrefRegistrySyncable;
}
class ChromeAuthenticatorRequestDelegate
: public content::AuthenticatorRequestClientDelegate,
public AuthenticatorRequestDialogModel::Observer {
public:
// TestObserver is an interface that observes certain events related to this
// class for testing purposes. Only a single instance of this interface can
// be installed at a given time.
class TestObserver {
public:
virtual void Created(ChromeAuthenticatorRequestDelegate* delegate) {}
virtual void OnDestroy(ChromeAuthenticatorRequestDelegate* delegate) {}
virtual void OnTransportAvailabilityEnumerated(
ChromeAuthenticatorRequestDelegate* delegate,
device::FidoRequestHandlerBase::TransportAvailabilityInfo* tai) {}
// Called when TAI enumeration has finished but it might have to wait for
// enclave availability.
virtual void OnPreTransportAvailabilityEnumerated(
ChromeAuthenticatorRequestDelegate* delegate) {}
// Called when the UI dialog is shown.
virtual void UIShown(ChromeAuthenticatorRequestDelegate* delegate) {}
virtual void CableV2ExtensionSeen(
base::span<const uint8_t> server_link_data) {}
virtual void ConfiguringCable(device::FidoRequestType request_type) {}
virtual void AccountSelectorShown(
const std::vector<device::AuthenticatorGetAssertionResponse>&
responses) {}
virtual void HintsSet(
const AuthenticatorRequestClientDelegate::Hints& hints) {}
// Called right before `start_over_callback_` is invoked.
virtual void PreStartOver() {}
};
static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry);
// The |render_frame_host| must outlive this instance.
explicit ChromeAuthenticatorRequestDelegate(
content::RenderFrameHost* render_frame_host);
ChromeAuthenticatorRequestDelegate(
const ChromeAuthenticatorRequestDelegate&) = delete;
ChromeAuthenticatorRequestDelegate& operator=(
const ChromeAuthenticatorRequestDelegate&) = delete;
~ChromeAuthenticatorRequestDelegate() override;
// SetGlobalObserverForTesting sets the single |TestObserver| that is active
// at a given time. Call be called with |nullptr| to unregister a
// |TestObserver|. It is a fatal error to try and register a |TestObserver|
// while one is still installed.
static void SetGlobalObserverForTesting(TestObserver*);
base::WeakPtr<ChromeAuthenticatorRequestDelegate> AsWeakPtr();
AuthenticatorRequestDialogModel* dialog_model() const {
return dialog_model_.get();
}
AuthenticatorRequestDialogController* dialog_controller() const {
return dialog_controller_.get();
}
GPMEnclaveController* enclave_controller_for_testing() const;
// content::AuthenticatorRequestClientDelegate:
void SetRelyingPartyId(const std::string& rp_id) override;
void SetUIPresentation(UIPresentation ui_presentation) override;
bool DoesBlockRequestOnFailure(InterestingFailureReason reason) override;
void RegisterActionCallbacks(
base::OnceClosure cancel_callback,
base::OnceClosure immediate_not_found_callback,
base::RepeatingClosure start_over_callback,
AccountPreselectedCallback account_preselected_callback,
PasswordSelectedCallback password_selected_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback,
base::OnceClosure cancel_ui_timeout_callback,
base::RepeatingClosure bluetooth_adapter_power_on_callback,
base::RepeatingCallback<
void(device::FidoRequestHandlerBase::BlePermissionCallback)>
bluetooth_query_status_callback) override;
void OnTransactionSuccessful(RequestSource request_source,
device::FidoRequestType,
device::AuthenticatorType) override;
void ConfigureDiscoveries(
const url::Origin& origin,
const std::string& rp_id,
RequestSource request_source,
device::FidoRequestType request_type,
std::optional<device::ResidentKeyRequirement> resident_key_requirement,
device::UserVerificationRequirement user_verification_requirement,
std::optional<std::string_view> user_name,
base::span<const device::CableDiscoveryData> pairings_from_extension,
bool is_enclave_authenticator_available,
device::FidoDiscoveryFactory* discovery_factory) override;
void SetHints(
const AuthenticatorRequestClientDelegate::Hints& hints) override;
void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) override;
void SetCredentialTypes(int credential_type_flags) override;
void SetCredentialIdFilter(std::vector<device::PublicKeyCredentialDescriptor>
credential_list) override;
void SetUserEntityForMakeCredentialRequest(
const device::PublicKeyCredentialUserEntity& user_entity) override;
void ProvideChallengeUrl(
const GURL& url,
base::OnceCallback<void(std::optional<base::span<const uint8_t>>)>
callback) override;
// device::FidoRequestHandlerBase::Observer:
void OnTransportAvailabilityEnumerated(
device::FidoRequestHandlerBase::TransportAvailabilityInfo data) override;
bool EmbedderControlsAuthenticatorDispatch(
const device::FidoAuthenticator& authenticator) override;
void FidoAuthenticatorAdded(
const device::FidoAuthenticator& authenticator) override;
void FidoAuthenticatorRemoved(std::string_view authenticator_id) override;
void BluetoothAdapterStatusChanged(
device::FidoRequestHandlerBase::BleStatus ble_status) override;
bool SupportsPIN() const override;
void CollectPIN(
CollectPINOptions options,
base::OnceCallback<void(std::u16string)> provide_pin_cb) override;
void StartBioEnrollment(base::OnceClosure next_callback) override;
void OnSampleCollected(int bio_samples_remaining) override;
void FinishCollectToken() override;
void OnRetryUserVerification(int attempts) override;
// AuthenticatorRequestDialogModel::Observer:
void OnStartOver() override;
void OnModelDestroyed(AuthenticatorRequestDialogModel* model) override;
void OnCancelRequest() override;
void SetPasswordControllerForTesting(
std::unique_ptr<PasswordCredentialController> controller);
// GetRenderFrameHost returns a pointer to the RenderFrameHost that was given
// to the constructor.
content::RenderFrameHost* GetRenderFrameHost() const;
private:
FRIEND_TEST_ALL_PREFIXES(ChromeAuthenticatorRequestDelegatePrivateTest,
DaysSinceDate);
FRIEND_TEST_ALL_PREFIXES(ChromeAuthenticatorRequestDelegatePrivateTest,
GetICloudKeychainPref);
FRIEND_TEST_ALL_PREFIXES(ChromeAuthenticatorRequestDelegatePrivateTest,
ShouldCreateInICloudKeychain);
class EnclaveManagerObserver;
content::BrowserContext* GetBrowserContext() const;
Profile* profile() const;
bool webauthn_ui_enabled() const;
// Returns `true` iff the handled request is an immediate `get()` request and
// no immediately available credentials found. This will trigger the
// `immediate_not_found_callback_` to notify the renderer.
bool MaybeHandleImmediateMediation(
const device::FidoRequestHandlerBase::TransportAvailabilityInfo& data,
const PasswordCredentialController::PasswordCredentials& passwords);
// Barriers showing the UI while waiting for
// - password credentials,
// - WebAuthn credentials,
// - enclave readiness.
void TryToShowUI();
void MaybeShowUI(
device::FidoRequestHandlerBase::TransportAvailabilityInfo tai,
PasswordCredentialController::PasswordCredentials passwords);
void FinishMaybeShowUI(
PasswordCredentialController::PasswordCredentials passwords,
device::FidoRequestHandlerBase::TransportAvailabilityInfo tai);
std::optional<device::FidoTransportProtocol> GetLastTransportUsed() const;
void OnReadyForUI() override;
// ShouldPermitCableExtension returns true if the given |origin| may set a
// caBLE extension. This extension contains website-chosen BLE pairing
// information that will be broadcast by the device.
bool ShouldPermitCableExtension(const url::Origin& origin);
void OnCableEvent(device::cablev2::Event event);
// Adds GPM passkeys matching |rp_id| to |tai|.
void GetPhoneContactableGpmPasskeysForRpId(
device::FidoRequestHandlerBase::TransportAvailabilityInfo tai,
base::OnceCallback<void(
device::FidoRequestHandlerBase::TransportAvailabilityInfo)> callback);
void DoGetPhoneContactableGpmPasskeysForRpId(
device::FidoRequestHandlerBase::TransportAvailabilityInfo tai,
base::OnceCallback<void(
device::FidoRequestHandlerBase::TransportAvailabilityInfo)> callback);
// Update `tai` to remove credentials that aren't applicable to this request.
void FilterRecognizedCredentials(
device::FidoRequestHandlerBase::TransportAvailabilityInfo* tai);
#if BUILDFLAG(IS_MAC)
// DaysSinceDate returns the number of days between `formatted_date` (in ISO
// 8601 format) and `now`. It returns `nullopt` if `formatted_date` cannot be
// parsed or if it's in `now`s future.
//
// It does not parse `formatted_date` strictly and is intended for trusted
// inputs.
static std::optional<int> DaysSinceDate(const std::string& formatted_date,
base::Time now);
// GetICloudKeychainPref returns the value of the iCloud Keychain preference
// as a tristate. If no value for the preference has been set then it
// returns `std::nullopt`.
static std::optional<bool> GetICloudKeychainPref(const PrefService* prefs);
// IsActiveProfileAuthenticatorUser returns true if the profile authenticator
// has been used in the past 31 days.
static bool IsActiveProfileAuthenticatorUser(const PrefService* prefs);
// ShouldCreateInICloudKeychain returns true if attachment=platform creation
// requests should default to iCloud Keychain.
static bool ShouldCreateInICloudKeychain(
RequestSource request_source,
bool is_active_profile_authenticator_user,
bool has_icloud_drive_enabled,
bool request_is_for_google_com,
std::optional<bool> preference);
// Configure the NSWindow* for the current RenderFrameHost. This is used by
// some macOS system APIs to center dialogs on the pertinent Chrome window.
void ConfigureNSWindow(device::FidoDiscoveryFactory* discovery_factory);
// ConfigureICloudKeychain is called by `ConfigureDiscoveries` to configure
// the `AuthenticatorRequestDialogController` with iCloud Keychain-related
// values.
void ConfigureICloudKeychain(RequestSource request_source,
const std::string& rp_id);
#endif
void OnPasswordCredentialsReceived(
PasswordCredentialController::PasswordCredentials credentials);
void UpdateModelForTransportAvailability(
const device::FidoRequestHandlerBase::TransportAvailabilityInfo& tai);
const content::GlobalRenderFrameHostId render_frame_host_id_;
const scoped_refptr<AuthenticatorRequestDialogModel> dialog_model_;
const std::unique_ptr<AuthenticatorRequestDialogController>
dialog_controller_;
base::OnceClosure cancel_callback_;
base::OnceClosure immediate_not_found_callback_;
base::RepeatingClosure start_over_callback_;
AccountPreselectedCallback account_preselected_callback_;
PasswordSelectedCallback password_selected_callback_;
device::FidoRequestHandlerBase::RequestCallback request_callback_;
base::OnceClosure cancel_ui_timeout_callback_;
// The number of credential types that have been requested to be displayed.
int credential_types_ =
static_cast<int>(blink::mojom::CredentialTypeFlags::kNone);
// A list of credentials used to filter passkeys by ID. When non-empty,
// non-matching passkeys will not be displayed during conditional mediation
// requests. When empty, no filter is applied and all passkeys are displayed.
std::vector<device::PublicKeyCredentialDescriptor> credential_filter_;
// cable_device_ready_ is true if a caBLE handshake has completed. At this
// point we assume that any errors were communicated on the caBLE device and
// don't show errors on the desktop too.
bool cable_device_ready_ = false;
// can_use_synced_phone_passkeys_ is true if there is a phone pairing
// available that can service requests for synced GPM passkeys.
bool can_use_synced_phone_passkeys_ = false;
std::unique_ptr<GPMEnclaveController> enclave_controller_;
std::unique_ptr<PasswordCredentialController> password_controller_;
// Stores the TransportAvailabilityInfo while we're waiting for the enclave
// state to load from the disk.
std::unique_ptr<device::FidoRequestHandlerBase::TransportAvailabilityInfo>
pending_transport_availability_info_;
// Stores the password credentials while waiting for enclave state, transport
// availability info to be ready.
std::unique_ptr<PasswordCredentialController::PasswordCredentials>
pending_password_credentials_;
base::WeakPtrFactory<ChromeAuthenticatorRequestDelegate> weak_ptr_factory_{
this};
};
#endif // CHROME_BROWSER_WEBAUTHN_CHROME_AUTHENTICATOR_REQUEST_DELEGATE_H_