blob: 7c87650dbb42675de81093246e64e2ee96363f16 [file]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/types/expected.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/browser/webid/idp_network_request_manager.h"
#include "content/browser/webid/request.h"
#include "content/browser/webid/request_service.h"
#include "content/browser/webid/test/federated_request_token_callback_helper.h"
#include "content/browser/webid/test/mock_api_permission_delegate.h"
#include "content/browser/webid/test/mock_auto_reauthn_permission_delegate.h"
#include "content/browser/webid/test/mock_identity_registry.h"
#include "content/browser/webid/test/mock_identity_request_dialog_controller.h"
#include "content/browser/webid/test/mock_idp_network_request_manager.h"
#include "content/browser/webid/test/mock_modal_dialog_view_delegate.h"
#include "content/browser/webid/test/mock_permission_delegate.h"
#include "content/common/content_navigation_policy.h"
#include "content/public/browser/webid/identity_request_dialog_controller.h"
#include "content/public/common/content_features.h"
#include "content/public/test/navigation_simulator.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
#include "content/test/test_web_contents.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/http/http_status_code.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/webid/federated_request.mojom.h"
#include "ui/base/page_transition_types.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content::webid {
using ::testing::NiceMock;
using ApiPermissionStatus =
FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using FedCmEntry = ukm::builders::Blink_FedCm;
using FedCmIdpEntry = ukm::builders::Blink_FedCmIdp;
using MediationRequirement = ::password_manager::CredentialMediationRequirement;
using RequestCallbackHelper = FederatedRequestTokenCallbackHelper;
using StartTokenRequestCallback =
blink::mojom::FederatedRequestService::StartTokenRequestCallback;
using blink::mojom::FederatedRequest;
using blink::mojom::FederatedRequestService;
using blink::mojom::RequestTokenStatus;
using blink::mojom::TokenRequestFailurePtr;
using blink::mojom::TokenRequestSuccessPtr;
namespace {
constexpr char kIdpUrl[] = "https://idp.example/";
constexpr char kProviderUrlFull[] = "https://idp.example/fedcm.json";
constexpr char kProviderUrlTwoFull[] = "https://idp-2.example/fedcm.json";
constexpr char kTopFrameUrl[] = "https://top-frame.example/";
constexpr char kAccountsEndpoint[] = "https://idp.example/accounts";
constexpr char kClientMetadataEndpoint[] = "https://idp.example/clientmd";
constexpr char kTokenEndpoint[] = "https://idp.example/token";
constexpr char kLoginUrl[] = "https://idp.example/login";
constexpr char kClientId[] = "client_id_123";
constexpr char kNonce[] = "nonce123";
constexpr char kAccountId[] = "1234";
constexpr char kToken[] = "[not a real token]";
static std::vector<IdentityRequestAccountPtr> kAccounts;
// IdpNetworkRequestManager which returns valid data from IdP.
class TestIdpNetworkRequestManager : public MockIdpNetworkRequestManager {
public:
void SetSendClientIsThirdPartyToTopFrameOrigin(bool value) {
send_client_is_third_party_to_top_frame_origin_ = value;
}
void FetchWellKnown(const GURL& provider,
FetchWellKnownCallback callback) override {
IdpNetworkRequestManager::WellKnown well_known;
std::set<GURL> well_known_configs;
well_known_configs.insert(GURL(kProviderUrlFull));
well_known.provider_urls = std::move(well_known_configs);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), kFetchStatusSuccess, well_known));
}
void FetchConfig(const GURL& provider,
int idp_brand_icon_ideal_size,
int idp_brand_icon_minimum_size,
FetchConfigCallback callback) override {
IdpNetworkRequestManager::Endpoints endpoints;
endpoints.token = GURL(kTokenEndpoint);
endpoints.accounts = GURL(kAccountsEndpoint);
if (send_client_is_third_party_to_top_frame_origin_) {
endpoints.client_metadata = GURL(kClientMetadataEndpoint);
}
IdentityProviderMetadata idp_metadata;
idp_metadata.idp_login_url = GURL(kLoginUrl);
idp_metadata.config_url = provider;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kFetchStatusSuccess,
endpoints, idp_metadata));
}
bool SendAccountsRequest(const url::Origin& idp_origin,
const GURL& accounts_url,
AccountsRequestCallback callback) override {
IdpNetworkRequestManager::AccountsResponse response;
response.accounts = kAccounts;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kFetchStatusSuccess,
std::move(response)));
return true;
}
void SendTokenRequest(
const GURL& token_url,
const std::string& account,
const std::string& url_encoded_post_data,
bool idp_blindness,
TokenRequestCallback callback,
ContinueOnCallback continue_on,
RedirectToCallback redirect_to,
RecordErrorMetricsCallback record_error_metrics_callback) override {
TokenResult result;
result.token = base::Value(kToken);
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kFetchStatusSuccess,
std::move(result)));
}
void FetchClientMetadata(const GURL& endpoint,
const std::string& client_id,
int rp_brand_icon_ideal_size,
int rp_brand_icon_minimum_size,
FetchClientMetadataCallback callback) override {
ClientMetadata client_metadata;
if (send_client_is_third_party_to_top_frame_origin_) {
client_metadata.client_is_third_party_to_top_frame_origin = true;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kFetchStatusSuccess,
client_metadata));
}
private:
// If true, will send `client_is_third_party_to_top_frame_origin: true` in the
// client metadata request.
bool send_client_is_third_party_to_top_frame_origin_{false};
FetchStatus kFetchStatusSuccess{ParseStatus::kSuccess, net::HTTP_OK};
};
class TestDialogController
: public NiceMock<MockIdentityRequestDialogController> {
public:
struct State {
bool did_show_accounts_dialog{false};
std::string rp_for_display;
std::string iframe_for_display;
};
enum class AccountsDialogAction {
kNone,
kSelectAccount,
};
// `state` is a pointer parameter so that it can outlive TestDialogController.
TestDialogController(AccountsDialogAction accounts_dialog_action,
State* state)
: accounts_dialog_action_(accounts_dialog_action), state_(state) {}
~TestDialogController() override = default;
TestDialogController(TestDialogController&) = delete;
TestDialogController& operator=(TestDialogController&) = delete;
bool ShowAccountsDialog(
RelyingPartyData rp_data,
const std::vector<scoped_refptr<IdentityProviderData>>& idp_list,
const std::vector<scoped_refptr<IdentityRequestAccount>>& accounts,
const std::vector<scoped_refptr<IdentityRequestAccount>>&
filtered_accounts,
blink::mojom::RpMode rp_mode,
IdentityRequestDialogController::AccountSelectionCallback on_selected,
IdentityRequestDialogController::LoginToIdPCallback on_add_account,
IdentityRequestDialogController::DismissCallback dismiss_callback,
IdentityRequestDialogController::AccountsDisplayedCallback
accounts_displayed_callback) override {
std::move(accounts_displayed_callback).Run();
state_->did_show_accounts_dialog = true;
state_->rp_for_display = base::UTF16ToUTF8(rp_data.rp_for_display);
state_->iframe_for_display = base::UTF16ToUTF8(rp_data.iframe_for_display);
if (accounts_dialog_action_ == AccountsDialogAction::kSelectAccount) {
std::move(on_selected)
.Run(GURL(kProviderUrlFull), kAccountId, /*is_sign_in=*/true);
}
return true;
}
private:
AccountsDialogAction accounts_dialog_action_{AccountsDialogAction::kNone};
raw_ptr<State> state_;
};
class TestApiPermissionDelegate : public MockApiPermissionDelegate {
public:
ApiPermissionStatus GetApiPermissionStatus(
const url::Origin& origin) override {
return ApiPermissionStatus::GRANTED;
}
};
class TestFederatedIdentityModalDialogViewDelegate
: public NiceMock<MockModalDialogViewDelegate> {
public:
base::WeakPtr<TestFederatedIdentityModalDialogViewDelegate> GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
private:
base::WeakPtrFactory<TestFederatedIdentityModalDialogViewDelegate>
weak_ptr_factory_{this};
};
} // namespace
class RequestMultipleFramesTest : public RenderViewHostImplTestHarness {
protected:
RequestMultipleFramesTest() = default;
~RequestMultipleFramesTest() override = default;
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
// Initialize `kAccounts` on SetUp() to ensure it is initialized correctly
// in every test.
kAccounts = {base::MakeRefCounted<IdentityRequestAccount>(
kAccountId, // id
"ken@idp.example", // display_identifier
"Ken R. Example", // display_name
"ken@idp.example", // email
"Ken R. Example", // name
"Ken", // given_name
GURL(), // picture
"(403) 293-3421", // phone
"@kenr", // username
std::vector<std::string>(), // potentially_approved_site_hashes
std::vector<std::string>(), // login_hints
std::vector<std::string>(), // domain_hints
std::vector<std::string>() // labels
)};
test_api_permission_delegate_ =
std::make_unique<TestApiPermissionDelegate>();
mock_auto_reauthn_permission_delegate_ =
std::make_unique<NiceMock<MockAutoReauthnPermissionDelegate>>();
mock_permission_delegate_ =
std::make_unique<NiceMock<MockPermissionDelegate>>();
ON_CALL(*mock_permission_delegate_, OnSetRequiresUserMediation)
.WillByDefault(base::test::RunOnceCallbackRepeatedly<1>());
test_modal_dialog_view_delegate_ =
std::make_unique<TestFederatedIdentityModalDialogViewDelegate>();
mock_identity_registry_ = std::make_unique<NiceMock<MockIdentityRegistry>>(
web_contents(), test_modal_dialog_view_delegate_->GetWeakPtr(),
GURL(kIdpUrl));
static_cast<TestWebContents*>(web_contents())
->NavigateAndCommit(GURL(kTopFrameUrl), ui::PAGE_TRANSITION_LINK);
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
// Does token request and waits for result.
void DoRequestTokenAndWait(
mojo::Remote<FederatedRequestService>& service_remote,
mojo::Remote<FederatedRequest>& request_remote,
RequestCallbackHelper& callback_helper) {
DoRequestToken(service_remote, request_remote, callback_helper.callback());
request_remote.set_disconnect_handler(callback_helper.quit_closure());
// Ensure that the request makes its way to Request.
base::RunLoop().RunUntilIdle();
// Fast forward clock so that the pending
// Request::OnRejectRequest() task, if any, gets a
// chance to run.
task_environment()->FastForwardBy(base::Minutes(10));
callback_helper.WaitForCallback();
request_remote.set_disconnect_handler(base::OnceClosure());
}
void CreateRequest(
RenderFrameHost& render_frame_host,
mojo::Remote<FederatedRequestService>& service_remote,
TestDialogController::AccountsDialogAction accounts_dialog_action,
TestDialogController::State* dialog_controller_state,
TestIdpNetworkRequestManager** out_network_manager = nullptr) {
RequestService* service =
RequestService::GetOrCreateForCurrentDocument(&render_frame_host);
// Inject the mock permission delegates owned by the test fixture!
service->SetDelegatesForTesting(
test_api_permission_delegate_.get(),
mock_auto_reauthn_permission_delegate_.get(),
mock_permission_delegate_.get(), mock_identity_registry_.get());
service->BindFederatedRequestService(
service_remote.BindNewPipeAndPassReceiver());
service->SetDialogControllerForTests(std::make_unique<TestDialogController>(
accounts_dialog_action, dialog_controller_state));
auto network_manager = std::make_unique<TestIdpNetworkRequestManager>();
if (out_network_manager) {
*out_network_manager = network_manager.get();
}
service->SetNetworkManagerForTests(std::move(network_manager));
}
// Creates a child frame with identity-credentials-get PP delegation
// (equivalent to <iframe allow="identity-credentials-get">).
TestRenderFrameHost* AppendChildFrameWithIdentityCredentialsGetPolicy(
const std::string& frame_name) {
network::ParsedPermissionsPolicy frame_policy(1);
frame_policy[0].feature =
network::mojom::PermissionsPolicyFeature::kIdentityCredentialsGet;
frame_policy[0].matches_all_origins = true;
frame_policy[0].matches_opaque_src = false;
return static_cast<TestRenderFrameHost*>(
web_contents()->GetPrimaryMainFrame())
->AppendChildWithPolicy(frame_name, frame_policy);
}
void DoRequestToken(mojo::Remote<FederatedRequestService>& service_remote,
mojo::Remote<FederatedRequest>& request_remote,
StartTokenRequestCallback callback,
const char* provider = kProviderUrlFull) {
auto config_ptr = blink::mojom::IdentityProviderConfig::New();
config_ptr->config_url = GURL(provider);
config_ptr->client_id = kClientId;
auto federated = blink::mojom::IdentityProviderRequestOptions::New();
federated->config = std::move(config_ptr);
federated->nonce = kNonce;
std::vector<blink::mojom::IdentityProviderRequestOptionsPtr> idp_ptrs;
idp_ptrs.push_back(std::move(federated));
auto get_params = blink::mojom::IdentityProviderGetParameters::New(
std::move(idp_ptrs),
/*rp_context=*/blink::mojom::RpContext::kSignIn,
/*rp_mode=*/blink::mojom::RpMode::kPassive);
std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
idp_get_params.push_back(std::move(get_params));
service_remote->StartTokenRequest(
std::move(idp_get_params), MediationRequirement::kOptional,
request_remote.BindNewPipeAndPassReceiver(), std::move(callback));
service_remote.FlushForTesting();
}
void ExpectUkmValueInEntry(const std::string& metric_name,
const char* entry_name,
int expected_value) {
auto entries = ukm_recorder_->GetEntriesByName(entry_name);
int count = 0;
for (const ukm::mojom::UkmEntry* const entry : entries) {
const int64_t* value = ukm_recorder_->GetEntryMetric(entry, metric_name);
if (!value) {
continue;
}
++count;
}
EXPECT_GT(count, 0) << "Did not find " << metric_name << " in "
<< entry_name;
}
ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); }
protected:
std::unique_ptr<TestApiPermissionDelegate> test_api_permission_delegate_;
std::unique_ptr<NiceMock<MockAutoReauthnPermissionDelegate>>
mock_auto_reauthn_permission_delegate_;
std::unique_ptr<NiceMock<MockPermissionDelegate>> mock_permission_delegate_;
std::unique_ptr<TestFederatedIdentityModalDialogViewDelegate>
test_modal_dialog_view_delegate_;
std::unique_ptr<NiceMock<MockIdentityRegistry>> mock_identity_registry_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
// Test that test harness can execute successful FedCM flow for iframe.
TEST_F(RequestMultipleFramesTest, TestHarness) {
RenderFrameHost* iframe_rfh =
RenderFrameHostTester::For(main_rfh())->AppendChild(/*frame_name=*/"");
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*iframe_rfh, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
EXPECT_EQ(RequestTokenStatus::kSuccess, iframe_callback_helper.status());
EXPECT_TRUE(iframe_dialog_state.did_show_accounts_dialog);
}
// Test that FedCM request fails on iframe if there is an in-progress FedCM
// request for a different frame on the page.
TEST_F(RequestMultipleFramesTest, IframeTooManyRequests) {
base::HistogramTester histogram_tester;
mojo::Remote<FederatedRequestService> main_frame_service_remote;
mojo::Remote<FederatedRequest> main_frame_request_remote;
TestDialogController::State main_frame_dialog_state;
CreateRequest(*main_rfh(), main_frame_service_remote,
TestDialogController::AccountsDialogAction::kNone,
&main_frame_dialog_state);
DoRequestToken(main_frame_service_remote, main_frame_request_remote,
StartTokenRequestCallback());
EXPECT_TRUE(main_frame_dialog_state.did_show_accounts_dialog);
RenderFrameHost* iframe_rfh =
RenderFrameHostTester::For(main_rfh())->AppendChild(/*frame_name=*/"");
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*iframe_rfh, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
EXPECT_EQ(RequestTokenStatus::kErrorTooManyRequests,
iframe_callback_helper.status());
EXPECT_FALSE(iframe_dialog_state.did_show_accounts_dialog);
histogram_tester.ExpectUniqueSample(
"Blink.FedCm.MultipleRequestsFromDifferentIdPs", 0, 1);
}
// Test that when requests from different IdPs get rejected, a proper histogram
// can be recorded.
TEST_F(RequestMultipleFramesTest, IframeTooManyRequestsDifferentIdP) {
base::HistogramTester histogram_tester;
mojo::Remote<FederatedRequestService> main_frame_service_remote;
mojo::Remote<FederatedRequest> main_frame_request_remote;
TestDialogController::State main_frame_dialog_state;
CreateRequest(*main_rfh(), main_frame_service_remote,
TestDialogController::AccountsDialogAction::kNone,
&main_frame_dialog_state);
DoRequestToken(main_frame_service_remote, main_frame_request_remote,
StartTokenRequestCallback());
EXPECT_TRUE(main_frame_dialog_state.did_show_accounts_dialog);
RenderFrameHost* iframe_rfh =
RenderFrameHostTester::For(main_rfh())->AppendChild(/*frame_name=*/"");
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*iframe_rfh, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
// Initiates a new API call with a different IdP.
DoRequestToken(iframe_service_remote, iframe_request_remote,
StartTokenRequestCallback(), kProviderUrlTwoFull);
EXPECT_FALSE(iframe_dialog_state.did_show_accounts_dialog);
histogram_tester.ExpectUniqueSample(
"Blink.FedCm.MultipleRequestsFromDifferentIdPs", 1, 1);
}
// Test that only top frame URL is available for display when FedCM is called
// within iframes which are same-origin with the top frame.
TEST_F(RequestMultipleFramesTest, SameOriginIframe) {
base::HistogramTester histogram_tester;
const char kSameOriginIframeUrl[] = "https://top-frame.example/iframe.html";
RenderFrameHost* same_origin_iframe =
NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kSameOriginIframeUrl),
RenderFrameHostTester::For(web_contents()->GetPrimaryMainFrame())
->AppendChild("same_origin_iframe"));
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*same_origin_iframe, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
ukm_loop.Run();
EXPECT_EQ(RequestTokenStatus::kSuccess, iframe_callback_helper.status());
EXPECT_TRUE(iframe_dialog_state.did_show_accounts_dialog);
EXPECT_EQ("top-frame.example", iframe_dialog_state.rp_for_display);
// Same-origin iframe is treated the same as same-site frame.
histogram_tester.ExpectUniqueSample(
"Blink.FedCm.FrameType",
static_cast<int>(RequesterFrameType::kSameSiteIframe), 1);
ExpectUkmValueInEntry("FrameType", FedCmEntry::kEntryName,
static_cast<int>(RequesterFrameType::kSameSiteIframe));
ExpectUkmValueInEntry("FrameType", FedCmIdpEntry::kEntryName,
static_cast<int>(RequesterFrameType::kSameSiteIframe));
}
// Test that only top frame URL is available for display when FedCM is called
// within iframes which are same-site with the top frame.
TEST_F(RequestMultipleFramesTest, SameSiteIframe) {
base::HistogramTester histogram_tester;
// Same-site but cross-origin (subdomain differs), so
// identity-credentials-get PP delegation is needed because the default
// allowlist is EnableForSelf (same-origin, not same-site).
const char kSameSiteIframeUrl[] =
"https://subdomain.top-frame.example/iframe.html";
RenderFrameHost* same_site_iframe =
NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kSameSiteIframeUrl),
AppendChildFrameWithIdentityCredentialsGetPolicy("same_site_iframe"));
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*same_site_iframe, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
ukm_loop.Run();
EXPECT_EQ(RequestTokenStatus::kSuccess, iframe_callback_helper.status());
EXPECT_TRUE(iframe_dialog_state.did_show_accounts_dialog);
EXPECT_EQ("top-frame.example", iframe_dialog_state.rp_for_display);
histogram_tester.ExpectUniqueSample(
"Blink.FedCm.FrameType",
static_cast<int>(RequesterFrameType::kSameSiteIframe), 1);
ExpectUkmValueInEntry("FrameType", FedCmEntry::kEntryName,
static_cast<int>(RequesterFrameType::kSameSiteIframe));
ExpectUkmValueInEntry("FrameType", FedCmIdpEntry::kEntryName,
static_cast<int>(RequesterFrameType::kSameSiteIframe));
}
// Test that both top frame and iframe URLs are available for display when FedCM
// is called within iframes which are cross-site with the top frame.
TEST_F(RequestMultipleFramesTest, CrossSiteIframe) {
base::HistogramTester histogram_tester;
const char kCrossSiteIframeUrl[] = "https://cross-site.example/iframe.html";
RenderFrameHost* cross_site_iframe =
NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kCrossSiteIframeUrl),
AppendChildFrameWithIdentityCredentialsGetPolicy(
"cross_site_iframe"));
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
CreateRequest(*cross_site_iframe, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state);
base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(FedCmEntry::kEntryName,
ukm_loop.QuitClosure());
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
ukm_loop.Run();
EXPECT_EQ(RequestTokenStatus::kSuccess, iframe_callback_helper.status());
EXPECT_TRUE(iframe_dialog_state.did_show_accounts_dialog);
EXPECT_EQ("top-frame.example", iframe_dialog_state.rp_for_display);
histogram_tester.ExpectUniqueSample(
"Blink.FedCm.FrameType",
static_cast<int>(RequesterFrameType::kCrossSiteIframe), 1);
ExpectUkmValueInEntry("FrameType", FedCmEntry::kEntryName,
static_cast<int>(RequesterFrameType::kCrossSiteIframe));
ExpectUkmValueInEntry("FrameType", FedCmIdpEntry::kEntryName,
static_cast<int>(RequesterFrameType::kCrossSiteIframe));
}
// Tests that we send a client metadata request for cross-site iframes even if
// all accounts are returning.
TEST_F(RequestMultipleFramesTest, CrossSiteIframeSendClientMetadata) {
const char kCrossSiteIframeUrl[] = "https://cross-site.example/iframe.html";
RenderFrameHost* cross_site_iframe =
NavigationSimulator::NavigateAndCommitFromDocument(
GURL(kCrossSiteIframeUrl),
AppendChildFrameWithIdentityCredentialsGetPolicy(
"cross_site_iframe"));
mojo::Remote<FederatedRequestService> iframe_service_remote;
mojo::Remote<FederatedRequest> iframe_request_remote;
TestDialogController::State iframe_dialog_state;
TestIdpNetworkRequestManager* network_manager = nullptr;
CreateRequest(*cross_site_iframe, iframe_service_remote,
TestDialogController::AccountsDialogAction::kSelectAccount,
&iframe_dialog_state, &network_manager);
network_manager->SetSendClientIsThirdPartyToTopFrameOrigin(true);
RequestCallbackHelper iframe_callback_helper;
DoRequestTokenAndWait(iframe_service_remote, iframe_request_remote,
iframe_callback_helper);
EXPECT_EQ(RequestTokenStatus::kSuccess, iframe_callback_helper.status());
EXPECT_TRUE(iframe_dialog_state.did_show_accounts_dialog);
EXPECT_EQ("cross-site.example", iframe_dialog_state.iframe_for_display);
}
} // namespace content::webid