blob: 42ce72bbd574c4598a1505e7a507ef306cb2dc03 [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.
#include "chrome/browser/webauthn/authenticator_request_dialog_model.h"
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/containers/fixed_flat_map.h"
#include "base/functional/callback.h"
#include "base/observer_list.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/ui/webauthn/authenticator_request_dialog_view_controller.h"
#include "chrome/browser/ui/webauthn/authenticator_request_window.h"
#include "chrome/browser/webauthn/authenticator_transport.h"
#include "chrome/browser/webauthn/webauthn_pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/device_event_log/device_event_log.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "device/fido/discoverable_credential_metadata.h"
#include "device/fido/fido_types.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/paint_vector_icon.h"
namespace {
// StepUiType enumerates the different types of UI that can be displayed.
enum class StepUIType {
NONE,
// A Chromium captive dialog.
DIALOG,
// A top-level window.
WINDOW,
};
StepUIType step_ui_type(AuthenticatorRequestDialogModel::Step step) {
switch (step) {
case AuthenticatorRequestDialogModel::Step::kClosed:
case AuthenticatorRequestDialogModel::Step::kNotStarted:
case AuthenticatorRequestDialogModel::Step::kPasskeyAutofill:
case AuthenticatorRequestDialogModel::Step::kPasskeyUpgrade:
case AuthenticatorRequestDialogModel::Step::kPasswordOsAuth:
return StepUIType::NONE;
case AuthenticatorRequestDialogModel::Step::kRecoverSecurityDomain:
case AuthenticatorRequestDialogModel::Step::kGPMReauthForPinReset:
return StepUIType::WINDOW;
default:
return StepUIType::DIALOG;
}
}
std::optional<content::GlobalRenderFrameHostId> FrameHostIdFromMaybeNull(
content::RenderFrameHost* render_frame_host) {
if (render_frame_host == nullptr) {
return std::nullopt;
}
return render_frame_host->GetGlobalId();
}
content::WebContents* GetWebContentsFromFrameHostId(
std::optional<content::GlobalRenderFrameHostId> frame_host_id) {
if (!frame_host_id) {
return nullptr;
}
return content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(*frame_host_id));
}
} // namespace
#define AUTHENTICATOR_REQUEST_EVENT_0(name) \
void AuthenticatorRequestDialogModel::Observer::name() {}
#define AUTHENTICATOR_REQUEST_EVENT_1(name, arg1type) \
void AuthenticatorRequestDialogModel::Observer::name(arg1type) {}
AUTHENTICATOR_EVENTS
#undef AUTHENTICATOR_REQUEST_EVENT_0
#undef AUTHENTICATOR_REQUEST_EVENT_1
// static
std::u16string AuthenticatorRequestDialogModel::GetMechanismDescription(
const device::DiscoverableCredentialMetadata& cred,
UIPresentation ui_presentation) {
bool immediate_mode = UIPresentation::kModalImmediate == ui_presentation;
if (cred.provider_name) {
return immediate_mode ? l10n_util::GetStringFUTF16(
IDS_PASSWORD_MANAGER_PASSKEY_FROM_PROVIDER,
base::UTF8ToUTF16(*cred.provider_name))
: base::UTF8ToUTF16(*cred.provider_name);
}
int message;
switch (cred.source) {
case device::AuthenticatorType::kWinNative:
message = immediate_mode ? IDS_PASSWORD_MANAGER_PASSKEY_FROM_WINDOWS_HELLO
: IDS_WEBAUTHN_SOURCE_WINDOWS_HELLO;
break;
case device::AuthenticatorType::kTouchID:
message = immediate_mode
? IDS_PASSWORD_MANAGER_PASSKEY_FROM_CHROME_PROFILE
: IDS_WEBAUTHN_SOURCE_CHROME_PROFILE;
break;
case device::AuthenticatorType::kICloudKeychain:
message = immediate_mode
? IDS_PASSWORD_MANAGER_PASSKEY_FROM_ICLOUD_KEYCHAIN
: IDS_WEBAUTHN_SOURCE_ICLOUD_KEYCHAIN;
break;
case device::AuthenticatorType::kEnclave:
message = immediate_mode
? IDS_PASSWORD_MANAGER_PASSKEY_FROM_GOOGLE_PASSWORD_MANAGER
: IDS_WEBAUTHN_SOURCE_GOOGLE_PASSWORD_MANAGER;
break;
case device::AuthenticatorType::kOther:
// "Other" is USB security keys and the virtual authenticator.
CHECK(!immediate_mode);
message = IDS_WEBAUTHN_SOURCE_USB_SECURITY_KEY;
break;
default:
message = IDS_PASSWORD_MANAGER_USE_GENERIC_DEVICE;
}
return l10n_util::GetStringUTF16(message);
}
AuthenticatorRequestDialogModel::AuthenticatorRequestDialogModel(
content::RenderFrameHost* render_frame_host)
: frame_host_id(FrameHostIdFromMaybeNull(render_frame_host)) {}
AuthenticatorRequestDialogModel::~AuthenticatorRequestDialogModel() {
for (auto& observer : observers) {
observer.OnModelDestroyed(this);
}
}
void AuthenticatorRequestDialogModel::AddObserver(
AuthenticatorRequestDialogModel::Observer* observer) {
observers.AddObserver(observer);
}
void AuthenticatorRequestDialogModel::RemoveObserver(
AuthenticatorRequestDialogModel::Observer* observer) {
observers.RemoveObserver(observer);
}
void AuthenticatorRequestDialogModel::SetStep(Step step) {
FIDO_LOG(EVENT) << "UI step: " << step;
const StepUIType previous_ui_type = step_ui_type(step_);
step_ = step;
ui_disabled_ = false;
const StepUIType ui_type = step_ui_type(step_);
auto* web_contents = GetWebContentsFromFrameHostId(frame_host_id);
if (ui_type != StepUIType::DIALOG) {
view_controller_.reset();
if (ui_type == StepUIType::WINDOW &&
previous_ui_type != StepUIType::WINDOW && web_contents) {
ShowAuthenticatorRequestWindow(web_contents, this);
}
} else if (previous_ui_type != StepUIType::DIALOG && web_contents) {
view_controller_ =
AuthenticatorRequestDialogViewController::Create(web_contents, this);
}
for (auto& observer : observers) {
observer.OnStepTransition();
}
}
void AuthenticatorRequestDialogModel::DisableUiOrShowLoadingDialog() {
// If the current step is showing a dialog, disable it. Else, show the GPM
// Connecting dialog. The native Touch ID control cannot be effectively
// disabled so that sheet is an exception.
if (step() != Step::kPasskeyAutofill &&
(should_dialog_be_closed() || step() == Step::kGPMTouchID)) {
SetStep(Step::kGPMConnecting);
} else {
ui_disabled_ = true;
OnSheetModelChanged();
}
}
bool AuthenticatorRequestDialogModel::should_dialog_be_closed() const {
return step_ui_type(step_) != StepUIType::DIALOG;
}
std::optional<AccountInfo>
AuthenticatorRequestDialogModel::GetGpmAccountInfo() {
Profile* profile = GetProfile();
if (!profile) {
return std::nullopt;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
if (!identity_manager) {
return std::nullopt;
}
CoreAccountInfo core_account_info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
CHECK(!core_account_info.IsEmpty());
return identity_manager->FindExtendedAccountInfo(core_account_info);
}
std::string AuthenticatorRequestDialogModel::GetGpmAccountEmail() {
std::optional<AccountInfo> account_info = GetGpmAccountInfo();
if (!account_info) {
return "";
}
return account_info->email;
}
Profile* AuthenticatorRequestDialogModel::GetProfile() {
content::RenderFrameHost* rfh =
content::RenderFrameHost::FromID(*frame_host_id);
if (!rfh) {
return nullptr;
}
return Profile::FromBrowserContext(rfh->GetBrowserContext());
}
#define AUTHENTICATOR_REQUEST_EVENT_0(name) \
void AuthenticatorRequestDialogModel::name() { \
const int start_generation = this->generation; \
for (auto& observer : observers) { \
if (start_generation != this->generation) { \
break; \
} \
observer.name(); \
} \
}
#define AUTHENTICATOR_REQUEST_EVENT_1(name, arg1type) \
void AuthenticatorRequestDialogModel::name(arg1type arg1) { \
const int start_generation = this->generation; \
for (auto& observer : observers) { \
if (start_generation != this->generation) { \
break; \
} \
observer.name(arg1); \
} \
}
AUTHENTICATOR_EVENTS
#undef AUTHENTICATOR_REQUEST_EVENT_0
#undef AUTHENTICATOR_REQUEST_EVENT_1
std::ostream& operator<<(std::ostream& os,
const AuthenticatorRequestDialogModel::Step& step) {
using Step = AuthenticatorRequestDialogModel::Step;
constexpr auto kStepNames = base::MakeFixedFlatMap<Step, std::string_view>({
{Step::kNotStarted, "kNotStarted"},
{Step::kPasskeyAutofill, "kPasskeyAutofill"},
{Step::kPasskeyUpgrade, "kPasskeyUpgrade"},
{Step::kMechanismSelection, "kMechanismSelection"},
{Step::kErrorNoAvailableTransports, "kErrorNoAvailableTransports"},
{Step::kErrorNoPasskeys, "kErrorNoPasskeys"},
{Step::kErrorInternalUnrecognized, "kErrorInternalUnrecognized"},
{Step::kErrorWindowsHelloNotEnabled, "kErrorWindowsHelloNotEnabled"},
{Step::kTimedOut, "kTimedOut"},
{Step::kKeyNotRegistered, "kKeyNotRegistered"},
{Step::kKeyAlreadyRegistered, "kKeyAlreadyRegistered"},
{Step::kMissingCapability, "kMissingCapability"},
{Step::kStorageFull, "kStorageFull"},
{Step::kClosed, "kClosed"},
{Step::kUsbInsertAndActivate, "kUsbInsertAndActivate"},
{Step::kBlePowerOnAutomatic, "kBlePowerOnAutomatic"},
{Step::kBlePowerOnManual, "kBlePowerOnManual"},
{Step::kBlePermissionMac, "kBlePermissionMac"},
{Step::kOffTheRecordInterstitial, "kOffTheRecordInterstitial"},
{Step::kCableActivate, "kCableActivate"},
{Step::kCableV2QRCode, "kCableV2QRCode"},
{Step::kCableV2Connecting, "kCableV2Connecting"},
{Step::kCableV2Connected, "kCableV2Connected"},
{Step::kCableV2Error, "kCableV2Error"},
{Step::kClientPinChange, "kClientPinChange"},
{Step::kClientPinEntry, "kClientPinEntry"},
{Step::kClientPinSetup, "kClientPinSetup"},
{Step::kClientPinTapAgain, "kClientPinTapAgain"},
{Step::kClientPinErrorSoftBlock, "kClientPinErrorSoftBlock"},
{Step::kClientPinErrorHardBlock, "kClientPinErrorHardBlock"},
{Step::kClientPinErrorAuthenticatorRemoved,
"kClientPinErrorAuthenticatorRemoved"},
{Step::kInlineBioEnrollment, "kInlineBioEnrollment"},
{Step::kRetryInternalUserVerification, "kRetryInternalUserVerification"},
{Step::kResidentCredentialConfirmation,
"kResidentCredentialConfirmation"},
{Step::kSelectAccount, "kSelectAccount"},
{Step::kPreSelectAccount, "kPreSelectAccount"},
{Step::kSelectPriorityMechanism, "kSelectPriorityMechanism"},
{Step::kGPMChangePin, "kGPMChangePin"},
{Step::kGPMCreatePin, "kGPMCreatePin"},
{Step::kGPMEnterPin, "kGPMEnterPin"},
{Step::kGPMChangeArbitraryPin, "kGPMChangeArbitraryPin"},
{Step::kGPMCreateArbitraryPin, "kGPMCreateArbitraryPin"},
{Step::kGPMEnterArbitraryPin, "kGPMEnterArbitraryPin"},
{Step::kGPMTouchID, "kGPMTouchID"},
{Step::kGPMCreatePasskey, "kGPMCreatePasskey"},
{Step::kGPMConfirmOffTheRecordCreate, "kGPMConfirmOffTheRecordCreate"},
{Step::kCreatePasskey, "kCreatePasskey"},
{Step::kGPMError, "kGPMError"},
{Step::kGPMConnecting, "kGPMConnecting"},
{Step::kRecoverSecurityDomain, "kRecoverSecurityDomain"},
{Step::kTrustThisComputerAssertion, "kTrustThisComputerAssertion"},
{Step::kTrustThisComputerCreation, "kTrustThisComputerCreation"},
{Step::kGPMReauthForPinReset, "kGPMReauthForPinReset"},
{Step::kGPMLockedPin, "kGPMLockedPin"},
{Step::kErrorFetchingChallenge, "kErrorFetchingChallenge"},
{Step::kPasswordOsAuth, "kPasswordAuth"},
});
static_assert(Step::kMaxValue == Step::kPasswordOsAuth &&
kStepNames.size() - 1 == static_cast<int>(Step::kMaxValue),
"implement operator<< overload when adding new Step values");
return os << kStepNames.at(step);
}
AuthenticatorRequestDialogModel::Mechanism::Mechanism(
AuthenticatorRequestDialogModel::Mechanism::Type in_type,
std::u16string in_name,
std::u16string in_short_name,
const gfx::VectorIcon& in_icon,
base::RepeatingClosure in_callback,
std::u16string in_display_name)
: type(std::move(in_type)),
name(std::move(in_name)),
short_name(std::move(in_short_name)),
display_name(std::move(in_display_name)),
icon(in_icon),
callback(std::move(in_callback)) {}
AuthenticatorRequestDialogModel::Mechanism::~Mechanism() = default;
AuthenticatorRequestDialogModel::Mechanism::Mechanism(Mechanism&&) = default;
AuthenticatorRequestDialogModel::Mechanism::CredentialInfo::CredentialInfo(
device::AuthenticatorType source_in,
std::vector<uint8_t> user_id_in,
std::optional<base::Time> last_used_time_in)
: source(source_in),
user_id(std::move(user_id_in)),
last_used_time(last_used_time_in) {}
AuthenticatorRequestDialogModel::Mechanism::CredentialInfo::CredentialInfo(
const CredentialInfo&) = default;
AuthenticatorRequestDialogModel::Mechanism::CredentialInfo::~CredentialInfo() =
default;
bool AuthenticatorRequestDialogModel::Mechanism::CredentialInfo::operator==(
const CredentialInfo&) const = default;
AuthenticatorRequestDialogModel::Mechanism::PasswordInfo::PasswordInfo(
std::optional<base::Time> last_used_time_in)
: last_used_time(std::move(last_used_time_in)) {}
AuthenticatorRequestDialogModel::Mechanism::PasswordInfo::PasswordInfo(
const PasswordInfo&) = default;
AuthenticatorRequestDialogModel::Mechanism::PasswordInfo::~PasswordInfo() =
default;
bool AuthenticatorRequestDialogModel::Mechanism::PasswordInfo::operator==(
const PasswordInfo&) const = default;