blob: 0d395579f88d9e02a6f28590b0491de843edc6ea [file] [log] [blame]
// Copyright 2024 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/ui/webid/identity_dialog_controller.h"
#include "base/functional/callback_helpers.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "chrome/browser/ui/webid/account_selection_view.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/permissions/permission_request_manager.h"
#include "components/permissions/test/mock_permission_prompt_factory.h"
#include "components/permissions/test/mock_permission_request.h"
#include "content/public/browser/identity_request_account.h"
#include "content/public/browser/identity_request_dialog_controller.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
constexpr char kTopFrameEtldPlusOne[] = "top-frame-example.com";
constexpr char kIdpEtldPlusOne[] = "idp-example.com";
// Mock version of AccountSelectionView for injection during tests.
class MockAccountSelectionView : public AccountSelectionView {
public:
MockAccountSelectionView() : AccountSelectionView(/*delegate=*/nullptr) {}
~MockAccountSelectionView() override = default;
MockAccountSelectionView(const MockAccountSelectionView&) = delete;
MockAccountSelectionView& operator=(const MockAccountSelectionView&) = delete;
MOCK_METHOD(
bool,
Show,
(const std::string& rp_for_display,
const std::vector<content::IdentityProviderData>& identity_provider_data,
Account::SignInMode sign_in_mode,
blink::mojom::RpMode rp_mode,
const std::optional<content::IdentityProviderData>& new_account_idp),
(override));
MOCK_METHOD(bool,
ShowFailureDialog,
(const std::string& rp_for_display,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const content::IdentityProviderMetadata& idp_metadata),
(override));
MOCK_METHOD(bool,
ShowErrorDialog,
(const std::string& rp_for_display,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode,
const content::IdentityProviderMetadata& idp_metadata,
const std::optional<TokenError>& error),
(override));
MOCK_METHOD(bool,
ShowLoadingDialog,
(const std::string& rp_for_display,
const std::string& idp_for_display,
blink::mojom::RpContext rp_context,
blink::mojom::RpMode rp_mode),
(override));
MOCK_METHOD(std::string, GetTitle, (), (const, override));
MOCK_METHOD(std::optional<std::string>, GetSubtitle, (), (const, override));
MOCK_METHOD(void, ShowUrl, (LinkType type, const GURL& url), (override));
MOCK_METHOD(content::WebContents*,
ShowModalDialog,
(const GURL& url, blink::mojom::RpMode rp_mode),
(override));
MOCK_METHOD(void, CloseModalDialog, (), (override));
MOCK_METHOD(content::WebContents*, GetRpWebContents, (), (override));
};
class IdentityDialogControllerTest : public ChromeRenderViewHostTestHarness {
public:
IdentityDialogControllerTest()
: ChromeRenderViewHostTestHarness(
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~IdentityDialogControllerTest() override = default;
IdentityDialogControllerTest(IdentityDialogControllerTest&) = delete;
IdentityDialogControllerTest& operator=(IdentityDialogControllerTest&) =
delete;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
SetContents(CreateTestWebContents());
NavigateAndCommit(GURL(permissions::MockPermissionRequest::kDefaultOrigin));
permissions::PermissionRequestManager::CreateForWebContents(web_contents());
}
void TearDown() override { ChromeRenderViewHostTestHarness::TearDown(); }
void WaitForBubbleToBeShown(permissions::PermissionRequestManager* manager) {
manager->DocumentOnLoadCompletedInPrimaryMainFrame();
task_environment()->RunUntilIdle();
}
void Accept(permissions::PermissionRequestManager* manager) {
manager->Accept();
task_environment()->RunUntilIdle();
}
void Deny(permissions::PermissionRequestManager* manager) {
manager->Deny();
task_environment()->RunUntilIdle();
}
void Dismiss(permissions::PermissionRequestManager* manager) {
manager->Dismiss();
task_environment()->RunUntilIdle();
}
std::vector<content::IdentityRequestAccount> CreateAccount() {
return {
{"account_id1", "", "", "", GURL(),
/*login_hints=*/std::vector<std::string>(),
/*domain_hints=*/std::vector<std::string>(),
/*labels=*/std::vector<std::string>(),
/*login_state=*/content::IdentityRequestAccount::LoginState::kSignUp,
/*browser_trusted_login_state=*/
content::IdentityRequestAccount::LoginState::kSignUp}};
}
content::IdentityProviderData CreateIdentityProviderData(
std::vector<content::IdentityRequestAccount> accounts) {
return {kIdpEtldPlusOne,
accounts,
content::IdentityProviderMetadata(),
content::ClientMetadata(GURL(), GURL(), GURL()),
blink::mojom::RpContext::kSignIn,
/*request_permission=*/true,
/*has_login_status_mismatch=*/false};
}
};
TEST_F(IdentityDialogControllerTest, Accept) {
IdentityDialogController controller(web_contents());
base::MockCallback<base::OnceCallback<void(bool accepted)>> callback;
EXPECT_CALL(callback, Run(true)).WillOnce(testing::Return());
controller.RequestIdPRegistrationPermision(
url::Origin::Create(GURL("https://idp.example")), callback.Get());
auto* manager =
permissions::PermissionRequestManager::FromWebContents(web_contents());
auto prompt_factory =
std::make_unique<permissions::MockPermissionPromptFactory>(manager);
WaitForBubbleToBeShown(manager);
EXPECT_TRUE(prompt_factory->is_visible());
Accept(manager);
EXPECT_FALSE(prompt_factory->is_visible());
}
TEST_F(IdentityDialogControllerTest, Deny) {
IdentityDialogController controller(web_contents());
base::MockCallback<base::OnceCallback<void(bool accepted)>> callback;
EXPECT_CALL(callback, Run(false)).WillOnce(testing::Return());
controller.RequestIdPRegistrationPermision(
url::Origin::Create(GURL("https://idp.example")), callback.Get());
auto* manager =
permissions::PermissionRequestManager::FromWebContents(web_contents());
auto prompt_factory =
std::make_unique<permissions::MockPermissionPromptFactory>(manager);
WaitForBubbleToBeShown(manager);
EXPECT_TRUE(prompt_factory->is_visible());
Deny(manager);
EXPECT_FALSE(prompt_factory->is_visible());
}
TEST_F(IdentityDialogControllerTest, Dismiss) {
IdentityDialogController controller(web_contents());
base::MockCallback<base::OnceCallback<void(bool accepted)>> callback;
EXPECT_CALL(callback, Run(false)).WillOnce(testing::Return());
controller.RequestIdPRegistrationPermision(
url::Origin::Create(GURL("https://idp.example")), callback.Get());
auto* manager =
permissions::PermissionRequestManager::FromWebContents(web_contents());
auto prompt_factory =
std::make_unique<permissions::MockPermissionPromptFactory>(manager);
WaitForBubbleToBeShown(manager);
EXPECT_TRUE(prompt_factory->is_visible());
Dismiss(manager);
EXPECT_FALSE(prompt_factory->is_visible());
}
// Test that selecting an account in button mode, and then dismissing it should
// run the dismiss callback.
TEST_F(IdentityDialogControllerTest, OnAccountSelectedButtonCallsDismiss) {
IdentityDialogController controller(web_contents());
controller.SetAccountSelectionViewForTesting(
std::make_unique<MockAccountSelectionView>());
std::vector<content::IdentityRequestAccount> accounts = CreateAccount();
content::IdentityProviderData idp_data = CreateIdentityProviderData(accounts);
// Dismiss callback should run once.
base::MockCallback<DismissCallback> dismiss_callback;
EXPECT_CALL(dismiss_callback, Run).WillOnce(testing::Return());
// Show button mode accounts dialog.
controller.ShowAccountsDialog(
kTopFrameEtldPlusOne, {idp_data},
content::IdentityRequestAccount::SignInMode::kExplicit,
blink::mojom::RpMode::kButton, /*new_account_idp=*/std::nullopt,
/*on_selected=*/base::DoNothing(), /*on_add_account=*/base::DoNothing(),
/*dismiss_callback=*/dismiss_callback.Get(),
/*accounts_displayed_callback=*/base::DoNothing());
// User selects an account, and then dismisses it. The expectation set for
// dismiss callback should pass.
controller.OnAccountSelected(GURL(kIdpEtldPlusOne), accounts[0]);
controller.OnDismiss(IdentityDialogController::DismissReason::kOther);
}
// Test that selecting an account in widget, and then dismissing it should not
// run the dismiss callback.
TEST_F(IdentityDialogControllerTest, OnAccountSelectedWidgetResetsDismiss) {
IdentityDialogController controller(web_contents());
controller.SetAccountSelectionViewForTesting(
std::make_unique<MockAccountSelectionView>());
std::vector<content::IdentityRequestAccount> accounts = CreateAccount();
content::IdentityProviderData idp_data = CreateIdentityProviderData(accounts);
// Dismiss callback should not be run.
base::MockCallback<DismissCallback> dismiss_callback;
EXPECT_CALL(dismiss_callback, Run).Times(0);
// Show widget mode accounts dialog.
controller.ShowAccountsDialog(
kTopFrameEtldPlusOne, {idp_data},
content::IdentityRequestAccount::SignInMode::kExplicit,
blink::mojom::RpMode::kWidget, /*new_account_idp=*/std::nullopt,
/*on_selected=*/base::DoNothing(), /*on_add_account=*/base::DoNothing(),
/*dismiss_callback=*/dismiss_callback.Get(),
/*accounts_displayed_callback=*/base::DoNothing());
// User selects an account, and then dismisses it. The expectation set for
// dismiss callback should pass.
controller.OnAccountSelected(GURL(kIdpEtldPlusOne), accounts[0]);
controller.OnDismiss(IdentityDialogController::DismissReason::kOther);
}
// Crash test for crbug.com/358302105.
TEST_F(IdentityDialogControllerTest, NoTabDoesNotCrash) {
IdentityDialogController controller(web_contents());
std::vector<content::IdentityRequestAccount> accounts = CreateAccount();
content::IdentityProviderData idp_data = CreateIdentityProviderData(accounts);
// Show button mode accounts dialog.
EXPECT_FALSE(controller.ShowAccountsDialog(
kTopFrameEtldPlusOne, {idp_data},
content::IdentityRequestAccount::SignInMode::kExplicit,
blink::mojom::RpMode::kButton, /*new_account_idp=*/std::nullopt,
/*on_selected=*/base::DoNothing(), /*on_add_account=*/base::DoNothing(),
/*dismiss_callback=*/base::DoNothing(),
/*accounts_displayed_callback=*/base::DoNothing()));
}