Commit 262a14ce authored by Balazs Engedy's avatar Balazs Engedy Committed by Commit Bot

Introduce sticky post mortem states on WebAuthn UI.

After a timeout, as well as after activating the wrong key (not registered,
already registered), show an error sheet even after the promise is already
rejected.

Bug: 849323
Change-Id: I08c7403a0fae9c5cf19b6f775f6845bd28e8c6ed
Reviewed-on: https://chromium-review.googlesource.com/1195371
Commit-Queue: Balazs Engedy <engedy@chromium.org>
Reviewed-by: default avatarKim Paulhamus <kpaulhamus@chromium.org>
Reviewed-by: default avatarMartin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/master@{#587528}
parent 245b8ffc
...@@ -104,7 +104,7 @@ class AuthenticatorDialogViewTest : public DialogBrowserTest { ...@@ -104,7 +104,7 @@ class AuthenticatorDialogViewTest : public DialogBrowserTest {
auto dialog_model = std::make_unique<AuthenticatorRequestDialogModel>(); auto dialog_model = std::make_unique<AuthenticatorRequestDialogModel>();
dialog_model->SetCurrentStep( dialog_model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kErrorTimedOut); AuthenticatorRequestDialogModel::Step::kPostMortemTimedOut);
auto dialog = std::make_unique<AuthenticatorRequestDialogView>( auto dialog = std::make_unique<AuthenticatorRequestDialogView>(
web_contents, std::move(dialog_model)); web_contents, std::move(dialog_model));
......
...@@ -97,6 +97,34 @@ bool AuthenticatorRequestDialogView::Cancel() { ...@@ -97,6 +97,34 @@ bool AuthenticatorRequestDialogView::Cancel() {
} }
bool AuthenticatorRequestDialogView::Close() { bool AuthenticatorRequestDialogView::Close() {
// To keep the UI responsive, always allow immediately closing the dialog when
// desired; but still trigger cancelling the AuthenticatorRequest unless it is
// already complete.
//
// Note that on most sheets, cancelling will immediately destroy the request,
// so this method will be re-entered like so:
//
// AuthenticatorRequestDialogView::Close()
// views::DialogClientView::CanClose()
// views::Widget::Close()
// AuthenticatorRequestDialogView::OnStepTransition()
// AuthenticatorRequestDialogModel::SetCurrentStep()
// AuthenticatorRequestDialogModel::OnRequestComplete()
// ChromeAuthenticatorRequestDelegate::~ChromeAuthenticatorRequestDelegate()
// content::AuthenticatorImpl::InvokeCallbackAndCleanup()
// content::AuthenticatorImpl::FailWithNotAllowedErrorAndCleanup()
// <<invoke callback>>
// ChromeAuthenticatorRequestDelegate::OnCancelRequest()
// AuthenticatorRequestDialogModel::Cancel()
// AuthenticatorRequestDialogView::Cancel()
// AuthenticatorRequestDialogView::Close() [initial call]
//
// This should not be a problem as the native widget will never synchronously
// close and hence not synchronously destroy the model while it's iterating
// over observers in SetCurrentStep().
if (!model_->is_request_complete())
Cancel();
return true; return true;
} }
...@@ -167,8 +195,7 @@ void AuthenticatorRequestDialogView::OnModelDestroyed() { ...@@ -167,8 +195,7 @@ void AuthenticatorRequestDialogView::OnModelDestroyed() {
void AuthenticatorRequestDialogView::OnStepTransition() { void AuthenticatorRequestDialogView::OnStepTransition() {
ReplaceCurrentSheetWith(CreateSheetViewForCurrentStepOf(model_.get())); ReplaceCurrentSheetWith(CreateSheetViewForCurrentStepOf(model_.get()));
if (model_->current_step() == if (model_->should_dialog_be_closed()) {
AuthenticatorRequestDialogModel::Step::kCompleted) {
if (!GetWidget()) if (!GetWidget())
return; return;
GetWidget()->Close(); GetWidget()->Close();
......
...@@ -48,20 +48,20 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf( ...@@ -48,20 +48,20 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf(
std::make_unique<AuthenticatorInsertAndActivateUsbSheetModel>( std::make_unique<AuthenticatorInsertAndActivateUsbSheetModel>(
dialog_model)); dialog_model));
break; break;
case Step::kErrorTimedOut:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<AuthenticatorTimeoutErrorModel>(dialog_model));
break;
case Step::kErrorNoAvailableTransports: case Step::kErrorNoAvailableTransports:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>( sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<AuthenticatorNoAvailableTransportsErrorModel>( std::make_unique<AuthenticatorNoAvailableTransportsErrorModel>(
dialog_model)); dialog_model));
break; break;
case Step::kErrorKeyNotRegistered: case Step::kPostMortemTimedOut:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<AuthenticatorTimeoutErrorModel>(dialog_model));
break;
case Step::kPostMortemKeyNotRegistered:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>( sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<AuthenticatorNotRegisteredErrorModel>(dialog_model)); std::make_unique<AuthenticatorNotRegisteredErrorModel>(dialog_model));
break; break;
case Step::kErrorKeyAlreadyRegistered: case Step::kPostMortemKeyAlreadyRegistered:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>( sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<AuthenticatorAlreadyRegisteredErrorModel>( std::make_unique<AuthenticatorAlreadyRegisteredErrorModel>(
dialog_model)); dialog_model));
...@@ -112,7 +112,7 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf( ...@@ -112,7 +112,7 @@ std::unique_ptr<AuthenticatorRequestSheetView> CreateSheetViewForCurrentStepOf(
std::make_unique<AuthenticatorPaaskSheetModel>(dialog_model)); std::make_unique<AuthenticatorPaaskSheetModel>(dialog_model));
break; break;
case Step::kNotStarted: case Step::kNotStarted:
case Step::kCompleted: case Step::kClosed:
case Step::kBlePowerOnAutomatic: case Step::kBlePowerOnAutomatic:
sheet_view = std::make_unique<AuthenticatorRequestSheetView>( sheet_view = std::make_unique<AuthenticatorRequestSheetView>(
std::make_unique<PlaceholderSheetModel>(dialog_model)); std::make_unique<PlaceholderSheetModel>(dialog_model));
......
...@@ -31,8 +31,8 @@ class AuthenticatorDialogTest : public DialogBrowserTest { ...@@ -31,8 +31,8 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
model->StartFlow(std::move(transport_availability), base::nullopt); model->StartFlow(std::move(transport_availability), base::nullopt);
// The dialog should immediately close as soon as it is displayed. // The dialog should immediately close as soon as it is displayed.
if (name == "completed") { if (name == "closed") {
model->SetCurrentStep(AuthenticatorRequestDialogModel::Step::kCompleted); model->SetCurrentStep(AuthenticatorRequestDialogModel::Step::kClosed);
} else if (name == "transports") { } else if (name == "transports") {
model->SetCurrentStep( model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kTransportSelection); AuthenticatorRequestDialogModel::Step::kTransportSelection);
...@@ -41,16 +41,16 @@ class AuthenticatorDialogTest : public DialogBrowserTest { ...@@ -41,16 +41,16 @@ class AuthenticatorDialogTest : public DialogBrowserTest {
AuthenticatorRequestDialogModel::Step::kUsbInsertAndActivate); AuthenticatorRequestDialogModel::Step::kUsbInsertAndActivate);
} else if (name == "timeout") { } else if (name == "timeout") {
model->SetCurrentStep( model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kErrorTimedOut); AuthenticatorRequestDialogModel::Step::kPostMortemTimedOut);
} else if (name == "no_available_transports") { } else if (name == "no_available_transports") {
model->SetCurrentStep( model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kErrorNoAvailableTransports); AuthenticatorRequestDialogModel::Step::kErrorNoAvailableTransports);
} else if (name == "key_not_registered") { } else if (name == "key_not_registered") {
model->SetCurrentStep( model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kErrorKeyNotRegistered); AuthenticatorRequestDialogModel::Step::kPostMortemKeyNotRegistered);
} else if (name == "key_already_registered") { } else if (name == "key_already_registered") {
model->SetCurrentStep( model->SetCurrentStep(AuthenticatorRequestDialogModel::Step::
AuthenticatorRequestDialogModel::Step::kErrorKeyAlreadyRegistered); kPostMortemKeyAlreadyRegistered);
} else if (name == "ble_power_on_manual") { } else if (name == "ble_power_on_manual") {
model->SetCurrentStep( model->SetCurrentStep(
AuthenticatorRequestDialogModel::Step::kBlePowerOnManual); AuthenticatorRequestDialogModel::Step::kBlePowerOnManual);
...@@ -94,7 +94,7 @@ IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_default) { ...@@ -94,7 +94,7 @@ IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_default) {
ShowAndVerifyUi(); ShowAndVerifyUi();
} }
IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_completed) { IN_PROC_BROWSER_TEST_F(AuthenticatorDialogTest, InvokeUi_closed) {
ShowAndVerifyUi(); ShowAndVerifyUi();
} }
......
...@@ -190,6 +190,14 @@ AuthenticatorInsertAndActivateUsbSheetModel::GetOtherTransportsMenuModel() { ...@@ -190,6 +190,14 @@ AuthenticatorInsertAndActivateUsbSheetModel::GetOtherTransportsMenuModel() {
// AuthenticatorTimeoutErrorModel --------------------------------------------- // AuthenticatorTimeoutErrorModel ---------------------------------------------
bool AuthenticatorTimeoutErrorModel::IsBackButtonVisible() const {
return false;
}
base::string16 AuthenticatorTimeoutErrorModel::GetCancelButtonLabel() const {
return l10n_util::GetStringUTF16(IDS_CLOSE);
}
gfx::ImageSkia* AuthenticatorTimeoutErrorModel::GetStepIllustration() const { gfx::ImageSkia* AuthenticatorTimeoutErrorModel::GetStepIllustration() const {
return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR); return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR);
} }
...@@ -231,6 +239,15 @@ AuthenticatorNoAvailableTransportsErrorModel::GetStepDescription() const { ...@@ -231,6 +239,15 @@ AuthenticatorNoAvailableTransportsErrorModel::GetStepDescription() const {
// AuthenticatorNotRegisteredErrorModel --------------------------------------- // AuthenticatorNotRegisteredErrorModel ---------------------------------------
bool AuthenticatorNotRegisteredErrorModel::IsBackButtonVisible() const {
return false;
}
base::string16 AuthenticatorNotRegisteredErrorModel::GetCancelButtonLabel()
const {
return l10n_util::GetStringUTF16(IDS_CLOSE);
}
gfx::ImageSkia* AuthenticatorNotRegisteredErrorModel::GetStepIllustration() gfx::ImageSkia* AuthenticatorNotRegisteredErrorModel::GetStepIllustration()
const { const {
return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR); return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR);
...@@ -246,24 +263,17 @@ base::string16 AuthenticatorNotRegisteredErrorModel::GetStepDescription() ...@@ -246,24 +263,17 @@ base::string16 AuthenticatorNotRegisteredErrorModel::GetStepDescription()
IDS_WEBAUTHN_ERROR_WRONG_KEY_SIGN_DESCRIPTION); IDS_WEBAUTHN_ERROR_WRONG_KEY_SIGN_DESCRIPTION);
} }
bool AuthenticatorNotRegisteredErrorModel::IsAcceptButtonVisible() const { // AuthenticatorAlreadyRegisteredErrorModel -----------------------------------
return true;
}
bool AuthenticatorNotRegisteredErrorModel::IsAcceptButtonEnabled() const { bool AuthenticatorAlreadyRegisteredErrorModel::IsBackButtonVisible() const {
return true; return false;
} }
base::string16 AuthenticatorNotRegisteredErrorModel::GetAcceptButtonLabel() base::string16 AuthenticatorAlreadyRegisteredErrorModel::GetCancelButtonLabel()
const { const {
// TODO(engedy): This should use a separate string resource. return l10n_util::GetStringUTF16(IDS_CLOSE);
return l10n_util::GetStringUTF16(IDS_WEBAUTHN_BLUETOOTH_POWER_ON_MANUAL_NEXT);
} }
void AuthenticatorNotRegisteredErrorModel::OnAccept() {}
// AuthenticatorAlreadyRegisteredErrorModel -----------------------------------
gfx::ImageSkia* AuthenticatorAlreadyRegisteredErrorModel::GetStepIllustration() gfx::ImageSkia* AuthenticatorAlreadyRegisteredErrorModel::GetStepIllustration()
const { const {
return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR); return GetImage(IDR_WEBAUTHN_ILLUSTRATION_ERROR);
...@@ -279,22 +289,6 @@ base::string16 AuthenticatorAlreadyRegisteredErrorModel::GetStepDescription() ...@@ -279,22 +289,6 @@ base::string16 AuthenticatorAlreadyRegisteredErrorModel::GetStepDescription()
IDS_WEBAUTHN_ERROR_WRONG_KEY_REGISTER_DESCRIPTION); IDS_WEBAUTHN_ERROR_WRONG_KEY_REGISTER_DESCRIPTION);
} }
bool AuthenticatorAlreadyRegisteredErrorModel::IsAcceptButtonVisible() const {
return true;
}
bool AuthenticatorAlreadyRegisteredErrorModel::IsAcceptButtonEnabled() const {
return true;
}
base::string16 AuthenticatorAlreadyRegisteredErrorModel::GetAcceptButtonLabel()
const {
// TODO(engedy): This should use a separate string resource.
return l10n_util::GetStringUTF16(IDS_WEBAUTHN_BLUETOOTH_POWER_ON_MANUAL_NEXT);
}
void AuthenticatorAlreadyRegisteredErrorModel::OnAccept() {}
// AuthenticatorInternalUnrecognizedErrorSheetModel // AuthenticatorInternalUnrecognizedErrorSheetModel
// ----------------------------------- // -----------------------------------
......
...@@ -118,6 +118,8 @@ class AuthenticatorTimeoutErrorModel : public AuthenticatorSheetModelBase { ...@@ -118,6 +118,8 @@ class AuthenticatorTimeoutErrorModel : public AuthenticatorSheetModelBase {
private: private:
// AuthenticatorSheetModelBase: // AuthenticatorSheetModelBase:
bool IsBackButtonVisible() const override;
base::string16 GetCancelButtonLabel() const override;
gfx::ImageSkia* GetStepIllustration() const override; gfx::ImageSkia* GetStepIllustration() const override;
base::string16 GetStepTitle() const override; base::string16 GetStepTitle() const override;
base::string16 GetStepDescription() const override; base::string16 GetStepDescription() const override;
...@@ -144,13 +146,11 @@ class AuthenticatorNotRegisteredErrorModel ...@@ -144,13 +146,11 @@ class AuthenticatorNotRegisteredErrorModel
private: private:
// AuthenticatorSheetModelBase: // AuthenticatorSheetModelBase:
bool IsBackButtonVisible() const override;
base::string16 GetCancelButtonLabel() const override;
gfx::ImageSkia* GetStepIllustration() const override; gfx::ImageSkia* GetStepIllustration() const override;
base::string16 GetStepTitle() const override; base::string16 GetStepTitle() const override;
base::string16 GetStepDescription() const override; base::string16 GetStepDescription() const override;
bool IsAcceptButtonVisible() const override;
bool IsAcceptButtonEnabled() const override;
base::string16 GetAcceptButtonLabel() const override;
void OnAccept() override;
}; };
class AuthenticatorAlreadyRegisteredErrorModel class AuthenticatorAlreadyRegisteredErrorModel
...@@ -160,13 +160,11 @@ class AuthenticatorAlreadyRegisteredErrorModel ...@@ -160,13 +160,11 @@ class AuthenticatorAlreadyRegisteredErrorModel
private: private:
// AuthenticatorSheetModelBase: // AuthenticatorSheetModelBase:
bool IsBackButtonVisible() const override;
base::string16 GetCancelButtonLabel() const override;
gfx::ImageSkia* GetStepIllustration() const override; gfx::ImageSkia* GetStepIllustration() const override;
base::string16 GetStepTitle() const override; base::string16 GetStepTitle() const override;
base::string16 GetStepDescription() const override; base::string16 GetStepDescription() const override;
bool IsAcceptButtonVisible() const override;
bool IsAcceptButtonEnabled() const override;
base::string16 GetAcceptButtonLabel() const override;
void OnAccept() override;
}; };
class AuthenticatorInternalUnrecognizedErrorSheetModel class AuthenticatorInternalUnrecognizedErrorSheetModel
......
...@@ -210,6 +210,11 @@ void AuthenticatorRequestDialogModel::StartTouchIdFlow() { ...@@ -210,6 +210,11 @@ void AuthenticatorRequestDialogModel::StartTouchIdFlow() {
} }
void AuthenticatorRequestDialogModel::Cancel() { void AuthenticatorRequestDialogModel::Cancel() {
if (is_request_complete()) {
SetCurrentStep(Step::kClosed);
return;
}
for (auto& observer : observers_) for (auto& observer : observers_)
observer.OnCancelRequest(); observer.OnCancelRequest();
} }
...@@ -231,11 +236,25 @@ void AuthenticatorRequestDialogModel::RemoveObserver(Observer* observer) { ...@@ -231,11 +236,25 @@ void AuthenticatorRequestDialogModel::RemoveObserver(Observer* observer) {
} }
void AuthenticatorRequestDialogModel::OnRequestComplete() { void AuthenticatorRequestDialogModel::OnRequestComplete() {
SetCurrentStep(Step::kCompleted); DCHECK_NE(current_step(), Step::kClosed);
if (is_showing_post_mortem())
return;
SetCurrentStep(Step::kClosed);
} }
void AuthenticatorRequestDialogModel::OnRequestTimeout() { void AuthenticatorRequestDialogModel::OnRequestTimeout() {
SetCurrentStep(Step::kErrorTimedOut); DCHECK(!is_request_complete());
SetCurrentStep(Step::kPostMortemTimedOut);
}
void AuthenticatorRequestDialogModel::OnActivatedKeyNotRegistered() {
DCHECK(!is_request_complete());
SetCurrentStep(Step::kPostMortemKeyNotRegistered);
}
void AuthenticatorRequestDialogModel::OnActivatedKeyAlreadyRegistered() {
DCHECK(!is_request_complete());
SetCurrentStep(Step::kPostMortemKeyAlreadyRegistered);
} }
void AuthenticatorRequestDialogModel::OnBluetoothPoweredStateChanged( void AuthenticatorRequestDialogModel::OnBluetoothPoweredStateChanged(
......
...@@ -34,12 +34,19 @@ class AuthenticatorRequestDialogModel { ...@@ -34,12 +34,19 @@ class AuthenticatorRequestDialogModel {
kWelcomeScreen, kWelcomeScreen,
kTransportSelection, kTransportSelection,
kErrorTimedOut,
// The request is not yet complete, and will only be after user interaction.
kErrorNoAvailableTransports, kErrorNoAvailableTransports,
kErrorKeyNotRegistered,
kErrorKeyAlreadyRegistered,
kErrorInternalUnrecognized, kErrorInternalUnrecognized,
kCompleted,
// The request is already complete, but the dialog should remain open with
// an explaining of what went wrong.
kPostMortemTimedOut,
kPostMortemKeyNotRegistered,
kPostMortemKeyAlreadyRegistered,
// The request is completed, and the dialog should be closed.
kClosed,
// Universal Serial Bus (USB). // Universal Serial Bus (USB).
kUsbInsertAndActivate, kUsbInsertAndActivate,
...@@ -100,6 +107,20 @@ class AuthenticatorRequestDialogModel { ...@@ -100,6 +107,20 @@ class AuthenticatorRequestDialogModel {
void SetCurrentStep(Step step); void SetCurrentStep(Step step);
Step current_step() const { return current_step_; } Step current_step() const { return current_step_; }
bool is_showing_post_mortem() const {
return current_step() == Step::kPostMortemTimedOut ||
current_step() == Step::kPostMortemKeyNotRegistered ||
current_step() == Step::kPostMortemKeyAlreadyRegistered;
}
bool is_request_complete() const {
return is_showing_post_mortem() || current_step() == Step::kClosed;
}
bool should_dialog_be_closed() const {
return current_step() == Step::kClosed;
}
TransportListModel* transport_list_model() { return &transport_list_model_; } TransportListModel* transport_list_model() { return &transport_list_model_; }
const TransportAvailabilityInfo* transport_availability() const { const TransportAvailabilityInfo* transport_availability() const {
return &transport_availability_; return &transport_availability_;
...@@ -188,6 +209,14 @@ class AuthenticatorRequestDialogModel { ...@@ -188,6 +209,14 @@ class AuthenticatorRequestDialogModel {
// To be called when Web Authentication request times-out. // To be called when Web Authentication request times-out.
void OnRequestTimeout(); void OnRequestTimeout();
// To be called when the user activates a security key that does not recognize
// any of the allowed credentials (during a GetAssertion request).
void OnActivatedKeyNotRegistered();
// To be called when the user activates a security key that does recognize
// one of excluded credentials (during a MakeCredential request).
void OnActivatedKeyAlreadyRegistered();
// To be called when the Bluetooth adapter powered state changes. // To be called when the Bluetooth adapter powered state changes.
void OnBluetoothPoweredStateChanged(bool powered); void OnBluetoothPoweredStateChanged(bool powered);
......
...@@ -22,7 +22,24 @@ const base::flat_set<device::FidoTransportProtocol> kAllTransports = { ...@@ -22,7 +22,24 @@ const base::flat_set<device::FidoTransportProtocol> kAllTransports = {
device::FidoTransportProtocol::kInternal, device::FidoTransportProtocol::kInternal,
device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy, device::FidoTransportProtocol::kCloudAssistedBluetoothLowEnergy,
}; };
}
using TransportAvailabilityInfo =
::device::FidoRequestHandlerBase::TransportAvailabilityInfo;
class MockDialogModelObserver
: public testing::StrictMock<AuthenticatorRequestDialogModel::Observer> {
public:
MockDialogModelObserver() = default;
MOCK_METHOD0(OnModelDestroyed, void());
MOCK_METHOD0(OnStepTransition, void());
MOCK_METHOD0(OnCancelRequest, void());
private:
DISALLOW_COPY_AND_ASSIGN(MockDialogModelObserver);
};
} // namespace
class AuthenticatorRequestDialogModelTest : public ::testing::Test { class AuthenticatorRequestDialogModelTest : public ::testing::Test {
public: public:
...@@ -159,7 +176,7 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportAutoSelection) { ...@@ -159,7 +176,7 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportAutoSelection) {
}; };
for (const auto& test_case : kTestCases) { for (const auto& test_case : kTestCases) {
::device::FidoRequestHandlerBase::TransportAvailabilityInfo transports_info; TransportAvailabilityInfo transports_info;
transports_info.request_type = test_case.request_type; transports_info.request_type = test_case.request_type;
transports_info.available_transports = test_case.available_transports; transports_info.available_transports = test_case.available_transports;
transports_info.has_recognized_mac_touch_id_credential = transports_info.has_recognized_mac_touch_id_credential =
...@@ -178,11 +195,11 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportAutoSelection) { ...@@ -178,11 +195,11 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportAutoSelection) {
} }
TEST_F(AuthenticatorRequestDialogModelTest, TransportList) { TEST_F(AuthenticatorRequestDialogModelTest, TransportList) {
::device::FidoRequestHandlerBase::TransportAvailabilityInfo transports_info_1; TransportAvailabilityInfo transports_info;
transports_info_1.available_transports = kAllTransports; transports_info.available_transports = kAllTransports;
AuthenticatorRequestDialogModel model; AuthenticatorRequestDialogModel model;
model.StartFlow(std::move(transports_info_1), base::nullopt); model.StartFlow(std::move(transports_info), base::nullopt);
EXPECT_THAT(model.transport_list_model()->transports(), EXPECT_THAT(model.transport_list_model()->transports(),
::testing::UnorderedElementsAre( ::testing::UnorderedElementsAre(
AuthenticatorTransport::kUsbHumanInterfaceDevice, AuthenticatorTransport::kUsbHumanInterfaceDevice,
...@@ -191,3 +208,71 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportList) { ...@@ -191,3 +208,71 @@ TEST_F(AuthenticatorRequestDialogModelTest, TransportList) {
AuthenticatorTransport::kInternal, AuthenticatorTransport::kInternal,
AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy)); AuthenticatorTransport::kCloudAssistedBluetoothLowEnergy));
} }
TEST_F(AuthenticatorRequestDialogModelTest, NoAvailableTransports) {
using Step = AuthenticatorRequestDialogModel::Step;
MockDialogModelObserver mock_observer;
AuthenticatorRequestDialogModel model;
model.AddObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnStepTransition());
model.StartFlow(TransportAvailabilityInfo(),
AuthenticatorTransport::kInternal);
EXPECT_EQ(Step::kErrorNoAvailableTransports, model.current_step());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnCancelRequest());
model.Cancel();
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnStepTransition());
model.OnRequestComplete();
EXPECT_EQ(Step::kClosed, model.current_step());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnModelDestroyed());
}
TEST_F(AuthenticatorRequestDialogModelTest, PostMortems) {
using Step = AuthenticatorRequestDialogModel::Step;
const struct {
void (AuthenticatorRequestDialogModel::*event)();
Step expected_post_mortem_sheet;
} kTestCases[] = {
{&AuthenticatorRequestDialogModel::OnRequestTimeout,
Step::kPostMortemTimedOut},
{&AuthenticatorRequestDialogModel::OnActivatedKeyNotRegistered,
Step::kPostMortemKeyNotRegistered},
{&AuthenticatorRequestDialogModel::OnActivatedKeyAlreadyRegistered,
Step::kPostMortemKeyAlreadyRegistered},
};
for (const auto& test_case : kTestCases) {
MockDialogModelObserver mock_observer;
AuthenticatorRequestDialogModel model;
model.AddObserver(&mock_observer);
TransportAvailabilityInfo transports_info;
transports_info.available_transports = kAllTransports;
EXPECT_CALL(mock_observer, OnStepTransition());
model.StartFlow(std::move(transports_info), base::nullopt);
EXPECT_EQ(Step::kTransportSelection, model.current_step());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnStepTransition());
(model.*test_case.event)();
model.OnRequestComplete();
EXPECT_EQ(test_case.expected_post_mortem_sheet, model.current_step());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnStepTransition());
model.Cancel();
EXPECT_EQ(Step::kClosed, model.current_step());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
EXPECT_CALL(mock_observer, OnModelDestroyed());
}
}
...@@ -123,6 +123,24 @@ content::BrowserContext* ChromeAuthenticatorRequestDelegate::browser_context() ...@@ -123,6 +123,24 @@ content::BrowserContext* ChromeAuthenticatorRequestDelegate::browser_context()
->GetBrowserContext(); ->GetBrowserContext();
} }
void ChromeAuthenticatorRequestDelegate::DidFailWithInterestingReason(
InterestingFailureReason reason) {
if (!weak_dialog_model_)
return;
switch (reason) {
case InterestingFailureReason::kTimeout:
weak_dialog_model_->OnRequestTimeout();
break;
case InterestingFailureReason::kKeyNotRegistered:
weak_dialog_model_->OnActivatedKeyNotRegistered();
break;
case InterestingFailureReason::kKeyAlreadyRegistered:
weak_dialog_model_->OnActivatedKeyAlreadyRegistered();
break;
}
}
void ChromeAuthenticatorRequestDelegate::RegisterActionCallbacks( void ChromeAuthenticatorRequestDelegate::RegisterActionCallbacks(
base::OnceClosure cancel_callback, base::OnceClosure cancel_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback, device::FidoRequestHandlerBase::RequestCallback request_callback,
......
...@@ -64,6 +64,7 @@ class ChromeAuthenticatorRequestDelegate ...@@ -64,6 +64,7 @@ class ChromeAuthenticatorRequestDelegate
content::BrowserContext* browser_context() const; content::BrowserContext* browser_context() const;
// content::AuthenticatorRequestClientDelegate: // content::AuthenticatorRequestClientDelegate:
void DidFailWithInterestingReason(InterestingFailureReason reason) override;
void RegisterActionCallbacks( void RegisterActionCallbacks(
base::OnceClosure cancel_callback, base::OnceClosure cancel_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback, device::FidoRequestHandlerBase::RequestCallback request_callback,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment